Skip to content
Snippets Groups Projects
Commit 67312d11 authored by Joscha Schmiedt's avatar Joscha Schmiedt
Browse files

WIP: Towards construction of a commandline from grpc sss request

parent 704d601d
No related branches found
No related tags found
1 merge request!121Draft: Add server side scripting to gRPC API
Pipeline #62059 failed
......@@ -21,34 +21,47 @@
package org.caosdb.server.grpc;
import com.google.protobuf.Timestamp;
import com.ibm.icu.text.Collator;
import io.grpc.Status;
import io.grpc.StatusException;
import io.grpc.stub.StreamObserver;
import java.util.ArrayList;
import java.util.List;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authz.UnauthorizedException;
import org.apache.shiro.subject.Subject;
import org.caosdb.api.scripting.v1alpha1.ExecuteServerSideScriptRequest;
import org.caosdb.api.scripting.v1alpha1.ExecuteServerSideScriptResponse;
import org.caosdb.api.scripting.v1alpha1.NamedArgument;
import org.caosdb.api.scripting.v1alpha1.ServerSideScriptExecutionId;
import org.caosdb.api.scripting.v1alpha1.ServerSideScriptExecutionResult;
import org.caosdb.api.scripting.v1alpha1.ServerSideScriptingServiceGrpc.ServerSideScriptingServiceImplBase;
import org.caosdb.server.accessControl.AuthenticationUtils;
import org.caosdb.server.accessControl.OneTimeAuthenticationToken;
import org.caosdb.server.accessControl.SessionToken;
import org.caosdb.server.entity.FileProperties;
import org.caosdb.server.entity.Message;
import org.caosdb.server.scripting.ServerSideScriptingCaller;
import org.caosdb.server.utils.ServerMessages;
import org.restlet.data.Form;
import org.restlet.data.Parameter;
/**
* Main entry point for the server-side scripting service of the server's GRPC API.
* Main entry point for the server-side scripting service of the server's GRPC
* API.
*
* @author Joscha Schmiedt <joscha@schmiedt.dev>
*/
public class ServerSideScriptingServiceImpl extends ServerSideScriptingServiceImplBase {
public class ServerSideScriptingServiceImpl
extends ServerSideScriptingServiceImplBase {
private ServerSideScriptingCaller caller;
@Override
public void executeServerSideScript(
ExecuteServerSideScriptRequest request,
io.grpc.stub.StreamObserver<org.caosdb.api.scripting.v1alpha1.ExecuteServerSideScriptResponse>
responseObserver) {
// ServerSideScriptingCaller caller = new ServerSideScriptingCaller()
// caller.executeServerSideScript(request, responseObserver);
io.grpc.stub.StreamObserver<org.caosdb.api.scripting.v1alpha1.ExecuteServerSideScriptResponse> responseObserver) {
try {
AuthInterceptor.bindSubject();
ExecuteServerSideScriptResponse response = executeScript(request);
......@@ -60,12 +73,90 @@ public class ServerSideScriptingServiceImpl extends ServerSideScriptingServiceIm
}
}
private ExecuteServerSideScriptResponse executeScript(ExecuteServerSideScriptRequest request) {
public int callScript(List<String> commandLine, Integer timeoutMs,
List<FileProperties> files, Object authToken)
throws Message {
this.caller = new ServerSideScriptingCaller(
commandLine.toArray(new String[commandLine.size()]), timeoutMs, files,
authToken);
return caller.invoke();
}
public static ArrayList<String> request2CommandLine(ExecuteServerSideScriptRequest request)
throws Message {
if (request.getScriptFilename().length() == 0) {
throw ServerMessages.SERVER_SIDE_SCRIPT_MISSING_CALL;
}
ArrayList<String> commandLine = new ArrayList<>(
1 + request.getPositionalArgumentsCount() + request.getNamedArgumentsCount());
// first item is the script filename
commandLine.add(request.getScriptFilename());
// add named arguments
for (NamedArgument namedArgument : request.getNamedArgumentsList()) {
String name = namedArgument.getName();
String value = namedArgument.getValue();
commandLine.add(String.format("--%s=%s", name, value));
}
// add positional arguments
for (String positionalArgument : request.getPositionalArgumentsList()) {
commandLine.add(positionalArgument);
}
return commandLine;
}
/////////////////////////////////////////////////////////////////////////////////////////
// TODO: isAnonymous, getUser and generateAuthToken are copy-pasted from
// elsewhere. Consider refactoring.
/////////////////////////////////////////////////////////////////////////////////////////
public boolean isAnonymous() {
return AuthenticationUtils.isAnonymous(getUser());
}
public Subject getUser() {
Subject ret = SecurityUtils.getSubject();
return ret;
}
private String[] constructCallStringArray(ArrayList<String> commandLine) {
return commandLine.toArray(new String[commandLine.size()]);
}
public Object generateAuthToken(String call) {
String purpose = "SCRIPTING:EXECUTE:" + call;
Object authtoken = OneTimeAuthenticationToken.generateForPurpose(purpose, getUser());
if (authtoken != null || isAnonymous()) {
return authtoken;
}
return SessionToken.generate(getUser());
}
/////////////////////////////////////////////////////////////////////////////////////////
private ExecuteServerSideScriptResponse executeScript(ExecuteServerSideScriptRequest request) throws Message {
String[] call = constructCallStringArray(request2CommandLine(request));
// construct caller and invoke (using an empty file list for now)
Integer timeoutMs = request.getTimeoutMs() != 0 ? (int) request.getTimeoutMs() : null;
Object authToken = request.getAuthToken();
if (authToken == null) {
authToken = generateAuthToken(request.getScriptFilename());
}
caller = new ServerSideScriptingCaller(call, timeoutMs, new ArrayList<FileProperties>(), authToken);
// request.getCommandLineList(), request.getTimeoutMs(),
// request.getFilesList(), request.getAuthToken());
// caller.invoke();
// response
String scriptExecutionId = "";
String scriptFilename = "";
ServerSideScriptExecutionResult result =
ServerSideScriptExecutionResult.SERVER_SIDE_SCRIPT_EXECUTION_RESULT_GENERAL_FAILURE;
ServerSideScriptExecutionResult result = ServerSideScriptExecutionResult.SERVER_SIDE_SCRIPT_EXECUTION_RESULT_GENERAL_FAILURE;
int returnCode = 1;
String stdout = "";
String stderr = "";
......@@ -73,25 +164,24 @@ public class ServerSideScriptingServiceImpl extends ServerSideScriptingServiceIm
Timestamp endTime = Timestamp.newBuilder().setSeconds(0).setNanos(0).build();
int duration_ms = 0;
final ExecuteServerSideScriptResponse response =
ExecuteServerSideScriptResponse.newBuilder()
.setScriptExecutionId(
ServerSideScriptExecutionId.newBuilder()
.setScriptExecutionId(scriptExecutionId)
.build())
.setScriptFilename(scriptFilename)
.setResult(result)
.setReturnCode(returnCode)
.setStdout(stdout)
.setStderr(stderr)
.setStartTime(startTime)
.setEndTime(endTime)
.setDurationMs(duration_ms)
.build();
final ExecuteServerSideScriptResponse response = ExecuteServerSideScriptResponse.newBuilder()
.setScriptExecutionId(ServerSideScriptExecutionId.newBuilder()
.setScriptExecutionId(scriptExecutionId)
.build())
.setCall(scriptFilename)
.setResult(result)
.setReturnCode(returnCode)
.setStdout(stdout)
.setStderr(stderr)
.setStartTime(startTime)
.setEndTime(endTime)
.setDurationMs(duration_ms)
.build();
return response;
}
public static void handleException(StreamObserver<?> responseObserver, Exception e) {
public static void handleException(StreamObserver<?> responseObserver,
Exception e) {
String description = e.getMessage();
if (description == null || description.isBlank()) {
description = "Unknown Error. Please Report!";
......@@ -99,25 +189,26 @@ public class ServerSideScriptingServiceImpl extends ServerSideScriptingServiceIm
if (e instanceof UnauthorizedException) {
Subject subject = SecurityUtils.getSubject();
if (AuthenticationUtils.isAnonymous(subject)) {
responseObserver.onError(new StatusException(AuthInterceptor.PLEASE_LOG_IN));
responseObserver.onError(
new StatusException(AuthInterceptor.PLEASE_LOG_IN));
return;
} else {
responseObserver.onError(
new StatusException(
Status.PERMISSION_DENIED.withCause(e).withDescription(description)));
responseObserver.onError(new StatusException(
Status.PERMISSION_DENIED.withCause(e).withDescription(
description)));
return;
}
} else if (e == ServerMessages.ROLE_DOES_NOT_EXIST
|| e == ServerMessages.ACCOUNT_DOES_NOT_EXIST) {
responseObserver.onError(
new StatusException(Status.NOT_FOUND.withDescription(description).withCause(e)));
} else if (e == ServerMessages.ROLE_DOES_NOT_EXIST ||
e == ServerMessages.ACCOUNT_DOES_NOT_EXIST) {
responseObserver.onError(new StatusException(
Status.NOT_FOUND.withDescription(description).withCause(e)));
return;
}
// TODO: SERVER_SIDE_DOES_NOT_EXIST, SERVER_SIDE_SCRIPT_NOT_EXECUTABLE,
// SERVER_SIDE_SCRIPT_ERROR, SERVER_SIDE_SCRIPT_SETUP_ERROR,
// SERVER_SIDE_SCRIPT_TIMEOUT, SERVER_SIDE_SCRIPT_MISSING_CALL
e.printStackTrace();
responseObserver.onError(
new StatusException(Status.UNKNOWN.withDescription(description).withCause(e)));
responseObserver.onError(new StatusException(
Status.UNKNOWN.withDescription(description).withCause(e)));
}
}
/*
* This file is a part of the CaosDB Project.
*
* Copyright (C) 2025 IndiScale GmbH <info@indiscale.com>
* Copyright (C) 2025 Joscha Schmiedt <joscha@schmiedt.dev>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.caosdb.server.grpc;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.caosdb.api.scripting.v1alpha1.ExecuteServerSideScriptRequest;
import org.caosdb.api.scripting.v1alpha1.NamedArgument;
// Python Test Case
// response = run_server_side_script("my_script.py",
// "pos0",
// "pos1",
// option1="val1",
// option2="val2",
// files={"-Ofile": "test_file.txt"})
// assert response.stderr is None
// assert response.code == 0
// assert response.call == ('my_script.py '
// '--option1=val1 --option2=val2 --file=.upload_files/test_file.txt '
// 'pos0 pos1')
import org.junit.jupiter.api.Test;
public class ServerSideScriptingGrpcTest {
@Test
public void testExecuteServerSideScriptRequestToCommandline() {
ExecuteServerSideScriptRequest request = ExecuteServerSideScriptRequest.newBuilder()
.setScriptFilename("my_script.py")
.addPositionalArguments("pos0")
.addPositionalArguments("pos1")
.addNamedArguments(NamedArgument.newBuilder().setName("option1").setValue("val1").build())
.addNamedArguments(NamedArgument.newBuilder().setName("option2").setValue("val2").build())
// .putFiles("-Ofile", "test_file.txt")
.build();
ArrayList<String> commandline = ServerSideScriptingServiceImpl.request2CommandLine(request);
// assertEquals(commandline,
// "my_script.py --option1=val1 --option2=val2 --file=.upload_files/test_file.txt pos0 pos1");
// }
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment