diff --git a/USAGE.md b/USAGE.md index 153a0c54..6ac4d567 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 @@ -764,10 +766,19 @@ tw lineage resolve -w my-org/my-workspace --session-id --run-name < tw lineage upstream -w my-org/my-workspace -i ``` +## 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 53ad4b87..42da7ad6 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, @@ -395,6 +401,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, @@ -1735,6 +1775,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, @@ -1766,6 +1812,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, @@ -1778,6 +1830,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, @@ -2418,6 +2526,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, @@ -2642,6 +2757,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, @@ -2977,6 +3110,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, @@ -3019,6 +3170,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 b90593d9..be9bb526 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; @@ -63,6 +64,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 7bbf072a..4fd2788e 100644 --- a/src/main/java/io/seqera/tower/cli/commands/AbstractApiCmd.java +++ b/src/main/java/io/seqera/tower/cli/commands/AbstractApiCmd.java @@ -21,6 +21,7 @@ import io.seqera.tower.ApiResponse; import io.seqera.tower.Pair; import io.seqera.tower.api.ActionsApi; +import io.seqera.tower.api.AdminApi; import io.seqera.tower.api.AvatarsApi; import io.seqera.tower.api.CompatibilityApi; import io.seqera.tower.api.ComputeEnvsApi; @@ -69,6 +70,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; @@ -215,6 +217,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; } @@ -580,6 +586,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