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
Branches
No related tags found
1 merge request!121Add server side scripting to gRPC API
Pipeline #62059 failed
...@@ -21,34 +21,47 @@ ...@@ -21,34 +21,47 @@
package org.caosdb.server.grpc; package org.caosdb.server.grpc;
import com.google.protobuf.Timestamp; import com.google.protobuf.Timestamp;
import com.ibm.icu.text.Collator;
import io.grpc.Status; import io.grpc.Status;
import io.grpc.StatusException; import io.grpc.StatusException;
import io.grpc.stub.StreamObserver; import io.grpc.stub.StreamObserver;
import java.util.ArrayList;
import java.util.List;
import org.apache.shiro.SecurityUtils; import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authz.UnauthorizedException; import org.apache.shiro.authz.UnauthorizedException;
import org.apache.shiro.subject.Subject; import org.apache.shiro.subject.Subject;
import org.caosdb.api.scripting.v1alpha1.ExecuteServerSideScriptRequest; import org.caosdb.api.scripting.v1alpha1.ExecuteServerSideScriptRequest;
import org.caosdb.api.scripting.v1alpha1.ExecuteServerSideScriptResponse; 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.ServerSideScriptExecutionId;
import org.caosdb.api.scripting.v1alpha1.ServerSideScriptExecutionResult; import org.caosdb.api.scripting.v1alpha1.ServerSideScriptExecutionResult;
import org.caosdb.api.scripting.v1alpha1.ServerSideScriptingServiceGrpc.ServerSideScriptingServiceImplBase; import org.caosdb.api.scripting.v1alpha1.ServerSideScriptingServiceGrpc.ServerSideScriptingServiceImplBase;
import org.caosdb.server.accessControl.AuthenticationUtils; 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.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> * @author Joscha Schmiedt <joscha@schmiedt.dev>
*/ */
public class ServerSideScriptingServiceImpl extends ServerSideScriptingServiceImplBase { public class ServerSideScriptingServiceImpl
extends ServerSideScriptingServiceImplBase {
private ServerSideScriptingCaller caller;
@Override @Override
public void executeServerSideScript( public void executeServerSideScript(
ExecuteServerSideScriptRequest request, ExecuteServerSideScriptRequest request,
io.grpc.stub.StreamObserver<org.caosdb.api.scripting.v1alpha1.ExecuteServerSideScriptResponse> io.grpc.stub.StreamObserver<org.caosdb.api.scripting.v1alpha1.ExecuteServerSideScriptResponse> responseObserver) {
responseObserver) {
// ServerSideScriptingCaller caller = new ServerSideScriptingCaller()
// caller.executeServerSideScript(request, responseObserver);
try { try {
AuthInterceptor.bindSubject(); AuthInterceptor.bindSubject();
ExecuteServerSideScriptResponse response = executeScript(request); ExecuteServerSideScriptResponse response = executeScript(request);
...@@ -60,12 +73,90 @@ public class ServerSideScriptingServiceImpl extends ServerSideScriptingServiceIm ...@@ -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 scriptExecutionId = "";
String scriptFilename = ""; String scriptFilename = "";
ServerSideScriptExecutionResult result = ServerSideScriptExecutionResult result = ServerSideScriptExecutionResult.SERVER_SIDE_SCRIPT_EXECUTION_RESULT_GENERAL_FAILURE;
ServerSideScriptExecutionResult.SERVER_SIDE_SCRIPT_EXECUTION_RESULT_GENERAL_FAILURE;
int returnCode = 1; int returnCode = 1;
String stdout = ""; String stdout = "";
String stderr = ""; String stderr = "";
...@@ -73,25 +164,24 @@ public class ServerSideScriptingServiceImpl extends ServerSideScriptingServiceIm ...@@ -73,25 +164,24 @@ public class ServerSideScriptingServiceImpl extends ServerSideScriptingServiceIm
Timestamp endTime = Timestamp.newBuilder().setSeconds(0).setNanos(0).build(); Timestamp endTime = Timestamp.newBuilder().setSeconds(0).setNanos(0).build();
int duration_ms = 0; int duration_ms = 0;
final ExecuteServerSideScriptResponse response = final ExecuteServerSideScriptResponse response = ExecuteServerSideScriptResponse.newBuilder()
ExecuteServerSideScriptResponse.newBuilder() .setScriptExecutionId(ServerSideScriptExecutionId.newBuilder()
.setScriptExecutionId( .setScriptExecutionId(scriptExecutionId)
ServerSideScriptExecutionId.newBuilder() .build())
.setScriptExecutionId(scriptExecutionId) .setCall(scriptFilename)
.build()) .setResult(result)
.setScriptFilename(scriptFilename) .setReturnCode(returnCode)
.setResult(result) .setStdout(stdout)
.setReturnCode(returnCode) .setStderr(stderr)
.setStdout(stdout) .setStartTime(startTime)
.setStderr(stderr) .setEndTime(endTime)
.setStartTime(startTime) .setDurationMs(duration_ms)
.setEndTime(endTime) .build();
.setDurationMs(duration_ms)
.build();
return response; return response;
} }
public static void handleException(StreamObserver<?> responseObserver, Exception e) { public static void handleException(StreamObserver<?> responseObserver,
Exception e) {
String description = e.getMessage(); String description = e.getMessage();
if (description == null || description.isBlank()) { if (description == null || description.isBlank()) {
description = "Unknown Error. Please Report!"; description = "Unknown Error. Please Report!";
...@@ -99,25 +189,26 @@ public class ServerSideScriptingServiceImpl extends ServerSideScriptingServiceIm ...@@ -99,25 +189,26 @@ public class ServerSideScriptingServiceImpl extends ServerSideScriptingServiceIm
if (e instanceof UnauthorizedException) { if (e instanceof UnauthorizedException) {
Subject subject = SecurityUtils.getSubject(); Subject subject = SecurityUtils.getSubject();
if (AuthenticationUtils.isAnonymous(subject)) { if (AuthenticationUtils.isAnonymous(subject)) {
responseObserver.onError(new StatusException(AuthInterceptor.PLEASE_LOG_IN)); responseObserver.onError(
new StatusException(AuthInterceptor.PLEASE_LOG_IN));
return; return;
} else { } else {
responseObserver.onError( responseObserver.onError(new StatusException(
new StatusException( Status.PERMISSION_DENIED.withCause(e).withDescription(
Status.PERMISSION_DENIED.withCause(e).withDescription(description))); description)));
return; return;
} }
} else if (e == ServerMessages.ROLE_DOES_NOT_EXIST } else if (e == ServerMessages.ROLE_DOES_NOT_EXIST ||
|| e == ServerMessages.ACCOUNT_DOES_NOT_EXIST) { e == ServerMessages.ACCOUNT_DOES_NOT_EXIST) {
responseObserver.onError( responseObserver.onError(new StatusException(
new StatusException(Status.NOT_FOUND.withDescription(description).withCause(e))); Status.NOT_FOUND.withDescription(description).withCause(e)));
return; return;
} }
// TODO: SERVER_SIDE_DOES_NOT_EXIST, SERVER_SIDE_SCRIPT_NOT_EXECUTABLE, // TODO: SERVER_SIDE_DOES_NOT_EXIST, SERVER_SIDE_SCRIPT_NOT_EXECUTABLE,
// SERVER_SIDE_SCRIPT_ERROR, SERVER_SIDE_SCRIPT_SETUP_ERROR, // SERVER_SIDE_SCRIPT_ERROR, SERVER_SIDE_SCRIPT_SETUP_ERROR,
// SERVER_SIDE_SCRIPT_TIMEOUT, SERVER_SIDE_SCRIPT_MISSING_CALL // SERVER_SIDE_SCRIPT_TIMEOUT, SERVER_SIDE_SCRIPT_MISSING_CALL
e.printStackTrace(); e.printStackTrace();
responseObserver.onError( responseObserver.onError(new StatusException(
new StatusException(Status.UNKNOWN.withDescription(description).withCause(e))); 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.
Please register or to comment