diff --git a/plugins/nf-nomad/src/main/nextflow/nomad/NomadPlugin.groovy b/plugins/nf-nomad/src/main/nextflow/nomad/NomadPlugin.groovy index 5273ae8..55512ba 100644 --- a/plugins/nf-nomad/src/main/nextflow/nomad/NomadPlugin.groovy +++ b/plugins/nf-nomad/src/main/nextflow/nomad/NomadPlugin.groovy @@ -18,6 +18,9 @@ package nextflow.nomad import groovy.transform.CompileStatic +import groovy.util.logging.Slf4j +import nextflow.cli.PluginAbstractExec +import nextflow.nomad.secrets.NomadSecretCmd import nextflow.nomad.executor.TaskDirectives import nextflow.plugin.BasePlugin import nextflow.script.ProcessConfig @@ -31,7 +34,8 @@ import org.pf4j.PluginWrapper * @author : matthdsm */ @CompileStatic -class NomadPlugin extends BasePlugin { +@Slf4j +class NomadPlugin extends BasePlugin implements PluginAbstractExec{ NomadPlugin(PluginWrapper wrapper) { super(wrapper) @@ -42,4 +46,23 @@ class NomadPlugin extends BasePlugin { private static void addCustomDirectives() { ProcessConfig.DIRECTIVES.addAll(TaskDirectives.ALL) } + + @Override + List getCommands() { + return ['secrets'] + } + + @Override + int exec(String cmd, List args) { + return switch (cmd){ + case 'secrets'-> secrets(args.first(), args.drop(1)) + default -> -1 + } + } + + int secrets(String action, Listargs){ + NomadSecretCmd nomadSecretCmd = new NomadSecretCmd() + nomadSecretCmd.runCommand( session.config , action, args) + return 0 + } } diff --git a/plugins/nf-nomad/src/main/nextflow/nomad/config/NomadJobOpts.groovy b/plugins/nf-nomad/src/main/nextflow/nomad/config/NomadJobOpts.groovy index a31dd3f..2c70e8c 100644 --- a/plugins/nf-nomad/src/main/nextflow/nomad/config/NomadJobOpts.groovy +++ b/plugins/nf-nomad/src/main/nextflow/nomad/config/NomadJobOpts.groovy @@ -40,6 +40,7 @@ class NomadJobOpts{ List datacenters String region String namespace + String secretsPath String dockerVolume JobVolume[] volumeSpec JobAffinity affinitySpec @@ -76,6 +77,8 @@ class NomadJobOpts{ this.affinitySpec = parseAffinity(nomadJobOpts) this.constraintSpec = parseConstraint(nomadJobOpts) this.constraintsSpec = parseConstraints(nomadJobOpts) + + this.secretsPath = nomadJobOpts.secretsPath ?: sysEnv.get('NOMAD_SECRETS_PATH') ?: "secrets/nf-nomad" } JobVolume[] parseVolumes(Map nomadJobOpts){ diff --git a/plugins/nf-nomad/src/main/nextflow/nomad/executor/NomadService.groovy b/plugins/nf-nomad/src/main/nextflow/nomad/executor/NomadService.groovy index 5325304..0d01c0f 100644 --- a/plugins/nf-nomad/src/main/nextflow/nomad/executor/NomadService.groovy +++ b/plugins/nf-nomad/src/main/nextflow/nomad/executor/NomadService.groovy @@ -22,6 +22,7 @@ import groovy.util.logging.Slf4j import io.nomadproject.client.ApiClient import io.nomadproject.client.ApiException import io.nomadproject.client.api.JobsApi +import io.nomadproject.client.api.VariablesApi import io.nomadproject.client.model.* import nextflow.nomad.models.ConstraintsBuilder import nextflow.nomad.models.JobConstraints @@ -44,8 +45,9 @@ import java.nio.file.Path class NomadService implements Closeable{ NomadConfig config - + ApiClient apiClient JobsApi jobsApi + VariablesApi variablesApi NomadService(NomadConfig config) { this.config = config @@ -55,7 +57,7 @@ class NomadService implements Closeable{ final READ_TIMEOUT_MILLISECONDS = 60000 final WRITE_TIMEOUT_MILLISECONDS = 60000 - ApiClient apiClient = new ApiClient( connectTimeout: CONNECTION_TIMEOUT_MILLISECONDS, readTimeout: READ_TIMEOUT_MILLISECONDS, writeTimeout: WRITE_TIMEOUT_MILLISECONDS) + apiClient = new ApiClient( connectTimeout: CONNECTION_TIMEOUT_MILLISECONDS, readTimeout: READ_TIMEOUT_MILLISECONDS, writeTimeout: WRITE_TIMEOUT_MILLISECONDS) apiClient.basePath = config.clientOpts().address log.debug "[NOMAD] Client Address: ${config.clientOpts().address}" @@ -64,6 +66,7 @@ class NomadService implements Closeable{ apiClient.apiKey = config.clientOpts().token } this.jobsApi = new JobsApi(apiClient) + this.variablesApi = new VariablesApi(apiClient) } protected Resources getResources(TaskRun task) { @@ -284,8 +287,9 @@ class NomadService implements Closeable{ def secrets = task.processor?.config?.get(TaskDirectives.SECRETS) if( secrets ){ Template template = new Template(envvars: true, destPath: "/secrets/nf-nomad") + String secretPath = config.jobOpts().secretsPath String tmpl = secrets.collect{ String name-> - "${name}={{ with nomadVar \"secrets/${name}\" }}{{ .${name} }}{{ end }}" + "${name}={{ with nomadVar \"$secretPath/${name}\" }}{{ .${name} }}{{ end }}" }.join('\n').stripIndent() template.embeddedTmpl(tmpl) taskDef.addTemplatesItem(template) @@ -380,4 +384,49 @@ class NomadService implements Closeable{ throw new ProcessSubmitException("[NOMAD] Failed to get alloactions ${jobId} -- Cause: ${e.message ?: e}", e) } } + + String getVariableValue(String key){ + getVariableValue(config.jobOpts().secretsPath, key) + } + + String getVariableValue(String path, String key){ + var variable = variablesApi.getVariableQuery("$path/$key", + config.jobOpts().region, + config.jobOpts().namespace, + null, null, null, null, null, null, null) + variable?.items?.find{ it.key == key }?.value + } + + void setVariableValue(String key, String value){ + setVariableValue(config.jobOpts().secretsPath, key, value) + } + + void setVariableValue(String path, String key, String value){ + var content = Map.of(key,value) + var variable = new Variable(path: path, items: content) + variablesApi.postVariable("$path/$key", variable, + config.jobOpts().region, + config.jobOpts().namespace, + null, null, null) + } + + List getVariablesList(){ + var listRequest = variablesApi.getVariablesListRequest( + config.jobOpts().region, + config.jobOpts().namespace, + null, null, null, null, null, null, null) + listRequest.collect{ it.path} + } + + void deleteVariable(String key){ + deleteVariable(config.jobOpts().secretsPath, key) + } + + void deleteVariable(String path, String key){ + var variable = new Variable( items: Map.of(key, "")) + variablesApi.deleteVariable("$path/$key", variable, + config.jobOpts().region, + config.jobOpts().namespace, + null, null, null) + } } diff --git a/plugins/nf-nomad/src/main/nextflow/nomad/secrets/NomadSecretCmd.groovy b/plugins/nf-nomad/src/main/nextflow/nomad/secrets/NomadSecretCmd.groovy new file mode 100644 index 0000000..0ae2ba9 --- /dev/null +++ b/plugins/nf-nomad/src/main/nextflow/nomad/secrets/NomadSecretCmd.groovy @@ -0,0 +1,79 @@ +package nextflow.nomad.secrets + +import groovy.util.logging.Slf4j +import nextflow.exception.AbortOperationException +import nextflow.nomad.config.NomadConfig +import nextflow.nomad.executor.NomadService +import nextflow.plugin.Priority +import nextflow.secret.Secret +import nextflow.secret.SecretImpl +import nextflow.secret.SecretsProvider + +@Slf4j +class NomadSecretCmd { + + protected NomadService service + protected NomadConfig nomadConfig + + int runCommand(Map config, String action, List args){ + nomadConfig = new NomadConfig((config.nomad ?: Collections.emptyMap()) as Map) + service = new NomadService(nomadConfig) + return switch (action){ + case 'get' ->execGetSecretNames(args.removeAt(0).toString()) + case 'set' ->execSetSecretNames(args.removeAt(0).toString(),args.removeAt(0).toString()) + case 'list'->execListSecretsNames() + case 'delete'->execDeleteSecretNames(args.removeAt(0).toString()) + default -> -1 + } + } + + int execListSecretsNames(){ + def list = listSecretsNames() + println list.join('\n') + return 0 + } + + int execGetSecretNames(String name){ + if(!name){ + throw new AbortOperationException("Wrong number of arguments") + } + def secret = getSecret(name) + println secret + return 0 + } + + int execSetSecretNames(String name, String value){ + if(!name){ + throw new AbortOperationException("Wrong number of arguments") + } + setSecret(name, value) + return 0 + } + + int execDeleteSecretNames(String name){ + if(!name){ + throw new AbortOperationException("Wrong number of arguments") + } + deleteSecret(name) + return 0 + } + + String getSecret(String name) { + String value = service.getVariableValue(name) + if( !value ) + throw new AbortOperationException("Missing secret name") + value + } + + Set listSecretsNames() { + service.variablesList + } + + void setSecret(String name, String value) { + service.setVariableValue(name, value) + } + + void deleteSecret(String name){ + service.deleteVariable(name) + } +} diff --git a/plugins/nf-nomad/src/main/nextflow/nomad/NomadSecretProvider.groovy b/plugins/nf-nomad/src/main/nextflow/nomad/secrets/NomadSecretProvider.groovy similarity index 61% rename from plugins/nf-nomad/src/main/nextflow/nomad/NomadSecretProvider.groovy rename to plugins/nf-nomad/src/main/nextflow/nomad/secrets/NomadSecretProvider.groovy index 668b846..9e4bb45 100644 --- a/plugins/nf-nomad/src/main/nextflow/nomad/NomadSecretProvider.groovy +++ b/plugins/nf-nomad/src/main/nextflow/nomad/secrets/NomadSecretProvider.groovy @@ -1,4 +1,4 @@ -package nextflow.nomad +package nextflow.nomad.secrets import groovy.util.logging.Slf4j import nextflow.plugin.Priority @@ -25,35 +25,35 @@ class NomadSecretProvider implements SecretsProvider, Closeable{ @Override Secret getSecret(String name) { - log.error("NomadSecretProvider can't get secret, use nomad cli or disable it") + log.debug("NomadSecretProvider can't get secret, use nomad cli or nextflow plugin nf-nomad:secrets") null } @Override String getSecretsEnv(List secretNames) { - log.error("NomadSecretProvider can't get secret, use nomad cli or disable it") + log.debug("NomadSecretProvider can't get secret, use nomad cli or nextflow plugin nf-nomad:secrets") null } @Override String getSecretsEnv() { - log.error("NomadSecretProvider can't get secret, use nomad cli or disable it") + log.debug("NomadSecretProvider can't get secret, use nomad cli or nextflow plugin nf-nomad:secrets") null } @Override void putSecret(String name, String value) { - throw new UnsupportedOperationException("NomadSecretProvider can't put secret, use nomad cli") + throw new UnsupportedOperationException("NomadSecretProvider can't put secret, use nomad cli or nextflow plugin nf-nomad:secrets") } @Override void removeSecret(String name) { - throw new UnsupportedOperationException("NomadSecretProvider can't remove secret, use nomad cli") + throw new UnsupportedOperationException("NomadSecretProvider can't remove secret, use nomad cli or nextflow plugin nf-nomad:secrets") } @Override Set listSecretsNames() { - log.error("NomadSecretProvider can't get secret, use nomad cli or disable it") + log.debug("NomadSecretProvider can't get secret, use nomad cli or nextflow plugin nf-nomad:secrets") null } diff --git a/validation/secrets/nextflow.config b/validation/secrets/nextflow.config index d35b509..6dc2be9 100644 --- a/validation/secrets/nextflow.config +++ b/validation/secrets/nextflow.config @@ -15,6 +15,7 @@ nomad { jobs { deleteOnCompletion = false volume = { type "host" name "scratchdir" } + namespace = 'ns-qa' } }