diff --git a/USAGE.md b/USAGE.md index 87d356d7..3c7df47f 100644 --- a/USAGE.md +++ b/USAGE.md @@ -32,12 +32,32 @@ Run `tw credentials add -h` to view the required fields for your prov Seqera requires credentials to access your cloud compute environments. See the [compute environment page][compute-envs] for your cloud provider for more information. +AWS credentials support two modes: **keys** (default) and **role**. + +**Keys mode** — uses an AWS access key and secret key: + ```console $ tw credentials add aws --name=my_aws_creds --access-key= --secret-key= New AWS credentials 'my_aws_creds (1sxCxvxfx8xnxdxGxQxqxH)' added at user workspace ``` +Keys mode also supports an optional IAM role ARN for cross-account access, with an optional platform-managed External ID: + + ```bash + tw credentials add aws --name=my_aws_creds --access-key= --secret-key= --assume-role-arn= --generate-external-id + ``` + +**Role mode** — uses IAM role assumption without static credentials. An External ID is automatically generated by the platform: + + ```console + $ tw credentials add aws --name=my_aws_role_creds --mode=role --assume-role-arn= + + New AWS credentials 'my_aws_role_creds (2sxCxvxfx8xnxdxGxQxqxH)' added at user workspace + ``` + +> **Note**: In role mode, `--access-key` and `--secret-key` cannot be used. The `--assume-role-arn` option is required. + #### Git credentials Seqera requires access credentials to interact with pipeline Git repositories. See [Git integration][git-integration] for more information. diff --git a/VERSION-API b/VERSION-API index b0b1c19c..a6f66a22 100644 --- a/VERSION-API +++ b/VERSION-API @@ -1,4 +1,4 @@ -1.109.0 +1.114.0 // Only first line of this file is read // This version should be bumped to the minimum version where dependent API changes were introduced // But never higher then the current Platform API Version deployed in Cloud Production: https://cloud.seqera.io/api/service-info diff --git a/conf/reflect-config.json b/conf/reflect-config.json index 72c36309..112239e3 100644 --- a/conf/reflect-config.json +++ b/conf/reflect-config.json @@ -2620,6 +2620,11 @@ "queryAllDeclaredConstructors":true, "methods":[{"name":"","parameterTypes":[] }, {"name":"addBucketsItem","parameterTypes":["io.seqera.tower.model.Bucket"] }, {"name":"addImagesItem","parameterTypes":["io.seqera.tower.model.Image"] }, {"name":"addInstanceTypesItem","parameterTypes":["io.seqera.tower.model.InstanceType"] }, {"name":"addKeyPairsItem","parameterTypes":["java.lang.String"] }, {"name":"addSecurityGroupsItem","parameterTypes":["io.seqera.tower.model.SecurityGroup"] }, {"name":"addSubnetsItem","parameterTypes":["io.seqera.tower.model.Subnet"] }, {"name":"addVpcsItem","parameterTypes":["io.seqera.tower.model.Vpc"] }, {"name":"addWarningsItem","parameterTypes":["java.lang.String"] }, {"name":"buckets","parameterTypes":["java.util.List"] }, {"name":"discriminator","parameterTypes":["java.lang.String"] }, {"name":"equals","parameterTypes":["java.lang.Object"] }, {"name":"getBuckets","parameterTypes":[] }, {"name":"getDiscriminator","parameterTypes":[] }, {"name":"getImages","parameterTypes":[] }, {"name":"getInstanceTypes","parameterTypes":[] }, {"name":"getKeyPairs","parameterTypes":[] }, {"name":"getSecurityGroups","parameterTypes":[] }, {"name":"getSubnets","parameterTypes":[] }, {"name":"getVpcs","parameterTypes":[] }, {"name":"getWarnings","parameterTypes":[] }, {"name":"hashCode","parameterTypes":[] }, {"name":"images","parameterTypes":["java.util.List"] }, {"name":"instanceTypes","parameterTypes":["java.util.List"] }, {"name":"keyPairs","parameterTypes":["java.util.List"] }, {"name":"securityGroups","parameterTypes":["java.util.List"] }, {"name":"setBuckets","parameterTypes":["java.util.List"] }, {"name":"setDiscriminator","parameterTypes":["java.lang.String"] }, {"name":"setImages","parameterTypes":["java.util.List"] }, {"name":"setInstanceTypes","parameterTypes":["java.util.List"] }, {"name":"setKeyPairs","parameterTypes":["java.util.List"] }, {"name":"setSecurityGroups","parameterTypes":["java.util.List"] }, {"name":"setSubnets","parameterTypes":["java.util.List"] }, {"name":"setVpcs","parameterTypes":["java.util.List"] }, {"name":"setWarnings","parameterTypes":["java.util.List"] }, {"name":"subnets","parameterTypes":["java.util.List"] }, {"name":"toIndentedString","parameterTypes":["java.lang.Object"] }, {"name":"toString","parameterTypes":[] }, {"name":"vpcs","parameterTypes":["java.util.List"] }, {"name":"warnings","parameterTypes":["java.util.List"] }] }, +{ + "name":"io.seqera.tower.model.AwsCredentialsMode", + "queryAllDeclaredMethods":true, + "methods":[{"name":"$values","parameterTypes":[] }, {"name":"fromValue","parameterTypes":["java.lang.String"] }, {"name":"getValue","parameterTypes":[] }, {"name":"toString","parameterTypes":[] }, {"name":"valueOf","parameterTypes":["java.lang.String"] }, {"name":"values","parameterTypes":[] }] +}, { "name":"io.seqera.tower.model.AwsSecurityKeys", "allDeclaredFields":true, @@ -2765,7 +2770,7 @@ "allDeclaredFields":true, "queryAllDeclaredMethods":true, "queryAllDeclaredConstructors":true, - "methods":[{"name":"","parameterTypes":[] }, {"name":"addLabelsItem","parameterTypes":["io.seqera.tower.model.LabelDbDto"] }, {"name":"config","parameterTypes":["io.seqera.tower.model.ComputeConfig"] }, {"name":"credentialsId","parameterTypes":["java.lang.String"] }, {"name":"dateCreated","parameterTypes":["java.time.OffsetDateTime"] }, {"name":"deleted","parameterTypes":["java.lang.Boolean"] }, {"name":"description","parameterTypes":["java.lang.String"] }, {"name":"equals","parameterTypes":["java.lang.Object"] }, {"name":"equalsNullable","parameterTypes":["org.openapitools.jackson.nullable.JsonNullable","org.openapitools.jackson.nullable.JsonNullable"] }, {"name":"getConfig","parameterTypes":[] }, {"name":"getCredentialsId","parameterTypes":[] }, {"name":"getDateCreated","parameterTypes":[] }, {"name":"getDeleted","parameterTypes":[] }, {"name":"getDescription","parameterTypes":[] }, {"name":"getId","parameterTypes":[] }, {"name":"getLabels","parameterTypes":[] }, {"name":"getLastUpdated","parameterTypes":[] }, {"name":"getLastUsed","parameterTypes":[] }, {"name":"getManagedIdentityId","parameterTypes":[] }, {"name":"getMessage","parameterTypes":[] }, {"name":"getName","parameterTypes":[] }, {"name":"getOrgId","parameterTypes":[] }, {"name":"getPlatform","parameterTypes":[] }, {"name":"getPrimary","parameterTypes":[] }, {"name":"getResources","parameterTypes":[] }, {"name":"getResources_JsonNullable","parameterTypes":[] }, {"name":"getStatus","parameterTypes":[] }, {"name":"getWorkspaceId","parameterTypes":[] }, {"name":"hashCode","parameterTypes":[] }, {"name":"hashCodeNullable","parameterTypes":["org.openapitools.jackson.nullable.JsonNullable"] }, {"name":"id","parameterTypes":["java.lang.String"] }, {"name":"labels","parameterTypes":["java.util.List"] }, {"name":"lastUpdated","parameterTypes":["java.time.OffsetDateTime"] }, {"name":"lastUsed","parameterTypes":["java.time.OffsetDateTime"] }, {"name":"managedIdentityId","parameterTypes":["java.lang.String"] }, {"name":"message","parameterTypes":["java.lang.String"] }, {"name":"name","parameterTypes":["java.lang.String"] }, {"name":"orgId","parameterTypes":["java.lang.Long"] }, {"name":"platform","parameterTypes":["io.seqera.tower.model.ComputeEnvResponseDto$PlatformEnum"] }, {"name":"primary","parameterTypes":["java.lang.Boolean"] }, {"name":"resources","parameterTypes":["io.seqera.tower.model.ComputeEnvResources"] }, {"name":"setConfig","parameterTypes":["io.seqera.tower.model.ComputeConfig"] }, {"name":"setCredentialsId","parameterTypes":["java.lang.String"] }, {"name":"setDateCreated","parameterTypes":["java.time.OffsetDateTime"] }, {"name":"setDeleted","parameterTypes":["java.lang.Boolean"] }, {"name":"setDescription","parameterTypes":["java.lang.String"] }, {"name":"setId","parameterTypes":["java.lang.String"] }, {"name":"setLabels","parameterTypes":["java.util.List"] }, {"name":"setLastUpdated","parameterTypes":["java.time.OffsetDateTime"] }, {"name":"setLastUsed","parameterTypes":["java.time.OffsetDateTime"] }, {"name":"setManagedIdentityId","parameterTypes":["java.lang.String"] }, {"name":"setMessage","parameterTypes":["java.lang.String"] }, {"name":"setName","parameterTypes":["java.lang.String"] }, {"name":"setOrgId","parameterTypes":["java.lang.Long"] }, {"name":"setPlatform","parameterTypes":["io.seqera.tower.model.ComputeEnvResponseDto$PlatformEnum"] }, {"name":"setPrimary","parameterTypes":["java.lang.Boolean"] }, {"name":"setResources","parameterTypes":["io.seqera.tower.model.ComputeEnvResources"] }, {"name":"setResources_JsonNullable","parameterTypes":["org.openapitools.jackson.nullable.JsonNullable"] }, {"name":"setStatus","parameterTypes":["io.seqera.tower.model.ComputeEnvStatus"] }, {"name":"setWorkspaceId","parameterTypes":["java.lang.Long"] }, {"name":"status","parameterTypes":["io.seqera.tower.model.ComputeEnvStatus"] }, {"name":"toIndentedString","parameterTypes":["java.lang.Object"] }, {"name":"toString","parameterTypes":[] }, {"name":"workspaceId","parameterTypes":["java.lang.Long"] }] + "methods":[{"name":"","parameterTypes":[] }, {"name":"addLabelsItem","parameterTypes":["io.seqera.tower.model.LabelDbDto"] }, {"name":"awsAccountId","parameterTypes":["java.lang.String"] }, {"name":"config","parameterTypes":["io.seqera.tower.model.ComputeConfig"] }, {"name":"credentialsId","parameterTypes":["java.lang.String"] }, {"name":"dateCreated","parameterTypes":["java.time.OffsetDateTime"] }, {"name":"deleted","parameterTypes":["java.lang.Boolean"] }, {"name":"description","parameterTypes":["java.lang.String"] }, {"name":"equals","parameterTypes":["java.lang.Object"] }, {"name":"equalsNullable","parameterTypes":["org.openapitools.jackson.nullable.JsonNullable","org.openapitools.jackson.nullable.JsonNullable"] }, {"name":"getAwsAccountId","parameterTypes":[] }, {"name":"getAwsAccountId_JsonNullable","parameterTypes":[] }, {"name":"getConfig","parameterTypes":[] }, {"name":"getCredentialsId","parameterTypes":[] }, {"name":"getDateCreated","parameterTypes":[] }, {"name":"getDeleted","parameterTypes":[] }, {"name":"getDescription","parameterTypes":[] }, {"name":"getId","parameterTypes":[] }, {"name":"getLabels","parameterTypes":[] }, {"name":"getLastUpdated","parameterTypes":[] }, {"name":"getLastUsed","parameterTypes":[] }, {"name":"getManagedIdentityId","parameterTypes":[] }, {"name":"getMessage","parameterTypes":[] }, {"name":"getName","parameterTypes":[] }, {"name":"getOrgId","parameterTypes":[] }, {"name":"getPlatform","parameterTypes":[] }, {"name":"getPrimary","parameterTypes":[] }, {"name":"getResources","parameterTypes":[] }, {"name":"getResources_JsonNullable","parameterTypes":[] }, {"name":"getStatus","parameterTypes":[] }, {"name":"getWorkspaceId","parameterTypes":[] }, {"name":"hashCode","parameterTypes":[] }, {"name":"hashCodeNullable","parameterTypes":["org.openapitools.jackson.nullable.JsonNullable"] }, {"name":"id","parameterTypes":["java.lang.String"] }, {"name":"labels","parameterTypes":["java.util.List"] }, {"name":"lastUpdated","parameterTypes":["java.time.OffsetDateTime"] }, {"name":"lastUsed","parameterTypes":["java.time.OffsetDateTime"] }, {"name":"managedIdentityId","parameterTypes":["java.lang.String"] }, {"name":"message","parameterTypes":["java.lang.String"] }, {"name":"name","parameterTypes":["java.lang.String"] }, {"name":"orgId","parameterTypes":["java.lang.Long"] }, {"name":"platform","parameterTypes":["io.seqera.tower.model.ComputeEnvResponseDto$PlatformEnum"] }, {"name":"primary","parameterTypes":["java.lang.Boolean"] }, {"name":"resources","parameterTypes":["io.seqera.tower.model.ComputeEnvResources"] }, {"name":"setAwsAccountId","parameterTypes":["java.lang.String"] }, {"name":"setAwsAccountId_JsonNullable","parameterTypes":["org.openapitools.jackson.nullable.JsonNullable"] }, {"name":"setConfig","parameterTypes":["io.seqera.tower.model.ComputeConfig"] }, {"name":"setCredentialsId","parameterTypes":["java.lang.String"] }, {"name":"setDateCreated","parameterTypes":["java.time.OffsetDateTime"] }, {"name":"setDeleted","parameterTypes":["java.lang.Boolean"] }, {"name":"setDescription","parameterTypes":["java.lang.String"] }, {"name":"setId","parameterTypes":["java.lang.String"] }, {"name":"setLabels","parameterTypes":["java.util.List"] }, {"name":"setLastUpdated","parameterTypes":["java.time.OffsetDateTime"] }, {"name":"setLastUsed","parameterTypes":["java.time.OffsetDateTime"] }, {"name":"setManagedIdentityId","parameterTypes":["java.lang.String"] }, {"name":"setMessage","parameterTypes":["java.lang.String"] }, {"name":"setName","parameterTypes":["java.lang.String"] }, {"name":"setOrgId","parameterTypes":["java.lang.Long"] }, {"name":"setPlatform","parameterTypes":["io.seqera.tower.model.ComputeEnvResponseDto$PlatformEnum"] }, {"name":"setPrimary","parameterTypes":["java.lang.Boolean"] }, {"name":"setResources","parameterTypes":["io.seqera.tower.model.ComputeEnvResources"] }, {"name":"setResources_JsonNullable","parameterTypes":["org.openapitools.jackson.nullable.JsonNullable"] }, {"name":"setStatus","parameterTypes":["io.seqera.tower.model.ComputeEnvStatus"] }, {"name":"setWorkspaceId","parameterTypes":["java.lang.Long"] }, {"name":"status","parameterTypes":["io.seqera.tower.model.ComputeEnvStatus"] }, {"name":"toIndentedString","parameterTypes":["java.lang.Object"] }, {"name":"toString","parameterTypes":[] }, {"name":"workspaceId","parameterTypes":["java.lang.Long"] }] }, { "name":"io.seqera.tower.model.ComputeEnvResponseDto$PlatformEnum", @@ -3382,11 +3387,17 @@ "queryAllDeclaredConstructors":true, "methods":[{"name":"$values","parameterTypes":[] }, {"name":"","parameterTypes":["java.lang.String","int","java.lang.String"] }, {"name":"fromValue","parameterTypes":["java.lang.String"] }, {"name":"getValue","parameterTypes":[] }, {"name":"toString","parameterTypes":[] }, {"name":"valueOf","parameterTypes":["java.lang.String"] }, {"name":"values","parameterTypes":[] }] }, +{ + "name":"io.seqera.tower.model.DataStudioWorkspaceSettingsRequest", + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":[] }, {"name":"containerRepository","parameterTypes":["java.lang.String"] }, {"name":"equals","parameterTypes":["java.lang.Object"] }, {"name":"equalsNullable","parameterTypes":["org.openapitools.jackson.nullable.JsonNullable","org.openapitools.jackson.nullable.JsonNullable"] }, {"name":"getContainerRepository","parameterTypes":[] }, {"name":"getContainerRepository_JsonNullable","parameterTypes":[] }, {"name":"getLifespanHours","parameterTypes":[] }, {"name":"getPrivateStudioByDefault","parameterTypes":[] }, {"name":"hashCode","parameterTypes":[] }, {"name":"hashCodeNullable","parameterTypes":["org.openapitools.jackson.nullable.JsonNullable"] }, {"name":"lifespanHours","parameterTypes":["java.lang.Integer"] }, {"name":"privateStudioByDefault","parameterTypes":["java.lang.Boolean"] }, {"name":"setContainerRepository","parameterTypes":["java.lang.String"] }, {"name":"setContainerRepository_JsonNullable","parameterTypes":["org.openapitools.jackson.nullable.JsonNullable"] }, {"name":"setLifespanHours","parameterTypes":["java.lang.Integer"] }, {"name":"setPrivateStudioByDefault","parameterTypes":["java.lang.Boolean"] }, {"name":"toIndentedString","parameterTypes":["java.lang.Object"] }, {"name":"toString","parameterTypes":[] }] +}, { "name":"io.seqera.tower.model.DataStudioWorkspaceSettingsResponse", "queryAllDeclaredMethods":true, "queryAllDeclaredConstructors":true, - "methods":[{"name":"","parameterTypes":[] }, {"name":"containerRepository","parameterTypes":["java.lang.String"] }, {"name":"equals","parameterTypes":["java.lang.Object"] }, {"name":"equalsNullable","parameterTypes":["org.openapitools.jackson.nullable.JsonNullable","org.openapitools.jackson.nullable.JsonNullable"] }, {"name":"getContainerRepository","parameterTypes":[] }, {"name":"getContainerRepository_JsonNullable","parameterTypes":[] }, {"name":"getLifespanHours","parameterTypes":[] }, {"name":"getOrgId","parameterTypes":[] }, {"name":"getWspId","parameterTypes":[] }, {"name":"hashCode","parameterTypes":[] }, {"name":"hashCodeNullable","parameterTypes":["org.openapitools.jackson.nullable.JsonNullable"] }, {"name":"lifespanHours","parameterTypes":["java.lang.Integer"] }, {"name":"orgId","parameterTypes":["java.lang.Long"] }, {"name":"setContainerRepository","parameterTypes":["java.lang.String"] }, {"name":"setContainerRepository_JsonNullable","parameterTypes":["org.openapitools.jackson.nullable.JsonNullable"] }, {"name":"setLifespanHours","parameterTypes":["java.lang.Integer"] }, {"name":"setOrgId","parameterTypes":["java.lang.Long"] }, {"name":"setWspId","parameterTypes":["java.lang.Long"] }, {"name":"toIndentedString","parameterTypes":["java.lang.Object"] }, {"name":"toString","parameterTypes":[] }, {"name":"wspId","parameterTypes":["java.lang.Long"] }] + "methods":[{"name":"","parameterTypes":[] }, {"name":"containerRepository","parameterTypes":["java.lang.String"] }, {"name":"equals","parameterTypes":["java.lang.Object"] }, {"name":"equalsNullable","parameterTypes":["org.openapitools.jackson.nullable.JsonNullable","org.openapitools.jackson.nullable.JsonNullable"] }, {"name":"getContainerRepository","parameterTypes":[] }, {"name":"getContainerRepository_JsonNullable","parameterTypes":[] }, {"name":"getLifespanHours","parameterTypes":[] }, {"name":"getOrgId","parameterTypes":[] }, {"name":"getPrivateStudioByDefault","parameterTypes":[] }, {"name":"getWspId","parameterTypes":[] }, {"name":"hashCode","parameterTypes":[] }, {"name":"hashCodeNullable","parameterTypes":["org.openapitools.jackson.nullable.JsonNullable"] }, {"name":"lifespanHours","parameterTypes":["java.lang.Integer"] }, {"name":"orgId","parameterTypes":["java.lang.Long"] }, {"name":"privateStudioByDefault","parameterTypes":["java.lang.Boolean"] }, {"name":"setContainerRepository","parameterTypes":["java.lang.String"] }, {"name":"setContainerRepository_JsonNullable","parameterTypes":["org.openapitools.jackson.nullable.JsonNullable"] }, {"name":"setLifespanHours","parameterTypes":["java.lang.Integer"] }, {"name":"setOrgId","parameterTypes":["java.lang.Long"] }, {"name":"setPrivateStudioByDefault","parameterTypes":["java.lang.Boolean"] }, {"name":"setWspId","parameterTypes":["java.lang.Long"] }, {"name":"toIndentedString","parameterTypes":["java.lang.Object"] }, {"name":"toString","parameterTypes":[] }, {"name":"wspId","parameterTypes":["java.lang.Long"] }] }, { "name":"io.seqera.tower.model.Dataset", @@ -3429,6 +3440,12 @@ "queryAllDeclaredConstructors":true, "methods":[{"name":"","parameterTypes":[] }, {"name":"createdBy","parameterTypes":["io.seqera.tower.model.UserInfo"] }, {"name":"datasetDescription","parameterTypes":["java.lang.String"] }, {"name":"datasetId","parameterTypes":["java.lang.String"] }, {"name":"datasetName","parameterTypes":["java.lang.String"] }, {"name":"dateCreated","parameterTypes":["java.time.OffsetDateTime"] }, {"name":"disabled","parameterTypes":["java.lang.Boolean"] }, {"name":"equals","parameterTypes":["java.lang.Object"] }, {"name":"fileName","parameterTypes":["java.lang.String"] }, {"name":"getCreatedBy","parameterTypes":[] }, {"name":"getDatasetDescription","parameterTypes":[] }, {"name":"getDatasetId","parameterTypes":[] }, {"name":"getDatasetName","parameterTypes":[] }, {"name":"getDateCreated","parameterTypes":[] }, {"name":"getDisabled","parameterTypes":[] }, {"name":"getFileName","parameterTypes":[] }, {"name":"getHasHeader","parameterTypes":[] }, {"name":"getLastUpdated","parameterTypes":[] }, {"name":"getMediaType","parameterTypes":[] }, {"name":"getUrl","parameterTypes":[] }, {"name":"getVersion","parameterTypes":[] }, {"name":"getWorkspaceId","parameterTypes":[] }, {"name":"hasHeader","parameterTypes":["java.lang.Boolean"] }, {"name":"hashCode","parameterTypes":[] }, {"name":"lastUpdated","parameterTypes":["java.time.OffsetDateTime"] }, {"name":"mediaType","parameterTypes":["java.lang.String"] }, {"name":"setCreatedBy","parameterTypes":["io.seqera.tower.model.UserInfo"] }, {"name":"setDatasetDescription","parameterTypes":["java.lang.String"] }, {"name":"setDatasetId","parameterTypes":["java.lang.String"] }, {"name":"setDatasetName","parameterTypes":["java.lang.String"] }, {"name":"setDateCreated","parameterTypes":["java.time.OffsetDateTime"] }, {"name":"setDisabled","parameterTypes":["java.lang.Boolean"] }, {"name":"setFileName","parameterTypes":["java.lang.String"] }, {"name":"setHasHeader","parameterTypes":["java.lang.Boolean"] }, {"name":"setLastUpdated","parameterTypes":["java.time.OffsetDateTime"] }, {"name":"setMediaType","parameterTypes":["java.lang.String"] }, {"name":"setUrl","parameterTypes":["java.lang.String"] }, {"name":"setVersion","parameterTypes":["java.lang.Long"] }, {"name":"setWorkspaceId","parameterTypes":["java.lang.Long"] }, {"name":"toIndentedString","parameterTypes":["java.lang.Object"] }, {"name":"toString","parameterTypes":[] }, {"name":"url","parameterTypes":["java.lang.String"] }, {"name":"version","parameterTypes":["java.lang.Long"] }, {"name":"workspaceId","parameterTypes":["java.lang.Long"] }] }, +{ + "name":"io.seqera.tower.model.DefaultWorkflowEngineParameter", + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":[] }, {"name":"defaultValue","parameterTypes":["java.lang.String"] }, {"name":"equals","parameterTypes":["java.lang.Object"] }, {"name":"getDefaultValue","parameterTypes":[] }, {"name":"getName","parameterTypes":[] }, {"name":"getType","parameterTypes":[] }, {"name":"hashCode","parameterTypes":[] }, {"name":"name","parameterTypes":["java.lang.String"] }, {"name":"setDefaultValue","parameterTypes":["java.lang.String"] }, {"name":"setName","parameterTypes":["java.lang.String"] }, {"name":"setType","parameterTypes":["java.lang.String"] }, {"name":"toIndentedString","parameterTypes":["java.lang.Object"] }, {"name":"toString","parameterTypes":[] }, {"name":"type","parameterTypes":["java.lang.String"] }] +}, { "name":"io.seqera.tower.model.DeleteCredentialsConflictResponse", "queryAllDeclaredMethods":true, @@ -3655,6 +3672,12 @@ "queryAllDeclaredConstructors":true, "methods":[{"name":"","parameterTypes":[] }, {"name":"dns","parameterTypes":["java.lang.String"] }, {"name":"equals","parameterTypes":["java.lang.Object"] }, {"name":"getDns","parameterTypes":[] }, {"name":"getId","parameterTypes":[] }, {"name":"getMount","parameterTypes":[] }, {"name":"hashCode","parameterTypes":[] }, {"name":"id","parameterTypes":["java.lang.String"] }, {"name":"mount","parameterTypes":["java.lang.String"] }, {"name":"setDns","parameterTypes":["java.lang.String"] }, {"name":"setId","parameterTypes":["java.lang.String"] }, {"name":"setMount","parameterTypes":["java.lang.String"] }, {"name":"toIndentedString","parameterTypes":["java.lang.Object"] }, {"name":"toString","parameterTypes":[] }] }, +{ + "name":"io.seqera.tower.model.GetCredentialsKeysResponse", + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":[] }, {"name":"equals","parameterTypes":["java.lang.Object"] }, {"name":"getKeys","parameterTypes":[] }, {"name":"hashCode","parameterTypes":[] }, {"name":"keys","parameterTypes":["java.lang.String"] }, {"name":"setKeys","parameterTypes":["java.lang.String"] }, {"name":"toIndentedString","parameterTypes":["java.lang.Object"] }, {"name":"toString","parameterTypes":[] }] +}, { "name":"io.seqera.tower.model.GetProgressResponse", "allDeclaredFields":true, @@ -4627,7 +4650,7 @@ "name":"io.seqera.tower.model.TraceCreateResponse", "queryAllDeclaredMethods":true, "queryAllDeclaredConstructors":true, - "methods":[{"name":"","parameterTypes":[] }, {"name":"equals","parameterTypes":["java.lang.Object"] }, {"name":"getMessage","parameterTypes":[] }, {"name":"getWorkflowId","parameterTypes":[] }, {"name":"hashCode","parameterTypes":[] }, {"name":"message","parameterTypes":["java.lang.String"] }, {"name":"setMessage","parameterTypes":["java.lang.String"] }, {"name":"setWorkflowId","parameterTypes":["java.lang.String"] }, {"name":"toIndentedString","parameterTypes":["java.lang.Object"] }, {"name":"toString","parameterTypes":[] }, {"name":"workflowId","parameterTypes":["java.lang.String"] }] + "methods":[{"name":"","parameterTypes":[] }, {"name":"equals","parameterTypes":["java.lang.Object"] }, {"name":"getMessage","parameterTypes":[] }, {"name":"getWatchUrl","parameterTypes":[] }, {"name":"getWorkflowId","parameterTypes":[] }, {"name":"hashCode","parameterTypes":[] }, {"name":"message","parameterTypes":["java.lang.String"] }, {"name":"setMessage","parameterTypes":["java.lang.String"] }, {"name":"setWatchUrl","parameterTypes":["java.lang.String"] }, {"name":"setWorkflowId","parameterTypes":["java.lang.String"] }, {"name":"toIndentedString","parameterTypes":["java.lang.Object"] }, {"name":"toString","parameterTypes":[] }, {"name":"watchUrl","parameterTypes":["java.lang.String"] }, {"name":"workflowId","parameterTypes":["java.lang.String"] }] }, { "name":"io.seqera.tower.model.TraceHeartbeatRequest", @@ -4859,6 +4882,12 @@ "queryAllDeclaredConstructors":true, "methods":[{"name":"","parameterTypes":[] }, {"name":"equals","parameterTypes":["java.lang.Object"] }, {"name":"getMsg","parameterTypes":[] }, {"name":"getStatusCode","parameterTypes":[] }, {"name":"hashCode","parameterTypes":[] }, {"name":"msg","parameterTypes":["java.lang.String"] }, {"name":"setMsg","parameterTypes":["java.lang.String"] }, {"name":"setStatusCode","parameterTypes":["java.lang.Integer"] }, {"name":"statusCode","parameterTypes":["java.lang.Integer"] }, {"name":"toIndentedString","parameterTypes":["java.lang.Object"] }, {"name":"toString","parameterTypes":[] }] }, +{ + "name":"io.seqera.tower.model.WesServiceInfo", + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":[] }, {"name":"addDefaultWorkflowEngineParametersItem","parameterTypes":["io.seqera.tower.model.DefaultWorkflowEngineParameter"] }, {"name":"addSupportedFilesystemProtocolsItem","parameterTypes":["java.lang.String"] }, {"name":"addSupportedWesVersionsItem","parameterTypes":["java.lang.String"] }, {"name":"authInstructionsUrl","parameterTypes":["java.lang.String"] }, {"name":"contactInfoUrl","parameterTypes":["java.lang.String"] }, {"name":"defaultWorkflowEngineParameters","parameterTypes":["java.util.List"] }, {"name":"equals","parameterTypes":["java.lang.Object"] }, {"name":"getAuthInstructionsUrl","parameterTypes":[] }, {"name":"getContactInfoUrl","parameterTypes":[] }, {"name":"getDefaultWorkflowEngineParameters","parameterTypes":[] }, {"name":"getSupportedFilesystemProtocols","parameterTypes":[] }, {"name":"getSupportedWesVersions","parameterTypes":[] }, {"name":"getSystemStateCounts","parameterTypes":[] }, {"name":"getTags","parameterTypes":[] }, {"name":"getWorkflowEngineVersions","parameterTypes":[] }, {"name":"getWorkflowTypeVersions","parameterTypes":[] }, {"name":"hashCode","parameterTypes":[] }, {"name":"putSystemStateCountsItem","parameterTypes":["java.lang.String","java.lang.Long"] }, {"name":"putTagsItem","parameterTypes":["java.lang.String","java.lang.String"] }, {"name":"putWorkflowEngineVersionsItem","parameterTypes":["java.lang.String","java.lang.String"] }, {"name":"putWorkflowTypeVersionsItem","parameterTypes":["java.lang.String","io.seqera.tower.model.WorkflowTypeVersion"] }, {"name":"setAuthInstructionsUrl","parameterTypes":["java.lang.String"] }, {"name":"setContactInfoUrl","parameterTypes":["java.lang.String"] }, {"name":"setDefaultWorkflowEngineParameters","parameterTypes":["java.util.List"] }, {"name":"setSupportedFilesystemProtocols","parameterTypes":["java.util.List"] }, {"name":"setSupportedWesVersions","parameterTypes":["java.util.List"] }, {"name":"setSystemStateCounts","parameterTypes":["java.util.Map"] }, {"name":"setTags","parameterTypes":["java.util.Map"] }, {"name":"setWorkflowEngineVersions","parameterTypes":["java.util.Map"] }, {"name":"setWorkflowTypeVersions","parameterTypes":["java.util.Map"] }, {"name":"supportedFilesystemProtocols","parameterTypes":["java.util.List"] }, {"name":"supportedWesVersions","parameterTypes":["java.util.List"] }, {"name":"systemStateCounts","parameterTypes":["java.util.Map"] }, {"name":"tags","parameterTypes":["java.util.Map"] }, {"name":"toIndentedString","parameterTypes":["java.lang.Object"] }, {"name":"toString","parameterTypes":[] }, {"name":"workflowEngineVersions","parameterTypes":["java.util.Map"] }, {"name":"workflowTypeVersions","parameterTypes":["java.util.Map"] }] +}, { "name":"io.seqera.tower.model.WfFusionMeta", "allDeclaredFields":true, @@ -4962,6 +4991,12 @@ "queryAllDeclaredConstructors":true, "methods":[{"name":"$values","parameterTypes":[] }, {"name":"","parameterTypes":["java.lang.String","int","java.lang.String"] }, {"name":"fromValue","parameterTypes":["java.lang.String"] }, {"name":"getValue","parameterTypes":[] }, {"name":"toString","parameterTypes":[] }, {"name":"valueOf","parameterTypes":["java.lang.String"] }, {"name":"values","parameterTypes":[] }] }, +{ + "name":"io.seqera.tower.model.WorkflowTypeVersion", + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":[] }, {"name":"addWorkflowTypeVersionItem","parameterTypes":["java.lang.String"] }, {"name":"equals","parameterTypes":["java.lang.Object"] }, {"name":"getWorkflowTypeVersion","parameterTypes":[] }, {"name":"hashCode","parameterTypes":[] }, {"name":"setWorkflowTypeVersion","parameterTypes":["java.util.List"] }, {"name":"toIndentedString","parameterTypes":["java.lang.Object"] }, {"name":"toString","parameterTypes":[] }, {"name":"workflowTypeVersion","parameterTypes":["java.util.List"] }] +}, { "name":"io.seqera.tower.model.Workspace", "allDeclaredFields":true, diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 8f659ecd..89834aa4 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -3,6 +3,7 @@ classgraphVersion = "4.8.180" commonsCompressVersion = "1.28.0" commonsIoVersion = "2.21.0" graalvmNativeVersion = "0.10.6" +jacksonDatabindNullableVersion = "0.2.8" jakartaAnnotationVersion = "3.0.0" javaxAnnotationVersion = "1.3.2" jerseyVersion = "2.47" @@ -13,13 +14,14 @@ mockserverVersion = "5.15.0" picocliVersion = "4.6.3" shadowVersion = "9.3.1" slf4jVersion = "2.0.17" -towerJavaSdkVersion = "1.107.0" +towerJavaSdkVersion = "1.114.0" xzVersion = "1.10" [libraries] classgraph = { group = "io.github.classgraph", name = "classgraph", version.ref = "classgraphVersion" } commonsCompress = { group = "org.apache.commons", name = "commons-compress", version.ref = "commonsCompressVersion" } commonsIo = { group = "commons-io", name = "commons-io", version.ref = "commonsIoVersion" } +jacksonDatabindNullable = { group = "org.openapitools", name = "jackson-databind-nullable", version.ref = "jacksonDatabindNullableVersion" } jakartaAnnotation = { group = "jakarta.annotation", name = "jakarta.annotation-api", version.ref = "jakartaAnnotationVersion" } javaxAnnotation = { group = "javax.annotation", name = "javax.annotation-api", version.ref = "javaxAnnotationVersion" } jerseyClient = { group = "org.glassfish.jersey.core", name = "jersey-client", version.ref = "jerseyVersion" } diff --git a/src/main/java/io/seqera/tower/cli/commands/credentials/add/AbstractAddCmd.java b/src/main/java/io/seqera/tower/cli/commands/credentials/add/AbstractAddCmd.java index 29040dd5..99c0537d 100644 --- a/src/main/java/io/seqera/tower/cli/commands/credentials/add/AbstractAddCmd.java +++ b/src/main/java/io/seqera/tower/cli/commands/credentials/add/AbstractAddCmd.java @@ -58,7 +58,7 @@ protected Response exec() throws ApiException, IOException { if (overwrite) tryDeleteCredentials(name, wspId); - CreateCredentialsResponse resp = credentialsApi().createCredentials(new CreateCredentialsRequest().credentials(specs), wspId); + CreateCredentialsResponse resp = credentialsApi().createCredentials(new CreateCredentialsRequest().credentials(specs), wspId, getProvider().useExternalId()); return new CredentialsAdded(getProvider().type().name(), resp.getCredentialsId(), name, workspaceRef(wspId)); } diff --git a/src/main/java/io/seqera/tower/cli/commands/credentials/providers/AwsProvider.java b/src/main/java/io/seqera/tower/cli/commands/credentials/providers/AwsProvider.java index e6e23401..5b6c58cd 100644 --- a/src/main/java/io/seqera/tower/cli/commands/credentials/providers/AwsProvider.java +++ b/src/main/java/io/seqera/tower/cli/commands/credentials/providers/AwsProvider.java @@ -16,6 +16,7 @@ package io.seqera.tower.cli.commands.credentials.providers; +import io.seqera.tower.model.AwsCredentialsMode; import io.seqera.tower.model.AwsSecurityKeys; import io.seqera.tower.model.Credentials.ProviderEnum; import picocli.CommandLine.ArgGroup; @@ -25,16 +26,30 @@ public class AwsProvider extends AbstractProvider { @ArgGroup(exclusive = false) public Keys keys; + @Option(names = {"-r", "--assume-role-arn"}, description = "IAM role ARN to assume for accessing AWS resources. Allows cross-account access or privilege elevation. Must be a fully qualified ARN (e.g., arn:aws:iam::123456789012:role/RoleName).") String assumeRoleArn; + @Option(names = {"--mode"}, description = "AWS credential mode: 'keys' (access key + secret key) or 'role' (IAM role only). Default: keys.") + String mode; + + @Option(names = {"--generate-external-id"}, description = "Generate a platform-managed External ID for the credential (used with IAM role ARN).", defaultValue = "false") + boolean generateExternalId; + public AwsProvider() { super(ProviderEnum.AWS); } @Override public AwsSecurityKeys securityKeys() { + validate(); + AwsSecurityKeys result = new AwsSecurityKeys(); + + if (getMode() != null) { + result.mode(getMode()); + } + if (keys != null) { result.accessKey(keys.accessKey).secretKey(keys.secretKey); } @@ -46,6 +61,46 @@ public AwsSecurityKeys securityKeys() { return result; } + @Override + public Boolean useExternalId() { + AwsCredentialsMode mode = getMode(); + if (mode == AwsCredentialsMode.role) { + return true; + } + if (generateExternalId && assumeRoleArn != null) { + return true; + } + return null; + } + + private AwsCredentialsMode getMode() { + if (mode == null) { + return null; + } + return switch (mode.toLowerCase()) { + case "keys" -> AwsCredentialsMode.keys; + case "role" -> AwsCredentialsMode.role; + default -> throw new IllegalArgumentException(String.format("Invalid AWS credential mode '%s'. Allowed values: 'keys', 'role'.", mode)); + }; + } + + private void validate() { + AwsCredentialsMode mode = getMode(); + + if (mode == AwsCredentialsMode.role) { + if (keys != null && (keys.accessKey != null || keys.secretKey != null)) { + throw new IllegalArgumentException("Options '--access-key' and '--secret-key' cannot be used with '--mode=role'. Role mode uses IAM role assumption without static credentials."); + } + if (assumeRoleArn == null) { + throw new IllegalArgumentException("Option '--assume-role-arn' is required when using '--mode=role'."); + } + } + + if (generateExternalId && mode != AwsCredentialsMode.role && assumeRoleArn == null) { + throw new IllegalArgumentException("Option '--generate-external-id' requires '--assume-role-arn' to be specified."); + } + } + public static class Keys { @Option(names = {"-a", "--access-key"}, description = "AWS access key identifier. Part of AWS IAM credentials used for programmatic access to AWS services.") @@ -54,4 +109,4 @@ public static class Keys { @Option(names = {"-s", "--secret-key"}, description = "AWS secret access key. Part of AWS IAM credentials used for programmatic access to AWS services. Keep this value secure.") String secretKey; } -} +} \ No newline at end of file diff --git a/src/main/java/io/seqera/tower/cli/commands/credentials/providers/CredentialsProvider.java b/src/main/java/io/seqera/tower/cli/commands/credentials/providers/CredentialsProvider.java index 169fb46f..d95091a1 100644 --- a/src/main/java/io/seqera/tower/cli/commands/credentials/providers/CredentialsProvider.java +++ b/src/main/java/io/seqera/tower/cli/commands/credentials/providers/CredentialsProvider.java @@ -28,4 +28,8 @@ public interface CredentialsProvider { ProviderEnum type(); SecurityKeys securityKeys() throws IOException, ApiException; + + default Boolean useExternalId() { + return null; + } } diff --git a/src/main/java/io/seqera/tower/cli/commands/credentials/update/AbstractUpdateCmd.java b/src/main/java/io/seqera/tower/cli/commands/credentials/update/AbstractUpdateCmd.java index 13fdc6cb..2f5c7158 100644 --- a/src/main/java/io/seqera/tower/cli/commands/credentials/update/AbstractUpdateCmd.java +++ b/src/main/java/io/seqera/tower/cli/commands/credentials/update/AbstractUpdateCmd.java @@ -76,7 +76,7 @@ protected Response update(Credentials creds, Long wspId) throws ApiException, IO .provider(getProvider().type()) .id(creds.getId()); - credentialsApi().updateCredentials(creds.getId(), new UpdateCredentialsRequest().credentials(specs), wspId); + credentialsApi().updateCredentials(creds.getId(), new UpdateCredentialsRequest().credentials(specs), wspId, getProvider().useExternalId()); return new CredentialsUpdated(getProvider().type().name(), name, workspaceRef(wspId)); } diff --git a/src/test/java/io/seqera/tower/cli/InfoCmdTest.java b/src/test/java/io/seqera/tower/cli/InfoCmdTest.java index 693ee3f7..d3d786d3 100644 --- a/src/test/java/io/seqera/tower/cli/InfoCmdTest.java +++ b/src/test/java/io/seqera/tower/cli/InfoCmdTest.java @@ -56,7 +56,7 @@ void testInfo(OutputType format, MockServerClient mock) throws IOException { Map opts = new HashMap<>(); opts.put("cliVersion", getCliVersion() ); opts.put("cliApiVersion", getCliApiVersion()); - opts.put("towerApiVersion", "1.109.0"); + opts.put("towerApiVersion", "1.114.0"); opts.put("towerVersion", "22.3.0-torricelli"); opts.put("towerApiEndpoint", "http://localhost:"+mock.getPort()); opts.put("userName", "jordi"); @@ -86,7 +86,7 @@ void testInfoStatusTokenFail(MockServerClient mock) throws IOException { Map opts = new HashMap<>(); opts.put("cliVersion", getCliVersion() ); opts.put("cliApiVersion", getCliApiVersion()); - opts.put("towerApiVersion", "1.109.0"); + opts.put("towerApiVersion", "1.114.0"); opts.put("towerVersion", "22.3.0-torricelli"); opts.put("towerApiEndpoint", "http://localhost:"+mock.getPort()); opts.put("userName", null); diff --git a/src/test/java/io/seqera/tower/cli/credentials/providers/AwsProviderTest.java b/src/test/java/io/seqera/tower/cli/credentials/providers/AwsProviderTest.java index 8c124adc..59efabf9 100644 --- a/src/test/java/io/seqera/tower/cli/credentials/providers/AwsProviderTest.java +++ b/src/test/java/io/seqera/tower/cli/credentials/providers/AwsProviderTest.java @@ -33,6 +33,7 @@ import static io.seqera.tower.cli.commands.AbstractApiCmd.USER_WORKSPACE_NAME; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockserver.matchers.Times.exactly; import static org.mockserver.model.HttpRequest.request; import static org.mockserver.model.HttpResponse.response; @@ -80,6 +81,89 @@ void testAdd(OutputType format, MockServerClient mock) { } + @ParameterizedTest + @EnumSource(OutputType.class) + void testAddWithExplicitKeysMode(OutputType format, MockServerClient mock) { + + mock.when( + request() + .withMethod("POST") + .withPath("/credentials") + .withBody(json("{\"credentials\":{\"keys\":{\"mode\":\"keys\",\"accessKey\":\"access_key\",\"secretKey\":\"secret_key\"},\"name\":\"aws-keys\",\"provider\":\"aws\"}}")), + exactly(1) + ).respond( + response().withStatusCode(200).withBody("{\"credentialsId\":\"2cz5A8cuBkB5iJliCwJCFU\"}").withContentType(MediaType.APPLICATION_JSON) + ); + + ExecOut out = exec(format, mock, "credentials", "add", "aws", "-n", "aws-keys", "--mode=keys", "-a", "access_key", "-s", "secret_key"); + assertOutput(format, out, new CredentialsAdded("AWS", "2cz5A8cuBkB5iJliCwJCFU", "aws-keys", USER_WORKSPACE_NAME)); + } + + @ParameterizedTest + @EnumSource(OutputType.class) + void testAddWithRoleMode(OutputType format, MockServerClient mock) { + + mock.when( + request() + .withMethod("POST") + .withPath("/credentials") + .withQueryStringParameter("useExternalId", "true") + .withBody(json("{\"credentials\":{\"keys\":{\"mode\":\"role\",\"assumeRoleArn\":\"arn:aws:iam::123456789012:role/MyRole\"},\"name\":\"aws-role\",\"provider\":\"aws\"}}")), + exactly(1) + ).respond( + response().withStatusCode(200).withBody("{\"credentialsId\":\"3cz5A8cuBkB5iJliCwJCFU\"}").withContentType(MediaType.APPLICATION_JSON) + ); + + ExecOut out = exec(format, mock, "credentials", "add", "aws", "-n", "aws-role", "--mode=role", "-r", "arn:aws:iam::123456789012:role/MyRole"); + assertOutput(format, out, new CredentialsAdded("AWS", "3cz5A8cuBkB5iJliCwJCFU", "aws-role", USER_WORKSPACE_NAME)); + } + + @ParameterizedTest + @EnumSource(OutputType.class) + void testAddKeysModeWithGenerateExternalId(OutputType format, MockServerClient mock) { + + mock.when( + request() + .withMethod("POST") + .withPath("/credentials") + .withQueryStringParameter("useExternalId", "true") + .withBody(json("{\"credentials\":{\"keys\":{\"accessKey\":\"access_key\",\"secretKey\":\"secret_key\",\"assumeRoleArn\":\"arn_role\"},\"name\":\"aws-ext\",\"provider\":\"aws\"}}")), + exactly(1) + ).respond( + response().withStatusCode(200).withBody("{\"credentialsId\":\"4cz5A8cuBkB5iJliCwJCFU\"}").withContentType(MediaType.APPLICATION_JSON) + ); + + ExecOut out = exec(format, mock, "credentials", "add", "aws", "-n", "aws-ext", "-a", "access_key", "-s", "secret_key", "-r", "arn_role", "--generate-external-id"); + assertOutput(format, out, new CredentialsAdded("AWS", "4cz5A8cuBkB5iJliCwJCFU", "aws-ext", USER_WORKSPACE_NAME)); + } + + @Test + void testAddRoleModeRejectsAccessKeys(MockServerClient mock) { + + ExecOut out = exec(mock, "credentials", "add", "aws", "-n", "aws-role-bad", "--mode=role", "-a", "access_key", "-s", "secret_key", "-r", "arn_role"); + + assertTrue(out.stdErr.contains("'--access-key' and '--secret-key' cannot be used with '--mode=role'"), "Expected error about access keys not allowed in role mode, got: " + out.stdErr); + assertEquals(1, out.exitCode); + } + + @Test + void testAddRoleModeRequiresAssumeRoleArn(MockServerClient mock) { + + ExecOut out = exec(mock, "credentials", "add", "aws", "-n", "aws-role-bad", "--mode=role"); + + assertTrue(out.stdErr.contains("'--assume-role-arn' is required when using '--mode=role'"), "Expected error about missing assume-role-arn, got: " + out.stdErr); + assertEquals(1, out.exitCode); + } + + @Test + void testAddGenerateExternalIdRequiresAssumeRoleArn(MockServerClient mock) { + + ExecOut out = exec(mock, "credentials", "add", "aws", "-n", "aws-ext-bad", "-a", "access_key", "-s", "secret_key", "--generate-external-id"); + + assertTrue(out.stdErr.contains("'--generate-external-id' requires '--assume-role-arn'"), "Expected error about missing assume-role-arn for generate-external-id, got: " + out.stdErr); + assertEquals(1, out.exitCode); + } + @ParameterizedTest @EnumSource(OutputType.class) void testUpdate(OutputType format, MockServerClient mock) { @@ -104,6 +188,55 @@ void testUpdate(OutputType format, MockServerClient mock) { assertOutput(format, out, new CredentialsUpdated("AWS", "aws", USER_WORKSPACE_NAME)); } + @ParameterizedTest + @EnumSource(OutputType.class) + void testUpdateWithRoleMode(OutputType format, MockServerClient mock) { + + mock.when( + request().withMethod("GET").withPath("/credentials/kfKx9xRgzpIIZrbCMOcU4"), exactly(1) + ).respond( + response().withStatusCode(200).withBody("{\"credentials\":{\"id\":\"kfKx9xRgzpIIZrbCMOcU4\",\"name\":\"aws\",\"description\":null,\"discriminator\":\"aws\",\"baseUrl\":null,\"category\":null,\"deleted\":null,\"lastUsed\":\"2021-09-06T15:16:52Z\",\"dateCreated\":\"2021-09-03T13:23:37Z\",\"lastUpdated\":\"2021-09-03T13:23:37Z\"}}").withContentType(MediaType.APPLICATION_JSON) + ); + + mock.when( + request() + .withMethod("PUT") + .withPath("/credentials/kfKx9xRgzpIIZrbCMOcU4") + .withQueryStringParameter("useExternalId", "true") + .withBody(json("{\"credentials\":{\"keys\":{\"mode\":\"role\",\"assumeRoleArn\":\"arn:aws:iam::123456789012:role/NewRole\"},\"id\":\"kfKx9xRgzpIIZrbCMOcU4\",\"name\":\"aws\",\"provider\":\"aws\"}}")) + .withContentType(MediaType.APPLICATION_JSON) + ).respond( + response().withStatusCode(204) + ); + + ExecOut out = exec(format, mock, "credentials", "update", "aws", "-i", "kfKx9xRgzpIIZrbCMOcU4", "--mode=role", "-r", "arn:aws:iam::123456789012:role/NewRole"); + assertOutput(format, out, new CredentialsUpdated("AWS", "aws", USER_WORKSPACE_NAME)); + } + + @ParameterizedTest + @EnumSource(OutputType.class) + void testUpdateWithKeysMode(OutputType format, MockServerClient mock) { + + mock.when( + request().withMethod("GET").withPath("/credentials/kfKx9xRgzpIIZrbCMOcU4"), exactly(1) + ).respond( + response().withStatusCode(200).withBody("{\"credentials\":{\"id\":\"kfKx9xRgzpIIZrbCMOcU4\",\"name\":\"aws\",\"description\":null,\"discriminator\":\"aws\",\"baseUrl\":null,\"category\":null,\"deleted\":null,\"lastUsed\":\"2021-09-06T15:16:52Z\",\"dateCreated\":\"2021-09-03T13:23:37Z\",\"lastUpdated\":\"2021-09-03T13:23:37Z\"}}").withContentType(MediaType.APPLICATION_JSON) + ); + + mock.when( + request() + .withMethod("PUT") + .withPath("/credentials/kfKx9xRgzpIIZrbCMOcU4") + .withBody(json("{\"credentials\":{\"keys\":{\"mode\":\"keys\",\"accessKey\":\"new_key\",\"secretKey\":\"new_secret\"},\"id\":\"kfKx9xRgzpIIZrbCMOcU4\",\"name\":\"aws\",\"provider\":\"aws\"}}")) + .withContentType(MediaType.APPLICATION_JSON) + ).respond( + response().withStatusCode(204) + ); + + ExecOut out = exec(format, mock, "credentials", "update", "aws", "-i", "kfKx9xRgzpIIZrbCMOcU4", "--mode=keys", "-a", "new_key", "-s", "new_secret"); + assertOutput(format, out, new CredentialsUpdated("AWS", "aws", USER_WORKSPACE_NAME)); + } + @Test void testUpdateNotFound(MockServerClient mock) { @@ -135,4 +268,4 @@ void testInvalidAuth(MockServerClient mock) { assertEquals(1, out.exitCode); } -} +} \ No newline at end of file diff --git a/src/test/resources/runcmd/info/service-info.json b/src/test/resources/runcmd/info/service-info.json index a78a8c19..5810d77e 100644 --- a/src/test/resources/runcmd/info/service-info.json +++ b/src/test/resources/runcmd/info/service-info.json @@ -1,7 +1,7 @@ { "serviceInfo": { "version": "22.3.0-torricelli", - "apiVersion": "1.109.0", + "apiVersion": "1.114.0", "commitId": "3f04bfd4", "authTypes": [ "github",