diff --git a/USAGE.md b/USAGE.md index d6c1af6f..31f38bb1 100644 --- a/USAGE.md +++ b/USAGE.md @@ -722,9 +722,11 @@ tw roles permissions -o my-org tw roles add -o my-org -n launcher -d "Launch only" -p , ``` -### Teams and quotas +### Teams, IdP groups and SCIM -- `tw teams view|update|workspaces`: view a team, update its name or description, and list its workspaces. +- `tw teams view|update|workspaces`: view a team, update its name, description or linked IdP group (`--idp-group`, `--unlink-idp-group`), and list its workspaces. +- `tw organizations idp-groups list|add|delete`: manage the identity provider groups available to the organization. +- `tw organizations scim view|create-token|rotate-token|revoke-token`: manage SCIM provisioning. - `tw organizations quotas` and `tw members roles -u `: view organization quotas and a member's workspace roles. ## Personal access @@ -754,10 +756,19 @@ tw managed-identities add -o my-org -n hpc1 -p slurm -H login.hpc.example.com tw managed-identities credentials -o my-org -n hpc1 add -l my_linux_user -k ~/.ssh/id_rsa ``` +## Audit logs + +Run `tw audit-logs -h` to view the supported operations: `list`, `view`, and `export`. These commands require a root user. + +```bash +tw audit-logs export --after 2026-09-01T00:00:00Z --before 2026-09-30T23:59:59Z -o september.csv +``` + ## Other additions - `tw compute-envs enable|disable`: enable or disable a compute environment. - `tw compute-envs add`: + - Intelligent Compute options on `aws-cloud`, `azure-cloud`, and `google-cloud`. Run `tw compute-envs add -h` for the options each platform supports. - AWS Batch: CloudWatch log group and DRAGEN options. - AWS Cloud: `--log-group`. Google Cloud: `--project-id`. - HPC platforms: `--propagate-head-job-options`. diff --git a/conf/reflect-config.json b/conf/reflect-config.json index d55fccd0..0bc736cb 100644 --- a/conf/reflect-config.json +++ b/conf/reflect-config.json @@ -166,6 +166,12 @@ "allDeclaredMethods":true, "methods":[{"name":"","parameterTypes":[] }] }, +{ + "name":"io.seqera.tower.cli.commands.AuditLogsCmd", + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"io.seqera.tower.cli.commands.CollaboratorsCmd", "allDeclaredFields":true, @@ -403,6 +409,40 @@ "allDeclaredMethods":true, "methods":[{"name":"","parameterTypes":[] }] }, +{ + "name":"io.seqera.tower.cli.commands.auditlogs.AuditLogsFilterOptions", + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "fields":[{"name":"after"}, {"name":"before"}], + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"io.seqera.tower.cli.commands.auditlogs.AuditLogsFilterOptions$DateTimeConverter", + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"io.seqera.tower.cli.commands.auditlogs.ExportCmd", + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "fields":[{"name":"filter"}, {"name":"output"}, {"name":"state"}], + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"io.seqera.tower.cli.commands.auditlogs.ListCmd", + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "fields":[{"name":"filter"}, {"name":"max"}, {"name":"pageToken"}], + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"io.seqera.tower.cli.commands.auditlogs.ViewCmd", + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "fields":[{"name":"id"}], + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"io.seqera.tower.cli.commands.collaborators.AbstractCollaboratorsCmd", "allDeclaredFields":true, @@ -1664,6 +1704,12 @@ "allDeclaredMethods":true, "methods":[{"name":"","parameterTypes":[] }] }, +{ + "name":"io.seqera.tower.cli.commands.organizations.IdpGroupsCmd", + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"io.seqera.tower.cli.commands.organizations.ListCmd", "allDeclaredFields":true, @@ -1695,6 +1741,12 @@ "fields":[{"name":"organizationRefOptions"}], "methods":[{"name":"","parameterTypes":[] }] }, +{ + "name":"io.seqera.tower.cli.commands.organizations.ScimCmd", + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"io.seqera.tower.cli.commands.organizations.UpdateCmd", "allDeclaredFields":true, @@ -1707,6 +1759,62 @@ "allDeclaredMethods":true, "methods":[{"name":"","parameterTypes":[] }] }, +{ + "name":"io.seqera.tower.cli.commands.organizations.idpgroups.AddCmd", + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "fields":[{"name":"name"}, {"name":"organizationRef"}], + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"io.seqera.tower.cli.commands.organizations.idpgroups.DeleteCmd", + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "fields":[{"name":"group"}, {"name":"organizationRef"}], + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"io.seqera.tower.cli.commands.organizations.idpgroups.DeleteCmd$GroupRef", + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "fields":[{"name":"id"}, {"name":"name"}], + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"io.seqera.tower.cli.commands.organizations.idpgroups.ListCmd", + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "fields":[{"name":"organizationRef"}], + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"io.seqera.tower.cli.commands.organizations.scim.CreateTokenCmd", + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "fields":[{"name":"organizationRef"}], + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"io.seqera.tower.cli.commands.organizations.scim.RevokeTokenCmd", + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "fields":[{"name":"organizationRef"}], + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"io.seqera.tower.cli.commands.organizations.scim.RotateTokenCmd", + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "fields":[{"name":"organizationRef"}], + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"io.seqera.tower.cli.commands.organizations.scim.ViewCmd", + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "fields":[{"name":"organizationRef"}], + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"io.seqera.tower.cli.commands.participants.AbstractParticipantsCmd", "allDeclaredFields":true, @@ -2326,6 +2434,13 @@ "fields":[{"name":"description"}, {"name":"idpGroup"}, {"name":"newName"}, {"name":"organizationRef"}, {"name":"ref"}], "methods":[{"name":"","parameterTypes":[] }] }, +{ + "name":"io.seqera.tower.cli.commands.teams.UpdateCmd$IdpGroup", + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "fields":[{"name":"name"}, {"name":"unlink"}], + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"io.seqera.tower.cli.commands.teams.ViewCmd", "queryAllDeclaredMethods":true, @@ -2531,6 +2646,24 @@ "allDeclaredMethods":true, "allDeclaredConstructors":true }, +{ + "name":"io.seqera.tower.cli.responses.auditlogs.AuditLogView", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true +}, +{ + "name":"io.seqera.tower.cli.responses.auditlogs.AuditLogsExported", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true +}, +{ + "name":"io.seqera.tower.cli.responses.auditlogs.AuditLogsList", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true +}, { "name":"io.seqera.tower.cli.responses.collaborators.CollaboratorsList", "allDeclaredFields":true, @@ -2848,6 +2981,24 @@ "allDeclaredMethods":true, "allDeclaredConstructors":true }, +{ + "name":"io.seqera.tower.cli.responses.organizations.IdpGroupAdded", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true +}, +{ + "name":"io.seqera.tower.cli.responses.organizations.IdpGroupDeleted", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true +}, +{ + "name":"io.seqera.tower.cli.responses.organizations.IdpGroupsList", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true +}, { "name":"io.seqera.tower.cli.responses.organizations.OrganizationQuotasView", "allDeclaredFields":true, @@ -2890,6 +3041,28 @@ "queryAllDeclaredMethods":true, "queryAllDeclaredConstructors":true }, +{ + "name":"io.seqera.tower.cli.responses.organizations.ScimConfigView", + "allDeclaredFields":true, + "allDeclaredMethods":true, + "allDeclaredConstructors":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true +}, +{ + "name":"io.seqera.tower.cli.responses.organizations.ScimTokenCreated", + "allDeclaredFields":true, + "allDeclaredMethods":true, + "allDeclaredConstructors":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true +}, +{ + "name":"io.seqera.tower.cli.responses.organizations.ScimTokenRevoked", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true +}, { "name":"io.seqera.tower.cli.responses.participants.ParticipantAdded", "allDeclaredFields":true, diff --git a/src/main/java/io/seqera/tower/cli/Tower.java b/src/main/java/io/seqera/tower/cli/Tower.java index 68c8c9e7..74a92294 100644 --- a/src/main/java/io/seqera/tower/cli/Tower.java +++ b/src/main/java/io/seqera/tower/cli/Tower.java @@ -23,6 +23,7 @@ import io.seqera.tower.cli.commands.AbstractCmd; import io.seqera.tower.cli.commands.AccessTokensCmd; import io.seqera.tower.cli.commands.ActionsCmd; +import io.seqera.tower.cli.commands.AuditLogsCmd; import io.seqera.tower.cli.commands.CollaboratorsCmd; import io.seqera.tower.cli.commands.ComputeEnvsCmd; import io.seqera.tower.cli.commands.CredentialsCmd; @@ -62,6 +63,7 @@ subcommands = { AccessTokensCmd.class, ActionsCmd.class, + AuditLogsCmd.class, CollaboratorsCmd.class, ComputeEnvsCmd.class, CredentialsCmd.class, diff --git a/src/main/java/io/seqera/tower/cli/commands/AbstractApiCmd.java b/src/main/java/io/seqera/tower/cli/commands/AbstractApiCmd.java index 628f90f0..cf52ab0e 100644 --- a/src/main/java/io/seqera/tower/cli/commands/AbstractApiCmd.java +++ b/src/main/java/io/seqera/tower/cli/commands/AbstractApiCmd.java @@ -19,6 +19,7 @@ import io.seqera.tower.ApiClient; import io.seqera.tower.ApiException; import io.seqera.tower.api.ActionsApi; +import io.seqera.tower.api.AdminApi; import io.seqera.tower.api.AvatarsApi; import io.seqera.tower.api.ComputeEnvsApi; import io.seqera.tower.api.CredentialsApi; @@ -63,6 +64,7 @@ import io.seqera.tower.model.ComputeEnvResponseDto; import io.seqera.tower.model.Credentials; import io.seqera.tower.model.DataStudioQueryAttribute; +import io.seqera.tower.model.IdpGroupEntry; import io.seqera.tower.model.ListComputeEnvsResponseEntry; import io.seqera.tower.model.ListPipelineVersionsResponse; import io.seqera.tower.model.ListWorkspacesAndOrgResponse; @@ -206,6 +208,10 @@ protected ActionsApi actionsApi() throws ApiException { return actionsApi == null ? new ActionsApi(apiClient()) : actionsApi; } + protected AdminApi adminApi() throws ApiException { + return new AdminApi(apiClient()); + } + protected AvatarsApi avatarsApi() throws ApiException { return avatarsApi == null ? new AvatarsApi(apiClient()) : avatarsApi; } @@ -548,6 +554,14 @@ protected OrgAndWorkspaceDto findOrganizationByRef(String organizationRef) throw return orgAndWorkspaceDbDtoList.stream().findFirst().orElseThrow(() -> new OrganizationNotFoundException(organizationRef)); } + protected IdpGroupEntry findIdpGroupByName(Long orgId, String displayName) throws ApiException { + return orgsApi().listOrganizationIdpGroups(orgId).getGroups() + .stream() + .filter(group -> Objects.equals(group.getDisplayName(), displayName)) + .findFirst() + .orElseThrow(() -> new TowerException(String.format("IdP group '%s' not found in organization '%d'", displayName, orgId))); + } + protected List findOrCreateLabels(Long wspId, List