io.bdeploy.jersey.cli.RemoteServiceTool Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of api Show documentation
Show all versions of api Show documentation
Public API including dependencies, ready to be used for integrations and plugins.
package io.bdeploy.jersey.cli;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Method;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import io.bdeploy.common.cfg.Configuration.EnvironmentFallback;
import io.bdeploy.common.cfg.Configuration.Help;
import io.bdeploy.common.cfg.Configuration.Validator;
import io.bdeploy.common.cfg.ExistingPathValidator;
import io.bdeploy.common.cfg.RemoteValidator;
import io.bdeploy.common.cli.ToolBase;
import io.bdeploy.common.cli.ToolBase.ConfiguredCliTool;
import io.bdeploy.common.cli.data.RenderableResult;
import io.bdeploy.common.security.OnDiscKeyStore;
import io.bdeploy.common.security.RemoteService;
import jakarta.ws.rs.core.SecurityContext;
import jakarta.ws.rs.core.UriBuilder;
/**
* Base class for all CLI tools which need to access a remote service. Figuring
* out the {@link RemoteService} based on command line arguments is done entirely
* here.
*/
public abstract class RemoteServiceTool extends ConfiguredCliTool {
@Help("Configuration for remote access")
private @interface RemoteConfig {
@Help("URI of remote BHive. Supports file:, jar:file:")
@EnvironmentFallback("BDEPLOY_REMOTE")
@Validator(RemoteValidator.class)
String remote();
@Help("Path to keystore containing access token.")
@Validator(ExistingPathValidator.class)
String keystore();
@Help("Passphrase for the keystore.")
String passphrase();
@Help("Token for the remote access. Can be given alternatively to a keystore.")
@EnvironmentFallback("BDEPLOY_TOKEN")
String token();
@Help("Path to a file containing the access token. Can be given alternatively to a keystore.")
@EnvironmentFallback("BDEPLOY_TOKENFILE")
@Validator(ExistingPathValidator.class)
String tokenFile();
@Help("Override which named login session to use for this command.")
@EnvironmentFallback("BDEPLOY_LOGIN")
String useLogin();
@Help("Path to the directory which contains the local login data")
@EnvironmentFallback("BDEPLOY_LOGIN_STORAGE")
String loginStorage();
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
protected @interface RemoteOptional {
}
protected RemoteServiceTool(Class configClass) {
super(configClass);
}
@Override
protected List> getConfigsForHelp() {
List> help = new ArrayList<>();
help.add(RemoteConfig.class);
help.addAll(super.getConfigsForHelp());
return help;
}
private boolean isOptional() {
try {
Method method = getClass().getDeclaredMethod("run", getPrimaryConfigClass(), RemoteService.class);
return method.getParameters()[1].getAnnotation(RemoteOptional.class) != null;
} catch (NoSuchMethodException e) {
out().println("WARN: cannot determine whether remote is optional: " + e.toString());
return false;
}
}
@Override
protected final RenderableResult run(T config) {
RemoteConfig rc = getConfig(RemoteConfig.class);
boolean optional = isOptional();
boolean isTestMode = ToolBase.isTestModeLLM();
LocalLoginManager llm = new LocalLoginManager(rc.loginStorage());
if (!optional && (!isTestMode && (llm.getCurrent() == null && rc.useLogin() == null))) {
helpAndFailIfMissing(rc.remote(), "Missing --remote, --useLogin, or current login using `bdeploy login`");
}
RemoteService svc = null;
if (rc.remote() != null) {
svc = createServiceFromCli(rc, optional);
} else if (!isTestMode) {
svc = createServiceFromLLM(rc, optional, llm);
}
// TODO: subscribe to actions *triggered by us*. This requires sending a client ID and
// filtering actions on the server when broadcasting. This in turn has the same challenges
// as scope service and activities had (?).
return run(config, svc);
}
protected SecurityContext getLocalContext() {
return null; // Always null for the CLI. The server will infer the context from the token.
}
private RemoteService createServiceFromLLM(RemoteConfig rc, boolean optional, LocalLoginManager llm) {
RemoteService svc;
if (rc.useLogin() != null) {
svc = llm.getNamedService(rc.useLogin());
} else {
svc = llm.getCurrentService();
}
if (!optional && svc == null) {
helpAndFail(
"Need either --tokenFile, --token or --keystore arguments or a current login session to access remote service");
}
return svc;
}
private RemoteService createServiceFromCli(RemoteConfig rc, boolean optional) {
RemoteService svc;
URI r = null;
if (rc.remote() != null) {
r = UriBuilder.fromUri(rc.remote()).build();
}
if (rc.tokenFile() != null && rc.token() != null) {
out().println(
"WARNING: both tokenFile and token are given, preferring tokenFile (Hint: check arguments and environment)");
}
svc = createRemoteService(rc, optional, r);
return svc;
}
private RemoteService createRemoteService(RemoteConfig rc, boolean optional, URI r) {
RemoteService svc = null;
if (rc.tokenFile() != null) {
try {
String token = new String(Files.readAllBytes(Paths.get(rc.tokenFile())), StandardCharsets.UTF_8);
svc = new RemoteService(r, token);
} catch (IOException e) {
throw new IllegalStateException("Cannot read token from " + rc.tokenFile(), e);
}
} else if (rc.token() != null) {
svc = new RemoteService(r, rc.token());
} else if (rc.keystore() != null) {
helpAndFailIfMissing(rc.passphrase(), "Missing --passphrase");
svc = new RemoteService(r, new OnDiscKeyStore(Paths.get(rc.keystore()), rc.passphrase()));
} else if (r != null && (r.getScheme() == null || !"https".equals(r.getScheme()))) {
if (r.getScheme() == null) {
svc = new RemoteService(UriBuilder.fromUri(r).scheme("file").build());
} else {
svc = new RemoteService(UriBuilder.fromUri(r).build());
}
} else {
if (!optional) {
helpAndFail("Need either --tokenFile, --token or --keystore arguments to access remote service");
}
}
return svc;
}
protected abstract RenderableResult run(T config, RemoteService remote);
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy