From e8c93e912f5e59cda81050be0df61dd0756feb2c Mon Sep 17 00:00:00 2001 From: Thomas Kooi Date: Fri, 20 Mar 2026 18:05:49 +0100 Subject: [PATCH 1/2] feat(iam): Implement IAM commands for federated identities and identity providers - Added `iam` command to manage identity and access management functionalities. - Introduced commands for managing federated identities: create, delete, get, list, and update. - Added commands for managing federated identity providers: create, delete, get, list, and update. --- cmd/cmd.go | 2 + cmd/iam/federatedidentities/create.go | 110 ++++ cmd/iam/federatedidentities/delete.go | 45 ++ .../federatedidentities.go | 10 + cmd/iam/federatedidentities/get.go | 61 ++ cmd/iam/federatedidentities/list.go | 73 +++ cmd/iam/federatedidentities/update.go | 118 ++++ cmd/iam/federatedidentityproviders/create.go | 82 +++ cmd/iam/federatedidentityproviders/delete.go | 45 ++ .../federatedidentityproviders.go | 10 + cmd/iam/federatedidentityproviders/get.go | 46 ++ cmd/iam/federatedidentityproviders/list.go | 68 +++ cmd/iam/federatedidentityproviders/update.go | 89 +++ cmd/iam/iam.go | 34 ++ cmd/iam/internal/shared/federated.go | 62 ++ cmd/iam/internal/shared/shared.go | 65 +++ cmd/iam/internal/shared/shared_test.go | 46 ++ cmd/iam/invites/invites.go | 9 + cmd/iam/invites/list.go | 62 ++ cmd/iam/members/delete.go | 45 ++ cmd/iam/members/list.go | 57 ++ cmd/iam/members/members.go | 9 + cmd/iam/members/update.go | 50 ++ cmd/iam/roles/bindings/bindings.go | 9 + cmd/iam/roles/bindings/create.go | 105 ++++ cmd/iam/roles/bindings/delete.go | 50 ++ cmd/iam/roles/bindings/list.go | 64 +++ cmd/iam/roles/create.go | 61 ++ cmd/iam/roles/delete.go | 45 ++ cmd/iam/roles/get.go | 82 +++ cmd/iam/roles/list.go | 77 +++ cmd/iam/roles/roles.go | 21 + cmd/iam/roles/rules/add.go | 72 +++ cmd/iam/roles/rules/delete.go | 45 ++ cmd/iam/roles/rules/rules.go | 9 + cmd/iam/serviceaccounts/create.go | 67 +++ cmd/iam/serviceaccounts/delete.go | 45 ++ cmd/iam/serviceaccounts/get.go | 57 ++ cmd/iam/serviceaccounts/list.go | 72 +++ cmd/iam/serviceaccounts/serviceaccounts.go | 10 + cmd/iam/serviceaccounts/update.go | 70 +++ cmd/iam/teams/create.go | 63 +++ cmd/iam/teams/delete.go | 46 ++ cmd/iam/teams/get.go | 56 ++ cmd/iam/teams/list.go | 70 +++ cmd/iam/teams/members/add.go | 49 ++ cmd/iam/teams/members/list.go | 48 ++ cmd/iam/teams/members/members.go | 9 + cmd/iam/teams/members/remove.go | 46 ++ cmd/iam/teams/teams.go | 17 + cmd/iam/teams/update.go | 78 +++ .../workloadidentityfederation/bootstrap.go | 114 ++++ .../bootstrap_github.go | 65 +++ .../bootstrap_gitlab.go | 61 ++ .../bootstrap_kubernetes.go | 67 +++ .../bootstrap_output.go | 108 ++++ .../bootstrap_run.go | 533 ++++++++++++++++++ .../bootstrap_run_test.go | 62 ++ .../bootstrap_test.go | 33 ++ .../bootstrap_types.go | 56 ++ cmd/iam/workloadidentityfederation/hints.go | 131 +++++ .../kubernetes_resolve.go | 31 + cmd/iam/workloadidentityfederation/labels.go | 21 + cmd/iam/workloadidentityfederation/subject.go | 91 +++ .../subject_test.go | 81 +++ .../workloadidentityfederation.go | 16 + cmd/kubernetes/create.go | 2 - cmd/kubernetes/nodepools/update.go | 1 - cmd/kubernetes/update.go | 1 - internal/completion/iam.go | 393 +++++++++++++ internal/iamresolve/organisation_role.go | 42 ++ internal/iamresolve/organisation_role_test.go | 126 +++++ 72 files changed, 4672 insertions(+), 4 deletions(-) create mode 100644 cmd/iam/federatedidentities/create.go create mode 100644 cmd/iam/federatedidentities/delete.go create mode 100644 cmd/iam/federatedidentities/federatedidentities.go create mode 100644 cmd/iam/federatedidentities/get.go create mode 100644 cmd/iam/federatedidentities/list.go create mode 100644 cmd/iam/federatedidentities/update.go create mode 100644 cmd/iam/federatedidentityproviders/create.go create mode 100644 cmd/iam/federatedidentityproviders/delete.go create mode 100644 cmd/iam/federatedidentityproviders/federatedidentityproviders.go create mode 100644 cmd/iam/federatedidentityproviders/get.go create mode 100644 cmd/iam/federatedidentityproviders/list.go create mode 100644 cmd/iam/federatedidentityproviders/update.go create mode 100644 cmd/iam/iam.go create mode 100644 cmd/iam/internal/shared/federated.go create mode 100644 cmd/iam/internal/shared/shared.go create mode 100644 cmd/iam/internal/shared/shared_test.go create mode 100644 cmd/iam/invites/invites.go create mode 100644 cmd/iam/invites/list.go create mode 100644 cmd/iam/members/delete.go create mode 100644 cmd/iam/members/list.go create mode 100644 cmd/iam/members/members.go create mode 100644 cmd/iam/members/update.go create mode 100644 cmd/iam/roles/bindings/bindings.go create mode 100644 cmd/iam/roles/bindings/create.go create mode 100644 cmd/iam/roles/bindings/delete.go create mode 100644 cmd/iam/roles/bindings/list.go create mode 100644 cmd/iam/roles/create.go create mode 100644 cmd/iam/roles/delete.go create mode 100644 cmd/iam/roles/get.go create mode 100644 cmd/iam/roles/list.go create mode 100644 cmd/iam/roles/roles.go create mode 100644 cmd/iam/roles/rules/add.go create mode 100644 cmd/iam/roles/rules/delete.go create mode 100644 cmd/iam/roles/rules/rules.go create mode 100644 cmd/iam/serviceaccounts/create.go create mode 100644 cmd/iam/serviceaccounts/delete.go create mode 100644 cmd/iam/serviceaccounts/get.go create mode 100644 cmd/iam/serviceaccounts/list.go create mode 100644 cmd/iam/serviceaccounts/serviceaccounts.go create mode 100644 cmd/iam/serviceaccounts/update.go create mode 100644 cmd/iam/teams/create.go create mode 100644 cmd/iam/teams/delete.go create mode 100644 cmd/iam/teams/get.go create mode 100644 cmd/iam/teams/list.go create mode 100644 cmd/iam/teams/members/add.go create mode 100644 cmd/iam/teams/members/list.go create mode 100644 cmd/iam/teams/members/members.go create mode 100644 cmd/iam/teams/members/remove.go create mode 100644 cmd/iam/teams/teams.go create mode 100644 cmd/iam/teams/update.go create mode 100644 cmd/iam/workloadidentityfederation/bootstrap.go create mode 100644 cmd/iam/workloadidentityfederation/bootstrap_github.go create mode 100644 cmd/iam/workloadidentityfederation/bootstrap_gitlab.go create mode 100644 cmd/iam/workloadidentityfederation/bootstrap_kubernetes.go create mode 100644 cmd/iam/workloadidentityfederation/bootstrap_output.go create mode 100644 cmd/iam/workloadidentityfederation/bootstrap_run.go create mode 100644 cmd/iam/workloadidentityfederation/bootstrap_run_test.go create mode 100644 cmd/iam/workloadidentityfederation/bootstrap_test.go create mode 100644 cmd/iam/workloadidentityfederation/bootstrap_types.go create mode 100644 cmd/iam/workloadidentityfederation/hints.go create mode 100644 cmd/iam/workloadidentityfederation/kubernetes_resolve.go create mode 100644 cmd/iam/workloadidentityfederation/labels.go create mode 100644 cmd/iam/workloadidentityfederation/subject.go create mode 100644 cmd/iam/workloadidentityfederation/subject_test.go create mode 100644 cmd/iam/workloadidentityfederation/workloadidentityfederation.go create mode 100644 internal/completion/iam.go create mode 100644 internal/iamresolve/organisation_role.go create mode 100644 internal/iamresolve/organisation_role_test.go diff --git a/cmd/cmd.go b/cmd/cmd.go index f5706cd..0bce032 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -14,6 +14,7 @@ import ( "github.com/thalassa-cloud/cli/cmd/iaas/networking" "github.com/thalassa-cloud/cli/cmd/iaas/regions" "github.com/thalassa-cloud/cli/cmd/iaas/storage" + "github.com/thalassa-cloud/cli/cmd/iam" "github.com/thalassa-cloud/cli/cmd/kubernetes" "github.com/thalassa-cloud/cli/cmd/me" "github.com/thalassa-cloud/cli/cmd/objectstorage" @@ -69,6 +70,7 @@ func init() { RootCmd.AddCommand(kubernetes.KubernetesCmd) RootCmd.AddCommand(dbaas.DbaasCmd) RootCmd.AddCommand(me.MeCmd) + RootCmd.AddCommand(iam.IamCmd) RootCmd.AddCommand(audit.AuditCmd) RootCmd.AddCommand(oidc.OidcCmd) diff --git a/cmd/iam/federatedidentities/create.go b/cmd/iam/federatedidentities/create.go new file mode 100644 index 0000000..d30fd00 --- /dev/null +++ b/cmd/iam/federatedidentities/create.go @@ -0,0 +1,110 @@ +package federatedidentities + +import ( + "fmt" + + "github.com/spf13/cobra" + + "github.com/thalassa-cloud/cli/cmd/iam/internal/shared" + "github.com/thalassa-cloud/cli/internal/completion" + "github.com/thalassa-cloud/cli/internal/table" + "github.com/thalassa-cloud/cli/internal/thalassaclient" + clientiam "github.com/thalassa-cloud/client-go/iam" +) + +var ( + createName string + createDescription string + createLabels []string + createAnnotations []string + createSA string + createProvider string + createSubject string + createAudiences []string + createAudienceMode string + createScopes []string + createExpiresAt string + createConditions string + createConditionsFile string +) + +var createCmd = &cobra.Command{ + Use: "create", + Short: "Create a federated identity", + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, _ []string) error { + ctx := cmd.Context() + if createName == "" || createSA == "" || createProvider == "" || createSubject == "" { + return fmt.Errorf("--name, --service-account, --provider, and --subject are required") + } + scopes, err := shared.ParseAccessCredentialScopes(createScopes) + if err != nil { + return err + } + mode := clientiam.AudienceMatchMode(createAudienceMode) + if createAudienceMode == "" { + mode = clientiam.AudienceMatchModeAny + } else if mode != clientiam.AudienceMatchModeExact && mode != clientiam.AudienceMatchModeAny && mode != clientiam.AudienceMatchModeAll { + return fmt.Errorf("invalid --audience-match-mode (use exact, any, or all)") + } + expires, err := shared.ParseOptionalRFC3339(createExpiresAt) + if err != nil { + return err + } + conds, err := shared.ParseConditionsJSON(createConditions, createConditionsFile) + if err != nil { + return err + } + client, err := thalassaclient.GetThalassaClient() + if err != nil { + return fmt.Errorf("failed to create client: %w", err) + } + fi, err := client.IAM().CreateFederatedIdentity(ctx, clientiam.CreateFederatedIdentityRequest{ + Name: createName, + Description: createDescription, + Labels: shared.KeyValuePairsToMap(createLabels), + Annotations: shared.KeyValuePairsToMap(createAnnotations), + ServiceAccountIdentity: createSA, + ProviderIdentity: createProvider, + ProviderSubject: createSubject, + TrustedAudiences: createAudiences, + AudienceMatchMode: mode, + AllowedScopes: scopes, + ExpiresAt: expires, + Conditions: conds, + }) + if err != nil { + return fmt.Errorf("failed to create federated identity: %w", err) + } + if fi == nil { + return nil + } + body := [][]string{{fi.Identity, fi.Name, string(fi.Status)}} + if noHeader { + table.Print(nil, body) + } else { + table.Print([]string{"ID", "Name", "Status"}, body) + } + return nil + }, +} + +func init() { + FederatedIdentitiesCmd.AddCommand(createCmd) + createCmd.Flags().BoolVar(&noHeader, shared.NoHeaderKey, false, "Do not print table headers") + createCmd.Flags().StringVar(&createName, "name", "", "Display name") + createCmd.Flags().StringVar(&createDescription, "description", "", "Description") + createCmd.Flags().StringSliceVar(&createLabels, "labels", nil, "Labels as key=value (repeatable)") + createCmd.Flags().StringSliceVar(&createAnnotations, "annotations", nil, "Annotations as key=value (repeatable)") + createCmd.Flags().StringVar(&createSA, "service-account", "", "Service account identity to bind") + createCmd.Flags().StringVar(&createProvider, "provider", "", "Federated identity provider identity") + createCmd.Flags().StringVar(&createSubject, "subject", "", "OIDC sub claim for this identity") + createCmd.Flags().StringSliceVar(&createAudiences, "trusted-audience", nil, "Trusted JWT audiences (repeatable)") + createCmd.Flags().StringVar(&createAudienceMode, "audience-match-mode", "", "exact, any (default), or all") + createCmd.Flags().StringSliceVar(&createScopes, "scope", nil, "Allowed scopes: api:read, api:write, kubernetes, objectStorage (repeatable)") + createCmd.Flags().StringVar(&createExpiresAt, "expires-at", "", "RFC3339 expiry time") + createCmd.Flags().StringVar(&createConditions, "conditions", "", "Conditions as JSON object") + createCmd.Flags().StringVar(&createConditionsFile, "conditions-file", "", "Path to JSON file for conditions") + _ = createCmd.RegisterFlagCompletionFunc("service-account", completion.CompleteIAMServiceAccountIdentityFlag) + _ = createCmd.RegisterFlagCompletionFunc("provider", completion.CompleteIAMFederatedIdentityProviderIdentityFlag) +} diff --git a/cmd/iam/federatedidentities/delete.go b/cmd/iam/federatedidentities/delete.go new file mode 100644 index 0000000..1506a9d --- /dev/null +++ b/cmd/iam/federatedidentities/delete.go @@ -0,0 +1,45 @@ +package federatedidentities + +import ( + "fmt" + + "github.com/spf13/cobra" + + "github.com/thalassa-cloud/cli/cmd/iam/internal/shared" + "github.com/thalassa-cloud/cli/internal/completion" + "github.com/thalassa-cloud/cli/internal/thalassaclient" +) + +var deleteForce bool + +var deleteCmd = &cobra.Command{ + Use: "delete ", + Short: "Delete a federated identity", + Args: cobra.ExactArgs(1), + ValidArgsFunction: completion.CompleteIAMFederatedIdentityIdentity, + RunE: func(cmd *cobra.Command, args []string) error { + ctx := cmd.Context() + client, err := thalassaclient.GetThalassaClient() + if err != nil { + return fmt.Errorf("failed to create client: %w", err) + } + ok, err := shared.PromptDestructiveUnlessForce(deleteForce, fmt.Sprintf("Are you sure you want to delete this federated identity?\n Identity: %s\n", args[0])) + if err != nil { + return err + } + if !ok { + return nil + } + if err := client.IAM().DeleteFederatedIdentity(ctx, args[0]); err != nil { + return fmt.Errorf("failed to delete federated identity: %w", err) + } + fmt.Printf("Deleted federated identity %s\n", args[0]) + return nil + }, +} + +func init() { + FederatedIdentitiesCmd.AddCommand(deleteCmd) + deleteCmd.Flags().BoolVar(&deleteForce, shared.ForceKey, false, "Skip the confirmation prompt and delete") + deleteCmd.Flags().BoolVar(&noHeader, shared.NoHeaderKey, false, "Do not print table headers") +} diff --git a/cmd/iam/federatedidentities/federatedidentities.go b/cmd/iam/federatedidentities/federatedidentities.go new file mode 100644 index 0000000..f73c1b4 --- /dev/null +++ b/cmd/iam/federatedidentities/federatedidentities.go @@ -0,0 +1,10 @@ +package federatedidentities + +import "github.com/spf13/cobra" + +// FederatedIdentitiesCmd manages federated OIDC identities. +var FederatedIdentitiesCmd = &cobra.Command{ + Use: "federated-identities", + Aliases: []string{"fed-ids", "federated-identity"}, + Short: "Federated identities (OIDC subject bindings)", +} diff --git a/cmd/iam/federatedidentities/get.go b/cmd/iam/federatedidentities/get.go new file mode 100644 index 0000000..68c2fed --- /dev/null +++ b/cmd/iam/federatedidentities/get.go @@ -0,0 +1,61 @@ +package federatedidentities + +import ( + "fmt" + "strings" + "time" + + "github.com/spf13/cobra" + + "github.com/thalassa-cloud/cli/cmd/iam/internal/shared" + "github.com/thalassa-cloud/cli/internal/completion" + "github.com/thalassa-cloud/cli/internal/formattime" + "github.com/thalassa-cloud/cli/internal/thalassaclient" +) + +var getCmd = &cobra.Command{ + Use: "get ", + Short: "Show a federated identity", + Args: cobra.ExactArgs(1), + ValidArgsFunction: completion.CompleteIAMFederatedIdentityIdentity, + RunE: func(cmd *cobra.Command, args []string) error { + ctx := cmd.Context() + client, err := thalassaclient.GetThalassaClient() + if err != nil { + return fmt.Errorf("failed to create client: %w", err) + } + fi, err := client.IAM().GetFederatedIdentity(ctx, args[0]) + if err != nil { + return fmt.Errorf("failed to get federated identity: %w", err) + } + fmt.Printf("Identity: %s\n", fi.Identity) + fmt.Printf("Name: %s\n", fi.Name) + fmt.Printf("Description: %s\n", fi.Description) + fmt.Printf("Provider subject: %s\n", fi.ProviderSubject) + fmt.Printf("Status: %s\n", fi.Status) + fmt.Printf("Audience mode: %s\n", fi.AudienceMatchMode) + fmt.Printf("Trusted audiences: %s\n", strings.Join(fi.TrustedAudiences, ",")) + scopes := make([]string, 0, len(fi.AllowedScopes)) + for _, s := range fi.AllowedScopes { + scopes = append(scopes, string(s)) + } + fmt.Printf("Allowed scopes: %s\n", strings.Join(scopes, ",")) + if fi.Provider != nil { + fmt.Printf("Provider: %s (%s)\n", fi.Provider.Name, fi.Provider.Identity) + } + if fi.ServiceAccount != nil { + fmt.Printf("Service account: %s (%s)\n", fi.ServiceAccount.Name, fi.ServiceAccount.Identity) + } + if fi.ExpiresAt != nil { + fmt.Printf("Expires at: %s\n", fi.ExpiresAt.Format(time.RFC3339)) + } + fmt.Printf("Created: %s\n", formattime.FormatTime(fi.CreatedAt.Local(), showExactTime)) + return nil + }, +} + +func init() { + FederatedIdentitiesCmd.AddCommand(getCmd) + getCmd.Flags().BoolVar(&noHeader, shared.NoHeaderKey, false, "Do not print table headers") + getCmd.Flags().BoolVar(&showExactTime, "exact-time", false, "Show full timestamps instead of relative time") +} diff --git a/cmd/iam/federatedidentities/list.go b/cmd/iam/federatedidentities/list.go new file mode 100644 index 0000000..11c4318 --- /dev/null +++ b/cmd/iam/federatedidentities/list.go @@ -0,0 +1,73 @@ +package federatedidentities + +import ( + "fmt" + + "github.com/spf13/cobra" + + "github.com/thalassa-cloud/cli/cmd/iam/internal/shared" + "github.com/thalassa-cloud/cli/internal/formattime" + "github.com/thalassa-cloud/cli/internal/labels" + "github.com/thalassa-cloud/cli/internal/table" + "github.com/thalassa-cloud/cli/internal/thalassaclient" + "github.com/thalassa-cloud/client-go/filters" + clientiam "github.com/thalassa-cloud/client-go/iam" +) + +var ( + noHeader bool + showExactTime bool + listSelector string +) + +var listCmd = &cobra.Command{ + Use: "list", + Aliases: []string{"ls"}, + Short: "List federated identities", + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, _ []string) error { + ctx := cmd.Context() + client, err := thalassaclient.GetThalassaClient() + if err != nil { + return fmt.Errorf("failed to create client: %w", err) + } + req := &clientiam.ListFederatedIdentitiesRequest{} + if listSelector != "" { + req.Filters = []filters.Filter{ + &filters.LabelFilter{MatchLabels: labels.ParseLabelSelector(listSelector)}, + } + } + list, err := client.IAM().ListFederatedIdentities(ctx, req) + if err != nil { + return fmt.Errorf("failed to list federated identities: %w", err) + } + body := make([][]string, 0, len(list)) + for _, fi := range list { + prov := "" + if fi.Provider != nil { + prov = fi.Provider.Name + } + body = append(body, []string{ + fi.Identity, + fi.Name, + fi.ProviderSubject, + prov, + string(fi.Status), + formattime.FormatTime(fi.CreatedAt.Local(), showExactTime), + }) + } + if noHeader { + table.Print(nil, body) + } else { + table.Print([]string{"ID", "Name", "Subject", "Provider", "Status", "Created"}, body) + } + return nil + }, +} + +func init() { + FederatedIdentitiesCmd.AddCommand(listCmd) + listCmd.Flags().BoolVar(&noHeader, shared.NoHeaderKey, false, "Do not print table headers") + listCmd.Flags().BoolVar(&showExactTime, "exact-time", false, "Show full timestamps instead of relative time") + listCmd.Flags().StringVar(&listSelector, "label-selector", "", "Filter by labels (key=value,key2=value2)") +} diff --git a/cmd/iam/federatedidentities/update.go b/cmd/iam/federatedidentities/update.go new file mode 100644 index 0000000..9188bb4 --- /dev/null +++ b/cmd/iam/federatedidentities/update.go @@ -0,0 +1,118 @@ +package federatedidentities + +import ( + "fmt" + + "github.com/spf13/cobra" + + "github.com/thalassa-cloud/cli/cmd/iam/internal/shared" + "github.com/thalassa-cloud/cli/internal/completion" + "github.com/thalassa-cloud/cli/internal/table" + "github.com/thalassa-cloud/cli/internal/thalassaclient" + clientiam "github.com/thalassa-cloud/client-go/iam" +) + +var ( + updateDescription string + updateLabels []string + updateAnnotations []string + updateAudiences []string + updateAudienceMode string + updateScopes []string + updateStatus string + updateExpiresAt string + updateConditions string + updateConditionsFile string +) + +var updateCmd = &cobra.Command{ + Use: "update ", + Short: "Update a federated identity (only set flags are sent)", + Args: cobra.ExactArgs(1), + ValidArgsFunction: completion.CompleteIAMFederatedIdentityIdentity, + RunE: func(cmd *cobra.Command, args []string) error { + ctx := cmd.Context() + client, err := thalassaclient.GetThalassaClient() + if err != nil { + return fmt.Errorf("failed to create client: %w", err) + } + req := clientiam.UpdateFederatedIdentityRequest{} + if cmd.Flags().Changed("description") { + req.Description = updateDescription + } + if cmd.Flags().Changed("labels") { + req.Labels = shared.KeyValuePairsToMap(updateLabels) + } + if cmd.Flags().Changed("annotations") { + req.Annotations = shared.KeyValuePairsToMap(updateAnnotations) + } + if cmd.Flags().Changed("trusted-audience") { + req.TrustedAudiences = updateAudiences + } + if cmd.Flags().Changed("audience-match-mode") { + m := clientiam.AudienceMatchMode(updateAudienceMode) + if m != clientiam.AudienceMatchModeExact && m != clientiam.AudienceMatchModeAny && m != clientiam.AudienceMatchModeAll { + return fmt.Errorf("invalid --audience-match-mode") + } + req.AudienceMatchMode = m + } + if cmd.Flags().Changed("scope") { + scopes, err := shared.ParseAccessCredentialScopes(updateScopes) + if err != nil { + return err + } + req.AllowedScopes = scopes + } + if cmd.Flags().Changed("status") { + s := clientiam.FederatedIdentityStatus(updateStatus) + if s != clientiam.FederatedIdentityStatusActive && s != clientiam.FederatedIdentityStatusInactive && + s != clientiam.FederatedIdentityStatusExpired && s != clientiam.FederatedIdentityStatusRevoked { + return fmt.Errorf("invalid --status") + } + req.Status = s + } + if cmd.Flags().Changed("expires-at") { + t, err := shared.ParseOptionalRFC3339(updateExpiresAt) + if err != nil { + return err + } + req.ExpiresAt = t + } + if cmd.Flags().Changed("conditions") || cmd.Flags().Changed("conditions-file") { + conds, err := shared.ParseConditionsJSON(updateConditions, updateConditionsFile) + if err != nil { + return err + } + req.Conditions = conds + } + fi, err := client.IAM().UpdateFederatedIdentity(ctx, args[0], req) + if err != nil { + return fmt.Errorf("failed to update federated identity: %w", err) + } + if fi == nil { + return nil + } + body := [][]string{{fi.Identity, fi.Name, string(fi.Status)}} + if noHeader { + table.Print(nil, body) + } else { + table.Print([]string{"ID", "Name", "Status"}, body) + } + return nil + }, +} + +func init() { + FederatedIdentitiesCmd.AddCommand(updateCmd) + updateCmd.Flags().BoolVar(&noHeader, shared.NoHeaderKey, false, "Do not print table headers") + updateCmd.Flags().StringVar(&updateDescription, "description", "", "Description") + updateCmd.Flags().StringSliceVar(&updateLabels, "labels", nil, "Replace labels (key=value, repeatable)") + updateCmd.Flags().StringSliceVar(&updateAnnotations, "annotations", nil, "Replace annotations (key=value, repeatable)") + updateCmd.Flags().StringSliceVar(&updateAudiences, "trusted-audience", nil, "Replace trusted audiences (repeatable)") + updateCmd.Flags().StringVar(&updateAudienceMode, "audience-match-mode", "", "exact, any, or all") + updateCmd.Flags().StringSliceVar(&updateScopes, "scope", nil, "Replace allowed scopes (repeatable)") + updateCmd.Flags().StringVar(&updateStatus, "status", "", "active, inactive, expired, or revoked") + updateCmd.Flags().StringVar(&updateExpiresAt, "expires-at", "", "RFC3339 expiry (empty to clear not supported by all APIs)") + updateCmd.Flags().StringVar(&updateConditions, "conditions", "", "Conditions JSON object") + updateCmd.Flags().StringVar(&updateConditionsFile, "conditions-file", "", "Path to JSON file for conditions") +} diff --git a/cmd/iam/federatedidentityproviders/create.go b/cmd/iam/federatedidentityproviders/create.go new file mode 100644 index 0000000..344ae32 --- /dev/null +++ b/cmd/iam/federatedidentityproviders/create.go @@ -0,0 +1,82 @@ +package federatedidentityproviders + +import ( + "fmt" + + "github.com/spf13/cobra" + + "github.com/thalassa-cloud/cli/cmd/iam/internal/shared" + "github.com/thalassa-cloud/cli/internal/table" + "github.com/thalassa-cloud/cli/internal/thalassaclient" + clientiam "github.com/thalassa-cloud/client-go/iam" +) + +var ( + createName string + createDescription string + createLabels []string + createAnnotations []string + createIssuer string + createJwksURI string + createStatus string +) + +var createCmd = &cobra.Command{ + Use: "create", + Short: "Register a federated identity provider", + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, _ []string) error { + ctx := cmd.Context() + if createName == "" || createIssuer == "" { + return fmt.Errorf("--name and --issuer are required") + } + var jwks *string + if createJwksURI != "" { + jwks = &createJwksURI + } + status := clientiam.FederatedIdentityProviderStatus(createStatus) + if createStatus == "" { + status = clientiam.FederatedIdentityProviderStatusActive + } else if status != clientiam.FederatedIdentityProviderStatusActive && status != clientiam.FederatedIdentityProviderStatusInactive { + return fmt.Errorf("invalid --status (use active or inactive)") + } + client, err := thalassaclient.GetThalassaClient() + if err != nil { + return fmt.Errorf("failed to create client: %w", err) + } + p, err := client.IAM().CreateFederatedIdentityProvider(ctx, clientiam.CreateFederatedIdentityProviderRequest{ + Name: createName, + Description: createDescription, + Labels: shared.KeyValuePairsToMap(createLabels), + Annotations: shared.KeyValuePairsToMap(createAnnotations), + ProviderIssuer: createIssuer, + ProviderJwksURI: jwks, + Status: status, + }) + if err != nil { + return fmt.Errorf("failed to create provider: %w", err) + } + if p == nil { + return nil + } + body := [][]string{{p.Identity, p.Name, string(p.Status)}} + if noHeader { + table.Print(nil, body) + } else { + table.Print([]string{"ID", "Name", "Status"}, body) + } + return nil + }, +} + +func init() { + FederatedIdentityProvidersCmd.AddCommand(createCmd) + createCmd.Flags().BoolVar(&noHeader, shared.NoHeaderKey, false, "Do not print table headers") + createCmd.Flags().StringVar(&createName, "name", "", "Provider name") + createCmd.Flags().StringVar(&createDescription, "description", "", "Description") + createCmd.Flags().StringSliceVar(&createLabels, "labels", nil, "Labels as key=value (repeatable)") + createCmd.Flags().StringSliceVar(&createAnnotations, "annotations", nil, "Annotations as key=value (repeatable)") + createCmd.Flags().StringVar(&createIssuer, "issuer", "", "OIDC issuer URL (unique per organisation)") + createCmd.Flags().StringVar(&createJwksURI, "jwks-uri", "", "Optional JWKS URI override") + createCmd.Flags().StringVar(&createStatus, "status", "", "active (default) or inactive") +} diff --git a/cmd/iam/federatedidentityproviders/delete.go b/cmd/iam/federatedidentityproviders/delete.go new file mode 100644 index 0000000..06c001b --- /dev/null +++ b/cmd/iam/federatedidentityproviders/delete.go @@ -0,0 +1,45 @@ +package federatedidentityproviders + +import ( + "fmt" + + "github.com/spf13/cobra" + + "github.com/thalassa-cloud/cli/cmd/iam/internal/shared" + "github.com/thalassa-cloud/cli/internal/completion" + "github.com/thalassa-cloud/cli/internal/thalassaclient" +) + +var deleteForce bool + +var deleteCmd = &cobra.Command{ + Use: "delete ", + Short: "Delete a federated identity provider", + Args: cobra.ExactArgs(1), + ValidArgsFunction: completion.CompleteIAMFederatedIdentityProviderIdentity, + RunE: func(cmd *cobra.Command, args []string) error { + ctx := cmd.Context() + client, err := thalassaclient.GetThalassaClient() + if err != nil { + return fmt.Errorf("failed to create client: %w", err) + } + ok, err := shared.PromptDestructiveUnlessForce(deleteForce, fmt.Sprintf("Are you sure you want to delete this federated identity provider?\n Identity: %s\n", args[0])) + if err != nil { + return err + } + if !ok { + return nil + } + if err := client.IAM().DeleteFederatedIdentityProvider(ctx, args[0]); err != nil { + return fmt.Errorf("failed to delete provider: %w", err) + } + fmt.Printf("Deleted federated identity provider %s\n", args[0]) + return nil + }, +} + +func init() { + FederatedIdentityProvidersCmd.AddCommand(deleteCmd) + deleteCmd.Flags().BoolVar(&deleteForce, shared.ForceKey, false, "Skip the confirmation prompt and delete") + deleteCmd.Flags().BoolVar(&noHeader, shared.NoHeaderKey, false, "Do not print table headers") +} diff --git a/cmd/iam/federatedidentityproviders/federatedidentityproviders.go b/cmd/iam/federatedidentityproviders/federatedidentityproviders.go new file mode 100644 index 0000000..85f3a1c --- /dev/null +++ b/cmd/iam/federatedidentityproviders/federatedidentityproviders.go @@ -0,0 +1,10 @@ +package federatedidentityproviders + +import "github.com/spf13/cobra" + +// FederatedIdentityProvidersCmd manages federated OIDC providers. +var FederatedIdentityProvidersCmd = &cobra.Command{ + Use: "federated-identity-providers", + Aliases: []string{"fed-providers", "federated-providers"}, + Short: "Federated OIDC identity providers", +} diff --git a/cmd/iam/federatedidentityproviders/get.go b/cmd/iam/federatedidentityproviders/get.go new file mode 100644 index 0000000..cbb61c1 --- /dev/null +++ b/cmd/iam/federatedidentityproviders/get.go @@ -0,0 +1,46 @@ +package federatedidentityproviders + +import ( + "fmt" + + "github.com/spf13/cobra" + + "github.com/thalassa-cloud/cli/cmd/iam/internal/shared" + "github.com/thalassa-cloud/cli/internal/completion" + "github.com/thalassa-cloud/cli/internal/formattime" + "github.com/thalassa-cloud/cli/internal/thalassaclient" +) + +var getCmd = &cobra.Command{ + Use: "get ", + Short: "Show a federated identity provider", + Args: cobra.ExactArgs(1), + ValidArgsFunction: completion.CompleteIAMFederatedIdentityProviderIdentity, + RunE: func(cmd *cobra.Command, args []string) error { + ctx := cmd.Context() + client, err := thalassaclient.GetThalassaClient() + if err != nil { + return fmt.Errorf("failed to create client: %w", err) + } + p, err := client.IAM().GetFederatedIdentityProvider(ctx, args[0]) + if err != nil { + return fmt.Errorf("failed to get provider: %w", err) + } + fmt.Printf("Identity: %s\n", p.Identity) + fmt.Printf("Name: %s\n", p.Name) + fmt.Printf("Description: %s\n", p.Description) + fmt.Printf("Issuer: %s\n", p.ProviderIssuer) + if p.ProviderJwksURI != nil { + fmt.Printf("JWKS URI: %s\n", *p.ProviderJwksURI) + } + fmt.Printf("Status: %s\n", p.Status) + fmt.Printf("Created: %s\n", formattime.FormatTime(p.CreatedAt.Local(), showExactTime)) + return nil + }, +} + +func init() { + FederatedIdentityProvidersCmd.AddCommand(getCmd) + getCmd.Flags().BoolVar(&noHeader, shared.NoHeaderKey, false, "Do not print table headers") + getCmd.Flags().BoolVar(&showExactTime, "exact-time", false, "Show full timestamps instead of relative time") +} diff --git a/cmd/iam/federatedidentityproviders/list.go b/cmd/iam/federatedidentityproviders/list.go new file mode 100644 index 0000000..ac8576f --- /dev/null +++ b/cmd/iam/federatedidentityproviders/list.go @@ -0,0 +1,68 @@ +package federatedidentityproviders + +import ( + "fmt" + + "github.com/spf13/cobra" + + "github.com/thalassa-cloud/cli/cmd/iam/internal/shared" + "github.com/thalassa-cloud/cli/internal/formattime" + "github.com/thalassa-cloud/cli/internal/labels" + "github.com/thalassa-cloud/cli/internal/table" + "github.com/thalassa-cloud/cli/internal/thalassaclient" + "github.com/thalassa-cloud/client-go/filters" + clientiam "github.com/thalassa-cloud/client-go/iam" +) + +var ( + noHeader bool + showExactTime bool + listSelector string +) + +var listCmd = &cobra.Command{ + Use: "list", + Aliases: []string{"ls"}, + Short: "List federated identity providers", + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, _ []string) error { + ctx := cmd.Context() + client, err := thalassaclient.GetThalassaClient() + if err != nil { + return fmt.Errorf("failed to create client: %w", err) + } + req := &clientiam.ListFederatedIdentityProvidersRequest{} + if listSelector != "" { + req.Filters = []filters.Filter{ + &filters.LabelFilter{MatchLabels: labels.ParseLabelSelector(listSelector)}, + } + } + list, err := client.IAM().ListFederatedIdentityProviders(ctx, req) + if err != nil { + return fmt.Errorf("failed to list providers: %w", err) + } + body := make([][]string, 0, len(list)) + for _, p := range list { + body = append(body, []string{ + p.Identity, + p.Name, + p.ProviderIssuer, + string(p.Status), + formattime.FormatTime(p.CreatedAt.Local(), showExactTime), + }) + } + if noHeader { + table.Print(nil, body) + } else { + table.Print([]string{"ID", "Name", "Issuer", "Status", "Created"}, body) + } + return nil + }, +} + +func init() { + FederatedIdentityProvidersCmd.AddCommand(listCmd) + listCmd.Flags().BoolVar(&noHeader, shared.NoHeaderKey, false, "Do not print table headers") + listCmd.Flags().BoolVar(&showExactTime, "exact-time", false, "Show full timestamps instead of relative time") + listCmd.Flags().StringVar(&listSelector, "label-selector", "", "Filter by labels (key=value,key2=value2)") +} diff --git a/cmd/iam/federatedidentityproviders/update.go b/cmd/iam/federatedidentityproviders/update.go new file mode 100644 index 0000000..dcf3e54 --- /dev/null +++ b/cmd/iam/federatedidentityproviders/update.go @@ -0,0 +1,89 @@ +package federatedidentityproviders + +import ( + "fmt" + + "github.com/spf13/cobra" + + "github.com/thalassa-cloud/cli/cmd/iam/internal/shared" + "github.com/thalassa-cloud/cli/internal/completion" + "github.com/thalassa-cloud/cli/internal/table" + "github.com/thalassa-cloud/cli/internal/thalassaclient" + clientiam "github.com/thalassa-cloud/client-go/iam" +) + +var ( + updateName string + updateDescription string + updateLabels []string + updateAnnotations []string + updateJwksURI string + updateStatus string +) + +var updateCmd = &cobra.Command{ + Use: "update ", + Short: "Update a federated identity provider", + Args: cobra.ExactArgs(1), + ValidArgsFunction: completion.CompleteIAMFederatedIdentityProviderIdentity, + RunE: func(cmd *cobra.Command, args []string) error { + ctx := cmd.Context() + req := clientiam.UpdateFederatedIdentityProviderRequest{} + if cmd.Flags().Changed("name") { + req.Name = updateName + } + if cmd.Flags().Changed("description") { + req.Description = updateDescription + } + if cmd.Flags().Changed("labels") { + req.Labels = shared.KeyValuePairsToMap(updateLabels) + } + if cmd.Flags().Changed("annotations") { + req.Annotations = shared.KeyValuePairsToMap(updateAnnotations) + } + if cmd.Flags().Changed("jwks-uri") { + if updateJwksURI == "" { + empty := "" + req.ProviderJwksURI = &empty + } else { + req.ProviderJwksURI = &updateJwksURI + } + } + if cmd.Flags().Changed("status") { + s := clientiam.FederatedIdentityProviderStatus(updateStatus) + if s != clientiam.FederatedIdentityProviderStatusActive && s != clientiam.FederatedIdentityProviderStatusInactive { + return fmt.Errorf("invalid --status") + } + req.Status = s + } + client, err := thalassaclient.GetThalassaClient() + if err != nil { + return fmt.Errorf("failed to create client: %w", err) + } + p, err := client.IAM().UpdateFederatedIdentityProvider(ctx, args[0], req) + if err != nil { + return fmt.Errorf("failed to update provider: %w", err) + } + if p == nil { + return nil + } + body := [][]string{{p.Identity, p.Name, string(p.Status)}} + if noHeader { + table.Print(nil, body) + } else { + table.Print([]string{"ID", "Name", "Status"}, body) + } + return nil + }, +} + +func init() { + FederatedIdentityProvidersCmd.AddCommand(updateCmd) + updateCmd.Flags().BoolVar(&noHeader, shared.NoHeaderKey, false, "Do not print table headers") + updateCmd.Flags().StringVar(&updateName, "name", "", "Provider display name") + updateCmd.Flags().StringVar(&updateDescription, "description", "", "Description") + updateCmd.Flags().StringSliceVar(&updateLabels, "labels", nil, "Replace labels (key=value, repeatable)") + updateCmd.Flags().StringSliceVar(&updateAnnotations, "annotations", nil, "Replace annotations (key=value, repeatable)") + updateCmd.Flags().StringVar(&updateJwksURI, "jwks-uri", "", "JWKS URI (set to empty string to clear)") + updateCmd.Flags().StringVar(&updateStatus, "status", "", "active or inactive") +} diff --git a/cmd/iam/iam.go b/cmd/iam/iam.go new file mode 100644 index 0000000..b522a3c --- /dev/null +++ b/cmd/iam/iam.go @@ -0,0 +1,34 @@ +package iam + +import ( + "github.com/spf13/cobra" + + "github.com/thalassa-cloud/cli/cmd/iam/federatedidentities" + "github.com/thalassa-cloud/cli/cmd/iam/federatedidentityproviders" + "github.com/thalassa-cloud/cli/cmd/iam/invites" + "github.com/thalassa-cloud/cli/cmd/iam/members" + "github.com/thalassa-cloud/cli/cmd/iam/roles" + "github.com/thalassa-cloud/cli/cmd/iam/serviceaccounts" + "github.com/thalassa-cloud/cli/cmd/iam/teams" + "github.com/thalassa-cloud/cli/cmd/iam/workloadidentityfederation" +) + +// IamCmd is the root identity and access management command. +var IamCmd = &cobra.Command{ + Use: "iam", + Short: "Identity and access management for your organisation", + Long: `Manage teams, organisation members, custom roles, federated OIDC identities, +and related resources. Commands apply to the organisation selected in your context +(or the --organisation / -O flag).`, +} + +func init() { + IamCmd.AddCommand(teams.TeamsCmd) + IamCmd.AddCommand(members.MembersCmd) + IamCmd.AddCommand(roles.RolesCmd) + IamCmd.AddCommand(federatedidentities.FederatedIdentitiesCmd) + IamCmd.AddCommand(federatedidentityproviders.FederatedIdentityProvidersCmd) + IamCmd.AddCommand(invites.InvitesCmd) + IamCmd.AddCommand(serviceaccounts.ServiceAccountsCmd) + IamCmd.AddCommand(workloadidentityfederation.WorkloadIdentityFederationCmd) +} diff --git a/cmd/iam/internal/shared/federated.go b/cmd/iam/internal/shared/federated.go new file mode 100644 index 0000000..f38ee88 --- /dev/null +++ b/cmd/iam/internal/shared/federated.go @@ -0,0 +1,62 @@ +package shared + +import ( + "encoding/json" + "fmt" + "os" + "strings" + "time" + + clientiam "github.com/thalassa-cloud/client-go/iam" +) + +func ParseAccessCredentialScopes(ss []string) ([]clientiam.AccessCredentialsScope, error) { + if len(ss) == 0 { + return nil, nil + } + out := make([]clientiam.AccessCredentialsScope, 0, len(ss)) + for _, s := range ss { + s = strings.TrimSpace(s) + switch clientiam.AccessCredentialsScope(s) { + case clientiam.AccessCredentialsScopeAPIRead, + clientiam.AccessCredentialsScopeAPIWrite, + clientiam.AccessCredentialsScopeKubernetes, + clientiam.AccessCredentialsScopeObjectStorage: + out = append(out, clientiam.AccessCredentialsScope(s)) + default: + return nil, fmt.Errorf("invalid scope %q (allowed: api:read, api:write, kubernetes, objectStorage)", s) + } + } + return out, nil +} + +func ParseConditionsJSON(s, path string) (map[string]interface{}, error) { + raw := s + if path != "" { + b, err := os.ReadFile(path) + if err != nil { + return nil, fmt.Errorf("read conditions file: %w", err) + } + raw = string(b) + } + if strings.TrimSpace(raw) == "" { + return nil, nil + } + var m map[string]interface{} + if err := json.Unmarshal([]byte(raw), &m); err != nil { + return nil, fmt.Errorf("parse conditions JSON: %w", err) + } + return m, nil +} + +func ParseOptionalRFC3339(s string) (*time.Time, error) { + s = strings.TrimSpace(s) + if s == "" { + return nil, nil + } + t, err := time.Parse(time.RFC3339, s) + if err != nil { + return nil, fmt.Errorf("parse time as RFC3339: %w", err) + } + return &t, nil +} diff --git a/cmd/iam/internal/shared/shared.go b/cmd/iam/internal/shared/shared.go new file mode 100644 index 0000000..0be8cbe --- /dev/null +++ b/cmd/iam/internal/shared/shared.go @@ -0,0 +1,65 @@ +package shared + +import ( + "fmt" + "strings" + + "github.com/thalassa-cloud/client-go/pkg/base" +) + +const ( + NoHeaderKey = "no-header" + // ForceKey is the flag name for skipping the interactive prompt on destructive IAM commands. + ForceKey = "force" +) + +// PromptDestructiveUnlessForce prompts for typing "yes" unless force is true. +// On abort (anything other than "yes"), returns proceed=false and err=nil. +func PromptDestructiveUnlessForce(force bool, summary string) (proceed bool, err error) { + if force { + return true, nil + } + fmt.Print(summary) + if summary != "" && !strings.HasSuffix(summary, "\n") { + fmt.Println() + } + fmt.Print("Enter 'yes' to confirm: ") + var input string + if _, scanErr := fmt.Scanln(&input); scanErr != nil { + return false, fmt.Errorf("read confirmation: %w", scanErr) + } + if strings.TrimSpace(input) != "yes" { + fmt.Println("Aborted") + return false, nil + } + return true, nil +} + +func KeyValuePairsToMap(pairs []string) map[string]string { + out := make(map[string]string) + for _, pair := range pairs { + pair = strings.TrimSpace(pair) + parts := strings.SplitN(pair, "=", 2) + if len(parts) == 2 { + out[strings.TrimSpace(parts[0])] = strings.TrimSpace(parts[1]) + } + } + return out +} + +func UserDisplay(u base.AppUser) string { + if u.Email != "" { + return u.Email + } + if u.Name != "" { + return u.Name + } + return u.Subject +} + +func UserPtrDisplay(u *base.AppUser) string { + if u == nil { + return "" + } + return UserDisplay(*u) +} diff --git a/cmd/iam/internal/shared/shared_test.go b/cmd/iam/internal/shared/shared_test.go new file mode 100644 index 0000000..4923cd8 --- /dev/null +++ b/cmd/iam/internal/shared/shared_test.go @@ -0,0 +1,46 @@ +package shared + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + clientiam "github.com/thalassa-cloud/client-go/iam" +) + +func TestKeyValuePairsToMap(t *testing.T) { + tests := []struct { + name string + pairs []string + want map[string]string + }{ + {name: "empty", pairs: nil, want: map[string]string{}}, + {name: "valid pairs", pairs: []string{"a=b", "c=d"}, want: map[string]string{"a": "b", "c": "d"}}, + {name: "trim", pairs: []string{" a = b "}, want: map[string]string{"a": "b"}}, + {name: "skip invalid", pairs: []string{"nope", "ok=yes"}, want: map[string]string{"ok": "yes"}}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := KeyValuePairsToMap(tt.pairs) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestPromptDestructiveUnlessForce_SkipsPromptWhenForce(t *testing.T) { + proceed, err := PromptDestructiveUnlessForce(true, "will not print") + assert.NoError(t, err) + assert.True(t, proceed) +} + +func TestParseAccessCredentialScopes(t *testing.T) { + got, err := ParseAccessCredentialScopes([]string{"api:read", "kubernetes"}) + require.NoError(t, err) + assert.Equal(t, []clientiam.AccessCredentialsScope{ + clientiam.AccessCredentialsScopeAPIRead, + clientiam.AccessCredentialsScopeKubernetes, + }, got) + + _, err = ParseAccessCredentialScopes([]string{"nope"}) + require.Error(t, err) +} diff --git a/cmd/iam/invites/invites.go b/cmd/iam/invites/invites.go new file mode 100644 index 0000000..0d2e568 --- /dev/null +++ b/cmd/iam/invites/invites.go @@ -0,0 +1,9 @@ +package invites + +import "github.com/spf13/cobra" + +// InvitesCmd lists organisation member invitations. +var InvitesCmd = &cobra.Command{ + Use: "invites", + Short: "Organisation member invitations", +} diff --git a/cmd/iam/invites/list.go b/cmd/iam/invites/list.go new file mode 100644 index 0000000..078f5d0 --- /dev/null +++ b/cmd/iam/invites/list.go @@ -0,0 +1,62 @@ +package invites + +import ( + "fmt" + + "github.com/spf13/cobra" + + "github.com/thalassa-cloud/cli/cmd/iam/internal/shared" + "github.com/thalassa-cloud/cli/internal/formattime" + "github.com/thalassa-cloud/cli/internal/table" + "github.com/thalassa-cloud/cli/internal/thalassaclient" + clientiam "github.com/thalassa-cloud/client-go/iam" +) + +var ( + noHeader bool + showExactTime bool +) + +var listCmd = &cobra.Command{ + Use: "list", + Aliases: []string{"ls"}, + Short: "List pending organisation invites", + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, _ []string) error { + ctx := cmd.Context() + client, err := thalassaclient.GetThalassaClient() + if err != nil { + return fmt.Errorf("failed to create client: %w", err) + } + invites, err := client.IAM().ListOrganisationMemberInvites(ctx, &clientiam.ListOrganisationMemberInvitesRequest{}) + if err != nil { + return fmt.Errorf("failed to list invites: %w", err) + } + body := make([][]string, 0, len(invites)) + for _, inv := range invites { + exp := "" + if inv.ExpiresAt != nil { + exp = formattime.FormatTime(inv.ExpiresAt.Local(), showExactTime) + } + body = append(body, []string{ + inv.Email, + string(inv.Role), + inv.InviteCode, + formattime.FormatTime(inv.CreatedAt.Local(), showExactTime), + exp, + }) + } + if noHeader { + table.Print(nil, body) + } else { + table.Print([]string{"Email", "Role", "Code", "Created", "Expires"}, body) + } + return nil + }, +} + +func init() { + InvitesCmd.AddCommand(listCmd) + listCmd.Flags().BoolVar(&noHeader, shared.NoHeaderKey, false, "Do not print table headers") + listCmd.Flags().BoolVar(&showExactTime, "exact-time", false, "Show full timestamps instead of relative time") +} diff --git a/cmd/iam/members/delete.go b/cmd/iam/members/delete.go new file mode 100644 index 0000000..ef4ded8 --- /dev/null +++ b/cmd/iam/members/delete.go @@ -0,0 +1,45 @@ +package members + +import ( + "fmt" + + "github.com/spf13/cobra" + + "github.com/thalassa-cloud/cli/cmd/iam/internal/shared" + "github.com/thalassa-cloud/cli/internal/completion" + "github.com/thalassa-cloud/cli/internal/thalassaclient" +) + +var deleteForce bool + +var deleteCmd = &cobra.Command{ + Use: "delete ", + Short: "Remove a member from the organisation", + Args: cobra.ExactArgs(1), + ValidArgsFunction: completion.CompleteIAMOrganisationMemberIdentity, + RunE: func(cmd *cobra.Command, args []string) error { + ctx := cmd.Context() + client, err := thalassaclient.GetThalassaClient() + if err != nil { + return fmt.Errorf("failed to create client: %w", err) + } + ok, err := shared.PromptDestructiveUnlessForce(deleteForce, fmt.Sprintf("Are you sure you want to remove this member from the organisation?\n Member: %s\n", args[0])) + if err != nil { + return err + } + if !ok { + return nil + } + if err := client.IAM().DeleteOrganisationMember(ctx, args[0]); err != nil { + return fmt.Errorf("failed to remove member: %w", err) + } + fmt.Printf("Removed organisation member %s\n", args[0]) + return nil + }, +} + +func init() { + MembersCmd.AddCommand(deleteCmd) + deleteCmd.Flags().BoolVar(&deleteForce, shared.ForceKey, false, "Skip the confirmation prompt and remove the member") + deleteCmd.Flags().BoolVar(&noHeader, shared.NoHeaderKey, false, "Do not print table headers") +} diff --git a/cmd/iam/members/list.go b/cmd/iam/members/list.go new file mode 100644 index 0000000..f1ed8ef --- /dev/null +++ b/cmd/iam/members/list.go @@ -0,0 +1,57 @@ +package members + +import ( + "fmt" + + "github.com/spf13/cobra" + + "github.com/thalassa-cloud/cli/cmd/iam/internal/shared" + "github.com/thalassa-cloud/cli/internal/formattime" + "github.com/thalassa-cloud/cli/internal/table" + "github.com/thalassa-cloud/cli/internal/thalassaclient" + clientiam "github.com/thalassa-cloud/client-go/iam" +) + +var ( + noHeader bool + showExactTime bool +) + +var listCmd = &cobra.Command{ + Use: "list", + Aliases: []string{"ls"}, + Short: "List organisation members", + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, _ []string) error { + ctx := cmd.Context() + client, err := thalassaclient.GetThalassaClient() + if err != nil { + return fmt.Errorf("failed to create client: %w", err) + } + members, err := client.IAM().ListOrganisationMembers(ctx, &clientiam.ListMembersRequest{}) + if err != nil { + return fmt.Errorf("failed to list members: %w", err) + } + body := make([][]string, 0, len(members)) + for _, m := range members { + body = append(body, []string{ + m.Identity, + string(m.MemberType), + shared.UserPtrDisplay(m.User), + formattime.FormatTime(m.CreatedAt.Local(), showExactTime), + }) + } + if noHeader { + table.Print(nil, body) + } else { + table.Print([]string{"ID", "Role", "User", "Joined"}, body) + } + return nil + }, +} + +func init() { + MembersCmd.AddCommand(listCmd) + listCmd.Flags().BoolVar(&noHeader, shared.NoHeaderKey, false, "Do not print table headers") + listCmd.Flags().BoolVar(&showExactTime, "exact-time", false, "Show full timestamps instead of relative time") +} diff --git a/cmd/iam/members/members.go b/cmd/iam/members/members.go new file mode 100644 index 0000000..6341ae1 --- /dev/null +++ b/cmd/iam/members/members.go @@ -0,0 +1,9 @@ +package members + +import "github.com/spf13/cobra" + +// MembersCmd represents organisation members (owners and members). +var MembersCmd = &cobra.Command{ + Use: "members", + Short: "Organisation members (owners and members)", +} diff --git a/cmd/iam/members/update.go b/cmd/iam/members/update.go new file mode 100644 index 0000000..4bdc462 --- /dev/null +++ b/cmd/iam/members/update.go @@ -0,0 +1,50 @@ +package members + +import ( + "fmt" + + "github.com/spf13/cobra" + + "github.com/thalassa-cloud/cli/cmd/iam/internal/shared" + "github.com/thalassa-cloud/cli/internal/completion" + "github.com/thalassa-cloud/cli/internal/thalassaclient" + clientiam "github.com/thalassa-cloud/client-go/iam" +) + +var updateRole string + +var updateCmd = &cobra.Command{ + Use: "update ", + Short: "Change an organisation member's role (OWNER or MEMBER)", + Args: cobra.ExactArgs(1), + ValidArgsFunction: completion.CompleteIAMOrganisationMemberIdentity, + RunE: func(cmd *cobra.Command, args []string) error { + ctx := cmd.Context() + if updateRole == "" { + return fmt.Errorf("--role is required (OWNER or MEMBER)") + } + role := clientiam.OrganisationMemberType(updateRole) + if role != clientiam.OrganisationMemberTypeOwner && role != clientiam.OrganisationMemberTypeMember { + return fmt.Errorf("role must be OWNER or MEMBER") + } + client, err := thalassaclient.GetThalassaClient() + if err != nil { + return fmt.Errorf("failed to create client: %w", err) + } + if err := client.IAM().UpdateOrganisationMember(ctx, args[0], clientiam.UpdateOrganisationMemberRequest{ + MemberType: role, + }); err != nil { + return fmt.Errorf("failed to update member: %w", err) + } + fmt.Printf("Updated member %s to role %s\n", args[0], role) + return nil + }, +} + +func init() { + MembersCmd.AddCommand(updateCmd) + updateCmd.Flags().BoolVar(&noHeader, shared.NoHeaderKey, false, "Do not print table headers") + updateCmd.Flags().StringVar(&updateRole, "role", "", "Organisation role: OWNER or MEMBER") + _ = updateCmd.RegisterFlagCompletionFunc("role", completion.CompleteIAMOrganisationMemberType) + _ = updateCmd.MarkFlagRequired("role") +} diff --git a/cmd/iam/roles/bindings/bindings.go b/cmd/iam/roles/bindings/bindings.go new file mode 100644 index 0000000..e3d4741 --- /dev/null +++ b/cmd/iam/roles/bindings/bindings.go @@ -0,0 +1,9 @@ +package bindings + +import "github.com/spf13/cobra" + +// BindingsCmd manages role bindings. +var BindingsCmd = &cobra.Command{ + Use: "bindings", + Short: "Role bindings (who receives the role)", +} diff --git a/cmd/iam/roles/bindings/create.go b/cmd/iam/roles/bindings/create.go new file mode 100644 index 0000000..51251f3 --- /dev/null +++ b/cmd/iam/roles/bindings/create.go @@ -0,0 +1,105 @@ +package bindings + +import ( + "fmt" + + "github.com/spf13/cobra" + + "github.com/thalassa-cloud/cli/cmd/iam/internal/shared" + "github.com/thalassa-cloud/cli/internal/completion" + "github.com/thalassa-cloud/cli/internal/iamresolve" + "github.com/thalassa-cloud/cli/internal/table" + "github.com/thalassa-cloud/cli/internal/thalassaclient" + clientiam "github.com/thalassa-cloud/client-go/iam" +) + +var ( + createName string + createDescription string + createLabels []string + createAnnotations []string + createUserIdentity string + createTeamIdentity string + createServiceAccountIdentity string + createScopes []string +) + +var createCmd = &cobra.Command{ + Use: "create ", + Short: "Create a binding to a user, team, or service account", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + ctx := cmd.Context() + n := 0 + if createUserIdentity != "" { + n++ + } + if createTeamIdentity != "" { + n++ + } + if createServiceAccountIdentity != "" { + n++ + } + if n != 1 { + return fmt.Errorf("specify exactly one of --user-identity, --team-identity, or --service-account-identity") + } + if createName == "" { + return fmt.Errorf("--name is required") + } + client, err := thalassaclient.GetThalassaClient() + if err != nil { + return fmt.Errorf("failed to create client: %w", err) + } + create := clientiam.CreateRoleBinding{ + Name: createName, + Description: createDescription, + Labels: shared.KeyValuePairsToMap(createLabels), + Annotations: shared.KeyValuePairsToMap(createAnnotations), + Scopes: createScopes, + } + if createUserIdentity != "" { + create.UserIdentity = &createUserIdentity + } + if createTeamIdentity != "" { + create.TeamIdentity = &createTeamIdentity + } + if createServiceAccountIdentity != "" { + create.ServiceAccountIdentity = &createServiceAccountIdentity + } + role, err := iamresolve.ResolveOrganisationRoleRef(ctx, client.IAM(), args[0]) + if err != nil { + return err + } + binding, err := client.IAM().CreateRoleBinding(ctx, role.Identity, create) + if err != nil { + return fmt.Errorf("failed to create binding: %w", err) + } + if binding == nil { + return nil + } + body := [][]string{{binding.Identity, binding.Name, binding.Slug}} + if noHeader { + table.Print(nil, body) + } else { + table.Print([]string{"ID", "Name", "Slug"}, body) + } + return nil + }, +} + +func init() { + BindingsCmd.AddCommand(createCmd) + createCmd.Flags().BoolVar(&noHeader, shared.NoHeaderKey, false, "Do not print table headers") + createCmd.Flags().StringVar(&createName, "name", "", "Binding name") + createCmd.Flags().StringVar(&createDescription, "description", "", "Binding description") + createCmd.Flags().StringSliceVar(&createLabels, "labels", nil, "Labels as key=value (repeatable)") + createCmd.Flags().StringSliceVar(&createAnnotations, "annotations", nil, "Annotations as key=value (repeatable)") + createCmd.Flags().StringVar(&createUserIdentity, "user-identity", "", "Bind to this user identity") + createCmd.Flags().StringVar(&createTeamIdentity, "team-identity", "", "Bind to this team identity") + createCmd.Flags().StringVar(&createServiceAccountIdentity, "service-account-identity", "", "Bind to this service account identity") + _ = createCmd.RegisterFlagCompletionFunc("user-identity", completion.CompleteIAMAppUserSubject) + _ = createCmd.RegisterFlagCompletionFunc("team-identity", completion.CompleteIAMTeamIdentityFlag) + _ = createCmd.RegisterFlagCompletionFunc("service-account-identity", completion.CompleteIAMServiceAccountIdentityFlag) + createCmd.Flags().StringSliceVar(&createScopes, "scope", nil, "Scopes for the binding (repeatable)") + _ = createCmd.MarkFlagRequired("name") +} diff --git a/cmd/iam/roles/bindings/delete.go b/cmd/iam/roles/bindings/delete.go new file mode 100644 index 0000000..81a1577 --- /dev/null +++ b/cmd/iam/roles/bindings/delete.go @@ -0,0 +1,50 @@ +package bindings + +import ( + "fmt" + + "github.com/spf13/cobra" + + "github.com/thalassa-cloud/cli/cmd/iam/internal/shared" + "github.com/thalassa-cloud/cli/internal/completion" + "github.com/thalassa-cloud/cli/internal/iamresolve" + "github.com/thalassa-cloud/cli/internal/thalassaclient" +) + +var deleteForce bool + +var deleteCmd = &cobra.Command{ + Use: "delete ", + Short: "Delete a role binding", + Args: cobra.ExactArgs(2), + ValidArgsFunction: completion.CompleteIAMRoleThenBinding, + RunE: func(cmd *cobra.Command, args []string) error { + ctx := cmd.Context() + client, err := thalassaclient.GetThalassaClient() + if err != nil { + return fmt.Errorf("failed to create client: %w", err) + } + ok, err := shared.PromptDestructiveUnlessForce(deleteForce, fmt.Sprintf("Are you sure you want to delete this role binding?\n Role: %s\n Binding: %s\n", args[0], args[1])) + if err != nil { + return err + } + if !ok { + return nil + } + role, err := iamresolve.ResolveOrganisationRoleRef(ctx, client.IAM(), args[0]) + if err != nil { + return err + } + if err := client.IAM().DeleteRoleBinding(ctx, role.Identity, args[1]); err != nil { + return fmt.Errorf("failed to delete binding: %w", err) + } + fmt.Printf("Deleted binding %s from role %s\n", args[1], args[0]) + return nil + }, +} + +func init() { + BindingsCmd.AddCommand(deleteCmd) + deleteCmd.Flags().BoolVar(&deleteForce, shared.ForceKey, false, "Skip the confirmation prompt and delete") + deleteCmd.Flags().BoolVar(&noHeader, shared.NoHeaderKey, false, "Do not print table headers") +} diff --git a/cmd/iam/roles/bindings/list.go b/cmd/iam/roles/bindings/list.go new file mode 100644 index 0000000..15223b5 --- /dev/null +++ b/cmd/iam/roles/bindings/list.go @@ -0,0 +1,64 @@ +package bindings + +import ( + "fmt" + + "github.com/spf13/cobra" + + "github.com/thalassa-cloud/cli/cmd/iam/internal/shared" + "github.com/thalassa-cloud/cli/internal/completion" + "github.com/thalassa-cloud/cli/internal/iamresolve" + "github.com/thalassa-cloud/cli/internal/table" + "github.com/thalassa-cloud/cli/internal/thalassaclient" + clientiam "github.com/thalassa-cloud/client-go/iam" +) + +var noHeader bool + +var listCmd = &cobra.Command{ + Use: "list ", + Short: "List bindings for a role", + Args: cobra.ExactArgs(1), + ValidArgsFunction: completion.CompleteIAMOrganisationRoleIdentity, + RunE: func(cmd *cobra.Command, args []string) error { + ctx := cmd.Context() + client, err := thalassaclient.GetThalassaClient() + if err != nil { + return fmt.Errorf("failed to create client: %w", err) + } + + role, err := iamresolve.ResolveOrganisationRoleRef(ctx, client.IAM(), args[0]) + if err != nil { + return err + } + + bindings, err := client.IAM().ListRoleBindings(ctx, role.Identity, &clientiam.ListRoleBindingsRequest{}) + if err != nil { + return fmt.Errorf("failed to list bindings: %w", err) + } + body := make([][]string, 0, len(bindings)) + for _, b := range bindings { + subject := "" + switch { + case b.AppUser != nil: + subject = "user:" + shared.UserPtrDisplay(b.AppUser) + case b.OrganisationTeam != nil: + subject = "team:" + b.OrganisationTeam.Slug + case b.ServiceAccount != nil: + subject = "service_account:" + b.ServiceAccount.Slug + } + body = append(body, []string{b.Identity, b.Name, subject}) + } + if noHeader { + table.Print(nil, body) + } else { + table.Print([]string{"ID", "Name", "Subject"}, body) + } + return nil + }, +} + +func init() { + BindingsCmd.AddCommand(listCmd) + listCmd.Flags().BoolVar(&noHeader, shared.NoHeaderKey, false, "Do not print table headers") +} diff --git a/cmd/iam/roles/create.go b/cmd/iam/roles/create.go new file mode 100644 index 0000000..04a2077 --- /dev/null +++ b/cmd/iam/roles/create.go @@ -0,0 +1,61 @@ +package roles + +import ( + "fmt" + + "github.com/spf13/cobra" + + "github.com/thalassa-cloud/cli/cmd/iam/internal/shared" + "github.com/thalassa-cloud/cli/internal/table" + "github.com/thalassa-cloud/cli/internal/thalassaclient" + clientiam "github.com/thalassa-cloud/client-go/iam" +) + +var ( + createName string + createDescription string + createLabels []string + createAnnotations []string +) + +var createCmd = &cobra.Command{ + Use: "create", + Short: "Create a custom organisation role", + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, _ []string) error { + ctx := cmd.Context() + if createName == "" { + return fmt.Errorf("--name is required") + } + client, err := thalassaclient.GetThalassaClient() + if err != nil { + return fmt.Errorf("failed to create client: %w", err) + } + role, err := client.IAM().CreateOrganisationRole(ctx, clientiam.CreateOrganisationRoleRequest{ + Name: createName, + Description: createDescription, + Labels: shared.KeyValuePairsToMap(createLabels), + Annotations: shared.KeyValuePairsToMap(createAnnotations), + }) + if err != nil { + return fmt.Errorf("failed to create role: %w", err) + } + body := [][]string{{role.Identity, role.Name, role.Slug}} + if noHeader { + table.Print(nil, body) + } else { + table.Print([]string{"ID", "Name", "Slug"}, body) + } + return nil + }, +} + +func init() { + RolesCmd.AddCommand(createCmd) + createCmd.Flags().BoolVar(&noHeader, shared.NoHeaderKey, false, "Do not print table headers") + createCmd.Flags().StringVar(&createName, "name", "", "Role name") + createCmd.Flags().StringVar(&createDescription, "description", "", "Role description") + createCmd.Flags().StringSliceVar(&createLabels, "labels", nil, "Labels as key=value (repeatable)") + createCmd.Flags().StringSliceVar(&createAnnotations, "annotations", nil, "Annotations as key=value (repeatable)") + _ = createCmd.MarkFlagRequired("name") +} diff --git a/cmd/iam/roles/delete.go b/cmd/iam/roles/delete.go new file mode 100644 index 0000000..62a95f0 --- /dev/null +++ b/cmd/iam/roles/delete.go @@ -0,0 +1,45 @@ +package roles + +import ( + "fmt" + + "github.com/spf13/cobra" + + "github.com/thalassa-cloud/cli/cmd/iam/internal/shared" + "github.com/thalassa-cloud/cli/internal/completion" + "github.com/thalassa-cloud/cli/internal/thalassaclient" +) + +var deleteForce bool + +var deleteCmd = &cobra.Command{ + Use: "delete ", + Short: "Delete a custom organisation role", + Args: cobra.ExactArgs(1), + ValidArgsFunction: completion.CompleteIAMOrganisationRoleIdentity, + RunE: func(cmd *cobra.Command, args []string) error { + ctx := cmd.Context() + client, err := thalassaclient.GetThalassaClient() + if err != nil { + return fmt.Errorf("failed to create client: %w", err) + } + ok, err := shared.PromptDestructiveUnlessForce(deleteForce, fmt.Sprintf("Are you sure you want to delete this organisation role?\n Role: %s\n", args[0])) + if err != nil { + return err + } + if !ok { + return nil + } + if err := client.IAM().DeleteOrganisationRole(ctx, args[0]); err != nil { + return fmt.Errorf("failed to delete role: %w", err) + } + fmt.Printf("Deleted role %s\n", args[0]) + return nil + }, +} + +func init() { + RolesCmd.AddCommand(deleteCmd) + deleteCmd.Flags().BoolVar(&deleteForce, shared.ForceKey, false, "Skip the confirmation prompt and delete") + deleteCmd.Flags().BoolVar(&noHeader, shared.NoHeaderKey, false, "Do not print table headers") +} diff --git a/cmd/iam/roles/get.go b/cmd/iam/roles/get.go new file mode 100644 index 0000000..665c500 --- /dev/null +++ b/cmd/iam/roles/get.go @@ -0,0 +1,82 @@ +package roles + +import ( + "fmt" + "strings" + + "github.com/spf13/cobra" + + "github.com/thalassa-cloud/cli/cmd/iam/internal/shared" + "github.com/thalassa-cloud/cli/internal/completion" + "github.com/thalassa-cloud/cli/internal/formattime" + "github.com/thalassa-cloud/cli/internal/table" + "github.com/thalassa-cloud/cli/internal/thalassaclient" +) + +var getCmd = &cobra.Command{ + Use: "get ", + Short: "Show a role including rules and bindings summary", + Args: cobra.ExactArgs(1), + ValidArgsFunction: completion.CompleteIAMOrganisationRoleIdentity, + RunE: func(cmd *cobra.Command, args []string) error { + ctx := cmd.Context() + client, err := thalassaclient.GetThalassaClient() + if err != nil { + return fmt.Errorf("failed to create client: %w", err) + } + role, err := client.IAM().GetOrganisationRole(ctx, args[0]) + if err != nil { + return fmt.Errorf("failed to get role: %w", err) + } + fmt.Printf("Identity: %s\n", role.Identity) + fmt.Printf("Name: %s\n", role.Name) + fmt.Printf("Slug: %s\n", role.Slug) + fmt.Printf("Description: %s\n", role.Description) + fmt.Printf("System: %v\n", role.System) + fmt.Printf("Read-only: %v\n", role.IsReadOnly) + fmt.Printf("Created: %s\n", formattime.FormatTime(role.CreatedAt.Local(), showExactTime)) + + if len(role.Rules) > 0 { + fmt.Println("\nRules:") + body := make([][]string, 0, len(role.Rules)) + for _, ru := range role.Rules { + perms := make([]string, 0, len(ru.Permissions)) + for _, p := range ru.Permissions { + perms = append(perms, string(p)) + } + body = append(body, []string{ + ru.Identity, + strings.Join(ru.Resources, ","), + strings.Join(ru.ResourceIdentities, ","), + strings.Join(perms, ","), + ru.Note, + }) + } + table.Print([]string{"ID", "Resources", "Resource IDs", "Permissions", "Note"}, body) + } + if len(role.Bindings) > 0 { + fmt.Println("\nBindings:") + body := make([][]string, 0, len(role.Bindings)) + for _, b := range role.Bindings { + subject := "" + switch { + case b.AppUser != nil: + subject = "user:" + shared.UserPtrDisplay(b.AppUser) + case b.OrganisationTeam != nil: + subject = "team:" + b.OrganisationTeam.Slug + case b.ServiceAccount != nil: + subject = "service_account:" + b.ServiceAccount.Slug + } + body = append(body, []string{b.Identity, b.Name, subject}) + } + table.Print([]string{"ID", "Name", "Subject"}, body) + } + return nil + }, +} + +func init() { + RolesCmd.AddCommand(getCmd) + getCmd.Flags().BoolVar(&noHeader, shared.NoHeaderKey, false, "Do not print table headers") + getCmd.Flags().BoolVar(&showExactTime, "exact-time", false, "Show full timestamps instead of relative time") +} diff --git a/cmd/iam/roles/list.go b/cmd/iam/roles/list.go new file mode 100644 index 0000000..3cdcde0 --- /dev/null +++ b/cmd/iam/roles/list.go @@ -0,0 +1,77 @@ +package roles + +import ( + "fmt" + + "github.com/spf13/cobra" + + "github.com/thalassa-cloud/cli/cmd/iam/internal/shared" + "github.com/thalassa-cloud/cli/internal/labels" + "github.com/thalassa-cloud/cli/internal/table" + "github.com/thalassa-cloud/cli/internal/thalassaclient" + "github.com/thalassa-cloud/client-go/filters" + clientiam "github.com/thalassa-cloud/client-go/iam" +) + +var ( + noHeader bool + showExactTime bool + rolesListLabelSelector string +) + +var listCmd = &cobra.Command{ + Use: "list", + Aliases: []string{"ls"}, + Short: "List organisation roles", + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, _ []string) error { + ctx := cmd.Context() + client, err := thalassaclient.GetThalassaClient() + if err != nil { + return fmt.Errorf("failed to create client: %w", err) + } + req := &clientiam.ListOrganisationRolesRequest{} + if rolesListLabelSelector != "" { + req.Filters = []filters.Filter{ + &filters.LabelFilter{MatchLabels: labels.ParseLabelSelector(rolesListLabelSelector)}, + } + } + roles, err := client.IAM().ListOrganisationRoles(ctx, req) + if err != nil { + return fmt.Errorf("failed to list roles: %w", err) + } + body := make([][]string, 0, len(roles)) + for _, r := range roles { + sys := "no" + if r.System { + sys = "yes" + } + ro := "no" + if r.IsReadOnly { + ro = "yes" + } + body = append(body, []string{ + r.Identity, + r.Name, + r.Slug, + sys, + ro, + fmt.Sprintf("%d", len(r.Rules)), + fmt.Sprintf("%d", len(r.Bindings)), + }) + } + if noHeader { + table.Print(nil, body) + } else { + table.Print([]string{"ID", "Name", "Slug", "System", "Read-only", "Rules", "Bindings"}, body) + } + return nil + }, +} + +func init() { + RolesCmd.AddCommand(listCmd) + listCmd.Flags().BoolVar(&noHeader, shared.NoHeaderKey, false, "Do not print table headers") + listCmd.Flags().BoolVar(&showExactTime, "exact-time", false, "Show full timestamps instead of relative time") + listCmd.Flags().StringVar(&rolesListLabelSelector, "label-selector", "", "Filter by labels (key=value,key2=value2)") +} diff --git a/cmd/iam/roles/roles.go b/cmd/iam/roles/roles.go new file mode 100644 index 0000000..8b7aca5 --- /dev/null +++ b/cmd/iam/roles/roles.go @@ -0,0 +1,21 @@ +package roles + +import ( + "github.com/spf13/cobra" + + "github.com/thalassa-cloud/cli/cmd/iam/roles/bindings" + "github.com/thalassa-cloud/cli/cmd/iam/roles/rules" +) + +// RolesCmd represents organisation IAM roles. +var RolesCmd = &cobra.Command{ + Use: "roles", + Short: "Organisation IAM roles, permission rules, and bindings", + Long: `Custom organisation roles define permission rules and can be bound to users, teams, +or service accounts. System roles may be read-only; the API enforces what you can change.`, +} + +func init() { + RolesCmd.AddCommand(rules.RulesCmd) + RolesCmd.AddCommand(bindings.BindingsCmd) +} diff --git a/cmd/iam/roles/rules/add.go b/cmd/iam/roles/rules/add.go new file mode 100644 index 0000000..5d1fa1a --- /dev/null +++ b/cmd/iam/roles/rules/add.go @@ -0,0 +1,72 @@ +package rules + +import ( + "fmt" + "strings" + + "github.com/spf13/cobra" + + "github.com/thalassa-cloud/cli/cmd/iam/internal/shared" + "github.com/thalassa-cloud/cli/internal/completion" + "github.com/thalassa-cloud/cli/internal/table" + "github.com/thalassa-cloud/cli/internal/thalassaclient" + clientiam "github.com/thalassa-cloud/client-go/iam" +) + +var ( + noHeader bool + ruleResources []string + ruleResourceIdentities []string + rulePermissions []string + ruleNote string +) + +var addCmd = &cobra.Command{ + Use: "add ", + Short: "Add a permission rule to a role", + Args: cobra.ExactArgs(1), + ValidArgsFunction: completion.CompleteIAMOrganisationRoleIdentity, + RunE: func(cmd *cobra.Command, args []string) error { + ctx := cmd.Context() + if len(rulePermissions) == 0 { + return fmt.Errorf("at least one --permission is required (create, read, update, delete, list, *)") + } + perms := make([]clientiam.PermissionType, 0, len(rulePermissions)) + for _, p := range rulePermissions { + perms = append(perms, clientiam.PermissionType(p)) + } + client, err := thalassaclient.GetThalassaClient() + if err != nil { + return fmt.Errorf("failed to create client: %w", err) + } + rule, err := client.IAM().AddRoleRule(ctx, args[0], clientiam.OrganisationRolePermissionRule{ + Resources: ruleResources, + ResourceIdentities: ruleResourceIdentities, + Permissions: perms, + Note: ruleNote, + }) + if err != nil { + return fmt.Errorf("failed to add rule: %w", err) + } + if rule == nil { + return nil + } + body := [][]string{{rule.Identity, strings.Join(ruleResources, ","), strings.Join(rulePermissions, ",")}} + if noHeader { + table.Print(nil, body) + } else { + table.Print([]string{"Rule ID", "Resources", "Permissions"}, body) + } + return nil + }, +} + +func init() { + RulesCmd.AddCommand(addCmd) + addCmd.Flags().BoolVar(&noHeader, shared.NoHeaderKey, false, "Do not print table headers") + addCmd.Flags().StringSliceVar(&ruleResources, "resource", nil, "Resource type (repeatable)") + addCmd.Flags().StringSliceVar(&ruleResourceIdentities, "resource-identity", nil, "Concrete resource identity (repeatable)") + addCmd.Flags().StringSliceVar(&rulePermissions, "permission", nil, "Permission: create, read, update, delete, list, or * (repeatable)") + addCmd.Flags().StringVar(&ruleNote, "note", "", "Human-readable note for the rule") + _ = addCmd.RegisterFlagCompletionFunc("permission", completion.CompleteIAMPermissionType) +} diff --git a/cmd/iam/roles/rules/delete.go b/cmd/iam/roles/rules/delete.go new file mode 100644 index 0000000..a6c5578 --- /dev/null +++ b/cmd/iam/roles/rules/delete.go @@ -0,0 +1,45 @@ +package rules + +import ( + "fmt" + + "github.com/spf13/cobra" + + "github.com/thalassa-cloud/cli/cmd/iam/internal/shared" + "github.com/thalassa-cloud/cli/internal/completion" + "github.com/thalassa-cloud/cli/internal/thalassaclient" +) + +var deleteForce bool + +var deleteCmd = &cobra.Command{ + Use: "delete ", + Short: "Remove a permission rule from a role", + Args: cobra.ExactArgs(2), + ValidArgsFunction: completion.CompleteIAMRoleThenRule, + RunE: func(cmd *cobra.Command, args []string) error { + ctx := cmd.Context() + client, err := thalassaclient.GetThalassaClient() + if err != nil { + return fmt.Errorf("failed to create client: %w", err) + } + ok, err := shared.PromptDestructiveUnlessForce(deleteForce, fmt.Sprintf("Are you sure you want to remove this permission rule from the role?\n Role: %s\n Rule: %s\n", args[0], args[1])) + if err != nil { + return err + } + if !ok { + return nil + } + if err := client.IAM().DeleteRuleFromRole(ctx, args[0], args[1]); err != nil { + return fmt.Errorf("failed to delete rule: %w", err) + } + fmt.Printf("Deleted rule %s from role %s\n", args[1], args[0]) + return nil + }, +} + +func init() { + RulesCmd.AddCommand(deleteCmd) + deleteCmd.Flags().BoolVar(&deleteForce, shared.ForceKey, false, "Skip the confirmation prompt and delete") + deleteCmd.Flags().BoolVar(&noHeader, shared.NoHeaderKey, false, "Do not print table headers") +} diff --git a/cmd/iam/roles/rules/rules.go b/cmd/iam/roles/rules/rules.go new file mode 100644 index 0000000..070af68 --- /dev/null +++ b/cmd/iam/roles/rules/rules.go @@ -0,0 +1,9 @@ +package rules + +import "github.com/spf13/cobra" + +// RulesCmd manages permission rules on organisation roles. +var RulesCmd = &cobra.Command{ + Use: "rules", + Short: "Permission rules on a role", +} diff --git a/cmd/iam/serviceaccounts/create.go b/cmd/iam/serviceaccounts/create.go new file mode 100644 index 0000000..9d56f15 --- /dev/null +++ b/cmd/iam/serviceaccounts/create.go @@ -0,0 +1,67 @@ +package serviceaccounts + +import ( + "fmt" + + "github.com/spf13/cobra" + + "github.com/thalassa-cloud/cli/cmd/iam/internal/shared" + "github.com/thalassa-cloud/cli/internal/table" + "github.com/thalassa-cloud/cli/internal/thalassaclient" + clientiam "github.com/thalassa-cloud/client-go/iam" +) + +var ( + createName string + createDescription string + createLabels []string + createAnnotations []string +) + +var createCmd = &cobra.Command{ + Use: "create", + Short: "Create a service account", + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, _ []string) error { + ctx := cmd.Context() + if createName == "" { + return fmt.Errorf("--name is required") + } + client, err := thalassaclient.GetThalassaClient() + if err != nil { + return fmt.Errorf("failed to create client: %w", err) + } + req := clientiam.CreateServiceAccountRequest{ + Name: createName, + Labels: shared.KeyValuePairsToMap(createLabels), + Annotations: shared.KeyValuePairsToMap(createAnnotations), + } + if createDescription != "" { + req.Description = &createDescription + } + sa, err := client.IAM().CreateServiceAccount(ctx, req) + if err != nil { + return fmt.Errorf("failed to create service account: %w", err) + } + if sa == nil { + return nil + } + body := [][]string{{sa.Identity, sa.Name, sa.Slug}} + if noHeader { + table.Print(nil, body) + } else { + table.Print([]string{"ID", "Name", "Slug"}, body) + } + return nil + }, +} + +func init() { + ServiceAccountsCmd.AddCommand(createCmd) + createCmd.Flags().BoolVar(&noHeader, shared.NoHeaderKey, false, "Do not print table headers") + createCmd.Flags().StringVar(&createName, "name", "", "Service account name") + createCmd.Flags().StringVar(&createDescription, "description", "", "Description") + createCmd.Flags().StringSliceVar(&createLabels, "labels", nil, "Labels as key=value (repeatable)") + createCmd.Flags().StringSliceVar(&createAnnotations, "annotations", nil, "Annotations as key=value (repeatable)") + _ = createCmd.MarkFlagRequired("name") +} diff --git a/cmd/iam/serviceaccounts/delete.go b/cmd/iam/serviceaccounts/delete.go new file mode 100644 index 0000000..ccdcb8b --- /dev/null +++ b/cmd/iam/serviceaccounts/delete.go @@ -0,0 +1,45 @@ +package serviceaccounts + +import ( + "fmt" + + "github.com/spf13/cobra" + + "github.com/thalassa-cloud/cli/cmd/iam/internal/shared" + "github.com/thalassa-cloud/cli/internal/completion" + "github.com/thalassa-cloud/cli/internal/thalassaclient" +) + +var deleteForce bool + +var deleteCmd = &cobra.Command{ + Use: "delete ", + Short: "Delete a service account", + Args: cobra.ExactArgs(1), + ValidArgsFunction: completion.CompleteIAMServiceAccountIdentity, + RunE: func(cmd *cobra.Command, args []string) error { + ctx := cmd.Context() + client, err := thalassaclient.GetThalassaClient() + if err != nil { + return fmt.Errorf("failed to create client: %w", err) + } + ok, err := shared.PromptDestructiveUnlessForce(deleteForce, fmt.Sprintf("Are you sure you want to delete this service account?\n Identity: %s\n", args[0])) + if err != nil { + return err + } + if !ok { + return nil + } + if err := client.IAM().DeleteServiceAccount(ctx, args[0]); err != nil { + return fmt.Errorf("failed to delete service account: %w", err) + } + fmt.Printf("Deleted service account %s\n", args[0]) + return nil + }, +} + +func init() { + ServiceAccountsCmd.AddCommand(deleteCmd) + deleteCmd.Flags().BoolVar(&deleteForce, shared.ForceKey, false, "Skip the confirmation prompt and delete") + deleteCmd.Flags().BoolVar(&noHeader, shared.NoHeaderKey, false, "Do not print table headers") +} diff --git a/cmd/iam/serviceaccounts/get.go b/cmd/iam/serviceaccounts/get.go new file mode 100644 index 0000000..f1ad431 --- /dev/null +++ b/cmd/iam/serviceaccounts/get.go @@ -0,0 +1,57 @@ +package serviceaccounts + +import ( + "fmt" + + "github.com/spf13/cobra" + + "github.com/thalassa-cloud/cli/cmd/iam/internal/shared" + "github.com/thalassa-cloud/cli/internal/completion" + "github.com/thalassa-cloud/cli/internal/formattime" + "github.com/thalassa-cloud/cli/internal/table" + "github.com/thalassa-cloud/cli/internal/thalassaclient" +) + +var getCmd = &cobra.Command{ + Use: "get ", + Short: "Show a service account", + Args: cobra.ExactArgs(1), + ValidArgsFunction: completion.CompleteIAMServiceAccountIdentity, + RunE: func(cmd *cobra.Command, args []string) error { + ctx := cmd.Context() + client, err := thalassaclient.GetThalassaClient() + if err != nil { + return fmt.Errorf("failed to create client: %w", err) + } + sa, err := client.IAM().GetServiceAccount(ctx, args[0]) + if err != nil { + return fmt.Errorf("failed to get service account: %w", err) + } + fmt.Printf("Identity: %s\n", sa.Identity) + fmt.Printf("Name: %s\n", sa.Name) + fmt.Printf("Slug: %s\n", sa.Slug) + if sa.Description != nil { + fmt.Printf("Description: %s\n", *sa.Description) + } + fmt.Printf("Created: %s\n", formattime.FormatTime(sa.CreatedAt.Local(), showExactTime)) + if len(sa.RoleBindings) > 0 { + fmt.Println("\nRole bindings:") + body := make([][]string, 0, len(sa.RoleBindings)) + for _, b := range sa.RoleBindings { + roleName := "" + if b.OrganisationRole != nil { + roleName = b.OrganisationRole.Slug + } + body = append(body, []string{b.Identity, b.Name, roleName}) + } + table.Print([]string{"ID", "Name", "Role"}, body) + } + return nil + }, +} + +func init() { + ServiceAccountsCmd.AddCommand(getCmd) + getCmd.Flags().BoolVar(&noHeader, shared.NoHeaderKey, false, "Do not print table headers") + getCmd.Flags().BoolVar(&showExactTime, "exact-time", false, "Show full timestamps instead of relative time") +} diff --git a/cmd/iam/serviceaccounts/list.go b/cmd/iam/serviceaccounts/list.go new file mode 100644 index 0000000..b074441 --- /dev/null +++ b/cmd/iam/serviceaccounts/list.go @@ -0,0 +1,72 @@ +package serviceaccounts + +import ( + "fmt" + + "github.com/spf13/cobra" + + "github.com/thalassa-cloud/cli/cmd/iam/internal/shared" + "github.com/thalassa-cloud/cli/internal/formattime" + "github.com/thalassa-cloud/cli/internal/labels" + "github.com/thalassa-cloud/cli/internal/table" + "github.com/thalassa-cloud/cli/internal/thalassaclient" + "github.com/thalassa-cloud/client-go/filters" + clientiam "github.com/thalassa-cloud/client-go/iam" +) + +var ( + noHeader bool + showExactTime bool + listSelector string +) + +var listCmd = &cobra.Command{ + Use: "list", + Aliases: []string{"ls"}, + Short: "List service accounts", + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, _ []string) error { + ctx := cmd.Context() + client, err := thalassaclient.GetThalassaClient() + if err != nil { + return fmt.Errorf("failed to create client: %w", err) + } + req := &clientiam.ListServiceAccountsRequest{} + if listSelector != "" { + req.Filters = []filters.Filter{ + &filters.LabelFilter{MatchLabels: labels.ParseLabelSelector(listSelector)}, + } + } + list, err := client.IAM().ListServiceAccounts(ctx, req) + if err != nil { + return fmt.Errorf("failed to list service accounts: %w", err) + } + body := make([][]string, 0, len(list)) + for _, sa := range list { + desc := "" + if sa.Description != nil { + desc = *sa.Description + } + body = append(body, []string{ + sa.Identity, + sa.Name, + sa.Slug, + desc, + formattime.FormatTime(sa.CreatedAt.Local(), showExactTime), + }) + } + if noHeader { + table.Print(nil, body) + } else { + table.Print([]string{"ID", "Name", "Slug", "Description", "Created"}, body) + } + return nil + }, +} + +func init() { + ServiceAccountsCmd.AddCommand(listCmd) + listCmd.Flags().BoolVar(&noHeader, shared.NoHeaderKey, false, "Do not print table headers") + listCmd.Flags().BoolVar(&showExactTime, "exact-time", false, "Show full timestamps instead of relative time") + listCmd.Flags().StringVar(&listSelector, "label-selector", "", "Filter by labels (key=value,key2=value2)") +} diff --git a/cmd/iam/serviceaccounts/serviceaccounts.go b/cmd/iam/serviceaccounts/serviceaccounts.go new file mode 100644 index 0000000..9ee3c3e --- /dev/null +++ b/cmd/iam/serviceaccounts/serviceaccounts.go @@ -0,0 +1,10 @@ +package serviceaccounts + +import "github.com/spf13/cobra" + +// ServiceAccountsCmd manages organisation service accounts. +var ServiceAccountsCmd = &cobra.Command{ + Use: "service-accounts", + Aliases: []string{"sa", "service-account"}, + Short: "Organisation service accounts", +} diff --git a/cmd/iam/serviceaccounts/update.go b/cmd/iam/serviceaccounts/update.go new file mode 100644 index 0000000..90a36fa --- /dev/null +++ b/cmd/iam/serviceaccounts/update.go @@ -0,0 +1,70 @@ +package serviceaccounts + +import ( + "fmt" + + "github.com/spf13/cobra" + + "github.com/thalassa-cloud/cli/cmd/iam/internal/shared" + "github.com/thalassa-cloud/cli/internal/completion" + "github.com/thalassa-cloud/cli/internal/table" + "github.com/thalassa-cloud/cli/internal/thalassaclient" + clientiam "github.com/thalassa-cloud/client-go/iam" +) + +var ( + updateName string + updateDescription string + updateLabels []string + updateAnnotations []string +) + +var updateCmd = &cobra.Command{ + Use: "update ", + Short: "Update a service account (only set flags are sent)", + Args: cobra.ExactArgs(1), + ValidArgsFunction: completion.CompleteIAMServiceAccountIdentity, + RunE: func(cmd *cobra.Command, args []string) error { + ctx := cmd.Context() + client, err := thalassaclient.GetThalassaClient() + if err != nil { + return fmt.Errorf("failed to create client: %w", err) + } + req := clientiam.UpdateServiceAccountRequest{} + if cmd.Flags().Changed("name") { + req.Name = &updateName + } + if cmd.Flags().Changed("description") { + req.Description = &updateDescription + } + if cmd.Flags().Changed("labels") { + req.Labels = shared.KeyValuePairsToMap(updateLabels) + } + if cmd.Flags().Changed("annotations") { + req.Annotations = shared.KeyValuePairsToMap(updateAnnotations) + } + sa, err := client.IAM().UpdateServiceAccount(ctx, args[0], req) + if err != nil { + return fmt.Errorf("failed to update service account: %w", err) + } + if sa == nil { + return nil + } + body := [][]string{{sa.Identity, sa.Name, sa.Slug}} + if noHeader { + table.Print(nil, body) + } else { + table.Print([]string{"ID", "Name", "Slug"}, body) + } + return nil + }, +} + +func init() { + ServiceAccountsCmd.AddCommand(updateCmd) + updateCmd.Flags().BoolVar(&noHeader, shared.NoHeaderKey, false, "Do not print table headers") + updateCmd.Flags().StringVar(&updateName, "name", "", "Name") + updateCmd.Flags().StringVar(&updateDescription, "description", "", "Description (empty to clear)") + updateCmd.Flags().StringSliceVar(&updateLabels, "labels", nil, "Replace labels (key=value, repeatable)") + updateCmd.Flags().StringSliceVar(&updateAnnotations, "annotations", nil, "Replace annotations (key=value, repeatable)") +} diff --git a/cmd/iam/teams/create.go b/cmd/iam/teams/create.go new file mode 100644 index 0000000..0d06841 --- /dev/null +++ b/cmd/iam/teams/create.go @@ -0,0 +1,63 @@ +package teams + +import ( + "fmt" + + "github.com/spf13/cobra" + + "github.com/thalassa-cloud/cli/cmd/iam/internal/shared" + "github.com/thalassa-cloud/cli/internal/formattime" + "github.com/thalassa-cloud/cli/internal/table" + "github.com/thalassa-cloud/cli/internal/thalassaclient" + clientiam "github.com/thalassa-cloud/client-go/iam" +) + +var ( + createName string + createDescription string + createLabels []string + createAnnotations []string +) + +var createCmd = &cobra.Command{ + Use: "create", + Short: "Create a team", + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, _ []string) error { + ctx := cmd.Context() + if createName == "" { + return fmt.Errorf("--name is required") + } + client, err := thalassaclient.GetThalassaClient() + if err != nil { + return fmt.Errorf("failed to create client: %w", err) + } + team, err := client.IAM().CreateTeam(ctx, clientiam.CreateTeam{ + Name: createName, + Description: createDescription, + Labels: shared.KeyValuePairsToMap(createLabels), + Annotations: shared.KeyValuePairsToMap(createAnnotations), + }) + if err != nil { + return fmt.Errorf("failed to create team: %w", err) + } + body := [][]string{{team.Identity, team.Name, team.Slug, formattime.FormatTime(team.CreatedAt.Local(), showExactTime)}} + if noHeader { + table.Print(nil, body) + } else { + table.Print([]string{"ID", "Name", "Slug", "Created"}, body) + } + return nil + }, +} + +func init() { + TeamsCmd.AddCommand(createCmd) + createCmd.Flags().BoolVar(&noHeader, shared.NoHeaderKey, false, "Do not print table headers") + createCmd.Flags().BoolVar(&showExactTime, "exact-time", false, "Show full timestamps instead of relative time") + createCmd.Flags().StringVar(&createName, "name", "", "Team display name") + createCmd.Flags().StringVar(&createDescription, "description", "", "Team description") + createCmd.Flags().StringSliceVar(&createLabels, "labels", nil, "Labels as key=value (repeatable)") + createCmd.Flags().StringSliceVar(&createAnnotations, "annotations", nil, "Annotations as key=value (repeatable)") + _ = createCmd.MarkFlagRequired("name") +} diff --git a/cmd/iam/teams/delete.go b/cmd/iam/teams/delete.go new file mode 100644 index 0000000..ab0431a --- /dev/null +++ b/cmd/iam/teams/delete.go @@ -0,0 +1,46 @@ +package teams + +import ( + "fmt" + + "github.com/spf13/cobra" + + "github.com/thalassa-cloud/cli/cmd/iam/internal/shared" + "github.com/thalassa-cloud/cli/internal/completion" + "github.com/thalassa-cloud/cli/internal/thalassaclient" +) + +var deleteForce bool + +var deleteCmd = &cobra.Command{ + Use: "delete ", + Short: "Delete a team", + Args: cobra.ExactArgs(1), + ValidArgsFunction: completion.CompleteIAMTeamIdentity, + RunE: func(cmd *cobra.Command, args []string) error { + ctx := cmd.Context() + client, err := thalassaclient.GetThalassaClient() + if err != nil { + return fmt.Errorf("failed to create client: %w", err) + } + ok, err := shared.PromptDestructiveUnlessForce(deleteForce, fmt.Sprintf("Are you sure you want to delete this team?\n Team: %s\n", args[0])) + if err != nil { + return err + } + if !ok { + return nil + } + if err := client.IAM().DeleteTeam(ctx, args[0]); err != nil { + return fmt.Errorf("failed to delete team: %w", err) + } + fmt.Printf("Deleted team %s\n", args[0]) + return nil + }, +} + +func init() { + TeamsCmd.AddCommand(deleteCmd) + deleteCmd.Flags().BoolVar(&deleteForce, shared.ForceKey, false, "Skip the confirmation prompt and delete") + deleteCmd.Flags().BoolVar(&noHeader, shared.NoHeaderKey, false, "Do not print table headers") + deleteCmd.Flags().BoolVar(&showExactTime, "exact-time", false, "Show full timestamps instead of relative time") +} diff --git a/cmd/iam/teams/get.go b/cmd/iam/teams/get.go new file mode 100644 index 0000000..d0ca85e --- /dev/null +++ b/cmd/iam/teams/get.go @@ -0,0 +1,56 @@ +package teams + +import ( + "fmt" + + "github.com/spf13/cobra" + + "github.com/thalassa-cloud/cli/cmd/iam/internal/shared" + "github.com/thalassa-cloud/cli/internal/completion" + "github.com/thalassa-cloud/cli/internal/formattime" + "github.com/thalassa-cloud/cli/internal/table" + "github.com/thalassa-cloud/cli/internal/thalassaclient" + clientiam "github.com/thalassa-cloud/client-go/iam" +) + +var getCmd = &cobra.Command{ + Use: "get ", + Short: "Show a team and its members", + Args: cobra.ExactArgs(1), + ValidArgsFunction: completion.CompleteIAMTeamIdentity, + RunE: func(cmd *cobra.Command, args []string) error { + ctx := cmd.Context() + client, err := thalassaclient.GetThalassaClient() + if err != nil { + return fmt.Errorf("failed to create client: %w", err) + } + team, err := client.IAM().GetTeam(ctx, args[0], &clientiam.GetTeamRequest{}) + if err != nil { + return fmt.Errorf("failed to get team: %w", err) + } + fmt.Printf("Identity: %s\n", team.Identity) + fmt.Printf("Name: %s\n", team.Name) + fmt.Printf("Slug: %s\n", team.Slug) + fmt.Printf("Description: %s\n", team.Description) + fmt.Printf("Created: %s\n", formattime.FormatTime(team.CreatedAt.Local(), showExactTime)) + if len(team.Members) > 0 { + fmt.Println("\nMembers:") + body := make([][]string, 0, len(team.Members)) + for _, m := range team.Members { + body = append(body, []string{ + m.Identity, + m.Role, + shared.UserDisplay(m.User), + }) + } + table.Print([]string{"ID", "Role", "User"}, body) + } + return nil + }, +} + +func init() { + TeamsCmd.AddCommand(getCmd) + getCmd.Flags().BoolVar(&noHeader, shared.NoHeaderKey, false, "Do not print table headers") + getCmd.Flags().BoolVar(&showExactTime, "exact-time", false, "Show full timestamps instead of relative time") +} diff --git a/cmd/iam/teams/list.go b/cmd/iam/teams/list.go new file mode 100644 index 0000000..5bb1fcf --- /dev/null +++ b/cmd/iam/teams/list.go @@ -0,0 +1,70 @@ +package teams + +import ( + "fmt" + + "github.com/spf13/cobra" + + "github.com/thalassa-cloud/cli/cmd/iam/internal/shared" + "github.com/thalassa-cloud/cli/internal/formattime" + "github.com/thalassa-cloud/cli/internal/labels" + "github.com/thalassa-cloud/cli/internal/table" + "github.com/thalassa-cloud/cli/internal/thalassaclient" + "github.com/thalassa-cloud/client-go/filters" + clientiam "github.com/thalassa-cloud/client-go/iam" +) + +var ( + noHeader bool + showExactTime bool + teamsListLabelSelector string +) + +var listCmd = &cobra.Command{ + Use: "list", + Aliases: []string{"ls"}, + Short: "List teams", + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, _ []string) error { + ctx := cmd.Context() + client, err := thalassaclient.GetThalassaClient() + if err != nil { + return fmt.Errorf("failed to create client: %w", err) + } + req := &clientiam.ListTeamsRequest{} + if teamsListLabelSelector != "" { + req.Filters = []filters.Filter{ + &filters.LabelFilter{MatchLabels: labels.ParseLabelSelector(teamsListLabelSelector)}, + } + } + teams, err := client.IAM().ListTeams(ctx, req) + if err != nil { + return fmt.Errorf("failed to list teams: %w", err) + } + body := make([][]string, 0, len(teams)) + for _, t := range teams { + body = append(body, []string{ + t.Identity, + t.Name, + t.Slug, + t.Description, + fmt.Sprintf("%d", len(t.Members)), + formattime.FormatTime(t.CreatedAt.Local(), showExactTime), + }) + } + headers := []string{"ID", "Name", "Slug", "Description", "Members", "Age"} + if noHeader { + table.Print(nil, body) + } else { + table.Print(headers, body) + } + return nil + }, +} + +func init() { + TeamsCmd.AddCommand(listCmd) + listCmd.Flags().BoolVar(&noHeader, shared.NoHeaderKey, false, "Do not print table headers") + listCmd.Flags().BoolVar(&showExactTime, "exact-time", false, "Show full timestamps instead of relative time") + listCmd.Flags().StringVar(&teamsListLabelSelector, "label-selector", "", "Filter by labels (key=value,key2=value2)") +} diff --git a/cmd/iam/teams/members/add.go b/cmd/iam/teams/members/add.go new file mode 100644 index 0000000..1b70b1c --- /dev/null +++ b/cmd/iam/teams/members/add.go @@ -0,0 +1,49 @@ +package members + +import ( + "fmt" + + "github.com/spf13/cobra" + + "github.com/thalassa-cloud/cli/cmd/iam/internal/shared" + "github.com/thalassa-cloud/cli/internal/completion" + "github.com/thalassa-cloud/cli/internal/thalassaclient" + clientiam "github.com/thalassa-cloud/client-go/iam" +) + +var addUser string +var addRole string + +var addCmd = &cobra.Command{ + Use: "add ", + Short: "Add a user to a team", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + ctx := cmd.Context() + if addUser == "" || addRole == "" { + return fmt.Errorf("--user and --role are required") + } + client, err := thalassaclient.GetThalassaClient() + if err != nil { + return fmt.Errorf("failed to create client: %w", err) + } + if err := client.IAM().AddTeamMember(ctx, args[0], clientiam.AddTeamMemberRequest{ + UserIdentity: addUser, + Role: addRole, + }); err != nil { + return fmt.Errorf("failed to add team member: %w", err) + } + fmt.Printf("Added user %s to team %s with role %s\n", addUser, args[0], addRole) + return nil + }, +} + +func init() { + TeamMembersCmd.AddCommand(addCmd) + addCmd.Flags().BoolVar(&noHeader, shared.NoHeaderKey, false, "Do not print table headers") + addCmd.Flags().StringVar(&addUser, "user", "", "User identity to add") + addCmd.Flags().StringVar(&addRole, "role", "", "Team role for the user") + _ = addCmd.RegisterFlagCompletionFunc("user", completion.CompleteIAMAppUserSubject) + _ = addCmd.MarkFlagRequired("user") + _ = addCmd.MarkFlagRequired("role") +} diff --git a/cmd/iam/teams/members/list.go b/cmd/iam/teams/members/list.go new file mode 100644 index 0000000..fa2da55 --- /dev/null +++ b/cmd/iam/teams/members/list.go @@ -0,0 +1,48 @@ +package members + +import ( + "fmt" + + "github.com/spf13/cobra" + + "github.com/thalassa-cloud/cli/cmd/iam/internal/shared" + "github.com/thalassa-cloud/cli/internal/completion" + "github.com/thalassa-cloud/cli/internal/table" + "github.com/thalassa-cloud/cli/internal/thalassaclient" + clientiam "github.com/thalassa-cloud/client-go/iam" +) + +var noHeader bool + +var listCmd = &cobra.Command{ + Use: "list ", + Short: "List members of a team", + Args: cobra.ExactArgs(1), + ValidArgsFunction: completion.CompleteIAMTeamIdentity, + RunE: func(cmd *cobra.Command, args []string) error { + ctx := cmd.Context() + client, err := thalassaclient.GetThalassaClient() + if err != nil { + return fmt.Errorf("failed to create client: %w", err) + } + team, err := client.IAM().GetTeam(ctx, args[0], &clientiam.GetTeamRequest{}) + if err != nil { + return fmt.Errorf("failed to get team: %w", err) + } + body := make([][]string, 0, len(team.Members)) + for _, m := range team.Members { + body = append(body, []string{m.Identity, m.Role, shared.UserDisplay(m.User)}) + } + if noHeader { + table.Print(nil, body) + } else { + table.Print([]string{"ID", "Role", "User"}, body) + } + return nil + }, +} + +func init() { + TeamMembersCmd.AddCommand(listCmd) + listCmd.Flags().BoolVar(&noHeader, shared.NoHeaderKey, false, "Do not print table headers") +} diff --git a/cmd/iam/teams/members/members.go b/cmd/iam/teams/members/members.go new file mode 100644 index 0000000..e2ce04e --- /dev/null +++ b/cmd/iam/teams/members/members.go @@ -0,0 +1,9 @@ +package members + +import "github.com/spf13/cobra" + +// TeamMembersCmd manages membership on a team (distinct from organisation members). +var TeamMembersCmd = &cobra.Command{ + Use: "members", + Short: "Manage team membership", +} diff --git a/cmd/iam/teams/members/remove.go b/cmd/iam/teams/members/remove.go new file mode 100644 index 0000000..1a9878a --- /dev/null +++ b/cmd/iam/teams/members/remove.go @@ -0,0 +1,46 @@ +package members + +import ( + "fmt" + + "github.com/spf13/cobra" + + "github.com/thalassa-cloud/cli/cmd/iam/internal/shared" + "github.com/thalassa-cloud/cli/internal/completion" + "github.com/thalassa-cloud/cli/internal/thalassaclient" +) + +var removeForce bool + +var removeCmd = &cobra.Command{ + Use: "remove ", + Aliases: []string{"rm"}, + Short: "Remove a member from a team", + Args: cobra.ExactArgs(2), + ValidArgsFunction: completion.CompleteIAMTeamThenTeamMember, + RunE: func(cmd *cobra.Command, args []string) error { + ctx := cmd.Context() + client, err := thalassaclient.GetThalassaClient() + if err != nil { + return fmt.Errorf("failed to create client: %w", err) + } + ok, err := shared.PromptDestructiveUnlessForce(removeForce, fmt.Sprintf("Are you sure you want to remove this member from the team?\n Team: %s\n Member: %s\n", args[0], args[1])) + if err != nil { + return err + } + if !ok { + return nil + } + if err := client.IAM().RemoveTeamMember(ctx, args[0], args[1]); err != nil { + return fmt.Errorf("failed to remove team member: %w", err) + } + fmt.Printf("Removed member %s from team %s\n", args[1], args[0]) + return nil + }, +} + +func init() { + TeamMembersCmd.AddCommand(removeCmd) + removeCmd.Flags().BoolVar(&removeForce, shared.ForceKey, false, "Skip the confirmation prompt and remove the member") + removeCmd.Flags().BoolVar(&noHeader, shared.NoHeaderKey, false, "Do not print table headers") +} diff --git a/cmd/iam/teams/teams.go b/cmd/iam/teams/teams.go new file mode 100644 index 0000000..8a6845e --- /dev/null +++ b/cmd/iam/teams/teams.go @@ -0,0 +1,17 @@ +package teams + +import ( + "github.com/spf13/cobra" + + "github.com/thalassa-cloud/cli/cmd/iam/teams/members" +) + +// TeamsCmd represents the teams command +var TeamsCmd = &cobra.Command{ + Use: "teams", + Short: "Manage organisation teams", +} + +func init() { + TeamsCmd.AddCommand(members.TeamMembersCmd) +} diff --git a/cmd/iam/teams/update.go b/cmd/iam/teams/update.go new file mode 100644 index 0000000..244c869 --- /dev/null +++ b/cmd/iam/teams/update.go @@ -0,0 +1,78 @@ +package teams + +import ( + "fmt" + + "github.com/spf13/cobra" + + "github.com/thalassa-cloud/cli/cmd/iam/internal/shared" + "github.com/thalassa-cloud/cli/internal/completion" + "github.com/thalassa-cloud/cli/internal/formattime" + "github.com/thalassa-cloud/cli/internal/table" + "github.com/thalassa-cloud/cli/internal/thalassaclient" + clientiam "github.com/thalassa-cloud/client-go/iam" +) + +var ( + updateName string + updateDescription string + updateLabels []string + updateAnnotations []string +) + +var updateCmd = &cobra.Command{ + Use: "update ", + Short: "Update a team (only flags you set are changed)", + Args: cobra.ExactArgs(1), + ValidArgsFunction: completion.CompleteIAMTeamIdentity, + RunE: func(cmd *cobra.Command, args []string) error { + ctx := cmd.Context() + client, err := thalassaclient.GetThalassaClient() + if err != nil { + return fmt.Errorf("failed to create client: %w", err) + } + team, err := client.IAM().GetTeam(ctx, args[0], &clientiam.GetTeamRequest{}) + if err != nil { + return fmt.Errorf("failed to get team: %w", err) + } + update := clientiam.UpdateTeam{ + Name: team.Name, + Description: team.Description, + Labels: team.Labels, + Annotations: team.Annotations, + } + if cmd.Flags().Changed("name") { + update.Name = updateName + } + if cmd.Flags().Changed("description") { + update.Description = updateDescription + } + if cmd.Flags().Changed("labels") { + update.Labels = shared.KeyValuePairsToMap(updateLabels) + } + if cmd.Flags().Changed("annotations") { + update.Annotations = shared.KeyValuePairsToMap(updateAnnotations) + } + out, err := client.IAM().UpdateTeam(ctx, team.Identity, update) + if err != nil { + return fmt.Errorf("failed to update team: %w", err) + } + body := [][]string{{out.Identity, out.Name, out.Slug, formattime.FormatTime(out.CreatedAt.Local(), showExactTime)}} + if noHeader { + table.Print(nil, body) + } else { + table.Print([]string{"ID", "Name", "Slug", "Created"}, body) + } + return nil + }, +} + +func init() { + TeamsCmd.AddCommand(updateCmd) + updateCmd.Flags().BoolVar(&noHeader, shared.NoHeaderKey, false, "Do not print table headers") + updateCmd.Flags().BoolVar(&showExactTime, "exact-time", false, "Show full timestamps instead of relative time") + updateCmd.Flags().StringVar(&updateName, "name", "", "Team display name") + updateCmd.Flags().StringVar(&updateDescription, "description", "", "Team description") + updateCmd.Flags().StringSliceVar(&updateLabels, "labels", nil, "Replace labels (key=value, repeatable)") + updateCmd.Flags().StringSliceVar(&updateAnnotations, "annotations", nil, "Replace annotations (key=value, repeatable)") +} diff --git a/cmd/iam/workloadidentityfederation/bootstrap.go b/cmd/iam/workloadidentityfederation/bootstrap.go new file mode 100644 index 0000000..da9927d --- /dev/null +++ b/cmd/iam/workloadidentityfederation/bootstrap.go @@ -0,0 +1,114 @@ +package workloadidentityfederation + +import ( + "fmt" + "strings" + + "github.com/spf13/cobra" + + "github.com/thalassa-cloud/cli/cmd/iam/internal/shared" + "github.com/thalassa-cloud/cli/internal/completion" + "github.com/thalassa-cloud/cli/internal/config/contextstate" + "github.com/thalassa-cloud/cli/internal/thalassaclient" +) + +// Persistent flags shared by all bootstrap subcommands. +var ( + flagRole string + flagTrustedAudiences []string + flagScopes []string + flagProviderName string + flagProviderDesc string + flagBootstrapName string + flagDryRun bool + flagNoHints bool +) + +var bootstrapCmd = &cobra.Command{ + Use: "bootstrap", + Short: "Provision workload identity for GitHub, GitLab, or Kubernetes", + Long: fmt.Sprintf(`Creates (when missing) a federated OIDC identity provider, a Thalassa service account, +a role binding to your organisation role, and a federated identity for the workload JWT subject. + +Resources are labelled %s=%s and %s=. + +Subcommands: + github — GitHub Actions (issuer %s) + gitlab — GitLab CI id_token + kubernetes — in-cluster service account JWTs +`, + LabelManagedBy, ValueManagedBy, LabelWIFVCS, GitHubActionsIssuer), +} + +func executeBootstrap(cmd *cobra.Command, opts BootstrapOptions) error { + opts.RoleRef = strings.TrimSpace(flagRole) + opts.ProviderDisplayName = strings.TrimSpace(flagProviderName) + opts.ProviderDescription = strings.TrimSpace(flagProviderDesc) + opts.ResourceName = strings.TrimSpace(flagBootstrapName) + opts.DryRun = flagDryRun + + scopes, err := shared.ParseAccessCredentialScopes(flagScopes) + if err != nil { + return err + } + opts.AllowedScopes = scopes + + client, err := thalassaclient.GetThalassaClient() + if err != nil { + return fmt.Errorf("failed to create client: %w", err) + } + + apiBase := strings.TrimSuffix(strings.TrimSpace(contextstate.Server()), "/") + if apiBase == "" { + apiBase = strings.TrimSuffix(contextstate.DefaultAPIURL, "/") + } + trusted := append([]string(nil), flagTrustedAudiences...) + if len(trusted) == 0 { + trusted = []string{apiBase} + } + opts.TrustedAudiences = trusted + + res, err := RunBootstrap(cmd.Context(), client, opts) + if err != nil { + return err + } + + printBootstrapOutcome(opts.VCS, res, flagDryRun) + if !flagNoHints && !flagDryRun { + printBootstrapHints(opts, res, apiBase, strings.TrimSpace(contextstate.Organisation())) + } else if flagDryRun && !flagNoHints { + fmt.Println("\n(dry-run: hints omitted; run without --dry-run after applying changes)") + } + return nil +} + +// parseGitHubRefKind parses --ref-kind for the github bootstrap subcommand. +func parseGitHubRefKind(s string) (RefKind, error) { + s = strings.ToLower(strings.TrimSpace(s)) + if s == "" { + s = string(RefKindBranch) + } + switch RefKind(s) { + case RefKindBranch, RefKindTag, RefKindEnvironment, RefKindPullRequest: + return RefKind(s), nil + default: + return "", fmt.Errorf("--ref-kind must be branch, tag, environment, or pull_request (got %q)", s) + } +} + +func init() { + p := bootstrapCmd.PersistentFlags() + p.StringVar(&flagRole, "role", "", "Organisation role identity, slug, or name (required)") + p.StringSliceVar(&flagTrustedAudiences, "trusted-audience", nil, "JWT aud values to trust (repeatable; default: current context API URL, e.g. https://api.thalassa.cloud)") + p.StringSliceVar(&flagScopes, "scope", nil, "Federated identity allowed scopes: api:read, api:write, kubernetes, objectStorage (default: api:read,api:write)") + p.StringVar(&flagProviderName, "provider-name", "", "Optional display name when creating the federated identity provider") + p.StringVar(&flagProviderDesc, "provider-description", "", "Optional description when creating the federated identity provider") + p.StringVar(&flagBootstrapName, "name", "", "Base name for the Thalassa service account and federated identity (federated identity becomes -fi; default: wif--)") + p.BoolVar(&flagDryRun, "dry-run", false, "Print planned changes without calling the API") + p.BoolVar(&flagNoHints, "no-hints", false, "Do not print platform hints after bootstrap") + + _ = bootstrapCmd.MarkPersistentFlagRequired("role") + _ = bootstrapCmd.RegisterFlagCompletionFunc("role", completion.CompleteIAMOrganisationRoleIdentityFlag) + + bootstrapCmd.AddCommand(bootstrapGitHubCmd, bootstrapGitLabCmd, bootstrapKubernetesCmd) +} diff --git a/cmd/iam/workloadidentityfederation/bootstrap_github.go b/cmd/iam/workloadidentityfederation/bootstrap_github.go new file mode 100644 index 0000000..d921a86 --- /dev/null +++ b/cmd/iam/workloadidentityfederation/bootstrap_github.go @@ -0,0 +1,65 @@ +package workloadidentityfederation + +import ( + "fmt" + "strings" + + "github.com/spf13/cobra" + + "github.com/thalassa-cloud/cli/internal/completion" +) + +var ( + flagGitHubRepository string + flagGitHubRefKind string + flagGitHubRef string +) + +var bootstrapGitHubCmd = &cobra.Command{ + Use: "github", + Short: "Bootstrap workload identity for GitHub Actions", + Long: `Creates or reuses a federated identity provider for GitHub's OIDC issuer, then binds your repository +and ref to a Thalassa service account via a federated identity. + +The JWT issuer is https://token.actions.githubusercontent.com. Match subjects with --repository (owner/name), +--ref-kind, and --ref (omit --ref only when --ref-kind is pull_request).`, + Example: ` # Main branch (JWT aud defaults to context API URL) + tcloud iam workload-identity-federation bootstrap github --repository acme/api --ref main --role deployer + + # Specific ref kind + tcloud iam workload-identity-federation bootstrap github --repository acme/api --ref-kind branch --ref main --role deployer + + # Pull request workflows (subject repo:owner/repo:pull_request) + tcloud iam workload-identity-federation bootstrap github --repository acme/api --ref-kind pull_request --role deployer + + # Custom Thalassa service account / federated identity names (federated identity: platform-ci-fi) + tcloud iam workload-identity-federation bootstrap github --repository acme/api --ref main --name platform-ci --role deployer`, + Args: cobra.NoArgs, + RunE: runBootstrapGitHub, +} + +func runBootstrapGitHub(cmd *cobra.Command, _ []string) error { + refKind, err := parseGitHubRefKind(flagGitHubRefKind) + if err != nil { + return err + } + if refKind != RefKindPullRequest && strings.TrimSpace(flagGitHubRef) == "" { + return fmt.Errorf("--ref is required unless --ref-kind is pull_request") + } + return executeBootstrap(cmd, BootstrapOptions{ + VCS: ValueVCSGitHub, + Repository: strings.TrimSpace(flagGitHubRepository), + RefKind: refKind, + Ref: strings.TrimSpace(flagGitHubRef), + }) +} + +func init() { + f := bootstrapGitHubCmd.Flags() + f.StringVar(&flagGitHubRepository, "repository", "", "GitHub repository as owner/name (required)") + f.StringVar(&flagGitHubRefKind, "ref-kind", "branch", "branch, tag, environment, or pull_request") + f.StringVar(&flagGitHubRef, "ref", "", "Branch, tag, or environment name (omit when --ref-kind pull_request)") + _ = bootstrapGitHubCmd.RegisterFlagCompletionFunc("ref-kind", completion.CompleteIAMWIFGitHubRefKind) + + _ = bootstrapGitHubCmd.MarkFlagRequired("repository") +} diff --git a/cmd/iam/workloadidentityfederation/bootstrap_gitlab.go b/cmd/iam/workloadidentityfederation/bootstrap_gitlab.go new file mode 100644 index 0000000..c42696a --- /dev/null +++ b/cmd/iam/workloadidentityfederation/bootstrap_gitlab.go @@ -0,0 +1,61 @@ +package workloadidentityfederation + +import ( + "fmt" + "strings" + + "github.com/spf13/cobra" + + "github.com/thalassa-cloud/cli/internal/completion" +) + +var ( + flagGitLabRepository string + flagGitLabRef string + flagGitLabRefType string + flagGitLabIssuer string +) + +var bootstrapGitLabCmd = &cobra.Command{ + Use: "gitlab", + Short: "Bootstrap workload identity for GitLab CI", + Long: `Creates or reuses a federated identity provider for your GitLab OIDC issuer, then binds the project +and ref to a Thalassa service account. + +The GitLab id_token sub uses project_path::ref_type::ref:.`, + Example: ` # GitLab.com, branch main + tcloud iam workload-identity-federation bootstrap gitlab --repository mygroup/myproject --ref main --role deployer + + # Tag pipeline + tcloud iam workload-identity-federation bootstrap gitlab --repository mygroup/myproject --ref v1.0.0 --ref-type tag --role deployer + + # Self-managed GitLab + tcloud iam workload-identity-federation bootstrap gitlab --repository mygroup/myproject --ref main --issuer https://gitlab.example.com --role deployer`, + Args: cobra.NoArgs, + RunE: runBootstrapGitLab, +} + +func runBootstrapGitLab(cmd *cobra.Command, _ []string) error { + if strings.TrimSpace(flagGitLabRef) == "" { + return fmt.Errorf("--ref is required (branch or tag name for the GitLab id_token sub)") + } + return executeBootstrap(cmd, BootstrapOptions{ + VCS: ValueVCSGitLab, + Repository: strings.TrimSpace(flagGitLabRepository), + RefKind: RefKindBranch, + Ref: strings.TrimSpace(flagGitLabRef), + GitLabRefType: strings.TrimSpace(flagGitLabRefType), + GitLabIssuer: strings.TrimSpace(flagGitLabIssuer), + }) +} + +func init() { + f := bootstrapGitLabCmd.Flags() + f.StringVar(&flagGitLabRepository, "repository", "", "GitLab project path as group/project (required)") + f.StringVar(&flagGitLabRef, "ref", "", "Git ref segment for id_token sub, e.g. branch or tag name (required)") + f.StringVar(&flagGitLabRefType, "ref-type", "branch", "ref_type claim in JWT sub: branch, tag, merge_request, etc.") + f.StringVar(&flagGitLabIssuer, "issuer", "https://gitlab.com", "GitLab OIDC issuer URL (self-managed)") + _ = bootstrapGitLabCmd.RegisterFlagCompletionFunc("ref-type", completion.CompleteIAMWIFGitLabRefType) + + _ = bootstrapGitLabCmd.MarkFlagRequired("repository") +} diff --git a/cmd/iam/workloadidentityfederation/bootstrap_kubernetes.go b/cmd/iam/workloadidentityfederation/bootstrap_kubernetes.go new file mode 100644 index 0000000..edfa5f0 --- /dev/null +++ b/cmd/iam/workloadidentityfederation/bootstrap_kubernetes.go @@ -0,0 +1,67 @@ +package workloadidentityfederation + +import ( + "fmt" + "strings" + + "github.com/spf13/cobra" + + "github.com/thalassa-cloud/cli/internal/completion" +) + +var ( + flagK8sNamespace string + flagK8sSAName string + flagK8sCluster string + flagK8sIssuer string +) + +var bootstrapKubernetesCmd = &cobra.Command{ + Use: "kubernetes", + Short: "Bootstrap workload identity for Kubernetes service accounts", + Aliases: []string{"k8s"}, + Long: `Binds system:serviceaccount:: to a Thalassa service account via a federated identity. + +Thalassa clusters: pass --cluster to resolve the cluster and use the platform-managed federated identity +provider labelled kubernetes_cluster_id=. If that provider is missing, bootstrap fails +(you must wait for cluster OIDC integration). + +Other clusters: pass --issuer with the same URL as kube-apiserver --service-account-issuer; the CLI +creates the federated identity provider if it does not exist yet.`, + Example: ` # Thalassa-managed cluster (OIDC provider from label kubernetes_cluster_id) + tcloud iam workload-identity-federation bootstrap kubernetes --cluster my-cluster-slug \ + --namespace default --service-account my-app --role deployer + + # Self-managed / custom issuer + tcloud iam workload-identity-federation bootstrap kubernetes --issuer https://k8s.example.com \ + --namespace cicd --service-account terraform --role deployer`, + Args: cobra.NoArgs, + RunE: runBootstrapKubernetes, +} + +func runBootstrapKubernetes(cmd *cobra.Command, _ []string) error { + ns := strings.TrimSpace(flagK8sNamespace) + sa := strings.TrimSpace(flagK8sSAName) + if ns == "" || sa == "" { + return fmt.Errorf("--namespace and --service-account are required") + } + if strings.TrimSpace(flagK8sIssuer) == "" && strings.TrimSpace(flagK8sCluster) == "" { + return fmt.Errorf("set --cluster (Thalassa) and/or --issuer (self-managed)") + } + repo := ns + "/" + sa + return executeBootstrap(cmd, BootstrapOptions{ + VCS: ValueVCSKubernetes, + Repository: repo, + KubernetesIssuer: strings.TrimSpace(flagK8sIssuer), + KubernetesClusterRef: strings.TrimSpace(flagK8sCluster), + }) +} + +func init() { + f := bootstrapKubernetesCmd.Flags() + f.StringVar(&flagK8sNamespace, "namespace", "", "Kubernetes namespace for the workload service account (required)") + f.StringVar(&flagK8sSAName, "service-account", "", "Kubernetes service account name (required)") + f.StringVar(&flagK8sCluster, "cluster", "", "Thalassa cluster identity, slug, or name (uses federated identity provider with label kubernetes_cluster_id=)") + f.StringVar(&flagK8sIssuer, "issuer", "", "kube-apiserver service-account issuer URL (required without --cluster); with --cluster must match the cluster OIDC provider or omit") + _ = bootstrapKubernetesCmd.RegisterFlagCompletionFunc("cluster", completion.CompleteKubernetesCluster) +} diff --git a/cmd/iam/workloadidentityfederation/bootstrap_output.go b/cmd/iam/workloadidentityfederation/bootstrap_output.go new file mode 100644 index 0000000..b49363a --- /dev/null +++ b/cmd/iam/workloadidentityfederation/bootstrap_output.go @@ -0,0 +1,108 @@ +package workloadidentityfederation + +import ( + "fmt" + "os" + + "github.com/mattn/go-isatty" +) + +func stdoutIsTTY() bool { + return isatty.IsTerminal(os.Stdout.Fd()) +} + +func termGreen(s string) string { + if !stdoutIsTTY() { + return s + } + return "\x1b[32m" + s + "\x1b[0m" +} + +func termBold(s string) string { + if !stdoutIsTTY() { + return s + } + return "\x1b[1m" + s + "\x1b[0m" +} + +func termCyan(s string) string { + if !stdoutIsTTY() { + return s + } + return "\x1b[36m" + s + "\x1b[0m" +} + +func termDim(s string) string { + if !stdoutIsTTY() { + return s + } + return "\x1b[2m" + s + "\x1b[0m" +} + +func printBootstrapOutcome(vcs string, res *BootstrapResult, dry bool) { + check := termGreen("✔") + would := "○" + if stdoutIsTTY() { + would = "\x1b[33m○\x1b[0m" // amber for planned + } + + fmt.Printf("%s Bootstrap workload identity (%s)\n\n", termCyan("►"), termBold(vcs)) + + fmt.Printf("%s Organisation role - %s %s\n", check, res.RoleSlug, termDim("("+res.RoleIdentity+")")) + + // Federated identity provider (OIDC issuer registration) + switch { + case dry && res.WouldCreateProvider: + fmt.Printf("%s Federated identity provider - would create %s\n", would, termDim("("+res.Issuer+")")) + case dry: + fmt.Printf("%s Federated identity provider - already present %s\n", check, termDim(res.ProviderIdentity)) + case res.CreatedProvider: + fmt.Printf("%s Federated identity provider - created %s\n", check, res.ProviderIdentity) + default: + fmt.Printf("%s Federated identity provider - already present %s\n", check, res.ProviderIdentity) + } + + // Thalassa service account + switch { + case dry && res.WouldCreateServiceAccount: + fmt.Printf("%s Service account - would create\n", would) + case dry: + fmt.Printf("%s Service account - already present %s %s\n", check, res.ServiceAccountIdentity, termDim("("+res.ServiceAccountSlug+")")) + case res.CreatedServiceAccount: + fmt.Printf("%s Service account - created %s %s\n", check, res.ServiceAccountIdentity, termDim("("+res.ServiceAccountSlug+")")) + default: + fmt.Printf("%s Service account - already present %s %s\n", check, res.ServiceAccountIdentity, termDim("("+res.ServiceAccountSlug+")")) + } + + // Federated identity (JWT subject → service account) + switch { + case dry && res.WouldCreateFederatedIdentity: + fmt.Printf("%s Federated identity - would create %s\n", would, termDim("("+res.ProviderSubject+")")) + case dry && res.WouldUpdateFederatedIdentity: + fmt.Printf("%s Federated identity - would update configuration %s\n", would, res.FederatedIdentityIdentity) + case dry: + fmt.Printf("%s Federated identity - already present %s\n", check, res.FederatedIdentityIdentity) + case res.CreatedFederatedIdentity: + fmt.Printf("%s Federated identity - created %s\n", check, res.FederatedIdentityIdentity) + case res.UpdatedFederatedIdentity: + fmt.Printf("%s Federated identity - updated configuration %s\n", check, res.FederatedIdentityIdentity) + default: + fmt.Printf("%s Federated identity - already present %s\n", check, res.FederatedIdentityIdentity) + } + + // Organisation role binding + switch { + case dry && res.WouldCreateRoleBinding: + fmt.Printf("%s Organisation role binding - would create\n", would) + case dry: + fmt.Printf("%s Organisation role binding - already present\n", check) + case res.CreatedRoleBinding: + fmt.Printf("%s Organisation role binding - created\n", check) + default: + fmt.Printf("%s Organisation role binding - already present\n", check) + } + + fmt.Println() + fmt.Printf(" %s %s\n", termDim("issuer:"), res.Issuer) + fmt.Printf(" %s %s\n", termDim("JWT sub:"), res.ProviderSubject) +} diff --git a/cmd/iam/workloadidentityfederation/bootstrap_run.go b/cmd/iam/workloadidentityfederation/bootstrap_run.go new file mode 100644 index 0000000..8e66a5f --- /dev/null +++ b/cmd/iam/workloadidentityfederation/bootstrap_run.go @@ -0,0 +1,533 @@ +package workloadidentityfederation + +import ( + "context" + "crypto/sha256" + "encoding/hex" + "fmt" + "net/url" + "slices" + "strings" + + "github.com/thalassa-cloud/client-go/filters" + clientiam "github.com/thalassa-cloud/client-go/iam" + "github.com/thalassa-cloud/client-go/thalassa" +) + +// wifResourceKey is a stable id for bootstrap-labelled resources. It must include the OIDC issuer so the +// same repo/ref on GitLab.com vs self-managed (or any two issuers) does not reuse the same service account. +func wifResourceKey(vcs, repository, providerSubject, issuer string) string { + sum := sha256.Sum256([]byte(strings.ToLower(vcs) + "\n" + strings.TrimSpace(repository) + "\n" + providerSubject + "\n" + normalizeIssuer(issuer))) + return hex.EncodeToString(sum[:8]) +} + +func shortHexKey(s string) string { + sum := sha256.Sum256([]byte(strings.TrimSpace(s))) + return hex.EncodeToString(sum[:4]) +} + +// issuerURLHostname returns the host part of an issuer URL for display names (e.g. gitlab.com). +func issuerURLHostname(issuer string) string { + issuer = strings.TrimSpace(issuer) + if issuer == "" { + return "" + } + raw := issuer + if !strings.Contains(issuer, "://") { + raw = "https://" + issuer + } + u, err := url.Parse(raw) + if err != nil { + return "" + } + if h := u.Hostname(); h != "" { + return h + } + // Issuer may be stored without scheme + s := normalizeIssuer(issuer) + s = strings.TrimPrefix(s, "https://") + s = strings.TrimPrefix(s, "http://") + if i := strings.IndexByte(s, '/'); i >= 0 { + s = s[:i] + } + return s +} + +func bootstrapLabels(vcs, key string) map[string]string { + return map[string]string{ + LabelManagedBy: ValueManagedBy, + LabelWIFKey: key, + LabelWIFVCS: vcs, + } +} + +// labelsMatch reports whether have contains every key/value in want (API list filters may be ignored). +func labelsMatch(have map[string]string, want map[string]string) bool { + if len(want) == 0 { + return false + } + for k, v := range want { + if have[k] != v { + return false + } + } + return true +} + +// scopesEqual compares allowed scope sets (order-insensitive). +func scopesEqual(a, b []clientiam.AccessCredentialsScope) bool { + toSorted := func(s []clientiam.AccessCredentialsScope) []string { + out := make([]string, 0, len(s)) + for _, x := range s { + out = append(out, string(x)) + } + slices.Sort(out) + return out + } + return slices.Equal(toSorted(a), toSorted(b)) +} + +func trustedAudiencesEqual(a, b []string) bool { + aa := append([]string(nil), a...) + bb := append([]string(nil), b...) + slices.Sort(aa) + slices.Sort(bb) + return slices.Equal(aa, bb) +} + +func federatedIdentityNeedsBootstrapReconcile(fi *clientiam.FederatedIdentity, fiName, fiDesc, subject, vcs, key string, scopes []clientiam.AccessCredentialsScope, trustedAudiences []string) bool { + wantLabels := bootstrapLabels(vcs, key) + annSubj := "" + if fi.Annotations != nil { + annSubj = fi.Annotations["thalassa.cloud/wif.provider-subject"] + } + return !scopesEqual(fi.AllowedScopes, scopes) || + !trustedAudiencesEqual(fi.TrustedAudiences, trustedAudiences) || + (fi.AudienceMatchMode != "" && fi.AudienceMatchMode != clientiam.AudienceMatchModeAny) || + fi.Name != fiName || + fi.Description != fiDesc || + !labelsMatch(fi.Labels, wantLabels) || + annSubj != subject +} + +func findProviderByIssuer(ctx context.Context, c *clientiam.Client, issuer string) (*clientiam.FederatedIdentityProvider, error) { + want := normalizeIssuer(issuer) + list, err := c.ListFederatedIdentityProviders(ctx, &clientiam.ListFederatedIdentityProvidersRequest{}) + if err != nil { + return nil, err + } + for i := range list { + p := &list[i] + if normalizeIssuer(p.ProviderIssuer) == want { + return p, nil + } + } + return nil, nil +} + +func findProviderByKubernetesClusterID(ctx context.Context, c *clientiam.Client, clusterIdentity string) (*clientiam.FederatedIdentityProvider, error) { + clusterIdentity = strings.TrimSpace(clusterIdentity) + if clusterIdentity == "" { + return nil, nil + } + want := map[string]string{LabelKubernetesClusterID: clusterIdentity} + list, err := c.ListFederatedIdentityProviders(ctx, &clientiam.ListFederatedIdentityProvidersRequest{ + Filters: []filters.Filter{ + &filters.LabelFilter{MatchLabels: want}, + }, + }) + if err != nil { + return nil, err + } + var found *clientiam.FederatedIdentityProvider + n := 0 + for i := range list { + p := &list[i] + if labelsMatch(p.Labels, want) { + n++ + found = p + } + } + if n > 1 { + return nil, fmt.Errorf("multiple federated identity providers match label %s=%s", LabelKubernetesClusterID, clusterIdentity) + } + return found, nil +} + +func createBootstrapProvider(ctx context.Context, c *clientiam.Client, name, description, issuer, vcs string) (*clientiam.FederatedIdentityProvider, error) { + // Provider shares managed-by / wif-vcs with other bootstrap objects; wif-key is issuer-specific so + // multiple GitLab issuers in one org do not collide on the provider label value. + pKey := "issuer-" + shortHexKey(issuer) + p, err := c.CreateFederatedIdentityProvider(ctx, clientiam.CreateFederatedIdentityProviderRequest{ + Name: name, + Description: description, + ProviderIssuer: issuer, + Status: clientiam.FederatedIdentityProviderStatusActive, + Labels: bootstrapLabels(vcs, pKey), + }) + if err != nil { + return nil, err + } + return p, nil +} + +func findServiceAccountByWIFKey(ctx context.Context, c *clientiam.Client, vcs, key string) (*clientiam.ServiceAccount, error) { + want := bootstrapLabels(vcs, key) + list, err := c.ListServiceAccounts(ctx, &clientiam.ListServiceAccountsRequest{ + Filters: []filters.Filter{ + &filters.LabelFilter{MatchLabels: want}, + }, + }) + if err != nil { + return nil, err + } + for i := range list { + sa := &list[i] + if labelsMatch(sa.Labels, want) { + return sa, nil + } + } + return nil, nil +} + +func createBootstrapServiceAccount(ctx context.Context, c *clientiam.Client, name, description string, vcs, key, repo, subject string) (*clientiam.ServiceAccount, error) { + desc := description + req := clientiam.CreateServiceAccountRequest{ + Name: name, + Labels: bootstrapLabels(vcs, key), + Annotations: map[string]string{ + "thalassa.cloud/wif.repository": repo, + "thalassa.cloud/wif.provider-subject": subject, + }, + } + if desc != "" { + req.Description = &desc + } + return c.CreateServiceAccount(ctx, req) +} + +func resolveOrganisationRole(ctx context.Context, c *clientiam.Client, ref string) (*clientiam.OrganisationRole, error) { + ref = strings.TrimSpace(ref) + if ref == "" { + return nil, fmt.Errorf("role is required") + } + if role, err := c.GetOrganisationRole(ctx, ref); err == nil && role != nil { + return role, nil + } + roles, err := c.ListOrganisationRoles(ctx, &clientiam.ListOrganisationRolesRequest{}) + if err != nil { + return nil, fmt.Errorf("list organisation roles: %w", err) + } + for i := range roles { + r := &roles[i] + if strings.EqualFold(r.Identity, ref) || strings.EqualFold(r.Slug, ref) || strings.EqualFold(r.Name, ref) { + return r, nil + } + } + return nil, fmt.Errorf("organisation role not found: %s", ref) +} + +func hasRoleBindingForServiceAccount(ctx context.Context, c *clientiam.Client, roleIdentity, saIdentity string) (bool, error) { + bindings, err := c.ListRoleBindings(ctx, roleIdentity, &clientiam.ListRoleBindingsRequest{}) + if err != nil { + return false, err + } + for _, b := range bindings { + if b.ServiceAccount != nil && b.ServiceAccount.Identity == saIdentity { + return true, nil + } + } + return false, nil +} + +func createRoleBindingForSA(ctx context.Context, c *clientiam.Client, role *clientiam.OrganisationRole, sa *clientiam.ServiceAccount, vcs, key string) (*clientiam.OrganisationRoleBinding, error) { + name := fmt.Sprintf("wif-%s-%s", vcs, key) + if len(name) > 63 { + name = name[:63] + } + saID := sa.Identity + binding, err := c.CreateRoleBinding(ctx, role.Identity, clientiam.CreateRoleBinding{ + Name: name, + Description: fmt.Sprintf("Workload identity federation (%s) for Thalassa service account %s", vcs, sa.Slug), + Labels: bootstrapLabels(vcs, key), + ServiceAccountIdentity: &saID, + }) + if err != nil { + return nil, err + } + return binding, nil +} + +func findFederatedIdentity(ctx context.Context, c *clientiam.Client, vcs, key string) (*clientiam.FederatedIdentity, error) { + want := bootstrapLabels(vcs, key) + list, err := c.ListFederatedIdentities(ctx, &clientiam.ListFederatedIdentitiesRequest{ + Filters: []filters.Filter{ + &filters.LabelFilter{MatchLabels: want}, + }, + }) + if err != nil { + return nil, err + } + for i := range list { + fi := &list[i] + if labelsMatch(fi.Labels, want) { + return fi, nil + } + } + return nil, nil +} + +func createBootstrapFederatedIdentity(ctx context.Context, c *clientiam.Client, name, description, providerID, saID, subject string, audiences []string, scopes []clientiam.AccessCredentialsScope, vcs, key string) (*clientiam.FederatedIdentity, error) { + return c.CreateFederatedIdentity(ctx, clientiam.CreateFederatedIdentityRequest{ + Name: name, + Description: description, + Labels: bootstrapLabels(vcs, key), + Annotations: map[string]string{ + "thalassa.cloud/wif.provider-subject": subject, + }, + ServiceAccountIdentity: saID, + ProviderIdentity: providerID, + ProviderSubject: subject, + TrustedAudiences: audiences, + AudienceMatchMode: clientiam.AudienceMatchModeAny, + AllowedScopes: scopes, + }) +} + +// RunBootstrap executes the bootstrap flow. client must be a full thalassa.Client (IAM and, for kubernetes, Kubernetes API). +func RunBootstrap(ctx context.Context, client thalassa.Client, opts BootstrapOptions) (*BootstrapResult, error) { + iamc := client.IAM() + res := &BootstrapResult{} + + var issuer string + var subject string + var err error + // Set when --kubernetes-cluster resolves to a Thalassa cluster: OIDC provider must exist with label kubernetes_cluster_id=. + var k8sClusterBoundProvider *clientiam.FederatedIdentityProvider + + switch opts.VCS { + case ValueVCSGitHub: + issuer = GitHubActionsIssuer + subject, err = BuildGitHubSubject(opts.Repository, opts.RefKind, opts.Ref) + case ValueVCSGitLab: + issuer = normalizeIssuer(opts.GitLabIssuer) + if issuer == "" { + issuer = "https://gitlab.com" + } + subject, err = BuildGitLabSubject(opts.Repository, opts.GitLabRefType, opts.Ref) + case ValueVCSKubernetes: + explicitIssuer := normalizeIssuer(opts.KubernetesIssuer) + if strings.TrimSpace(opts.KubernetesClusterRef) != "" { + cluster, rerr := resolveKubernetesCluster(ctx, client.Kubernetes(), opts.KubernetesClusterRef) + if rerr != nil { + return nil, rerr + } + p, perr := findProviderByKubernetesClusterID(ctx, iamc, cluster.Identity) + if perr != nil { + return nil, fmt.Errorf("list federated identity providers for cluster: %w", perr) + } + if p == nil { + return nil, fmt.Errorf("no federated identity provider for kubernetes cluster %q (identity %s): expected exactly one provider with label %s=%s (cluster OIDC not provisioned yet?)", + cluster.Name, cluster.Identity, LabelKubernetesClusterID, cluster.Identity) + } + k8sClusterBoundProvider = p + issuer = normalizeIssuer(p.ProviderIssuer) + if issuer == "" { + return nil, fmt.Errorf("federated identity provider %s for cluster %q has an empty issuer URL", p.Identity, cluster.Name) + } + if explicitIssuer != "" && explicitIssuer != issuer { + return nil, fmt.Errorf("kubernetes: --issuer %q does not match cluster OIDC provider issuer %q (omit --issuer when using --cluster)", opts.KubernetesIssuer, p.ProviderIssuer) + } + } else if explicitIssuer != "" { + issuer = explicitIssuer + } else { + return nil, fmt.Errorf("kubernetes: set --kubernetes-cluster (Thalassa-managed cluster) and/or --kubernetes-issuer (self-managed or custom service-account issuer)") + } + subject, err = BuildKubernetesSubject(opts.Repository) + default: + return nil, fmt.Errorf("unsupported --vcs %q (use github, gitlab, or kubernetes)", opts.VCS) + } + if err != nil { + return nil, err + } + if len(opts.TrustedAudiences) == 0 { + return nil, fmt.Errorf("at least one trusted audience is required") + } + + key := wifResourceKey(opts.VCS, opts.Repository, subject, issuer) + res.WIFKey = key + res.ProviderSubject = subject + res.Issuer = issuer + + scopes := opts.AllowedScopes + if len(scopes) == 0 { + scopes = []clientiam.AccessCredentialsScope{ + clientiam.AccessCredentialsScopeAPIRead, + clientiam.AccessCredentialsScopeAPIWrite, + } + } + + role, err := resolveOrganisationRole(ctx, iamc, opts.RoleRef) + if err != nil { + return nil, err + } + res.RoleIdentity = role.Identity + res.RoleSlug = role.Slug + + var provider *clientiam.FederatedIdentityProvider + if k8sClusterBoundProvider != nil { + provider = k8sClusterBoundProvider + } else { + provider, err = findProviderByIssuer(ctx, iamc, issuer) + if err != nil { + return nil, fmt.Errorf("list federated identity providers: %w", err) + } + } + if provider == nil { + if opts.DryRun { + res.WouldCreateProvider = true + res.ProviderIdentity = "(dry-run: would create provider)" + } else { + pName := opts.ProviderDisplayName + if pName == "" { + switch opts.VCS { + case ValueVCSGitHub: + pName = "GitHub Actions OIDC" + case ValueVCSGitLab: + if h := issuerURLHostname(issuer); h != "" { + pName = h + } else { + pName = "GitLab CI OIDC" + } + case ValueVCSKubernetes: + if h := issuerURLHostname(issuer); h != "" { + pName = h + } else { + pName = "Kubernetes service account OIDC" + } + default: + pName = "OIDC" + } + } + pDesc := opts.ProviderDescription + if pDesc == "" { + pDesc = fmt.Sprintf("OIDC issuer %s (created by tcloud iam workload-identity-federation bootstrap )", issuer) + } + p, err := createBootstrapProvider(ctx, iamc, pName, pDesc, issuer, opts.VCS) + if err != nil { + return nil, fmt.Errorf("create federated identity provider: %w", err) + } + provider = p + res.CreatedProvider = true + } + } + if provider != nil { + res.ProviderIdentity = provider.Identity + } + + saName := fmt.Sprintf("wif-%s-%s", opts.VCS, key) + fiName := fmt.Sprintf("wif-%s-%s-fi", opts.VCS, key) + if n := strings.TrimSpace(opts.ResourceName); n != "" { + saName = n + fiName = n + "-fi" + } + if len(saName) > 200 { + saName = saName[:200] + } + if len(fiName) > 200 { + fiName = fiName[:200] + } + + sa, err := findServiceAccountByWIFKey(ctx, iamc, opts.VCS, key) + if err != nil { + return nil, fmt.Errorf("list service accounts: %w", err) + } + if sa == nil { + if opts.DryRun { + res.WouldCreateServiceAccount = true + res.ServiceAccountIdentity = "(dry-run: would create service account)" + } else { + desc := fmt.Sprintf("Workload identity (%s %s); JWT sub: %s", opts.VCS, opts.Repository, subject) + sa, err = createBootstrapServiceAccount(ctx, iamc, saName, desc, opts.VCS, key, opts.Repository, subject) + if err != nil { + return nil, fmt.Errorf("create service account: %w", err) + } + res.CreatedServiceAccount = true + } + } + if sa != nil { + res.ServiceAccountIdentity = sa.Identity + res.ServiceAccountSlug = sa.Slug + } + + fiDesc := fmt.Sprintf("Workload identity (%s %s) → %s", opts.VCS, opts.Repository, subject) + fi, err := findFederatedIdentity(ctx, iamc, opts.VCS, key) + if err != nil { + return nil, fmt.Errorf("list federated identities: %w", err) + } + if fi == nil { + if opts.DryRun { + res.WouldCreateFederatedIdentity = true + res.FederatedIdentityIdentity = "(dry-run: would create federated identity)" + } else { + if provider == nil || sa == nil { + return nil, fmt.Errorf("internal: missing provider or service account after provisioning") + } + fi, err = createBootstrapFederatedIdentity(ctx, iamc, fiName, fiDesc, provider.Identity, sa.Identity, subject, opts.TrustedAudiences, scopes, opts.VCS, key) + if err != nil { + return nil, fmt.Errorf("create federated identity: %w", err) + } + res.CreatedFederatedIdentity = true + } + } + if fi != nil { + res.FederatedIdentityIdentity = fi.Identity + if federatedIdentityNeedsBootstrapReconcile(fi, fiName, fiDesc, subject, opts.VCS, key, scopes, opts.TrustedAudiences) { + if opts.DryRun { + res.WouldUpdateFederatedIdentity = true + } else { + _, err := iamc.UpdateFederatedIdentity(ctx, fi.Identity, clientiam.UpdateFederatedIdentityRequest{ + Name: fiName, + Description: fiDesc, + Labels: bootstrapLabels(opts.VCS, key), + Annotations: map[string]string{"thalassa.cloud/wif.provider-subject": subject}, + TrustedAudiences: append([]string(nil), opts.TrustedAudiences...), + AudienceMatchMode: clientiam.AudienceMatchModeAny, + AllowedScopes: scopes, + }) + if err != nil { + return nil, fmt.Errorf("update federated identity: %w", err) + } + res.UpdatedFederatedIdentity = true + } + } + } + + if !opts.DryRun && sa != nil { + ok, err := hasRoleBindingForServiceAccount(ctx, iamc, role.Identity, sa.Identity) + if err != nil { + return nil, fmt.Errorf("list role bindings: %w", err) + } + if !ok { + _, err = createRoleBindingForSA(ctx, iamc, role, sa, opts.VCS, key) + if err != nil { + return nil, fmt.Errorf("create role binding: %w", err) + } + res.CreatedRoleBinding = true + } + } else if opts.DryRun { + if sa != nil { + ok, err := hasRoleBindingForServiceAccount(ctx, iamc, role.Identity, sa.Identity) + if err != nil { + return nil, fmt.Errorf("list role bindings: %w", err) + } + if !ok { + res.WouldCreateRoleBinding = true + } + } else if res.WouldCreateServiceAccount { + res.WouldCreateRoleBinding = true + } + } + + return res, nil +} diff --git a/cmd/iam/workloadidentityfederation/bootstrap_run_test.go b/cmd/iam/workloadidentityfederation/bootstrap_run_test.go new file mode 100644 index 0000000..acc5709 --- /dev/null +++ b/cmd/iam/workloadidentityfederation/bootstrap_run_test.go @@ -0,0 +1,62 @@ +package workloadidentityfederation + +import ( + "testing" + + "github.com/stretchr/testify/assert" + clientiam "github.com/thalassa-cloud/client-go/iam" +) + +func TestLabelsMatch(t *testing.T) { + want := map[string]string{"a": "1", "b": "2"} + assert.True(t, labelsMatch(map[string]string{"a": "1", "b": "2", "c": "3"}, want)) + assert.False(t, labelsMatch(map[string]string{"a": "1"}, want)) + assert.False(t, labelsMatch(map[string]string{"a": "1", "b": "x"}, want)) + assert.False(t, labelsMatch(nil, want)) + assert.False(t, labelsMatch(map[string]string{}, map[string]string{})) +} + +func TestIssuerURLHostname(t *testing.T) { + assert.Equal(t, "gitlab.com", issuerURLHostname("https://gitlab.com")) + assert.Equal(t, "gitlab.com", issuerURLHostname("https://gitlab.com/")) + assert.Equal(t, "gitlab.example.org", issuerURLHostname("https://gitlab.example.org:8443")) + assert.Equal(t, "k8s.example.com", issuerURLHostname("k8s.example.com")) + assert.Equal(t, "", issuerURLHostname("")) +} + +func TestWifResourceKeyIncludesIssuer(t *testing.T) { + subject := "project_path:g/p:ref_type:branch:ref:main" + a := wifResourceKey(ValueVCSGitLab, "g/p", subject, "https://gitlab.com") + b := wifResourceKey(ValueVCSGitLab, "g/p", subject, "https://gitlab.example.com") + assert.NotEqual(t, a, b, "different issuers must not share the same wif-key") + c := wifResourceKey(ValueVCSGitLab, "g/p", subject, "https://gitlab.com/") + assert.Equal(t, a, c, "issuer normalization should match") +} + +func TestFederatedIdentityNeedsBootstrapReconcile(t *testing.T) { + key := "abc123" + fi := &clientiam.FederatedIdentity{ + Name: "n", + Description: "d", + AllowedScopes: []clientiam.AccessCredentialsScope{clientiam.AccessCredentialsScopeAPIRead}, + TrustedAudiences: []string{"https://api.example"}, + AudienceMatchMode: clientiam.AudienceMatchModeAny, + Labels: bootstrapLabels(ValueVCSGitLab, key), + Annotations: map[string]string{"thalassa.cloud/wif.provider-subject": "sub"}, + } + wantScopes := []clientiam.AccessCredentialsScope{clientiam.AccessCredentialsScopeAPIRead} + wantAud := []string{"https://api.example"} + assert.False(t, federatedIdentityNeedsBootstrapReconcile(fi, "n", "d", "sub", ValueVCSGitLab, key, wantScopes, wantAud)) + assert.True(t, federatedIdentityNeedsBootstrapReconcile(fi, "n", "d", "sub", ValueVCSGitLab, key, + []clientiam.AccessCredentialsScope{clientiam.AccessCredentialsScopeAPIRead, clientiam.AccessCredentialsScopeAPIWrite}, wantAud)) +} + +func TestScopesEqual(t *testing.T) { + a := []clientiam.AccessCredentialsScope{clientiam.AccessCredentialsScopeAPIRead, clientiam.AccessCredentialsScopeAPIWrite} + b := []clientiam.AccessCredentialsScope{clientiam.AccessCredentialsScopeAPIWrite, clientiam.AccessCredentialsScopeAPIRead} + c := []clientiam.AccessCredentialsScope{clientiam.AccessCredentialsScopeAPIRead} + assert.True(t, scopesEqual(a, b)) + assert.True(t, scopesEqual(nil, nil)) + assert.True(t, scopesEqual(nil, []clientiam.AccessCredentialsScope{})) + assert.False(t, scopesEqual(a, c)) +} diff --git a/cmd/iam/workloadidentityfederation/bootstrap_test.go b/cmd/iam/workloadidentityfederation/bootstrap_test.go new file mode 100644 index 0000000..07ae482 --- /dev/null +++ b/cmd/iam/workloadidentityfederation/bootstrap_test.go @@ -0,0 +1,33 @@ +package workloadidentityfederation + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestParseGitHubRefKind(t *testing.T) { + tests := []struct { + name string + input string + want RefKind + wantErr bool + }{ + {name: "empty defaults branch", input: "", want: RefKindBranch}, + {name: "branch", input: "branch", want: RefKindBranch}, + {name: "pull_request", input: "pull_request", want: RefKindPullRequest}, + {name: "invalid", input: "merge_request", wantErr: true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := parseGitHubRefKind(tt.input) + if tt.wantErr { + require.Error(t, err) + return + } + require.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/cmd/iam/workloadidentityfederation/bootstrap_types.go b/cmd/iam/workloadidentityfederation/bootstrap_types.go new file mode 100644 index 0000000..808dcf5 --- /dev/null +++ b/cmd/iam/workloadidentityfederation/bootstrap_types.go @@ -0,0 +1,56 @@ +package workloadidentityfederation + +import clientiam "github.com/thalassa-cloud/client-go/iam" + +// BootstrapOptions configures workload-identity-federation bootstrap. +type BootstrapOptions struct { + VCS string // ValueVCSGitHub, ValueVCSGitLab, or ValueVCSKubernetes + Repository string // owner/repo (GitHub), group/project (GitLab), or namespace/sa (Kubernetes) + RefKind RefKind + Ref string // branch name, tag name, or environment name (GitHub) + GitLabRefType string // branch, tag, etc. (GitLab id_token sub ref_type segment) + RoleRef string // organisation role identity, slug, or name + TrustedAudiences []string + AllowedScopes []clientiam.AccessCredentialsScope + + GitLabIssuer string // default https://gitlab.com + + // KubernetesIssuer is the OIDC issuer URL for service account tokens (--service-account-issuer). + // Optional if KubernetesClusterRef is set and the issuer matches the cluster API URL (typical Thalassa-managed clusters). + KubernetesIssuer string + // KubernetesClusterRef is a Thalassa cluster identity, slug, or name used to resolve the default issuer. + KubernetesClusterRef string + + ProviderDisplayName string + ProviderDescription string + + // ResourceName, if set, is the Thalassa service account Name and the base for the federated identity Name ({name}-fi). + // When empty, names default to wif-- and wif---fi. + ResourceName string + + DryRun bool +} + +// BootstrapResult summarises what bootstrap did or would do. +type BootstrapResult struct { + WIFKey string + Issuer string + ProviderSubject string + ProviderIdentity string + ServiceAccountIdentity string + ServiceAccountSlug string + RoleIdentity string + RoleSlug string + FederatedIdentityIdentity string + + CreatedProvider bool + CreatedServiceAccount bool + CreatedRoleBinding bool + CreatedFederatedIdentity bool + UpdatedFederatedIdentity bool // existing FI reconciled to bootstrap (scopes, audiences, labels, etc.) + WouldCreateProvider bool + WouldCreateServiceAccount bool + WouldCreateRoleBinding bool + WouldCreateFederatedIdentity bool + WouldUpdateFederatedIdentity bool // dry-run: existing FI would be reconciled +} diff --git a/cmd/iam/workloadidentityfederation/hints.go b/cmd/iam/workloadidentityfederation/hints.go new file mode 100644 index 0000000..3d32fbb --- /dev/null +++ b/cmd/iam/workloadidentityfederation/hints.go @@ -0,0 +1,131 @@ +package workloadidentityfederation + +import ( + "fmt" + "strings" +) + +func printBootstrapHints(opts BootstrapOptions, res *BootstrapResult, apiBase string, organisation string) { + tokenURL := apiBase + "/oidc/token" + primaryAud := opts.TrustedAudiences[0] + if len(opts.TrustedAudiences) > 1 { + fmt.Printf("\nNote: federated identity trusts multiple audiences; CI id_token aud must be one of: %s\n", + strings.Join(opts.TrustedAudiences, ", ")) + } + + fmt.Println() + fmt.Printf("%s Next steps CI configuration (https://docs.thalassa.cloud/docs/iam/oidc/) \n", termCyan("►")) + switch opts.VCS { + case ValueVCSGitHub: + fmt.Printf(` +name: Deploy to Thalassa Cloud +on: + push: + branches: [main] +jobs: + deploy: + runs-on: ubuntu-latest + permissions: + id-token: write + contents: read + steps: + - uses: actions/checkout@v4 + - name: Get OIDC token + id: oidc + uses: actions/github-script@v7 + with: + script: | + const token = await core.getIDToken(%q); + core.setOutput('token', token); + - name: Exchange OIDC token + id: auth + run: | + RESPONSE=$(curl -sS -X POST %s \ + -H "Content-Type: application/x-www-form-urlencoded" \ + -d "grant_type=urn:ietf:params:oauth:grant-type:token-exchange" \ + -d "subject_token=${{ steps.oidc.outputs.token }}" \ + -d "subject_token_type=urn:ietf:params:oauth:token-type:id_token" \ + -d "organisation_id=${{ vars.THALASSA_ORGANISATION_ID }}" \ + -d "service_account_id=${{ vars.THALASSA_SERVICE_ACCOUNT_ID }}") + echo "token=$(echo "$RESPONSE" | jq -r '.access_token')" >> "$GITHUB_OUTPUT" + - name: Example use + env: + THALASSA_TOKEN: ${{ steps.auth.outputs.token }} + run: echo "THALASSA_TOKEN is set" +`, primaryAud, tokenURL) + + case ValueVCSGitLab: + fmt.Printf(` +stages: + - deploy + +deploy: + stage: deploy + image: alpine:latest + id_tokens: + THALASSA_ID_TOKEN: + aud: %q + before_script: + - apk add --no-cache curl jq + variables: + THALASSA_SERVICE_ACCOUNT_ID: "%s" + THALASSA_ORGANISATION_ID: "" + script: + - | + BEARER_TOKEN=$(curl -sS -X POST %s \ + -H "Content-Type: application/x-www-form-urlencoded" \ + -d "grant_type=urn:ietf:params:oauth:grant-type:token-exchange" \ + -d "subject_token=${THALASSA_ID_TOKEN}" \ + -d "subject_token_type=urn:ietf:params:oauth:token-type:id_token" \ + -d "organisation_id=${THALASSA_ORGANISATION_ID}" \ + -d "service_account_id=${THALASSA_SERVICE_ACCOUNT_ID}" \ + | jq -r '.access_token') + - export THALASSA_TOKEN="${BEARER_TOKEN}" +`, primaryAud, res.ServiceAccountIdentity, tokenURL) + + case ValueVCSKubernetes: + fmt.Printf(` +Kubernetes: request a projected service account token whose audience matches the federated identity, then exchange it. + +apiVersion: v1 +kind: Pod +metadata: + name: thalassa-example +spec: + serviceAccountName: + volumes: + - name: thalassa-oidc + projected: + sources: + - serviceAccountToken: + path: thalassa-oidc + expirationSeconds: 3600 + audience: %q + containers: + - name: app + image: alpine:latest + volumeMounts: + - name: thalassa-oidc + mountPath: /var/run/secrets/thalassa + readOnly: true + env: + - name: THALASSA_SERVICE_ACCOUNT_ID + value: "%s" + - name: THALASSA_ORGANISATION_ID + value: "" + command: ["/bin/sh", "-c"] + args: + - | + apk add --no-cache curl jq + export THALASSA_SUBJECT_ID_TOKEN="$(cat /var/run/secrets/thalassa/thalassa-oidc)" + BEARER_TOKEN=$(curl -sS -X POST %s \ + -H "Content-Type: application/x-www-form-urlencoded" \ + -d "grant_type=urn:ietf:params:oauth:grant-type:token-exchange" \ + -d "subject_token=${THALASSA_SUBJECT_ID_TOKEN}" -d "subject_token_type=urn:ietf:params:oauth:token-type:id_token" \ + -d "organisation_id=${THALASSA_ORGANISATION_ID}" -d "service_account_id=${THALASSA_SERVICE_ACCOUNT_ID}" \ + | jq -r '.access_token') + export THALASSA_TOKEN="${BEARER_TOKEN}" + # ... call Thalassa API or run tcloud with THALASSA_TOKEN +`, primaryAud, res.ServiceAccountIdentity, tokenURL) + } +} diff --git a/cmd/iam/workloadidentityfederation/kubernetes_resolve.go b/cmd/iam/workloadidentityfederation/kubernetes_resolve.go new file mode 100644 index 0000000..ff19c85 --- /dev/null +++ b/cmd/iam/workloadidentityfederation/kubernetes_resolve.go @@ -0,0 +1,31 @@ +package workloadidentityfederation + +import ( + "context" + "fmt" + "strings" + + "github.com/thalassa-cloud/client-go/kubernetes" +) + +func resolveKubernetesCluster(ctx context.Context, kc *kubernetes.Client, ref string) (*kubernetes.KubernetesCluster, error) { + ref = strings.TrimSpace(ref) + if ref == "" { + return nil, fmt.Errorf("kubernetes cluster reference is empty") + } + c, err := kc.GetKubernetesCluster(ctx, ref) + if err == nil && c != nil { + return c, nil + } + list, err := kc.ListKubernetesClusters(ctx, &kubernetes.ListKubernetesClustersRequest{}) + if err != nil { + return nil, fmt.Errorf("list kubernetes clusters: %w", err) + } + for i := range list { + cl := &list[i] + if strings.EqualFold(cl.Identity, ref) || strings.EqualFold(cl.Slug, ref) || strings.EqualFold(cl.Name, ref) { + return cl, nil + } + } + return nil, fmt.Errorf("kubernetes cluster not found: %s", ref) +} diff --git a/cmd/iam/workloadidentityfederation/labels.go b/cmd/iam/workloadidentityfederation/labels.go new file mode 100644 index 0000000..2d811d0 --- /dev/null +++ b/cmd/iam/workloadidentityfederation/labels.go @@ -0,0 +1,21 @@ +package workloadidentityfederation + +// Labels and values for resources created by workload-identity bootstrap, so they can be +// listed or removed consistently (e.g. label selector thalassa.cloud/managed-by=workload-identity-bootstrap). +const ( + LabelManagedBy = "thalassa.cloud/managed-by" + ValueManagedBy = "workload-identity-bootstrap" + + // LabelWIFKey is a short stable id derived from vcs + repository + JWT subject + OIDC issuer URL. + LabelWIFKey = "thalassa.cloud/wif-key" + + // LabelWIFVCS is github, gitlab, or kubernetes for filtering. + LabelWIFVCS = "thalassa.cloud/wif-vcs" + + // LabelKubernetesClusterID is set on the platform-managed federated identity provider for a Thalassa Kubernetes cluster (value = cluster identity). + LabelKubernetesClusterID = "kubernetes_cluster_id" + + ValueVCSGitHub = "github" + ValueVCSGitLab = "gitlab" + ValueVCSKubernetes = "kubernetes" +) diff --git a/cmd/iam/workloadidentityfederation/subject.go b/cmd/iam/workloadidentityfederation/subject.go new file mode 100644 index 0000000..e101044 --- /dev/null +++ b/cmd/iam/workloadidentityfederation/subject.go @@ -0,0 +1,91 @@ +package workloadidentityfederation + +import ( + "fmt" + "strings" +) + +// RefKind selects how the OIDC subject is built. +type RefKind string + +const ( + RefKindBranch RefKind = "branch" + RefKindTag RefKind = "tag" + RefKindEnvironment RefKind = "environment" + // RefKindPullRequest is GitHub's repo:owner/repo:pull_request subject (no ref segment). + RefKindPullRequest RefKind = "pull_request" +) + +// GitHub issuer URL for Actions OIDC tokens. +const GitHubActionsIssuer = "https://token.actions.githubusercontent.com" + +// BuildGitHubSubject returns the JWT `sub` claim GitHub uses for the given repo and ref. +// Repo must be "owner/name" (no leading slash). +func BuildGitHubSubject(repo string, kind RefKind, ref string) (string, error) { + repo = strings.TrimSpace(repo) + repo = strings.TrimPrefix(repo, "/") + repo = strings.TrimSuffix(repo, "/") + if repo == "" { + return "", fmt.Errorf("repository is required (owner/name)") + } + if strings.Count(repo, "/") < 1 { + return "", fmt.Errorf("repository must be owner/name, got %q", repo) + } + ref = strings.TrimSpace(ref) + if kind != RefKindPullRequest && ref == "" { + return "", fmt.Errorf("ref value is required (branch name, tag name, or environment name; omit for pull_request)") + } + switch kind { + case RefKindBranch: + return fmt.Sprintf("repo:%s:ref:refs/heads/%s", repo, ref), nil + case RefKindTag: + return fmt.Sprintf("repo:%s:ref:refs/tags/%s", repo, ref), nil + case RefKindEnvironment: + return fmt.Sprintf("repo:%s:environment:%s", repo, ref), nil + case RefKindPullRequest: + return fmt.Sprintf("repo:%s:pull_request", repo), nil + default: + return "", fmt.Errorf("unsupported ref kind %q for GitHub", kind) + } +} + +// BuildGitLabSubject returns the default GitLab CI id_token `sub` claim: +// project_path:group/project:ref_type::ref: +// See: https://docs.gitlab.com/ci/secrets/id_token_authentication/ +func BuildGitLabSubject(repoPath, refType, ref string) (string, error) { + repoPath = strings.TrimSpace(repoPath) + repoPath = strings.TrimPrefix(repoPath, "/") + repoPath = strings.TrimSuffix(repoPath, "/") + if repoPath == "" { + return "", fmt.Errorf("repository path is required (e.g. group/project)") + } + refType = strings.TrimSpace(strings.ToLower(refType)) + if refType == "" { + refType = "branch" + } + ref = strings.TrimSpace(ref) + if ref == "" { + return "", fmt.Errorf("ref is required (branch or tag name)") + } + return fmt.Sprintf("project_path:%s:ref_type:%s:ref:%s", repoPath, refType, ref), nil +} + +// BuildKubernetesSubject returns the JWT sub claim for a Kubernetes bound service account token: +// system:serviceaccount:: +// Namespace and name must be given as a single argument "namespace/name". +func BuildKubernetesSubject(namespaceAndName string) (string, error) { + s := strings.TrimSpace(namespaceAndName) + s = strings.Trim(s, "/") + if s == "" { + return "", fmt.Errorf("namespace/name is required (e.g. default/my-app)") + } + parts := strings.SplitN(s, "/", 2) + if len(parts) != 2 || parts[0] == "" || parts[1] == "" { + return "", fmt.Errorf("kubernetes service account must be namespace/name, got %q", namespaceAndName) + } + return fmt.Sprintf("system:serviceaccount:%s:%s", parts[0], parts[1]), nil +} + +func normalizeIssuer(issuer string) string { + return strings.TrimSuffix(strings.TrimSpace(issuer), "/") +} diff --git a/cmd/iam/workloadidentityfederation/subject_test.go b/cmd/iam/workloadidentityfederation/subject_test.go new file mode 100644 index 0000000..af3eee8 --- /dev/null +++ b/cmd/iam/workloadidentityfederation/subject_test.go @@ -0,0 +1,81 @@ +package workloadidentityfederation + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestBuildGitHubSubject(t *testing.T) { + tests := []struct { + name string + repo string + kind RefKind + ref string + want string + wantErr bool + }{ + {name: "branch", repo: "acme/app", kind: RefKindBranch, ref: "main", want: "repo:acme/app:ref:refs/heads/main"}, + {name: "tag", repo: "acme/app", kind: RefKindTag, ref: "v1.0.0", want: "repo:acme/app:ref:refs/tags/v1.0.0"}, + {name: "environment", repo: "acme/app", kind: RefKindEnvironment, ref: "production", want: "repo:acme/app:environment:production"}, + {name: "trim repo", repo: "/acme/app/", kind: RefKindBranch, ref: "main", want: "repo:acme/app:ref:refs/heads/main"}, + {name: "bad repo", repo: "nope", kind: RefKindBranch, ref: "main", wantErr: true}, + {name: "empty ref", repo: "a/b", kind: RefKindBranch, ref: " ", wantErr: true}, + {name: "pull_request", repo: "acme/app", kind: RefKindPullRequest, ref: "", want: "repo:acme/app:pull_request"}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := BuildGitHubSubject(tt.repo, tt.kind, tt.ref) + if tt.wantErr { + require.Error(t, err) + return + } + require.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestBuildGitLabSubject(t *testing.T) { + got, err := BuildGitLabSubject("mygroup/myproj", "branch", "main") + require.NoError(t, err) + assert.Equal(t, "project_path:mygroup/myproj:ref_type:branch:ref:main", got) + + got, err = BuildGitLabSubject("g/p", "", "v1") + require.NoError(t, err) + assert.Equal(t, "project_path:g/p:ref_type:branch:ref:v1", got) + + _, err = BuildGitLabSubject("", "branch", "main") + require.Error(t, err) +} + +func TestNormalizeIssuer(t *testing.T) { + assert.Equal(t, "https://gitlab.com", normalizeIssuer("https://gitlab.com/")) +} + +func TestBuildKubernetesSubject(t *testing.T) { + tests := []struct { + name string + input string + want string + wantErr bool + }{ + {name: "ok", input: "default/my-sa", want: "system:serviceaccount:default:my-sa"}, + {name: "trim", input: " /prod/worker/ ", want: "system:serviceaccount:prod:worker"}, + {name: "empty", input: "", wantErr: true}, + {name: "no slash", input: "default", wantErr: true}, + {name: "empty ns", input: "/sa", wantErr: true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := BuildKubernetesSubject(tt.input) + if tt.wantErr { + require.Error(t, err) + return + } + require.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/cmd/iam/workloadidentityfederation/workloadidentityfederation.go b/cmd/iam/workloadidentityfederation/workloadidentityfederation.go new file mode 100644 index 0000000..92976fd --- /dev/null +++ b/cmd/iam/workloadidentityfederation/workloadidentityfederation.go @@ -0,0 +1,16 @@ +package workloadidentityfederation + +import "github.com/spf13/cobra" + +// WorkloadIdentityFederationCmd groups workload identity federation helpers. +var WorkloadIdentityFederationCmd = &cobra.Command{ + Use: "workload-identity-federation", + Aliases: []string{"wif"}, + Short: "Bootstrap and manage CI/CD workload identity (OIDC)", + Long: `Commands to provision federated identity providers, service accounts, role bindings, +and federated identities for GitHub Actions, GitLab CI, and Kubernetes service account OIDC tokens.`, +} + +func init() { + WorkloadIdentityFederationCmd.AddCommand(bootstrapCmd) +} diff --git a/cmd/kubernetes/create.go b/cmd/kubernetes/create.go index 6899731..2d2e746 100644 --- a/cmd/kubernetes/create.go +++ b/cmd/kubernetes/create.go @@ -364,7 +364,6 @@ Examples: } } - // Output in table format like IaaS commands vpcName := "" if cluster.VPC != nil { vpcName = cluster.VPC.Name @@ -487,7 +486,6 @@ func createNodePool(ctx context.Context, client thalassa.Client, cluster *kubern } } - // Output in table format like IaaS commands replicas := formatReplicas(nodePool) body := [][]string{ { diff --git a/cmd/kubernetes/nodepools/update.go b/cmd/kubernetes/nodepools/update.go index 0107467..83637a1 100644 --- a/cmd/kubernetes/nodepools/update.go +++ b/cmd/kubernetes/nodepools/update.go @@ -257,7 +257,6 @@ to manage labels and annotations separately.`, } } - // Output in table format like IaaS commands replicas := formatReplicasForUpdate(updatedNodePool) body := [][]string{ { diff --git a/cmd/kubernetes/update.go b/cmd/kubernetes/update.go index b873636..221b32b 100644 --- a/cmd/kubernetes/update.go +++ b/cmd/kubernetes/update.go @@ -222,7 +222,6 @@ to manage labels and annotations separately.`, } } - // Output in table format like IaaS commands vpcName := "" if updatedCluster.VPC != nil { vpcName = updatedCluster.VPC.Name diff --git a/internal/completion/iam.go b/internal/completion/iam.go new file mode 100644 index 0000000..6332ce8 --- /dev/null +++ b/internal/completion/iam.go @@ -0,0 +1,393 @@ +package completion + +import ( + "github.com/spf13/cobra" + + "github.com/thalassa-cloud/cli/internal/iamresolve" + "github.com/thalassa-cloud/cli/internal/thalassaclient" + clientiam "github.com/thalassa-cloud/client-go/iam" + "github.com/thalassa-cloud/client-go/pkg/base" +) + +// CompleteIAMWIFGitHubRefKind completes --ref-kind for GitHub bootstrap. +func CompleteIAMWIFGitHubRefKind(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return []string{ + "branch\tGit branch", + "tag\tGit tag", + "environment\tGitHub environment", + "pull_request\tPull request", + }, cobra.ShellCompDirectiveNoFileComp +} + +// CompleteIAMWIFGitLabRefType completes common --ref-type values for GitLab bootstrap. +func CompleteIAMWIFGitLabRefType(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return []string{ + "branch\tBranch", + "tag\tTag", + "merge_request\tMerge request", + "pipeline\tPipeline", + }, cobra.ShellCompDirectiveNoFileComp +} + +// CompleteIAMPermissionType completes permission values for role rules. +func CompleteIAMPermissionType(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return []string{ + "create\tCreate", + "read\tRead", + "update\tUpdate", + "delete\tDelete", + "list\tList", + "*\tAll actions", + }, cobra.ShellCompDirectiveNoFileComp +} + +func iamUserDesc(u *base.AppUser) string { + if u == nil { + return "" + } + if u.Email != "" { + return u.Email + } + if u.Name != "" { + return u.Name + } + return u.Subject +} + +// CompleteIAMOrganisationMemberIdentity completes organisation member record identities. +func CompleteIAMOrganisationMemberIdentity(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + if len(args) > 0 { + return nil, cobra.ShellCompDirectiveNoFileComp + } + client, err := thalassaclient.GetThalassaClient() + if err != nil { + return nil, cobra.ShellCompDirectiveError + } + members, err := client.IAM().ListOrganisationMembers(cmd.Context(), &clientiam.ListMembersRequest{}) + if err != nil { + return nil, cobra.ShellCompDirectiveError + } + out := make([]string, 0, len(members)) + for _, m := range members { + desc := iamUserDesc(m.User) + if desc != "" { + out = append(out, m.Identity+"\t"+desc) + } else { + out = append(out, m.Identity) + } + } + return out, cobra.ShellCompDirectiveNoFileComp +} + +// CompleteIAMAppUserSubject completes user subjects from organisation members (for --user / --user-identity). +func CompleteIAMAppUserSubject(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + client, err := thalassaclient.GetThalassaClient() + if err != nil { + return nil, cobra.ShellCompDirectiveError + } + members, err := client.IAM().ListOrganisationMembers(cmd.Context(), &clientiam.ListMembersRequest{}) + if err != nil { + return nil, cobra.ShellCompDirectiveError + } + out := make([]string, 0, len(members)) + for _, m := range members { + if m.User == nil || m.User.Subject == "" { + continue + } + desc := iamUserDesc(m.User) + if desc != "" && desc != m.User.Subject { + out = append(out, m.User.Subject+"\t"+desc) + } else { + out = append(out, m.User.Subject) + } + } + return out, cobra.ShellCompDirectiveNoFileComp +} + +// CompleteIAMOrganisationMemberType completes OWNER / MEMBER. +func CompleteIAMOrganisationMemberType(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return []string{ + string(clientiam.OrganisationMemberTypeOwner) + "\tOrganisation owner", + string(clientiam.OrganisationMemberTypeMember) + "\tOrganisation member", + }, cobra.ShellCompDirectiveNoFileComp +} + +func completeIAMTeamIdentities(cmd *cobra.Command) ([]string, cobra.ShellCompDirective) { + client, err := thalassaclient.GetThalassaClient() + if err != nil { + return nil, cobra.ShellCompDirectiveError + } + teams, err := client.IAM().ListTeams(cmd.Context(), &clientiam.ListTeamsRequest{}) + if err != nil { + return nil, cobra.ShellCompDirectiveError + } + out := make([]string, 0, len(teams)*3) + for _, t := range teams { + desc := t.Name + if desc == "" { + desc = t.Slug + } + out = append(out, t.Identity+"\t"+desc) + if t.Slug != "" && t.Slug != t.Identity { + out = append(out, t.Slug+"\t"+desc) + } + if t.Name != "" && t.Name != t.Identity && t.Name != t.Slug { + out = append(out, t.Name+"\t"+desc) + } + } + return out, cobra.ShellCompDirectiveNoFileComp +} + +// CompleteIAMTeamIdentity completes team identities, slugs, and names (first positional only). +func CompleteIAMTeamIdentity(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + if len(args) > 0 { + return nil, cobra.ShellCompDirectiveNoFileComp + } + return completeIAMTeamIdentities(cmd) +} + +// CompleteIAMTeamIdentityFlag completes team identities for flag values (ignores positional args). +func CompleteIAMTeamIdentityFlag(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return completeIAMTeamIdentities(cmd) +} + +func completeIAMOrganisationRoleIdentities(cmd *cobra.Command) ([]string, cobra.ShellCompDirective) { + client, err := thalassaclient.GetThalassaClient() + if err != nil { + return nil, cobra.ShellCompDirectiveError + } + roles, err := client.IAM().ListOrganisationRoles(cmd.Context(), &clientiam.ListOrganisationRolesRequest{}) + if err != nil { + return nil, cobra.ShellCompDirectiveError + } + out := make([]string, 0, len(roles)*3) + for _, r := range roles { + desc := r.Name + if desc == "" { + desc = r.Slug + } + out = append(out, r.Identity+"\t"+desc) + if r.Slug != "" && r.Slug != r.Identity { + out = append(out, r.Slug+"\t"+desc) + } + if r.Name != "" && r.Name != r.Identity && r.Name != r.Slug { + out = append(out, r.Name+"\t"+desc) + } + } + return out, cobra.ShellCompDirectiveNoFileComp +} + +// CompleteIAMOrganisationRoleIdentity completes organisation role identities and slugs (first positional only). +func CompleteIAMOrganisationRoleIdentity(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + if len(args) > 0 { + return nil, cobra.ShellCompDirectiveNoFileComp + } + return completeIAMOrganisationRoleIdentities(cmd) +} + +// CompleteIAMOrganisationRoleIdentityFlag completes organisation roles for flag values (ignores positional args). +func CompleteIAMOrganisationRoleIdentityFlag(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return completeIAMOrganisationRoleIdentities(cmd) +} + +func completeIAMServiceAccountIdentities(cmd *cobra.Command) ([]string, cobra.ShellCompDirective) { + client, err := thalassaclient.GetThalassaClient() + if err != nil { + return nil, cobra.ShellCompDirectiveError + } + list, err := client.IAM().ListServiceAccounts(cmd.Context(), &clientiam.ListServiceAccountsRequest{}) + if err != nil { + return nil, cobra.ShellCompDirectiveError + } + out := make([]string, 0, len(list)*3) + for _, sa := range list { + desc := sa.Name + if desc == "" { + desc = sa.Slug + } + out = append(out, sa.Identity+"\t"+desc) + if sa.Slug != "" && sa.Slug != sa.Identity { + out = append(out, sa.Slug+"\t"+desc) + } + if sa.Name != "" && sa.Name != sa.Identity && sa.Name != sa.Slug { + out = append(out, sa.Name+"\t"+desc) + } + } + return out, cobra.ShellCompDirectiveNoFileComp +} + +// CompleteIAMServiceAccountIdentity completes service account identities (first positional only). +func CompleteIAMServiceAccountIdentity(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + if len(args) > 0 { + return nil, cobra.ShellCompDirectiveNoFileComp + } + return completeIAMServiceAccountIdentities(cmd) +} + +// CompleteIAMServiceAccountIdentityFlag completes service accounts for flag values (ignores positional args). +func CompleteIAMServiceAccountIdentityFlag(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return completeIAMServiceAccountIdentities(cmd) +} + +func completeIAMFederatedIdentityIdentities(cmd *cobra.Command) ([]string, cobra.ShellCompDirective) { + client, err := thalassaclient.GetThalassaClient() + if err != nil { + return nil, cobra.ShellCompDirectiveError + } + list, err := client.IAM().ListFederatedIdentities(cmd.Context(), &clientiam.ListFederatedIdentitiesRequest{}) + if err != nil { + return nil, cobra.ShellCompDirectiveError + } + out := make([]string, 0, len(list)*2) + for _, fi := range list { + if fi.Name != "" && fi.Name != fi.Identity { + out = append(out, fi.Identity+"\t"+fi.Name) + out = append(out, fi.Name+"\t"+fi.Identity) + } else { + out = append(out, fi.Identity) + } + } + return out, cobra.ShellCompDirectiveNoFileComp +} + +// CompleteIAMFederatedIdentityIdentity completes federated identity identifiers (first positional only). +func CompleteIAMFederatedIdentityIdentity(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + if len(args) > 0 { + return nil, cobra.ShellCompDirectiveNoFileComp + } + return completeIAMFederatedIdentityIdentities(cmd) +} + +// CompleteIAMFederatedIdentityIdentityFlag completes federated identities for flag values. +func CompleteIAMFederatedIdentityIdentityFlag(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return completeIAMFederatedIdentityIdentities(cmd) +} + +func completeIAMFederatedIdentityProviderIdentities(cmd *cobra.Command) ([]string, cobra.ShellCompDirective) { + client, err := thalassaclient.GetThalassaClient() + if err != nil { + return nil, cobra.ShellCompDirectiveError + } + list, err := client.IAM().ListFederatedIdentityProviders(cmd.Context(), &clientiam.ListFederatedIdentityProvidersRequest{}) + if err != nil { + return nil, cobra.ShellCompDirectiveError + } + out := make([]string, 0, len(list)*2) + for _, p := range list { + if p.Name != "" && p.Name != p.Identity { + out = append(out, p.Identity+"\t"+p.Name) + out = append(out, p.Name+"\t"+p.Identity) + } else { + out = append(out, p.Identity) + } + } + return out, cobra.ShellCompDirectiveNoFileComp +} + +// CompleteIAMFederatedIdentityProviderIdentity completes federated identity provider identifiers (first positional only). +func CompleteIAMFederatedIdentityProviderIdentity(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + if len(args) > 0 { + return nil, cobra.ShellCompDirectiveNoFileComp + } + return completeIAMFederatedIdentityProviderIdentities(cmd) +} + +// CompleteIAMFederatedIdentityProviderIdentityFlag completes federated identity providers for flag values. +func CompleteIAMFederatedIdentityProviderIdentityFlag(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return completeIAMFederatedIdentityProviderIdentities(cmd) +} + +// CompleteIAMRoleThenBinding completes the role then binding identities for that role. +func CompleteIAMRoleThenBinding(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + switch len(args) { + case 0: + return CompleteIAMOrganisationRoleIdentity(cmd, args, toComplete) + case 1: + client, err := thalassaclient.GetThalassaClient() + if err != nil { + return nil, cobra.ShellCompDirectiveError + } + role, err := iamresolve.ResolveOrganisationRoleRef(cmd.Context(), client.IAM(), args[0]) + if err != nil { + return nil, cobra.ShellCompDirectiveError + } + bindings, err := client.IAM().ListRoleBindings(cmd.Context(), role.Identity, &clientiam.ListRoleBindingsRequest{}) + if err != nil { + return nil, cobra.ShellCompDirectiveError + } + out := make([]string, 0, len(bindings)*3) + for _, b := range bindings { + desc := b.Name + if desc == "" { + desc = b.Slug + } + out = append(out, b.Identity+"\t"+desc) + if b.Slug != "" && b.Slug != b.Identity { + out = append(out, b.Slug+"\t"+desc) + } + if b.Name != "" && b.Name != b.Identity && b.Name != b.Slug { + out = append(out, b.Name+"\t"+desc) + } + } + return out, cobra.ShellCompDirectiveNoFileComp + default: + return nil, cobra.ShellCompDirectiveNoFileComp + } +} + +// CompleteIAMRoleThenRule completes the role then rule identities on that role. +func CompleteIAMRoleThenRule(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + switch len(args) { + case 0: + return CompleteIAMOrganisationRoleIdentity(cmd, args, toComplete) + case 1: + client, err := thalassaclient.GetThalassaClient() + if err != nil { + return nil, cobra.ShellCompDirectiveError + } + role, err := client.IAM().GetOrganisationRole(cmd.Context(), args[0]) + if err != nil { + return nil, cobra.ShellCompDirectiveError + } + out := make([]string, 0, len(role.Rules)) + for _, ru := range role.Rules { + note := ru.Note + if note == "" { + note = "rule" + } + out = append(out, ru.Identity+"\t"+note) + } + return out, cobra.ShellCompDirectiveNoFileComp + default: + return nil, cobra.ShellCompDirectiveNoFileComp + } +} + +// CompleteIAMTeamThenTeamMember completes the team then team-member identities for that team. +func CompleteIAMTeamThenTeamMember(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + switch len(args) { + case 0: + return CompleteIAMTeamIdentity(cmd, args, toComplete) + case 1: + client, err := thalassaclient.GetThalassaClient() + if err != nil { + return nil, cobra.ShellCompDirectiveError + } + team, err := client.IAM().GetTeam(cmd.Context(), args[0], &clientiam.GetTeamRequest{}) + if err != nil { + return nil, cobra.ShellCompDirectiveError + } + out := make([]string, 0, len(team.Members)) + for _, m := range team.Members { + desc := iamUserDesc(&m.User) + if desc != "" { + out = append(out, m.Identity+"\t"+desc) + } else { + out = append(out, m.Identity) + } + } + return out, cobra.ShellCompDirectiveNoFileComp + default: + return nil, cobra.ShellCompDirectiveNoFileComp + } +} diff --git a/internal/iamresolve/organisation_role.go b/internal/iamresolve/organisation_role.go new file mode 100644 index 0000000..975107f --- /dev/null +++ b/internal/iamresolve/organisation_role.go @@ -0,0 +1,42 @@ +package iamresolve + +import ( + "context" + "fmt" + "strings" + + clientiam "github.com/thalassa-cloud/client-go/iam" + tcclient "github.com/thalassa-cloud/client-go/pkg/client" +) + +// OrganisationRoleAPI is implemented by *iam.Client. +type OrganisationRoleAPI interface { + GetOrganisationRole(ctx context.Context, identity string) (*clientiam.OrganisationRole, error) + ListOrganisationRoles(ctx context.Context, req *clientiam.ListOrganisationRolesRequest) ([]clientiam.OrganisationRole, error) +} + +// ResolveOrganisationRoleRef resolves a user-supplied role identity, slug, or display name. +func ResolveOrganisationRoleRef(ctx context.Context, api OrganisationRoleAPI, ref string) (*clientiam.OrganisationRole, error) { + ref = strings.TrimSpace(ref) + if ref == "" { + return nil, fmt.Errorf("role reference is empty") + } + role, err := api.GetOrganisationRole(ctx, ref) + if err == nil { + return role, nil + } + if !tcclient.IsNotFound(err) { + return nil, fmt.Errorf("get organisation role: %w", err) + } + roles, err := api.ListOrganisationRoles(ctx, &clientiam.ListOrganisationRolesRequest{}) + if err != nil { + return nil, fmt.Errorf("list organisation roles: %w", err) + } + for i := range roles { + r := &roles[i] + if strings.EqualFold(r.Name, ref) || strings.EqualFold(r.Identity, ref) || strings.EqualFold(r.Slug, ref) { + return r, nil + } + } + return nil, fmt.Errorf("role not found: %s", ref) +} diff --git a/internal/iamresolve/organisation_role_test.go b/internal/iamresolve/organisation_role_test.go new file mode 100644 index 0000000..85b52e7 --- /dev/null +++ b/internal/iamresolve/organisation_role_test.go @@ -0,0 +1,126 @@ +package iamresolve + +import ( + "context" + "errors" + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + clientiam "github.com/thalassa-cloud/client-go/iam" + tcclient "github.com/thalassa-cloud/client-go/pkg/client" +) + +type fakeOrgRoleAPI struct { + getFn func(ctx context.Context, identity string) (*clientiam.OrganisationRole, error) + listFn func(ctx context.Context, req *clientiam.ListOrganisationRolesRequest) ([]clientiam.OrganisationRole, error) +} + +func (f *fakeOrgRoleAPI) GetOrganisationRole(ctx context.Context, identity string) (*clientiam.OrganisationRole, error) { + return f.getFn(ctx, identity) +} + +func (f *fakeOrgRoleAPI) ListOrganisationRoles(ctx context.Context, req *clientiam.ListOrganisationRolesRequest) ([]clientiam.OrganisationRole, error) { + return f.listFn(ctx, req) +} + +func TestResolveOrganisationRoleRef(t *testing.T) { + ctx := context.Background() + notFound := fmt.Errorf("role missing: %w", tcclient.ErrNotFound) + + tests := []struct { + name string + api *fakeOrgRoleAPI + ref string + wantID string + wantErr string + }{ + { + name: "direct get", + api: &fakeOrgRoleAPI{ + getFn: func(_ context.Context, id string) (*clientiam.OrganisationRole, error) { + if id == "rid" { + return &clientiam.OrganisationRole{Identity: "rid", Name: "Editor", Slug: "editor"}, nil + } + return nil, notFound + }, + listFn: func(context.Context, *clientiam.ListOrganisationRolesRequest) ([]clientiam.OrganisationRole, error) { + return nil, errors.New("list should not be called") + }, + }, + ref: "rid", + wantID: "rid", + }, + { + name: "resolve by slug after not found", + api: &fakeOrgRoleAPI{ + getFn: func(_ context.Context, _ string) (*clientiam.OrganisationRole, error) { + return nil, notFound + }, + listFn: func(context.Context, *clientiam.ListOrganisationRolesRequest) ([]clientiam.OrganisationRole, error) { + return []clientiam.OrganisationRole{ + {Identity: "uuid-1", Name: "Deploy", Slug: "deploy"}, + }, nil + }, + }, + ref: "deploy", + wantID: "uuid-1", + }, + { + name: "resolve by name case insensitive", + api: &fakeOrgRoleAPI{ + getFn: func(_ context.Context, _ string) (*clientiam.OrganisationRole, error) { + return nil, notFound + }, + listFn: func(context.Context, *clientiam.ListOrganisationRolesRequest) ([]clientiam.OrganisationRole, error) { + return []clientiam.OrganisationRole{ + {Identity: "uuid-2", Name: "Owner Role", Slug: "owner"}, + }, nil + }, + }, + ref: "owner role", + wantID: "uuid-2", + }, + { + name: "get error not notfound", + api: &fakeOrgRoleAPI{ + getFn: func(_ context.Context, _ string) (*clientiam.OrganisationRole, error) { + return nil, errors.New("server down") + }, + listFn: func(context.Context, *clientiam.ListOrganisationRolesRequest) ([]clientiam.OrganisationRole, error) { + return nil, errors.New("list should not be called") + }, + }, + ref: "x", + wantErr: "get organisation role", + }, + { + name: "not in list", + api: &fakeOrgRoleAPI{ + getFn: func(_ context.Context, _ string) (*clientiam.OrganisationRole, error) { + return nil, notFound + }, + listFn: func(context.Context, *clientiam.ListOrganisationRolesRequest) ([]clientiam.OrganisationRole, error) { + return []clientiam.OrganisationRole{{Identity: "a", Name: "A", Slug: "a"}}, nil + }, + }, + ref: "missing", + wantErr: "role not found", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := ResolveOrganisationRoleRef(ctx, tt.api, tt.ref) + if tt.wantErr != "" { + require.Error(t, err) + assert.Contains(t, err.Error(), tt.wantErr) + assert.Nil(t, got) + return + } + require.NoError(t, err) + require.NotNil(t, got) + assert.Equal(t, tt.wantID, got.Identity) + }) + } +} From 6f873c5727968a0c2d17ca4a9bd22265ca127f3f Mon Sep 17 00:00:00 2001 From: Thomas Kooi Date: Fri, 20 Mar 2026 18:06:26 +0100 Subject: [PATCH 2/2] feat(docs): Update tcloud documentation and add new commands --- docs/tcloud/api/_index.md | 41 ++++++++++ docs/tcloud/api/raw/_index.md | 63 ++++++++++++++++ docs/tcloud/audit/_index.md | 2 +- docs/tcloud/audit/export/_index.md | 2 +- docs/tcloud/compute/_index.md | 2 +- docs/tcloud/compute/machine-images/_index.md | 11 +-- docs/tcloud/compute/machine-types/_index.md | 2 +- docs/tcloud/compute/machines/_index.md | 2 +- docs/tcloud/compute/machines_delete/_index.md | 2 +- docs/tcloud/compute/machines_list/_index.md | 5 +- docs/tcloud/compute/machines_start/_index.md | 2 +- docs/tcloud/compute/machines_stop/_index.md | 2 +- docs/tcloud/context/_index.md | 2 +- docs/tcloud/context/create/_index.md | 2 +- docs/tcloud/context/current/_index.md | 2 +- docs/tcloud/context/delete-server/_index.md | 2 +- docs/tcloud/context/delete-user/_index.md | 2 +- docs/tcloud/context/delete/_index.md | 2 +- docs/tcloud/context/list/_index.md | 2 +- docs/tcloud/context/login/_index.md | 2 +- docs/tcloud/context/organisation/_index.md | 2 +- docs/tcloud/context/use/_index.md | 2 +- docs/tcloud/context/view/_index.md | 2 +- docs/tcloud/dbaas/_index.md | 8 +- docs/tcloud/dbaas/backup-schedules/_index.md | 45 +++++++++++ .../dbaas/backup-schedules_create/_index.md | 52 +++++++++++++ .../dbaas/backup-schedules_delete/_index.md | 45 +++++++++++ .../dbaas/backup-schedules_list/_index.md | 48 ++++++++++++ .../dbaas/backup-schedules_update/_index.md | 51 +++++++++++++ .../dbaas/backup-schedules_view/_index.md | 46 ++++++++++++ docs/tcloud/dbaas/backup/_index.md | 45 +++++++++++ .../dbaas/backup_cancel-deletion/_index.md | 44 +++++++++++ docs/tcloud/dbaas/backup_create/_index.md | 51 +++++++++++++ docs/tcloud/dbaas/backup_delete/_index.md | 47 ++++++++++++ docs/tcloud/dbaas/backup_list/_index.md | 52 +++++++++++++ docs/tcloud/dbaas/backup_view/_index.md | 46 ++++++++++++ docs/tcloud/dbaas/create/_index.md | 61 +++++++++++++++ docs/tcloud/dbaas/delete/_index.md | 47 ++++++++++++ docs/tcloud/dbaas/instance-types/_index.md | 2 +- docs/tcloud/dbaas/list/_index.md | 13 +++- docs/tcloud/dbaas/update/_index.md | 53 +++++++++++++ docs/tcloud/dbaas/versions/_index.md | 2 +- docs/tcloud/dbaas/view/_index.md | 46 ++++++++++++ docs/tcloud/iam/_index.md | 50 +++++++++++++ .../tcloud/iam/federated-identities/_index.md | 41 ++++++++++ .../iam/federated-identities_create/_index.md | 54 +++++++++++++ .../iam/federated-identities_delete/_index.md | 42 +++++++++++ .../iam/federated-identities_get/_index.md | 42 +++++++++++ .../iam/federated-identities_list/_index.md | 43 +++++++++++ .../iam/federated-identities_update/_index.md | 51 +++++++++++++ .../federated-identity-providers/_index.md | 41 ++++++++++ .../_index.md | 48 ++++++++++++ .../_index.md | 42 +++++++++++ .../_index.md | 42 +++++++++++ .../_index.md | 43 +++++++++++ .../_index.md | 47 ++++++++++++ docs/tcloud/iam/invites/_index.md | 37 +++++++++ docs/tcloud/iam/invites_list/_index.md | 42 +++++++++++ docs/tcloud/iam/members/_index.md | 39 ++++++++++ docs/tcloud/iam/members_delete/_index.md | 42 +++++++++++ docs/tcloud/iam/members_list/_index.md | 42 +++++++++++ docs/tcloud/iam/members_update/_index.md | 42 +++++++++++ docs/tcloud/iam/roles/_index.md | 47 ++++++++++++ docs/tcloud/iam/roles_bindings/_index.md | 39 ++++++++++ .../iam/roles_bindings_create/_index.md | 49 ++++++++++++ .../iam/roles_bindings_delete/_index.md | 42 +++++++++++ docs/tcloud/iam/roles_bindings_list/_index.md | 41 ++++++++++ docs/tcloud/iam/roles_create/_index.md | 45 +++++++++++ docs/tcloud/iam/roles_delete/_index.md | 42 +++++++++++ docs/tcloud/iam/roles_get/_index.md | 42 +++++++++++ docs/tcloud/iam/roles_list/_index.md | 43 +++++++++++ docs/tcloud/iam/roles_rules/_index.md | 38 ++++++++++ docs/tcloud/iam/roles_rules_add/_index.md | 45 +++++++++++ docs/tcloud/iam/roles_rules_delete/_index.md | 42 +++++++++++ docs/tcloud/iam/service-accounts/_index.md | 41 ++++++++++ .../iam/service-accounts_create/_index.md | 45 +++++++++++ .../iam/service-accounts_delete/_index.md | 42 +++++++++++ .../tcloud/iam/service-accounts_get/_index.md | 42 +++++++++++ .../iam/service-accounts_list/_index.md | 43 +++++++++++ .../iam/service-accounts_update/_index.md | 45 +++++++++++ docs/tcloud/iam/teams/_index.md | 42 +++++++++++ docs/tcloud/iam/teams_create/_index.md | 46 ++++++++++++ docs/tcloud/iam/teams_delete/_index.md | 43 +++++++++++ docs/tcloud/iam/teams_get/_index.md | 42 +++++++++++ docs/tcloud/iam/teams_list/_index.md | 43 +++++++++++ docs/tcloud/iam/teams_members/_index.md | 39 ++++++++++ docs/tcloud/iam/teams_members_add/_index.md | 43 +++++++++++ docs/tcloud/iam/teams_members_list/_index.md | 41 ++++++++++ .../tcloud/iam/teams_members_remove/_index.md | 42 +++++++++++ docs/tcloud/iam/teams_update/_index.md | 46 ++++++++++++ .../workload-identity-federation/_index.md | 42 +++++++++++ .../_index.md | 60 +++++++++++++++ .../_index.md | 75 +++++++++++++++++++ .../_index.md | 72 ++++++++++++++++++ .../_index.md | 75 +++++++++++++++++++ docs/tcloud/kubernetes/_index.md | 2 +- docs/tcloud/kubernetes/connect/_index.md | 2 +- docs/tcloud/kubernetes/create/_index.md | 2 +- docs/tcloud/kubernetes/delete/_index.md | 2 +- docs/tcloud/kubernetes/kubeconfig/_index.md | 2 +- docs/tcloud/kubernetes/list/_index.md | 7 +- docs/tcloud/kubernetes/nodepools/_index.md | 2 +- .../kubernetes/nodepools_create/_index.md | 2 +- .../kubernetes/nodepools_delete/_index.md | 18 ++--- .../kubernetes/nodepools_list/_index.md | 2 +- .../kubernetes/nodepools_update/_index.md | 2 +- docs/tcloud/kubernetes/update/_index.md | 2 +- docs/tcloud/kubernetes/upgrade/_index.md | 2 +- docs/tcloud/kubernetes/versions/_index.md | 2 +- docs/tcloud/me/_index.md | 2 +- docs/tcloud/me/organisations/_index.md | 2 +- docs/tcloud/networking/_index.md | 2 +- docs/tcloud/networking/natgateways/_index.md | 2 +- .../networking/natgateways_delete/_index.md | 2 +- .../networking/natgateways_list/_index.md | 2 +- .../networking/natgateways_view/_index.md | 2 +- docs/tcloud/networking/routetables/_index.md | 2 +- .../networking/routetables_list/_index.md | 2 +- .../networking/security-groups/_index.md | 2 +- .../security-groups_create/_index.md | 2 +- .../security-groups_delete/_index.md | 2 +- .../networking/security-groups_list/_index.md | 2 +- .../networking/security-groups_view/_index.md | 2 +- docs/tcloud/networking/subnets/_index.md | 2 +- .../networking/subnets_create/_index.md | 3 +- .../networking/subnets_delete/_index.md | 2 +- docs/tcloud/networking/subnets_list/_index.md | 3 +- docs/tcloud/networking/vpc-peering/_index.md | 2 +- .../networking/vpc-peering_accept/_index.md | 2 +- .../networking/vpc-peering_create/_index.md | 2 +- .../networking/vpc-peering_delete/_index.md | 2 +- .../networking/vpc-peering_list/_index.md | 2 +- .../networking/vpc-peering_reject/_index.md | 2 +- .../networking/vpc-peering_update/_index.md | 2 +- docs/tcloud/networking/vpcs/_index.md | 2 +- docs/tcloud/networking/vpcs_create/_index.md | 3 +- docs/tcloud/networking/vpcs_delete/_index.md | 2 +- docs/tcloud/networking/vpcs_list/_index.md | 2 +- docs/tcloud/object-storage/_index.md | 2 +- docs/tcloud/object-storage/create/_index.md | 2 +- docs/tcloud/object-storage/delete/_index.md | 2 +- docs/tcloud/object-storage/list/_index.md | 2 +- docs/tcloud/object-storage/update/_index.md | 2 +- docs/tcloud/oidc/_index.md | 2 +- docs/tcloud/oidc/token-exchange/_index.md | 2 +- docs/tcloud/regions/_index.md | 2 +- docs/tcloud/regions/list/_index.md | 2 +- docs/tcloud/storage/_index.md | 3 +- docs/tcloud/storage/snapshots/_index.md | 2 +- .../tcloud/storage/snapshots_create/_index.md | 2 +- .../tcloud/storage/snapshots_delete/_index.md | 2 +- docs/tcloud/storage/snapshots_list/_index.md | 5 +- docs/tcloud/storage/tfs/_index.md | 41 ++++++++++ docs/tcloud/storage/tfs_create/_index.md | 55 ++++++++++++++ docs/tcloud/storage/tfs_delete/_index.md | 47 ++++++++++++ docs/tcloud/storage/tfs_list/_index.md | 47 ++++++++++++ docs/tcloud/storage/tfs_update/_index.md | 50 +++++++++++++ docs/tcloud/storage/tfs_view/_index.md | 46 ++++++++++++ docs/tcloud/storage/volumes/_index.md | 2 +- docs/tcloud/storage/volumes_attach/_index.md | 2 +- docs/tcloud/storage/volumes_create/_index.md | 2 +- docs/tcloud/storage/volumes_delete/_index.md | 2 +- docs/tcloud/storage/volumes_detach/_index.md | 2 +- docs/tcloud/storage/volumes_list/_index.md | 2 +- docs/tcloud/storage/volumes_resize/_index.md | 2 +- docs/tcloud/tcloud.md | 4 +- docs/tcloud/tcloud_version.md | 2 +- 167 files changed, 3656 insertions(+), 108 deletions(-) create mode 100644 docs/tcloud/api/_index.md create mode 100644 docs/tcloud/api/raw/_index.md create mode 100644 docs/tcloud/dbaas/backup-schedules/_index.md create mode 100644 docs/tcloud/dbaas/backup-schedules_create/_index.md create mode 100644 docs/tcloud/dbaas/backup-schedules_delete/_index.md create mode 100644 docs/tcloud/dbaas/backup-schedules_list/_index.md create mode 100644 docs/tcloud/dbaas/backup-schedules_update/_index.md create mode 100644 docs/tcloud/dbaas/backup-schedules_view/_index.md create mode 100644 docs/tcloud/dbaas/backup/_index.md create mode 100644 docs/tcloud/dbaas/backup_cancel-deletion/_index.md create mode 100644 docs/tcloud/dbaas/backup_create/_index.md create mode 100644 docs/tcloud/dbaas/backup_delete/_index.md create mode 100644 docs/tcloud/dbaas/backup_list/_index.md create mode 100644 docs/tcloud/dbaas/backup_view/_index.md create mode 100644 docs/tcloud/dbaas/create/_index.md create mode 100644 docs/tcloud/dbaas/delete/_index.md create mode 100644 docs/tcloud/dbaas/update/_index.md create mode 100644 docs/tcloud/dbaas/view/_index.md create mode 100644 docs/tcloud/iam/_index.md create mode 100644 docs/tcloud/iam/federated-identities/_index.md create mode 100644 docs/tcloud/iam/federated-identities_create/_index.md create mode 100644 docs/tcloud/iam/federated-identities_delete/_index.md create mode 100644 docs/tcloud/iam/federated-identities_get/_index.md create mode 100644 docs/tcloud/iam/federated-identities_list/_index.md create mode 100644 docs/tcloud/iam/federated-identities_update/_index.md create mode 100644 docs/tcloud/iam/federated-identity-providers/_index.md create mode 100644 docs/tcloud/iam/federated-identity-providers_create/_index.md create mode 100644 docs/tcloud/iam/federated-identity-providers_delete/_index.md create mode 100644 docs/tcloud/iam/federated-identity-providers_get/_index.md create mode 100644 docs/tcloud/iam/federated-identity-providers_list/_index.md create mode 100644 docs/tcloud/iam/federated-identity-providers_update/_index.md create mode 100644 docs/tcloud/iam/invites/_index.md create mode 100644 docs/tcloud/iam/invites_list/_index.md create mode 100644 docs/tcloud/iam/members/_index.md create mode 100644 docs/tcloud/iam/members_delete/_index.md create mode 100644 docs/tcloud/iam/members_list/_index.md create mode 100644 docs/tcloud/iam/members_update/_index.md create mode 100644 docs/tcloud/iam/roles/_index.md create mode 100644 docs/tcloud/iam/roles_bindings/_index.md create mode 100644 docs/tcloud/iam/roles_bindings_create/_index.md create mode 100644 docs/tcloud/iam/roles_bindings_delete/_index.md create mode 100644 docs/tcloud/iam/roles_bindings_list/_index.md create mode 100644 docs/tcloud/iam/roles_create/_index.md create mode 100644 docs/tcloud/iam/roles_delete/_index.md create mode 100644 docs/tcloud/iam/roles_get/_index.md create mode 100644 docs/tcloud/iam/roles_list/_index.md create mode 100644 docs/tcloud/iam/roles_rules/_index.md create mode 100644 docs/tcloud/iam/roles_rules_add/_index.md create mode 100644 docs/tcloud/iam/roles_rules_delete/_index.md create mode 100644 docs/tcloud/iam/service-accounts/_index.md create mode 100644 docs/tcloud/iam/service-accounts_create/_index.md create mode 100644 docs/tcloud/iam/service-accounts_delete/_index.md create mode 100644 docs/tcloud/iam/service-accounts_get/_index.md create mode 100644 docs/tcloud/iam/service-accounts_list/_index.md create mode 100644 docs/tcloud/iam/service-accounts_update/_index.md create mode 100644 docs/tcloud/iam/teams/_index.md create mode 100644 docs/tcloud/iam/teams_create/_index.md create mode 100644 docs/tcloud/iam/teams_delete/_index.md create mode 100644 docs/tcloud/iam/teams_get/_index.md create mode 100644 docs/tcloud/iam/teams_list/_index.md create mode 100644 docs/tcloud/iam/teams_members/_index.md create mode 100644 docs/tcloud/iam/teams_members_add/_index.md create mode 100644 docs/tcloud/iam/teams_members_list/_index.md create mode 100644 docs/tcloud/iam/teams_members_remove/_index.md create mode 100644 docs/tcloud/iam/teams_update/_index.md create mode 100644 docs/tcloud/iam/workload-identity-federation/_index.md create mode 100644 docs/tcloud/iam/workload-identity-federation_bootstrap/_index.md create mode 100644 docs/tcloud/iam/workload-identity-federation_bootstrap_github/_index.md create mode 100644 docs/tcloud/iam/workload-identity-federation_bootstrap_gitlab/_index.md create mode 100644 docs/tcloud/iam/workload-identity-federation_bootstrap_kubernetes/_index.md create mode 100644 docs/tcloud/storage/tfs/_index.md create mode 100644 docs/tcloud/storage/tfs_create/_index.md create mode 100644 docs/tcloud/storage/tfs_delete/_index.md create mode 100644 docs/tcloud/storage/tfs_list/_index.md create mode 100644 docs/tcloud/storage/tfs_update/_index.md create mode 100644 docs/tcloud/storage/tfs_view/_index.md diff --git a/docs/tcloud/api/_index.md b/docs/tcloud/api/_index.md new file mode 100644 index 0000000..2affb9b --- /dev/null +++ b/docs/tcloud/api/_index.md @@ -0,0 +1,41 @@ +--- +linkTitle: "tcloud api" +title: "api" +slug: tcloud_api +url: /docs/tcloud/tcloud_api/ +weight: 9998 +cascade: + type: docs +--- +## tcloud api + +Direct API access + +### Synopsis + +Make raw HTTP requests to the Thalassa Cloud API. + +### Options + +``` + -h, --help help for api +``` + +### Options inherited from parent commands + +``` + --access-token string Access Token authentication (overrides context) + --api string API endpoint (overrides context) + --client-id string OIDC client ID for OIDC authentication (overrides context) + --client-secret string OIDC client secret for OIDC authentication (overrides context) + -c, --context string Context name + --debug Debug mode + -O, --organisation string Organisation slug or identity (overrides context) + --token string Personal access token (overrides context) +``` + +### SEE ALSO + +* [tcloud](/docs/tcloud/tcloud/) - A CLI for working with the Thalassa Cloud Platform +* [tcloud api raw](/docs/tcloud/api/raw/) - Make a raw HTTP request to the API + diff --git a/docs/tcloud/api/raw/_index.md b/docs/tcloud/api/raw/_index.md new file mode 100644 index 0000000..b393977 --- /dev/null +++ b/docs/tcloud/api/raw/_index.md @@ -0,0 +1,63 @@ +--- +linkTitle: "tcloud api raw" +title: "api raw" +slug: tcloud_api_raw +url: /docs/tcloud/api/raw/ +weight: 9999 +cascade: + type: docs +--- +## tcloud api raw + +Make a raw HTTP request to the API + +### Synopsis + +Make a raw HTTP request to the Thalassa Cloud API. + +Similar to 'kubectl get --raw', this bypasses the CLI resource layer and sends +the request directly to the API server. Uses the same authentication and +context (organisation, endpoint) as other tcloud commands. + +PATH must start with a slash (e.g. /v1/me/organisations). +Requires client-go with RawRequest support. + +``` +tcloud api raw PATH [flags] +``` + +### Examples + +``` + tcloud api raw /v1/me/organisations + tcloud api raw -X GET /v1/iaas/regions + tcloud api raw -X POST -d '{"name":"test"}' /v1/some/resource + tcloud api raw --show-headers /v1/me +``` + +### Options + +``` + -d, --data string Request body (for POST, PUT, PATCH) + -h, --help help for raw + -X, --request string HTTP method (GET, POST, PUT, PATCH, DELETE) (default "GET") + --show-headers Print response headers +``` + +### Options inherited from parent commands + +``` + --access-token string Access Token authentication (overrides context) + --api string API endpoint (overrides context) + --client-id string OIDC client ID for OIDC authentication (overrides context) + --client-secret string OIDC client secret for OIDC authentication (overrides context) + -c, --context string Context name + --debug Debug mode + -O, --organisation string Organisation slug or identity (overrides context) + --token string Personal access token (overrides context) +``` + +### SEE ALSO + +* [tcloud api](/docs/tcloud/tcloud_api/) - Direct API access + diff --git a/docs/tcloud/audit/_index.md b/docs/tcloud/audit/_index.md index eeb667b..cd61f06 100644 --- a/docs/tcloud/audit/_index.md +++ b/docs/tcloud/audit/_index.md @@ -3,7 +3,7 @@ linkTitle: "tcloud audit" title: "audit" slug: tcloud_audit url: /docs/tcloud/tcloud_audit/ -weight: 9998 +weight: 9996 cascade: type: docs --- diff --git a/docs/tcloud/audit/export/_index.md b/docs/tcloud/audit/export/_index.md index 96a3f1a..be540a5 100644 --- a/docs/tcloud/audit/export/_index.md +++ b/docs/tcloud/audit/export/_index.md @@ -3,7 +3,7 @@ linkTitle: "tcloud audit export" title: "audit export" slug: tcloud_audit_export url: /docs/tcloud/audit/export/ -weight: 9999 +weight: 9997 cascade: type: docs --- diff --git a/docs/tcloud/compute/_index.md b/docs/tcloud/compute/_index.md index 7fae641..6990c8a 100644 --- a/docs/tcloud/compute/_index.md +++ b/docs/tcloud/compute/_index.md @@ -3,7 +3,7 @@ linkTitle: "tcloud compute" title: "compute" slug: tcloud_compute url: /docs/tcloud/tcloud_compute/ -weight: 9990 +weight: 9988 cascade: type: docs --- diff --git a/docs/tcloud/compute/machine-images/_index.md b/docs/tcloud/compute/machine-images/_index.md index 7f85508..55c6dbe 100644 --- a/docs/tcloud/compute/machine-images/_index.md +++ b/docs/tcloud/compute/machine-images/_index.md @@ -3,7 +3,7 @@ linkTitle: "tcloud compute machine-images" title: "compute machine-images" slug: tcloud_compute_machine-images url: /docs/tcloud/compute/machine-images/ -weight: 9997 +weight: 9995 cascade: type: docs --- @@ -28,10 +28,11 @@ thalassa compute machine-images ### Options ``` - -h, --help help for machine-images - --no-header Do not print the header - -o, --output string Output format. One of: wide - --show-labels Show labels associated with machines + -h, --help help for machine-images + --no-header Do not print the header + -o, --output string Output format. One of: wide + -l, --selector string Label selector to filter machine images (format: key1=value1,key2=value2) + --show-labels Show labels associated with machines ``` ### Options inherited from parent commands diff --git a/docs/tcloud/compute/machine-types/_index.md b/docs/tcloud/compute/machine-types/_index.md index c4fa4bc..6d0a37c 100644 --- a/docs/tcloud/compute/machine-types/_index.md +++ b/docs/tcloud/compute/machine-types/_index.md @@ -3,7 +3,7 @@ linkTitle: "tcloud compute machine-types" title: "compute machine-types" slug: tcloud_compute_machine-types url: /docs/tcloud/compute/machine-types/ -weight: 9996 +weight: 9994 cascade: type: docs --- diff --git a/docs/tcloud/compute/machines/_index.md b/docs/tcloud/compute/machines/_index.md index 92562be..bbea8ec 100644 --- a/docs/tcloud/compute/machines/_index.md +++ b/docs/tcloud/compute/machines/_index.md @@ -3,7 +3,7 @@ linkTitle: "tcloud compute machines" title: "compute machines" slug: tcloud_compute_machines url: /docs/tcloud/compute/machines/ -weight: 9991 +weight: 9989 cascade: type: docs --- diff --git a/docs/tcloud/compute/machines_delete/_index.md b/docs/tcloud/compute/machines_delete/_index.md index 04a34e0..28b270b 100644 --- a/docs/tcloud/compute/machines_delete/_index.md +++ b/docs/tcloud/compute/machines_delete/_index.md @@ -3,7 +3,7 @@ linkTitle: "tcloud compute machines delete" title: "compute machines delete" slug: tcloud_compute_machines_delete url: /docs/tcloud/compute/machines_delete/ -weight: 9995 +weight: 9993 cascade: type: docs --- diff --git a/docs/tcloud/compute/machines_list/_index.md b/docs/tcloud/compute/machines_list/_index.md index ddda64c..b62ded3 100644 --- a/docs/tcloud/compute/machines_list/_index.md +++ b/docs/tcloud/compute/machines_list/_index.md @@ -3,7 +3,7 @@ linkTitle: "tcloud compute machines list" title: "compute machines list" slug: tcloud_compute_machines_list url: /docs/tcloud/compute/machines_list/ -weight: 9994 +weight: 9992 cascade: type: docs --- @@ -21,9 +21,12 @@ tcloud compute machines list [flags] -h, --help help for list --no-header Do not print the header -o, --output string Output format. One of: wide + --region string Region of the machine -l, --selector string Label selector to filter machines (format: key1=value1,key2=value2) --show-exact-time Show exact time instead of relative time --show-labels Show labels associated with machines + --status string Status of the machine + --vpc string VPC of the machine ``` ### Options inherited from parent commands diff --git a/docs/tcloud/compute/machines_start/_index.md b/docs/tcloud/compute/machines_start/_index.md index 949ec4a..9205a98 100644 --- a/docs/tcloud/compute/machines_start/_index.md +++ b/docs/tcloud/compute/machines_start/_index.md @@ -3,7 +3,7 @@ linkTitle: "tcloud compute machines start" title: "compute machines start" slug: tcloud_compute_machines_start url: /docs/tcloud/compute/machines_start/ -weight: 9993 +weight: 9991 cascade: type: docs --- diff --git a/docs/tcloud/compute/machines_stop/_index.md b/docs/tcloud/compute/machines_stop/_index.md index 49e0860..a2e7a45 100644 --- a/docs/tcloud/compute/machines_stop/_index.md +++ b/docs/tcloud/compute/machines_stop/_index.md @@ -3,7 +3,7 @@ linkTitle: "tcloud compute machines stop" title: "compute machines stop" slug: tcloud_compute_machines_stop url: /docs/tcloud/compute/machines_stop/ -weight: 9992 +weight: 9990 cascade: type: docs --- diff --git a/docs/tcloud/context/_index.md b/docs/tcloud/context/_index.md index 601770b..00b211d 100644 --- a/docs/tcloud/context/_index.md +++ b/docs/tcloud/context/_index.md @@ -3,7 +3,7 @@ linkTitle: "tcloud context" title: "context" slug: tcloud_context url: /docs/tcloud/tcloud_context/ -weight: 9979 +weight: 9977 cascade: type: docs --- diff --git a/docs/tcloud/context/create/_index.md b/docs/tcloud/context/create/_index.md index cb4b575..255a11f 100644 --- a/docs/tcloud/context/create/_index.md +++ b/docs/tcloud/context/create/_index.md @@ -3,7 +3,7 @@ linkTitle: "tcloud context create" title: "context create" slug: tcloud_context_create url: /docs/tcloud/context/create/ -weight: 9989 +weight: 9987 cascade: type: docs --- diff --git a/docs/tcloud/context/current/_index.md b/docs/tcloud/context/current/_index.md index dd521d6..c4fdb61 100644 --- a/docs/tcloud/context/current/_index.md +++ b/docs/tcloud/context/current/_index.md @@ -3,7 +3,7 @@ linkTitle: "tcloud context current" title: "context current" slug: tcloud_context_current url: /docs/tcloud/context/current/ -weight: 9988 +weight: 9986 cascade: type: docs --- diff --git a/docs/tcloud/context/delete-server/_index.md b/docs/tcloud/context/delete-server/_index.md index 855aa59..cb8a520 100644 --- a/docs/tcloud/context/delete-server/_index.md +++ b/docs/tcloud/context/delete-server/_index.md @@ -3,7 +3,7 @@ linkTitle: "tcloud context delete-server" title: "context delete-server" slug: tcloud_context_delete-server url: /docs/tcloud/context/delete-server/ -weight: 9986 +weight: 9984 cascade: type: docs --- diff --git a/docs/tcloud/context/delete-user/_index.md b/docs/tcloud/context/delete-user/_index.md index dc932a5..4800cea 100644 --- a/docs/tcloud/context/delete-user/_index.md +++ b/docs/tcloud/context/delete-user/_index.md @@ -3,7 +3,7 @@ linkTitle: "tcloud context delete-user" title: "context delete-user" slug: tcloud_context_delete-user url: /docs/tcloud/context/delete-user/ -weight: 9985 +weight: 9983 cascade: type: docs --- diff --git a/docs/tcloud/context/delete/_index.md b/docs/tcloud/context/delete/_index.md index deaa2e0..283a9e1 100644 --- a/docs/tcloud/context/delete/_index.md +++ b/docs/tcloud/context/delete/_index.md @@ -3,7 +3,7 @@ linkTitle: "tcloud context delete" title: "context delete" slug: tcloud_context_delete url: /docs/tcloud/context/delete/ -weight: 9987 +weight: 9985 cascade: type: docs --- diff --git a/docs/tcloud/context/list/_index.md b/docs/tcloud/context/list/_index.md index f9d5874..1eeb665 100644 --- a/docs/tcloud/context/list/_index.md +++ b/docs/tcloud/context/list/_index.md @@ -3,7 +3,7 @@ linkTitle: "tcloud context list" title: "context list" slug: tcloud_context_list url: /docs/tcloud/context/list/ -weight: 9984 +weight: 9982 cascade: type: docs --- diff --git a/docs/tcloud/context/login/_index.md b/docs/tcloud/context/login/_index.md index 34bae7c..da922e9 100644 --- a/docs/tcloud/context/login/_index.md +++ b/docs/tcloud/context/login/_index.md @@ -3,7 +3,7 @@ linkTitle: "tcloud context login" title: "context login" slug: tcloud_context_login url: /docs/tcloud/context/login/ -weight: 9983 +weight: 9981 cascade: type: docs --- diff --git a/docs/tcloud/context/organisation/_index.md b/docs/tcloud/context/organisation/_index.md index 8e7ee23..765e814 100644 --- a/docs/tcloud/context/organisation/_index.md +++ b/docs/tcloud/context/organisation/_index.md @@ -3,7 +3,7 @@ linkTitle: "tcloud context organisation" title: "context organisation" slug: tcloud_context_organisation url: /docs/tcloud/context/organisation/ -weight: 9982 +weight: 9980 cascade: type: docs --- diff --git a/docs/tcloud/context/use/_index.md b/docs/tcloud/context/use/_index.md index a736637..b8e50af 100644 --- a/docs/tcloud/context/use/_index.md +++ b/docs/tcloud/context/use/_index.md @@ -3,7 +3,7 @@ linkTitle: "tcloud context use" title: "context use" slug: tcloud_context_use url: /docs/tcloud/context/use/ -weight: 9981 +weight: 9979 cascade: type: docs --- diff --git a/docs/tcloud/context/view/_index.md b/docs/tcloud/context/view/_index.md index 7f39935..0ce72ba 100644 --- a/docs/tcloud/context/view/_index.md +++ b/docs/tcloud/context/view/_index.md @@ -3,7 +3,7 @@ linkTitle: "tcloud context view" title: "context view" slug: tcloud_context_view url: /docs/tcloud/context/view/ -weight: 9980 +weight: 9978 cascade: type: docs --- diff --git a/docs/tcloud/dbaas/_index.md b/docs/tcloud/dbaas/_index.md index 70adc75..b3c8437 100644 --- a/docs/tcloud/dbaas/_index.md +++ b/docs/tcloud/dbaas/_index.md @@ -3,7 +3,7 @@ linkTitle: "tcloud dbaas" title: "dbaas" slug: tcloud_dbaas url: /docs/tcloud/tcloud_dbaas/ -weight: 9975 +weight: 9957 cascade: type: docs --- @@ -45,7 +45,13 @@ tcloud dbaas versions --engine postgres ### SEE ALSO * [tcloud](/docs/tcloud/tcloud/) - A CLI for working with the Thalassa Cloud Platform +* [tcloud dbaas backup](/docs/tcloud/dbaas/backup/) - Manage database backups +* [tcloud dbaas backup-schedules](/docs/tcloud/dbaas/backup-schedules/) - Manage database backup schedules +* [tcloud dbaas create](/docs/tcloud/dbaas/create/) - Create a database cluster +* [tcloud dbaas delete](/docs/tcloud/dbaas/delete/) - Delete database cluster(s) * [tcloud dbaas instance-types](/docs/tcloud/dbaas/instance-types/) - Get a list of database instance types * [tcloud dbaas list](/docs/tcloud/dbaas/list/) - Get a list of database clusters +* [tcloud dbaas update](/docs/tcloud/dbaas/update/) - Update a database cluster * [tcloud dbaas versions](/docs/tcloud/dbaas/versions/) - Get a list of database engine versions +* [tcloud dbaas view](/docs/tcloud/dbaas/view/) - View database cluster details diff --git a/docs/tcloud/dbaas/backup-schedules/_index.md b/docs/tcloud/dbaas/backup-schedules/_index.md new file mode 100644 index 0000000..b24741f --- /dev/null +++ b/docs/tcloud/dbaas/backup-schedules/_index.md @@ -0,0 +1,45 @@ +--- +linkTitle: "tcloud dbaas backup-schedules" +title: "dbaas backup-schedules" +slug: tcloud_dbaas_backup-schedules +url: /docs/tcloud/dbaas/backup-schedules/ +weight: 9965 +cascade: + type: docs +--- +## tcloud dbaas backup-schedules + +Manage database backup schedules + +### Synopsis + +Manage database backup schedules for database clusters + +### Options + +``` + -h, --help help for backup-schedules +``` + +### Options inherited from parent commands + +``` + --access-token string Access Token authentication (overrides context) + --api string API endpoint (overrides context) + --client-id string OIDC client ID for OIDC authentication (overrides context) + --client-secret string OIDC client secret for OIDC authentication (overrides context) + -c, --context string Context name + --debug Debug mode + -O, --organisation string Organisation slug or identity (overrides context) + --token string Personal access token (overrides context) +``` + +### SEE ALSO + +* [tcloud dbaas](/docs/tcloud/tcloud_dbaas/) - Manage database clusters and related services +* [tcloud dbaas backup-schedules create](/docs/tcloud/dbaas/backup-schedules_create/) - Create a database backup schedule +* [tcloud dbaas backup-schedules delete](/docs/tcloud/dbaas/backup-schedules_delete/) - Delete a database backup schedule +* [tcloud dbaas backup-schedules list](/docs/tcloud/dbaas/backup-schedules_list/) - List database backup schedules +* [tcloud dbaas backup-schedules update](/docs/tcloud/dbaas/backup-schedules_update/) - Update a database backup schedule +* [tcloud dbaas backup-schedules view](/docs/tcloud/dbaas/backup-schedules_view/) - View backup schedule details + diff --git a/docs/tcloud/dbaas/backup-schedules_create/_index.md b/docs/tcloud/dbaas/backup-schedules_create/_index.md new file mode 100644 index 0000000..4b5bde1 --- /dev/null +++ b/docs/tcloud/dbaas/backup-schedules_create/_index.md @@ -0,0 +1,52 @@ +--- +linkTitle: "tcloud dbaas backup-schedules create" +title: "dbaas backup-schedules create" +slug: tcloud_dbaas_backup-schedules_create +url: /docs/tcloud/dbaas/backup-schedules_create/ +weight: 9970 +cascade: + type: docs +--- +## tcloud dbaas backup-schedules create + +Create a database backup schedule + +### Synopsis + +Create a new backup schedule for a database cluster + +``` +tcloud dbaas backup-schedules create [flags] +``` + +### Options + +``` + --annotations strings Annotations in key=value format (can be specified multiple times) + --description string Description of the backup schedule + -h, --help help for create + --labels strings Labels in key=value format (can be specified multiple times) + --method string Backup method: 'barman' (default) (default "barman") + -n, --name string Name of the backup schedule (required) + --no-header Do not print the header + --retention-policy string Retention policy for the backup schedule (required) + --schedule string Cron expression for the backup schedule (required) +``` + +### Options inherited from parent commands + +``` + --access-token string Access Token authentication (overrides context) + --api string API endpoint (overrides context) + --client-id string OIDC client ID for OIDC authentication (overrides context) + --client-secret string OIDC client secret for OIDC authentication (overrides context) + -c, --context string Context name + --debug Debug mode + -O, --organisation string Organisation slug or identity (overrides context) + --token string Personal access token (overrides context) +``` + +### SEE ALSO + +* [tcloud dbaas backup-schedules](/docs/tcloud/dbaas/backup-schedules/) - Manage database backup schedules + diff --git a/docs/tcloud/dbaas/backup-schedules_delete/_index.md b/docs/tcloud/dbaas/backup-schedules_delete/_index.md new file mode 100644 index 0000000..a61281f --- /dev/null +++ b/docs/tcloud/dbaas/backup-schedules_delete/_index.md @@ -0,0 +1,45 @@ +--- +linkTitle: "tcloud dbaas backup-schedules delete" +title: "dbaas backup-schedules delete" +slug: tcloud_dbaas_backup-schedules_delete +url: /docs/tcloud/dbaas/backup-schedules_delete/ +weight: 9969 +cascade: + type: docs +--- +## tcloud dbaas backup-schedules delete + +Delete a database backup schedule + +### Synopsis + +Delete a database backup schedule + +``` +tcloud dbaas backup-schedules delete [flags] +``` + +### Options + +``` + --force Force the deletion and skip the confirmation + -h, --help help for delete +``` + +### Options inherited from parent commands + +``` + --access-token string Access Token authentication (overrides context) + --api string API endpoint (overrides context) + --client-id string OIDC client ID for OIDC authentication (overrides context) + --client-secret string OIDC client secret for OIDC authentication (overrides context) + -c, --context string Context name + --debug Debug mode + -O, --organisation string Organisation slug or identity (overrides context) + --token string Personal access token (overrides context) +``` + +### SEE ALSO + +* [tcloud dbaas backup-schedules](/docs/tcloud/dbaas/backup-schedules/) - Manage database backup schedules + diff --git a/docs/tcloud/dbaas/backup-schedules_list/_index.md b/docs/tcloud/dbaas/backup-schedules_list/_index.md new file mode 100644 index 0000000..d2a80ce --- /dev/null +++ b/docs/tcloud/dbaas/backup-schedules_list/_index.md @@ -0,0 +1,48 @@ +--- +linkTitle: "tcloud dbaas backup-schedules list" +title: "dbaas backup-schedules list" +slug: tcloud_dbaas_backup-schedules_list +url: /docs/tcloud/dbaas/backup-schedules_list/ +weight: 9968 +cascade: + type: docs +--- +## tcloud dbaas backup-schedules list + +List database backup schedules + +### Synopsis + +List database backup schedules for a specific cluster or all schedules in the organisation + +``` +tcloud dbaas backup-schedules list [flags] +``` + +### Options + +``` + --cluster string Filter by database cluster identity, slug, or name + --exact-time Show exact time instead of relative time + -h, --help help for list + --no-header Do not print the header + --show-labels Show labels +``` + +### Options inherited from parent commands + +``` + --access-token string Access Token authentication (overrides context) + --api string API endpoint (overrides context) + --client-id string OIDC client ID for OIDC authentication (overrides context) + --client-secret string OIDC client secret for OIDC authentication (overrides context) + -c, --context string Context name + --debug Debug mode + -O, --organisation string Organisation slug or identity (overrides context) + --token string Personal access token (overrides context) +``` + +### SEE ALSO + +* [tcloud dbaas backup-schedules](/docs/tcloud/dbaas/backup-schedules/) - Manage database backup schedules + diff --git a/docs/tcloud/dbaas/backup-schedules_update/_index.md b/docs/tcloud/dbaas/backup-schedules_update/_index.md new file mode 100644 index 0000000..d58143d --- /dev/null +++ b/docs/tcloud/dbaas/backup-schedules_update/_index.md @@ -0,0 +1,51 @@ +--- +linkTitle: "tcloud dbaas backup-schedules update" +title: "dbaas backup-schedules update" +slug: tcloud_dbaas_backup-schedules_update +url: /docs/tcloud/dbaas/backup-schedules_update/ +weight: 9967 +cascade: + type: docs +--- +## tcloud dbaas backup-schedules update + +Update a database backup schedule + +### Synopsis + +Update properties of an existing backup schedule + +``` +tcloud dbaas backup-schedules update [flags] +``` + +### Options + +``` + --annotations strings Annotations in key=value format (can be specified multiple times) + --description string Description of the backup schedule + -h, --help help for update + --labels strings Labels in key=value format (can be specified multiple times) + --name string Name of the backup schedule + --no-header Do not print the header + --retention-policy string Retention policy for the backup schedule + --schedule string Cron expression for the backup schedule +``` + +### Options inherited from parent commands + +``` + --access-token string Access Token authentication (overrides context) + --api string API endpoint (overrides context) + --client-id string OIDC client ID for OIDC authentication (overrides context) + --client-secret string OIDC client secret for OIDC authentication (overrides context) + -c, --context string Context name + --debug Debug mode + -O, --organisation string Organisation slug or identity (overrides context) + --token string Personal access token (overrides context) +``` + +### SEE ALSO + +* [tcloud dbaas backup-schedules](/docs/tcloud/dbaas/backup-schedules/) - Manage database backup schedules + diff --git a/docs/tcloud/dbaas/backup-schedules_view/_index.md b/docs/tcloud/dbaas/backup-schedules_view/_index.md new file mode 100644 index 0000000..2872634 --- /dev/null +++ b/docs/tcloud/dbaas/backup-schedules_view/_index.md @@ -0,0 +1,46 @@ +--- +linkTitle: "tcloud dbaas backup-schedules view" +title: "dbaas backup-schedules view" +slug: tcloud_dbaas_backup-schedules_view +url: /docs/tcloud/dbaas/backup-schedules_view/ +weight: 9966 +cascade: + type: docs +--- +## tcloud dbaas backup-schedules view + +View backup schedule details + +### Synopsis + +View detailed information about a database backup schedule + +``` +tcloud dbaas backup-schedules view [flags] +``` + +### Options + +``` + --exact-time Show exact time instead of relative time + -h, --help help for view + --no-header Do not print the header +``` + +### Options inherited from parent commands + +``` + --access-token string Access Token authentication (overrides context) + --api string API endpoint (overrides context) + --client-id string OIDC client ID for OIDC authentication (overrides context) + --client-secret string OIDC client secret for OIDC authentication (overrides context) + -c, --context string Context name + --debug Debug mode + -O, --organisation string Organisation slug or identity (overrides context) + --token string Personal access token (overrides context) +``` + +### SEE ALSO + +* [tcloud dbaas backup-schedules](/docs/tcloud/dbaas/backup-schedules/) - Manage database backup schedules + diff --git a/docs/tcloud/dbaas/backup/_index.md b/docs/tcloud/dbaas/backup/_index.md new file mode 100644 index 0000000..016c964 --- /dev/null +++ b/docs/tcloud/dbaas/backup/_index.md @@ -0,0 +1,45 @@ +--- +linkTitle: "tcloud dbaas backup" +title: "dbaas backup" +slug: tcloud_dbaas_backup +url: /docs/tcloud/dbaas/backup/ +weight: 9971 +cascade: + type: docs +--- +## tcloud dbaas backup + +Manage database backups + +### Synopsis + +Manage database backups for database clusters + +### Options + +``` + -h, --help help for backup +``` + +### Options inherited from parent commands + +``` + --access-token string Access Token authentication (overrides context) + --api string API endpoint (overrides context) + --client-id string OIDC client ID for OIDC authentication (overrides context) + --client-secret string OIDC client secret for OIDC authentication (overrides context) + -c, --context string Context name + --debug Debug mode + -O, --organisation string Organisation slug or identity (overrides context) + --token string Personal access token (overrides context) +``` + +### SEE ALSO + +* [tcloud dbaas](/docs/tcloud/tcloud_dbaas/) - Manage database clusters and related services +* [tcloud dbaas backup cancel-deletion](/docs/tcloud/dbaas/backup_cancel-deletion/) - Cancel scheduled deletion of a backup +* [tcloud dbaas backup create](/docs/tcloud/dbaas/backup_create/) - Create a database backup +* [tcloud dbaas backup delete](/docs/tcloud/dbaas/backup_delete/) - Delete database backup(s) +* [tcloud dbaas backup list](/docs/tcloud/dbaas/backup_list/) - List database backups +* [tcloud dbaas backup view](/docs/tcloud/dbaas/backup_view/) - View backup details + diff --git a/docs/tcloud/dbaas/backup_cancel-deletion/_index.md b/docs/tcloud/dbaas/backup_cancel-deletion/_index.md new file mode 100644 index 0000000..0286b0d --- /dev/null +++ b/docs/tcloud/dbaas/backup_cancel-deletion/_index.md @@ -0,0 +1,44 @@ +--- +linkTitle: "tcloud dbaas backup cancel-deletion" +title: "dbaas backup cancel-deletion" +slug: tcloud_dbaas_backup_cancel-deletion +url: /docs/tcloud/dbaas/backup_cancel-deletion/ +weight: 9976 +cascade: + type: docs +--- +## tcloud dbaas backup cancel-deletion + +Cancel scheduled deletion of a backup + +### Synopsis + +Cancel the scheduled deletion of a database backup + +``` +tcloud dbaas backup cancel-deletion [flags] +``` + +### Options + +``` + -h, --help help for cancel-deletion +``` + +### Options inherited from parent commands + +``` + --access-token string Access Token authentication (overrides context) + --api string API endpoint (overrides context) + --client-id string OIDC client ID for OIDC authentication (overrides context) + --client-secret string OIDC client secret for OIDC authentication (overrides context) + -c, --context string Context name + --debug Debug mode + -O, --organisation string Organisation slug or identity (overrides context) + --token string Personal access token (overrides context) +``` + +### SEE ALSO + +* [tcloud dbaas backup](/docs/tcloud/dbaas/backup/) - Manage database backups + diff --git a/docs/tcloud/dbaas/backup_create/_index.md b/docs/tcloud/dbaas/backup_create/_index.md new file mode 100644 index 0000000..cf63179 --- /dev/null +++ b/docs/tcloud/dbaas/backup_create/_index.md @@ -0,0 +1,51 @@ +--- +linkTitle: "tcloud dbaas backup create" +title: "dbaas backup create" +slug: tcloud_dbaas_backup_create +url: /docs/tcloud/dbaas/backup_create/ +weight: 9975 +cascade: + type: docs +--- +## tcloud dbaas backup create + +Create a database backup + +### Synopsis + +Create a new backup for a database cluster + +``` +tcloud dbaas backup create [flags] +``` + +### Options + +``` + --annotations strings Annotations in key=value format (can be specified multiple times) + --description string Description of the backup + -h, --help help for create + --labels strings Labels in key=value format (can be specified multiple times) + --name string Name of the backup (required) + --no-header Do not print the header + --retention-policy string Retention policy for the backup + --wait Wait for the backup to be completed before returning +``` + +### Options inherited from parent commands + +``` + --access-token string Access Token authentication (overrides context) + --api string API endpoint (overrides context) + --client-id string OIDC client ID for OIDC authentication (overrides context) + --client-secret string OIDC client secret for OIDC authentication (overrides context) + -c, --context string Context name + --debug Debug mode + -O, --organisation string Organisation slug or identity (overrides context) + --token string Personal access token (overrides context) +``` + +### SEE ALSO + +* [tcloud dbaas backup](/docs/tcloud/dbaas/backup/) - Manage database backups + diff --git a/docs/tcloud/dbaas/backup_delete/_index.md b/docs/tcloud/dbaas/backup_delete/_index.md new file mode 100644 index 0000000..0934dcd --- /dev/null +++ b/docs/tcloud/dbaas/backup_delete/_index.md @@ -0,0 +1,47 @@ +--- +linkTitle: "tcloud dbaas backup delete" +title: "dbaas backup delete" +slug: tcloud_dbaas_backup_delete +url: /docs/tcloud/dbaas/backup_delete/ +weight: 9974 +cascade: + type: docs +--- +## tcloud dbaas backup delete + +Delete database backup(s) + +### Synopsis + +Delete database backup(s) by identity, label selector, or all failed backups + +``` +tcloud dbaas backup delete [flags] +``` + +### Options + +``` + --all-failed Delete all failed backups + --force Force the deletion and skip the confirmation + -h, --help help for delete + -l, --selector string Label selector to filter backups (format: key1=value1,key2=value2) +``` + +### Options inherited from parent commands + +``` + --access-token string Access Token authentication (overrides context) + --api string API endpoint (overrides context) + --client-id string OIDC client ID for OIDC authentication (overrides context) + --client-secret string OIDC client secret for OIDC authentication (overrides context) + -c, --context string Context name + --debug Debug mode + -O, --organisation string Organisation slug or identity (overrides context) + --token string Personal access token (overrides context) +``` + +### SEE ALSO + +* [tcloud dbaas backup](/docs/tcloud/dbaas/backup/) - Manage database backups + diff --git a/docs/tcloud/dbaas/backup_list/_index.md b/docs/tcloud/dbaas/backup_list/_index.md new file mode 100644 index 0000000..068ec66 --- /dev/null +++ b/docs/tcloud/dbaas/backup_list/_index.md @@ -0,0 +1,52 @@ +--- +linkTitle: "tcloud dbaas backup list" +title: "dbaas backup list" +slug: tcloud_dbaas_backup_list +url: /docs/tcloud/dbaas/backup_list/ +weight: 9973 +cascade: + type: docs +--- +## tcloud dbaas backup list + +List database backups + +### Synopsis + +List database backups for a specific cluster or all backups in the organisation + +``` +tcloud dbaas backup list [flags] +``` + +### Options + +``` + --cluster string Filter by database cluster identity, slug, or name + --exact-time Show exact time instead of relative time + -h, --help help for list + --newer-than string Filter backups newer than the specified duration (e.g., 7d, 1w, 1mo, 1y, 24h) + --no-header Do not print the header + --older-than string Filter backups older than the specified duration (e.g., 30d, 1w, 1mo, 1y, 24h) + -l, --selector string Label selector to filter backups (format: key1=value1,key2=value2) + --show-labels Show labels + --status strings Filter by backup status (can be specified multiple times, e.g., --status ready --status failed) +``` + +### Options inherited from parent commands + +``` + --access-token string Access Token authentication (overrides context) + --api string API endpoint (overrides context) + --client-id string OIDC client ID for OIDC authentication (overrides context) + --client-secret string OIDC client secret for OIDC authentication (overrides context) + -c, --context string Context name + --debug Debug mode + -O, --organisation string Organisation slug or identity (overrides context) + --token string Personal access token (overrides context) +``` + +### SEE ALSO + +* [tcloud dbaas backup](/docs/tcloud/dbaas/backup/) - Manage database backups + diff --git a/docs/tcloud/dbaas/backup_view/_index.md b/docs/tcloud/dbaas/backup_view/_index.md new file mode 100644 index 0000000..d9e6fe8 --- /dev/null +++ b/docs/tcloud/dbaas/backup_view/_index.md @@ -0,0 +1,46 @@ +--- +linkTitle: "tcloud dbaas backup view" +title: "dbaas backup view" +slug: tcloud_dbaas_backup_view +url: /docs/tcloud/dbaas/backup_view/ +weight: 9972 +cascade: + type: docs +--- +## tcloud dbaas backup view + +View backup details + +### Synopsis + +View detailed information about a database backup + +``` +tcloud dbaas backup view [flags] +``` + +### Options + +``` + --exact-time Show exact time instead of relative time + -h, --help help for view + --no-header Do not print the header +``` + +### Options inherited from parent commands + +``` + --access-token string Access Token authentication (overrides context) + --api string API endpoint (overrides context) + --client-id string OIDC client ID for OIDC authentication (overrides context) + --client-secret string OIDC client secret for OIDC authentication (overrides context) + -c, --context string Context name + --debug Debug mode + -O, --organisation string Organisation slug or identity (overrides context) + --token string Personal access token (overrides context) +``` + +### SEE ALSO + +* [tcloud dbaas backup](/docs/tcloud/dbaas/backup/) - Manage database backups + diff --git a/docs/tcloud/dbaas/create/_index.md b/docs/tcloud/dbaas/create/_index.md new file mode 100644 index 0000000..23de1b2 --- /dev/null +++ b/docs/tcloud/dbaas/create/_index.md @@ -0,0 +1,61 @@ +--- +linkTitle: "tcloud dbaas create" +title: "dbaas create" +slug: tcloud_dbaas_create +url: /docs/tcloud/dbaas/create/ +weight: 9964 +cascade: + type: docs +--- +## tcloud dbaas create + +Create a database cluster + +### Synopsis + +Create a new database cluster in the Thalassa Cloud Platform. + +``` +tcloud dbaas create [flags] +``` + +### Options + +``` + --annotations strings Annotations in key=value format (can be specified multiple times) + --backup-object-storage-id string Backup object storage ID (enables backup storage, requires --with-backup-bucket=false) + --delete-protection Enable delete protection + --description string Description of the database cluster + --engine string Database engine (e.g., postgres) (required) + --engine-version string Engine version (required) + -h, --help help for create + --instance-type string Instance type (required) + --labels strings Labels in key=value format (can be specified multiple times) + --name string Name of the database cluster (required) + --no-header Do not print the header + --replicas int Number of replicas (default: 0) + --storage int Storage size in GB (required) + --subnet string Subnet identity, slug, or name (required) + --volume-type string Volume type (default "block") + --vpc string VPC identity, slug, or name + --wait Wait for the database cluster to be available before returning + --with-backup-bucket Provision a backup object storage bucket for the database cluster +``` + +### Options inherited from parent commands + +``` + --access-token string Access Token authentication (overrides context) + --api string API endpoint (overrides context) + --client-id string OIDC client ID for OIDC authentication (overrides context) + --client-secret string OIDC client secret for OIDC authentication (overrides context) + -c, --context string Context name + --debug Debug mode + -O, --organisation string Organisation slug or identity (overrides context) + --token string Personal access token (overrides context) +``` + +### SEE ALSO + +* [tcloud dbaas](/docs/tcloud/tcloud_dbaas/) - Manage database clusters and related services + diff --git a/docs/tcloud/dbaas/delete/_index.md b/docs/tcloud/dbaas/delete/_index.md new file mode 100644 index 0000000..c50ad71 --- /dev/null +++ b/docs/tcloud/dbaas/delete/_index.md @@ -0,0 +1,47 @@ +--- +linkTitle: "tcloud dbaas delete" +title: "dbaas delete" +slug: tcloud_dbaas_delete +url: /docs/tcloud/dbaas/delete/ +weight: 9963 +cascade: + type: docs +--- +## tcloud dbaas delete + +Delete database cluster(s) + +### Synopsis + +Delete database cluster(s) by identity or label selector. + +``` +tcloud dbaas delete [flags] +``` + +### Options + +``` + --force Force the deletion and skip the confirmation + -h, --help help for delete + -l, --selector string Label selector to filter clusters (format: key1=value1,key2=value2) + --wait Wait for the database cluster(s) to be deleted +``` + +### Options inherited from parent commands + +``` + --access-token string Access Token authentication (overrides context) + --api string API endpoint (overrides context) + --client-id string OIDC client ID for OIDC authentication (overrides context) + --client-secret string OIDC client secret for OIDC authentication (overrides context) + -c, --context string Context name + --debug Debug mode + -O, --organisation string Organisation slug or identity (overrides context) + --token string Personal access token (overrides context) +``` + +### SEE ALSO + +* [tcloud dbaas](/docs/tcloud/tcloud_dbaas/) - Manage database clusters and related services + diff --git a/docs/tcloud/dbaas/instance-types/_index.md b/docs/tcloud/dbaas/instance-types/_index.md index 32fa496..0897b88 100644 --- a/docs/tcloud/dbaas/instance-types/_index.md +++ b/docs/tcloud/dbaas/instance-types/_index.md @@ -3,7 +3,7 @@ linkTitle: "tcloud dbaas instance-types" title: "dbaas instance-types" slug: tcloud_dbaas_instance-types url: /docs/tcloud/dbaas/instance-types/ -weight: 9978 +weight: 9962 cascade: type: docs --- diff --git a/docs/tcloud/dbaas/list/_index.md b/docs/tcloud/dbaas/list/_index.md index 6d50de8..1f7d62e 100644 --- a/docs/tcloud/dbaas/list/_index.md +++ b/docs/tcloud/dbaas/list/_index.md @@ -3,7 +3,7 @@ linkTitle: "tcloud dbaas list" title: "dbaas list" slug: tcloud_dbaas_list url: /docs/tcloud/dbaas/list/ -weight: 9977 +weight: 9961 cascade: type: docs --- @@ -30,9 +30,14 @@ tcloud dbaas list --exact-time ### Options ``` - --exact-time Show exact time instead of relative time - -h, --help help for list - --no-header Do not print the header + --engine string Filter by database engine (e.g., postgres) + --exact-time Show exact time instead of relative time + -h, --help help for list + --no-header Do not print the header + -l, --selector string Label selector to filter clusters (format: key1=value1,key2=value2) + --show-labels Show labels + --subnet string Filter by subnet identity, slug, or name + --vpc string Filter by VPC identity, slug, or name ``` ### Options inherited from parent commands diff --git a/docs/tcloud/dbaas/update/_index.md b/docs/tcloud/dbaas/update/_index.md new file mode 100644 index 0000000..a1b9993 --- /dev/null +++ b/docs/tcloud/dbaas/update/_index.md @@ -0,0 +1,53 @@ +--- +linkTitle: "tcloud dbaas update" +title: "dbaas update" +slug: tcloud_dbaas_update +url: /docs/tcloud/dbaas/update/ +weight: 9960 +cascade: + type: docs +--- +## tcloud dbaas update + +Update a database cluster + +### Synopsis + +Update properties of an existing database cluster. + +``` +tcloud dbaas update [flags] +``` + +### Options + +``` + --annotations strings Annotations in key=value format (can be specified multiple times) + --delete-protection Enable or disable delete protection + --description string Description of the database cluster + -h, --help help for update + --instance-type string Instance type + --labels strings Labels in key=value format (can be specified multiple times) + --name string Name of the database cluster + --no-header Do not print the header + --replicas int Number of replicas (default -1) + --storage int Storage size in GB +``` + +### Options inherited from parent commands + +``` + --access-token string Access Token authentication (overrides context) + --api string API endpoint (overrides context) + --client-id string OIDC client ID for OIDC authentication (overrides context) + --client-secret string OIDC client secret for OIDC authentication (overrides context) + -c, --context string Context name + --debug Debug mode + -O, --organisation string Organisation slug or identity (overrides context) + --token string Personal access token (overrides context) +``` + +### SEE ALSO + +* [tcloud dbaas](/docs/tcloud/tcloud_dbaas/) - Manage database clusters and related services + diff --git a/docs/tcloud/dbaas/versions/_index.md b/docs/tcloud/dbaas/versions/_index.md index d728d28..6158f6d 100644 --- a/docs/tcloud/dbaas/versions/_index.md +++ b/docs/tcloud/dbaas/versions/_index.md @@ -3,7 +3,7 @@ linkTitle: "tcloud dbaas versions" title: "dbaas versions" slug: tcloud_dbaas_versions url: /docs/tcloud/dbaas/versions/ -weight: 9976 +weight: 9959 cascade: type: docs --- diff --git a/docs/tcloud/dbaas/view/_index.md b/docs/tcloud/dbaas/view/_index.md new file mode 100644 index 0000000..8fe156a --- /dev/null +++ b/docs/tcloud/dbaas/view/_index.md @@ -0,0 +1,46 @@ +--- +linkTitle: "tcloud dbaas view" +title: "dbaas view" +slug: tcloud_dbaas_view +url: /docs/tcloud/dbaas/view/ +weight: 9958 +cascade: + type: docs +--- +## tcloud dbaas view + +View database cluster details + +### Synopsis + +View detailed information about a database cluster. + +``` +tcloud dbaas view [flags] +``` + +### Options + +``` + --exact-time Show exact time instead of relative time + -h, --help help for view + --no-header Do not print the header +``` + +### Options inherited from parent commands + +``` + --access-token string Access Token authentication (overrides context) + --api string API endpoint (overrides context) + --client-id string OIDC client ID for OIDC authentication (overrides context) + --client-secret string OIDC client secret for OIDC authentication (overrides context) + -c, --context string Context name + --debug Debug mode + -O, --organisation string Organisation slug or identity (overrides context) + --token string Personal access token (overrides context) +``` + +### SEE ALSO + +* [tcloud dbaas](/docs/tcloud/tcloud_dbaas/) - Manage database clusters and related services + diff --git a/docs/tcloud/iam/_index.md b/docs/tcloud/iam/_index.md new file mode 100644 index 0000000..1ace906 --- /dev/null +++ b/docs/tcloud/iam/_index.md @@ -0,0 +1,50 @@ +--- +linkTitle: "tcloud iam" +title: "iam" +slug: tcloud_iam +url: /docs/tcloud/tcloud_iam/ +weight: 9905 +cascade: + type: docs +--- +## tcloud iam + +Identity and access management for your organisation + +### Synopsis + +Manage teams, organisation members, custom roles, federated OIDC identities, +and related resources. Commands apply to the organisation selected in your context +(or the --organisation / -O flag). + +### Options + +``` + -h, --help help for iam +``` + +### Options inherited from parent commands + +``` + --access-token string Access Token authentication (overrides context) + --api string API endpoint (overrides context) + --client-id string OIDC client ID for OIDC authentication (overrides context) + --client-secret string OIDC client secret for OIDC authentication (overrides context) + -c, --context string Context name + --debug Debug mode + -O, --organisation string Organisation slug or identity (overrides context) + --token string Personal access token (overrides context) +``` + +### SEE ALSO + +* [tcloud](/docs/tcloud/tcloud/) - A CLI for working with the Thalassa Cloud Platform +* [tcloud iam federated-identities](/docs/tcloud/iam/federated-identities/) - Federated identities (OIDC subject bindings) +* [tcloud iam federated-identity-providers](/docs/tcloud/iam/federated-identity-providers/) - Federated OIDC identity providers +* [tcloud iam invites](/docs/tcloud/iam/invites/) - Organisation member invitations +* [tcloud iam members](/docs/tcloud/iam/members/) - Organisation members (owners and members) +* [tcloud iam roles](/docs/tcloud/iam/roles/) - Organisation IAM roles, permission rules, and bindings +* [tcloud iam service-accounts](/docs/tcloud/iam/service-accounts/) - Organisation service accounts +* [tcloud iam teams](/docs/tcloud/iam/teams/) - Manage organisation teams +* [tcloud iam workload-identity-federation](/docs/tcloud/iam/workload-identity-federation/) - Bootstrap and manage CI/CD workload identity (OIDC) + diff --git a/docs/tcloud/iam/federated-identities/_index.md b/docs/tcloud/iam/federated-identities/_index.md new file mode 100644 index 0000000..2a4fa48 --- /dev/null +++ b/docs/tcloud/iam/federated-identities/_index.md @@ -0,0 +1,41 @@ +--- +linkTitle: "tcloud iam federated-identities" +title: "iam federated-identities" +slug: tcloud_iam_federated-identities +url: /docs/tcloud/iam/federated-identities/ +weight: 9951 +cascade: + type: docs +--- +## tcloud iam federated-identities + +Federated identities (OIDC subject bindings) + +### Options + +``` + -h, --help help for federated-identities +``` + +### Options inherited from parent commands + +``` + --access-token string Access Token authentication (overrides context) + --api string API endpoint (overrides context) + --client-id string OIDC client ID for OIDC authentication (overrides context) + --client-secret string OIDC client secret for OIDC authentication (overrides context) + -c, --context string Context name + --debug Debug mode + -O, --organisation string Organisation slug or identity (overrides context) + --token string Personal access token (overrides context) +``` + +### SEE ALSO + +* [tcloud iam](/docs/tcloud/tcloud_iam/) - Identity and access management for your organisation +* [tcloud iam federated-identities create](/docs/tcloud/iam/federated-identities_create/) - Create a federated identity +* [tcloud iam federated-identities delete](/docs/tcloud/iam/federated-identities_delete/) - Delete a federated identity +* [tcloud iam federated-identities get](/docs/tcloud/iam/federated-identities_get/) - Show a federated identity +* [tcloud iam federated-identities list](/docs/tcloud/iam/federated-identities_list/) - List federated identities +* [tcloud iam federated-identities update](/docs/tcloud/iam/federated-identities_update/) - Update a federated identity (only set flags are sent) + diff --git a/docs/tcloud/iam/federated-identities_create/_index.md b/docs/tcloud/iam/federated-identities_create/_index.md new file mode 100644 index 0000000..ba60eef --- /dev/null +++ b/docs/tcloud/iam/federated-identities_create/_index.md @@ -0,0 +1,54 @@ +--- +linkTitle: "tcloud iam federated-identities create" +title: "iam federated-identities create" +slug: tcloud_iam_federated-identities_create +url: /docs/tcloud/iam/federated-identities_create/ +weight: 9956 +cascade: + type: docs +--- +## tcloud iam federated-identities create + +Create a federated identity + +``` +tcloud iam federated-identities create [flags] +``` + +### Options + +``` + --annotations strings Annotations as key=value (repeatable) + --audience-match-mode string exact, any (default), or all + --conditions string Conditions as JSON object + --conditions-file string Path to JSON file for conditions + --description string Description + --expires-at string RFC3339 expiry time + -h, --help help for create + --labels strings Labels as key=value (repeatable) + --name string Display name + --no-header Do not print table headers + --provider string Federated identity provider identity + --scope strings Allowed scopes: api:read, api:write, kubernetes, objectStorage (repeatable) + --service-account string Service account identity to bind + --subject string OIDC sub claim for this identity + --trusted-audience strings Trusted JWT audiences (repeatable) +``` + +### Options inherited from parent commands + +``` + --access-token string Access Token authentication (overrides context) + --api string API endpoint (overrides context) + --client-id string OIDC client ID for OIDC authentication (overrides context) + --client-secret string OIDC client secret for OIDC authentication (overrides context) + -c, --context string Context name + --debug Debug mode + -O, --organisation string Organisation slug or identity (overrides context) + --token string Personal access token (overrides context) +``` + +### SEE ALSO + +* [tcloud iam federated-identities](/docs/tcloud/iam/federated-identities/) - Federated identities (OIDC subject bindings) + diff --git a/docs/tcloud/iam/federated-identities_delete/_index.md b/docs/tcloud/iam/federated-identities_delete/_index.md new file mode 100644 index 0000000..84d026d --- /dev/null +++ b/docs/tcloud/iam/federated-identities_delete/_index.md @@ -0,0 +1,42 @@ +--- +linkTitle: "tcloud iam federated-identities delete" +title: "iam federated-identities delete" +slug: tcloud_iam_federated-identities_delete +url: /docs/tcloud/iam/federated-identities_delete/ +weight: 9955 +cascade: + type: docs +--- +## tcloud iam federated-identities delete + +Delete a federated identity + +``` +tcloud iam federated-identities delete [flags] +``` + +### Options + +``` + --force Skip the confirmation prompt and delete + -h, --help help for delete + --no-header Do not print table headers +``` + +### Options inherited from parent commands + +``` + --access-token string Access Token authentication (overrides context) + --api string API endpoint (overrides context) + --client-id string OIDC client ID for OIDC authentication (overrides context) + --client-secret string OIDC client secret for OIDC authentication (overrides context) + -c, --context string Context name + --debug Debug mode + -O, --organisation string Organisation slug or identity (overrides context) + --token string Personal access token (overrides context) +``` + +### SEE ALSO + +* [tcloud iam federated-identities](/docs/tcloud/iam/federated-identities/) - Federated identities (OIDC subject bindings) + diff --git a/docs/tcloud/iam/federated-identities_get/_index.md b/docs/tcloud/iam/federated-identities_get/_index.md new file mode 100644 index 0000000..176e67a --- /dev/null +++ b/docs/tcloud/iam/federated-identities_get/_index.md @@ -0,0 +1,42 @@ +--- +linkTitle: "tcloud iam federated-identities get" +title: "iam federated-identities get" +slug: tcloud_iam_federated-identities_get +url: /docs/tcloud/iam/federated-identities_get/ +weight: 9954 +cascade: + type: docs +--- +## tcloud iam federated-identities get + +Show a federated identity + +``` +tcloud iam federated-identities get [flags] +``` + +### Options + +``` + --exact-time Show full timestamps instead of relative time + -h, --help help for get + --no-header Do not print table headers +``` + +### Options inherited from parent commands + +``` + --access-token string Access Token authentication (overrides context) + --api string API endpoint (overrides context) + --client-id string OIDC client ID for OIDC authentication (overrides context) + --client-secret string OIDC client secret for OIDC authentication (overrides context) + -c, --context string Context name + --debug Debug mode + -O, --organisation string Organisation slug or identity (overrides context) + --token string Personal access token (overrides context) +``` + +### SEE ALSO + +* [tcloud iam federated-identities](/docs/tcloud/iam/federated-identities/) - Federated identities (OIDC subject bindings) + diff --git a/docs/tcloud/iam/federated-identities_list/_index.md b/docs/tcloud/iam/federated-identities_list/_index.md new file mode 100644 index 0000000..8b16e90 --- /dev/null +++ b/docs/tcloud/iam/federated-identities_list/_index.md @@ -0,0 +1,43 @@ +--- +linkTitle: "tcloud iam federated-identities list" +title: "iam federated-identities list" +slug: tcloud_iam_federated-identities_list +url: /docs/tcloud/iam/federated-identities_list/ +weight: 9953 +cascade: + type: docs +--- +## tcloud iam federated-identities list + +List federated identities + +``` +tcloud iam federated-identities list [flags] +``` + +### Options + +``` + --exact-time Show full timestamps instead of relative time + -h, --help help for list + --label-selector string Filter by labels (key=value,key2=value2) + --no-header Do not print table headers +``` + +### Options inherited from parent commands + +``` + --access-token string Access Token authentication (overrides context) + --api string API endpoint (overrides context) + --client-id string OIDC client ID for OIDC authentication (overrides context) + --client-secret string OIDC client secret for OIDC authentication (overrides context) + -c, --context string Context name + --debug Debug mode + -O, --organisation string Organisation slug or identity (overrides context) + --token string Personal access token (overrides context) +``` + +### SEE ALSO + +* [tcloud iam federated-identities](/docs/tcloud/iam/federated-identities/) - Federated identities (OIDC subject bindings) + diff --git a/docs/tcloud/iam/federated-identities_update/_index.md b/docs/tcloud/iam/federated-identities_update/_index.md new file mode 100644 index 0000000..d477a0e --- /dev/null +++ b/docs/tcloud/iam/federated-identities_update/_index.md @@ -0,0 +1,51 @@ +--- +linkTitle: "tcloud iam federated-identities update" +title: "iam federated-identities update" +slug: tcloud_iam_federated-identities_update +url: /docs/tcloud/iam/federated-identities_update/ +weight: 9952 +cascade: + type: docs +--- +## tcloud iam federated-identities update + +Update a federated identity (only set flags are sent) + +``` +tcloud iam federated-identities update [flags] +``` + +### Options + +``` + --annotations strings Replace annotations (key=value, repeatable) + --audience-match-mode string exact, any, or all + --conditions string Conditions JSON object + --conditions-file string Path to JSON file for conditions + --description string Description + --expires-at string RFC3339 expiry (empty to clear not supported by all APIs) + -h, --help help for update + --labels strings Replace labels (key=value, repeatable) + --no-header Do not print table headers + --scope strings Replace allowed scopes (repeatable) + --status string active, inactive, expired, or revoked + --trusted-audience strings Replace trusted audiences (repeatable) +``` + +### Options inherited from parent commands + +``` + --access-token string Access Token authentication (overrides context) + --api string API endpoint (overrides context) + --client-id string OIDC client ID for OIDC authentication (overrides context) + --client-secret string OIDC client secret for OIDC authentication (overrides context) + -c, --context string Context name + --debug Debug mode + -O, --organisation string Organisation slug or identity (overrides context) + --token string Personal access token (overrides context) +``` + +### SEE ALSO + +* [tcloud iam federated-identities](/docs/tcloud/iam/federated-identities/) - Federated identities (OIDC subject bindings) + diff --git a/docs/tcloud/iam/federated-identity-providers/_index.md b/docs/tcloud/iam/federated-identity-providers/_index.md new file mode 100644 index 0000000..2ecaab0 --- /dev/null +++ b/docs/tcloud/iam/federated-identity-providers/_index.md @@ -0,0 +1,41 @@ +--- +linkTitle: "tcloud iam federated-identity-providers" +title: "iam federated-identity-providers" +slug: tcloud_iam_federated-identity-providers +url: /docs/tcloud/iam/federated-identity-providers/ +weight: 9945 +cascade: + type: docs +--- +## tcloud iam federated-identity-providers + +Federated OIDC identity providers + +### Options + +``` + -h, --help help for federated-identity-providers +``` + +### Options inherited from parent commands + +``` + --access-token string Access Token authentication (overrides context) + --api string API endpoint (overrides context) + --client-id string OIDC client ID for OIDC authentication (overrides context) + --client-secret string OIDC client secret for OIDC authentication (overrides context) + -c, --context string Context name + --debug Debug mode + -O, --organisation string Organisation slug or identity (overrides context) + --token string Personal access token (overrides context) +``` + +### SEE ALSO + +* [tcloud iam](/docs/tcloud/tcloud_iam/) - Identity and access management for your organisation +* [tcloud iam federated-identity-providers create](/docs/tcloud/iam/federated-identity-providers_create/) - Register a federated identity provider +* [tcloud iam federated-identity-providers delete](/docs/tcloud/iam/federated-identity-providers_delete/) - Delete a federated identity provider +* [tcloud iam federated-identity-providers get](/docs/tcloud/iam/federated-identity-providers_get/) - Show a federated identity provider +* [tcloud iam federated-identity-providers list](/docs/tcloud/iam/federated-identity-providers_list/) - List federated identity providers +* [tcloud iam federated-identity-providers update](/docs/tcloud/iam/federated-identity-providers_update/) - Update a federated identity provider + diff --git a/docs/tcloud/iam/federated-identity-providers_create/_index.md b/docs/tcloud/iam/federated-identity-providers_create/_index.md new file mode 100644 index 0000000..a7bac43 --- /dev/null +++ b/docs/tcloud/iam/federated-identity-providers_create/_index.md @@ -0,0 +1,48 @@ +--- +linkTitle: "tcloud iam federated-identity-providers create" +title: "iam federated-identity-providers create" +slug: tcloud_iam_federated-identity-providers_create +url: /docs/tcloud/iam/federated-identity-providers_create/ +weight: 9950 +cascade: + type: docs +--- +## tcloud iam federated-identity-providers create + +Register a federated identity provider + +``` +tcloud iam federated-identity-providers create [flags] +``` + +### Options + +``` + --annotations strings Annotations as key=value (repeatable) + --description string Description + -h, --help help for create + --issuer string OIDC issuer URL (unique per organisation) + --jwks-uri string Optional JWKS URI override + --labels strings Labels as key=value (repeatable) + --name string Provider name + --no-header Do not print table headers + --status string active (default) or inactive +``` + +### Options inherited from parent commands + +``` + --access-token string Access Token authentication (overrides context) + --api string API endpoint (overrides context) + --client-id string OIDC client ID for OIDC authentication (overrides context) + --client-secret string OIDC client secret for OIDC authentication (overrides context) + -c, --context string Context name + --debug Debug mode + -O, --organisation string Organisation slug or identity (overrides context) + --token string Personal access token (overrides context) +``` + +### SEE ALSO + +* [tcloud iam federated-identity-providers](/docs/tcloud/iam/federated-identity-providers/) - Federated OIDC identity providers + diff --git a/docs/tcloud/iam/federated-identity-providers_delete/_index.md b/docs/tcloud/iam/federated-identity-providers_delete/_index.md new file mode 100644 index 0000000..6b804e4 --- /dev/null +++ b/docs/tcloud/iam/federated-identity-providers_delete/_index.md @@ -0,0 +1,42 @@ +--- +linkTitle: "tcloud iam federated-identity-providers delete" +title: "iam federated-identity-providers delete" +slug: tcloud_iam_federated-identity-providers_delete +url: /docs/tcloud/iam/federated-identity-providers_delete/ +weight: 9949 +cascade: + type: docs +--- +## tcloud iam federated-identity-providers delete + +Delete a federated identity provider + +``` +tcloud iam federated-identity-providers delete [flags] +``` + +### Options + +``` + --force Skip the confirmation prompt and delete + -h, --help help for delete + --no-header Do not print table headers +``` + +### Options inherited from parent commands + +``` + --access-token string Access Token authentication (overrides context) + --api string API endpoint (overrides context) + --client-id string OIDC client ID for OIDC authentication (overrides context) + --client-secret string OIDC client secret for OIDC authentication (overrides context) + -c, --context string Context name + --debug Debug mode + -O, --organisation string Organisation slug or identity (overrides context) + --token string Personal access token (overrides context) +``` + +### SEE ALSO + +* [tcloud iam federated-identity-providers](/docs/tcloud/iam/federated-identity-providers/) - Federated OIDC identity providers + diff --git a/docs/tcloud/iam/federated-identity-providers_get/_index.md b/docs/tcloud/iam/federated-identity-providers_get/_index.md new file mode 100644 index 0000000..ce29064 --- /dev/null +++ b/docs/tcloud/iam/federated-identity-providers_get/_index.md @@ -0,0 +1,42 @@ +--- +linkTitle: "tcloud iam federated-identity-providers get" +title: "iam federated-identity-providers get" +slug: tcloud_iam_federated-identity-providers_get +url: /docs/tcloud/iam/federated-identity-providers_get/ +weight: 9948 +cascade: + type: docs +--- +## tcloud iam federated-identity-providers get + +Show a federated identity provider + +``` +tcloud iam federated-identity-providers get [flags] +``` + +### Options + +``` + --exact-time Show full timestamps instead of relative time + -h, --help help for get + --no-header Do not print table headers +``` + +### Options inherited from parent commands + +``` + --access-token string Access Token authentication (overrides context) + --api string API endpoint (overrides context) + --client-id string OIDC client ID for OIDC authentication (overrides context) + --client-secret string OIDC client secret for OIDC authentication (overrides context) + -c, --context string Context name + --debug Debug mode + -O, --organisation string Organisation slug or identity (overrides context) + --token string Personal access token (overrides context) +``` + +### SEE ALSO + +* [tcloud iam federated-identity-providers](/docs/tcloud/iam/federated-identity-providers/) - Federated OIDC identity providers + diff --git a/docs/tcloud/iam/federated-identity-providers_list/_index.md b/docs/tcloud/iam/federated-identity-providers_list/_index.md new file mode 100644 index 0000000..5d46f81 --- /dev/null +++ b/docs/tcloud/iam/federated-identity-providers_list/_index.md @@ -0,0 +1,43 @@ +--- +linkTitle: "tcloud iam federated-identity-providers list" +title: "iam federated-identity-providers list" +slug: tcloud_iam_federated-identity-providers_list +url: /docs/tcloud/iam/federated-identity-providers_list/ +weight: 9947 +cascade: + type: docs +--- +## tcloud iam federated-identity-providers list + +List federated identity providers + +``` +tcloud iam federated-identity-providers list [flags] +``` + +### Options + +``` + --exact-time Show full timestamps instead of relative time + -h, --help help for list + --label-selector string Filter by labels (key=value,key2=value2) + --no-header Do not print table headers +``` + +### Options inherited from parent commands + +``` + --access-token string Access Token authentication (overrides context) + --api string API endpoint (overrides context) + --client-id string OIDC client ID for OIDC authentication (overrides context) + --client-secret string OIDC client secret for OIDC authentication (overrides context) + -c, --context string Context name + --debug Debug mode + -O, --organisation string Organisation slug or identity (overrides context) + --token string Personal access token (overrides context) +``` + +### SEE ALSO + +* [tcloud iam federated-identity-providers](/docs/tcloud/iam/federated-identity-providers/) - Federated OIDC identity providers + diff --git a/docs/tcloud/iam/federated-identity-providers_update/_index.md b/docs/tcloud/iam/federated-identity-providers_update/_index.md new file mode 100644 index 0000000..0d3f0e9 --- /dev/null +++ b/docs/tcloud/iam/federated-identity-providers_update/_index.md @@ -0,0 +1,47 @@ +--- +linkTitle: "tcloud iam federated-identity-providers update" +title: "iam federated-identity-providers update" +slug: tcloud_iam_federated-identity-providers_update +url: /docs/tcloud/iam/federated-identity-providers_update/ +weight: 9946 +cascade: + type: docs +--- +## tcloud iam federated-identity-providers update + +Update a federated identity provider + +``` +tcloud iam federated-identity-providers update [flags] +``` + +### Options + +``` + --annotations strings Replace annotations (key=value, repeatable) + --description string Description + -h, --help help for update + --jwks-uri string JWKS URI (set to empty string to clear) + --labels strings Replace labels (key=value, repeatable) + --name string Provider display name + --no-header Do not print table headers + --status string active or inactive +``` + +### Options inherited from parent commands + +``` + --access-token string Access Token authentication (overrides context) + --api string API endpoint (overrides context) + --client-id string OIDC client ID for OIDC authentication (overrides context) + --client-secret string OIDC client secret for OIDC authentication (overrides context) + -c, --context string Context name + --debug Debug mode + -O, --organisation string Organisation slug or identity (overrides context) + --token string Personal access token (overrides context) +``` + +### SEE ALSO + +* [tcloud iam federated-identity-providers](/docs/tcloud/iam/federated-identity-providers/) - Federated OIDC identity providers + diff --git a/docs/tcloud/iam/invites/_index.md b/docs/tcloud/iam/invites/_index.md new file mode 100644 index 0000000..95a17aa --- /dev/null +++ b/docs/tcloud/iam/invites/_index.md @@ -0,0 +1,37 @@ +--- +linkTitle: "tcloud iam invites" +title: "iam invites" +slug: tcloud_iam_invites +url: /docs/tcloud/iam/invites/ +weight: 9943 +cascade: + type: docs +--- +## tcloud iam invites + +Organisation member invitations + +### Options + +``` + -h, --help help for invites +``` + +### Options inherited from parent commands + +``` + --access-token string Access Token authentication (overrides context) + --api string API endpoint (overrides context) + --client-id string OIDC client ID for OIDC authentication (overrides context) + --client-secret string OIDC client secret for OIDC authentication (overrides context) + -c, --context string Context name + --debug Debug mode + -O, --organisation string Organisation slug or identity (overrides context) + --token string Personal access token (overrides context) +``` + +### SEE ALSO + +* [tcloud iam](/docs/tcloud/tcloud_iam/) - Identity and access management for your organisation +* [tcloud iam invites list](/docs/tcloud/iam/invites_list/) - List pending organisation invites + diff --git a/docs/tcloud/iam/invites_list/_index.md b/docs/tcloud/iam/invites_list/_index.md new file mode 100644 index 0000000..9bf3e02 --- /dev/null +++ b/docs/tcloud/iam/invites_list/_index.md @@ -0,0 +1,42 @@ +--- +linkTitle: "tcloud iam invites list" +title: "iam invites list" +slug: tcloud_iam_invites_list +url: /docs/tcloud/iam/invites_list/ +weight: 9944 +cascade: + type: docs +--- +## tcloud iam invites list + +List pending organisation invites + +``` +tcloud iam invites list [flags] +``` + +### Options + +``` + --exact-time Show full timestamps instead of relative time + -h, --help help for list + --no-header Do not print table headers +``` + +### Options inherited from parent commands + +``` + --access-token string Access Token authentication (overrides context) + --api string API endpoint (overrides context) + --client-id string OIDC client ID for OIDC authentication (overrides context) + --client-secret string OIDC client secret for OIDC authentication (overrides context) + -c, --context string Context name + --debug Debug mode + -O, --organisation string Organisation slug or identity (overrides context) + --token string Personal access token (overrides context) +``` + +### SEE ALSO + +* [tcloud iam invites](/docs/tcloud/iam/invites/) - Organisation member invitations + diff --git a/docs/tcloud/iam/members/_index.md b/docs/tcloud/iam/members/_index.md new file mode 100644 index 0000000..57a3452 --- /dev/null +++ b/docs/tcloud/iam/members/_index.md @@ -0,0 +1,39 @@ +--- +linkTitle: "tcloud iam members" +title: "iam members" +slug: tcloud_iam_members +url: /docs/tcloud/iam/members/ +weight: 9939 +cascade: + type: docs +--- +## tcloud iam members + +Organisation members (owners and members) + +### Options + +``` + -h, --help help for members +``` + +### Options inherited from parent commands + +``` + --access-token string Access Token authentication (overrides context) + --api string API endpoint (overrides context) + --client-id string OIDC client ID for OIDC authentication (overrides context) + --client-secret string OIDC client secret for OIDC authentication (overrides context) + -c, --context string Context name + --debug Debug mode + -O, --organisation string Organisation slug or identity (overrides context) + --token string Personal access token (overrides context) +``` + +### SEE ALSO + +* [tcloud iam](/docs/tcloud/tcloud_iam/) - Identity and access management for your organisation +* [tcloud iam members delete](/docs/tcloud/iam/members_delete/) - Remove a member from the organisation +* [tcloud iam members list](/docs/tcloud/iam/members_list/) - List organisation members +* [tcloud iam members update](/docs/tcloud/iam/members_update/) - Change an organisation member's role (OWNER or MEMBER) + diff --git a/docs/tcloud/iam/members_delete/_index.md b/docs/tcloud/iam/members_delete/_index.md new file mode 100644 index 0000000..e5e2a81 --- /dev/null +++ b/docs/tcloud/iam/members_delete/_index.md @@ -0,0 +1,42 @@ +--- +linkTitle: "tcloud iam members delete" +title: "iam members delete" +slug: tcloud_iam_members_delete +url: /docs/tcloud/iam/members_delete/ +weight: 9942 +cascade: + type: docs +--- +## tcloud iam members delete + +Remove a member from the organisation + +``` +tcloud iam members delete [flags] +``` + +### Options + +``` + --force Skip the confirmation prompt and remove the member + -h, --help help for delete + --no-header Do not print table headers +``` + +### Options inherited from parent commands + +``` + --access-token string Access Token authentication (overrides context) + --api string API endpoint (overrides context) + --client-id string OIDC client ID for OIDC authentication (overrides context) + --client-secret string OIDC client secret for OIDC authentication (overrides context) + -c, --context string Context name + --debug Debug mode + -O, --organisation string Organisation slug or identity (overrides context) + --token string Personal access token (overrides context) +``` + +### SEE ALSO + +* [tcloud iam members](/docs/tcloud/iam/members/) - Organisation members (owners and members) + diff --git a/docs/tcloud/iam/members_list/_index.md b/docs/tcloud/iam/members_list/_index.md new file mode 100644 index 0000000..c066335 --- /dev/null +++ b/docs/tcloud/iam/members_list/_index.md @@ -0,0 +1,42 @@ +--- +linkTitle: "tcloud iam members list" +title: "iam members list" +slug: tcloud_iam_members_list +url: /docs/tcloud/iam/members_list/ +weight: 9941 +cascade: + type: docs +--- +## tcloud iam members list + +List organisation members + +``` +tcloud iam members list [flags] +``` + +### Options + +``` + --exact-time Show full timestamps instead of relative time + -h, --help help for list + --no-header Do not print table headers +``` + +### Options inherited from parent commands + +``` + --access-token string Access Token authentication (overrides context) + --api string API endpoint (overrides context) + --client-id string OIDC client ID for OIDC authentication (overrides context) + --client-secret string OIDC client secret for OIDC authentication (overrides context) + -c, --context string Context name + --debug Debug mode + -O, --organisation string Organisation slug or identity (overrides context) + --token string Personal access token (overrides context) +``` + +### SEE ALSO + +* [tcloud iam members](/docs/tcloud/iam/members/) - Organisation members (owners and members) + diff --git a/docs/tcloud/iam/members_update/_index.md b/docs/tcloud/iam/members_update/_index.md new file mode 100644 index 0000000..87e8ff9 --- /dev/null +++ b/docs/tcloud/iam/members_update/_index.md @@ -0,0 +1,42 @@ +--- +linkTitle: "tcloud iam members update" +title: "iam members update" +slug: tcloud_iam_members_update +url: /docs/tcloud/iam/members_update/ +weight: 9940 +cascade: + type: docs +--- +## tcloud iam members update + +Change an organisation member's role (OWNER or MEMBER) + +``` +tcloud iam members update [flags] +``` + +### Options + +``` + -h, --help help for update + --no-header Do not print table headers + --role string Organisation role: OWNER or MEMBER +``` + +### Options inherited from parent commands + +``` + --access-token string Access Token authentication (overrides context) + --api string API endpoint (overrides context) + --client-id string OIDC client ID for OIDC authentication (overrides context) + --client-secret string OIDC client secret for OIDC authentication (overrides context) + -c, --context string Context name + --debug Debug mode + -O, --organisation string Organisation slug or identity (overrides context) + --token string Personal access token (overrides context) +``` + +### SEE ALSO + +* [tcloud iam members](/docs/tcloud/iam/members/) - Organisation members (owners and members) + diff --git a/docs/tcloud/iam/roles/_index.md b/docs/tcloud/iam/roles/_index.md new file mode 100644 index 0000000..b99a883 --- /dev/null +++ b/docs/tcloud/iam/roles/_index.md @@ -0,0 +1,47 @@ +--- +linkTitle: "tcloud iam roles" +title: "iam roles" +slug: tcloud_iam_roles +url: /docs/tcloud/iam/roles/ +weight: 9927 +cascade: + type: docs +--- +## tcloud iam roles + +Organisation IAM roles, permission rules, and bindings + +### Synopsis + +Custom organisation roles define permission rules and can be bound to users, teams, +or service accounts. System roles may be read-only; the API enforces what you can change. + +### Options + +``` + -h, --help help for roles +``` + +### Options inherited from parent commands + +``` + --access-token string Access Token authentication (overrides context) + --api string API endpoint (overrides context) + --client-id string OIDC client ID for OIDC authentication (overrides context) + --client-secret string OIDC client secret for OIDC authentication (overrides context) + -c, --context string Context name + --debug Debug mode + -O, --organisation string Organisation slug or identity (overrides context) + --token string Personal access token (overrides context) +``` + +### SEE ALSO + +* [tcloud iam](/docs/tcloud/tcloud_iam/) - Identity and access management for your organisation +* [tcloud iam roles bindings](/docs/tcloud/iam/roles_bindings/) - Role bindings (who receives the role) +* [tcloud iam roles create](/docs/tcloud/iam/roles_create/) - Create a custom organisation role +* [tcloud iam roles delete](/docs/tcloud/iam/roles_delete/) - Delete a custom organisation role +* [tcloud iam roles get](/docs/tcloud/iam/roles_get/) - Show a role including rules and bindings summary +* [tcloud iam roles list](/docs/tcloud/iam/roles_list/) - List organisation roles +* [tcloud iam roles rules](/docs/tcloud/iam/roles_rules/) - Permission rules on a role + diff --git a/docs/tcloud/iam/roles_bindings/_index.md b/docs/tcloud/iam/roles_bindings/_index.md new file mode 100644 index 0000000..134d568 --- /dev/null +++ b/docs/tcloud/iam/roles_bindings/_index.md @@ -0,0 +1,39 @@ +--- +linkTitle: "tcloud iam roles bindings" +title: "iam roles bindings" +slug: tcloud_iam_roles_bindings +url: /docs/tcloud/iam/roles_bindings/ +weight: 9935 +cascade: + type: docs +--- +## tcloud iam roles bindings + +Role bindings (who receives the role) + +### Options + +``` + -h, --help help for bindings +``` + +### Options inherited from parent commands + +``` + --access-token string Access Token authentication (overrides context) + --api string API endpoint (overrides context) + --client-id string OIDC client ID for OIDC authentication (overrides context) + --client-secret string OIDC client secret for OIDC authentication (overrides context) + -c, --context string Context name + --debug Debug mode + -O, --organisation string Organisation slug or identity (overrides context) + --token string Personal access token (overrides context) +``` + +### SEE ALSO + +* [tcloud iam roles](/docs/tcloud/iam/roles/) - Organisation IAM roles, permission rules, and bindings +* [tcloud iam roles bindings create](/docs/tcloud/iam/roles_bindings_create/) - Create a binding to a user, team, or service account +* [tcloud iam roles bindings delete](/docs/tcloud/iam/roles_bindings_delete/) - Delete a role binding +* [tcloud iam roles bindings list](/docs/tcloud/iam/roles_bindings_list/) - List bindings for a role + diff --git a/docs/tcloud/iam/roles_bindings_create/_index.md b/docs/tcloud/iam/roles_bindings_create/_index.md new file mode 100644 index 0000000..ed7ea0c --- /dev/null +++ b/docs/tcloud/iam/roles_bindings_create/_index.md @@ -0,0 +1,49 @@ +--- +linkTitle: "tcloud iam roles bindings create" +title: "iam roles bindings create" +slug: tcloud_iam_roles_bindings_create +url: /docs/tcloud/iam/roles_bindings_create/ +weight: 9938 +cascade: + type: docs +--- +## tcloud iam roles bindings create + +Create a binding to a user, team, or service account + +``` +tcloud iam roles bindings create [flags] +``` + +### Options + +``` + --annotations strings Annotations as key=value (repeatable) + --description string Binding description + -h, --help help for create + --labels strings Labels as key=value (repeatable) + --name string Binding name + --no-header Do not print table headers + --scope strings Scopes for the binding (repeatable) + --service-account-identity string Bind to this service account identity + --team-identity string Bind to this team identity + --user-identity string Bind to this user identity +``` + +### Options inherited from parent commands + +``` + --access-token string Access Token authentication (overrides context) + --api string API endpoint (overrides context) + --client-id string OIDC client ID for OIDC authentication (overrides context) + --client-secret string OIDC client secret for OIDC authentication (overrides context) + -c, --context string Context name + --debug Debug mode + -O, --organisation string Organisation slug or identity (overrides context) + --token string Personal access token (overrides context) +``` + +### SEE ALSO + +* [tcloud iam roles bindings](/docs/tcloud/iam/roles_bindings/) - Role bindings (who receives the role) + diff --git a/docs/tcloud/iam/roles_bindings_delete/_index.md b/docs/tcloud/iam/roles_bindings_delete/_index.md new file mode 100644 index 0000000..1a86d3f --- /dev/null +++ b/docs/tcloud/iam/roles_bindings_delete/_index.md @@ -0,0 +1,42 @@ +--- +linkTitle: "tcloud iam roles bindings delete" +title: "iam roles bindings delete" +slug: tcloud_iam_roles_bindings_delete +url: /docs/tcloud/iam/roles_bindings_delete/ +weight: 9937 +cascade: + type: docs +--- +## tcloud iam roles bindings delete + +Delete a role binding + +``` +tcloud iam roles bindings delete [flags] +``` + +### Options + +``` + --force Skip the confirmation prompt and delete + -h, --help help for delete + --no-header Do not print table headers +``` + +### Options inherited from parent commands + +``` + --access-token string Access Token authentication (overrides context) + --api string API endpoint (overrides context) + --client-id string OIDC client ID for OIDC authentication (overrides context) + --client-secret string OIDC client secret for OIDC authentication (overrides context) + -c, --context string Context name + --debug Debug mode + -O, --organisation string Organisation slug or identity (overrides context) + --token string Personal access token (overrides context) +``` + +### SEE ALSO + +* [tcloud iam roles bindings](/docs/tcloud/iam/roles_bindings/) - Role bindings (who receives the role) + diff --git a/docs/tcloud/iam/roles_bindings_list/_index.md b/docs/tcloud/iam/roles_bindings_list/_index.md new file mode 100644 index 0000000..03cfff7 --- /dev/null +++ b/docs/tcloud/iam/roles_bindings_list/_index.md @@ -0,0 +1,41 @@ +--- +linkTitle: "tcloud iam roles bindings list" +title: "iam roles bindings list" +slug: tcloud_iam_roles_bindings_list +url: /docs/tcloud/iam/roles_bindings_list/ +weight: 9936 +cascade: + type: docs +--- +## tcloud iam roles bindings list + +List bindings for a role + +``` +tcloud iam roles bindings list [flags] +``` + +### Options + +``` + -h, --help help for list + --no-header Do not print table headers +``` + +### Options inherited from parent commands + +``` + --access-token string Access Token authentication (overrides context) + --api string API endpoint (overrides context) + --client-id string OIDC client ID for OIDC authentication (overrides context) + --client-secret string OIDC client secret for OIDC authentication (overrides context) + -c, --context string Context name + --debug Debug mode + -O, --organisation string Organisation slug or identity (overrides context) + --token string Personal access token (overrides context) +``` + +### SEE ALSO + +* [tcloud iam roles bindings](/docs/tcloud/iam/roles_bindings/) - Role bindings (who receives the role) + diff --git a/docs/tcloud/iam/roles_create/_index.md b/docs/tcloud/iam/roles_create/_index.md new file mode 100644 index 0000000..6e413a9 --- /dev/null +++ b/docs/tcloud/iam/roles_create/_index.md @@ -0,0 +1,45 @@ +--- +linkTitle: "tcloud iam roles create" +title: "iam roles create" +slug: tcloud_iam_roles_create +url: /docs/tcloud/iam/roles_create/ +weight: 9934 +cascade: + type: docs +--- +## tcloud iam roles create + +Create a custom organisation role + +``` +tcloud iam roles create [flags] +``` + +### Options + +``` + --annotations strings Annotations as key=value (repeatable) + --description string Role description + -h, --help help for create + --labels strings Labels as key=value (repeatable) + --name string Role name + --no-header Do not print table headers +``` + +### Options inherited from parent commands + +``` + --access-token string Access Token authentication (overrides context) + --api string API endpoint (overrides context) + --client-id string OIDC client ID for OIDC authentication (overrides context) + --client-secret string OIDC client secret for OIDC authentication (overrides context) + -c, --context string Context name + --debug Debug mode + -O, --organisation string Organisation slug or identity (overrides context) + --token string Personal access token (overrides context) +``` + +### SEE ALSO + +* [tcloud iam roles](/docs/tcloud/iam/roles/) - Organisation IAM roles, permission rules, and bindings + diff --git a/docs/tcloud/iam/roles_delete/_index.md b/docs/tcloud/iam/roles_delete/_index.md new file mode 100644 index 0000000..657b0d6 --- /dev/null +++ b/docs/tcloud/iam/roles_delete/_index.md @@ -0,0 +1,42 @@ +--- +linkTitle: "tcloud iam roles delete" +title: "iam roles delete" +slug: tcloud_iam_roles_delete +url: /docs/tcloud/iam/roles_delete/ +weight: 9933 +cascade: + type: docs +--- +## tcloud iam roles delete + +Delete a custom organisation role + +``` +tcloud iam roles delete [flags] +``` + +### Options + +``` + --force Skip the confirmation prompt and delete + -h, --help help for delete + --no-header Do not print table headers +``` + +### Options inherited from parent commands + +``` + --access-token string Access Token authentication (overrides context) + --api string API endpoint (overrides context) + --client-id string OIDC client ID for OIDC authentication (overrides context) + --client-secret string OIDC client secret for OIDC authentication (overrides context) + -c, --context string Context name + --debug Debug mode + -O, --organisation string Organisation slug or identity (overrides context) + --token string Personal access token (overrides context) +``` + +### SEE ALSO + +* [tcloud iam roles](/docs/tcloud/iam/roles/) - Organisation IAM roles, permission rules, and bindings + diff --git a/docs/tcloud/iam/roles_get/_index.md b/docs/tcloud/iam/roles_get/_index.md new file mode 100644 index 0000000..597f034 --- /dev/null +++ b/docs/tcloud/iam/roles_get/_index.md @@ -0,0 +1,42 @@ +--- +linkTitle: "tcloud iam roles get" +title: "iam roles get" +slug: tcloud_iam_roles_get +url: /docs/tcloud/iam/roles_get/ +weight: 9932 +cascade: + type: docs +--- +## tcloud iam roles get + +Show a role including rules and bindings summary + +``` +tcloud iam roles get [flags] +``` + +### Options + +``` + --exact-time Show full timestamps instead of relative time + -h, --help help for get + --no-header Do not print table headers +``` + +### Options inherited from parent commands + +``` + --access-token string Access Token authentication (overrides context) + --api string API endpoint (overrides context) + --client-id string OIDC client ID for OIDC authentication (overrides context) + --client-secret string OIDC client secret for OIDC authentication (overrides context) + -c, --context string Context name + --debug Debug mode + -O, --organisation string Organisation slug or identity (overrides context) + --token string Personal access token (overrides context) +``` + +### SEE ALSO + +* [tcloud iam roles](/docs/tcloud/iam/roles/) - Organisation IAM roles, permission rules, and bindings + diff --git a/docs/tcloud/iam/roles_list/_index.md b/docs/tcloud/iam/roles_list/_index.md new file mode 100644 index 0000000..e0c1764 --- /dev/null +++ b/docs/tcloud/iam/roles_list/_index.md @@ -0,0 +1,43 @@ +--- +linkTitle: "tcloud iam roles list" +title: "iam roles list" +slug: tcloud_iam_roles_list +url: /docs/tcloud/iam/roles_list/ +weight: 9931 +cascade: + type: docs +--- +## tcloud iam roles list + +List organisation roles + +``` +tcloud iam roles list [flags] +``` + +### Options + +``` + --exact-time Show full timestamps instead of relative time + -h, --help help for list + --label-selector string Filter by labels (key=value,key2=value2) + --no-header Do not print table headers +``` + +### Options inherited from parent commands + +``` + --access-token string Access Token authentication (overrides context) + --api string API endpoint (overrides context) + --client-id string OIDC client ID for OIDC authentication (overrides context) + --client-secret string OIDC client secret for OIDC authentication (overrides context) + -c, --context string Context name + --debug Debug mode + -O, --organisation string Organisation slug or identity (overrides context) + --token string Personal access token (overrides context) +``` + +### SEE ALSO + +* [tcloud iam roles](/docs/tcloud/iam/roles/) - Organisation IAM roles, permission rules, and bindings + diff --git a/docs/tcloud/iam/roles_rules/_index.md b/docs/tcloud/iam/roles_rules/_index.md new file mode 100644 index 0000000..4c5cde3 --- /dev/null +++ b/docs/tcloud/iam/roles_rules/_index.md @@ -0,0 +1,38 @@ +--- +linkTitle: "tcloud iam roles rules" +title: "iam roles rules" +slug: tcloud_iam_roles_rules +url: /docs/tcloud/iam/roles_rules/ +weight: 9928 +cascade: + type: docs +--- +## tcloud iam roles rules + +Permission rules on a role + +### Options + +``` + -h, --help help for rules +``` + +### Options inherited from parent commands + +``` + --access-token string Access Token authentication (overrides context) + --api string API endpoint (overrides context) + --client-id string OIDC client ID for OIDC authentication (overrides context) + --client-secret string OIDC client secret for OIDC authentication (overrides context) + -c, --context string Context name + --debug Debug mode + -O, --organisation string Organisation slug or identity (overrides context) + --token string Personal access token (overrides context) +``` + +### SEE ALSO + +* [tcloud iam roles](/docs/tcloud/iam/roles/) - Organisation IAM roles, permission rules, and bindings +* [tcloud iam roles rules add](/docs/tcloud/iam/roles_rules_add/) - Add a permission rule to a role +* [tcloud iam roles rules delete](/docs/tcloud/iam/roles_rules_delete/) - Remove a permission rule from a role + diff --git a/docs/tcloud/iam/roles_rules_add/_index.md b/docs/tcloud/iam/roles_rules_add/_index.md new file mode 100644 index 0000000..ac55ab9 --- /dev/null +++ b/docs/tcloud/iam/roles_rules_add/_index.md @@ -0,0 +1,45 @@ +--- +linkTitle: "tcloud iam roles rules add" +title: "iam roles rules add" +slug: tcloud_iam_roles_rules_add +url: /docs/tcloud/iam/roles_rules_add/ +weight: 9930 +cascade: + type: docs +--- +## tcloud iam roles rules add + +Add a permission rule to a role + +``` +tcloud iam roles rules add [flags] +``` + +### Options + +``` + -h, --help help for add + --no-header Do not print table headers + --note string Human-readable note for the rule + --permission strings Permission: create, read, update, delete, list, or * (repeatable) + --resource strings Resource type (repeatable) + --resource-identity strings Concrete resource identity (repeatable) +``` + +### Options inherited from parent commands + +``` + --access-token string Access Token authentication (overrides context) + --api string API endpoint (overrides context) + --client-id string OIDC client ID for OIDC authentication (overrides context) + --client-secret string OIDC client secret for OIDC authentication (overrides context) + -c, --context string Context name + --debug Debug mode + -O, --organisation string Organisation slug or identity (overrides context) + --token string Personal access token (overrides context) +``` + +### SEE ALSO + +* [tcloud iam roles rules](/docs/tcloud/iam/roles_rules/) - Permission rules on a role + diff --git a/docs/tcloud/iam/roles_rules_delete/_index.md b/docs/tcloud/iam/roles_rules_delete/_index.md new file mode 100644 index 0000000..83fa787 --- /dev/null +++ b/docs/tcloud/iam/roles_rules_delete/_index.md @@ -0,0 +1,42 @@ +--- +linkTitle: "tcloud iam roles rules delete" +title: "iam roles rules delete" +slug: tcloud_iam_roles_rules_delete +url: /docs/tcloud/iam/roles_rules_delete/ +weight: 9929 +cascade: + type: docs +--- +## tcloud iam roles rules delete + +Remove a permission rule from a role + +``` +tcloud iam roles rules delete [flags] +``` + +### Options + +``` + --force Skip the confirmation prompt and delete + -h, --help help for delete + --no-header Do not print table headers +``` + +### Options inherited from parent commands + +``` + --access-token string Access Token authentication (overrides context) + --api string API endpoint (overrides context) + --client-id string OIDC client ID for OIDC authentication (overrides context) + --client-secret string OIDC client secret for OIDC authentication (overrides context) + -c, --context string Context name + --debug Debug mode + -O, --organisation string Organisation slug or identity (overrides context) + --token string Personal access token (overrides context) +``` + +### SEE ALSO + +* [tcloud iam roles rules](/docs/tcloud/iam/roles_rules/) - Permission rules on a role + diff --git a/docs/tcloud/iam/service-accounts/_index.md b/docs/tcloud/iam/service-accounts/_index.md new file mode 100644 index 0000000..a226f83 --- /dev/null +++ b/docs/tcloud/iam/service-accounts/_index.md @@ -0,0 +1,41 @@ +--- +linkTitle: "tcloud iam service-accounts" +title: "iam service-accounts" +slug: tcloud_iam_service-accounts +url: /docs/tcloud/iam/service-accounts/ +weight: 9921 +cascade: + type: docs +--- +## tcloud iam service-accounts + +Organisation service accounts + +### Options + +``` + -h, --help help for service-accounts +``` + +### Options inherited from parent commands + +``` + --access-token string Access Token authentication (overrides context) + --api string API endpoint (overrides context) + --client-id string OIDC client ID for OIDC authentication (overrides context) + --client-secret string OIDC client secret for OIDC authentication (overrides context) + -c, --context string Context name + --debug Debug mode + -O, --organisation string Organisation slug or identity (overrides context) + --token string Personal access token (overrides context) +``` + +### SEE ALSO + +* [tcloud iam](/docs/tcloud/tcloud_iam/) - Identity and access management for your organisation +* [tcloud iam service-accounts create](/docs/tcloud/iam/service-accounts_create/) - Create a service account +* [tcloud iam service-accounts delete](/docs/tcloud/iam/service-accounts_delete/) - Delete a service account +* [tcloud iam service-accounts get](/docs/tcloud/iam/service-accounts_get/) - Show a service account +* [tcloud iam service-accounts list](/docs/tcloud/iam/service-accounts_list/) - List service accounts +* [tcloud iam service-accounts update](/docs/tcloud/iam/service-accounts_update/) - Update a service account (only set flags are sent) + diff --git a/docs/tcloud/iam/service-accounts_create/_index.md b/docs/tcloud/iam/service-accounts_create/_index.md new file mode 100644 index 0000000..d29065e --- /dev/null +++ b/docs/tcloud/iam/service-accounts_create/_index.md @@ -0,0 +1,45 @@ +--- +linkTitle: "tcloud iam service-accounts create" +title: "iam service-accounts create" +slug: tcloud_iam_service-accounts_create +url: /docs/tcloud/iam/service-accounts_create/ +weight: 9926 +cascade: + type: docs +--- +## tcloud iam service-accounts create + +Create a service account + +``` +tcloud iam service-accounts create [flags] +``` + +### Options + +``` + --annotations strings Annotations as key=value (repeatable) + --description string Description + -h, --help help for create + --labels strings Labels as key=value (repeatable) + --name string Service account name + --no-header Do not print table headers +``` + +### Options inherited from parent commands + +``` + --access-token string Access Token authentication (overrides context) + --api string API endpoint (overrides context) + --client-id string OIDC client ID for OIDC authentication (overrides context) + --client-secret string OIDC client secret for OIDC authentication (overrides context) + -c, --context string Context name + --debug Debug mode + -O, --organisation string Organisation slug or identity (overrides context) + --token string Personal access token (overrides context) +``` + +### SEE ALSO + +* [tcloud iam service-accounts](/docs/tcloud/iam/service-accounts/) - Organisation service accounts + diff --git a/docs/tcloud/iam/service-accounts_delete/_index.md b/docs/tcloud/iam/service-accounts_delete/_index.md new file mode 100644 index 0000000..4cc71db --- /dev/null +++ b/docs/tcloud/iam/service-accounts_delete/_index.md @@ -0,0 +1,42 @@ +--- +linkTitle: "tcloud iam service-accounts delete" +title: "iam service-accounts delete" +slug: tcloud_iam_service-accounts_delete +url: /docs/tcloud/iam/service-accounts_delete/ +weight: 9925 +cascade: + type: docs +--- +## tcloud iam service-accounts delete + +Delete a service account + +``` +tcloud iam service-accounts delete [flags] +``` + +### Options + +``` + --force Skip the confirmation prompt and delete + -h, --help help for delete + --no-header Do not print table headers +``` + +### Options inherited from parent commands + +``` + --access-token string Access Token authentication (overrides context) + --api string API endpoint (overrides context) + --client-id string OIDC client ID for OIDC authentication (overrides context) + --client-secret string OIDC client secret for OIDC authentication (overrides context) + -c, --context string Context name + --debug Debug mode + -O, --organisation string Organisation slug or identity (overrides context) + --token string Personal access token (overrides context) +``` + +### SEE ALSO + +* [tcloud iam service-accounts](/docs/tcloud/iam/service-accounts/) - Organisation service accounts + diff --git a/docs/tcloud/iam/service-accounts_get/_index.md b/docs/tcloud/iam/service-accounts_get/_index.md new file mode 100644 index 0000000..ae3c311 --- /dev/null +++ b/docs/tcloud/iam/service-accounts_get/_index.md @@ -0,0 +1,42 @@ +--- +linkTitle: "tcloud iam service-accounts get" +title: "iam service-accounts get" +slug: tcloud_iam_service-accounts_get +url: /docs/tcloud/iam/service-accounts_get/ +weight: 9924 +cascade: + type: docs +--- +## tcloud iam service-accounts get + +Show a service account + +``` +tcloud iam service-accounts get [flags] +``` + +### Options + +``` + --exact-time Show full timestamps instead of relative time + -h, --help help for get + --no-header Do not print table headers +``` + +### Options inherited from parent commands + +``` + --access-token string Access Token authentication (overrides context) + --api string API endpoint (overrides context) + --client-id string OIDC client ID for OIDC authentication (overrides context) + --client-secret string OIDC client secret for OIDC authentication (overrides context) + -c, --context string Context name + --debug Debug mode + -O, --organisation string Organisation slug or identity (overrides context) + --token string Personal access token (overrides context) +``` + +### SEE ALSO + +* [tcloud iam service-accounts](/docs/tcloud/iam/service-accounts/) - Organisation service accounts + diff --git a/docs/tcloud/iam/service-accounts_list/_index.md b/docs/tcloud/iam/service-accounts_list/_index.md new file mode 100644 index 0000000..fc580ee --- /dev/null +++ b/docs/tcloud/iam/service-accounts_list/_index.md @@ -0,0 +1,43 @@ +--- +linkTitle: "tcloud iam service-accounts list" +title: "iam service-accounts list" +slug: tcloud_iam_service-accounts_list +url: /docs/tcloud/iam/service-accounts_list/ +weight: 9923 +cascade: + type: docs +--- +## tcloud iam service-accounts list + +List service accounts + +``` +tcloud iam service-accounts list [flags] +``` + +### Options + +``` + --exact-time Show full timestamps instead of relative time + -h, --help help for list + --label-selector string Filter by labels (key=value,key2=value2) + --no-header Do not print table headers +``` + +### Options inherited from parent commands + +``` + --access-token string Access Token authentication (overrides context) + --api string API endpoint (overrides context) + --client-id string OIDC client ID for OIDC authentication (overrides context) + --client-secret string OIDC client secret for OIDC authentication (overrides context) + -c, --context string Context name + --debug Debug mode + -O, --organisation string Organisation slug or identity (overrides context) + --token string Personal access token (overrides context) +``` + +### SEE ALSO + +* [tcloud iam service-accounts](/docs/tcloud/iam/service-accounts/) - Organisation service accounts + diff --git a/docs/tcloud/iam/service-accounts_update/_index.md b/docs/tcloud/iam/service-accounts_update/_index.md new file mode 100644 index 0000000..25e5d31 --- /dev/null +++ b/docs/tcloud/iam/service-accounts_update/_index.md @@ -0,0 +1,45 @@ +--- +linkTitle: "tcloud iam service-accounts update" +title: "iam service-accounts update" +slug: tcloud_iam_service-accounts_update +url: /docs/tcloud/iam/service-accounts_update/ +weight: 9922 +cascade: + type: docs +--- +## tcloud iam service-accounts update + +Update a service account (only set flags are sent) + +``` +tcloud iam service-accounts update [flags] +``` + +### Options + +``` + --annotations strings Replace annotations (key=value, repeatable) + --description string Description (empty to clear) + -h, --help help for update + --labels strings Replace labels (key=value, repeatable) + --name string Name + --no-header Do not print table headers +``` + +### Options inherited from parent commands + +``` + --access-token string Access Token authentication (overrides context) + --api string API endpoint (overrides context) + --client-id string OIDC client ID for OIDC authentication (overrides context) + --client-secret string OIDC client secret for OIDC authentication (overrides context) + -c, --context string Context name + --debug Debug mode + -O, --organisation string Organisation slug or identity (overrides context) + --token string Personal access token (overrides context) +``` + +### SEE ALSO + +* [tcloud iam service-accounts](/docs/tcloud/iam/service-accounts/) - Organisation service accounts + diff --git a/docs/tcloud/iam/teams/_index.md b/docs/tcloud/iam/teams/_index.md new file mode 100644 index 0000000..37859f3 --- /dev/null +++ b/docs/tcloud/iam/teams/_index.md @@ -0,0 +1,42 @@ +--- +linkTitle: "tcloud iam teams" +title: "iam teams" +slug: tcloud_iam_teams +url: /docs/tcloud/iam/teams/ +weight: 9911 +cascade: + type: docs +--- +## tcloud iam teams + +Manage organisation teams + +### Options + +``` + -h, --help help for teams +``` + +### Options inherited from parent commands + +``` + --access-token string Access Token authentication (overrides context) + --api string API endpoint (overrides context) + --client-id string OIDC client ID for OIDC authentication (overrides context) + --client-secret string OIDC client secret for OIDC authentication (overrides context) + -c, --context string Context name + --debug Debug mode + -O, --organisation string Organisation slug or identity (overrides context) + --token string Personal access token (overrides context) +``` + +### SEE ALSO + +* [tcloud iam](/docs/tcloud/tcloud_iam/) - Identity and access management for your organisation +* [tcloud iam teams create](/docs/tcloud/iam/teams_create/) - Create a team +* [tcloud iam teams delete](/docs/tcloud/iam/teams_delete/) - Delete a team +* [tcloud iam teams get](/docs/tcloud/iam/teams_get/) - Show a team and its members +* [tcloud iam teams list](/docs/tcloud/iam/teams_list/) - List teams +* [tcloud iam teams members](/docs/tcloud/iam/teams_members/) - Manage team membership +* [tcloud iam teams update](/docs/tcloud/iam/teams_update/) - Update a team (only flags you set are changed) + diff --git a/docs/tcloud/iam/teams_create/_index.md b/docs/tcloud/iam/teams_create/_index.md new file mode 100644 index 0000000..2434aae --- /dev/null +++ b/docs/tcloud/iam/teams_create/_index.md @@ -0,0 +1,46 @@ +--- +linkTitle: "tcloud iam teams create" +title: "iam teams create" +slug: tcloud_iam_teams_create +url: /docs/tcloud/iam/teams_create/ +weight: 9920 +cascade: + type: docs +--- +## tcloud iam teams create + +Create a team + +``` +tcloud iam teams create [flags] +``` + +### Options + +``` + --annotations strings Annotations as key=value (repeatable) + --description string Team description + --exact-time Show full timestamps instead of relative time + -h, --help help for create + --labels strings Labels as key=value (repeatable) + --name string Team display name + --no-header Do not print table headers +``` + +### Options inherited from parent commands + +``` + --access-token string Access Token authentication (overrides context) + --api string API endpoint (overrides context) + --client-id string OIDC client ID for OIDC authentication (overrides context) + --client-secret string OIDC client secret for OIDC authentication (overrides context) + -c, --context string Context name + --debug Debug mode + -O, --organisation string Organisation slug or identity (overrides context) + --token string Personal access token (overrides context) +``` + +### SEE ALSO + +* [tcloud iam teams](/docs/tcloud/iam/teams/) - Manage organisation teams + diff --git a/docs/tcloud/iam/teams_delete/_index.md b/docs/tcloud/iam/teams_delete/_index.md new file mode 100644 index 0000000..449fa9f --- /dev/null +++ b/docs/tcloud/iam/teams_delete/_index.md @@ -0,0 +1,43 @@ +--- +linkTitle: "tcloud iam teams delete" +title: "iam teams delete" +slug: tcloud_iam_teams_delete +url: /docs/tcloud/iam/teams_delete/ +weight: 9919 +cascade: + type: docs +--- +## tcloud iam teams delete + +Delete a team + +``` +tcloud iam teams delete [flags] +``` + +### Options + +``` + --exact-time Show full timestamps instead of relative time + --force Skip the confirmation prompt and delete + -h, --help help for delete + --no-header Do not print table headers +``` + +### Options inherited from parent commands + +``` + --access-token string Access Token authentication (overrides context) + --api string API endpoint (overrides context) + --client-id string OIDC client ID for OIDC authentication (overrides context) + --client-secret string OIDC client secret for OIDC authentication (overrides context) + -c, --context string Context name + --debug Debug mode + -O, --organisation string Organisation slug or identity (overrides context) + --token string Personal access token (overrides context) +``` + +### SEE ALSO + +* [tcloud iam teams](/docs/tcloud/iam/teams/) - Manage organisation teams + diff --git a/docs/tcloud/iam/teams_get/_index.md b/docs/tcloud/iam/teams_get/_index.md new file mode 100644 index 0000000..837ff37 --- /dev/null +++ b/docs/tcloud/iam/teams_get/_index.md @@ -0,0 +1,42 @@ +--- +linkTitle: "tcloud iam teams get" +title: "iam teams get" +slug: tcloud_iam_teams_get +url: /docs/tcloud/iam/teams_get/ +weight: 9918 +cascade: + type: docs +--- +## tcloud iam teams get + +Show a team and its members + +``` +tcloud iam teams get [flags] +``` + +### Options + +``` + --exact-time Show full timestamps instead of relative time + -h, --help help for get + --no-header Do not print table headers +``` + +### Options inherited from parent commands + +``` + --access-token string Access Token authentication (overrides context) + --api string API endpoint (overrides context) + --client-id string OIDC client ID for OIDC authentication (overrides context) + --client-secret string OIDC client secret for OIDC authentication (overrides context) + -c, --context string Context name + --debug Debug mode + -O, --organisation string Organisation slug or identity (overrides context) + --token string Personal access token (overrides context) +``` + +### SEE ALSO + +* [tcloud iam teams](/docs/tcloud/iam/teams/) - Manage organisation teams + diff --git a/docs/tcloud/iam/teams_list/_index.md b/docs/tcloud/iam/teams_list/_index.md new file mode 100644 index 0000000..d8242ff --- /dev/null +++ b/docs/tcloud/iam/teams_list/_index.md @@ -0,0 +1,43 @@ +--- +linkTitle: "tcloud iam teams list" +title: "iam teams list" +slug: tcloud_iam_teams_list +url: /docs/tcloud/iam/teams_list/ +weight: 9917 +cascade: + type: docs +--- +## tcloud iam teams list + +List teams + +``` +tcloud iam teams list [flags] +``` + +### Options + +``` + --exact-time Show full timestamps instead of relative time + -h, --help help for list + --label-selector string Filter by labels (key=value,key2=value2) + --no-header Do not print table headers +``` + +### Options inherited from parent commands + +``` + --access-token string Access Token authentication (overrides context) + --api string API endpoint (overrides context) + --client-id string OIDC client ID for OIDC authentication (overrides context) + --client-secret string OIDC client secret for OIDC authentication (overrides context) + -c, --context string Context name + --debug Debug mode + -O, --organisation string Organisation slug or identity (overrides context) + --token string Personal access token (overrides context) +``` + +### SEE ALSO + +* [tcloud iam teams](/docs/tcloud/iam/teams/) - Manage organisation teams + diff --git a/docs/tcloud/iam/teams_members/_index.md b/docs/tcloud/iam/teams_members/_index.md new file mode 100644 index 0000000..ba58b49 --- /dev/null +++ b/docs/tcloud/iam/teams_members/_index.md @@ -0,0 +1,39 @@ +--- +linkTitle: "tcloud iam teams members" +title: "iam teams members" +slug: tcloud_iam_teams_members +url: /docs/tcloud/iam/teams_members/ +weight: 9913 +cascade: + type: docs +--- +## tcloud iam teams members + +Manage team membership + +### Options + +``` + -h, --help help for members +``` + +### Options inherited from parent commands + +``` + --access-token string Access Token authentication (overrides context) + --api string API endpoint (overrides context) + --client-id string OIDC client ID for OIDC authentication (overrides context) + --client-secret string OIDC client secret for OIDC authentication (overrides context) + -c, --context string Context name + --debug Debug mode + -O, --organisation string Organisation slug or identity (overrides context) + --token string Personal access token (overrides context) +``` + +### SEE ALSO + +* [tcloud iam teams](/docs/tcloud/iam/teams/) - Manage organisation teams +* [tcloud iam teams members add](/docs/tcloud/iam/teams_members_add/) - Add a user to a team +* [tcloud iam teams members list](/docs/tcloud/iam/teams_members_list/) - List members of a team +* [tcloud iam teams members remove](/docs/tcloud/iam/teams_members_remove/) - Remove a member from a team + diff --git a/docs/tcloud/iam/teams_members_add/_index.md b/docs/tcloud/iam/teams_members_add/_index.md new file mode 100644 index 0000000..245fc81 --- /dev/null +++ b/docs/tcloud/iam/teams_members_add/_index.md @@ -0,0 +1,43 @@ +--- +linkTitle: "tcloud iam teams members add" +title: "iam teams members add" +slug: tcloud_iam_teams_members_add +url: /docs/tcloud/iam/teams_members_add/ +weight: 9916 +cascade: + type: docs +--- +## tcloud iam teams members add + +Add a user to a team + +``` +tcloud iam teams members add [flags] +``` + +### Options + +``` + -h, --help help for add + --no-header Do not print table headers + --role string Team role for the user + --user string User identity to add +``` + +### Options inherited from parent commands + +``` + --access-token string Access Token authentication (overrides context) + --api string API endpoint (overrides context) + --client-id string OIDC client ID for OIDC authentication (overrides context) + --client-secret string OIDC client secret for OIDC authentication (overrides context) + -c, --context string Context name + --debug Debug mode + -O, --organisation string Organisation slug or identity (overrides context) + --token string Personal access token (overrides context) +``` + +### SEE ALSO + +* [tcloud iam teams members](/docs/tcloud/iam/teams_members/) - Manage team membership + diff --git a/docs/tcloud/iam/teams_members_list/_index.md b/docs/tcloud/iam/teams_members_list/_index.md new file mode 100644 index 0000000..af0ebdb --- /dev/null +++ b/docs/tcloud/iam/teams_members_list/_index.md @@ -0,0 +1,41 @@ +--- +linkTitle: "tcloud iam teams members list" +title: "iam teams members list" +slug: tcloud_iam_teams_members_list +url: /docs/tcloud/iam/teams_members_list/ +weight: 9915 +cascade: + type: docs +--- +## tcloud iam teams members list + +List members of a team + +``` +tcloud iam teams members list [flags] +``` + +### Options + +``` + -h, --help help for list + --no-header Do not print table headers +``` + +### Options inherited from parent commands + +``` + --access-token string Access Token authentication (overrides context) + --api string API endpoint (overrides context) + --client-id string OIDC client ID for OIDC authentication (overrides context) + --client-secret string OIDC client secret for OIDC authentication (overrides context) + -c, --context string Context name + --debug Debug mode + -O, --organisation string Organisation slug or identity (overrides context) + --token string Personal access token (overrides context) +``` + +### SEE ALSO + +* [tcloud iam teams members](/docs/tcloud/iam/teams_members/) - Manage team membership + diff --git a/docs/tcloud/iam/teams_members_remove/_index.md b/docs/tcloud/iam/teams_members_remove/_index.md new file mode 100644 index 0000000..67bc067 --- /dev/null +++ b/docs/tcloud/iam/teams_members_remove/_index.md @@ -0,0 +1,42 @@ +--- +linkTitle: "tcloud iam teams members remove" +title: "iam teams members remove" +slug: tcloud_iam_teams_members_remove +url: /docs/tcloud/iam/teams_members_remove/ +weight: 9914 +cascade: + type: docs +--- +## tcloud iam teams members remove + +Remove a member from a team + +``` +tcloud iam teams members remove [flags] +``` + +### Options + +``` + --force Skip the confirmation prompt and remove the member + -h, --help help for remove + --no-header Do not print table headers +``` + +### Options inherited from parent commands + +``` + --access-token string Access Token authentication (overrides context) + --api string API endpoint (overrides context) + --client-id string OIDC client ID for OIDC authentication (overrides context) + --client-secret string OIDC client secret for OIDC authentication (overrides context) + -c, --context string Context name + --debug Debug mode + -O, --organisation string Organisation slug or identity (overrides context) + --token string Personal access token (overrides context) +``` + +### SEE ALSO + +* [tcloud iam teams members](/docs/tcloud/iam/teams_members/) - Manage team membership + diff --git a/docs/tcloud/iam/teams_update/_index.md b/docs/tcloud/iam/teams_update/_index.md new file mode 100644 index 0000000..91e26af --- /dev/null +++ b/docs/tcloud/iam/teams_update/_index.md @@ -0,0 +1,46 @@ +--- +linkTitle: "tcloud iam teams update" +title: "iam teams update" +slug: tcloud_iam_teams_update +url: /docs/tcloud/iam/teams_update/ +weight: 9912 +cascade: + type: docs +--- +## tcloud iam teams update + +Update a team (only flags you set are changed) + +``` +tcloud iam teams update [flags] +``` + +### Options + +``` + --annotations strings Replace annotations (key=value, repeatable) + --description string Team description + --exact-time Show full timestamps instead of relative time + -h, --help help for update + --labels strings Replace labels (key=value, repeatable) + --name string Team display name + --no-header Do not print table headers +``` + +### Options inherited from parent commands + +``` + --access-token string Access Token authentication (overrides context) + --api string API endpoint (overrides context) + --client-id string OIDC client ID for OIDC authentication (overrides context) + --client-secret string OIDC client secret for OIDC authentication (overrides context) + -c, --context string Context name + --debug Debug mode + -O, --organisation string Organisation slug or identity (overrides context) + --token string Personal access token (overrides context) +``` + +### SEE ALSO + +* [tcloud iam teams](/docs/tcloud/iam/teams/) - Manage organisation teams + diff --git a/docs/tcloud/iam/workload-identity-federation/_index.md b/docs/tcloud/iam/workload-identity-federation/_index.md new file mode 100644 index 0000000..2b4991a --- /dev/null +++ b/docs/tcloud/iam/workload-identity-federation/_index.md @@ -0,0 +1,42 @@ +--- +linkTitle: "tcloud iam workload-identity-federation" +title: "iam workload-identity-federation" +slug: tcloud_iam_workload-identity-federation +url: /docs/tcloud/iam/workload-identity-federation/ +weight: 9906 +cascade: + type: docs +--- +## tcloud iam workload-identity-federation + +Bootstrap and manage CI/CD workload identity (OIDC) + +### Synopsis + +Commands to provision federated identity providers, service accounts, role bindings, +and federated identities for GitHub Actions, GitLab CI, and Kubernetes service account OIDC tokens. + +### Options + +``` + -h, --help help for workload-identity-federation +``` + +### Options inherited from parent commands + +``` + --access-token string Access Token authentication (overrides context) + --api string API endpoint (overrides context) + --client-id string OIDC client ID for OIDC authentication (overrides context) + --client-secret string OIDC client secret for OIDC authentication (overrides context) + -c, --context string Context name + --debug Debug mode + -O, --organisation string Organisation slug or identity (overrides context) + --token string Personal access token (overrides context) +``` + +### SEE ALSO + +* [tcloud iam](/docs/tcloud/tcloud_iam/) - Identity and access management for your organisation +* [tcloud iam workload-identity-federation bootstrap](/docs/tcloud/iam/workload-identity-federation_bootstrap/) - Provision workload identity for GitHub, GitLab, or Kubernetes + diff --git a/docs/tcloud/iam/workload-identity-federation_bootstrap/_index.md b/docs/tcloud/iam/workload-identity-federation_bootstrap/_index.md new file mode 100644 index 0000000..70755cd --- /dev/null +++ b/docs/tcloud/iam/workload-identity-federation_bootstrap/_index.md @@ -0,0 +1,60 @@ +--- +linkTitle: "tcloud iam workload-identity-federation bootstrap" +title: "iam workload-identity-federation bootstrap" +slug: tcloud_iam_workload-identity-federation_bootstrap +url: /docs/tcloud/iam/workload-identity-federation_bootstrap/ +weight: 9907 +cascade: + type: docs +--- +## tcloud iam workload-identity-federation bootstrap + +Provision workload identity for GitHub, GitLab, or Kubernetes + +### Synopsis + +Creates (when missing) a federated OIDC identity provider, a Thalassa service account, +a role binding to your organisation role, and a federated identity for the workload JWT subject. + +Resources are labelled thalassa.cloud/managed-by=workload-identity-bootstrap and thalassa.cloud/wif-vcs=. + +Subcommands: + github — GitHub Actions (issuer https://token.actions.githubusercontent.com) + gitlab — GitLab CI id_token + kubernetes — in-cluster service account JWTs + + +### Options + +``` + --dry-run Print planned changes without calling the API + -h, --help help for bootstrap + --name string Base name for the Thalassa service account and federated identity (federated identity becomes -fi; default: wif--) + --no-hints Do not print platform hints after bootstrap + --provider-description string Optional description when creating the federated identity provider + --provider-name string Optional display name when creating the federated identity provider + --role string Organisation role identity, slug, or name (required) + --scope strings Federated identity allowed scopes: api:read, api:write, kubernetes, objectStorage (default: api:read,api:write) + --trusted-audience strings JWT aud values to trust (repeatable; default: current context API URL, e.g. https://api.thalassa.cloud) +``` + +### Options inherited from parent commands + +``` + --access-token string Access Token authentication (overrides context) + --api string API endpoint (overrides context) + --client-id string OIDC client ID for OIDC authentication (overrides context) + --client-secret string OIDC client secret for OIDC authentication (overrides context) + -c, --context string Context name + --debug Debug mode + -O, --organisation string Organisation slug or identity (overrides context) + --token string Personal access token (overrides context) +``` + +### SEE ALSO + +* [tcloud iam workload-identity-federation](/docs/tcloud/iam/workload-identity-federation/) - Bootstrap and manage CI/CD workload identity (OIDC) +* [tcloud iam workload-identity-federation bootstrap github](/docs/tcloud/iam/workload-identity-federation_bootstrap_github/) - Bootstrap workload identity for GitHub Actions +* [tcloud iam workload-identity-federation bootstrap gitlab](/docs/tcloud/iam/workload-identity-federation_bootstrap_gitlab/) - Bootstrap workload identity for GitLab CI +* [tcloud iam workload-identity-federation bootstrap kubernetes](/docs/tcloud/iam/workload-identity-federation_bootstrap_kubernetes/) - Bootstrap workload identity for Kubernetes service accounts + diff --git a/docs/tcloud/iam/workload-identity-federation_bootstrap_github/_index.md b/docs/tcloud/iam/workload-identity-federation_bootstrap_github/_index.md new file mode 100644 index 0000000..0f86f86 --- /dev/null +++ b/docs/tcloud/iam/workload-identity-federation_bootstrap_github/_index.md @@ -0,0 +1,75 @@ +--- +linkTitle: "tcloud iam workload-identity-federation bootstrap github" +title: "iam workload-identity-federation bootstrap github" +slug: tcloud_iam_workload-identity-federation_bootstrap_github +url: /docs/tcloud/iam/workload-identity-federation_bootstrap_github/ +weight: 9910 +cascade: + type: docs +--- +## tcloud iam workload-identity-federation bootstrap github + +Bootstrap workload identity for GitHub Actions + +### Synopsis + +Creates or reuses a federated identity provider for GitHub's OIDC issuer, then binds your repository +and ref to a Thalassa service account via a federated identity. + +The JWT issuer is https://token.actions.githubusercontent.com. Match subjects with --repository (owner/name), +--ref-kind, and --ref (omit --ref only when --ref-kind is pull_request). + +``` +tcloud iam workload-identity-federation bootstrap github [flags] +``` + +### Examples + +``` + # Main branch (JWT aud defaults to context API URL) + tcloud iam workload-identity-federation bootstrap github --repository acme/api --ref main --role deployer + + # Specific ref kind + tcloud iam workload-identity-federation bootstrap github --repository acme/api --ref-kind branch --ref main --role deployer + + # Pull request workflows (subject repo:owner/repo:pull_request) + tcloud iam workload-identity-federation bootstrap github --repository acme/api --ref-kind pull_request --role deployer + + # Custom Thalassa service account / federated identity names (federated identity: platform-ci-fi) + tcloud iam workload-identity-federation bootstrap github --repository acme/api --ref main --name platform-ci --role deployer +``` + +### Options + +``` + -h, --help help for github + --ref string Branch, tag, or environment name (omit when --ref-kind pull_request) + --ref-kind string branch, tag, environment, or pull_request (default "branch") + --repository string GitHub repository as owner/name (required) +``` + +### Options inherited from parent commands + +``` + --access-token string Access Token authentication (overrides context) + --api string API endpoint (overrides context) + --client-id string OIDC client ID for OIDC authentication (overrides context) + --client-secret string OIDC client secret for OIDC authentication (overrides context) + -c, --context string Context name + --debug Debug mode + --dry-run Print planned changes without calling the API + --name string Base name for the Thalassa service account and federated identity (federated identity becomes -fi; default: wif--) + --no-hints Do not print platform hints after bootstrap + -O, --organisation string Organisation slug or identity (overrides context) + --provider-description string Optional description when creating the federated identity provider + --provider-name string Optional display name when creating the federated identity provider + --role string Organisation role identity, slug, or name (required) + --scope strings Federated identity allowed scopes: api:read, api:write, kubernetes, objectStorage (default: api:read,api:write) + --token string Personal access token (overrides context) + --trusted-audience strings JWT aud values to trust (repeatable; default: current context API URL, e.g. https://api.thalassa.cloud) +``` + +### SEE ALSO + +* [tcloud iam workload-identity-federation bootstrap](/docs/tcloud/iam/workload-identity-federation_bootstrap/) - Provision workload identity for GitHub, GitLab, or Kubernetes + diff --git a/docs/tcloud/iam/workload-identity-federation_bootstrap_gitlab/_index.md b/docs/tcloud/iam/workload-identity-federation_bootstrap_gitlab/_index.md new file mode 100644 index 0000000..bc82f82 --- /dev/null +++ b/docs/tcloud/iam/workload-identity-federation_bootstrap_gitlab/_index.md @@ -0,0 +1,72 @@ +--- +linkTitle: "tcloud iam workload-identity-federation bootstrap gitlab" +title: "iam workload-identity-federation bootstrap gitlab" +slug: tcloud_iam_workload-identity-federation_bootstrap_gitlab +url: /docs/tcloud/iam/workload-identity-federation_bootstrap_gitlab/ +weight: 9909 +cascade: + type: docs +--- +## tcloud iam workload-identity-federation bootstrap gitlab + +Bootstrap workload identity for GitLab CI + +### Synopsis + +Creates or reuses a federated identity provider for your GitLab OIDC issuer, then binds the project +and ref to a Thalassa service account. + +The GitLab id_token sub uses project_path::ref_type::ref:. + +``` +tcloud iam workload-identity-federation bootstrap gitlab [flags] +``` + +### Examples + +``` + # GitLab.com, branch main + tcloud iam workload-identity-federation bootstrap gitlab --repository mygroup/myproject --ref main --role deployer + + # Tag pipeline + tcloud iam workload-identity-federation bootstrap gitlab --repository mygroup/myproject --ref v1.0.0 --ref-type tag --role deployer + + # Self-managed GitLab + tcloud iam workload-identity-federation bootstrap gitlab --repository mygroup/myproject --ref main --issuer https://gitlab.example.com --role deployer +``` + +### Options + +``` + -h, --help help for gitlab + --issuer string GitLab OIDC issuer URL (self-managed) (default "https://gitlab.com") + --ref string Git ref segment for id_token sub, e.g. branch or tag name (required) + --ref-type string ref_type claim in JWT sub: branch, tag, merge_request, etc. (default "branch") + --repository string GitLab project path as group/project (required) +``` + +### Options inherited from parent commands + +``` + --access-token string Access Token authentication (overrides context) + --api string API endpoint (overrides context) + --client-id string OIDC client ID for OIDC authentication (overrides context) + --client-secret string OIDC client secret for OIDC authentication (overrides context) + -c, --context string Context name + --debug Debug mode + --dry-run Print planned changes without calling the API + --name string Base name for the Thalassa service account and federated identity (federated identity becomes -fi; default: wif--) + --no-hints Do not print platform hints after bootstrap + -O, --organisation string Organisation slug or identity (overrides context) + --provider-description string Optional description when creating the federated identity provider + --provider-name string Optional display name when creating the federated identity provider + --role string Organisation role identity, slug, or name (required) + --scope strings Federated identity allowed scopes: api:read, api:write, kubernetes, objectStorage (default: api:read,api:write) + --token string Personal access token (overrides context) + --trusted-audience strings JWT aud values to trust (repeatable; default: current context API URL, e.g. https://api.thalassa.cloud) +``` + +### SEE ALSO + +* [tcloud iam workload-identity-federation bootstrap](/docs/tcloud/iam/workload-identity-federation_bootstrap/) - Provision workload identity for GitHub, GitLab, or Kubernetes + diff --git a/docs/tcloud/iam/workload-identity-federation_bootstrap_kubernetes/_index.md b/docs/tcloud/iam/workload-identity-federation_bootstrap_kubernetes/_index.md new file mode 100644 index 0000000..8ab8eb0 --- /dev/null +++ b/docs/tcloud/iam/workload-identity-federation_bootstrap_kubernetes/_index.md @@ -0,0 +1,75 @@ +--- +linkTitle: "tcloud iam workload-identity-federation bootstrap kubernetes" +title: "iam workload-identity-federation bootstrap kubernetes" +slug: tcloud_iam_workload-identity-federation_bootstrap_kubernetes +url: /docs/tcloud/iam/workload-identity-federation_bootstrap_kubernetes/ +weight: 9908 +cascade: + type: docs +--- +## tcloud iam workload-identity-federation bootstrap kubernetes + +Bootstrap workload identity for Kubernetes service accounts + +### Synopsis + +Binds system:serviceaccount:: to a Thalassa service account via a federated identity. + +Thalassa clusters: pass --cluster to resolve the cluster and use the platform-managed federated identity +provider labelled kubernetes_cluster_id=. If that provider is missing, bootstrap fails +(you must wait for cluster OIDC integration). + +Other clusters: pass --issuer with the same URL as kube-apiserver --service-account-issuer; the CLI +creates the federated identity provider if it does not exist yet. + +``` +tcloud iam workload-identity-federation bootstrap kubernetes [flags] +``` + +### Examples + +``` + # Thalassa-managed cluster (OIDC provider from label kubernetes_cluster_id) + tcloud iam workload-identity-federation bootstrap kubernetes --cluster my-cluster-slug \ + --namespace default --service-account my-app --role deployer + + # Self-managed / custom issuer + tcloud iam workload-identity-federation bootstrap kubernetes --issuer https://k8s.example.com \ + --namespace cicd --service-account terraform --role deployer +``` + +### Options + +``` + --cluster string Thalassa cluster identity, slug, or name (uses federated identity provider with label kubernetes_cluster_id=) + -h, --help help for kubernetes + --issuer string kube-apiserver service-account issuer URL (required without --cluster); with --cluster must match the cluster OIDC provider or omit + --namespace string Kubernetes namespace for the workload service account (required) + --service-account string Kubernetes service account name (required) +``` + +### Options inherited from parent commands + +``` + --access-token string Access Token authentication (overrides context) + --api string API endpoint (overrides context) + --client-id string OIDC client ID for OIDC authentication (overrides context) + --client-secret string OIDC client secret for OIDC authentication (overrides context) + -c, --context string Context name + --debug Debug mode + --dry-run Print planned changes without calling the API + --name string Base name for the Thalassa service account and federated identity (federated identity becomes -fi; default: wif--) + --no-hints Do not print platform hints after bootstrap + -O, --organisation string Organisation slug or identity (overrides context) + --provider-description string Optional description when creating the federated identity provider + --provider-name string Optional display name when creating the federated identity provider + --role string Organisation role identity, slug, or name (required) + --scope strings Federated identity allowed scopes: api:read, api:write, kubernetes, objectStorage (default: api:read,api:write) + --token string Personal access token (overrides context) + --trusted-audience strings JWT aud values to trust (repeatable; default: current context API URL, e.g. https://api.thalassa.cloud) +``` + +### SEE ALSO + +* [tcloud iam workload-identity-federation bootstrap](/docs/tcloud/iam/workload-identity-federation_bootstrap/) - Provision workload identity for GitHub, GitLab, or Kubernetes + diff --git a/docs/tcloud/kubernetes/_index.md b/docs/tcloud/kubernetes/_index.md index 60d7ad3..80d9e69 100644 --- a/docs/tcloud/kubernetes/_index.md +++ b/docs/tcloud/kubernetes/_index.md @@ -3,7 +3,7 @@ linkTitle: "tcloud kubernetes" title: "kubernetes" slug: tcloud_kubernetes url: /docs/tcloud/tcloud_kubernetes/ -weight: 9957 +weight: 9887 cascade: type: docs --- diff --git a/docs/tcloud/kubernetes/connect/_index.md b/docs/tcloud/kubernetes/connect/_index.md index 001a6b6..eb02008 100644 --- a/docs/tcloud/kubernetes/connect/_index.md +++ b/docs/tcloud/kubernetes/connect/_index.md @@ -3,7 +3,7 @@ linkTitle: "tcloud kubernetes connect" title: "kubernetes connect" slug: tcloud_kubernetes_connect url: /docs/tcloud/kubernetes/connect/ -weight: 9974 +weight: 9904 cascade: type: docs --- diff --git a/docs/tcloud/kubernetes/create/_index.md b/docs/tcloud/kubernetes/create/_index.md index f27ae27..6db7282 100644 --- a/docs/tcloud/kubernetes/create/_index.md +++ b/docs/tcloud/kubernetes/create/_index.md @@ -3,7 +3,7 @@ linkTitle: "tcloud kubernetes create" title: "kubernetes create" slug: tcloud_kubernetes_create url: /docs/tcloud/kubernetes/create/ -weight: 9972 +weight: 9902 cascade: type: docs --- diff --git a/docs/tcloud/kubernetes/delete/_index.md b/docs/tcloud/kubernetes/delete/_index.md index 178f0b4..e2a5bfd 100644 --- a/docs/tcloud/kubernetes/delete/_index.md +++ b/docs/tcloud/kubernetes/delete/_index.md @@ -3,7 +3,7 @@ linkTitle: "tcloud kubernetes delete" title: "kubernetes delete" slug: tcloud_kubernetes_delete url: /docs/tcloud/kubernetes/delete/ -weight: 9970 +weight: 9900 cascade: type: docs --- diff --git a/docs/tcloud/kubernetes/kubeconfig/_index.md b/docs/tcloud/kubernetes/kubeconfig/_index.md index 37a2ae7..f4ec3f3 100644 --- a/docs/tcloud/kubernetes/kubeconfig/_index.md +++ b/docs/tcloud/kubernetes/kubeconfig/_index.md @@ -3,7 +3,7 @@ linkTitle: "tcloud kubernetes kubeconfig" title: "kubernetes kubeconfig" slug: tcloud_kubernetes_kubeconfig url: /docs/tcloud/kubernetes/kubeconfig/ -weight: 9969 +weight: 9899 cascade: type: docs --- diff --git a/docs/tcloud/kubernetes/list/_index.md b/docs/tcloud/kubernetes/list/_index.md index 8541aa4..592fd1a 100644 --- a/docs/tcloud/kubernetes/list/_index.md +++ b/docs/tcloud/kubernetes/list/_index.md @@ -3,7 +3,7 @@ linkTitle: "tcloud kubernetes list" title: "kubernetes list" slug: tcloud_kubernetes_list url: /docs/tcloud/kubernetes/list/ -weight: 9967 +weight: 9897 cascade: type: docs --- @@ -22,8 +22,9 @@ tcloud kubernetes list [flags] ### Options ``` - -h, --help help for list - --no-header Do not print the header + -h, --help help for list + --no-header Do not print the header + --vpc string VPC ID ``` ### Options inherited from parent commands diff --git a/docs/tcloud/kubernetes/nodepools/_index.md b/docs/tcloud/kubernetes/nodepools/_index.md index 8e03344..e18a65f 100644 --- a/docs/tcloud/kubernetes/nodepools/_index.md +++ b/docs/tcloud/kubernetes/nodepools/_index.md @@ -3,7 +3,7 @@ linkTitle: "tcloud kubernetes nodepools" title: "kubernetes nodepools" slug: tcloud_kubernetes_nodepools url: /docs/tcloud/kubernetes/nodepools/ -weight: 9962 +weight: 9892 cascade: type: docs --- diff --git a/docs/tcloud/kubernetes/nodepools_create/_index.md b/docs/tcloud/kubernetes/nodepools_create/_index.md index bfb47d1..94bafd1 100644 --- a/docs/tcloud/kubernetes/nodepools_create/_index.md +++ b/docs/tcloud/kubernetes/nodepools_create/_index.md @@ -3,7 +3,7 @@ linkTitle: "tcloud kubernetes nodepools create" title: "kubernetes nodepools create" slug: tcloud_kubernetes_nodepools_create url: /docs/tcloud/kubernetes/nodepools_create/ -weight: 9966 +weight: 9896 cascade: type: docs --- diff --git a/docs/tcloud/kubernetes/nodepools_delete/_index.md b/docs/tcloud/kubernetes/nodepools_delete/_index.md index 82be78b..25b6b89 100644 --- a/docs/tcloud/kubernetes/nodepools_delete/_index.md +++ b/docs/tcloud/kubernetes/nodepools_delete/_index.md @@ -3,7 +3,7 @@ linkTitle: "tcloud kubernetes nodepools delete" title: "kubernetes nodepools delete" slug: tcloud_kubernetes_nodepools_delete url: /docs/tcloud/kubernetes/nodepools_delete/ -weight: 9965 +weight: 9895 cascade: type: docs --- @@ -20,13 +20,13 @@ The cluster must be in a ready state to delete node pools. Examples: # Delete a node pool - tcloud kubernetes nodepools delete --cluster my-cluster --name worker-pool + tcloud kubernetes nodepools delete --cluster my-cluster --nodepool worker-pool # Delete a node pool and wait for completion - tcloud kubernetes nodepools delete --cluster my-cluster --name worker-pool --wait + tcloud kubernetes nodepools delete --cluster my-cluster --nodepool worker-pool --wait # Delete a node pool without confirmation - tcloud kubernetes nodepools delete --cluster my-cluster --name worker-pool --force + tcloud kubernetes nodepools delete --cluster my-cluster --nodepool worker-pool --force ``` tcloud kubernetes nodepools delete [flags] @@ -35,11 +35,11 @@ tcloud kubernetes nodepools delete [flags] ### Options ``` - --cluster string Cluster identity, name, or slug (required) - --force Skip confirmation prompt - -h, --help help for delete - --name string Node pool name, identity, or slug (required) - --wait Wait for the node pool to be deleted before returning + --cluster string Cluster identity, name, or slug (required) + --force Skip confirmation prompt + -h, --help help for delete + --nodepool string Node pool name, identity, or slug (required) + --wait Wait for the node pool to be deleted before returning ``` ### Options inherited from parent commands diff --git a/docs/tcloud/kubernetes/nodepools_list/_index.md b/docs/tcloud/kubernetes/nodepools_list/_index.md index fa522b4..187c836 100644 --- a/docs/tcloud/kubernetes/nodepools_list/_index.md +++ b/docs/tcloud/kubernetes/nodepools_list/_index.md @@ -3,7 +3,7 @@ linkTitle: "tcloud kubernetes nodepools list" title: "kubernetes nodepools list" slug: tcloud_kubernetes_nodepools_list url: /docs/tcloud/kubernetes/nodepools_list/ -weight: 9964 +weight: 9894 cascade: type: docs --- diff --git a/docs/tcloud/kubernetes/nodepools_update/_index.md b/docs/tcloud/kubernetes/nodepools_update/_index.md index 9be8f38..5db3498 100644 --- a/docs/tcloud/kubernetes/nodepools_update/_index.md +++ b/docs/tcloud/kubernetes/nodepools_update/_index.md @@ -3,7 +3,7 @@ linkTitle: "tcloud kubernetes nodepools update" title: "kubernetes nodepools update" slug: tcloud_kubernetes_nodepools_update url: /docs/tcloud/kubernetes/nodepools_update/ -weight: 9963 +weight: 9893 cascade: type: docs --- diff --git a/docs/tcloud/kubernetes/update/_index.md b/docs/tcloud/kubernetes/update/_index.md index 4323a1a..10e4ebf 100644 --- a/docs/tcloud/kubernetes/update/_index.md +++ b/docs/tcloud/kubernetes/update/_index.md @@ -3,7 +3,7 @@ linkTitle: "tcloud kubernetes update" title: "kubernetes update" slug: tcloud_kubernetes_update url: /docs/tcloud/kubernetes/update/ -weight: 9960 +weight: 9890 cascade: type: docs --- diff --git a/docs/tcloud/kubernetes/upgrade/_index.md b/docs/tcloud/kubernetes/upgrade/_index.md index 629b1f6..309c485 100644 --- a/docs/tcloud/kubernetes/upgrade/_index.md +++ b/docs/tcloud/kubernetes/upgrade/_index.md @@ -3,7 +3,7 @@ linkTitle: "tcloud kubernetes upgrade" title: "kubernetes upgrade" slug: tcloud_kubernetes_upgrade url: /docs/tcloud/kubernetes/upgrade/ -weight: 9959 +weight: 9889 cascade: type: docs --- diff --git a/docs/tcloud/kubernetes/versions/_index.md b/docs/tcloud/kubernetes/versions/_index.md index 7952608..99c1a0b 100644 --- a/docs/tcloud/kubernetes/versions/_index.md +++ b/docs/tcloud/kubernetes/versions/_index.md @@ -3,7 +3,7 @@ linkTitle: "tcloud kubernetes versions" title: "kubernetes versions" slug: tcloud_kubernetes_versions url: /docs/tcloud/kubernetes/versions/ -weight: 9958 +weight: 9888 cascade: type: docs --- diff --git a/docs/tcloud/me/_index.md b/docs/tcloud/me/_index.md index 37c6ef6..0a234eb 100644 --- a/docs/tcloud/me/_index.md +++ b/docs/tcloud/me/_index.md @@ -3,7 +3,7 @@ linkTitle: "tcloud me" title: "me" slug: tcloud_me url: /docs/tcloud/tcloud_me/ -weight: 9955 +weight: 9885 cascade: type: docs --- diff --git a/docs/tcloud/me/organisations/_index.md b/docs/tcloud/me/organisations/_index.md index 2c2d00a..033749f 100644 --- a/docs/tcloud/me/organisations/_index.md +++ b/docs/tcloud/me/organisations/_index.md @@ -3,7 +3,7 @@ linkTitle: "tcloud me organisations" title: "me organisations" slug: tcloud_me_organisations url: /docs/tcloud/me/organisations/ -weight: 9956 +weight: 9886 cascade: type: docs --- diff --git a/docs/tcloud/networking/_index.md b/docs/tcloud/networking/_index.md index 8e79344..2e51eb2 100644 --- a/docs/tcloud/networking/_index.md +++ b/docs/tcloud/networking/_index.md @@ -3,7 +3,7 @@ linkTitle: "tcloud networking" title: "networking" slug: tcloud_networking url: /docs/tcloud/tcloud_networking/ -weight: 9928 +weight: 9858 cascade: type: docs --- diff --git a/docs/tcloud/networking/natgateways/_index.md b/docs/tcloud/networking/natgateways/_index.md index 0305f20..855dbf9 100644 --- a/docs/tcloud/networking/natgateways/_index.md +++ b/docs/tcloud/networking/natgateways/_index.md @@ -3,7 +3,7 @@ linkTitle: "tcloud networking natgateways" title: "networking natgateways" slug: tcloud_networking_natgateways url: /docs/tcloud/networking/natgateways/ -weight: 9951 +weight: 9881 cascade: type: docs --- diff --git a/docs/tcloud/networking/natgateways_delete/_index.md b/docs/tcloud/networking/natgateways_delete/_index.md index 1244b69..8119326 100644 --- a/docs/tcloud/networking/natgateways_delete/_index.md +++ b/docs/tcloud/networking/natgateways_delete/_index.md @@ -3,7 +3,7 @@ linkTitle: "tcloud networking natgateways delete" title: "networking natgateways delete" slug: tcloud_networking_natgateways_delete url: /docs/tcloud/networking/natgateways_delete/ -weight: 9954 +weight: 9884 cascade: type: docs --- diff --git a/docs/tcloud/networking/natgateways_list/_index.md b/docs/tcloud/networking/natgateways_list/_index.md index 8e32a13..99fc9e3 100644 --- a/docs/tcloud/networking/natgateways_list/_index.md +++ b/docs/tcloud/networking/natgateways_list/_index.md @@ -3,7 +3,7 @@ linkTitle: "tcloud networking natgateways list" title: "networking natgateways list" slug: tcloud_networking_natgateways_list url: /docs/tcloud/networking/natgateways_list/ -weight: 9953 +weight: 9883 cascade: type: docs --- diff --git a/docs/tcloud/networking/natgateways_view/_index.md b/docs/tcloud/networking/natgateways_view/_index.md index 819a5e2..15bbfbb 100644 --- a/docs/tcloud/networking/natgateways_view/_index.md +++ b/docs/tcloud/networking/natgateways_view/_index.md @@ -3,7 +3,7 @@ linkTitle: "tcloud networking natgateways view" title: "networking natgateways view" slug: tcloud_networking_natgateways_view url: /docs/tcloud/networking/natgateways_view/ -weight: 9952 +weight: 9882 cascade: type: docs --- diff --git a/docs/tcloud/networking/routetables/_index.md b/docs/tcloud/networking/routetables/_index.md index f6d37c7..6675901 100644 --- a/docs/tcloud/networking/routetables/_index.md +++ b/docs/tcloud/networking/routetables/_index.md @@ -3,7 +3,7 @@ linkTitle: "tcloud networking routetables" title: "networking routetables" slug: tcloud_networking_routetables url: /docs/tcloud/networking/routetables/ -weight: 9949 +weight: 9879 cascade: type: docs --- diff --git a/docs/tcloud/networking/routetables_list/_index.md b/docs/tcloud/networking/routetables_list/_index.md index c61b1cd..a71c424 100644 --- a/docs/tcloud/networking/routetables_list/_index.md +++ b/docs/tcloud/networking/routetables_list/_index.md @@ -3,7 +3,7 @@ linkTitle: "tcloud networking routetables list" title: "networking routetables list" slug: tcloud_networking_routetables_list url: /docs/tcloud/networking/routetables_list/ -weight: 9950 +weight: 9880 cascade: type: docs --- diff --git a/docs/tcloud/networking/security-groups/_index.md b/docs/tcloud/networking/security-groups/_index.md index cad6043..1aca15f 100644 --- a/docs/tcloud/networking/security-groups/_index.md +++ b/docs/tcloud/networking/security-groups/_index.md @@ -3,7 +3,7 @@ linkTitle: "tcloud networking security-groups" title: "networking security-groups" slug: tcloud_networking_security-groups url: /docs/tcloud/networking/security-groups/ -weight: 9944 +weight: 9874 cascade: type: docs --- diff --git a/docs/tcloud/networking/security-groups_create/_index.md b/docs/tcloud/networking/security-groups_create/_index.md index aae79aa..a895b2c 100644 --- a/docs/tcloud/networking/security-groups_create/_index.md +++ b/docs/tcloud/networking/security-groups_create/_index.md @@ -3,7 +3,7 @@ linkTitle: "tcloud networking security-groups create" title: "networking security-groups create" slug: tcloud_networking_security-groups_create url: /docs/tcloud/networking/security-groups_create/ -weight: 9948 +weight: 9878 cascade: type: docs --- diff --git a/docs/tcloud/networking/security-groups_delete/_index.md b/docs/tcloud/networking/security-groups_delete/_index.md index 120d777..a367b1d 100644 --- a/docs/tcloud/networking/security-groups_delete/_index.md +++ b/docs/tcloud/networking/security-groups_delete/_index.md @@ -3,7 +3,7 @@ linkTitle: "tcloud networking security-groups delete" title: "networking security-groups delete" slug: tcloud_networking_security-groups_delete url: /docs/tcloud/networking/security-groups_delete/ -weight: 9947 +weight: 9877 cascade: type: docs --- diff --git a/docs/tcloud/networking/security-groups_list/_index.md b/docs/tcloud/networking/security-groups_list/_index.md index bc4dc0d..62f2c0f 100644 --- a/docs/tcloud/networking/security-groups_list/_index.md +++ b/docs/tcloud/networking/security-groups_list/_index.md @@ -3,7 +3,7 @@ linkTitle: "tcloud networking security-groups list" title: "networking security-groups list" slug: tcloud_networking_security-groups_list url: /docs/tcloud/networking/security-groups_list/ -weight: 9946 +weight: 9876 cascade: type: docs --- diff --git a/docs/tcloud/networking/security-groups_view/_index.md b/docs/tcloud/networking/security-groups_view/_index.md index fc51d34..c860030 100644 --- a/docs/tcloud/networking/security-groups_view/_index.md +++ b/docs/tcloud/networking/security-groups_view/_index.md @@ -3,7 +3,7 @@ linkTitle: "tcloud networking security-groups view" title: "networking security-groups view" slug: tcloud_networking_security-groups_view url: /docs/tcloud/networking/security-groups_view/ -weight: 9945 +weight: 9875 cascade: type: docs --- diff --git a/docs/tcloud/networking/subnets/_index.md b/docs/tcloud/networking/subnets/_index.md index 23c8510..b3fe833 100644 --- a/docs/tcloud/networking/subnets/_index.md +++ b/docs/tcloud/networking/subnets/_index.md @@ -3,7 +3,7 @@ linkTitle: "tcloud networking subnets" title: "networking subnets" slug: tcloud_networking_subnets url: /docs/tcloud/networking/subnets/ -weight: 9940 +weight: 9870 cascade: type: docs --- diff --git a/docs/tcloud/networking/subnets_create/_index.md b/docs/tcloud/networking/subnets_create/_index.md index 447bc60..d392c62 100644 --- a/docs/tcloud/networking/subnets_create/_index.md +++ b/docs/tcloud/networking/subnets_create/_index.md @@ -3,7 +3,7 @@ linkTitle: "tcloud networking subnets create" title: "networking subnets create" slug: tcloud_networking_subnets_create url: /docs/tcloud/networking/subnets_create/ -weight: 9943 +weight: 9873 cascade: type: docs --- @@ -24,6 +24,7 @@ tcloud networking subnets create [flags] --name string Name of the subnet --no-header Do not print the header --vpc string VPC of the subnet + --wait Wait for the subnet to be ready before returning ``` ### Options inherited from parent commands diff --git a/docs/tcloud/networking/subnets_delete/_index.md b/docs/tcloud/networking/subnets_delete/_index.md index 85a340e..6f034b9 100644 --- a/docs/tcloud/networking/subnets_delete/_index.md +++ b/docs/tcloud/networking/subnets_delete/_index.md @@ -3,7 +3,7 @@ linkTitle: "tcloud networking subnets delete" title: "networking subnets delete" slug: tcloud_networking_subnets_delete url: /docs/tcloud/networking/subnets_delete/ -weight: 9942 +weight: 9872 cascade: type: docs --- diff --git a/docs/tcloud/networking/subnets_list/_index.md b/docs/tcloud/networking/subnets_list/_index.md index ddbf4f2..5caaa74 100644 --- a/docs/tcloud/networking/subnets_list/_index.md +++ b/docs/tcloud/networking/subnets_list/_index.md @@ -3,7 +3,7 @@ linkTitle: "tcloud networking subnets list" title: "networking subnets list" slug: tcloud_networking_subnets_list url: /docs/tcloud/networking/subnets_list/ -weight: 9941 +weight: 9871 cascade: type: docs --- @@ -22,6 +22,7 @@ tcloud networking subnets list [flags] --no-header Do not print the header -l, --selector string Label selector to filter subnets (format: key1=value1,key2=value2) --show-labels Show labels + --vpc string Filter by VPC ``` ### Options inherited from parent commands diff --git a/docs/tcloud/networking/vpc-peering/_index.md b/docs/tcloud/networking/vpc-peering/_index.md index 0d97f87..47c1405 100644 --- a/docs/tcloud/networking/vpc-peering/_index.md +++ b/docs/tcloud/networking/vpc-peering/_index.md @@ -3,7 +3,7 @@ linkTitle: "tcloud networking vpc-peering" title: "networking vpc-peering" slug: tcloud_networking_vpc-peering url: /docs/tcloud/networking/vpc-peering/ -weight: 9933 +weight: 9863 cascade: type: docs --- diff --git a/docs/tcloud/networking/vpc-peering_accept/_index.md b/docs/tcloud/networking/vpc-peering_accept/_index.md index a7c6e9d..7c0070e 100644 --- a/docs/tcloud/networking/vpc-peering_accept/_index.md +++ b/docs/tcloud/networking/vpc-peering_accept/_index.md @@ -3,7 +3,7 @@ linkTitle: "tcloud networking vpc-peering accept" title: "networking vpc-peering accept" slug: tcloud_networking_vpc-peering_accept url: /docs/tcloud/networking/vpc-peering_accept/ -weight: 9939 +weight: 9869 cascade: type: docs --- diff --git a/docs/tcloud/networking/vpc-peering_create/_index.md b/docs/tcloud/networking/vpc-peering_create/_index.md index b5abdda..2ff60e2 100644 --- a/docs/tcloud/networking/vpc-peering_create/_index.md +++ b/docs/tcloud/networking/vpc-peering_create/_index.md @@ -3,7 +3,7 @@ linkTitle: "tcloud networking vpc-peering create" title: "networking vpc-peering create" slug: tcloud_networking_vpc-peering_create url: /docs/tcloud/networking/vpc-peering_create/ -weight: 9938 +weight: 9868 cascade: type: docs --- diff --git a/docs/tcloud/networking/vpc-peering_delete/_index.md b/docs/tcloud/networking/vpc-peering_delete/_index.md index 4fea99d..17adf59 100644 --- a/docs/tcloud/networking/vpc-peering_delete/_index.md +++ b/docs/tcloud/networking/vpc-peering_delete/_index.md @@ -3,7 +3,7 @@ linkTitle: "tcloud networking vpc-peering delete" title: "networking vpc-peering delete" slug: tcloud_networking_vpc-peering_delete url: /docs/tcloud/networking/vpc-peering_delete/ -weight: 9937 +weight: 9867 cascade: type: docs --- diff --git a/docs/tcloud/networking/vpc-peering_list/_index.md b/docs/tcloud/networking/vpc-peering_list/_index.md index cef6f05..d1f434d 100644 --- a/docs/tcloud/networking/vpc-peering_list/_index.md +++ b/docs/tcloud/networking/vpc-peering_list/_index.md @@ -3,7 +3,7 @@ linkTitle: "tcloud networking vpc-peering list" title: "networking vpc-peering list" slug: tcloud_networking_vpc-peering_list url: /docs/tcloud/networking/vpc-peering_list/ -weight: 9936 +weight: 9866 cascade: type: docs --- diff --git a/docs/tcloud/networking/vpc-peering_reject/_index.md b/docs/tcloud/networking/vpc-peering_reject/_index.md index cc967ff..1c18918 100644 --- a/docs/tcloud/networking/vpc-peering_reject/_index.md +++ b/docs/tcloud/networking/vpc-peering_reject/_index.md @@ -3,7 +3,7 @@ linkTitle: "tcloud networking vpc-peering reject" title: "networking vpc-peering reject" slug: tcloud_networking_vpc-peering_reject url: /docs/tcloud/networking/vpc-peering_reject/ -weight: 9935 +weight: 9865 cascade: type: docs --- diff --git a/docs/tcloud/networking/vpc-peering_update/_index.md b/docs/tcloud/networking/vpc-peering_update/_index.md index 1f8773d..1e00613 100644 --- a/docs/tcloud/networking/vpc-peering_update/_index.md +++ b/docs/tcloud/networking/vpc-peering_update/_index.md @@ -3,7 +3,7 @@ linkTitle: "tcloud networking vpc-peering update" title: "networking vpc-peering update" slug: tcloud_networking_vpc-peering_update url: /docs/tcloud/networking/vpc-peering_update/ -weight: 9934 +weight: 9864 cascade: type: docs --- diff --git a/docs/tcloud/networking/vpcs/_index.md b/docs/tcloud/networking/vpcs/_index.md index 3e19515..c080548 100644 --- a/docs/tcloud/networking/vpcs/_index.md +++ b/docs/tcloud/networking/vpcs/_index.md @@ -3,7 +3,7 @@ linkTitle: "tcloud networking vpcs" title: "networking vpcs" slug: tcloud_networking_vpcs url: /docs/tcloud/networking/vpcs/ -weight: 9929 +weight: 9859 cascade: type: docs --- diff --git a/docs/tcloud/networking/vpcs_create/_index.md b/docs/tcloud/networking/vpcs_create/_index.md index e850af8..6edadec 100644 --- a/docs/tcloud/networking/vpcs_create/_index.md +++ b/docs/tcloud/networking/vpcs_create/_index.md @@ -3,7 +3,7 @@ linkTitle: "tcloud networking vpcs create" title: "networking vpcs create" slug: tcloud_networking_vpcs_create url: /docs/tcloud/networking/vpcs_create/ -weight: 9932 +weight: 9862 cascade: type: docs --- @@ -24,6 +24,7 @@ tcloud networking vpcs create [flags] --name string Name of the vpc --no-header Do not print the header --region string Region of the vpc + --wait Wait for the VPC to be ready before returning ``` ### Options inherited from parent commands diff --git a/docs/tcloud/networking/vpcs_delete/_index.md b/docs/tcloud/networking/vpcs_delete/_index.md index 8c4bdf7..b3c7386 100644 --- a/docs/tcloud/networking/vpcs_delete/_index.md +++ b/docs/tcloud/networking/vpcs_delete/_index.md @@ -3,7 +3,7 @@ linkTitle: "tcloud networking vpcs delete" title: "networking vpcs delete" slug: tcloud_networking_vpcs_delete url: /docs/tcloud/networking/vpcs_delete/ -weight: 9931 +weight: 9861 cascade: type: docs --- diff --git a/docs/tcloud/networking/vpcs_list/_index.md b/docs/tcloud/networking/vpcs_list/_index.md index e1c7c5e..629f219 100644 --- a/docs/tcloud/networking/vpcs_list/_index.md +++ b/docs/tcloud/networking/vpcs_list/_index.md @@ -3,7 +3,7 @@ linkTitle: "tcloud networking vpcs list" title: "networking vpcs list" slug: tcloud_networking_vpcs_list url: /docs/tcloud/networking/vpcs_list/ -weight: 9930 +weight: 9860 cascade: type: docs --- diff --git a/docs/tcloud/object-storage/_index.md b/docs/tcloud/object-storage/_index.md index e606718..9538b7d 100644 --- a/docs/tcloud/object-storage/_index.md +++ b/docs/tcloud/object-storage/_index.md @@ -3,7 +3,7 @@ linkTitle: "tcloud object-storage" title: "object-storage" slug: tcloud_object-storage url: /docs/tcloud/tcloud_object-storage/ -weight: 9923 +weight: 9853 cascade: type: docs --- diff --git a/docs/tcloud/object-storage/create/_index.md b/docs/tcloud/object-storage/create/_index.md index 3dccf71..2ad2c67 100644 --- a/docs/tcloud/object-storage/create/_index.md +++ b/docs/tcloud/object-storage/create/_index.md @@ -3,7 +3,7 @@ linkTitle: "tcloud object-storage create" title: "object-storage create" slug: tcloud_object-storage_create url: /docs/tcloud/object-storage/create/ -weight: 9927 +weight: 9857 cascade: type: docs --- diff --git a/docs/tcloud/object-storage/delete/_index.md b/docs/tcloud/object-storage/delete/_index.md index 30f61d8..7f01243 100644 --- a/docs/tcloud/object-storage/delete/_index.md +++ b/docs/tcloud/object-storage/delete/_index.md @@ -3,7 +3,7 @@ linkTitle: "tcloud object-storage delete" title: "object-storage delete" slug: tcloud_object-storage_delete url: /docs/tcloud/object-storage/delete/ -weight: 9926 +weight: 9856 cascade: type: docs --- diff --git a/docs/tcloud/object-storage/list/_index.md b/docs/tcloud/object-storage/list/_index.md index 7da8be9..82f1894 100644 --- a/docs/tcloud/object-storage/list/_index.md +++ b/docs/tcloud/object-storage/list/_index.md @@ -3,7 +3,7 @@ linkTitle: "tcloud object-storage list" title: "object-storage list" slug: tcloud_object-storage_list url: /docs/tcloud/object-storage/list/ -weight: 9925 +weight: 9855 cascade: type: docs --- diff --git a/docs/tcloud/object-storage/update/_index.md b/docs/tcloud/object-storage/update/_index.md index a7924d3..430748a 100644 --- a/docs/tcloud/object-storage/update/_index.md +++ b/docs/tcloud/object-storage/update/_index.md @@ -3,7 +3,7 @@ linkTitle: "tcloud object-storage update" title: "object-storage update" slug: tcloud_object-storage_update url: /docs/tcloud/object-storage/update/ -weight: 9924 +weight: 9854 cascade: type: docs --- diff --git a/docs/tcloud/oidc/_index.md b/docs/tcloud/oidc/_index.md index 75f4726..f6fecdc 100644 --- a/docs/tcloud/oidc/_index.md +++ b/docs/tcloud/oidc/_index.md @@ -3,7 +3,7 @@ linkTitle: "tcloud oidc" title: "oidc" slug: tcloud_oidc url: /docs/tcloud/tcloud_oidc/ -weight: 9921 +weight: 9851 cascade: type: docs --- diff --git a/docs/tcloud/oidc/token-exchange/_index.md b/docs/tcloud/oidc/token-exchange/_index.md index a0b276d..0d4f363 100644 --- a/docs/tcloud/oidc/token-exchange/_index.md +++ b/docs/tcloud/oidc/token-exchange/_index.md @@ -3,7 +3,7 @@ linkTitle: "tcloud oidc token-exchange" title: "oidc token-exchange" slug: tcloud_oidc_token-exchange url: /docs/tcloud/oidc/token-exchange/ -weight: 9922 +weight: 9852 cascade: type: docs --- diff --git a/docs/tcloud/regions/_index.md b/docs/tcloud/regions/_index.md index 4340eda..68043da 100644 --- a/docs/tcloud/regions/_index.md +++ b/docs/tcloud/regions/_index.md @@ -3,7 +3,7 @@ linkTitle: "tcloud regions" title: "regions" slug: tcloud_regions url: /docs/tcloud/tcloud_regions/ -weight: 9919 +weight: 9849 cascade: type: docs --- diff --git a/docs/tcloud/regions/list/_index.md b/docs/tcloud/regions/list/_index.md index a31742f..87325a0 100644 --- a/docs/tcloud/regions/list/_index.md +++ b/docs/tcloud/regions/list/_index.md @@ -3,7 +3,7 @@ linkTitle: "tcloud regions list" title: "regions list" slug: tcloud_regions_list url: /docs/tcloud/regions/list/ -weight: 9920 +weight: 9850 cascade: type: docs --- diff --git a/docs/tcloud/storage/_index.md b/docs/tcloud/storage/_index.md index d990bbb..7d926e7 100644 --- a/docs/tcloud/storage/_index.md +++ b/docs/tcloud/storage/_index.md @@ -3,7 +3,7 @@ linkTitle: "tcloud storage" title: "storage" slug: tcloud_storage url: /docs/tcloud/tcloud_storage/ -weight: 9907 +weight: 9831 cascade: type: docs --- @@ -34,5 +34,6 @@ Manage storage resources * [tcloud](/docs/tcloud/tcloud/) - A CLI for working with the Thalassa Cloud Platform * [tcloud storage snapshots](/docs/tcloud/storage/snapshots/) - Manage volume snapshots +* [tcloud storage tfs](/docs/tcloud/storage/tfs/) - Manage TFS (Thalassa File System) instances * [tcloud storage volumes](/docs/tcloud/storage/volumes/) - Manage storage volumes diff --git a/docs/tcloud/storage/snapshots/_index.md b/docs/tcloud/storage/snapshots/_index.md index b5fa832..700ae62 100644 --- a/docs/tcloud/storage/snapshots/_index.md +++ b/docs/tcloud/storage/snapshots/_index.md @@ -3,7 +3,7 @@ linkTitle: "tcloud storage snapshots" title: "storage snapshots" slug: tcloud_storage_snapshots url: /docs/tcloud/storage/snapshots/ -weight: 9915 +weight: 9845 cascade: type: docs --- diff --git a/docs/tcloud/storage/snapshots_create/_index.md b/docs/tcloud/storage/snapshots_create/_index.md index 7f82621..7b7d08f 100644 --- a/docs/tcloud/storage/snapshots_create/_index.md +++ b/docs/tcloud/storage/snapshots_create/_index.md @@ -3,7 +3,7 @@ linkTitle: "tcloud storage snapshots create" title: "storage snapshots create" slug: tcloud_storage_snapshots_create url: /docs/tcloud/storage/snapshots_create/ -weight: 9918 +weight: 9848 cascade: type: docs --- diff --git a/docs/tcloud/storage/snapshots_delete/_index.md b/docs/tcloud/storage/snapshots_delete/_index.md index 4345a7c..cddf9b6 100644 --- a/docs/tcloud/storage/snapshots_delete/_index.md +++ b/docs/tcloud/storage/snapshots_delete/_index.md @@ -3,7 +3,7 @@ linkTitle: "tcloud storage snapshots delete" title: "storage snapshots delete" slug: tcloud_storage_snapshots_delete url: /docs/tcloud/storage/snapshots_delete/ -weight: 9917 +weight: 9847 cascade: type: docs --- diff --git a/docs/tcloud/storage/snapshots_list/_index.md b/docs/tcloud/storage/snapshots_list/_index.md index 92c27a1..1ad04e7 100644 --- a/docs/tcloud/storage/snapshots_list/_index.md +++ b/docs/tcloud/storage/snapshots_list/_index.md @@ -3,7 +3,7 @@ linkTitle: "tcloud storage snapshots list" title: "storage snapshots list" slug: tcloud_storage_snapshots_list url: /docs/tcloud/storage/snapshots_list/ -weight: 9916 +weight: 9846 cascade: type: docs --- @@ -20,8 +20,11 @@ tcloud storage snapshots list [flags] ``` -h, --help help for list --no-header Do not print the header + --region string Region of the snapshot -l, --selector string Label selector to filter snapshots (format: key1=value1,key2=value2) --show-labels Show labels + --status string Status of the snapshot + --volume string Source volume of the snapshot ``` ### Options inherited from parent commands diff --git a/docs/tcloud/storage/tfs/_index.md b/docs/tcloud/storage/tfs/_index.md new file mode 100644 index 0000000..62eb355 --- /dev/null +++ b/docs/tcloud/storage/tfs/_index.md @@ -0,0 +1,41 @@ +--- +linkTitle: "tcloud storage tfs" +title: "storage tfs" +slug: tcloud_storage_tfs +url: /docs/tcloud/storage/tfs/ +weight: 9839 +cascade: + type: docs +--- +## tcloud storage tfs + +Manage TFS (Thalassa File System) instances + +### Options + +``` + -h, --help help for tfs +``` + +### Options inherited from parent commands + +``` + --access-token string Access Token authentication (overrides context) + --api string API endpoint (overrides context) + --client-id string OIDC client ID for OIDC authentication (overrides context) + --client-secret string OIDC client secret for OIDC authentication (overrides context) + -c, --context string Context name + --debug Debug mode + -O, --organisation string Organisation slug or identity (overrides context) + --token string Personal access token (overrides context) +``` + +### SEE ALSO + +* [tcloud storage](/docs/tcloud/tcloud_storage/) - Manage storage resources +* [tcloud storage tfs create](/docs/tcloud/storage/tfs_create/) - Create a TFS instance +* [tcloud storage tfs delete](/docs/tcloud/storage/tfs_delete/) - Delete TFS instance(s) +* [tcloud storage tfs list](/docs/tcloud/storage/tfs_list/) - Get a list of TFS instances +* [tcloud storage tfs update](/docs/tcloud/storage/tfs_update/) - Update a TFS instance +* [tcloud storage tfs view](/docs/tcloud/storage/tfs_view/) - View TFS instance details + diff --git a/docs/tcloud/storage/tfs_create/_index.md b/docs/tcloud/storage/tfs_create/_index.md new file mode 100644 index 0000000..f64fd22 --- /dev/null +++ b/docs/tcloud/storage/tfs_create/_index.md @@ -0,0 +1,55 @@ +--- +linkTitle: "tcloud storage tfs create" +title: "storage tfs create" +slug: tcloud_storage_tfs_create +url: /docs/tcloud/storage/tfs_create/ +weight: 9844 +cascade: + type: docs +--- +## tcloud storage tfs create + +Create a TFS instance + +### Synopsis + +Create a new TFS (Thalassa File System) instance for shared file storage. + +``` +tcloud storage tfs create [flags] +``` + +### Options + +``` + --annotations strings Annotations in key=value format (can be specified multiple times) + --delete-protection Enable delete protection + --description string Description of the TFS instance + -h, --help help for create + --labels strings Labels in key=value format (can be specified multiple times) + --name string Name of the TFS instance (required) + --no-header Do not print the header + --region string Region of the TFS instance (required) + --size int Size of the TFS instance in GB (required) (default 1) + --subnet string Subnet of the TFS instance (required) + --vpc string VPC of the TFS instance (required) + --wait Wait for the TFS instance to be available before returning +``` + +### Options inherited from parent commands + +``` + --access-token string Access Token authentication (overrides context) + --api string API endpoint (overrides context) + --client-id string OIDC client ID for OIDC authentication (overrides context) + --client-secret string OIDC client secret for OIDC authentication (overrides context) + -c, --context string Context name + --debug Debug mode + -O, --organisation string Organisation slug or identity (overrides context) + --token string Personal access token (overrides context) +``` + +### SEE ALSO + +* [tcloud storage tfs](/docs/tcloud/storage/tfs/) - Manage TFS (Thalassa File System) instances + diff --git a/docs/tcloud/storage/tfs_delete/_index.md b/docs/tcloud/storage/tfs_delete/_index.md new file mode 100644 index 0000000..812480c --- /dev/null +++ b/docs/tcloud/storage/tfs_delete/_index.md @@ -0,0 +1,47 @@ +--- +linkTitle: "tcloud storage tfs delete" +title: "storage tfs delete" +slug: tcloud_storage_tfs_delete +url: /docs/tcloud/storage/tfs_delete/ +weight: 9843 +cascade: + type: docs +--- +## tcloud storage tfs delete + +Delete TFS instance(s) + +### Synopsis + +Delete TFS instance(s) by identity or label selector. + +``` +tcloud storage tfs delete [flags] +``` + +### Options + +``` + --force Force the deletion and skip the confirmation + -h, --help help for delete + -l, --selector string Label selector to filter TFS instances (format: key1=value1,key2=value2) + --wait Wait for the TFS instance(s) to be deleted +``` + +### Options inherited from parent commands + +``` + --access-token string Access Token authentication (overrides context) + --api string API endpoint (overrides context) + --client-id string OIDC client ID for OIDC authentication (overrides context) + --client-secret string OIDC client secret for OIDC authentication (overrides context) + -c, --context string Context name + --debug Debug mode + -O, --organisation string Organisation slug or identity (overrides context) + --token string Personal access token (overrides context) +``` + +### SEE ALSO + +* [tcloud storage tfs](/docs/tcloud/storage/tfs/) - Manage TFS (Thalassa File System) instances + diff --git a/docs/tcloud/storage/tfs_list/_index.md b/docs/tcloud/storage/tfs_list/_index.md new file mode 100644 index 0000000..8c37d64 --- /dev/null +++ b/docs/tcloud/storage/tfs_list/_index.md @@ -0,0 +1,47 @@ +--- +linkTitle: "tcloud storage tfs list" +title: "storage tfs list" +slug: tcloud_storage_tfs_list +url: /docs/tcloud/storage/tfs_list/ +weight: 9842 +cascade: + type: docs +--- +## tcloud storage tfs list + +Get a list of TFS instances + +``` +tcloud storage tfs list [flags] +``` + +### Options + +``` + --exact-time Show exact time instead of relative time + -h, --help help for list + --no-header Do not print the header + --region string Region of the TFS instance + -l, --selector string Label selector to filter TFS instances (format: key1=value1,key2=value2) + --show-labels Show labels + --status string Status of the TFS instance + --vpc string VPC of the TFS instance +``` + +### Options inherited from parent commands + +``` + --access-token string Access Token authentication (overrides context) + --api string API endpoint (overrides context) + --client-id string OIDC client ID for OIDC authentication (overrides context) + --client-secret string OIDC client secret for OIDC authentication (overrides context) + -c, --context string Context name + --debug Debug mode + -O, --organisation string Organisation slug or identity (overrides context) + --token string Personal access token (overrides context) +``` + +### SEE ALSO + +* [tcloud storage tfs](/docs/tcloud/storage/tfs/) - Manage TFS (Thalassa File System) instances + diff --git a/docs/tcloud/storage/tfs_update/_index.md b/docs/tcloud/storage/tfs_update/_index.md new file mode 100644 index 0000000..3fb5e81 --- /dev/null +++ b/docs/tcloud/storage/tfs_update/_index.md @@ -0,0 +1,50 @@ +--- +linkTitle: "tcloud storage tfs update" +title: "storage tfs update" +slug: tcloud_storage_tfs_update +url: /docs/tcloud/storage/tfs_update/ +weight: 9841 +cascade: + type: docs +--- +## tcloud storage tfs update + +Update a TFS instance + +### Synopsis + +Update properties of an existing TFS instance. + +``` +tcloud storage tfs update [flags] +``` + +### Options + +``` + --annotations strings Annotations in key=value format (can be specified multiple times) + --delete-protection Enable or disable delete protection + --description string Description of the TFS instance + -h, --help help for update + --labels strings Labels in key=value format (can be specified multiple times) + --name string Name of the TFS instance + --no-header Do not print the header +``` + +### Options inherited from parent commands + +``` + --access-token string Access Token authentication (overrides context) + --api string API endpoint (overrides context) + --client-id string OIDC client ID for OIDC authentication (overrides context) + --client-secret string OIDC client secret for OIDC authentication (overrides context) + -c, --context string Context name + --debug Debug mode + -O, --organisation string Organisation slug or identity (overrides context) + --token string Personal access token (overrides context) +``` + +### SEE ALSO + +* [tcloud storage tfs](/docs/tcloud/storage/tfs/) - Manage TFS (Thalassa File System) instances + diff --git a/docs/tcloud/storage/tfs_view/_index.md b/docs/tcloud/storage/tfs_view/_index.md new file mode 100644 index 0000000..0858eee --- /dev/null +++ b/docs/tcloud/storage/tfs_view/_index.md @@ -0,0 +1,46 @@ +--- +linkTitle: "tcloud storage tfs view" +title: "storage tfs view" +slug: tcloud_storage_tfs_view +url: /docs/tcloud/storage/tfs_view/ +weight: 9840 +cascade: + type: docs +--- +## tcloud storage tfs view + +View TFS instance details + +### Synopsis + +View detailed information about a TFS instance. + +``` +tcloud storage tfs view [flags] +``` + +### Options + +``` + --exact-time Show exact time instead of relative time + -h, --help help for view + --no-header Do not print the header +``` + +### Options inherited from parent commands + +``` + --access-token string Access Token authentication (overrides context) + --api string API endpoint (overrides context) + --client-id string OIDC client ID for OIDC authentication (overrides context) + --client-secret string OIDC client secret for OIDC authentication (overrides context) + -c, --context string Context name + --debug Debug mode + -O, --organisation string Organisation slug or identity (overrides context) + --token string Personal access token (overrides context) +``` + +### SEE ALSO + +* [tcloud storage tfs](/docs/tcloud/storage/tfs/) - Manage TFS (Thalassa File System) instances + diff --git a/docs/tcloud/storage/volumes/_index.md b/docs/tcloud/storage/volumes/_index.md index 011d546..d43756e 100644 --- a/docs/tcloud/storage/volumes/_index.md +++ b/docs/tcloud/storage/volumes/_index.md @@ -3,7 +3,7 @@ linkTitle: "tcloud storage volumes" title: "storage volumes" slug: tcloud_storage_volumes url: /docs/tcloud/storage/volumes/ -weight: 9908 +weight: 9832 cascade: type: docs --- diff --git a/docs/tcloud/storage/volumes_attach/_index.md b/docs/tcloud/storage/volumes_attach/_index.md index c29d53d..2fec62c 100644 --- a/docs/tcloud/storage/volumes_attach/_index.md +++ b/docs/tcloud/storage/volumes_attach/_index.md @@ -3,7 +3,7 @@ linkTitle: "tcloud storage volumes attach" title: "storage volumes attach" slug: tcloud_storage_volumes_attach url: /docs/tcloud/storage/volumes_attach/ -weight: 9914 +weight: 9838 cascade: type: docs --- diff --git a/docs/tcloud/storage/volumes_create/_index.md b/docs/tcloud/storage/volumes_create/_index.md index 1704adb..cedba97 100644 --- a/docs/tcloud/storage/volumes_create/_index.md +++ b/docs/tcloud/storage/volumes_create/_index.md @@ -3,7 +3,7 @@ linkTitle: "tcloud storage volumes create" title: "storage volumes create" slug: tcloud_storage_volumes_create url: /docs/tcloud/storage/volumes_create/ -weight: 9913 +weight: 9837 cascade: type: docs --- diff --git a/docs/tcloud/storage/volumes_delete/_index.md b/docs/tcloud/storage/volumes_delete/_index.md index 4c23725..f6de2b2 100644 --- a/docs/tcloud/storage/volumes_delete/_index.md +++ b/docs/tcloud/storage/volumes_delete/_index.md @@ -3,7 +3,7 @@ linkTitle: "tcloud storage volumes delete" title: "storage volumes delete" slug: tcloud_storage_volumes_delete url: /docs/tcloud/storage/volumes_delete/ -weight: 9912 +weight: 9836 cascade: type: docs --- diff --git a/docs/tcloud/storage/volumes_detach/_index.md b/docs/tcloud/storage/volumes_detach/_index.md index 6e1d689..682b3c2 100644 --- a/docs/tcloud/storage/volumes_detach/_index.md +++ b/docs/tcloud/storage/volumes_detach/_index.md @@ -3,7 +3,7 @@ linkTitle: "tcloud storage volumes detach" title: "storage volumes detach" slug: tcloud_storage_volumes_detach url: /docs/tcloud/storage/volumes_detach/ -weight: 9911 +weight: 9835 cascade: type: docs --- diff --git a/docs/tcloud/storage/volumes_list/_index.md b/docs/tcloud/storage/volumes_list/_index.md index f1d46a1..c10cda2 100644 --- a/docs/tcloud/storage/volumes_list/_index.md +++ b/docs/tcloud/storage/volumes_list/_index.md @@ -3,7 +3,7 @@ linkTitle: "tcloud storage volumes list" title: "storage volumes list" slug: tcloud_storage_volumes_list url: /docs/tcloud/storage/volumes_list/ -weight: 9910 +weight: 9834 cascade: type: docs --- diff --git a/docs/tcloud/storage/volumes_resize/_index.md b/docs/tcloud/storage/volumes_resize/_index.md index 9147de3..c618a93 100644 --- a/docs/tcloud/storage/volumes_resize/_index.md +++ b/docs/tcloud/storage/volumes_resize/_index.md @@ -3,7 +3,7 @@ linkTitle: "tcloud storage volumes resize" title: "storage volumes resize" slug: tcloud_storage_volumes_resize url: /docs/tcloud/storage/volumes_resize/ -weight: 9909 +weight: 9833 cascade: type: docs --- diff --git a/docs/tcloud/tcloud.md b/docs/tcloud/tcloud.md index c771667..5f89851 100644 --- a/docs/tcloud/tcloud.md +++ b/docs/tcloud/tcloud.md @@ -3,7 +3,7 @@ linkTitle: "tcloud" title: "tcloud" slug: tcloud url: /docs/tcloud/tcloud/ -weight: 9905 +weight: 9829 cascade: type: docs --- @@ -27,10 +27,12 @@ A CLI for working with the Thalassa Cloud Platform ### SEE ALSO +* [tcloud api](/docs/tcloud/tcloud_api/) - Direct API access * [tcloud audit](/docs/tcloud/tcloud_audit/) - Manage organisation audit logs * [tcloud compute](/docs/tcloud/tcloud_compute/) - Manage compute resources * [tcloud context](/docs/tcloud/tcloud_context/) - Manage context * [tcloud dbaas](/docs/tcloud/tcloud_dbaas/) - Manage database clusters and related services +* [tcloud iam](/docs/tcloud/tcloud_iam/) - Identity and access management for your organisation * [tcloud kubernetes](/docs/tcloud/tcloud_kubernetes/) - Manage Kubernetes clusters, node pools and more services related to Kubernetes * [tcloud me](/docs/tcloud/tcloud_me/) - Get information about the current user * [tcloud networking](/docs/tcloud/tcloud_networking/) - Manage networking resources diff --git a/docs/tcloud/tcloud_version.md b/docs/tcloud/tcloud_version.md index ffc63d1..3bd4890 100644 --- a/docs/tcloud/tcloud_version.md +++ b/docs/tcloud/tcloud_version.md @@ -3,7 +3,7 @@ linkTitle: "tcloud version" title: "version" slug: tcloud_version url: /docs/tcloud/tcloud_version/ -weight: 9906 +weight: 9830 cascade: type: docs ---