diff --git a/.circleci/config.yml b/.circleci/config.yml index 1eaa0bbae..ea0353801 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -8,18 +8,24 @@ jobs: docker: - image: quay.io/astronomer/ap-dind-golang:24.0.6 resource_class: large + environment: + GOPRIVATE: github.com/astronomer/* steps: - lint test: working_directory: /go/src/github.com/astronomer/astro-cli docker: - image: quay.io/astronomer/ap-dind-golang:24.0.6 + environment: + GOPRIVATE: github.com/astronomer/* steps: - test integration-test: machine: resource_class: medium image: ubuntu-2204:current + environment: + GOPRIVATE: github.com/astronomer/* steps: - integration-test new-tag: @@ -29,11 +35,14 @@ jobs: - commit-next-tag release: docker: - - image: golang:1.24 + - image: golang:1.26 working_directory: /go/src/github.com/astronomer/astro-cli resource_class: xlarge + environment: + GOPRIVATE: github.com/astronomer/* steps: - checkout + - configure-private-modules - setup-cross-build-libs - run: name: goreleaser @@ -42,11 +51,17 @@ workflows: version: 2.1 build-images: jobs: - - lint - - test + - lint: + context: + - github-repo + - test: + context: + - github-repo - approve-run-integration-tests: type: approval - integration-test: + context: + - github-repo requires: - lint - test @@ -83,10 +98,31 @@ executors: docker: - image: cimg/python:3.11 commands: + configure-private-modules: + description: "Configure git for private Go module access" + steps: + - run: + name: Configure private Go modules + command: git config --global url."https://${GITHUB_TOKEN}@github.com/".insteadOf "https://github.com/" + install-go: + description: "Install Go 1.26.0 from official tarball" + steps: + - run: + name: Install Go 1.26.0 + command: | + set -e + cd /tmp + curl -Lo go.tar.gz https://go.dev/dl/go1.26.0.linux-amd64.tar.gz + rm -rf /usr/local/go + tar -C /usr/local -xzf go.tar.gz + echo 'export PATH="/usr/local/go/bin:$PATH"' >> $BASH_ENV + /usr/local/go/bin/go version lint: description: "Run linting checks" steps: - checkout + - install-go + - configure-private-modules - run: name: Run golangci-lint command: make lint @@ -94,6 +130,8 @@ commands: description: "Run tests and code coverage" steps: - checkout + - install-go + - configure-private-modules - setup_remote_docker - run: name: install dockerize @@ -112,16 +150,8 @@ commands: description: "Run integration tests" steps: - checkout - - run: - name: Install Go - command: | - set -e - cd /tmp - curl -Lo go.tar.gz https://go.dev/dl/go1.24.0.linux-amd64.tar.gz - sudo rm -rf /usr/local/go - sudo tar -C /usr/local -xvf go.tar.gz - GOROOT=/usr/local/go go version - echo 'export GOROOT=/usr/local/go' >> $BASH_ENV + - configure-private-modules + - install-go - run: name: Setup python env command: | diff --git a/.gitignore b/.gitignore index b78a50a43..7f6f2e820 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,8 @@ cover.out coverage-2.txt coverage.txt dist/ +go.work +go.work.sum hello-astro/ meta/ packages.txt diff --git a/BREAKING_CHANGES.md b/BREAKING_CHANGES.md new file mode 100644 index 000000000..dc65b23c2 --- /dev/null +++ b/BREAKING_CHANGES.md @@ -0,0 +1,126 @@ +# Astro CLI Breaking Changes (Nexus Integration) + +This document lists all breaking changes introduced by the Nexus integration. Commands marked as "removed" have been replaced by auto-generated API commands powered by Nexus. The new commands use the same noun-verb structure but accept API-level parameters instead of CLI-specific flags. + +## Removed Commands + +### Deployment + +| Old Command | Replacement | Notes | +|---|---|---| +| `deployment create` | `deployment create` (nexus) | Prompts for required fields interactively; see [Removed Features](#--deployment-file-flag) for `--deployment-file` removal | +| `deployment update` | `deployment update` (nexus) | Prompts for confirmation when args auto-filled from config; see [Removed Features](#--deployment-file-flag) for `--deployment-file` removal | +| `deployment delete` | `deployment delete` (nexus) | Prompts for confirmation; use `--force` to skip; no `--deployment-name` lookup | +| `deployment list` | `deployment list` (nexus) | No `--all` flag for cross-workspace listing; output is pretty-printed JSON instead of a formatted table | +| `deployment inspect` | `deployment get` | Output is pretty-printed JSON instead of a custom formatted YAML/JSON view; no `--key` field selector; no `--template` flag | +| `deployment hibernate` | `deployment-hibernation-override update` | No `--for` duration shorthand; no `--remove-override`; no `--deployment-name` lookup | +| `deployment wake-up` | `deployment-hibernation-override update` / `delete` | Same as hibernate; wake-up is now an update with `is_hibernating=false` or a delete of the override | +| `deployment user add` | `user-role update` | Scoped to org level; requires user ID instead of email; no interactive prompts | +| `deployment user list` | `user list` | Lists org-level users; no deployment-scoped filtering | +| `deployment user update` | `user-role update` | Scoped to org level; requires user ID instead of email; no interactive role prompt | +| `deployment user remove` | `user-role update` | Scoped to org level; requires user ID instead of email | +| `deployment team list` | `team list` | Lists org-level teams; no deployment-scoped filtering | +| `deployment team add` | `team-role update` | Scoped to org level; no interactive prompts | +| `deployment team update` | `team-role update` | Scoped to org level; no interactive role prompt | +| `deployment team remove` | `team-role update` | Scoped to org level | +| `deployment token list` | `api-token list` | Lists org-level tokens; no deployment-scoped filtering | +| `deployment token create` | `api-token create` | Creates org-level token with deployment role in body; no interactive prompts | +| `deployment token update` | `api-token update` | No `--name` lookup; requires token ID | +| `deployment token rotate` | `api-token rotate` | Prompts for confirmation; use `--force` to skip; no `--name` lookup | +| `deployment token delete` | `api-token delete` | Prompts for confirmation; use `--force` to skip; no `--name` lookup | +| `deployment token organization-token add` | `api-token-role update` | Different command path | +| `deployment token organization-token update` | `api-token-role update` | Different command path | +| `deployment token organization-token remove` | `api-token-role update` | Different command path | +| `deployment token organization-token list` | `api-token list` | Different command path | +| `deployment token workspace-token add` | `api-token-role update` | Different command path | +| `deployment token workspace-token update` | `api-token-role update` | Different command path | +| `deployment token workspace-token remove` | `api-token-role update` | Different command path | +| `deployment token workspace-token list` | `api-token list` | Different command path | + +### Workspace + +| Old Command | Replacement | Notes | +|---|---|---| +| `workspace create` | `workspace create` (nexus) | No `--enforce-cicd` shorthand flag | +| `workspace update` | `workspace update` (nexus) | No `--enforce-cicd` shorthand flag | +| `workspace delete` | `workspace delete` (nexus) | Prompts for confirmation; use `--force` to skip | +| `workspace list` | `workspace list` (nexus) | Output is pretty-printed JSON instead of a formatted table | +| `workspace user add` | `user-role update` | Scoped to org level; requires user ID instead of email; no interactive prompts | +| `workspace user list` | `user list` | Lists org-level users; no workspace-scoped filtering | +| `workspace user update` | `user-role update` | Scoped to org level; requires user ID instead of email; no interactive role prompt | +| `workspace user remove` | `user-role update` | Scoped to org level; requires user ID instead of email | +| `workspace team list` | `team list` | Lists org-level teams; no workspace-scoped filtering | +| `workspace team add` | `team-role update` | Scoped to org level; no interactive prompts | +| `workspace team update` | `team-role update` | Scoped to org level; no interactive role prompt | +| `workspace team remove` | `team-role update` | Scoped to org level | +| `workspace token list` | `api-token list` | Lists org-level tokens; no workspace-scoped filtering | +| `workspace token create` | `api-token create` | Creates org-level token with workspace role in body; no interactive prompts | +| `workspace token update` | `api-token update` | No `--name` lookup; requires token ID | +| `workspace token rotate` | `api-token rotate` | Prompts for confirmation; use `--force` to skip; no `--name` lookup | +| `workspace token delete` | `api-token delete` | Prompts for confirmation; use `--force` to skip; no `--name` lookup | +| `workspace token add` | `api-token-role update` | Different command path; was for adding org token to workspace | +| `workspace token organization-token add` | `api-token-role update` | Different command path | +| `workspace token organization-token update` | `api-token-role update` | Different command path | +| `workspace token organization-token remove` | `api-token-role update` | Different command path | +| `workspace token organization-token list` | `api-token list` | Different command path | + +### Organization + +| Old Command | Replacement | Notes | +|---|---|---| +| `organization list` | `organization list` (nexus) | Output is pretty-printed JSON instead of a formatted table | +| `organization user invite` | `user-invite create` | No interactive email prompt; different command path | +| `organization user list` | `user list` | Different command path (no longer under `organization`) | +| `organization user update` | `user-role update` | No interactive role selection prompt; different command path | +| `organization team create` | `team create` | No interactive role selection prompt; different command path | +| `organization team delete` | `team delete` | No interactive team selection prompt; different command path | +| `organization team list` | `team list` | Different command path | +| `organization team update` | `team update` | No interactive team selection prompt; different command path | +| `organization team user add` | `team-member add` | Different command path | +| `organization team user list` | `team-member list` | Different command path | +| `organization team user remove` | `team-member remove` | Different command path | +| `organization audit-logs export` | `organization-audit-log get` | No `--output-file` flag; no `--include` days parameter; output is not automatically saved to a GZIP file | +| `organization token create` | `api-token create` | No interactive name/role prompts; different command path | +| `organization token delete` | `api-token delete` | Prompts for confirmation; use `--force` to skip; different command path | +| `organization token list` | `api-token list` | Different command path | +| `organization token update` | `api-token update` | Different command path | +| `organization token rotate` | `api-token rotate` | Prompts for confirmation; use `--force` to skip; different command path | +| `organization token roles` | `api-token-role list` | Different command path | +| `organization role list` | `role list` | No `--include-default-roles` flag; different command path | + +## Removed Features + +### `--deployment-file` flag + +The `--deployment-file` flag on `deployment create` and `deployment update` has been removed. This flag allowed creating or updating a deployment from a YAML/JSON specification file that included: + +- Deployment configuration (name, executor, runtime version, cloud provider, region, etc.) +- Worker queue definitions with validation against API defaults +- Environment variables +- Alert email addresses +- Hibernation schedules +- Automatic cluster name-to-ID and workspace name-to-ID resolution + +**Migration**: Use the nexus `deployment create` / `deployment update` commands with individual flags, or use `astro api` for raw API access with a JSON body. + +## Behavioral Changes + +### Output format + +In a terminal, nexus-generated commands display pretty-printed JSON with 2-space indentation. When output is piped (non-terminal), raw compact JSON is returned for scripting compatibility (e.g., `astro deployment list | jq '.deployments[].id'`). API error responses are formatted as `Error (): ` instead of raw JSON. + +### Interactive prompts + +Nexus-generated commands now prompt interactively for missing required body fields when running in a terminal. Enum fields show a numbered selection menu. When piped, no prompts are shown and the API will return a validation error for missing fields. + +### Delete confirmation + +All `delete` commands now prompt for confirmation (`Are you sure you want to delete? [y/N]`) in a terminal. Use `--force` / `-f` to skip the prompt. Update commands still prompt only when positional arguments were auto-filled from config defaults. + +### Name-based lookups + +The old commands supported `--deployment-name` to look up deployments by name. The nexus-generated commands require the resource ID directly. Use `deployment list` to find the ID first. + +### Flag naming + +Nexus-generated commands use API field names in kebab-case (e.g., `--is-dag-deploy-enabled` instead of `--dag-deploy`, `--is-cicd-enforced` instead of `--enforce-cicd`). Run ` --help` to see available flags. diff --git a/cmd/cloud/deployment.go b/cmd/cloud/deployment.go index 7d199528e..dd3d98bd1 100644 --- a/cmd/cloud/deployment.go +++ b/cmd/cloud/deployment.go @@ -1,95 +1,36 @@ package cloud import ( - "fmt" "io" - "strings" - "time" - airflowversions "github.com/astronomer/astro-cli/airflow_versions" - astrocore "github.com/astronomer/astro-cli/astro-client-core" - astroplatformcore "github.com/astronomer/astro-cli/astro-client-platform-core" "github.com/astronomer/astro-cli/cloud/deployment" - "github.com/astronomer/astro-cli/cloud/deployment/fromfile" - "github.com/astronomer/astro-cli/cloud/organization" - "github.com/astronomer/astro-cli/cloud/team" - "github.com/astronomer/astro-cli/cloud/user" - "github.com/astronomer/astro-cli/pkg/httputil" - "github.com/astronomer/astro-cli/pkg/input" "github.com/pkg/errors" "github.com/spf13/cobra" - "github.com/spf13/pflag" ) const ( - enable = "enable" - disable = "disable" - standard = "standard" - dedicated = "dedicated" - - deploymentWaitTime = 600 * time.Second + enable = "enable" + disable = "disable" ) var ( - label string - runtimeVersion string - deploymentID string - logsKeyword string - forceDelete bool - description string - clusterID string - dagDeploy string - schedulerAU int - schedulerReplicas int - updateSchedulerReplicas int - updateSchedulerAU int - forceUpdate bool - allDeployments bool - warnLogs bool - errorLogs bool - infoLogs bool - waitForStatus bool - waitTimeForDeployment time.Duration - deploymentCreateEnforceCD bool - deploymentUpdateEnforceCD bool - logCount = 500 - variableKey string - variableValue string - useEnvFile bool - makeSecret bool - executor string - inputFile string - cloudProvider string - region string - schedulerSize string - highAvailability string - developmentMode string - cicdEnforcement string - defaultTaskPodMemory string - resourceQuotaCPU string - resourceQuotaMemory string - defaultTaskPodCPU string - addDeploymentRole string - updateDeploymentRole string - workloadIdentity string - until string - forDuration string - removeOverride bool - forceOverride bool - logApiserver bool - logWebserver bool - logScheduler bool - logWorkers bool - logTriggerer bool - flagRemoteExecutionEnabled bool - flagAllowedIPAddressRanges string - flagTaskLogBucket string - flagTaskLogURLPattern string - allowedIPAddressRanges *[]string - taskLogBucket *string - taskLogURLPattern *string + deploymentID string + logsKeyword string + description string + warnLogs bool + errorLogs bool + infoLogs bool + logCount = 500 + variableKey string + variableValue string + useEnvFile bool + makeSecret bool + logApiserver bool + logWebserver bool + logScheduler bool + logWorkers bool + logTriggerer bool - deploymentType = standard deploymentVariableListExample = ` # List a deployment's variables $ astro deployment variable list --deployment-id --key FOO @@ -108,10 +49,6 @@ var ( # Update a deployment variables from a file $ astro deployment variable update --deployment-id --load --env .env.my-deployment ` - httpClient = httputil.NewHTTPClient() - errFlag = errors.New("--deployment-file can not be used with other arguments") - errInvalidExecutor = errors.New("not a valid executor") - errInvalidCloudProvider = errors.New("not a valid cloud provider. It can only be gcp, azure or aws") ) func newDeploymentRootCmd(out io.Writer) *cobra.Command { @@ -123,241 +60,13 @@ func newDeploymentRootCmd(out io.Writer) *cobra.Command { } cmd.PersistentFlags().StringVar(&workspaceID, "workspace-id", "", "workspace assigned to deployment") cmd.AddCommand( - newDeploymentListCmd(out), - newDeploymentDeleteCmd(), - newDeploymentCreateCmd(out), newDeploymentLogsCmd(), - newDeploymentUpdateCmd(out), newDeploymentVariableRootCmd(out), newDeploymentWorkerQueueRootCmd(out), - newDeploymentInspectCmd(out), newDeploymentConnectionRootCmd(out), newDeploymentAirflowVariableRootCmd(out), newDeploymentPoolRootCmd(out), - newDeploymentUserRootCmd(out), - newDeploymentTeamRootCmd(out), - newDeploymentTokenRootCmd(out), - newDeploymentHibernateCmd(), - newDeploymentWakeUpCmd(), - ) - return cmd -} - -//nolint:dupl -func newDeploymentTeamRootCmd(out io.Writer) *cobra.Command { - cmd := &cobra.Command{ - Use: "team", - Aliases: []string{"te", "teams"}, - Short: "Manage teams in your Astro Deployment", - Long: "Manage teams in your Astro Deployment.", - } - cmd.SetOut(out) - cmd.AddCommand( - newDeploymentTeamListCmd(out), - newDeploymentTeamUpdateCmd(out), - newDeploymentTeamRemoveCmd(out), - newDeploymentTeamAddCmd(out), - ) - cmd.PersistentFlags().StringVar(&deploymentID, "deployment-id", "", "deployment where you'd like to manage teams") - return cmd -} - -//nolint:dupl -func newDeploymentTeamListCmd(out io.Writer) *cobra.Command { - cmd := &cobra.Command{ - Use: "list", - Aliases: []string{"ls"}, - Short: "List all the teams in an Astro Deployment", - Long: "List all the teams in an Astro Deployment", - RunE: func(cmd *cobra.Command, args []string) error { - return listDeploymentTeam(cmd, out) - }, - } - return cmd -} - -func newDeploymentTeamRemoveCmd(out io.Writer) *cobra.Command { - cmd := &cobra.Command{ - Use: "remove", - Aliases: []string{"rm"}, - Short: "Remove a team from an Astro Deployment", - Long: "Remove a team from an Astro Deployment", - RunE: func(cmd *cobra.Command, args []string) error { - return removeDeploymentTeam(cmd, args, out) - }, - } - return cmd -} - -func listDeploymentTeam(cmd *cobra.Command, out io.Writer) error { - if deploymentID == "" { - return errors.New("flag --deployment-id is required") - } - cmd.SilenceUsage = true - return team.ListDeploymentTeams(out, astroCoreClient, deploymentID) -} - -func removeDeploymentTeam(cmd *cobra.Command, args []string, out io.Writer) error { - if deploymentID == "" { - return errors.New("flag --deployment-id is required") - } - var id string - - // if an id was provided in the args we use it - if len(args) > 0 { - id = args[0] - } - cmd.SilenceUsage = true - return team.RemoveDeploymentTeam(id, deploymentID, out, astroCoreClient) -} - -func newDeploymentTeamAddCmd(out io.Writer) *cobra.Command { - cmd := &cobra.Command{ - Use: "add [id]", - Short: "Add a team to an Astro Deployment with a specific role", - Long: "Add a team to an Astro Deployment with a specific role\n$astro deployment team add [id] --role [DEPLOYMENT_ADMIN or the custom role name].", - RunE: func(cmd *cobra.Command, args []string) error { - return addDeploymentTeam(cmd, args, out) - }, - } - cmd.Flags().StringVarP(&deploymentID, "deployment-id", "w", "", "The Deployment's unique identifier") - cmd.Flags().StringVarP(&addDeploymentRole, "role", "r", "DEPLOYMENT_ADMIN", "The role for the "+ - "new team. Possible values are DEPLOYMENT_ADMIN or the custom role name.") - return cmd -} - -func addDeploymentTeam(cmd *cobra.Command, args []string, out io.Writer) error { - if deploymentID == "" { - return errors.New("flag --deployment-id is required") - } - var id string - - if len(args) > 0 { - id = args[0] - } - cmd.SilenceUsage = true - return team.AddDeploymentTeam(id, addDeploymentRole, deploymentID, out, astroCoreClient) -} - -func newDeploymentTeamUpdateCmd(out io.Writer) *cobra.Command { - cmd := &cobra.Command{ - Use: "update [id]", - Aliases: []string{"up"}, - Short: "Update the role of a team in an Astro Deployment", - Long: "Update the role of a team in an Astro Deployment\n$astro deployment team update [id] --role [DEPLOYMENT_ADMIN or the custom role name].", - RunE: func(cmd *cobra.Command, args []string) error { - return updateDeploymentTeam(cmd, args, out) - }, - } - cmd.Flags().StringVarP(&updateDeploymentRole, "role", "r", "", "The new role for the "+ - "team. Possible values are DEPLOYMENT_ADMIN or the custom role name.") - return cmd -} - -func updateDeploymentTeam(cmd *cobra.Command, args []string, out io.Writer) error { - if deploymentID == "" { - return errors.New("flag --deployment-id is required") - } - var id string - - // if an id was provided in the args we use it - if len(args) > 0 { - id = args[0] - } - if updateDeploymentRole == "" { - // no role was provided so ask the user for it - updateDeploymentRole = input.Text("Enter a Deployment role or custom role name to update team: ") - } - - cmd.SilenceUsage = true - return team.UpdateDeploymentTeamRole(id, updateDeploymentRole, deploymentID, out, astroCoreClient) -} - -func newDeploymentUserRootCmd(out io.Writer) *cobra.Command { - cmd := &cobra.Command{ - Use: "user", - Aliases: []string{"us", "users"}, - Short: "Manage users in your Astro Deployment", - Long: "Manage users in your Astro Deployment.", - } - cmd.SetOut(out) - cmd.AddCommand( - newDeploymentUserListCmd(out), - newDeploymentUserUpdateCmd(out), - newDeploymentUserRemoveCmd(out), - newDeploymentUserAddCmd(out), ) - cmd.PersistentFlags().StringVar(&deploymentID, "deployment-id", "", "deployment where you'd like to manage users") - - return cmd -} - -func newDeploymentUserAddCmd(out io.Writer) *cobra.Command { - cmd := &cobra.Command{ - Use: "add [email]", - Short: "Add a user to an Astro Deployment with a specific role", - Long: "Add a user to an Astro Deployment with a specific role\n$astro deployment user add [email] --role [DEPLOYMENT_ADMIN or the custom role name].", - RunE: func(cmd *cobra.Command, args []string) error { - return addDeploymentUser(cmd, args, out) - }, - } - cmd.Flags().StringVarP(&addDeploymentRole, "role", "r", "DEPLOYMENT_ADMIN", "The role for the "+ - "new user. Possible values are DEPLOYMENT_ADMIN or the custom role name.") - return cmd -} - -func newDeploymentUserListCmd(out io.Writer) *cobra.Command { - cmd := &cobra.Command{ - Use: "list", - Aliases: []string{"ls"}, - Short: "List all the users in an Astro Deployment", - Long: "List all the users in an Astro Deployment", - RunE: func(cmd *cobra.Command, args []string) error { - return listDeploymentUser(cmd, out) - }, - } - return cmd -} - -func newDeploymentUserUpdateCmd(out io.Writer) *cobra.Command { - cmd := &cobra.Command{ - Use: "update [email]", - Aliases: []string{"up"}, - Short: "Update a the role of a user in an Astro Deployment", - Long: "Update the role of a user in an Astro Deployment\n$astro deployment user update [email] --role [DEPLOYMENT_ADMIN or the custom role name].", - RunE: func(cmd *cobra.Command, args []string) error { - return updateDeploymentUser(cmd, args, out) - }, - } - cmd.Flags().StringVarP(&updateDeploymentRole, "role", "r", "", "The new role for the "+ - "user. Possible values are DEPLOYMENT_ADMIN or the custom role name.") - return cmd -} - -func newDeploymentUserRemoveCmd(out io.Writer) *cobra.Command { - cmd := &cobra.Command{ - Use: "remove", - Aliases: []string{"rm"}, - Short: "Remove a user from an Astro Deployment", - Long: "Remove a user from an Astro Deployment", - RunE: func(cmd *cobra.Command, args []string) error { - return removeDeploymentUser(cmd, args, out) - }, - } - return cmd -} - -func newDeploymentListCmd(out io.Writer) *cobra.Command { - cmd := &cobra.Command{ - Use: "list", - Aliases: []string{"ls"}, - Short: "List all Deployments running in your Astronomer Workspace", - Long: "List all Deployments running in your Astronomer Workspace. Switch Workspaces to see other Deployments in your Organization.", - RunE: func(cmd *cobra.Command, args []string) error { - return deploymentList(cmd, out) - }, - } - cmd.Flags().BoolVarP(&allDeployments, "all", "a", false, "Show deployments across all workspaces") return cmd } @@ -383,118 +92,6 @@ func newDeploymentLogsCmd() *cobra.Command { return cmd } -func newDeploymentCreateCmd(out io.Writer) *cobra.Command { - cmd := &cobra.Command{ - Use: "create", - Aliases: []string{"cr"}, - Short: "Create a new Astro Deployment", - Long: "Create a new Astro Deployment. All flags are optional", - RunE: func(cmd *cobra.Command, args []string) error { - return deploymentCreate(cmd, args, out) - }, - } - cmd.Flags().StringVarP(&label, "name", "n", "", "The Deployment's name. If the name contains a space, specify the entire name within quotes \"\" ") - cmd.Flags().StringVarP(&workspaceID, "workspace-id", "w", "", "Workspace to create the Deployment in") - cmd.Flags().StringVarP(&description, "description", "d", "", "Description of the Deployment. If the description contains a space, specify the entire description in quotes \"\"") - cmd.Flags().StringVarP(&runtimeVersion, "runtime-version", "v", "", "Runtime version for the Deployment") - cmd.Flags().StringVarP(&dagDeploy, "dag-deploy", "", "", "Enables DAG-only deploys for the Deployment") - cmd.Flags().StringVarP(&executor, "executor", "e", "CeleryExecutor", "The executor to use for the Deployment. Possible values can be CeleryExecutor, KubernetesExecutor, or AstroExecutor.") - cmd.Flags().StringVarP(&cicdEnforcement, "cicd-enforcement", "", "", "When enabled CI/CD Enforcement where deploys to deployment must use an API Key or Token. This essentially forces Deploys to happen through CI/CD. Possible values disable/enable") - cmd.Flags().BoolVarP(&deploymentCreateEnforceCD, "enforce-cicd", "", false, "Provide this flag means deploys to deployment must use an API Key or Token. This essentially forces Deploys to happen through CI/CD. This flag has been deprecated for the --cicd-enforcement flag.") - err := cmd.Flags().MarkDeprecated("enforce-cicd", "use --cicd-enforcement instead") - if err != nil { - fmt.Println(err) - } - cmd.Flags().StringVarP(&inputFile, "deployment-file", "", "", "Location of file containing the Deployment to create. File can be in either JSON or YAML format.") - cmd.Flags().BoolVarP(&waitForStatus, "wait", "i", false, "Wait for the Deployment to become healthy before ending the command") - cmd.Flags().DurationVar(&waitTimeForDeployment, "wait-time", deploymentWaitTime, "Wait time for the Deployment to become healthy before ending the command. Can only be used with --wait=true") - cmd.Flags().BoolVarP(&cleanOutput, "clean-output", "", false, "clean output to only include inspect yaml or json file in any situation.") - cmd.Flags().StringVarP(&workloadIdentity, "workload-identity", "", "", "The Workload Identity to use for the Deployment") - if organization.IsOrgHosted() { - cmd.Flags().StringVarP(&deploymentType, "cluster-type", "", standard, "The Cluster Type to use for the Deployment. Possible values can be standard or dedicated. This flag has been deprecated for the --type flag.") - err := cmd.Flags().MarkDeprecated("cluster-type", "use --type instead") - if err != nil { - fmt.Println(err) - } - cmd.Flags().StringVarP(&deploymentType, "type", "", standard, "The Type to use for the Deployment. Possible values can be standard or dedicated.") - cmd.Flags().StringVarP(&defaultTaskPodCPU, "default-task-pod-cpu", "", "", "The default task pod CPU to use for the Deployment. Example value: 0.25") - cmd.Flags().StringVarP(&defaultTaskPodMemory, "default-task-pod-memory", "", "", "The default task pod memory to use for the Deployment. Example value: 0.5Gi") - cmd.Flags().StringVarP(&resourceQuotaCPU, "resource-quota-cpu", "", "", "The Deployment's CPU resource quota. Example value: 10") - cmd.Flags().StringVarP(&resourceQuotaMemory, "resource-quota-memory", "", "", "The Deployment's memory resource quota. Example value: 20Gi") - cmd.Flags().StringVarP(&cloudProvider, "cloud-provider", "p", "azure", "The Cloud Provider to use for the Deployment. Possible values can be gcp, aws, azure.") - cmd.Flags().StringVarP(®ion, "region", "", "", "The Cloud Provider region to use for the Deployment.") - cmd.Flags().StringVarP(&schedulerSize, "scheduler-size", "", "", "The size of scheduler for the Deployment. Possible values can be small, medium, large, extra_large") - cmd.Flags().StringVarP(&highAvailability, "high-availability", "a", "disable", "Enables High Availability for the Deployment") - cmd.Flags().StringVarP(&developmentMode, "development-mode", "m", "disable", "Set to 'enable' to enable development-only features such as hibernation. When enabled, the Deployment will not have guaranteed uptime SLAs.'") - cmd.Flags().BoolVarP(&flagRemoteExecutionEnabled, "remote-execution-enabled", "", false, "Enables Remote Execution for the Deployment.") - cmd.Flags().StringVarP(&flagAllowedIPAddressRanges, "allowed-ip-address-ranges", "", "", "A comma-separated list of allowed IP address ranges for the Deployment. By default, there's no IP restriction. Example: 203.0.113.0/24,198.51.100.42/32") - cmd.Flags().StringVarP(&flagTaskLogBucket, "task-log-bucket", "", "", "The bucket to use for storing task logs. Example: s3://my-bucket/airflow-logs") - cmd.Flags().StringVarP(&flagTaskLogURLPattern, "task-log-url-pattern", "", "", "The URL pattern to use for accessing task logs. Example: dag_id={{ ti.dag_id }}/run_id={{ ti.run_id }}/task_id={{ ti.task_id }}/{% if ti.map_index >= 0 %}map_index={{ ti.map_index }}/{% endif %}attempt={{ try_number|default(ti.try_number) }}/{{ ti.id }}.log") - } else { - cmd.Flags().IntVarP(&schedulerAU, "scheduler-au", "s", 0, "The Deployment's scheduler resources in AUs") - cmd.Flags().IntVarP(&schedulerReplicas, "scheduler-replicas", "r", 0, "The number of scheduler replicas for the Deployment") - } - cmd.Flags().StringVarP(&clusterID, "cluster-id", "c", "", "Cluster to create the Deployment in") - return cmd -} - -func newDeploymentUpdateCmd(out io.Writer) *cobra.Command { - cmd := &cobra.Command{ - Use: "update [DEPLOYMENT-ID]", - Aliases: []string{"up"}, - Short: "Update an Astro Deployment", - Long: "Update the configuration for an Astro Deployment. All flags are optional", - RunE: func(cmd *cobra.Command, args []string) error { - return deploymentUpdate(cmd, args, out) - }, - } - cmd.Flags().StringVarP(&label, "name", "n", "", "Update the Deployment's name. If the new name contains a space, specify the entire name within quotes \"\" ") - cmd.Flags().StringVarP(&workspaceID, "workspace-id", "w", "", "Workspace the Deployment is located in") - cmd.Flags().StringVarP(&description, "description", "d", "", "Description of the Deployment. If the description contains a space, specify the entire description in quotes \"\"") - cmd.Flags().StringVarP(&executor, "executor", "e", "", "The executor to use for the deployment. Possible values can be CeleryExecutor, KubernetesExecutor or AstroExecutor.") - cmd.Flags().StringVarP(&inputFile, "deployment-file", "", "", "Location of file containing the deployment to update. File can be in either JSON or YAML format.") - cmd.Flags().BoolVarP(&forceUpdate, "force", "f", false, "Force update: Don't prompt a user before Deployment update") - cmd.Flags().StringVarP(&cicdEnforcement, "cicd-enforcement", "", "", "When enabled CI/CD Enforcement where deploys to deployment must use an API Key or Token. This essentially forces Deploys to happen through CI/CD. Possible values disable/enable.") - cmd.Flags().BoolVarP(&deploymentUpdateEnforceCD, "enforce-cicd", "", false, "Provide this flag means deploys to deployment must use an API Key or Token. This essentially forces Deploys to happen through CI/CD. Pass enforce-cicd=false to disable this feature. This flag has been deprecated for the --cicd-enforcement flag.") - err := cmd.Flags().MarkDeprecated("enforce-cicd", "use --cicd-enforcement instead") - if err != nil { - fmt.Println(err) - } - cmd.Flags().StringVarP(&deploymentName, "deployment-name", "", "", "Name of the deployment to update") - cmd.Flags().StringVarP(&dagDeploy, "dag-deploy", "", "", "Enables DAG-only deploys for the deployment") - cmd.Flags().BoolVarP(&cleanOutput, "clean-output", "c", false, "clean output to only include inspect yaml or json file in any situation.") - cmd.Flags().StringVarP(&workloadIdentity, "workload-identity", "", "", "The Workload Identity to use for the Deployment") - if organization.IsOrgHosted() { - cmd.Flags().StringVarP(&schedulerSize, "scheduler-size", "", "", "The size of Scheduler for the Deployment. Possible values can be small, medium, large, extra_large") - cmd.Flags().StringVarP(&highAvailability, "high-availability", "a", "", "Enables High Availability for the Deployment") - cmd.Flags().StringVarP(&defaultTaskPodCPU, "default-task-pod-cpu", "", "", "The Default Task Pod CPU to use for the Deployment. Example value: 0.25") - cmd.Flags().StringVarP(&defaultTaskPodMemory, "default-task-pod-memory", "", "", "The Default Taks Pod Memory to use for the Deployment. Example value: 0.5Gi") - cmd.Flags().StringVarP(&resourceQuotaCPU, "resource-quota-cpu", "", "", "The Resource Quota CPU to use for the Deployment. Example value: 10") - cmd.Flags().StringVarP(&resourceQuotaMemory, "resource-quota-memory", "", "", "The Resource Quota Memory to use for the Deployment. Example value: 20Gi") - cmd.Flags().StringVarP(&developmentMode, "development-mode", "m", "", "Whether the Deployment is for development only. If 'disable', the Deployment can be considered production for the purposes of support case priority, but development-only features such as hibernation will not be available. You can't update this value to `enable` for existing non-development Deployments.'") - cmd.Flags().StringVarP(&flagAllowedIPAddressRanges, "allowed-ip-address-ranges", "", "", "A comma-separated list of allowed IP address ranges for the Deployment. By default, there's no IP restriction. Example: 203.0.113.0/24,198.51.100.42/32") - cmd.Flags().StringVarP(&flagTaskLogBucket, "task-log-bucket", "", "", "The bucket to use for storing task logs. Example: s3://my-bucket/airflow-logs") - cmd.Flags().StringVarP(&flagTaskLogURLPattern, "task-log-url-pattern", "", "", "The URL pattern to use for accessing task logs. Example: dag_id={{ ti.dag_id }}/run_id={{ ti.run_id }}/task_id={{ ti.task_id }}/{% if ti.map_index >= 0 %}map_index={{ ti.map_index }}/{% endif %}attempt={{ try_number|default(ti.try_number) }}/{{ ti.id }}.log") - } else { - cmd.Flags().IntVarP(&updateSchedulerAU, "scheduler-au", "s", 0, "The Deployment's Scheduler resources in AUs.") - cmd.Flags().IntVarP(&updateSchedulerReplicas, "scheduler-replicas", "r", 0, "The number of Scheduler replicas for the Deployment.") - } - return cmd -} - -func newDeploymentDeleteCmd() *cobra.Command { - cmd := &cobra.Command{ - Use: "delete DEPLOYMENT-ID", - Aliases: []string{"de"}, - Short: "Delete an Astro Deployment", - Long: "Delete an Astro Deployment", - RunE: deploymentDelete, - } - cmd.Flags().BoolVarP(&forceDelete, "force", "f", false, "Force delete. Don't prompt a user before Deployment deletion") - cmd.Flags().StringVarP(&deploymentName, "deployment-name", "n", "", "Name of the deployment to delete") - return cmd -} - func newDeploymentVariableRootCmd(out io.Writer) *cobra.Command { cmd := &cobra.Command{ Use: "variable", @@ -579,59 +176,6 @@ func newDeploymentVariableUpdateCmd(out io.Writer) *cobra.Command { return cmd } -//nolint:dupl -func newDeploymentHibernateCmd() *cobra.Command { - cmd := &cobra.Command{ - Use: "hibernate [DEPLOYMENT-ID]", - Aliases: []string{"hb"}, - Short: "Hibernate an Astro development Deployment", - Long: "Hibernate an Astro development Deployment for a set amount of time. Overrides any existing hibernation schedule and sets the Deployment to hibernate for a specific duration or until a specific time. Use the '--remove-override' flag to remove any existing override and resume the regular hibernation schedule.", - RunE: func(cmd *cobra.Command, args []string) error { return deploymentOverrideHibernation(cmd, args, true) }, - } - cmd.Flags().StringVarP(&deploymentName, "deployment-name", "n", "", "Name of the Deployment to hibernate") - cmd.Flags().StringVarP(&until, "until", "u", "", "Specify the hibernation period using an end date and time. Example value: 2021-01-01T00:00:00Z") - cmd.Flags().StringVarP(&forDuration, "for", "d", "", "Specify the hibernation period using a duration. Example value: 1h30m") - cmd.Flags().BoolVarP(&removeOverride, "remove-override", "r", false, "Remove any existing override and resume regular hibernation schedule.") - cmd.Flags().BoolVarP(&forceOverride, "force", "f", false, "Force hibernate. The CLI will not prompt to confirm before hibernating the Deployment.") - cmd.MarkFlagsMutuallyExclusive("until", "for", "remove-override") - return cmd -} - -//nolint:dupl -func newDeploymentWakeUpCmd() *cobra.Command { - cmd := &cobra.Command{ - Use: "wake-up [DEPLOYMENT-ID]", - Aliases: []string{"wu"}, - Short: "Wake up an Astro development Deployment", - Long: "Wake up an Astro development Deployment from hibernation. Overrides any existing hibernation schedule and sets the Deployment to run for a specific duration or until a specific time. Use the '--remove-override' flag to remove any existing override and resume the regular hibernation schedule.", - RunE: func(cmd *cobra.Command, args []string) error { return deploymentOverrideHibernation(cmd, args, false) }, - } - cmd.Flags().StringVarP(&deploymentName, "deployment-name", "n", "", "Name of the Deployment to wake up.") - cmd.Flags().StringVarP(&until, "until", "u", "", "Specify the awake period using an end date. Example value: 2021-01-01T00:00:00Z") - cmd.Flags().StringVarP(&forDuration, "for", "d", "", "Specify the awake period using a duration. Example value: 1h30m") - cmd.Flags().BoolVarP(&removeOverride, "remove-override", "r", false, "Remove any existing override and resume the regular hibernation schedule.") - cmd.Flags().BoolVarP(&forceOverride, "force", "f", false, "Force wake up. The CLI will not prompt to confirm before waking up the Deployment.") - cmd.MarkFlagsMutuallyExclusive("until", "for", "remove-override") - return cmd -} - -func deploymentList(cmd *cobra.Command, out io.Writer) error { - ws, err := coalesceWorkspace() - if err != nil { - return errors.Wrap(err, "failed to find a valid workspace") - } - - // Don't validate workspace if viewing all deployments - if allDeployments { - ws = "" - } - - // Silence Usage as we have now validated command input - cmd.SilenceUsage = true - - return deployment.List(ws, allDeployments, platformCoreClient, out) -} - func deploymentLogs(cmd *cobra.Command, args []string) error { // Get release name from args, if passed if len(args) > 0 { @@ -647,234 +191,6 @@ func deploymentLogs(cmd *cobra.Command, args []string) error { return deployment.Logs(deploymentID, ws, deploymentName, logsKeyword, logServer, logScheduler, logTriggerer, logWorkers, warnLogs, errorLogs, infoLogs, logCount, platformCoreClient, astroCoreClient) } -func deploymentCreate(cmd *cobra.Command, _ []string, out io.Writer) error { //nolint:gocognit,gocyclo - // Find Workspace ID - ws, err := coalesceWorkspace() - if err != nil { - return errors.Wrap(err, "failed to find a valid Workspace") - } - workspaceID = ws - - // clean output - deployment.CleanOutput = cleanOutput - - // Get latest runtime version - if runtimeVersion == "" { - airflowVersionClient := airflowversions.NewClient(httpClient, false, false) - runtimeVersion, err = airflowversions.GetDefaultImageTag(airflowVersionClient, "", "", false) - if err != nil { - return err - } - } - - // set default executor if none was specified - if executor == "" { - if airflowversions.IsAirflow3(runtimeVersion) { - executor = deployment.AstroExecutor - } else { - executor = deployment.CeleryExecutor - } - } - - // check if executor is valid - if !deployment.IsValidExecutor(executor, runtimeVersion, deploymentType) { - return fmt.Errorf("%s is %w for runtime version %s deployment type %s", executor, errInvalidExecutor, runtimeVersion, deploymentType) - } - - if highAvailability != "" && !(highAvailability == enable || highAvailability == disable) { - return errors.New("Invalid --high-availability value") - } - if developmentMode != "" && !(developmentMode == enable || developmentMode == disable) { - return errors.New("Invalid --development-mode value") - } - if organization.IsOrgHosted() && !(deploymentType == standard || deploymentType == dedicated || deploymentType == fromfile.HostedStandard || deploymentType == fromfile.HostedShared || deploymentType == fromfile.HostedDedicated) { - return errors.New("Invalid --type value") - } - if cicdEnforcement != "" && !(cicdEnforcement == enable || cicdEnforcement == disable) { - return errors.New("Invalid --cicd-enforcement value") - } - if deploymentCreateEnforceCD && cicdEnforcement == disable { - return errors.New("flags --enforce-cicd and --cicd-enforcement contradict each other. Use only --cicd-enforcement") - } - if organization.IsOrgHosted() && clusterID != "" && (deploymentType == standard || deploymentType == fromfile.HostedStandard || deploymentType == fromfile.HostedShared) { - return errors.New("flag --cluster-id cannot be used to create a standard deployment. If you want to create a dedicated deployment, use --type dedicated along with --cluster-id") - } - if cmd.Flags().Changed("allowed-ip-address-ranges") { - if !flagRemoteExecutionEnabled { - return errors.New("flag --allowed-ip-address-ranges cannot be used when remote execution is disabled") - } - allowedIPAddressRanges = fromCsv(flagAllowedIPAddressRanges) - } - if cmd.Flags().Changed("task-log-bucket") { - if !flagRemoteExecutionEnabled { - return errors.New("flag --task-log-bucket cannot be used when remote execution is disabled") - } - taskLogBucket = &flagTaskLogBucket - } - if cmd.Flags().Changed("task-log-url-pattern") { - if !flagRemoteExecutionEnabled { - return errors.New("flag --task-log-url-pattern cannot be used when remote execution is disabled") - } - taskLogURLPattern = &flagTaskLogURLPattern - } - - if cmd.Flags().Changed("wait-time") && !waitForStatus { - return errors.New("cannot use --wait-time with --wait=false") - } - - if deploymentCreateEnforceCD { - cicdEnforcement = enable - } - var coreDeploymentType astroplatformcore.DeploymentType - if deploymentType == standard || deploymentType == fromfile.HostedStandard || deploymentType == fromfile.HostedShared { - coreDeploymentType = astroplatformcore.DeploymentTypeSTANDARD - } - if deploymentType == dedicated || deploymentType == fromfile.HostedDedicated { - coreDeploymentType = astroplatformcore.DeploymentTypeDEDICATED - } - - if !organization.IsOrgHosted() { - coreDeploymentType = astroplatformcore.DeploymentTypeHYBRID - } - // request is to create from a file - if inputFile != "" { - // Collect all requested flags except --deployment-file and --wait - allowedFlags := map[string]bool{ - "wait": true, - "wait-time": true, - "deployment-file": true, - "verbosity": true, - } - var disallowedFlagSet bool - cmd.Flags().Visit(func(f *pflag.Flag) { - if !allowedFlags[f.Name] { - disallowedFlagSet = true - } - }) - if disallowedFlagSet { - return errFlag - } - return fromfile.CreateOrUpdate(inputFile, cmd.Name(), platformCoreClient, astroCoreClient, out, waitForStatus, waitTimeForDeployment) - } - - if dagDeploy != "" && !(dagDeploy == enable || dagDeploy == disable) { - return errors.New("Invalid --dag-deploy value)") - } - if dagDeploy == "" { - if organization.IsOrgHosted() && !flagRemoteExecutionEnabled { - dagDeploy = enable - } else { - dagDeploy = disable - } - } - - // validate cloudProvider - if cloudProvider != "" { - if !isValidCloudProvider(astrocore.SharedClusterCloudProvider(cloudProvider)) { - return fmt.Errorf("%s is %w", cloudProvider, errInvalidCloudProvider) - } - } - // Silence Usage as we have now validated command input - cmd.SilenceUsage = true - return deployment.Create(label, workspaceID, description, clusterID, runtimeVersion, dagDeploy, executor, cloudProvider, region, schedulerSize, highAvailability, developmentMode, cicdEnforcement, defaultTaskPodCPU, defaultTaskPodMemory, resourceQuotaCPU, resourceQuotaMemory, workloadIdentity, coreDeploymentType, schedulerAU, schedulerReplicas, flagRemoteExecutionEnabled, allowedIPAddressRanges, taskLogBucket, taskLogURLPattern, platformCoreClient, astroCoreClient, waitForStatus, waitTimeForDeployment) -} - -func deploymentUpdate(cmd *cobra.Command, args []string, out io.Writer) error { //nolint:gocognit - // Find Workspace ID - ws, err := coalesceWorkspace() - if err != nil { - return errors.Wrap(err, "failed to find a valid Workspace") - } - - // Silence Usage as we have now validated command input - cmd.SilenceUsage = true - - // clean output - deployment.CleanOutput = cleanOutput - - // check if executor is valid - if executor != "" && !deployment.IsValidExecutor(executor, runtimeVersion, deploymentType) { - return fmt.Errorf("%s is %w", executor, errInvalidExecutor) - } - // request is to update from a file - if inputFile != "" { - requestedFlags := cmd.Flags().NFlag() - if requestedFlags > 1 { - // other flags were requested - return errFlag - } - return fromfile.CreateOrUpdate(inputFile, cmd.Name(), platformCoreClient, astroCoreClient, out, false, 0*time.Second) - } - if dagDeploy != "" && !(dagDeploy == enable || dagDeploy == disable) { - return errors.New("Invalid --dag-deploy value") - } - - if highAvailability != "" && !(highAvailability == enable || highAvailability == disable) { - return errors.New("Invalid --high-availability value") - } - if developmentMode != "" && !(developmentMode == enable || developmentMode == disable) { - return errors.New("Invalid --development-mode value") - } - if cicdEnforcement != "" && !(cicdEnforcement == enable || cicdEnforcement == disable) { - return errors.New("Invalid --cicd-enforcement value") - } - if cmd.Flags().Changed("enforce-cicd") { - err1 := validateCICD() - if err1 != nil { - return err1 - } - } - if cmd.Flags().Changed("allowed-ip-address-ranges") { - allowedIPAddressRanges = fromCsv(flagAllowedIPAddressRanges) - } - if cmd.Flags().Changed("task-log-bucket") { - taskLogBucket = &flagTaskLogBucket - } - if cmd.Flags().Changed("task-log-url-pattern") { - taskLogURLPattern = &flagTaskLogURLPattern - } - - // Get release name from args, if passed - if len(args) > 0 { - deploymentID = args[0] - } - - return deployment.Update(deploymentID, label, ws, description, deploymentName, dagDeploy, executor, schedulerSize, highAvailability, developmentMode, cicdEnforcement, defaultTaskPodCPU, defaultTaskPodMemory, resourceQuotaCPU, resourceQuotaMemory, workloadIdentity, updateSchedulerAU, updateSchedulerReplicas, []astroplatformcore.WorkerQueueRequest{}, []astroplatformcore.HybridWorkerQueueRequest{}, []astroplatformcore.DeploymentEnvironmentVariableRequest{}, allowedIPAddressRanges, taskLogBucket, taskLogURLPattern, forceUpdate, astroCoreClient, platformCoreClient) -} - -func validateCICD() error { - if deploymentUpdateEnforceCD && cicdEnforcement == disable { - return errors.New("flags --enforce-cicd and --cicd-enforcement contradict each other. Use only --cicd-enforcement") - } - if !deploymentUpdateEnforceCD && cicdEnforcement == enable { - return errors.New("flags --enforce-cicd and --cicd-enforcement contradict each other. Use only --cicd-enforcement") - } - if deploymentUpdateEnforceCD { - cicdEnforcement = enable - } - if !deploymentUpdateEnforceCD { - cicdEnforcement = disable - } - return nil -} - -func deploymentDelete(cmd *cobra.Command, args []string) error { - ws, err := coalesceWorkspace() - if err != nil { - return errors.Wrap(err, "failed to find a valid Workspace") - } - - // Silence Usage as we have now validated command input - cmd.SilenceUsage = true - - // Get release name from args, if passed - if len(args) > 0 { - deploymentID = args[0] - } - - return deployment.Delete(deploymentID, ws, deploymentName, forceDelete, platformCoreClient) -} - func deploymentVariableList(cmd *cobra.Command, _ []string, out io.Writer) error { ws, err := coalesceWorkspace() if err != nil { @@ -914,568 +230,3 @@ func deploymentVariableUpdate(cmd *cobra.Command, args []string, out io.Writer) return deployment.VariableModify(deploymentID, variableKey, variableValue, ws, envFile, deploymentName, variableList, useEnvFile, makeSecret, true, astroCoreClient, platformCoreClient, out) } - -func deploymentOverrideHibernation(cmd *cobra.Command, args []string, isHibernating bool) error { - ws, err := coalesceWorkspace() - if err != nil { - return errors.Wrap(err, "failed to find a valid workspace") - } - - // Get deploymentId from args, if passed - if len(args) > 0 { - deploymentID = args[0] - } - - // Silence Usage as we have now validated command input - cmd.SilenceUsage = true - - if removeOverride { - return deployment.DeleteDeploymentHibernationOverride(deploymentID, ws, deploymentName, forceOverride, platformCoreClient) - } - - overrideUntil, err := getOverrideUntil(until, forDuration) - if err != nil { - return err - } - - return deployment.UpdateDeploymentHibernationOverride(deploymentID, ws, deploymentName, isHibernating, overrideUntil, forceOverride, platformCoreClient) -} - -// isValidCloudProvider returns true for valid CloudProvider values and false if not. -func isValidCloudProvider(cloudProvider astrocore.SharedClusterCloudProvider) bool { - return cloudProvider == astrocore.SharedClusterCloudProviderGcp || cloudProvider == astrocore.SharedClusterCloudProviderAws || cloudProvider == astrocore.SharedClusterCloudProviderAzure -} - -func addDeploymentUser(cmd *cobra.Command, args []string, out io.Writer) error { - if deploymentID == "" { - return errors.New("flag --deployment-id is required") - } - var email string - - // if an email was provided in the args we use it - if len(args) > 0 { - // make sure the email is lowercase - email = strings.ToLower(args[0]) - } - - cmd.SilenceUsage = true - return user.AddDeploymentUser(email, addDeploymentRole, deploymentID, out, astroCoreClient) -} - -func listDeploymentUser(cmd *cobra.Command, out io.Writer) error { - if deploymentID == "" { - return errors.New("flag --deployment-id is required") - } - cmd.SilenceUsage = true - return user.ListDeploymentUsers(out, astroCoreClient, deploymentID) -} - -func updateDeploymentUser(cmd *cobra.Command, args []string, out io.Writer) error { - if deploymentID == "" { - return errors.New("flag --deployment-id is required") - } - var email string - - // if an email was provided in the args we use it - if len(args) > 0 { - // make sure the email is lowercase - email = strings.ToLower(args[0]) - } - - if updateDeploymentRole == "" { - // no role was provided so ask the user for it - updateDeploymentRole = input.Text("Enter a user Deployment role or custom role name to update user: ") - } - - cmd.SilenceUsage = true - return user.UpdateDeploymentUserRole(email, updateDeploymentRole, deploymentID, out, astroCoreClient) -} - -func removeDeploymentUser(cmd *cobra.Command, args []string, out io.Writer) error { - if deploymentID == "" { - return errors.New("flag --deployment-id is required") - } - var email string - - // if an email was provided in the args we use it - if len(args) > 0 { - // make sure the email is lowercase - email = strings.ToLower(args[0]) - } - - cmd.SilenceUsage = true - return user.RemoveDeploymentUser(email, deploymentID, out, astroCoreClient) -} - -//nolint:dupl -func newDeploymentTokenRootCmd(out io.Writer) *cobra.Command { - cmd := &cobra.Command{ - Use: "token", - Aliases: []string{"to"}, - Short: "Manage tokens in your Astro Deployment", - Long: "Manage tokens in your Astro Deployment.", - } - cmd.SetOut(out) - cmd.AddCommand( - newDeploymentTokenListCmd(out), - newDeploymentTokenCreateCmd(out), - newDeploymentTokenUpdateCmd(out), - newDeploymentTokenRotateCmd(out), - newDeploymentTokenDeleteCmd(out), - newWorkspaceTokenManageCmd(out), - newOrgTokenManageCmd(out), - ) - cmd.PersistentFlags().StringVar(&deploymentID, "deployment-id", "", "deployment where you would like to manage tokens") - return cmd -} - -//nolint:dupl -func newDeploymentTokenListCmd(out io.Writer) *cobra.Command { - cmd := &cobra.Command{ - Use: "list", - Aliases: []string{"ls"}, - Short: "List all the API tokens in an Astro Deployment", - Long: "List all the API tokens in an Astro Deployment", - RunE: func(cmd *cobra.Command, args []string) error { - return listDeploymentToken(cmd, out) - }, - } - return cmd -} - -//nolint:dupl -func newDeploymentTokenCreateCmd(out io.Writer) *cobra.Command { - cmd := &cobra.Command{ - Use: "create", - Aliases: []string{"cr"}, - Short: "Create an API token in an Astro Deployment", - Long: "Create an API token in an Astro Deployment\n$astro workspace token create --name [token name] --role [Possible values are DEPLOYMENT_ADMIN or a custom role name].", - RunE: func(cmd *cobra.Command, args []string) error { - return createDeploymentToken(cmd, out) - }, - } - cmd.Flags().StringVarP(&tokenName, "name", "n", "", "The token's name. If the name contains a space, specify the entire name within quotes \"\" ") - cmd.Flags().BoolVarP(&cleanTokenOutput, "clean-output", "c", false, "Print only the token as output. For use of the command in scripts") - cmd.Flags().StringVarP(&tokenDescription, "description", "d", "", "Description of the token. If the description contains a space, specify the entire description within quotes \"\"") - cmd.Flags().StringVarP(&tokenRole, "role", "r", "", "The role for the "+ - "token. Possible values are DEPLOYMENT_ADMIN or a custom role name") - cmd.Flags().IntVarP(&tokenExpiration, "expiration", "e", 0, "Expiration of the token in days. If the flag isn't used the token won't have an expiration. Must be between 1 and 3650 days. ") - return cmd -} - -//nolint:dupl -func newDeploymentTokenUpdateCmd(out io.Writer) *cobra.Command { - cmd := &cobra.Command{ - Use: "update [TOKEN_ID]", - Aliases: []string{"up"}, - Short: "Update a Deployment API token", - Long: "Update a Deployment API token that has a role in an Astro Deployment\n$astro deployment token update [TOKEN_ID] --name [new token name] --role [Possible values are DEPLOYMENT_ADMIN or a custom role name].", - RunE: func(cmd *cobra.Command, args []string) error { - return updateDeploymentToken(cmd, args, out) - }, - } - cmd.Flags().StringVarP(&name, "name", "t", "", "The current name of the token. If the name contains a space, specify the entire name within quotes \"\" ") - cmd.Flags().StringVarP(&tokenName, "new-name", "n", "", "The token's new name. If the name contains a space, specify the entire name within quotes \"\" ") - cmd.Flags().StringVarP(&tokenDescription, "description", "d", "", "updated description of the token. If the description contains a space, specify the entire description in quotes \"\"") - cmd.Flags().StringVarP(&tokenRole, "role", "r", "DEPLOYMENT_ADMIN", "The new role for the "+ - "token. Possible values are DEPLOYMENT_ADMIN or a custom role name") - return cmd -} - -//nolint:dupl -func newDeploymentTokenRotateCmd(out io.Writer) *cobra.Command { - cmd := &cobra.Command{ - Use: "rotate [TOKEN_ID]", - Aliases: []string{"ro"}, - Short: "Rotate a Deployment API token", - Long: "Rotate a Deployment API token. You can only rotate Deployment API tokens. You cannot rotate Organization API tokens with this command", - RunE: func(cmd *cobra.Command, args []string) error { - return rotateDeploymentToken(cmd, args, out) - }, - } - cmd.Flags().BoolVarP(&cleanTokenOutput, "clean-output", "c", false, "Print only the token as output. For use of the command in scripts") - cmd.Flags().StringVarP(&name, "name", "t", "", "The name of the token to be rotated. If the name contains a space, specify the entire name within quotes \"\" ") - cmd.Flags().BoolVarP(&forceRotate, "force", "f", false, "Rotate the Deployment API token without showing a warning") - - return cmd -} - -//nolint:dupl -func newDeploymentTokenDeleteCmd(out io.Writer) *cobra.Command { - cmd := &cobra.Command{ - Use: "delete [TOKEN_ID]", - Aliases: []string{"de"}, - Short: "Delete a Deployment API token", - Long: "Delete a Deployment API token", - RunE: func(cmd *cobra.Command, args []string) error { - return deleteDeploymentToken(cmd, args, out) - }, - } - cmd.Flags().StringVarP(&name, "name", "t", "", "The name of the token to be deleted. If the name contains a space, specify the entire name within quotes \"\" ") - cmd.Flags().BoolVarP(&forceDelete, "force", "f", false, "Delete or remove the API token without showing a warning") - - return cmd -} - -//nolint:dupl -func newOrgTokenManageCmd(out io.Writer) *cobra.Command { - cmd := &cobra.Command{ - Use: "organization-token", - Short: "Manage organization tokens in a deployment", - Long: "Manage organization tokens in a deployment", - } - cmd.SetOut(out) - cmd.AddCommand( - newAddOrganizationTokenDeploymentRole(out), - newUpdateOrganizationTokenDeploymentRole(out), - newRemoveOrganizationTokenDeploymentRole(out), - newListOrganizationTokensInDeployment(out), - ) - return cmd -} - -//nolint:dupl -func newWorkspaceTokenManageCmd(out io.Writer) *cobra.Command { - cmd := &cobra.Command{ - Use: "workspace-token", - Short: "Manage workspace tokens in a deployment", - Long: "Manage workspace tokens in a deployment", - } - cmd.SetOut(out) - cmd.AddCommand( - newAddWorkspaceTokenDeploymentRole(out), - newUpdateWorkspaceTokenDeploymentRole(out), - newRemoveWorkspaceTokenDeploymentRole(out), - newListWorkspaceTokensInDeployment(out), - ) - return cmd -} - -func newAddOrganizationTokenDeploymentRole(out io.Writer) *cobra.Command { - cmd := &cobra.Command{ - Use: "add [ORG_TOKEN_ID]", - Short: "Add an Organization API token to a Deployment", - Long: "Add an Organization API token to a Deployment\n$astro deployment token organization-token add [ORG_TOKEN_ID] --org-token-name [token name] --role [DEPLOYMENT_ADMIN or a custom role name].", - RunE: func(cmd *cobra.Command, args []string) error { - return addOrgTokenToDeploymentRole(cmd, args, out) - }, - } - cmd.Flags().StringVarP(&orgTokenName, "org-token-name", "n", "", "The name of the Organization API token you want to add to a Deployment. If the name contains a space, specify the entire name within quotes \"\" ") - cmd.Flags().StringVarP(&tokenRole, "role", "r", "", "The Deployment role to add to the "+ - "Organization API token. Possible values are DEPLOYMENT_ADMIN or a custom role name.") - return cmd -} - -func newUpdateOrganizationTokenDeploymentRole(out io.Writer) *cobra.Command { - cmd := &cobra.Command{ - Use: "update [ORG_TOKEN_ID]", - Short: "Update an Organization API token's Deployment Role", - Long: "Update an Organization API token's Deployment Role\n$astro deployment token organization-token update [ORG_TOKEN_ID] --org-token-name [token name] --role [DEPLOYMENT_ADMIN or a custom role name].", - RunE: func(cmd *cobra.Command, args []string) error { - return updateOrgTokenToDeploymentRole(cmd, args, out) - }, - } - cmd.Flags().StringVarP(&orgTokenName, "org-token-name", "n", "", "The name of the Organization API token you want to update in a Deployment. If the name contains a space, specify the entire name within quotes \"\" ") - cmd.Flags().StringVarP(&tokenRole, "role", "r", "", "The Deployment role to update the "+ - "Organization API token. Possible values are DEPLOYMENT_ADMIN or a custom role name.") - return cmd -} - -func newAddWorkspaceTokenDeploymentRole(out io.Writer) *cobra.Command { - cmd := &cobra.Command{ - Use: "add [WORKSPACE_TOKEN_ID]", - Short: "Add a Workspace API token's Deployment Role", - Long: "Add a Workspace API token's Deployment Role\n$astro deployment token workspace-token add [WORKSPACE_TOKEN_ID] --workspace-token-name [token name] --role [DEPLOYMENT_ADMIN or a custom role name].", - RunE: func(cmd *cobra.Command, args []string) error { - return addWorkspaceTokenDeploymentRole(cmd, args, out) - }, - } - cmd.Flags().StringVarP(&orgTokenName, "workspace-token-name", "n", "", "The name of the WORKSPACE API token you want to add to a Deployment. If the name contains a space, specify the entire name within quotes \"\" ") - cmd.Flags().StringVarP(&tokenRole, "role", "r", "", "The Deployment role to grant to the "+ - "Workspace API token. Possible values are DEPLOYMENT_ADMIN or a custom role name.") - return cmd -} - -func newUpdateWorkspaceTokenDeploymentRole(out io.Writer) *cobra.Command { - cmd := &cobra.Command{ - Use: "update [WORKSPACE_TOKEN_ID]", - Short: "Update a Workspace API token's Deployment Role", - Long: "Update a Workspace API token's Deployment Role\n$astro deployment token workspace-token update [WORKSPACE_TOKEN_ID] --workspace-token-name [token name] --role [DEPLOYMENT_ADMIN or a custom role name].", - RunE: func(cmd *cobra.Command, args []string) error { - return updateWorkspaceTokenDeploymentRole(cmd, args, out) - }, - } - cmd.Flags().StringVarP(&orgTokenName, "workspace-token-name", "n", "", "The name of the WORKSPACE API token you want to update in a Deployment. If the name contains a space, specify the entire name within quotes \"\" ") - cmd.Flags().StringVarP(&tokenRole, "role", "r", "", "The Deployment role to grant to the "+ - "Workspace API token. Possible values are DEPLOYMENT_ADMIN or a custom role name.") - return cmd -} - -func addOrgTokenToDeploymentRole(cmd *cobra.Command, args []string, out io.Writer) error { - if deploymentID == "" { - return errors.New("flag --deployment-id is required") - } - // if an id was provided in the args we use it - if len(args) > 0 { - // make sure the id is lowercase - orgTokenID = strings.ToLower(args[0]) - } - if tokenRole == "" { - // no role was provided so ask the user for it - tokenRole = input.Text("Enter a role for the API token (Possible values are DEPLOYMENT_ADMIN or a custom role name): ") - } - cmd.SilenceUsage = true - - return deployment.UpsertOrgTokenDeploymentRole(orgTokenID, orgTokenName, tokenRole, deploymentID, "create", out, astroCoreClient, astroCoreIamClient) -} - -func updateOrgTokenToDeploymentRole(cmd *cobra.Command, args []string, out io.Writer) error { - if deploymentID == "" { - return errors.New("flag --deployment-id is required") - } - // if an id was provided in the args we use it - if len(args) > 0 { - // make sure the id is lowercase - orgTokenID = strings.ToLower(args[0]) - } - if tokenRole == "" { - // no role was provided so ask the user for it - tokenRole = input.Text("Enter a role for the new Deployment API token (Possible values are DEPLOYMENT_ADMIN or a custom role name): ") - } - cmd.SilenceUsage = true - - return deployment.UpsertOrgTokenDeploymentRole(orgTokenID, orgTokenName, tokenRole, deploymentID, "update", out, astroCoreClient, astroCoreIamClient) -} - -func addWorkspaceTokenDeploymentRole(cmd *cobra.Command, args []string, out io.Writer) error { - if deploymentID == "" { - return errors.New("flag --deployment-id is required") - } - // if an id was provided in the args we use it - if len(args) > 0 { - // make sure the id is lowercase - workspaceTokenID = strings.ToLower(args[0]) - } - - if tokenRole == "" { - // no role was provided so ask the user for it - tokenRole = input.Text("Enter a role for the API token (Possible values are DEPLOYMENT_ADMIN or a custom role name): ") - } - - cmd.SilenceUsage = true - return deployment.UpsertWorkspaceTokenDeploymentRole(workspaceTokenID, orgTokenName, tokenRole, workspaceID, deploymentID, "create", out, astroCoreClient, astroCoreIamClient) -} - -func updateWorkspaceTokenDeploymentRole(cmd *cobra.Command, args []string, out io.Writer) error { - if deploymentID == "" { - return errors.New("flag --deployment-id is required") - } - // if an id was provided in the args we use it - if len(args) > 0 { - // make sure the id is lowercase - workspaceTokenID = strings.ToLower(args[0]) - } - - if tokenRole == "" { - // no role was provided so ask the user for it - tokenRole = input.Text("Enter a role for the new Deployment API token (Possible values are DEPLOYMENT_ADMIN or a custom role name): ") - } - - cmd.SilenceUsage = true - return deployment.UpsertWorkspaceTokenDeploymentRole(workspaceTokenID, orgTokenName, tokenRole, workspaceID, deploymentID, "update", out, astroCoreClient, astroCoreIamClient) -} - -func newRemoveOrganizationTokenDeploymentRole(out io.Writer) *cobra.Command { - cmd := &cobra.Command{ - Use: "remove [ORG_TOKEN_ID]", - Short: "Remove an Organization API token's Deployment Role", - Long: "Remove an Organization API token's Deployment Role\n$astro deployment token organization-token remove [ORG_TOKEN_ID] --org-token-name [token name].", - RunE: func(cmd *cobra.Command, args []string) error { - return removeOrgTokenFromDeploymentRole(cmd, args, out) - }, - } - cmd.Flags().StringVarP(&orgTokenName, "org-token-name", "n", "", "The name of the Organization API token you want to remove from a Deployment. If the name contains a space, specify the entire name within quotes \"\" ") - return cmd -} - -func newRemoveWorkspaceTokenDeploymentRole(out io.Writer) *cobra.Command { - cmd := &cobra.Command{ - Use: "remove [WORKSPACE_TOKEN_ID]", - Short: "Remove a Workspace API token's Deployment Role", - Long: "Remove a Workspace API token's Deployment Role\n$astro deployment token workspace-token remove [WORKSPACE_TOKEN_ID] --workspace-token-name [token name].", - RunE: func(cmd *cobra.Command, args []string) error { - return removeWorkspaceTokenDeploymentRole(cmd, args, out) - }, - } - cmd.Flags().StringVarP(&orgTokenName, "workspace-token-name", "n", "", "The name of the WORKSPACE API token you want to remove from a Deployment. If the name contains a space, specify the entire name within quotes \"\" ") - return cmd -} - -func removeOrgTokenFromDeploymentRole(cmd *cobra.Command, args []string, out io.Writer) error { - if deploymentID == "" { - return errors.New("flag --deployment-id is required") - } - // if an id was provided in the args we use it - if len(args) > 0 { - // make sure the id is lowercase - orgTokenID = strings.ToLower(args[0]) - } - - cmd.SilenceUsage = true - return deployment.RemoveOrgTokenDeploymentRole(orgTokenID, orgTokenName, deploymentID, out, astroCoreClient, astroCoreIamClient) -} - -func removeWorkspaceTokenDeploymentRole(cmd *cobra.Command, args []string, out io.Writer) error { - if deploymentID == "" { - return errors.New("flag --deployment-id is required") - } - // if an id was provided in the args we use it - if len(args) > 0 { - // make sure the id is lowercase - workspaceTokenID = strings.ToLower(args[0]) - } - - cmd.SilenceUsage = true - return deployment.RemoveWorkspaceTokenDeploymentRole(workspaceTokenID, orgTokenName, workspaceID, deploymentID, out, astroCoreClient, astroCoreIamClient) -} - -func newListOrganizationTokensInDeployment(out io.Writer) *cobra.Command { - cmd := &cobra.Command{ - Use: "list", - Short: "List all Organization API tokens in a deployment", - Long: "List all Organization API tokens in a deployment\n$astro deployment token organization-token list", - RunE: func(cmd *cobra.Command, args []string) error { - return listOrganizationTokensInDeployment(cmd, out) - }, - } - return cmd -} - -func newListWorkspaceTokensInDeployment(out io.Writer) *cobra.Command { - cmd := &cobra.Command{ - Use: "list", - Short: "List all Workspace API tokens in a deployment", - Long: "List all Workspace API tokens in a deployment\n$astro deployment token workspace-token list", - RunE: func(cmd *cobra.Command, args []string) error { - return listWorkspaceTokensInDeployment(cmd, out) - }, - } - return cmd -} - -func listOrganizationTokensInDeployment(cmd *cobra.Command, out io.Writer) error { - if deploymentID == "" { - return errors.New("flag --deployment-id is required") - } - // if an id was provided in the args we use it - - cmd.SilenceUsage = true - tokenTypes := []astrocore.ListDeploymentApiTokensParamsTokenTypes{ - "ORGANIZATION", - } - return deployment.ListTokens(astroCoreClient, deploymentID, &tokenTypes, out) -} - -func listWorkspaceTokensInDeployment(cmd *cobra.Command, out io.Writer) error { - if deploymentID == "" { - return errors.New("flag --deployment-id is required") - } - // if an id was provided in the args we use it - - cmd.SilenceUsage = true - tokenTypes := []astrocore.ListDeploymentApiTokensParamsTokenTypes{ - "WORKSPACE", - } - return deployment.ListTokens(astroCoreClient, deploymentID, &tokenTypes, out) -} - -func listDeploymentToken(cmd *cobra.Command, out io.Writer) error { - if deploymentID == "" { - return errors.New("flag --deployment-id is required") - } - cmd.SilenceUsage = true - return deployment.ListTokens(astroCoreClient, deploymentID, nil, out) -} - -func createDeploymentToken(cmd *cobra.Command, out io.Writer) error { - if deploymentID == "" { - return errors.New("flag --deployment-id is required") - } - if tokenName == "" { - // no role was provided so ask the user for it - tokenName = input.Text("Enter a name for the new Deployment API token: ") - } - - if tokenRole == "" { - // no role was provided so ask the user for it - tokenRole = input.Text("Enter a role for the new Deployment API token (Possible values are DEPLOYMENT_ADMIN or a custom role name): ") - } - - cmd.SilenceUsage = true - return deployment.CreateToken(tokenName, tokenDescription, tokenRole, deploymentID, tokenExpiration, cleanTokenOutput, out, astroCoreClient) -} - -func updateDeploymentToken(cmd *cobra.Command, args []string, out io.Writer) error { - if deploymentID == "" { - return errors.New("flag --deployment-id is required") - } - // if an id was provided in the args we use it - if len(args) > 0 { - // make sure the id is lowercase - tokenID = strings.ToLower(args[0]) - } - - cmd.SilenceUsage = true - return deployment.UpdateToken(tokenID, name, tokenName, tokenDescription, tokenRole, deploymentID, out, astroCoreClient, astroCoreIamClient) -} - -func rotateDeploymentToken(cmd *cobra.Command, args []string, out io.Writer) error { - if deploymentID == "" { - return errors.New("flag --deployment-id is required") - } - // if an id was provided in the args we use it - if len(args) > 0 { - // make sure the id is lowercase - tokenID = strings.ToLower(args[0]) - } - cmd.SilenceUsage = true - return deployment.RotateToken(tokenID, name, deploymentID, cleanTokenOutput, forceRotate, out, astroCoreClient, astroCoreIamClient) -} - -func deleteDeploymentToken(cmd *cobra.Command, args []string, out io.Writer) error { - if deploymentID == "" { - return errors.New("flag --deployment-id is required") - } - // if an id was provided in the args we use it - if len(args) > 0 { - // make sure the id is lowercase - tokenID = strings.ToLower(args[0]) - } - - cmd.SilenceUsage = true - return deployment.DeleteToken(tokenID, name, deploymentID, forceDelete, out, astroCoreClient, astroCoreIamClient) -} - -func getOverrideUntil(until, forDuration string) (*time.Time, error) { - if until != "" { - untilParsed, err := time.Parse(time.RFC3339, until) - if err != nil { - return nil, err - } - return &untilParsed, nil - } - if forDuration != "" { - forDurationParsed, err := time.ParseDuration(forDuration) - if err != nil { - return nil, err - } - overrideUntil := time.Now().Add(forDurationParsed) - return &overrideUntil, nil - } - return nil, nil -} - -func fromCsv(s string) *[]string { - ss := strings.Split(strings.TrimSpace(s), ",") - return &ss -} diff --git a/cmd/cloud/deployment_inspect.go b/cmd/cloud/deployment_inspect.go deleted file mode 100644 index 8800a058a..000000000 --- a/cmd/cloud/deployment_inspect.go +++ /dev/null @@ -1,54 +0,0 @@ -package cloud - -import ( - "io" - - "github.com/astronomer/astro-cli/cloud/deployment" - "github.com/astronomer/astro-cli/cloud/deployment/inspect" - - "github.com/spf13/cobra" -) - -var ( - outputFormat, requestedField string - template bool - cleanOutput bool - showWorkloadIdentity bool -) - -func newDeploymentInspectCmd(out io.Writer) *cobra.Command { - cmd := &cobra.Command{ - Use: "inspect", - Aliases: []string{"in"}, - Short: "Inspect a deployment configuration", - Long: "Inspect an Astro Deployment configuration, which can be useful if you manage deployments as code or use Deployment configuration templates. This command returns the Deployment's configuration as a YAML or JSON output, which includes information about resources, such as cluster ID, region, and Airflow API URL, as well as scheduler and worker queue configurations.", - RunE: func(cmd *cobra.Command, args []string) error { - return deploymentInspect(cmd, args, out) - }, - } - cmd.Flags().StringVarP(&deploymentName, "deployment-name", "n", "", "Name of the deployment to inspect.") - cmd.Flags().StringVarP(&outputFormat, "output", "o", "yaml", "Output format can be one of: yaml or json. By default the inspected deployment will be in YAML format.") - cmd.Flags().BoolVarP(&template, "template", "t", false, "Create a template from the deployment being inspected.") - cmd.Flags().StringVarP(&requestedField, "key", "k", "", "A specific key for the deployment. Use --key configuration.cluster_id to get a deployment's cluster id.") - cmd.Flags().BoolVarP(&cleanOutput, "clean-output", "c", false, "clean output to only include inspect yaml or json file in any situation.") - cmd.Flags().BoolVarP(&showWorkloadIdentity, "show-workload-identity", "", false, "Include the workload identity configured for the deployment in the output.") - return cmd -} - -func deploymentInspect(cmd *cobra.Command, args []string, out io.Writer) error { - cmd.SilenceUsage = true - - wsID, err := coalesceWorkspace() - if err != nil { - return err - } - - if len(args) > 0 { - deploymentID = args[0] - } - - // clean output - deployment.CleanOutput = cleanOutput - - return inspect.Inspect(wsID, deploymentName, deploymentID, outputFormat, platformCoreClient, astroCoreClient, out, requestedField, template, showWorkloadIdentity) -} diff --git a/cmd/cloud/deployment_inspect_test.go b/cmd/cloud/deployment_inspect_test.go deleted file mode 100644 index f1dd2165d..000000000 --- a/cmd/cloud/deployment_inspect_test.go +++ /dev/null @@ -1,98 +0,0 @@ -package cloud - -import ( - "fmt" - "testing" - - astroplatformcore_mocks "github.com/astronomer/astro-cli/astro-client-platform-core/mocks" - "github.com/stretchr/testify/mock" - - testUtil "github.com/astronomer/astro-cli/pkg/testing" - "github.com/stretchr/testify/assert" -) - -func TestNewDeploymentInspectCmd(t *testing.T) { - expectedHelp := "Inspect an Astro Deployment configuration, which can be useful if you manage deployments as code or use Deployment configuration templates. This command returns the Deployment's configuration as a YAML or JSON output, which includes information about resources, such as cluster ID, region, and Airflow API URL, as well as scheduler and worker queue configurations." - testUtil.InitTestConfig(testUtil.LocalPlatform) - mockPlatformCoreClient := new(astroplatformcore_mocks.ClientWithResponsesInterface) - platformCoreClient = mockPlatformCoreClient - - t.Run("-h prints help", func(t *testing.T) { - cmdArgs := []string{"inspect", "-h"} - resp, err := execDeploymentCmd(cmdArgs...) - assert.NoError(t, err) - assert.Contains(t, resp, expectedHelp) - }) - t.Run("returns deployment in yaml format when a deployment name was provided", func(t *testing.T) { - mockPlatformCoreClient.On("ListDeploymentsWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&mockListDeploymentsResponse, nil).Times(1) - mockPlatformCoreClient.On("GetDeploymentWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&deploymentResponse, nil).Times(1) - mockPlatformCoreClient.On("GetClusterWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&mockGetClusterResponse, nil).Times(1) - - cmdArgs := []string{"inspect", "-n", "test"} - resp, err := execDeploymentCmd(cmdArgs...) - assert.NoError(t, err) - assert.Contains(t, resp, deploymentResponse.JSON200.Namespace) - assert.Contains(t, resp, deploymentResponse.JSON200.Name) - assert.Contains(t, resp, deploymentResponse.JSON200.RuntimeVersion) - mockPlatformCoreClient.AssertExpectations(t) - }) - t.Run("returns deployment in yaml format when a deployment id was provided", func(t *testing.T) { - mockPlatformCoreClient.On("ListDeploymentsWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&mockListDeploymentsResponse, nil).Times(1) - mockPlatformCoreClient.On("GetDeploymentWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&deploymentResponse, nil).Times(1) - mockPlatformCoreClient.On("GetClusterWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&mockGetClusterResponse, nil).Times(1) - cmdArgs := []string{"inspect", "test-id-1"} - resp, err := execDeploymentCmd(cmdArgs...) - assert.NoError(t, err) - assert.Contains(t, resp, deploymentResponse.JSON200.Namespace) - assert.Contains(t, resp, deploymentResponse.JSON200.Name) - assert.Contains(t, resp, deploymentResponse.JSON200.RuntimeVersion) - mockPlatformCoreClient.AssertExpectations(t) - }) - t.Run("returns deployment template in yaml format when a deployment id was provided", func(t *testing.T) { - mockPlatformCoreClient.On("ListDeploymentsWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&mockListDeploymentsResponse, nil).Times(1) - mockPlatformCoreClient.On("GetDeploymentWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&deploymentResponse, nil).Times(1) - mockPlatformCoreClient.On("GetClusterWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&mockGetClusterResponse, nil).Times(1) - cmdArgs := []string{"inspect", "test-id-1", "--template"} - resp, err := execDeploymentCmd(cmdArgs...) - assert.NoError(t, err) - assert.Contains(t, resp, deploymentResponse.JSON200.RuntimeVersion) - mockPlatformCoreClient.AssertExpectations(t) - }) - t.Run("returns a deployment's specific field", func(t *testing.T) { - mockPlatformCoreClient.On("ListDeploymentsWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&mockListDeploymentsResponse, nil).Times(1) - mockPlatformCoreClient.On("GetDeploymentWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&deploymentResponse, nil).Times(1) - mockPlatformCoreClient.On("GetClusterWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&mockGetClusterResponse, nil).Times(1) - cmdArgs := []string{"inspect", "-n", "test", "-k", "metadata.cluster_id"} - _, err := execDeploymentCmd(cmdArgs...) - assert.NoError(t, err) - mockPlatformCoreClient.AssertExpectations(t) - }) - t.Run("returns empty workload identity when show-workload-identity flag is not passed", func(t *testing.T) { - mockPlatformCoreClient.On("ListDeploymentsWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&mockListDeploymentsResponse, nil).Times(1) - mockPlatformCoreClient.On("GetDeploymentWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&deploymentResponse, nil).Times(1) - mockPlatformCoreClient.On("GetClusterWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&mockGetClusterResponse, nil).Times(1) - cmdArgs := []string{"inspect", "-n", "test"} - out, err := execDeploymentCmd(cmdArgs...) - assert.NoError(t, err) - assert.Contains(t, out, `workload_identity: ""`) - mockPlatformCoreClient.AssertExpectations(t) - }) - t.Run("returns workload identity when show-workload-identity flag is passed", func(t *testing.T) { - mockPlatformCoreClient.On("ListDeploymentsWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&mockListDeploymentsResponse, nil).Times(1) - mockPlatformCoreClient.On("GetDeploymentWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&deploymentResponse, nil).Times(1) - mockPlatformCoreClient.On("GetClusterWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&mockGetClusterResponse, nil).Times(1) - cmdArgs := []string{"inspect", "-n", "test", "--show-workload-identity"} - out, err := execDeploymentCmd(cmdArgs...) - assert.NoError(t, err) - assert.Contains(t, out, fmt.Sprintf(`workload_identity: %s`, mockWorkloadIdentity)) - mockPlatformCoreClient.AssertExpectations(t) - }) - t.Run("returns an error when getting workspace fails", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.Initial) - expectedOut := "Usage:\n" - cmdArgs := []string{"inspect", "-n", "doesnotexist"} - resp, err := execDeploymentCmd(cmdArgs...) - assert.Error(t, err) - assert.NotContains(t, resp, expectedOut) - }) -} diff --git a/cmd/cloud/deployment_test.go b/cmd/cloud/deployment_test.go index 94eb5c0ef..969a9dc20 100644 --- a/cmd/cloud/deployment_test.go +++ b/cmd/cloud/deployment_test.go @@ -2,33 +2,33 @@ package cloud import ( "bytes" - "encoding/json" "fmt" - "io" "net/http" "os" "testing" - "time" - airflowversions "github.com/astronomer/astro-cli/airflow_versions" astrocore "github.com/astronomer/astro-cli/astro-client-core" astrocore_mocks "github.com/astronomer/astro-cli/astro-client-core/mocks" - astroiamcore_mocks "github.com/astronomer/astro-cli/astro-client-iam-core/mocks" astroplatformcore "github.com/astronomer/astro-cli/astro-client-platform-core" astroplatformcore_mocks "github.com/astronomer/astro-cli/astro-client-platform-core/mocks" "github.com/astronomer/astro-cli/cloud/deployment" - "github.com/astronomer/astro-cli/config" - "github.com/astronomer/astro-cli/context" - "github.com/astronomer/astro-cli/pkg/fileutil" testUtil "github.com/astronomer/astro-cli/pkg/testing" - "github.com/spf13/afero" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" ) var ( + clusterID string + cloudProvider string + region string + schedulerAU int + resourceQuotaCPU string + workspaceName string + defaultTaskPodCPU string + defaultTaskPodMemory string + mockTokenID = "ck05r3bor07h40d02y2hw4n4t" csID = "test-cluster-id" testCluster = "test-cluster" @@ -419,21 +419,6 @@ func TestDeploymentRootCommand(t *testing.T) { assert.Contains(t, buf.String(), "deployment") } -func TestDeploymentList(t *testing.T) { - testUtil.InitTestConfig(testUtil.LocalPlatform) - - mockPlatformCoreClient := new(astroplatformcore_mocks.ClientWithResponsesInterface) - mockPlatformCoreClient.On("ListDeploymentsWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&mockListDeploymentsResponse, nil).Once() - platformCoreClient = mockPlatformCoreClient - - cmdArgs := []string{"list", "-a"} - resp, err := execDeploymentCmd(cmdArgs...) - assert.NoError(t, err) - assert.Contains(t, resp, "test-id-1") - assert.Contains(t, resp, "test-id-2") - mockPlatformCoreClient.AssertExpectations(t) -} - func TestDeploymentLogs(t *testing.T) { testUtil.InitTestConfig(testUtil.LocalPlatform) @@ -486,810 +471,6 @@ func TestDeploymentLogsMultipleComponents(t *testing.T) { mockCoreClient.AssertExpectations(t) } -func TestDeploymentCreate(t *testing.T) { - testUtil.InitTestConfig(testUtil.LocalPlatform) - - ws := "workspace-id" - mockCoreClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockPlatformCoreClient := new(astroplatformcore_mocks.ClientWithResponsesInterface) - - mockResponse := &airflowversions.Response{ - RuntimeVersions: map[string]airflowversions.RuntimeVersion{ - "4.2.5": {Metadata: airflowversions.RuntimeVersionMetadata{AirflowVersion: "2.2.5", Channel: "stable"}, Migrations: airflowversions.RuntimeVersionMigrations{}}, - }, - } - jsonResponse, err := json.Marshal(mockResponse) - assert.NoError(t, err) - - httpClient = testUtil.NewTestClient(func(req *http.Request) *http.Response { - return &http.Response{ - StatusCode: 200, - Body: io.NopCloser(bytes.NewBuffer(jsonResponse)), - Header: make(http.Header), - } - }) - t.Run("creates a deployment when dag-deploy is disabled", func(t *testing.T) { - mockCoreClient.On("GetDeploymentOptionsWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&GetDeploymentOptionsResponseAlphaOK, nil).Times(1) - mockPlatformCoreClient.On("CreateDeploymentWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&mockCreateDeploymentResponse, nil).Times(1) - mockCoreClient.On("ListWorkspacesWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&ListWorkspacesResponseOK, nil).Times(1) - mockPlatformCoreClient.On("ListClustersWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&mockListClustersResponse, nil).Times(1) - mockPlatformCoreClient.On("GetClusterWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&mockGetClusterResponse, nil).Times(1) - - cmdArgs := []string{"create", "--name", "test", "--workspace-id", ws, "--cluster-id", csID, "--dag-deploy", "disable"} - astroCoreClient = mockCoreClient - platformCoreClient = mockPlatformCoreClient - _, err = execDeploymentCmd(cmdArgs...) - assert.NoError(t, err) - - mockPlatformCoreClient.AssertExpectations(t) - mockCoreClient.AssertExpectations(t) - }) - t.Run("creates a deployment when dag deploy is enabled", func(t *testing.T) { - mockCoreClient.On("GetDeploymentOptionsWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&GetDeploymentOptionsResponseAlphaOK, nil).Times(1) - mockPlatformCoreClient.On("CreateDeploymentWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&mockCreateDeploymentResponse, nil).Times(1) - mockCoreClient.On("ListWorkspacesWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&ListWorkspacesResponseOK, nil).Times(1) - mockPlatformCoreClient.On("ListClustersWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&mockListClustersResponse, nil).Times(1) - mockPlatformCoreClient.On("GetClusterWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&mockGetClusterResponse, nil).Times(1) - - cmdArgs := []string{"create", "--name", "test-name", "--workspace-id", ws, "--cluster-id", csID, "--dag-deploy", "enable"} - astroCoreClient = mockCoreClient - platformCoreClient = mockPlatformCoreClient - _, err = execDeploymentCmd(cmdArgs...) - assert.NoError(t, err) - - mockPlatformCoreClient.AssertExpectations(t) - mockCoreClient.AssertExpectations(t) - }) - t.Run("creates a deployment when executor is specified", func(t *testing.T) { - mockCoreClient.On("GetDeploymentOptionsWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&GetDeploymentOptionsResponseAlphaOK, nil).Times(1) - mockPlatformCoreClient.On("CreateDeploymentWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&mockCreateDeploymentResponse, nil).Times(1) - mockCoreClient.On("ListWorkspacesWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&ListWorkspacesResponseOK, nil).Times(1) - mockPlatformCoreClient.On("ListClustersWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&mockListClustersResponse, nil).Times(1) - mockPlatformCoreClient.On("GetClusterWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&mockGetClusterResponse, nil).Times(1) - - cmdArgs := []string{"create", "--name", "test-name", "--workspace-id", ws, "--cluster-id", csID, "--dag-deploy", "disable", "--executor", "KubernetesExecutor"} - astroCoreClient = mockCoreClient - platformCoreClient = mockPlatformCoreClient - _, err = execDeploymentCmd(cmdArgs...) - assert.NoError(t, err) - - mockPlatformCoreClient.AssertExpectations(t) - mockCoreClient.AssertExpectations(t) - }) - t.Run("creates a deployment with default executor", func(t *testing.T) { - mockCoreClient.On("GetDeploymentOptionsWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&GetDeploymentOptionsResponseAlphaOK, nil).Times(1) - mockPlatformCoreClient.On("CreateDeploymentWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&mockCreateDeploymentResponse, nil).Times(1) - mockCoreClient.On("ListWorkspacesWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&ListWorkspacesResponseOK, nil).Times(1) - mockPlatformCoreClient.On("ListClustersWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&mockListClustersResponse, nil).Times(1) - mockPlatformCoreClient.On("GetClusterWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&mockGetClusterResponse, nil).Times(1) - - cmdArgs := []string{"create", "--name", "test-name", "--workspace-id", ws, "--cluster-id", csID, "--dag-deploy", "disable"} - astroCoreClient = mockCoreClient - platformCoreClient = mockPlatformCoreClient - _, err = execDeploymentCmd(cmdArgs...) - assert.NoError(t, err) - - mockPlatformCoreClient.AssertExpectations(t) - mockCoreClient.AssertExpectations(t) - }) - t.Run("returns an error if dag-deploy flag has an incorrect value", func(t *testing.T) { - cmdArgs := []string{"create", "--name", "test-name", "--workspace-id", ws, "--cluster-id", csID, "--dag-deploy", "some-value"} - _, err = execDeploymentCmd(cmdArgs...) - assert.Error(t, err) - }) - t.Run("returns an error if cluster-type flag has an incorrect value", func(t *testing.T) { - cmdArgs := []string{"create", "--name", "test-name", "--workspace-id", ws, "--cluster-id", csID, "--cluster-type", "some-value"} - _, err = execDeploymentCmd(cmdArgs...) - assert.Error(t, err) - }) - t.Run("returns an error if type flag has an incorrect value", func(t *testing.T) { - cmdArgs := []string{"create", "--name", "test-name", "--workspace-id", ws, "--cluster-id", csID, "--type", "some-value"} - _, err = execDeploymentCmd(cmdArgs...) - assert.Error(t, err) - }) - t.Run("returns an error if cicd-enforcement flag has an incorrect value", func(t *testing.T) { - cmdArgs := []string{"create", "--name", "test-name", "--workspace-id", ws, "--cluster-id", csID, "--cicd-enforcement", "some-value"} - _, err = execDeploymentCmd(cmdArgs...) - assert.Error(t, err) - }) - t.Run("returns an error if executor has an incorrect value", func(t *testing.T) { - cmdArgs := []string{"create", "--name", "test-name", "--workspace-id", ws, "--cluster-id", csID, "--dag-deploy", "disable", "--executor", "KubeExecutor"} - _, err = execDeploymentCmd(cmdArgs...) - assert.ErrorContains(t, err, "KubeExecutor is not a valid executor") - }) - t.Run("returns an error if remote-execution-enabled flag is set but org is not hosted", func(t *testing.T) { - cmdArgs := []string{"create", "--name", "test-name", "--workspace-id", ws, "--cluster-id", csID, "--remote-execution-enabled"} - _, err = execDeploymentCmd(cmdArgs...) - assert.ErrorContains(t, err, "unknown flag: --remote-execution-enabled") - }) - t.Run("creates a deployment from file", func(t *testing.T) { - filePath := "./test-deployment.yaml" - data := ` -deployment: - environment_variables: - - is_secret: false - key: foo - updated_at: NOW - value: bar - - is_secret: true - key: bar - updated_at: NOW+1 - value: baz - configuration: - name: test-deployment-label - description: description - runtime_version: 6.0.0 - dag_deploy_enabled: true - executor: CeleryExecutor - scheduler_au: 5 - scheduler_count: 3 - cluster_name: test-cluster - workspace_name: test-workspace - deployment_type: HYBRID - worker_queues: - - name: default - is_default: true - max_worker_count: 130 - min_worker_count: 12 - worker_concurrency: 180 - worker_type: test-worker-1 - - name: test-queue-1 - is_default: false - max_worker_count: 175 - min_worker_count: 8 - worker_concurrency: 176 - worker_type: test-worker-2 - metadata: - deployment_id: test-deployment-id - workspace_id: test-ws-id - cluster_id: cluster-id - release_name: great-release-name - airflow_version: 2.4.0 - status: UNHEALTHY - created_at: 2022-11-17T13:25:55.275697-08:00 - updated_at: 2022-11-17T13:25:55.275697-08:00 - deployment_url: cloud.astronomer.io/test-ws-id/deployments/test-deployment-id - webserver_url: some-url - alert_emails: - - test1@test.com - - test2@test.com -` - mockPlatformCoreClient.On("GetDeploymentOptionsWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&GetDeploymentOptionsResponseOK, nil).Times(2) - mockCoreClient.On("ListWorkspacesWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&ListWorkspacesResponseOK, nil).Times(1) - mockPlatformCoreClient.On("ListClustersWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&mockListClustersResponse, nil).Once() - mockPlatformCoreClient.On("ListDeploymentsWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&mockListDeploymentsResponse, nil).Times(1) - mockPlatformCoreClient.On("ListDeploymentsWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&mockListDeploymentsCreateResponse, nil).Times(2) - mockPlatformCoreClient.On("CreateDeploymentWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&mockCreateDeploymentResponse, nil).Once() - mockPlatformCoreClient.On("UpdateDeploymentWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&mockUpdateDeploymentResponse, nil).Times(1) - mockPlatformCoreClient.On("GetDeploymentWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&deploymentResponse, nil).Times(3) - mockPlatformCoreClient.On("GetClusterWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&mockGetClusterResponse, nil).Once() - - astroCoreClient = mockCoreClient - platformCoreClient = mockPlatformCoreClient - fileutil.WriteStringToFile(filePath, data) - defer afero.NewOsFs().Remove(filePath) - - cmdArgs := []string{"create", "--deployment-file", "test-deployment.yaml"} - astroCoreClient = mockCoreClient - _, err = execDeploymentCmd(cmdArgs...) - assert.NoError(t, err) - - mockPlatformCoreClient.AssertExpectations(t) - mockCoreClient.AssertExpectations(t) - }) - t.Run("returns an error if creating a deployment from file fails", func(t *testing.T) { - cmdArgs := []string{"create", "--deployment-file", "test-file-name.json"} - _, err = execDeploymentCmd(cmdArgs...) - assert.ErrorContains(t, err, "open test-file-name.json: no such file or directory") - }) - t.Run("returns an error if from-file is specified with any other flags", func(t *testing.T) { - cmdArgs := []string{"create", "--deployment-file", "test-deployment.yaml", "--description", "fail"} - _, err = execDeploymentCmd(cmdArgs...) - assert.ErrorIs(t, err, errFlag) - }) - t.Run("creates a deployment from file when supported flags are set", func(t *testing.T) { - filePath := "./test-deployment.yaml" - data := ` -deployment: - environment_variables: - - is_secret: false - key: foo - updated_at: NOW - value: bar - - is_secret: true - key: bar - updated_at: NOW+1 - value: baz - configuration: - name: test-deployment-label - description: description - runtime_version: 6.0.0 - dag_deploy_enabled: true - executor: CeleryExecutor - scheduler_au: 5 - scheduler_count: 3 - cluster_name: test-cluster - workspace_name: test-workspace - deployment_type: HYBRID - worker_queues: - - name: default - is_default: true - max_worker_count: 130 - min_worker_count: 12 - worker_concurrency: 180 - worker_type: test-worker-1 - - name: test-queue-1 - is_default: false - max_worker_count: 175 - min_worker_count: 8 - worker_concurrency: 176 - worker_type: test-worker-2 - metadata: - deployment_id: test-deployment-id - workspace_id: test-ws-id - cluster_id: cluster-id - release_name: great-release-name - airflow_version: 2.4.0 - status: UNHEALTHY - created_at: 2022-11-17T13:25:55.275697-08:00 - updated_at: 2022-11-17T13:25:55.275697-08:00 - deployment_url: cloud.astronomer.io/test-ws-id/deployments/test-deployment-id - webserver_url: some-url - alert_emails: - - test1@test.com - - test2@test.com -` - astroCoreClient = mockCoreClient - platformCoreClient = mockPlatformCoreClient - fileutil.WriteStringToFile(filePath, data) - defer afero.NewOsFs().Remove(filePath) - mockPlatformCoreClient.On("GetDeploymentOptionsWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&GetDeploymentOptionsResponseOK, nil).Times(2) - mockCoreClient.On("ListWorkspacesWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&ListWorkspacesResponseOK, nil).Times(1) - mockPlatformCoreClient.On("ListClustersWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&mockListClustersResponse, nil).Once() - mockPlatformCoreClient.On("ListDeploymentsWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&mockListDeploymentsResponse, nil).Times(1) - mockPlatformCoreClient.On("ListDeploymentsWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&mockListDeploymentsCreateResponse, nil).Times(2) - mockPlatformCoreClient.On("CreateDeploymentWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&mockCreateDeploymentResponse, nil).Once() - mockPlatformCoreClient.On("UpdateDeploymentWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&mockUpdateDeploymentResponse, nil).Times(1) - mockPlatformCoreClient.On("GetDeploymentWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&deploymentResponse, nil).Times(4) - mockPlatformCoreClient.On("GetClusterWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&mockGetClusterResponse, nil).Once() - - origSleep := deployment.SleepTime - origTick := deployment.TickNum - deployment.SleepTime = 0 - deployment.TickNum = 1 - defer func() { - deployment.SleepTime = origSleep - deployment.TickNum = origTick - }() - - cmdArgs := []string{"create", "--deployment-file", "test-deployment.yaml", "--wait", "--verbosity", "debug"} - astroCoreClient = mockCoreClient - _, err = execDeploymentCmd(cmdArgs...) - assert.NoError(t, err) - - mockPlatformCoreClient.AssertExpectations(t) - mockCoreClient.AssertExpectations(t) - }) - t.Run("returns an error if from-file is specified with supported and unsupported flags", func(t *testing.T) { - cmdArgs := []string{"create", "--deployment-file", "test-deployment.yaml", "--wait", "--description", "fail"} - _, err = execDeploymentCmd(cmdArgs...) - assert.ErrorIs(t, err, errFlag) - }) - t.Run("creates a deployment with cloud provider and region", func(t *testing.T) { - ctx, err := context.GetCurrentContext() - assert.NoError(t, err) - ctx.SetContextKey("organization_product", "HOSTED") - ctx.SetContextKey("organization", "test-org-id") - ctx.SetContextKey("organization_short_name", "test-org") - ctx.SetContextKey("workspace", ws) - - astroCoreClient = mockCoreClient - platformCoreClient = mockPlatformCoreClient - mockCoreClient.On("GetDeploymentOptionsWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&GetDeploymentOptionsResponseAlphaOK, nil).Once() - mockCoreClient.On("ListWorkspacesWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&ListWorkspacesResponseOK, nil).Once() - mockPlatformCoreClient.On("CreateDeploymentWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&mockCreateDeploymentResponse, nil).Once() - - cmdArgs := []string{ - "create", "--name", "test-name", "--workspace-id", ws, "--dag-deploy", "disable", - "--cloud-provider", "gcp", "--region", "us-central1", - } - _, err = execDeploymentCmd(cmdArgs...) - assert.NoError(t, err) - mockPlatformCoreClient.AssertExpectations(t) - mockCoreClient.AssertExpectations(t) - }) - t.Run("returns an error with incorrect high-availability value", func(t *testing.T) { - ctx, err := context.GetCurrentContext() - assert.NoError(t, err) - ctx.SetContextKey("organization_product", "HOSTED") - ctx.SetContextKey("organization_short_name", "test-org") - ctx.SetContextKey("organization", "test-org-id") - ctx.SetContextKey("workspace", ws) - cmdArgs := []string{ - "create", "--name", "test-name", "--workspace-id", ws, "--dag-deploy", "disable", - "--executor", "KubernetesExecutor", "--cloud-provider", "gcp", "--region", "us-east1", "--high-availability", "some-value", - } - _, err = execDeploymentCmd(cmdArgs...) - assert.ErrorContains(t, err, "Invalid --high-availability value") - }) - t.Run("returns an error with incorrect development-mode value", func(t *testing.T) { - ctx, err := context.GetCurrentContext() - assert.NoError(t, err) - ctx.SetContextKey("organization_product", "HOSTED") - ctx.SetContextKey("organization_short_name", "test-org") - ctx.SetContextKey("organization", "test-org-id") - ctx.SetContextKey("workspace", ws) - cmdArgs := []string{ - "create", "--name", "test-name", "--workspace-id", ws, "--dag-deploy", "disable", - "--executor", "KubernetesExecutor", "--cloud-provider", "gcp", "--region", "us-east1", "--development-mode", "some-value", - } - _, err = execDeploymentCmd(cmdArgs...) - assert.ErrorContains(t, err, "Invalid --development-mode value") - }) - t.Run("returns an error if cloud provider is not valid", func(t *testing.T) { - ctx, err := context.GetCurrentContext() - assert.NoError(t, err) - ctx.SetContextKey("organization_product", "HOSTED") - ctx.SetContextKey("organization", "test-org-id") - ctx.SetContextKey("workspace", ws) - cmdArgs := []string{ - "create", "--name", "test-name", "--workspace-id", ws, "--dag-deploy", "disable", - "--executor", "KubernetesExecutor", "--cloud-provider", "ibm", - } - _, err = execDeploymentCmd(cmdArgs...) - assert.ErrorContains(t, err, "ibm is not a valid cloud provider. It can only be gcp") - }) - t.Run("creates a hosted dedicated deployment", func(t *testing.T) { - ctx, err := context.GetCurrentContext() - assert.NoError(t, err) - ctx.SetContextKey("organization_product", "HOSTED") - ctx.SetContextKey("organization", "test-org-id") - ctx.SetContextKey("workspace", ws) - ctx.SetContextKey("organization_short_name", "test-org") - mockCoreClient.On("GetDeploymentOptionsWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&GetDeploymentOptionsResponseAlphaOK, nil).Once() - mockCoreClient.On("ListWorkspacesWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&ListWorkspacesResponseOK, nil).Once() - mockPlatformCoreClient.On("ListClustersWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&mockListClustersResponse, nil).Once() - mockPlatformCoreClient.On("CreateDeploymentWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&mockCreateDeploymentResponse, nil).Once() - astroCoreClient = mockCoreClient - platformCoreClient = mockPlatformCoreClient - cmdArgs := []string{ - "create", "--name", "test-name", "--workspace-id", ws, "--type", "dedicated", "--remote-execution-enabled", "--allowed-ip-address-ranges", "0.0.0.0/0", "--task-log-bucket", "test-bucket", "--task-log-url-pattern", "test-url-pattern", - } - - // Mock user input for deployment name and wait for status - defer testUtil.MockUserInput(t, "test-name")() - defer testUtil.MockUserInput(t, "1")() - - _, err = execDeploymentCmd(cmdArgs...) - assert.NoError(t, err) - mockPlatformCoreClient.AssertExpectations(t) - mockCoreClient.AssertExpectations(t) - }) - t.Run("returns an error if incorrect cluster type is passed for a hosted dedicated deployment", func(t *testing.T) { - ctx, err := context.GetCurrentContext() - assert.NoError(t, err) - ctx.SetContextKey("organization_product", "HOSTED") - ctx.SetContextKey("organization", "test-org-id") - ctx.SetContextKey("workspace", ws) - astroCoreClient = mockCoreClient - cmdArgs := []string{ - "create", "--name", "test-name", "--workspace-id", ws, "--type", "wrong-value", - } - - _, err = execDeploymentCmd(cmdArgs...) - assert.ErrorContains(t, err, "Invalid --type value") - mockCoreClient.AssertExpectations(t) - }) - - t.Run("creates an extra large deployment", func(t *testing.T) { - ctx, err := context.GetCurrentContext() - assert.NoError(t, err) - extraLarge := astroplatformcore.DeploymentSchedulerSizeEXTRALARGE - mockCreateDeploymentResponse.JSON200.SchedulerSize = &extraLarge - ctx.SetContextKey("organization_product", "HOSTED") - ctx.SetContextKey("organization", "test-org-id") - ctx.SetContextKey("workspace", ws) - ctx.SetContextKey("organization_short_name", "test-org") - mockCoreClient.On("GetDeploymentOptionsWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&GetDeploymentOptionsResponseAlphaOK, nil).Once() - mockCoreClient.On("ListWorkspacesWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&ListWorkspacesResponseOK, nil).Once() - mockPlatformCoreClient.On("ListClustersWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&mockListClustersResponse, nil).Once() - mockPlatformCoreClient.On("CreateDeploymentWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&mockCreateDeploymentResponse, nil).Once() - astroCoreClient = mockCoreClient - platformCoreClient = mockPlatformCoreClient - cmdArgs := []string{ - "create", "--name", "test-name", "--workspace-id", ws, "--type", "dedicated", "--scheduler-size", "extra-large", - } - - // Mock user input for deployment name and wait for status - defer testUtil.MockUserInput(t, "test-name")() - defer testUtil.MockUserInput(t, "1")() - - _, err = execDeploymentCmd(cmdArgs...) - assert.NoError(t, err) - mockPlatformCoreClient.AssertExpectations(t) - mockCoreClient.AssertExpectations(t) - }) - - t.Run("creates a hosted deployment with workload identity", func(t *testing.T) { - ctx, err := context.GetCurrentContext() - assert.NoError(t, err) - workloadIdentity := "arn:aws:iam::1234567890:role/unit-test-1" - mockCreateDeploymentResponse.JSON200.WorkloadIdentity = &workloadIdentity - ctx.SetContextKey("organization_product", "HOSTED") - ctx.SetContextKey("organization", "test-org-id") - ctx.SetContextKey("workspace", ws) - ctx.SetContextKey("organization_short_name", "test-org") - mockCoreClient.On("GetDeploymentOptionsWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&GetDeploymentOptionsResponseAlphaOK, nil).Once() - mockCoreClient.On("ListWorkspacesWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&ListWorkspacesResponseOK, nil).Once() - mockPlatformCoreClient.On("CreateDeploymentWithResponse", mock.Anything, mock.Anything, mock.MatchedBy(func(i astroplatformcore.CreateDeploymentRequest) bool { - input, _ := i.AsCreateStandardDeploymentRequest() - return input.WorkloadIdentity != nil && *input.WorkloadIdentity == workloadIdentity - })).Return(&mockCreateDeploymentResponse, nil).Once() - astroCoreClient = mockCoreClient - platformCoreClient = mockPlatformCoreClient - cmdArgs := []string{ - "create", "--name", "test-name", "--workspace-id", ws, "--type", "standard", "--workload-identity", workloadIdentity, "--cloud-provider", "aws", "--region", "us-west-2", - } - - _, err = execDeploymentCmd(cmdArgs...) - assert.NoError(t, err) - mockPlatformCoreClient.AssertExpectations(t) - mockCoreClient.AssertExpectations(t) - }) -} - -func TestDeploymentUpdate(t *testing.T) { - testUtil.InitTestConfig(testUtil.LocalPlatform) - mockCoreClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockPlatformCoreClient := new(astroplatformcore_mocks.ClientWithResponsesInterface) - ws := "test-ws-id" - - t.Run("updates the deployment successfully", func(t *testing.T) { - astroCoreClient = mockCoreClient - platformCoreClient = mockPlatformCoreClient - - mockCoreClient.On("GetDeploymentOptionsWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&GetDeploymentOptionsResponseAlphaOK, nil).Times(1) - mockPlatformCoreClient.On("UpdateDeploymentWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&mockUpdateDeploymentResponse, nil).Times(1) - mockPlatformCoreClient.On("ListDeploymentsWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&mockListDeploymentsResponse, nil).Times(1) - mockPlatformCoreClient.On("GetDeploymentWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&deploymentResponse, nil).Times(1) - mockPlatformCoreClient.On("GetClusterWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&mockGetClusterResponse, nil).Once() - - cmdArgs := []string{"update", "test-id-1", "--name", "test", "--workspace-id", ws, "--force"} - _, err := execDeploymentCmd(cmdArgs...) - assert.NoError(t, err) - mockPlatformCoreClient.AssertExpectations(t) - mockCoreClient.AssertExpectations(t) - }) - t.Run("updates the deployment successfully to enable ci-cd enforcement", func(t *testing.T) { - astroCoreClient = mockCoreClient - platformCoreClient = mockPlatformCoreClient - - mockCoreClient.On("GetDeploymentOptionsWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&GetDeploymentOptionsResponseAlphaOK, nil).Times(1) - mockPlatformCoreClient.On("UpdateDeploymentWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&mockUpdateDeploymentResponse, nil).Times(1) - mockPlatformCoreClient.On("ListDeploymentsWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&mockListDeploymentsResponse, nil).Times(1) - mockPlatformCoreClient.On("GetDeploymentWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&deploymentResponse, nil).Times(1) - mockPlatformCoreClient.On("GetClusterWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&mockGetClusterResponse, nil).Once() - cmdArgs := []string{"update", "test-id-1", "--name", "test-name", "--workspace-id", ws, "--force", "--enforce-cicd", "enable"} - _, err := execDeploymentCmd(cmdArgs...) - assert.NoError(t, err) - mockPlatformCoreClient.AssertExpectations(t) - mockCoreClient.AssertExpectations(t) - }) - t.Run("updates the deployment successfully to disable ci-cd enforcement", func(t *testing.T) { - astroCoreClient = mockCoreClient - platformCoreClient = mockPlatformCoreClient - - mockCoreClient.On("GetDeploymentOptionsWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&GetDeploymentOptionsResponseAlphaOK, nil).Times(1) - mockPlatformCoreClient.On("UpdateDeploymentWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&mockUpdateDeploymentResponse, nil).Times(1) - mockPlatformCoreClient.On("ListDeploymentsWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&mockListDeploymentsResponse, nil).Times(1) - mockPlatformCoreClient.On("GetDeploymentWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&deploymentResponse, nil).Times(1) - mockPlatformCoreClient.On("GetClusterWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&mockGetClusterResponse, nil).Once() - cmdArgs := []string{"update", "test-id-1", "--name", "test-name", "--workspace-id", ws, "--force", "--enforce-cicd", "disable"} - _, err := execDeploymentCmd(cmdArgs...) - assert.NoError(t, err) - mockPlatformCoreClient.AssertExpectations(t) - mockCoreClient.AssertExpectations(t) - }) - t.Run("returns an error if ci-cd enforcement has an incorrect value", func(t *testing.T) { - cmdArgs := []string{"update", "test-id", "--name", "test-name", "--workspace-id", ws, "--force", "--cicd-enforcement", "some-value"} - _, err := execDeploymentCmd(cmdArgs...) - assert.Error(t, err) - }) - t.Run("returns an error if cluster-type enforcement has an incorrect value", func(t *testing.T) { - cmdArgs := []string{"update", "test-id", "--name", "test-name", "--workspace-id", ws, "--force", "--cluster-type", "some-value"} - _, err := execDeploymentCmd(cmdArgs...) - assert.Error(t, err) - }) - t.Run("returns an error if type enforcement has an incorrect value", func(t *testing.T) { - cmdArgs := []string{"update", "test-id", "--name", "test-name", "--workspace-id", ws, "--force", "--type", "some-value"} - _, err := execDeploymentCmd(cmdArgs...) - assert.Error(t, err) - }) - t.Run("returns an error if dag-deploy has an incorrect value", func(t *testing.T) { - cmdArgs := []string{"update", "test-id", "--name", "test-name", "--workspace-id", ws, "--force", "--dag-deploy", "some-value"} - _, err := execDeploymentCmd(cmdArgs...) - assert.Error(t, err) - }) - t.Run("returns an error if executor has an incorrect value", func(t *testing.T) { - cmdArgs := []string{"update", "test-id", "--name", "test-name", "--workspace-id", ws, "--force", "--executor", "KubeExecutor"} - _, err := execDeploymentCmd(cmdArgs...) - assert.ErrorContains(t, err, "KubeExecutor is not a valid executor") - }) - t.Run("returns an error when getting workspace fails", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.LocalPlatform) - ctx, err := config.GetCurrentContext() - assert.NoError(t, err) - ctx.Workspace = "" - err = ctx.SetContext() - assert.NoError(t, err) - defer testUtil.InitTestConfig(testUtil.LocalPlatform) - expectedOut := "Usage:\n" - cmdArgs := []string{"update", "-n", "doesnotexist"} - resp, err := execDeploymentCmd(cmdArgs...) - assert.ErrorContains(t, err, "failed to find a valid Workspace") - assert.Contains(t, resp, expectedOut) - }) - t.Run("updates a deployment from file", func(t *testing.T) { - filePath := "./test-deployment.yaml" - data := ` -deployment: - environment_variables: - - is_secret: false - key: foo - updated_at: NOW - value: bar - - is_secret: true - key: bar - updated_at: NOW+1 - value: baz - configuration: - name: test-deployment-label - description: description - runtime_version: 6.0.0 - dag_deploy_enabled: true - executor: CeleryExecutor - scheduler_au: 5 - scheduler_count: 3 - cluster_name: test-cluster - workspace_name: test-workspace - deployment_type: HYBRID - worker_queues: - - name: default - is_default: true - max_worker_count: 130 - min_worker_count: 12 - worker_concurrency: 180 - worker_type: test-worker-1 - - name: test-queue-1 - is_default: false - max_worker_count: 175 - min_worker_count: 8 - worker_concurrency: 176 - worker_type: test-worker-2 - metadata: - deployment_id: test-deployment-id - workspace_id: test-ws-id - cluster_id: cluster-id - release_name: great-release-name - airflow_version: 2.4.0 - status: UNHEALTHY - created_at: 2022-11-17T13:25:55.275697-08:00 - updated_at: 2022-11-17T13:25:55.275697-08:00 - deployment_url: cloud.astronomer.io/test-ws-id/deployments/test-deployment-id - webserver_url: some-url - alert_emails: - - test1@test.com - - test2@test.com -` - astroCoreClient = mockCoreClient - platformCoreClient = mockPlatformCoreClient - fileutil.WriteStringToFile(filePath, data) - defer afero.NewOsFs().Remove(filePath) - mockPlatformCoreClient.On("ListClustersWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&mockListClustersResponse, nil).Once() - mockPlatformCoreClient.On("GetDeploymentOptionsWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&GetDeploymentOptionsResponseOK, nil).Times(1) - mockPlatformCoreClient.On("ListDeploymentsWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&mockListDeploymentsCreateResponse, nil).Times(3) - mockPlatformCoreClient.On("UpdateDeploymentWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&mockUpdateDeploymentResponse, nil).Times(1) - mockPlatformCoreClient.On("GetDeploymentWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&deploymentResponse, nil).Times(3) - mockPlatformCoreClient.On("GetClusterWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&mockGetClusterResponse, nil).Once() - mockCoreClient.On("ListWorkspacesWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&ListWorkspacesResponseOK, nil).Once() - - cmdArgs := []string{"update", "--deployment-file", "test-deployment.yaml"} - astroCoreClient = mockCoreClient - _, err := execDeploymentCmd(cmdArgs...) - assert.NoError(t, err) - mockPlatformCoreClient.AssertExpectations(t) - mockCoreClient.AssertExpectations(t) - }) - t.Run("returns an error if updating a deployment from file fails", func(t *testing.T) { - cmdArgs := []string{"update", "--deployment-file", "test-file-name.json"} - _, err := execDeploymentCmd(cmdArgs...) - assert.ErrorContains(t, err, "open test-file-name.json: no such file or directory") - }) - t.Run("returns an error if from-file is specified with any other flags", func(t *testing.T) { - cmdArgs := []string{"update", "--deployment-file", "test-deployment.yaml", "--description", "fail"} - _, err := execDeploymentCmd(cmdArgs...) - assert.ErrorIs(t, err, errFlag) - }) - t.Run("updates a deployment with small scheduler size", func(t *testing.T) { - ctx, err := context.GetCurrentContext() - assert.NoError(t, err) - ctx.SetContextKey("organization_product", "HOSTED") - ctx.SetContextKey("organization", "test-org-id") - ctx.SetContextKey("workspace", ws) - - astroCoreClient = mockCoreClient - platformCoreClient = mockPlatformCoreClient - - mockCoreClient.On("GetDeploymentOptionsWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&GetDeploymentOptionsResponseAlphaOK, nil).Times(1) - mockPlatformCoreClient.On("UpdateDeploymentWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&mockUpdateDeploymentResponse, nil).Times(1) - mockPlatformCoreClient.On("ListDeploymentsWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&mockListDeploymentsResponse, nil).Times(1) - mockPlatformCoreClient.On("GetDeploymentWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&hostedDeploymentResponse, nil).Times(1) - - cmdArgs := []string{"update", "test-id-1", "--name", "test-name", "--workspace-id", ws, "--scheduler-size", "small", "--force"} - _, err = execDeploymentCmd(cmdArgs...) - assert.NoError(t, err) - mockPlatformCoreClient.AssertExpectations(t) - mockCoreClient.AssertExpectations(t) - }) - t.Run("returns an error with incorrect high-availability value", func(t *testing.T) { - ctx, err := context.GetCurrentContext() - assert.NoError(t, err) - ctx.SetContextKey("organization_product", "HOSTED") - ctx.SetContextKey("organization", "test-org-id") - ctx.SetContextKey("workspace", ws) - cmdArgs := []string{"update", "test-id", "--name", "test-name", "--workspace-id", ws, "--high-availability", "some-value", "--force"} - _, err = execDeploymentCmd(cmdArgs...) - assert.ErrorContains(t, err, "Invalid --high-availability value") - }) - t.Run("returns an error with incorrect development-mode value", func(t *testing.T) { - ctx, err := context.GetCurrentContext() - assert.NoError(t, err) - ctx.SetContextKey("organization_product", "HOSTED") - ctx.SetContextKey("organization", "test-org-id") - ctx.SetContextKey("workspace", ws) - cmdArgs := []string{"update", "test-id", "--name", "test-name", "--workspace-id", ws, "--development-mode", "some-value", "--force"} - _, err = execDeploymentCmd(cmdArgs...) - assert.ErrorContains(t, err, "Invalid --development-mode value") - }) - t.Run("returns an error if cluster-id is provided with implicit standard deployment", func(t *testing.T) { - ctx, err := context.GetCurrentContext() - assert.NoError(t, err) - ctx.SetContextKey("organization_product", "HOSTED") - ctx.SetContextKey("organization", "test-org-id") - ctx.SetContextKey("workspace", ws) - cmdArgs := []string{"create", "--name", "test-name", "--workspace-id", ws, "--cluster-id", csID} - _, err = execDeploymentCmd(cmdArgs...) - assert.Error(t, err) - assert.ErrorContains(t, err, "flag --cluster-id cannot be used to create a standard deployment") - }) - t.Run("returns an error if cluster-id is provided with explicit standard deployment", func(t *testing.T) { - ctx, err := context.GetCurrentContext() - assert.NoError(t, err) - ctx.SetContextKey("organization_product", "HOSTED") - ctx.SetContextKey("organization", "test-org-id") - ctx.SetContextKey("workspace", ws) - cmdArgs := []string{"create", "--name", "test-name", "--workspace-id", ws, "--cluster-id", csID, "--type", standard} - _, err = execDeploymentCmd(cmdArgs...) - assert.Error(t, err) - assert.ErrorContains(t, err, "flag --cluster-id cannot be used to create a standard deployment") - }) - - t.Run("updates a deployment with extra large scheduler size", func(t *testing.T) { - ctx, err := context.GetCurrentContext() - assert.NoError(t, err) - ctx.SetContextKey("organization_product", "HOSTED") - ctx.SetContextKey("organization", "test-org-id") - ctx.SetContextKey("workspace", ws) - - astroCoreClient = mockCoreClient - platformCoreClient = mockPlatformCoreClient - - mockCoreClient.On("GetDeploymentOptionsWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&GetDeploymentOptionsResponseAlphaOK, nil).Times(1) - mockPlatformCoreClient.On("UpdateDeploymentWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&mockUpdateDeploymentResponse, nil).Times(1) - mockPlatformCoreClient.On("ListDeploymentsWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&mockListDeploymentsResponse, nil).Times(1) - mockPlatformCoreClient.On("GetDeploymentWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&hostedDeploymentResponse, nil).Times(1) - - cmdArgs := []string{"update", "test-id-1", "--name", "test-name", "--workspace-id", ws, "--scheduler-size", "extra_large", "--force"} - _, err = execDeploymentCmd(cmdArgs...) - assert.NoError(t, err) - mockPlatformCoreClient.AssertExpectations(t) - mockCoreClient.AssertExpectations(t) - }) - - t.Run("updates a hosted deployment with workload identity", func(t *testing.T) { - ctx, err := context.GetCurrentContext() - assert.NoError(t, err) - ctx.SetContextKey("organization_product", "HOSTED") - ctx.SetContextKey("organization", "test-org-id") - ctx.SetContextKey("workspace", ws) - - workloadIdentity := "arn:aws:iam::1234567890:role/unit-test-1" - mockUpdateDeploymentResponse.JSON200.WorkloadIdentity = &workloadIdentity - - mockCoreClient.On("GetDeploymentOptionsWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&GetDeploymentOptionsResponseAlphaOK, nil).Once() - mockPlatformCoreClient.On("UpdateDeploymentWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.MatchedBy(func(i astroplatformcore.UpdateDeploymentRequest) bool { - input, _ := i.AsUpdateDedicatedDeploymentRequest() - return input.WorkloadIdentity != nil && *input.WorkloadIdentity == workloadIdentity - })).Return(&mockUpdateDeploymentResponse, nil).Times(1) - mockPlatformCoreClient.On("ListDeploymentsWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&mockListDeploymentsResponse, nil).Times(1) - mockPlatformCoreClient.On("GetDeploymentWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&hostedDeploymentResponse, nil).Times(1) - - astroCoreClient = mockCoreClient - platformCoreClient = mockPlatformCoreClient - cmdArgs := []string{ - "update", "test-id-1", "--name", "test-name", "--workload-identity", workloadIdentity, - } - - _, err = execDeploymentCmd(cmdArgs...) - assert.NoError(t, err) - mockPlatformCoreClient.AssertExpectations(t) - mockCoreClient.AssertExpectations(t) - }) - - t.Run("updates a hosted dedicated deployment with remote execution config", func(t *testing.T) { - ctx, err := context.GetCurrentContext() - assert.NoError(t, err) - ctx.SetContextKey("organization_product", "HOSTED") - ctx.SetContextKey("organization", "test-org-id") - ctx.SetContextKey("workspace", ws) - - taskLogBucket := "test-bucket" - taskLogURLPattern := "test-url-pattern" - allowedIPAddressRanges := []string{"1.2.3.4/32"} - mockUpdateDeploymentResponse.JSON200.RemoteExecution = &astroplatformcore.DeploymentRemoteExecution{ - Enabled: true, - AllowedIpAddressRanges: allowedIPAddressRanges, - TaskLogBucket: &taskLogBucket, - TaskLogUrlPattern: &taskLogURLPattern, - } - - mockCoreClient.On("GetDeploymentOptionsWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&GetDeploymentOptionsResponseAlphaOK, nil).Once() - mockPlatformCoreClient.On("UpdateDeploymentWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.MatchedBy(func(i astroplatformcore.UpdateDeploymentRequest) bool { - input, _ := i.AsUpdateDedicatedDeploymentRequest() - return input.RemoteExecution != nil && input.RemoteExecution.Enabled && - input.RemoteExecution.AllowedIpAddressRanges != nil && (*input.RemoteExecution.AllowedIpAddressRanges)[0] == allowedIPAddressRanges[0] && - input.RemoteExecution.TaskLogBucket != nil && *input.RemoteExecution.TaskLogBucket == taskLogBucket && - input.RemoteExecution.TaskLogUrlPattern != nil && *input.RemoteExecution.TaskLogUrlPattern == taskLogURLPattern - })).Return(&mockUpdateDeploymentResponse, nil).Times(1) - mockPlatformCoreClient.On("ListDeploymentsWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&mockListDeploymentsResponse, nil).Times(1) - mockPlatformCoreClient.On("GetDeploymentWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&hostedDedicatedDeploymentResponse, nil).Times(1) - - astroCoreClient = mockCoreClient - platformCoreClient = mockPlatformCoreClient - cmdArgs := []string{ - "update", "test-id-1", "--name", "test-name", "--allowed-ip-address-ranges", "1.2.3.4/32", "--task-log-bucket", taskLogBucket, "--task-log-url-pattern", taskLogURLPattern, - } - - _, err = execDeploymentCmd(cmdArgs...) - assert.NoError(t, err) - mockPlatformCoreClient.AssertExpectations(t) - mockCoreClient.AssertExpectations(t) - }) -} - -func TestDeploymentDelete(t *testing.T) { - testUtil.InitTestConfig(testUtil.LocalPlatform) - - mockPlatformCoreClient := new(astroplatformcore_mocks.ClientWithResponsesInterface) - - mockDeleteDeploymentResponse := astroplatformcore.DeleteDeploymentResponse{ - HTTPResponse: &http.Response{ - StatusCode: 200, - }, - } - - platformCoreClient = mockPlatformCoreClient - - mockPlatformCoreClient.On("ListDeploymentsWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&mockListDeploymentsResponse, nil).Times(1) - mockPlatformCoreClient.On("GetDeploymentWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&deploymentResponse, nil).Times(1) - mockPlatformCoreClient.On("DeleteDeploymentWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&mockDeleteDeploymentResponse, nil).Times(1) - - cmdArgs := []string{"delete", "test-id-1", "--force"} - _, err := execDeploymentCmd(cmdArgs...) - assert.NoError(t, err) - mockPlatformCoreClient.AssertExpectations(t) -} - func TestDeploymentVariableList(t *testing.T) { testUtil.InitTestConfig(testUtil.LocalPlatform) @@ -1413,157 +594,6 @@ func TestDeploymentVariableUpdate(t *testing.T) { mockCoreClient.AssertExpectations(t) } -func TestDeploymentHibernateAndWakeUp(t *testing.T) { - testUtil.InitTestConfig(testUtil.LocalPlatform) - - tests := []struct { - IsHibernating bool - command string - }{ - {true, "hibernate"}, - {false, "wake-up"}, - } - - for _, tt := range tests { - t.Run(tt.command, func(t *testing.T) { - mockPlatformCoreClient := new(astroplatformcore_mocks.ClientWithResponsesInterface) - platformCoreClient = mockPlatformCoreClient - - isActive := true - mockResponse := astroplatformcore.UpdateDeploymentHibernationOverrideResponse{ - HTTPResponse: &http.Response{ - StatusCode: astrocore.HTTPStatus200, - }, - JSON200: &astroplatformcore.DeploymentHibernationOverride{ - IsHibernating: &tt.IsHibernating, - IsActive: &isActive, - }, - } - - mockPlatformCoreClient.On("ListDeploymentsWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&mockListDeploymentsResponse, nil).Once() - mockPlatformCoreClient.On("GetDeploymentWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&hostedDeploymentResponse, nil).Once() - mockPlatformCoreClient.On("UpdateDeploymentHibernationOverrideWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&mockResponse, nil).Once() - - defer testUtil.MockUserInput(t, "1")() - - cmdArgs := []string{tt.command, "", "--force"} - _, err := execDeploymentCmd(cmdArgs...) - assert.NoError(t, err) - mockPlatformCoreClient.AssertExpectations(t) - }) - - t.Run(fmt.Sprintf("%s with until", tt.command), func(t *testing.T) { - mockPlatformCoreClient := new(astroplatformcore_mocks.ClientWithResponsesInterface) - platformCoreClient = mockPlatformCoreClient - - until := "2022-11-17T13:25:55.275697-08:00" - untilParsed, err := time.Parse(time.RFC3339, until) - assert.NoError(t, err) - isActive := true - mockResponse := astroplatformcore.UpdateDeploymentHibernationOverrideResponse{ - HTTPResponse: &http.Response{ - StatusCode: astrocore.HTTPStatus200, - }, - JSON200: &astroplatformcore.DeploymentHibernationOverride{ - IsHibernating: &tt.IsHibernating, - OverrideUntil: &untilParsed, - IsActive: &isActive, - }, - } - - mockPlatformCoreClient.On("ListDeploymentsWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&mockListDeploymentsResponse, nil).Once() - mockPlatformCoreClient.On("GetDeploymentWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&hostedDeploymentResponse, nil).Once() - mockPlatformCoreClient.On("UpdateDeploymentHibernationOverrideWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&mockResponse, nil).Once() - - cmdArgs := []string{tt.command, "test-id-1", "--until", until, "--force"} - _, err = execDeploymentCmd(cmdArgs...) - assert.NoError(t, err) - mockPlatformCoreClient.AssertExpectations(t) - }) - - t.Run(fmt.Sprintf("%s with until returns an error if invalid", tt.command), func(t *testing.T) { - until := "invalid-duration" - - cmdArgs := []string{tt.command, "test-id-1", "--until", until, "--force"} - _, err := execDeploymentCmd(cmdArgs...) - assert.Error(t, err) - }) - - t.Run(fmt.Sprintf("%s with for", tt.command), func(t *testing.T) { - mockPlatformCoreClient := new(astroplatformcore_mocks.ClientWithResponsesInterface) - platformCoreClient = mockPlatformCoreClient - - forDuration := "1h" - forDurationParsed, err := time.ParseDuration(forDuration) - overrideUntil := time.Now().Add(forDurationParsed) - assert.NoError(t, err) - isActive := true - mockResponse := astroplatformcore.UpdateDeploymentHibernationOverrideResponse{ - HTTPResponse: &http.Response{ - StatusCode: astrocore.HTTPStatus200, - }, - JSON200: &astroplatformcore.DeploymentHibernationOverride{ - IsHibernating: &tt.IsHibernating, - OverrideUntil: &overrideUntil, - IsActive: &isActive, - }, - } - - mockPlatformCoreClient.On("ListDeploymentsWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&mockListDeploymentsResponse, nil).Once() - mockPlatformCoreClient.On("GetDeploymentWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&hostedDeploymentResponse, nil).Once() - mockPlatformCoreClient.On("UpdateDeploymentHibernationOverrideWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&mockResponse, nil).Once() - - cmdArgs := []string{tt.command, "test-id-1", "--for", forDuration, "--force"} - _, err = execDeploymentCmd(cmdArgs...) - assert.NoError(t, err) - mockPlatformCoreClient.AssertExpectations(t) - }) - - t.Run(fmt.Sprintf("%s with for returns an error if invalid", tt.command), func(t *testing.T) { - forDuration := "invalid-duration" - - cmdArgs := []string{tt.command, "test-id-1", "--for", forDuration, "--force"} - _, err := execDeploymentCmd(cmdArgs...) - assert.Error(t, err) - }) - - t.Run(fmt.Sprintf("%s with remove override", tt.command), func(t *testing.T) { - mockPlatformCoreClient := new(astroplatformcore_mocks.ClientWithResponsesInterface) - platformCoreClient = mockPlatformCoreClient - - mockResponse := astroplatformcore.DeleteDeploymentHibernationOverrideResponse{ - HTTPResponse: &http.Response{ - StatusCode: astrocore.HTTPStatus204, - }, - } - - mockPlatformCoreClient.On("ListDeploymentsWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&mockListDeploymentsResponse, nil).Once() - mockPlatformCoreClient.On("GetDeploymentWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&hostedDeploymentResponse, nil).Once() - mockPlatformCoreClient.On("DeleteDeploymentHibernationOverrideWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&mockResponse, nil).Once() - - cmdArgs := []string{tt.command, "test-id-1", "--remove-override", "--force"} - _, err := execDeploymentCmd(cmdArgs...) - assert.NoError(t, err) - mockPlatformCoreClient.AssertExpectations(t) - }) - - t.Run(fmt.Sprintf("%s returns an error when getting workspace fails", tt.command), func(t *testing.T) { - testUtil.InitTestConfig(testUtil.LocalPlatform) - ctx, err := config.GetCurrentContext() - assert.NoError(t, err) - ctx.Workspace = "" - err = ctx.SetContext() - assert.NoError(t, err) - defer testUtil.InitTestConfig(testUtil.LocalPlatform) - expectedOut := "Usage:\n" - cmdArgs := []string{tt.command, "-n", "doesnotexist"} - resp, err := execDeploymentCmd(cmdArgs...) - assert.ErrorContains(t, err, "failed to find a valid workspace") - assert.Contains(t, resp, expectedOut) - }) - } -} - func TestIsValidExecutor(t *testing.T) { af3OnlyValidExecutors := []string{"astro", "astroexecutor", "ASTRO", deployment.AstroExecutor, deployment.ASTRO} af2ValidExecutors := []string{"celery", "celeryexecutor", "kubernetes", "kubernetesexecutor", "CELERY", "KUBERNETES", deployment.CeleryExecutor, deployment.KubeExecutor, deployment.CELERY, deployment.KUBERNETES} @@ -1584,7 +614,6 @@ func TestIsValidExecutor(t *testing.T) { assert.False(t, actual) }) - // Airflow 3 introduces AstroExecutor as a valid executor af3ValidExecutors := append(af3OnlyValidExecutors, af2ValidExecutors...) //nolint:gocritic for _, executor := range af3ValidExecutors { t.Run(fmt.Sprintf("returns true if executor is %s isAirflow3=true", executor), func(t *testing.T) { @@ -1593,7 +622,6 @@ func TestIsValidExecutor(t *testing.T) { }) } - // astro exec not allowed on hybrid for _, executor := range af3OnlyValidExecutors { t.Run(fmt.Sprintf("returns false if executor is %s isAirflow3=true for hybrid", executor), func(t *testing.T) { actual := deployment.IsValidExecutor(executor, "3.0-1", "hybrid") @@ -1606,1423 +634,3 @@ func TestIsValidExecutor(t *testing.T) { assert.False(t, actual) }) } - -func TestIsValidCloudProvider(t *testing.T) { - t.Run("returns true if cloudProvider is gcp", func(t *testing.T) { - actual := isValidCloudProvider("gcp") - assert.True(t, actual) - }) - t.Run("returns true if cloudProvider is aws", func(t *testing.T) { - actual := isValidCloudProvider("aws") - assert.True(t, actual) - }) - t.Run("returns true if cloudProvider is azure", func(t *testing.T) { - actual := isValidCloudProvider("azure") - assert.True(t, actual) - }) - t.Run("returns false if cloudProvider is not gcp,aws or azure", func(t *testing.T) { - actual := isValidCloudProvider("ibm") - assert.False(t, actual) - }) -} - -var ( - tokenValue = "token" - mockDeploymentID = "ck05r3bor07h40d02y2hw4n4v" - deploymentRole = "DEPLOYMENT_ADMIN" - deploymentUser1 = astrocore.User{ - CreatedAt: time.Now(), - FullName: "user 1", - Id: "user1-id", - DeploymentRole: &deploymentRole, - Username: "user@1.com", - } - deploymentUsers = []astrocore.User{ - deploymentUser1, - } - - deploymentTeams = []astrocore.Team{ - team1, - } - ListDeploymentUsersResponseOK = astrocore.ListDeploymentUsersResponse{ - HTTPResponse: &http.Response{ - StatusCode: 200, - }, - JSON200: &astrocore.UsersPaginated{ - Limit: 1, - Offset: 0, - TotalCount: 1, - Users: deploymentUsers, - }, - } - ListDeploymentUsersResponseError = astrocore.ListDeploymentUsersResponse{ - HTTPResponse: &http.Response{ - StatusCode: 500, - }, - Body: errorBodyList, - JSON200: nil, - } - MutateDeploymentUserRoleResponseOK = astrocore.MutateDeploymentUserRoleResponse{ - HTTPResponse: &http.Response{ - StatusCode: 200, - }, - JSON200: &astrocore.UserRole{ - Role: "DEPLOYMENT_ADMIN", - }, - } - MutateDeploymentUserRoleResponseError = astrocore.MutateDeploymentUserRoleResponse{ - HTTPResponse: &http.Response{ - StatusCode: 500, - }, - Body: errorBodyUpdate, - JSON200: nil, - } - DeleteDeploymentUserResponseOK = astrocore.DeleteDeploymentUserResponse{ - HTTPResponse: &http.Response{ - StatusCode: 200, - }, - } - DeleteDeploymentUserResponseError = astrocore.DeleteDeploymentUserResponse{ - HTTPResponse: &http.Response{ - StatusCode: 500, - }, - Body: errorBodyUpdate, - } - ListDeploymentTeamsResponseOK = astrocore.ListDeploymentTeamsResponse{ - HTTPResponse: &http.Response{ - StatusCode: 200, - }, - JSON200: &astrocore.TeamsPaginated{ - Limit: 1, - Offset: 0, - TotalCount: 1, - Teams: deploymentTeams, - }, - } - ListDeploymentTeamsResponseError = astrocore.ListDeploymentTeamsResponse{ - HTTPResponse: &http.Response{ - StatusCode: 500, - }, - Body: teamRequestErrorBodyList, - JSON200: nil, - } - MutateDeploymentTeamRoleResponseOK = astrocore.MutateDeploymentTeamRoleResponse{ - HTTPResponse: &http.Response{ - StatusCode: 200, - }, - JSON200: &astrocore.TeamRole{ - Role: "DEPLOYMENT_ADMIN", - }, - } - MutateDeploymentTeamRoleResponseError = astrocore.MutateDeploymentTeamRoleResponse{ - HTTPResponse: &http.Response{ - StatusCode: 500, - }, - Body: teamRequestErrorBodyUpdate, - JSON200: nil, - } - DeleteDeploymentTeamResponseOK = astrocore.DeleteDeploymentTeamResponse{ - HTTPResponse: &http.Response{ - StatusCode: 200, - }, - } - DeleteDeploymentTeamResponseError = astrocore.DeleteDeploymentTeamResponse{ - HTTPResponse: &http.Response{ - StatusCode: 500, - }, - Body: teamRequestErrorDelete, - } - - tokenDeploymentRole = astrocore.ApiTokenRole{ - EntityType: "DEPLOYMENT", - EntityId: deploymentID, - Role: "DEPLOYMENT_ADMIN", - } - - tokenDeploymentRole2 = astrocore.ApiTokenRole{ - EntityType: "DEPLOYMENT", - EntityId: deploymentID, - Role: "custom role", - } - - deploymentAPIToken1 = astrocore.ApiToken{Id: "token1", Name: tokenName1, Token: &token, Description: description1, Type: "Type 1", Roles: []astrocore.ApiTokenRole{tokenDeploymentRole}, CreatedAt: time.Now(), CreatedBy: &astrocore.BasicSubjectProfile{FullName: &fullName1}} - deploymentAPITokens = []astrocore.ApiToken{ - apiToken1, - {Id: "token2", Name: "Token 2", Description: description2, Type: "Type 2", Roles: []astrocore.ApiTokenRole{tokenDeploymentRole2}, CreatedAt: time.Now(), CreatedBy: &astrocore.BasicSubjectProfile{FullName: &fullName2}}, - } - - ListDeploymentAPITokensResponseOK = astrocore.ListDeploymentApiTokensResponse{ - HTTPResponse: &http.Response{ - StatusCode: 200, - }, - JSON200: &astrocore.ListApiTokensPaginated{ - Limit: 1, - Offset: 0, - TotalCount: 1, - ApiTokens: deploymentAPITokens, - }, - } - apiTokenRequestErrorBodyList, _ = json.Marshal(astrocore.Error{ - Message: "failed to list api tokens", - }) - ListDeploymentAPITokensResponseError = astrocore.ListDeploymentApiTokensResponse{ - HTTPResponse: &http.Response{ - StatusCode: 500, - }, - Body: apiTokenRequestErrorBodyList, - JSON200: nil, - } - CreateDeploymentAPITokenRoleResponseOK = astrocore.CreateDeploymentApiTokenResponse{ - HTTPResponse: &http.Response{ - StatusCode: 200, - }, - JSON200: &deploymentAPIToken1, - } - apiTokenRequestErrorBodyCreate, _ = json.Marshal(astrocore.Error{ - Message: "failed to create api token", - }) - CreateDeploymentAPITokenRoleResponseError = astrocore.CreateDeploymentApiTokenResponse{ - HTTPResponse: &http.Response{ - StatusCode: 500, - }, - Body: apiTokenRequestErrorBodyCreate, - JSON200: nil, - } - MutateDeploymentAPITokenRoleResponseOK = astrocore.UpdateDeploymentApiTokenResponse{ - HTTPResponse: &http.Response{ - StatusCode: 200, - }, - JSON200: &deploymentAPIToken1, - } - apiTokenRequestErrorBodyUpdate, _ = json.Marshal(astrocore.Error{ - Message: "failed to update api token", - }) - MutateDeploymentAPITokenRoleResponseError = astrocore.UpdateDeploymentApiTokenResponse{ - HTTPResponse: &http.Response{ - StatusCode: 500, - }, - Body: apiTokenRequestErrorBodyUpdate, - JSON200: nil, - } - DeleteDeploymentAPITokenResponseOK = astrocore.DeleteDeploymentApiTokenResponse{ - HTTPResponse: &http.Response{ - StatusCode: 200, - }, - } - - apiTokenRequestErrorDelete, _ = json.Marshal(astrocore.Error{ - Message: "failed to delete api token", - }) - DeleteDeploymentAPITokenResponseError = astrocore.DeleteDeploymentApiTokenResponse{ - HTTPResponse: &http.Response{ - StatusCode: 500, - }, - Body: apiTokenRequestErrorDelete, - } - - RotateDeploymentAPITokenResponseOK = astrocore.RotateDeploymentApiTokenResponse{ - HTTPResponse: &http.Response{ - StatusCode: 200, - }, - JSON200: &deploymentAPIToken1, - } - - apiTokenRequestErrorRotate, _ = json.Marshal(astrocore.Error{ - Message: "failed to rotate api token", - }) - RotateDeploymentAPITokenResponseError = astrocore.RotateDeploymentApiTokenResponse{ - HTTPResponse: &http.Response{ - StatusCode: 500, - }, - Body: apiTokenRequestErrorRotate, - } - GetDeploymentAPITokenWithResponseOK = astrocore.GetDeploymentApiTokenResponse{ - HTTPResponse: &http.Response{ - StatusCode: 200, - }, - JSON200: &deploymentAPIToken1, - } -) - -func TestDeploymentUserList(t *testing.T) { - testUtil.InitTestConfig(testUtil.LocalPlatform) - - t.Run("-h prints list help", func(t *testing.T) { - cmdArgs := []string{"user", "list", "-h"} - _, err := execDeploymentCmd(cmdArgs...) - assert.NoError(t, err) - }) - t.Run("will error if deployment id flag is not provided", func(t *testing.T) { - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - astroCoreClient = mockClient - cmdArgs := []string{"user", "list"} - _, err := execDeploymentCmd(cmdArgs...) - assert.EqualError(t, err, "flag --deployment-id is required") - }) - t.Run("any errors from api are returned and users are not listed", func(t *testing.T) { - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListDeploymentUsersWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&ListDeploymentUsersResponseError, nil).Once() - astroCoreClient = mockClient - cmdArgs := []string{"user", "list", "--deployment-id", mockDeploymentID} - _, err := execDeploymentCmd(cmdArgs...) - assert.EqualError(t, err, "failed to list users") - }) - t.Run("any context errors from api are returned and users are not listed", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.Initial) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListDeploymentUsersWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&ListDeploymentUsersResponseError, nil).Once() - astroCoreClient = mockClient - cmdArgs := []string{"user", "list", "--deployment-id", mockDeploymentID} - _, err := execDeploymentCmd(cmdArgs...) - assert.Error(t, err) - }) -} - -func TestDeploymentUserUpdate(t *testing.T) { - testUtil.InitTestConfig(testUtil.LocalPlatform) - - t.Run("-h prints update help", func(t *testing.T) { - cmdArgs := []string{"user", "update", "-h"} - _, err := execDeploymentCmd(cmdArgs...) - assert.NoError(t, err) - }) - t.Run("will error if deployment id flag is not provided", func(t *testing.T) { - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - astroCoreClient = mockClient - cmdArgs := []string{"user", "update", "user@1.com", "--role", "DEPLOYMENT_ADMIN"} - _, err := execDeploymentCmd(cmdArgs...) - assert.EqualError(t, err, "flag --deployment-id is required") - }) - t.Run("valid email with valid role updates user", func(t *testing.T) { - expectedOut := "The deployment user user@1.com role was successfully updated to DEPLOYMENT_ADMIN" - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListDeploymentUsersWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&ListDeploymentUsersResponseOK, nil).Twice() - mockClient.On("MutateDeploymentUserRoleWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&MutateDeploymentUserRoleResponseOK, nil).Once() - astroCoreClient = mockClient - cmdArgs := []string{"user", "update", "user@1.com", "--role", "DEPLOYMENT_ADMIN", "--deployment-id", mockDeploymentID} - resp, err := execDeploymentCmd(cmdArgs...) - assert.NoError(t, err) - assert.Contains(t, resp, expectedOut) - }) - - t.Run("any errors from api are returned and role is not updated", func(t *testing.T) { - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListDeploymentUsersWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&ListDeploymentUsersResponseOK, nil).Twice() - mockClient.On("MutateDeploymentUserRoleWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&MutateDeploymentUserRoleResponseError, nil).Once() - astroCoreClient = mockClient - cmdArgs := []string{"user", "update", "user@1.com", "--role", "DEPLOYMENT_ADMIN", "--deployment-id", mockDeploymentID} - _, err := execDeploymentCmd(cmdArgs...) - assert.EqualError(t, err, "failed to update user") - }) - - t.Run("any context errors from api are returned and role is not updated", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.Initial) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListDeploymentUsersWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&ListDeploymentUsersResponseOK, nil).Twice() - mockClient.On("MutateDeploymentUserRoleWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&MutateDeploymentUserRoleResponseOK, nil).Once() - astroCoreClient = mockClient - cmdArgs := []string{"user", "update", "user@1.com", "--role", "DEPLOYMENT_ADMIN", "--deployment-id", mockDeploymentID} - _, err := execDeploymentCmd(cmdArgs...) - assert.Error(t, err) - }) - t.Run("command asks for input when no email is passed in as an arg", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.LocalPlatform) - - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListDeploymentUsersWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&ListDeploymentUsersResponseOK, nil).Twice() - // mock os.Stdin - expectedInput := []byte("1") - r, w, err := os.Pipe() - assert.NoError(t, err) - _, err = w.Write(expectedInput) - assert.NoError(t, err) - w.Close() - stdin := os.Stdin - // Restore stdin right after the test. - defer func() { os.Stdin = stdin }() - os.Stdin = r - - expectedOut := "The deployment user user@1.com role was successfully updated to DEPLOYMENT_ADMIN" - mockClient.On("MutateDeploymentUserRoleWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&MutateDeploymentUserRoleResponseOK, nil).Once() - - cmdArgs := []string{"user", "update", "--role", "DEPLOYMENT_ADMIN", "--deployment-id", mockDeploymentID} - resp, err := execDeploymentCmd(cmdArgs...) - assert.NoError(t, err) - assert.Contains(t, resp, expectedOut) - }) -} - -func TestDeploymentUserAdd(t *testing.T) { - testUtil.InitTestConfig(testUtil.LocalPlatform) - - t.Run("-h prints add help", func(t *testing.T) { - cmdArgs := []string{"user", "add", "-h"} - _, err := execDeploymentCmd(cmdArgs...) - assert.NoError(t, err) - }) - t.Run("will error if deployment id flag is not provided", func(t *testing.T) { - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - astroCoreClient = mockClient - cmdArgs := []string{"user", "add", "user@1.com", "--role", "DEPLOYMENT_ADMIN"} - _, err := execDeploymentCmd(cmdArgs...) - assert.EqualError(t, err, "flag --deployment-id is required") - }) - t.Run("valid email with valid role adds user", func(t *testing.T) { - expectedOut := "The user user@1.com was successfully added to the deployment with the role DEPLOYMENT_ADMIN\n" - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListOrgUsersWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&ListOrgUsersResponseOK, nil).Twice() - mockClient.On("MutateDeploymentUserRoleWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&MutateDeploymentUserRoleResponseOK, nil).Once() - astroCoreClient = mockClient - cmdArgs := []string{"user", "add", "user@1.com", "--role", "DEPLOYMENT_ADMIN", "--deployment-id", mockDeploymentID} - resp, err := execDeploymentCmd(cmdArgs...) - assert.NoError(t, err) - assert.Contains(t, resp, expectedOut) - }) - t.Run("any errors from api are returned and user is not added", func(t *testing.T) { - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListOrgUsersWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&ListOrgUsersResponseOK, nil).Twice() - mockClient.On("MutateDeploymentUserRoleWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&MutateDeploymentUserRoleResponseError, nil).Once() - astroCoreClient = mockClient - cmdArgs := []string{"user", "add", "user@1.com", "--role", "DEPLOYMENT_ADMIN", "--deployment-id", mockDeploymentID} - _, err := execDeploymentCmd(cmdArgs...) - assert.EqualError(t, err, "failed to update user", "--deployment-id", mockDeploymentID) - }) - - t.Run("any context errors from api are returned and role is not added", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.Initial) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListOrgUsersWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&ListOrgUsersResponseOK, nil).Twice() - mockClient.On("MutateDeploymentUserRoleWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&MutateDeploymentUserRoleResponseOK, nil).Once() - astroCoreClient = mockClient - cmdArgs := []string{"user", "add", "user@1.com", "--role", "DEPLOYMENT_ADMIN", "--deployment-id", mockDeploymentID} - _, err := execDeploymentCmd(cmdArgs...) - assert.Error(t, err) - }) - t.Run("command asks for input when no email is passed in as an arg", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.LocalPlatform) - - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListOrgUsersWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&ListOrgUsersResponseOK, nil).Twice() - // mock os.Stdin - expectedInput := []byte("1") - r, w, err := os.Pipe() - assert.NoError(t, err) - _, err = w.Write(expectedInput) - assert.NoError(t, err) - w.Close() - stdin := os.Stdin - // Restore stdin right after the test. - defer func() { os.Stdin = stdin }() - os.Stdin = r - - expectedOut := "The user user@1.com was successfully added to the deployment with the role DEPLOYMENT_ADMIN\n" - mockClient.On("MutateDeploymentUserRoleWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&MutateDeploymentUserRoleResponseOK, nil).Once() - - cmdArgs := []string{"user", "add", "--role", "DEPLOYMENT_ADMIN", "--deployment-id", mockDeploymentID} - resp, err := execDeploymentCmd(cmdArgs...) - assert.NoError(t, err) - assert.Contains(t, resp, expectedOut) - }) -} - -func TestDeploymentUserRemove(t *testing.T) { - expectedHelp := "Remove a user from an Astro Deployment" - testUtil.InitTestConfig(testUtil.LocalPlatform) - - t.Run("-h prints remove help", func(t *testing.T) { - cmdArgs := []string{"user", "remove", "-h"} - resp, err := execDeploymentCmd(cmdArgs...) - assert.NoError(t, err) - assert.Contains(t, resp, expectedHelp) - }) - t.Run("will error if deployment id flag is not provided", func(t *testing.T) { - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - astroCoreClient = mockClient - cmdArgs := []string{"user", "remove", "user@1.com"} - _, err := execDeploymentCmd(cmdArgs...) - assert.EqualError(t, err, "flag --deployment-id is required") - }) - t.Run("valid email removes user", func(t *testing.T) { - expectedOut := "The user user@1.com was successfully removed from the deployment" - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListDeploymentUsersWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&ListDeploymentUsersResponseOK, nil).Twice() - mockClient.On("DeleteDeploymentUserWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&DeleteDeploymentUserResponseOK, nil).Once() - astroCoreClient = mockClient - cmdArgs := []string{"user", "remove", "user@1.com", "--deployment-id", mockDeploymentID} - resp, err := execDeploymentCmd(cmdArgs...) - assert.NoError(t, err) - assert.Contains(t, resp, expectedOut) - }) - t.Run("any errors from api are returned and user is not removed", func(t *testing.T) { - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListDeploymentUsersWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&ListDeploymentUsersResponseOK, nil).Twice() - mockClient.On("DeleteDeploymentUserWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&DeleteDeploymentUserResponseError, nil).Once() - astroCoreClient = mockClient - cmdArgs := []string{"user", "remove", "user@1.com", "--deployment-id", mockDeploymentID} - _, err := execDeploymentCmd(cmdArgs...) - assert.EqualError(t, err, "failed to update user") - }) - t.Run("any context errors from api are returned and the user is not removed", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.Initial) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListDeploymentUsersWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&ListDeploymentUsersResponseOK, nil).Twice() - mockClient.On("DeleteDeploymentUserWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&DeleteDeploymentUserResponseOK, nil).Once() - astroCoreClient = mockClient - cmdArgs := []string{"user", "remove", "user@1.com", "--deployment-id", mockDeploymentID} - _, err := execDeploymentCmd(cmdArgs...) - assert.Error(t, err) - }) - t.Run("command asks for input when no email is passed in as an arg", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.LocalPlatform) - - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListDeploymentUsersWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&ListDeploymentUsersResponseOK, nil).Twice() - // mock os.Stdin - expectedInput := []byte("1") - r, w, err := os.Pipe() - assert.NoError(t, err) - _, err = w.Write(expectedInput) - assert.NoError(t, err) - w.Close() - stdin := os.Stdin - // Restore stdin right after the test. - defer func() { os.Stdin = stdin }() - os.Stdin = r - - expectedOut := "The user user@1.com was successfully removed from the deployment" - mockClient.On("DeleteDeploymentUserWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&DeleteDeploymentUserResponseOK, nil).Once() - - cmdArgs := []string{"user", "remove", "--deployment-id", mockDeploymentID} - resp, err := execDeploymentCmd(cmdArgs...) - assert.NoError(t, err) - assert.Contains(t, resp, expectedOut) - }) -} - -func TestDeploymentTeamList(t *testing.T) { - expectedHelp := "List all the teams in an Astro Deployment" - testUtil.InitTestConfig(testUtil.LocalPlatform) - - t.Run("-h prints list help", func(t *testing.T) { - cmdArgs := []string{"team", "list", "-h"} - resp, err := execDeploymentCmd(cmdArgs...) - assert.NoError(t, err) - assert.Contains(t, resp, expectedHelp) - }) - t.Run("will error if deployment id flag is not provided", func(t *testing.T) { - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - astroCoreClient = mockClient - cmdArgs := []string{"team", "list"} - _, err := execDeploymentCmd(cmdArgs...) - assert.EqualError(t, err, "flag --deployment-id is required") - }) - t.Run("any errors from api are returned and teams are not listed", func(t *testing.T) { - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListDeploymentTeamsWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&ListDeploymentTeamsResponseError, nil).Once() - astroCoreClient = mockClient - cmdArgs := []string{"team", "list", "--deployment-id", mockDeploymentID} - _, err := execDeploymentCmd(cmdArgs...) - assert.EqualError(t, err, "failed to list teams") - }) - t.Run("any context errors from api are returned and teams are not listed", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.Initial) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListDeploymentTeamsWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&ListDeploymentTeamsResponseError, nil).Once() - astroCoreClient = mockClient - cmdArgs := []string{"team", "list", "--deployment-id", mockDeploymentID} - _, err := execDeploymentCmd(cmdArgs...) - assert.Error(t, err) - }) -} - -func TestDeploymentTeamUpdate(t *testing.T) { - testUtil.InitTestConfig(testUtil.LocalPlatform) - - t.Run("-h prints update help", func(t *testing.T) { - cmdArgs := []string{"team", "update", "-h"} - _, err := execDeploymentCmd(cmdArgs...) - assert.NoError(t, err) - }) - t.Run("will error if deployment id flag is not provided", func(t *testing.T) { - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - astroCoreClient = mockClient - cmdArgs := []string{"team", "update", team1.Id, "--role", "DEPLOYMENT_ADMIN"} - _, err := execDeploymentCmd(cmdArgs...) - assert.EqualError(t, err, "flag --deployment-id is required") - }) - t.Run("valid id with valid role updates team", func(t *testing.T) { - expectedOut := fmt.Sprintf("The deployment team %s role was successfully updated to DEPLOYMENT_ADMIN", team1.Id) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("GetTeamWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&GetTeamWithResponseOK, nil).Twice() - mockClient.On("MutateDeploymentTeamRoleWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&MutateDeploymentTeamRoleResponseOK, nil).Once() - astroCoreClient = mockClient - cmdArgs := []string{"team", "update", team1.Id, "--role", "DEPLOYMENT_ADMIN", "--deployment-id", mockDeploymentID} - resp, err := execDeploymentCmd(cmdArgs...) - assert.NoError(t, err) - assert.Contains(t, resp, expectedOut) - }) - t.Run("any errors from api are returned and role is not updated", func(t *testing.T) { - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("GetTeamWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&GetTeamWithResponseOK, nil).Twice() - mockClient.On("MutateDeploymentTeamRoleWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&MutateDeploymentTeamRoleResponseError, nil).Once() - astroCoreClient = mockClient - cmdArgs := []string{"team", "update", team1.Id, "--role", "DEPLOYMENT_ADMIN", "--deployment-id", mockDeploymentID} - _, err := execDeploymentCmd(cmdArgs...) - assert.EqualError(t, err, "failed to update team") - }) - - t.Run("any context errors from api are returned and role is not updated", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.Initial) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("GetTeamWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&GetTeamWithResponseOK, nil).Twice() - mockClient.On("MutateDeploymentTeamRoleWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&MutateDeploymentTeamRoleResponseOK, nil).Once() - astroCoreClient = mockClient - cmdArgs := []string{"team", "update", team1.Id, "--role", "DEPLOYMENT_ADMIN", "--deployment-id", mockDeploymentID} - _, err := execDeploymentCmd(cmdArgs...) - assert.Error(t, err) - }) - - t.Run("command asks for input when no team id is passed in as an arg", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.LocalPlatform) - - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListDeploymentTeamsWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&ListDeploymentTeamsResponseOK, nil).Twice() - // mock os.Stdin - expectedInput := []byte("1") - r, w, err := os.Pipe() - assert.NoError(t, err) - _, err = w.Write(expectedInput) - assert.NoError(t, err) - w.Close() - stdin := os.Stdin - // Restore stdin right after the test. - defer func() { os.Stdin = stdin }() - os.Stdin = r - - expectedOut := fmt.Sprintf("The deployment team %s role was successfully updated to DEPLOYMENT_ADMIN", team1.Id) - mockClient.On("MutateDeploymentTeamRoleWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&MutateDeploymentTeamRoleResponseOK, nil).Once() - astroCoreClient = mockClient - - cmdArgs := []string{"team", "update", "--role", "DEPLOYMENT_ADMIN", "--deployment-id", mockDeploymentID} - resp, err := execDeploymentCmd(cmdArgs...) - assert.NoError(t, err) - assert.Contains(t, resp, expectedOut) - }) -} - -func TestDeploymentTeamAdd(t *testing.T) { - testUtil.InitTestConfig(testUtil.LocalPlatform) - - t.Run("-h prints add help", func(t *testing.T) { - cmdArgs := []string{"team", "add", "-h"} - _, err := execDeploymentCmd(cmdArgs...) - assert.NoError(t, err) - }) - t.Run("valid id with valid role adds team", func(t *testing.T) { - expectedOut := fmt.Sprintf("The team %s was successfully added to the deployment with the role DEPLOYMENT_ADMIN\n", team1.Id) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("GetTeamWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&GetTeamWithResponseOK, nil).Twice() - mockClient.On("MutateDeploymentTeamRoleWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&MutateDeploymentTeamRoleResponseOK, nil).Once() - astroCoreClient = mockClient - cmdArgs := []string{"team", "add", team1.Id, "--role", "DEPLOYMENT_ADMIN", "--deployment-id", mockDeploymentID} - resp, err := execDeploymentCmd(cmdArgs...) - assert.NoError(t, err) - assert.Contains(t, resp, expectedOut) - }) - - t.Run("will error if deployment id flag is not provided", func(t *testing.T) { - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - astroCoreClient = mockClient - cmdArgs := []string{"team", "add", team1.Id} - _, err := execDeploymentCmd(cmdArgs...) - assert.EqualError(t, err, "flag --deployment-id is required") - }) - t.Run("any errors from api are returned and team is not added", func(t *testing.T) { - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("GetTeamWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&GetTeamWithResponseOK, nil).Twice() - mockClient.On("MutateDeploymentTeamRoleWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&MutateDeploymentTeamRoleResponseError, nil).Once() - astroCoreClient = mockClient - cmdArgs := []string{"team", "add", team1.Id, "--role", "DEPLOYMENT_ADMIN", "--deployment-id", mockDeploymentID} - _, err := execDeploymentCmd(cmdArgs...) - assert.EqualError(t, err, "failed to update team") - }) - - t.Run("any context errors from api are returned and role is not added", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.Initial) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("GetTeamWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&GetTeamWithResponseOK, nil).Twice() - mockClient.On("MutateDeploymentTeamRoleWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&MutateDeploymentTeamRoleResponseOK, nil).Once() - astroCoreClient = mockClient - cmdArgs := []string{"team", "add", team1.Id, "--role", "DEPLOYMENT_ADMIN", "--deployment-id", mockDeploymentID} - _, err := execDeploymentCmd(cmdArgs...) - assert.Error(t, err) - }) - - t.Run("command asks for input when no team id is passed in as an arg", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.LocalPlatform) - - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListOrganizationTeamsWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&ListOrgTeamsResponseOK, nil).Twice() - // mock os.Stdin - expectedInput := []byte("1") - r, w, err := os.Pipe() - assert.NoError(t, err) - _, err = w.Write(expectedInput) - assert.NoError(t, err) - w.Close() - stdin := os.Stdin - // Restore stdin right after the test. - defer func() { os.Stdin = stdin }() - os.Stdin = r - - expectedOut := fmt.Sprintf("The team %s was successfully added to the deployment with the role DEPLOYMENT_ADMIN\n", team1.Id) - mockClient.On("MutateDeploymentTeamRoleWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&MutateDeploymentTeamRoleResponseOK, nil).Once() - astroCoreClient = mockClient - - cmdArgs := []string{"team", "add", "--role", "DEPLOYMENT_ADMIN", "--deployment-id", mockDeploymentID} - resp, err := execDeploymentCmd(cmdArgs...) - assert.NoError(t, err) - assert.Contains(t, resp, expectedOut) - }) -} - -func TestDeploymentTeamRemove(t *testing.T) { - expectedHelp := "Remove a team from an Astro Deployment" - testUtil.InitTestConfig(testUtil.LocalPlatform) - - t.Run("-h prints remove help", func(t *testing.T) { - cmdArgs := []string{"team", "remove", "-h"} - resp, err := execDeploymentCmd(cmdArgs...) - assert.NoError(t, err) - assert.Contains(t, resp, expectedHelp) - }) - t.Run("will error if deployment id flag is not provided", func(t *testing.T) { - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - astroCoreClient = mockClient - cmdArgs := []string{"team", "remove", team1.Id} - _, err := execDeploymentCmd(cmdArgs...) - assert.EqualError(t, err, "flag --deployment-id is required") - }) - t.Run("valid id removes team", func(t *testing.T) { - expectedOut := fmt.Sprintf("Astro Team %s was successfully removed from deployment %s\n", team1.Name, mockDeploymentID) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("GetTeamWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&GetTeamWithResponseOK, nil).Twice() - mockClient.On("DeleteDeploymentTeamWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&DeleteDeploymentTeamResponseOK, nil).Once() - astroCoreClient = mockClient - cmdArgs := []string{"team", "remove", team1.Id, "--deployment-id", mockDeploymentID} - resp, err := execDeploymentCmd(cmdArgs...) - assert.NoError(t, err) - assert.Contains(t, resp, expectedOut) - }) - t.Run("any errors from api are returned and team is not removed", func(t *testing.T) { - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("GetTeamWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&GetTeamWithResponseOK, nil).Twice() - mockClient.On("DeleteDeploymentTeamWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&DeleteDeploymentTeamResponseError, nil).Once() - astroCoreClient = mockClient - cmdArgs := []string{"team", "remove", team1.Id, "--deployment-id", mockDeploymentID} - _, err := execDeploymentCmd(cmdArgs...) - assert.EqualError(t, err, "failed to delete team") - }) - t.Run("any context errors from api are returned and the team is not removed", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.Initial) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("GetTeamWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&GetTeamWithResponseOK, nil).Twice() - mockClient.On("DeleteDeploymentTeamWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&DeleteDeploymentTeamResponseOK, nil).Once() - astroCoreClient = mockClient - cmdArgs := []string{"team", "remove", team1.Id, "--deployment-id", mockDeploymentID} - _, err := execDeploymentCmd(cmdArgs...) - assert.Error(t, err) - }) - t.Run("command asks for input when no id is passed in as an arg", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.LocalPlatform) - - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListDeploymentTeamsWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&ListDeploymentTeamsResponseOK, nil).Twice() - // mock os.Stdin - expectedInput := []byte("1") - r, w, err := os.Pipe() - assert.NoError(t, err) - _, err = w.Write(expectedInput) - assert.NoError(t, err) - w.Close() - stdin := os.Stdin - // Restore stdin right after the test. - defer func() { os.Stdin = stdin }() - os.Stdin = r - - expectedOut := fmt.Sprintf("Astro Team %s was successfully removed from deployment %s", team1.Name, mockDeploymentID) - mockClient.On("DeleteDeploymentTeamWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&DeleteDeploymentTeamResponseOK, nil).Once() - astroCoreClient = mockClient - - cmdArgs := []string{"team", "remove", "--deployment-id", mockDeploymentID} - resp, err := execDeploymentCmd(cmdArgs...) - assert.NoError(t, err) - assert.Contains(t, resp, expectedOut) - }) -} - -func TestDeploymentTokenRootCommand(t *testing.T) { - testUtil.InitTestConfig(testUtil.LocalPlatform) - buf := new(bytes.Buffer) - cmd := newDeploymentRootCmd(os.Stdout) - cmd.SetOut(buf) - cmdArgs := []string{"token", "-h"} - _, err := execDeploymentCmd(cmdArgs...) - assert.NoError(t, err) -} - -func TestDeploymentTokenList(t *testing.T) { - expectedHelp := "List all the API tokens in an Astro Deployment" - testUtil.InitTestConfig(testUtil.LocalPlatform) - - t.Run("-h prints list help", func(t *testing.T) { - cmdArgs := []string{"token", "list", "-h"} - resp, err := execDeploymentCmd(cmdArgs...) - assert.NoError(t, err) - assert.Contains(t, resp, expectedHelp) - }) - - t.Run("will error if deployment id flag is not provided", func(t *testing.T) { - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - astroCoreClient = mockClient - cmdArgs := []string{"token", "list"} - _, err := execDeploymentCmd(cmdArgs...) - assert.EqualError(t, err, "flag --deployment-id is required") - }) - - t.Run("any errors from api are returned and tokens are not listed", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.LocalPlatform) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListDeploymentApiTokensWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&ListDeploymentAPITokensResponseError, nil).Twice() - astroCoreClient = mockClient - cmdArgs := []string{"token", "list", "--deployment-id", mockDeploymentID} - _, err := execDeploymentCmd(cmdArgs...) - assert.ErrorContains(t, err, "failed to list api tokens") - }) - t.Run("any context errors from api are returned and tokens are not listed", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.Initial) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListDeploymentApiTokensWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&ListDeploymentAPITokensResponseOK, nil).Twice() - astroCoreClient = mockClient - cmdArgs := []string{"token", "list", "--deployment-id", mockDeploymentID} - _, err := execDeploymentCmd(cmdArgs...) - assert.Error(t, err) - }) - - t.Run("tokens are listed", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.LocalPlatform) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListDeploymentApiTokensWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&ListDeploymentAPITokensResponseOK, nil).Twice() - astroCoreClient = mockClient - cmdArgs := []string{"token", "list", "--deployment-id", mockDeploymentID} - _, err := execDeploymentCmd(cmdArgs...) - assert.NoError(t, err) - }) -} - -func TestDeploymentTokenCreate(t *testing.T) { - expectedHelp := "Create an API token in an Astro Deployment" - testUtil.InitTestConfig(testUtil.LocalPlatform) - - t.Run("-h prints list help", func(t *testing.T) { - cmdArgs := []string{"token", "create", "-h"} - resp, err := execDeploymentCmd(cmdArgs...) - assert.NoError(t, err) - assert.Contains(t, resp, expectedHelp) - }) - - t.Run("will error if deployment id flag is not provided", func(t *testing.T) { - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - astroCoreClient = mockClient - cmdArgs := []string{"token", "create", "--name", "Token 1", "--role", "DEPLOYMENT_ADMIN"} - _, err := execDeploymentCmd(cmdArgs...) - assert.EqualError(t, err, "flag --deployment-id is required") - }) - - t.Run("any errors from api are returned and token is not created", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.LocalPlatform) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("CreateDeploymentApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&CreateDeploymentAPITokenRoleResponseError, nil) - astroCoreClient = mockClient - cmdArgs := []string{"token", "create", "--name", "Token 1", "--role", "DEPLOYMENT_ADMIN", "--deployment-id", mockDeploymentID} - _, err := execDeploymentCmd(cmdArgs...) - assert.ErrorContains(t, err, "failed to create api token") - }) - t.Run("any context errors from api are returned and token is not created", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.Initial) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("CreateDeploymentApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&CreateDeploymentAPITokenRoleResponseOK, nil) - astroCoreClient = mockClient - cmdArgs := []string{"token", "list", "--deployment-id", mockDeploymentID} - _, err := execDeploymentCmd(cmdArgs...) - assert.Error(t, err) - }) - t.Run("token is created", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.LocalPlatform) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("CreateDeploymentApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&CreateDeploymentAPITokenRoleResponseOK, nil) - astroCoreClient = mockClient - cmdArgs := []string{"token", "create", "--name", "Token 1", "--role", "DEPLOYMENT_ADMIN", "--deployment-id", mockDeploymentID} - _, err := execDeploymentCmd(cmdArgs...) - assert.NoError(t, err) - }) - t.Run("token is created with no name provided", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.LocalPlatform) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("CreateDeploymentApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&CreateDeploymentAPITokenRoleResponseOK, nil) - // mock os.Stdin - expectedInput := []byte("Token 1") - r, w, err := os.Pipe() - assert.NoError(t, err) - _, err = w.Write(expectedInput) - assert.NoError(t, err) - w.Close() - stdin := os.Stdin - // Restore stdin right after the test. - defer func() { os.Stdin = stdin }() - os.Stdin = r - astroCoreClient = mockClient - cmdArgs := []string{"token", "create", "--role", "DEPLOYMENT_ADMIN", "--deployment-id", mockDeploymentID} - _, err = execDeploymentCmd(cmdArgs...) - assert.NoError(t, err) - }) - t.Run("token is created with no role provided", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.LocalPlatform) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("CreateDeploymentApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&CreateDeploymentAPITokenRoleResponseOK, nil) - // mock os.Stdin - expectedInput := []byte("1") - r, w, err := os.Pipe() - assert.NoError(t, err) - _, err = w.Write(expectedInput) - assert.NoError(t, err) - w.Close() - stdin := os.Stdin - // Restore stdin right after the test. - defer func() { os.Stdin = stdin }() - os.Stdin = r - astroCoreClient = mockClient - cmdArgs := []string{"token", "create", "--name", "Token 1", "--deployment-id", mockDeploymentID} - _, err = execDeploymentCmd(cmdArgs...) - assert.NoError(t, err) - }) -} - -func TestDeploymentTokenUpdate(t *testing.T) { - expectedHelp := "Update a Deployment API token" - testUtil.InitTestConfig(testUtil.LocalPlatform) - tokenID = "" - - t.Run("-h prints list help", func(t *testing.T) { - cmdArgs := []string{"token", "update", "-h"} - resp, err := execDeploymentCmd(cmdArgs...) - assert.NoError(t, err) - assert.Contains(t, resp, expectedHelp) - }) - - t.Run("will error if deployment id flag is not provided", func(t *testing.T) { - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - astroCoreClient = mockClient - cmdArgs := []string{"token", "update", "--name", tokenName1} - _, err := execDeploymentCmd(cmdArgs...) - assert.EqualError(t, err, "flag --deployment-id is required") - }) - - t.Run("any errors from api are returned and token is not updated", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.LocalPlatform) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListDeploymentApiTokensWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&ListDeploymentAPITokensResponseOK, nil) - mockClient.On("UpdateDeploymentApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&MutateDeploymentAPITokenRoleResponseError, nil) - astroCoreClient = mockClient - - mockIamClient := new(astroiamcore_mocks.ClientWithResponsesInterface) - mockIamClient.On("GetApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&GetAPITokensResponseOKDeploymentToken, nil) - astroCoreIamClient = mockIamClient - - cmdArgs := []string{"token", "update", "--name", tokenName1, "--deployment-id", mockDeploymentID} - _, err := execDeploymentCmd(cmdArgs...) - assert.ErrorContains(t, err, "failed to update api token") - }) - t.Run("any context errors from api are returned and token is not updated", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.Initial) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListDeploymentApiTokensWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&ListDeploymentAPITokensResponseOK, nil) - mockClient.On("UpdateDeploymentApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&MutateDeploymentAPITokenRoleResponseOK, nil) - astroCoreClient = mockClient - cmdArgs := []string{"token", "update", "--name", tokenName1, "--deployment-id", mockDeploymentID} - _, err := execDeploymentCmd(cmdArgs...) - assert.Error(t, err) - }) - t.Run("token is updated", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.LocalPlatform) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListDeploymentApiTokensWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&ListDeploymentAPITokensResponseOK, nil) - mockClient.On("UpdateDeploymentApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&MutateDeploymentAPITokenRoleResponseOK, nil) - astroCoreClient = mockClient - cmdArgs := []string{"token", "update", "--name", tokenName1, "--deployment-id", mockDeploymentID} - _, err := execDeploymentCmd(cmdArgs...) - assert.NoError(t, err) - }) - - t.Run("token is updated with no ID provided", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.LocalPlatform) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListDeploymentApiTokensWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&ListDeploymentAPITokensResponseOK, nil) - mockClient.On("UpdateDeploymentApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&MutateDeploymentAPITokenRoleResponseOK, nil) - // mock os.Stdin - expectedInput := []byte("1") - r, w, err := os.Pipe() - assert.NoError(t, err) - _, err = w.Write(expectedInput) - assert.NoError(t, err) - w.Close() - stdin := os.Stdin - // Restore stdin right after the test. - defer func() { os.Stdin = stdin }() - os.Stdin = r - astroCoreClient = mockClient - cmdArgs := []string{"token", "update", "--deployment-id", mockDeploymentID} - _, err = execDeploymentCmd(cmdArgs...) - assert.NoError(t, err) - }) - - t.Run("token is updated with id provided", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.LocalPlatform) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("GetDeploymentApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&GetDeploymentAPITokenWithResponseOK, nil) - mockClient.On("UpdateDeploymentApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&MutateDeploymentAPITokenRoleResponseOK, nil) - astroCoreClient = mockClient - cmdArgs := []string{"token", "update", mockTokenID, "--deployment-id", mockDeploymentID} - _, err := execDeploymentCmd(cmdArgs...) - assert.NoError(t, err) - }) -} - -func TestDeploymentTokenRotate(t *testing.T) { - tokenID = "" - expectedHelp := "Rotate a Deployment API token" - testUtil.InitTestConfig(testUtil.LocalPlatform) - - t.Run("-h prints list help", func(t *testing.T) { - cmdArgs := []string{"token", "rotate", "-h"} - resp, err := execDeploymentCmd(cmdArgs...) - assert.NoError(t, err) - assert.Contains(t, resp, expectedHelp) - }) - - t.Run("will error if deployment id flag is not provided", func(t *testing.T) { - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - astroCoreClient = mockClient - cmdArgs := []string{"token", "rotate", "--name", tokenName1, "--force"} - _, err := execDeploymentCmd(cmdArgs...) - assert.EqualError(t, err, "flag --deployment-id is required") - }) - - t.Run("any errors from api are returned and token is not rotated", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.LocalPlatform) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListDeploymentApiTokensWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&ListDeploymentAPITokensResponseOK, nil) - mockClient.On("RotateDeploymentApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&RotateDeploymentAPITokenResponseError, nil) - astroCoreClient = mockClient - cmdArgs := []string{"token", "rotate", "--name", tokenName1, "--force", "--deployment-id", mockDeploymentID} - _, err := execDeploymentCmd(cmdArgs...) - assert.ErrorContains(t, err, "failed to rotate api token") - }) - t.Run("any context errors from api are returned and token is not rotated", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.Initial) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListDeploymentApiTokensWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&ListDeploymentAPITokensResponseOK, nil).Twice() - mockClient.On("RotateDeploymentApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&RotateDeploymentAPITokenResponseOK, nil) - astroCoreClient = mockClient - cmdArgs := []string{"token", "rotate", "--name", tokenName1, "--deployment-id", mockDeploymentID} - _, err := execDeploymentCmd(cmdArgs...) - assert.Error(t, err) - }) - t.Run("token is rotated with name provided", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.LocalPlatform) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListDeploymentApiTokensWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&ListDeploymentAPITokensResponseOK, nil) - mockClient.On("RotateDeploymentApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&RotateDeploymentAPITokenResponseOK, nil) - astroCoreClient = mockClient - cmdArgs := []string{"token", "rotate", "--name", tokenName1, "--force", "--deployment-id", mockDeploymentID} - _, err := execDeploymentCmd(cmdArgs...) - assert.NoError(t, err) - }) - - t.Run("token is rotated with no ID or name provided", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.LocalPlatform) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListDeploymentApiTokensWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&ListDeploymentAPITokensResponseOK, nil) - mockClient.On("RotateDeploymentApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&RotateDeploymentAPITokenResponseOK, nil) - // mock os.Stdin - expectedInput := []byte("1") - r, w, err := os.Pipe() - assert.NoError(t, err) - _, err = w.Write(expectedInput) - assert.NoError(t, err) - w.Close() - stdin := os.Stdin - // Restore stdin right after the test. - defer func() { os.Stdin = stdin }() - os.Stdin = r - astroCoreClient = mockClient - cmdArgs := []string{"token", "rotate", "--force", "--deployment-id", mockDeploymentID} - _, err = execDeploymentCmd(cmdArgs...) - assert.NoError(t, err) - }) - t.Run("token is rotated with and confirmed", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.LocalPlatform) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListDeploymentApiTokensWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&ListDeploymentAPITokensResponseOK, nil) - mockClient.On("RotateDeploymentApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&RotateDeploymentAPITokenResponseOK, nil) - // mock os.Stdin - expectedInput := []byte("y") - r, w, err := os.Pipe() - assert.NoError(t, err) - _, err = w.Write(expectedInput) - assert.NoError(t, err) - w.Close() - stdin := os.Stdin - // Restore stdin right after the test. - defer func() { os.Stdin = stdin }() - os.Stdin = r - astroCoreClient = mockClient - cmdArgs := []string{"token", "rotate", "--name", tokenName1, "--deployment-id", mockDeploymentID} - _, err = execDeploymentCmd(cmdArgs...) - assert.NoError(t, err) - }) - - t.Run("token is rotated with id provided", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.LocalPlatform) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("GetDeploymentApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&GetDeploymentAPITokenWithResponseOK, nil) - mockClient.On("RotateDeploymentApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&RotateDeploymentAPITokenResponseOK, nil) - astroCoreClient = mockClient - cmdArgs := []string{"token", "rotate", mockTokenID, "--force", "--deployment-id", mockDeploymentID} - _, err := execDeploymentCmd(cmdArgs...) - assert.NoError(t, err) - }) -} - -func TestDeploymentTokenDelete(t *testing.T) { - tokenID = "" - expectedHelp := "Delete a Deployment API token" - testUtil.InitTestConfig(testUtil.LocalPlatform) - - t.Run("-h prints list help", func(t *testing.T) { - cmdArgs := []string{"token", "delete", "-h"} - resp, err := execDeploymentCmd(cmdArgs...) - assert.NoError(t, err) - assert.Contains(t, resp, expectedHelp) - }) - t.Run("will error if deployment id flag is not provided", func(t *testing.T) { - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - astroCoreClient = mockClient - cmdArgs := []string{"token", "delete", apiToken1.Id} - _, err := execDeploymentCmd(cmdArgs...) - assert.EqualError(t, err, "flag --deployment-id is required") - }) - t.Run("any errors from api are returned and token is not deleted", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.LocalPlatform) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListDeploymentApiTokensWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&ListDeploymentAPITokensResponseOK, nil) - mockClient.On("DeleteDeploymentApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&DeleteDeploymentAPITokenResponseError, nil) - astroCoreClient = mockClient - cmdArgs := []string{"token", "delete", "--name", tokenName1, "--force", "--deployment-id", mockDeploymentID} - _, err := execDeploymentCmd(cmdArgs...) - assert.ErrorContains(t, err, "failed to delete api token") - }) - t.Run("any context errors from api are returned and token is not deleted", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.Initial) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListDeploymentApiTokensWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&ListDeploymentAPITokensResponseOK, nil).Twice() - mockClient.On("DeleteDeploymentApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&DeleteDeploymentAPITokenResponseOK, nil) - astroCoreClient = mockClient - cmdArgs := []string{"token", "delete", "--name", tokenName1, "--deployment-id", mockDeploymentID} - _, err := execDeploymentCmd(cmdArgs...) - assert.Error(t, err) - }) - t.Run("token is deleted", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.LocalPlatform) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListDeploymentApiTokensWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&ListDeploymentAPITokensResponseOK, nil) - mockClient.On("DeleteDeploymentApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&DeleteDeploymentAPITokenResponseOK, nil) - astroCoreClient = mockClient - cmdArgs := []string{"token", "delete", "--name", tokenName1, "--force", "--deployment-id", mockDeploymentID} - _, err := execDeploymentCmd(cmdArgs...) - assert.NoError(t, err) - }) - - t.Run("token is deleted with no ID provided", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.LocalPlatform) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListDeploymentApiTokensWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&ListDeploymentAPITokensResponseOK, nil) - mockClient.On("DeleteDeploymentApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&DeleteDeploymentAPITokenResponseOK, nil) - // mock os.Stdin - expectedInput := []byte("1") - r, w, err := os.Pipe() - assert.NoError(t, err) - _, err = w.Write(expectedInput) - assert.NoError(t, err) - w.Close() - stdin := os.Stdin - // Restore stdin right after the test. - defer func() { os.Stdin = stdin }() - os.Stdin = r - astroCoreClient = mockClient - cmdArgs := []string{"token", "delete", "--force", "--deployment-id", mockDeploymentID} - _, err = execDeploymentCmd(cmdArgs...) - assert.NoError(t, err) - }) - t.Run("token is delete with and confirmed", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.LocalPlatform) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListDeploymentApiTokensWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&ListDeploymentAPITokensResponseOK, nil) - mockClient.On("DeleteDeploymentApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&DeleteDeploymentAPITokenResponseOK, nil) - // mock os.Stdin - expectedInput := []byte("y") - r, w, err := os.Pipe() - assert.NoError(t, err) - _, err = w.Write(expectedInput) - assert.NoError(t, err) - w.Close() - stdin := os.Stdin - // Restore stdin right after the test. - defer func() { os.Stdin = stdin }() - os.Stdin = r - astroCoreClient = mockClient - cmdArgs := []string{"token", "delete", "--name", tokenName1, "--deployment-id", mockDeploymentID} - _, err = execDeploymentCmd(cmdArgs...) - assert.NoError(t, err) - }) - t.Run("token is deleted with id provided", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.LocalPlatform) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("GetDeploymentApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&GetDeploymentAPITokenWithResponseOK, nil) - mockClient.On("DeleteDeploymentApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&DeleteDeploymentAPITokenResponseOK, nil) - astroCoreClient = mockClient - cmdArgs := []string{"token", "delete", mockTokenID, "--force", "--deployment-id", mockDeploymentID} - _, err := execDeploymentCmd(cmdArgs...) - assert.NoError(t, err) - }) -} - -func TestAddWorkspaceTokenDeploymentRole(t *testing.T) { - testUtil.InitTestConfig(testUtil.LocalPlatform) - t.Run("happy path Create", func(t *testing.T) { - mockIamClient := new(astroiamcore_mocks.ClientWithResponsesInterface) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListWorkspaceApiTokensWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&ListWorkspaceAPITokensResponseOK, nil).Once() - mockClient.On("UpdateWorkspaceApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&UpdateWorkspaceAPITokenResponseOK, nil).Once() - mockIamClient.On("GetApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&GetAPITokensResponseOKWorkspaceToken, nil).Once() - - defer testUtil.MockUserInput(t, "1")() - astroCoreClient = mockClient - astroCoreIamClient = mockIamClient - - cmdArgs := []string{"token", "workspace-token", "add", "--deployment-id", mockDeploymentID, "--role", "DEPLOYMENT_ADMIN"} - _, err := execDeploymentCmd(cmdArgs...) - assert.NoError(t, err) - }) - - t.Run("happy path with token id passed in", func(t *testing.T) { - mockIamClient := new(astroiamcore_mocks.ClientWithResponsesInterface) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("UpdateWorkspaceApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&UpdateWorkspaceAPITokenResponseOK, nil).Once() - mockIamClient.On("GetApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&GetAPITokensResponseOKWorkspaceToken, nil).Once() - // mock os.Stdin - defer testUtil.MockUserInput(t, "DEPLOYMENT_ADMIN")() - astroCoreClient = mockClient - astroCoreIamClient = mockIamClient - cmdArgs := []string{"token", "workspace-token", "add", "token-id", "--deployment-id", mockDeploymentID} - _, err := execDeploymentCmd(cmdArgs...) - assert.NoError(t, err) - }) -} - -func TestUpdateWorkspaceTokenDeploymentRole(t *testing.T) { - testUtil.InitTestConfig(testUtil.LocalPlatform) - - t.Run("happy path Update", func(t *testing.T) { - mockIamClient := new(astroiamcore_mocks.ClientWithResponsesInterface) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListDeploymentApiTokensWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&ListDeploymentAPITokensResponseOK, nil).Once() - mockClient.On("UpdateWorkspaceApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&UpdateWorkspaceAPITokenResponseOK, nil).Once() - mockIamClient.On("GetApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&GetAPITokensResponseOKWorkspaceToken, nil).Once() - defer testUtil.MockUserInput(t, "1")() - - astroCoreClient = mockClient - astroCoreIamClient = mockIamClient - cmdArgs := []string{"token", "workspace-token", "update", "--deployment-id", mockDeploymentID, "--role", "DEPLOYMENT_ADMIN"} - _, err := execDeploymentCmd(cmdArgs...) - assert.NoError(t, err) - }) - - t.Run("happy path with token id passed in", func(t *testing.T) { - mockIamClient := new(astroiamcore_mocks.ClientWithResponsesInterface) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("UpdateWorkspaceApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&UpdateWorkspaceAPITokenResponseOK, nil).Once() - mockIamClient.On("GetApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&GetAPITokensResponseOKWorkspaceToken, nil).Once() - // mock os.Stdin - defer testUtil.MockUserInput(t, "DEPLOYMENT_ADMIN")() - astroCoreClient = mockClient - astroCoreIamClient = mockIamClient - cmdArgs := []string{"token", "workspace-token", "update", "token-id", "--deployment-id", mockDeploymentID} - _, err := execDeploymentCmd(cmdArgs...) - assert.NoError(t, err) - }) -} - -func TestAddOrganizationTokenDeploymentRole(t *testing.T) { - testUtil.InitTestConfig(testUtil.LocalPlatform) - t.Run("happy path Create", func(t *testing.T) { - mockIamClient := new(astroiamcore_mocks.ClientWithResponsesInterface) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListOrganizationApiTokensWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&ListOrganizationAPITokensResponseOK, nil).Once() - mockClient.On("UpdateOrganizationApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&UpdateOrganizationAPITokenResponseOK, nil).Once() - mockIamClient.On("GetApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&GetAPITokensResponseOKOrganizationToken, nil).Once() - - defer testUtil.MockUserInput(t, "1")() - astroCoreClient = mockClient - astroCoreIamClient = mockIamClient - - cmdArgs := []string{"token", "organization-token", "add", "--deployment-id", mockDeploymentID, "--role", "DEPLOYMENT_ADMIN"} - _, err := execDeploymentCmd(cmdArgs...) - assert.NoError(t, err) - }) - - t.Run("happy path with token id passed in", func(t *testing.T) { - mockIamClient := new(astroiamcore_mocks.ClientWithResponsesInterface) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("UpdateOrganizationApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&UpdateOrganizationAPITokenResponseOK, nil).Once() - mockIamClient.On("GetApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&GetAPITokensResponseOKOrganizationToken, nil).Once() - // mock os.Stdin - defer testUtil.MockUserInput(t, "DEPLOYMENT_ADMIN")() - astroCoreClient = mockClient - astroCoreIamClient = mockIamClient - cmdArgs := []string{"token", "organization-token", "add", "token-id", "--deployment-id", mockDeploymentID} - _, err := execDeploymentCmd(cmdArgs...) - assert.NoError(t, err) - }) -} - -func TestUpdateOrganizationTokenDeploymentRole(t *testing.T) { - testUtil.InitTestConfig(testUtil.LocalPlatform) - - t.Run("happy path Update", func(t *testing.T) { - mockIamClient := new(astroiamcore_mocks.ClientWithResponsesInterface) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListDeploymentApiTokensWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&ListDeploymentAPITokensResponseOK, nil).Once() - mockClient.On("UpdateOrganizationApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&UpdateOrganizationAPITokenResponseOK, nil).Once() - mockIamClient.On("GetApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&GetAPITokensResponseOKOrganizationToken, nil).Once() - defer testUtil.MockUserInput(t, "1")() - - astroCoreClient = mockClient - astroCoreIamClient = mockIamClient - cmdArgs := []string{"token", "organization-token", "update", "--deployment-id", mockDeploymentID, "--role", "DEPLOYMENT_ADMIN"} - _, err := execDeploymentCmd(cmdArgs...) - assert.NoError(t, err) - }) - - t.Run("happy path with token id passed in", func(t *testing.T) { - mockIamClient := new(astroiamcore_mocks.ClientWithResponsesInterface) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("UpdateOrganizationApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&UpdateOrganizationAPITokenResponseOK, nil).Once() - mockIamClient.On("GetApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&GetAPITokensResponseOKOrganizationToken, nil).Once() - // mock os.Stdin - defer testUtil.MockUserInput(t, "DEPLOYMENT_ADMIN")() - astroCoreClient = mockClient - astroCoreIamClient = mockIamClient - cmdArgs := []string{"token", "organization-token", "update", "token-id", "--deployment-id", mockDeploymentID} - _, err := execDeploymentCmd(cmdArgs...) - assert.NoError(t, err) - }) -} - -func TestRemoveWorkspaceTokenDeploymentRole(t *testing.T) { - testUtil.InitTestConfig(testUtil.LocalPlatform) - t.Run("happy path", func(t *testing.T) { - mockIamClient := new(astroiamcore_mocks.ClientWithResponsesInterface) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListDeploymentApiTokensWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&ListDeploymentAPITokensResponseOK, nil).Once() - mockClient.On("UpdateWorkspaceApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&UpdateWorkspaceAPITokenResponseOK, nil).Once() - mockIamClient.On("GetApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&GetAPITokensResponseOKWorkspaceToken, nil).Once() - defer testUtil.MockUserInput(t, "1")() - astroCoreClient = mockClient - astroCoreIamClient = mockIamClient - cmdArgs := []string{"token", "workspace-token", "remove", "--deployment-id", mockDeploymentID} - _, err := execDeploymentCmd(cmdArgs...) - assert.NoError(t, err) - }) - - t.Run("happy path with token id passed in", func(t *testing.T) { - mockIamClient := new(astroiamcore_mocks.ClientWithResponsesInterface) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("UpdateWorkspaceApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&UpdateWorkspaceAPITokenResponseOK, nil).Once() - mockIamClient.On("GetApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&GetAPITokensResponseOKWorkspaceToken, nil).Once() - astroCoreClient = mockClient - astroCoreIamClient = mockIamClient - cmdArgs := []string{"token", "workspace-token", "remove", "token-id", "--deployment-id", mockDeploymentID} - _, err := execDeploymentCmd(cmdArgs...) - assert.NoError(t, err) - }) -} - -func TestRemoveOrganizationTokenDeploymentRole(t *testing.T) { - testUtil.InitTestConfig(testUtil.LocalPlatform) - t.Run("happy path", func(t *testing.T) { - mockIamClient := new(astroiamcore_mocks.ClientWithResponsesInterface) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListDeploymentApiTokensWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&ListDeploymentAPITokensResponseOK, nil).Once() - mockClient.On("UpdateOrganizationApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&UpdateOrganizationAPITokenResponseOK, nil).Once() - mockIamClient.On("GetApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&GetAPITokensResponseOKOrganizationToken, nil).Once() - defer testUtil.MockUserInput(t, "1")() - astroCoreClient = mockClient - astroCoreIamClient = mockIamClient - cmdArgs := []string{"token", "organization-token", "remove", "--deployment-id", mockDeploymentID} - _, err := execDeploymentCmd(cmdArgs...) - assert.NoError(t, err) - }) - - t.Run("happy path with token id passed in", func(t *testing.T) { - mockIamClient := new(astroiamcore_mocks.ClientWithResponsesInterface) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("UpdateOrganizationApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&UpdateOrganizationAPITokenResponseOK, nil).Once() - mockIamClient.On("GetApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&GetAPITokensResponseOKOrganizationToken, nil).Once() - astroCoreClient = mockClient - astroCoreIamClient = mockIamClient - cmdArgs := []string{"token", "organization-token", "remove", "token-id", "--deployment-id", mockDeploymentID} - _, err := execDeploymentCmd(cmdArgs...) - assert.NoError(t, err) - }) -} - -func TestListOrganizationTokenDeploymentRoles(t *testing.T) { - testUtil.InitTestConfig(testUtil.LocalPlatform) - t.Run("happy path", func(t *testing.T) { - mockIamClient := new(astroiamcore_mocks.ClientWithResponsesInterface) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListDeploymentApiTokensWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&ListDeploymentAPITokensResponseOK, nil).Once() - defer testUtil.MockUserInput(t, "1")() - astroCoreClient = mockClient - astroCoreIamClient = mockIamClient - cmdArgs := []string{"token", "organization-token", "list", "--deployment-id", mockDeploymentID} - _, err := execDeploymentCmd(cmdArgs...) - assert.NoError(t, err) - }) -} - -func TestListWorkspaceTokenDeploymentRoles(t *testing.T) { - testUtil.InitTestConfig(testUtil.LocalPlatform) - t.Run("happy path", func(t *testing.T) { - mockIamClient := new(astroiamcore_mocks.ClientWithResponsesInterface) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListDeploymentApiTokensWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&ListDeploymentAPITokensResponseOK, nil).Once() - defer testUtil.MockUserInput(t, "1")() - astroCoreClient = mockClient - astroCoreIamClient = mockIamClient - cmdArgs := []string{"token", "workspace-token", "list", "--deployment-id", mockDeploymentID} - _, err := execDeploymentCmd(cmdArgs...) - assert.NoError(t, err) - }) -} diff --git a/cmd/cloud/organization.go b/cmd/cloud/organization.go index a6c5aefdb..738a969f1 100644 --- a/cmd/cloud/organization.go +++ b/cmd/cloud/organization.go @@ -1,56 +1,19 @@ package cloud import ( - "errors" - "fmt" "io" - "os" - "strconv" - "strings" "github.com/astronomer/astro-cli/cloud/organization" - roleClient "github.com/astronomer/astro-cli/cloud/role" - "github.com/astronomer/astro-cli/cloud/team" - "github.com/astronomer/astro-cli/cloud/user" "github.com/astronomer/astro-cli/cloud/workspace" - "github.com/astronomer/astro-cli/pkg/input" - "github.com/astronomer/astro-cli/pkg/printutil" "github.com/spf13/cobra" ) var ( - errInvalidOrganizationRoleKey = errors.New("invalid organization role selection") - orgList = organization.List - orgSwitch = organization.Switch - orgExportAuditLogs = organization.ExportAuditLogs - wsSwitch = workspace.Switch - orgName string - auditLogsOutputFilePath string - auditLogsEarliestParam int - auditLogsEarliestParamDefaultValue = 1 - shouldDisplayLoginLink bool - role string - updateRole string - teamDescription string - teamName string - teamID string - userID string - organizationID string - updateOrganizationRole string - teamOrgRole string - validOrganizationRoles []string - shouldIncludeDefaultRoles bool + orgSwitch = organization.Switch + wsSwitch = workspace.Switch + shouldDisplayLoginLink bool ) -const ( - allowedOrganizationRoleNames = "ORGANIZATION_MEMBER, ORGANIZATION_BILLING_ADMIN, ORGANIZATION_OWNER" - allowedOrganizationRoleNamesProse = "ORGANIZATION_MEMBER, ORGANIZATION_BILLING_ADMIN, and ORGANIZATION_OWNER" -) - -func init() { - validOrganizationRoles = []string{"ORGANIZATION_MEMBER", "ORGANIZATION_BILLING_ADMIN", "ORGANIZATION_OWNER"} -} - func newOrganizationCmd(out io.Writer) *cobra.Command { cmd := &cobra.Command{ Use: "organization", @@ -59,30 +22,11 @@ func newOrganizationCmd(out io.Writer) *cobra.Command { Long: "Manage your Astro Organizations. These commands are for users in more than one Organization", } cmd.AddCommand( - newOrganizationListCmd(out), newOrganizationSwitchCmd(out), - newOrganizationUserRootCmd(out), - newOrganizationTeamRootCmd(out), - newOrganizationAuditLogs(out), - newOrganizationTokenRootCmd(out), - newOrganizationRoleRootCmd(out), ) return cmd } -func newOrganizationListCmd(out io.Writer) *cobra.Command { - cmd := &cobra.Command{ - Use: "list", - Aliases: []string{"ls"}, - Short: "List all Organizations you have access too", - Long: "List all Organizations you have access too", - RunE: func(cmd *cobra.Command, args []string) error { - return organizationList(cmd, out) - }, - } - return cmd -} - func newOrganizationSwitchCmd(out io.Writer) *cobra.Command { cmd := &cobra.Command{ Use: "switch [organization name/id]", @@ -101,103 +45,7 @@ func newOrganizationSwitchCmd(out io.Writer) *cobra.Command { return cmd } -func newOrganizationAuditLogs(out io.Writer) *cobra.Command { - cmd := &cobra.Command{ - Use: "audit-logs", - Aliases: []string{"al"}, - Short: "Manage your Organization audit logs.", - Long: "Manage your Organization audit logs.", - } - cmd.AddCommand( - newOrganizationExportAuditLogs(out), - ) - return cmd -} - -func newOrganizationExportAuditLogs(_ io.Writer) *cobra.Command { - cmd := &cobra.Command{ - Use: "export", - Aliases: []string{"e"}, - Short: "Export your Organization audit logs in GZIP. Requires Organization Owner permissions.", - Long: "Export your Organization audit logs in GZIP. Requires Organization Owner permissions.", - RunE: func(cmd *cobra.Command, args []string) error { - return organizationExportAuditLogs(cmd) - }, - } - cmd.Flags().StringVarP(&orgName, "organization-name", "n", "", "Name of the Organization to manage audit logs for.") - cmd.Flags().StringVarP(&auditLogsOutputFilePath, "output-file", "o", "", "Path to a file for storing exported audit logs") - cmd.Flags().IntVarP(&auditLogsEarliestParam, "include", "i", auditLogsEarliestParamDefaultValue, - "Number of days in the past to start exporting logs from. Minimum: 1. Maximum: 90.") - return cmd -} - -func newOrganizationUserRootCmd(out io.Writer) *cobra.Command { - cmd := &cobra.Command{ - Use: "user", - Aliases: []string{"us", "users"}, - Short: "Manage users in your Astro Organization", - Long: "Manage users in your Astro Organization.", - } - cmd.SetOut(out) - cmd.AddCommand( - newOrganizationUserInviteCmd(out), - newOrganizationUserListCmd(out), - newOrganizationUserUpdateCmd(out), - ) - return cmd -} - -func newOrganizationUserInviteCmd(out io.Writer) *cobra.Command { - cmd := &cobra.Command{ - Use: "invite [email]", - Aliases: []string{"inv"}, - Short: "Invite a user to your Astro Organization", - Long: "Invite a user to your Astro Organization\n$astro user invite [email] --role [" + allowedOrganizationRoleNames + "].", - RunE: func(cmd *cobra.Command, args []string) error { - return userInvite(cmd, args, out) - }, - } - cmd.Flags().StringVarP(&role, "role", "r", "ORGANIZATION_MEMBER", "The role for the "+ - "user. Possible values are"+allowedOrganizationRoleNamesProse) - return cmd -} - -func newOrganizationUserListCmd(out io.Writer) *cobra.Command { - cmd := &cobra.Command{ - Use: "list", - Aliases: []string{"ls"}, - Short: "List all the users in your Astro Organization", - Long: "List all the users in your Astro Organization", - RunE: func(cmd *cobra.Command, args []string) error { - return listUsers(cmd, out) - }, - } - return cmd -} - -func newOrganizationUserUpdateCmd(out io.Writer) *cobra.Command { - cmd := &cobra.Command{ - Use: "update [email]", - Aliases: []string{"up"}, - Short: "Update a the role of a user your in Astro Organization", - Long: "Update the role of a user in your Astro Organization\n$astro user update [email] --role [" + allowedOrganizationRoleNames + "].", - RunE: func(cmd *cobra.Command, args []string) error { - return userUpdate(cmd, args, out) - }, - } - cmd.Flags().StringVarP(&updateRole, "role", "r", "", "The new role for the "+ - "user. Possible values are "+allowedOrganizationRoleNamesProse) - return cmd -} - -func organizationList(cmd *cobra.Command, out io.Writer) error { - // Silence Usage as we have now validated command input - cmd.SilenceUsage = true - return orgList(out, platformCoreClient) -} - func organizationSwitch(cmd *cobra.Command, out io.Writer, args []string) error { - // Silence Usage as we have now validated command input cmd.SilenceUsage = true organizationNameOrID := "" @@ -215,500 +63,3 @@ func organizationSwitch(cmd *cobra.Command, out io.Writer, args []string) error } return nil } - -func organizationExportAuditLogs(cmd *cobra.Command) error { - // Silence Usage as we have now validated command input - cmd.SilenceUsage = true - - fmt.Println("This may take some time depending on how many days are being exported.") - return orgExportAuditLogs(astroCoreClient, platformCoreClient, - orgName, auditLogsOutputFilePath, auditLogsEarliestParam) -} - -func userInvite(cmd *cobra.Command, args []string, out io.Writer) error { - var email string - - // if an email was provided in the args we use it - if len(args) > 0 { - // make sure the email is lowercase - email = strings.ToLower(args[0]) - } else { - // no email was provided so ask the user for it - email = input.Text("enter email address to invite a user: ") - } - - cmd.SilenceUsage = true - return user.CreateInvite(email, role, out, astroCoreClient) -} - -func listUsers(cmd *cobra.Command, out io.Writer) error { - cmd.SilenceUsage = true - return user.ListOrgUsers(out, astroCoreClient) -} - -func userUpdate(cmd *cobra.Command, args []string, out io.Writer) error { - var email string - - // if an email was provided in the args we use it - if len(args) > 0 { - // make sure the email is lowercase - email = strings.ToLower(args[0]) - } - - if updateRole == "" { - // no role was provided so ask the user for it - updateRole = input.Text("enter a user Organization role(" + allowedOrganizationRoleNames + ") to update user: ") - } - - cmd.SilenceUsage = true - return user.UpdateUserRole(email, updateRole, out, astroCoreClient) -} - -func newOrganizationTeamRootCmd(out io.Writer) *cobra.Command { - cmd := &cobra.Command{ - Use: "team", - Aliases: []string{"te", "teams"}, - Short: "Manage teams in your Astro Organization", - Long: "Manage teams in your Astro Organization.", - } - cmd.SetOut(out) - cmd.AddCommand( - newTeamCreateCmd(out), - newOrganizationTeamListCmd(out), - newTeamUpdateCmd(out), - newTeamDeleteCmd(out), - newOrganizationTeamUserRootCmd(out), - ) - return cmd -} - -func newOrganizationTeamListCmd(out io.Writer) *cobra.Command { - cmd := &cobra.Command{ - Use: "list", - Aliases: []string{"ls"}, - Short: "List all the teams in your Astro Organization", - Long: "List all the teams in your Astro Organization", - RunE: func(cmd *cobra.Command, args []string) error { - return listTeams(cmd, out) - }, - } - return cmd -} - -func listTeams(cmd *cobra.Command, out io.Writer) error { - cmd.SilenceUsage = true - return team.ListOrgTeams(out, astroCoreClient) -} - -func newTeamUpdateCmd(out io.Writer) *cobra.Command { - cmd := &cobra.Command{ - Use: "update [team-id]", - Aliases: []string{"up"}, - Short: "Update an Astro team", - Long: "Update an Astro team", - Args: cobra.MaximumNArgs(1), - RunE: func(cmd *cobra.Command, args []string) error { - return teamUpdate(cmd, out, args) - }, - } - cmd.Flags().StringVarP(&teamName, "name", "n", "", "The Team's name. If the name contains a space, specify the entire name within quotes \"\" ") - cmd.Flags(). - StringVarP(&teamDescription, "description", "d", "", "Description of the Team. If the description contains a space, specify the entire team description in quotes \"\"") - cmd.Flags().StringVarP(&updateOrganizationRole, "role", "r", "", "The new role for the "+ - "team. Possible values are "+allowedOrganizationRoleNamesProse) - return cmd -} - -func teamUpdate(cmd *cobra.Command, out io.Writer, args []string) error { - cmd.SilenceUsage = true - - id := "" - - if len(args) == 1 { - id = args[0] - } - - return team.UpdateTeam(id, teamName, teamDescription, updateOrganizationRole, out, astroCoreClient) -} - -func newTeamCreateCmd(out io.Writer) *cobra.Command { - cmd := &cobra.Command{ - Use: "create", - Aliases: []string{"cr"}, - Short: "Create an Astro Team", - Long: "Create an Astro Team", - RunE: func(cmd *cobra.Command, args []string) error { - return teamCreate(cmd, out) - }, - } - cmd.Flags().StringVarP(&teamName, "name", "n", "", "The Team's name. If the name contains a space, specify the entire team within quotes \"\" ") - cmd.Flags().StringVarP(&teamDescription, "description", "d", "", "Description of the Team. If the description contains a space, specify the entire team in quotes \"\"") - cmd.Flags().StringVarP(&teamOrgRole, "role", "r", "", "The role for the token. Possible values are "+allowedOrganizationRoleNamesProse) - - return cmd -} - -func teamCreate(cmd *cobra.Command, out io.Writer) error { - cmd.SilenceUsage = true - if teamOrgRole == "" { - fmt.Println("select a Organization Role for the new team:") - // no role was provided so ask the user for it - var err error - teamOrgRole, err = selectOrganizationRole() - if err != nil { - return err - } - } - return team.CreateTeam(teamName, teamDescription, teamOrgRole, out, astroCoreClient) -} - -func newTeamDeleteCmd(out io.Writer) *cobra.Command { - cmd := &cobra.Command{ - Use: "delete [team-id]", - Aliases: []string{"de"}, - Short: "Delete an Astro Team", - Long: "Delete an Astro Team", - Args: cobra.MaximumNArgs(1), - RunE: func(cmd *cobra.Command, args []string) error { - return teamDelete(cmd, out, args) - }, - } - return cmd -} - -func teamDelete(cmd *cobra.Command, out io.Writer, args []string) error { - cmd.SilenceUsage = true - - id := "" - - if len(args) == 1 { - id = args[0] - } - - return team.Delete(id, out, astroCoreClient) -} - -func newOrganizationTeamUserRootCmd(out io.Writer) *cobra.Command { - cmd := &cobra.Command{ - Use: "user", - Aliases: []string{"us", "users"}, - Short: "Manage users in your Astro Team", - Long: "Manage users in your Astro Team.", - } - cmd.SetOut(out) - cmd.AddCommand( - newTeamRemoveUserCmd(out), - newTeamAddUserCmd(out), - newTeamListUsersCmd(out), - ) - return cmd -} - -func newTeamRemoveUserCmd(out io.Writer) *cobra.Command { - cmd := &cobra.Command{ - Use: "remove", - Short: "Remove a user from an Astro Team", - Long: "Remove a user from an Astro Team", - RunE: func(cmd *cobra.Command, args []string) error { - return removeTeamUser(cmd, out) - }, - } - cmd.Flags().StringVarP(&teamID, "team-id", "t", "", "The Team's unique identifier \"\" ") - cmd.Flags().StringVarP(&userID, "user-id", "u", "", "The User's unique identifier \"\"") - return cmd -} - -func removeTeamUser(cmd *cobra.Command, out io.Writer) error { - cmd.SilenceUsage = true - return team.RemoveUser(teamID, userID, out, astroCoreClient) -} - -func newTeamAddUserCmd(out io.Writer) *cobra.Command { - cmd := &cobra.Command{ - Use: "add", - Short: "Add a user to an Astro Team", - Long: "Add a user to an Astro Team", - RunE: func(cmd *cobra.Command, args []string) error { - return addTeamUser(cmd, out) - }, - } - cmd.Flags().StringVarP(&teamID, "team-id", "t", "", "The Team's unique identifier \"\" ") - cmd.Flags().StringVarP(&userID, "user-id", "u", "", "The User's unique identifier \"\"") - return cmd -} - -func addTeamUser(cmd *cobra.Command, out io.Writer) error { - cmd.SilenceUsage = true - return team.AddUser(teamID, userID, out, astroCoreClient) -} - -func newTeamListUsersCmd(out io.Writer) *cobra.Command { - cmd := &cobra.Command{ - Use: "list", - Short: "Lists users in an Astro Team", - Long: "Lists users in an Astro Team", - RunE: func(cmd *cobra.Command, args []string) error { - return listUsersCmd(cmd, out) - }, - } - cmd.Flags().StringVarP(&teamID, "team-id", "t", "", "The Team's id \"\" ") - return cmd -} - -func listUsersCmd(cmd *cobra.Command, out io.Writer) error { - cmd.SilenceUsage = true - return team.ListTeamUsers(teamID, out, astroCoreClient) -} - -// org tokens - -//nolint:dupl -func newOrganizationTokenRootCmd(out io.Writer) *cobra.Command { - cmd := &cobra.Command{ - Use: "token", - Aliases: []string{"to"}, - Short: "Manage tokens in your Astro Organization", - Long: "Manage tokens in your Astro Organization.", - } - cmd.SetOut(out) - cmd.AddCommand( - newOrganizationTokenListCmd(out), - newOrganizationTokenListRolesCmd(out), - newOrganizationTokenCreateCmd(out), - newOrganizationTokenUpdateCmd(out), - newOrganizationTokenRotateCmd(out), - newOrganizationTokenDeleteCmd(out), - ) - cmd.PersistentFlags().StringVar(&organizationID, "organization-id", "", "organization where you would like to manage tokens") - return cmd -} - -//nolint:dupl -func newOrganizationTokenListCmd(out io.Writer) *cobra.Command { - cmd := &cobra.Command{ - Use: "list", - Aliases: []string{"ls"}, - Short: "List all the API tokens in an Astro Organization", - Long: "List all the API tokens in an Astro Organization", - RunE: func(cmd *cobra.Command, args []string) error { - return listOrganizationToken(cmd, out) - }, - } - return cmd -} - -//nolint:dupl -func newOrganizationTokenListRolesCmd(out io.Writer) *cobra.Command { - cmd := &cobra.Command{ - Use: "roles [TOKEN_ID]", - Short: "List roles for an organization API token", - Long: "List roles for an organization API token\n$astro organization token roles [TOKEN_ID] ", - RunE: func(cmd *cobra.Command, args []string) error { - return listOrganizationTokenRoles(cmd, args, out) - }, - } - return cmd -} - -//nolint:dupl -func newOrganizationTokenCreateCmd(out io.Writer) *cobra.Command { - cmd := &cobra.Command{ - Use: "create", - Aliases: []string{"cr"}, - Short: "Create an API token in an Astro Organization", - Long: "Create an API token in an Astro Organization\n$astro organization token create --name [token name] --role [" + allowedOrganizationRoleNames + "].", - RunE: func(cmd *cobra.Command, args []string) error { - return createOrganizationToken(cmd, out) - }, - } - cmd.Flags().StringVarP(&tokenName, "name", "n", "", "The token's name. If the name contains a space, specify the entire name within quotes \"\" ") - cmd.Flags().BoolVarP(&cleanTokenOutput, "clean-output", "c", false, "Print only the token as output. For use of the command in scripts") - cmd.Flags().StringVarP(&tokenDescription, "description", "d", "", "Description of the token. If the description contains a space, specify the entire description within quotes \"\"") - cmd.Flags().StringVarP(&tokenRole, "role", "r", "", "The role for the token. Possible values are "+allowedOrganizationRoleNamesProse) - cmd.Flags().IntVarP(&tokenExpiration, "expiration", "e", 0, "Expiration of the token in days. If the flag isn't used the token won't have an expiration. Must be between 1 and 3650 days. ") - return cmd -} - -//nolint:dupl -func newOrganizationTokenUpdateCmd(out io.Writer) *cobra.Command { - cmd := &cobra.Command{ - Use: "update [TOKEN_ID]", - Aliases: []string{"up"}, - Short: "Update a Organization or Organaization API token", - Long: "Update a Organization or Organaization API token that has a role in an Astro Organization\n$astro organization token update [TOKEN_ID] --name [new token name] --role [" + allowedOrganizationRoleNames + "].", - RunE: func(cmd *cobra.Command, args []string) error { - return updateOrganizationToken(cmd, args, out) - }, - } - cmd.Flags().StringVarP(&name, "name", "t", "", "The current name of the token. If the name contains a space, specify the entire name within quotes \"\" ") - cmd.Flags().StringVarP(&tokenName, "new-name", "n", "", "The token's new name. If the name contains a space, specify the entire name within quotes \"\" ") - cmd.Flags().StringVarP(&tokenDescription, "description", "d", "", "updated description of the token. If the description contains a space, specify the entire description in quotes \"\"") - cmd.Flags().StringVarP(&tokenRole, "role", "r", "", "The new role for the token. Possible values are "+allowedOrganizationRoleNamesProse) - return cmd -} - -//nolint:dupl -func newOrganizationTokenRotateCmd(out io.Writer) *cobra.Command { - cmd := &cobra.Command{ - Use: "rotate [TOKEN_ID]", - Aliases: []string{"ro"}, - Short: "Rotate a Organization API token", - Long: "Rotate a Organization API token. You can only rotate Organization API tokens. You cannot rotate Workspace API tokens with this command", - RunE: func(cmd *cobra.Command, args []string) error { - return rotateOrganizationToken(cmd, args, out) - }, - } - cmd.Flags().BoolVarP(&cleanTokenOutput, "clean-output", "c", false, "Print only the token as output. For use of the command in scripts") - cmd.Flags().StringVarP(&name, "name", "t", "", "The name of the token to be rotated. If the name contains a space, specify the entire name within quotes \"\" ") - cmd.Flags().BoolVarP(&forceRotate, "force", "f", false, "Rotate the Organization API token without showing a warning") - - return cmd -} - -//nolint:dupl -func newOrganizationTokenDeleteCmd(out io.Writer) *cobra.Command { - cmd := &cobra.Command{ - Use: "delete [TOKEN_ID]", - Aliases: []string{"de"}, - Short: "Delete a Organization API token or remove an Organization API token from a Organization", - Long: "Delete a Organization API token or remove an Organization API token from a Organization", - RunE: func(cmd *cobra.Command, args []string) error { - return deleteOrganizationToken(cmd, args, out) - }, - } - cmd.Flags().StringVarP(&name, "name", "t", "", "The name of the token to be deleted. If the name contains a space, specify the entire name within quotes \"\" ") - cmd.Flags().BoolVarP(&forceDelete, "force", "f", false, "Delete or remove the API token without showing a warning") - - return cmd -} - -//nolint:dupl -func listOrganizationToken(cmd *cobra.Command, out io.Writer) error { - cmd.SilenceUsage = true - return organization.ListTokens(astroCoreClient, out) -} - -//nolint:dupl -func listOrganizationTokenRoles(cmd *cobra.Command, args []string, out io.Writer) error { - if len(args) > 0 { - // make sure the id is lowercase - tokenID = strings.ToLower(args[0]) - } - cmd.SilenceUsage = true - return organization.ListTokenRoles(tokenID, astroCoreClient, astroCoreIamClient, out) -} - -//nolint:dupl -func createOrganizationToken(cmd *cobra.Command, out io.Writer) error { - if tokenName == "" { - // no role was provided so ask the user for it - tokenName = input.Text("Enter a name for the new Organization API token: ") - } - if tokenRole == "" { - fmt.Println("select a Organization Role for the new API token:") - // no role was provided so ask the user for it - var err error - tokenRole, err = selectOrganizationRole() - if err != nil { - return err - } - } - cmd.SilenceUsage = true - - return organization.CreateToken(tokenName, tokenDescription, tokenRole, tokenExpiration, cleanTokenOutput, out, astroCoreClient) -} - -//nolint:dupl -func updateOrganizationToken(cmd *cobra.Command, args []string, out io.Writer) error { - // if an id was provided in the args we use it - if len(args) > 0 { - // make sure the id is lowercase - tokenID = strings.ToLower(args[0]) - } - - cmd.SilenceUsage = true - return organization.UpdateToken(tokenID, name, tokenName, tokenDescription, tokenRole, out, astroCoreClient, astroCoreIamClient) -} - -//nolint:dupl -func rotateOrganizationToken(cmd *cobra.Command, args []string, out io.Writer) error { - // if an id was provided in the args we use it - if len(args) > 0 { - // make sure the id is lowercase - tokenID = strings.ToLower(args[0]) - } - - cmd.SilenceUsage = true - return organization.RotateToken(tokenID, name, cleanTokenOutput, forceRotate, out, astroCoreClient, astroCoreIamClient) -} - -//nolint:dupl -func deleteOrganizationToken(cmd *cobra.Command, args []string, out io.Writer) error { - // if an id was provided in the args we use it - if len(args) > 0 { - // make sure the id is lowercase - tokenID = strings.ToLower(args[0]) - } - - cmd.SilenceUsage = true - return organization.DeleteToken(tokenID, name, forceDelete, out, astroCoreClient, astroCoreIamClient) -} - -//nolint:dupl -func selectOrganizationRole() (string, error) { - tokenRolesMap := map[string]string{} - tab := &printutil.Table{ - DynamicPadding: true, - Header: []string{"#", "ROLE"}, - } - for i := range validOrganizationRoles { - index := i + 1 - tab.AddRow([]string{ - strconv.Itoa(index), - validOrganizationRoles[i], - }, false) - tokenRolesMap[strconv.Itoa(index)] = validOrganizationRoles[i] - } - - tab.Print(os.Stdout) - choice := input.Text("\n> ") - selected, ok := tokenRolesMap[choice] - if !ok { - return "", errInvalidOrganizationRoleKey - } - return selected, nil -} - -func newOrganizationRoleRootCmd(out io.Writer) *cobra.Command { - cmd := &cobra.Command{ - Use: "role", - Aliases: []string{"ro", "roles"}, - Short: "Manage roles in your Astro Organization", - Long: "Manage roles in your Astro Organization.", - } - cmd.SetOut(out) - cmd.AddCommand( - newOrganizationRoleListCmd(out), - ) - return cmd -} - -func newOrganizationRoleListCmd(out io.Writer) *cobra.Command { - cmd := &cobra.Command{ - Use: "list", - Aliases: []string{"ls"}, - Short: "List all the roles in your Astro Organization", - Long: "List all the roles in your Astro Organization", - RunE: func(cmd *cobra.Command, args []string) error { - return listRoles(cmd, out) - }, - } - cmd.Flags().BoolVarP(&shouldIncludeDefaultRoles, "include-default-roles", "i", false, "Should include default roles in response") - - return cmd -} - -func listRoles(cmd *cobra.Command, out io.Writer) error { - cmd.SilenceUsage = true - return roleClient.ListOrgRoles(out, astroCoreClient, shouldIncludeDefaultRoles) -} diff --git a/cmd/cloud/organization_test.go b/cmd/cloud/organization_test.go index 7ae61a135..130b3a040 100644 --- a/cmd/cloud/organization_test.go +++ b/cmd/cloud/organization_test.go @@ -2,25 +2,16 @@ package cloud import ( "bytes" - "encoding/json" "fmt" "io" - "net/http" "os" - "strings" "testing" - "time" - astroiamcore_mocks "github.com/astronomer/astro-cli/astro-client-iam-core/mocks" astroplatformcore "github.com/astronomer/astro-cli/astro-client-platform-core" astrocore "github.com/astronomer/astro-cli/astro-client-core" - astrocore_mocks "github.com/astronomer/astro-cli/astro-client-core/mocks" - "github.com/astronomer/astro-cli/cloud/user" - "github.com/astronomer/astro-cli/config" testUtil "github.com/astronomer/astro-cli/pkg/testing" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" ) //nolint:unparam @@ -45,43 +36,6 @@ func TestOrganizationRootCommand(t *testing.T) { assert.Contains(t, buf.String(), "organization") } -func TestOrganizationRoleRootCommand(t *testing.T) { - expectedHelp := "Manage roles in your Astro Organization." - testUtil.InitTestConfig(testUtil.LocalPlatform) - cmdArgs := []string{"role"} - resp, err := execOrganizationCmd(cmdArgs...) - assert.NoError(t, err) - assert.Contains(t, resp, expectedHelp) -} - -func TestOrganizationUserRootCommand(t *testing.T) { - expectedHelp := "Manage users in your Astro Organization." - testUtil.InitTestConfig(testUtil.LocalPlatform) - cmdArgs := []string{"user"} - resp, err := execOrganizationCmd(cmdArgs...) - assert.NoError(t, err) - assert.Contains(t, resp, expectedHelp) -} - -func TestOrganizationTeamRootCommand(t *testing.T) { - expectedHelp := "Manage teams in your Astro Organization." - testUtil.InitTestConfig(testUtil.LocalPlatform) - cmdArgs := []string{"team"} - resp, err := execOrganizationCmd(cmdArgs...) - assert.NoError(t, err) - assert.Contains(t, resp, expectedHelp) -} - -func TestOrganizationList(t *testing.T) { - orgList = func(out io.Writer, platformCoreClient astroplatformcore.CoreClient) error { - return nil - } - - cmdArgs := []string{"list"} - _, err := execOrganizationCmd(cmdArgs...) - assert.NoError(t, err) -} - func TestOrganizationSwitch(t *testing.T) { t.Run("workspace flag triggers wsSwitch with provided id", func(t *testing.T) { testUtil.InitTestConfig(testUtil.LocalPlatform) @@ -133,1495 +87,3 @@ func TestOrganizationSwitch(t *testing.T) { assert.False(t, calledWs) }) } - -func TestOrganizationExportAuditLogs(t *testing.T) { - // turn on audit logs - config.CFG.AuditLogs.SetHomeString("true") - orgExportAuditLogs = func(coreClient astrocore.CoreClient, platformCoreClient astroplatformcore.CoreClient, orgName, filePath string, earliest int) error { - return nil - } - - t.Run("Without params", func(t *testing.T) { - cmdArgs := []string{"audit-logs", "export", "--organization-name", "Astronomer"} - _, err := execOrganizationCmd(cmdArgs...) - assert.NoError(t, err) - }) - - t.Run("with auditLogsOutputFilePath param", func(t *testing.T) { - cmdArgs := []string{"audit-logs", "export", "--organization-name", "Astronomer", "--output-file", "test.json"} - _, err := execOrganizationCmd(cmdArgs...) - assert.NoError(t, err) - }) - - // Delete audit logs exports - currentDir, _ := os.Getwd() - files, _ := os.ReadDir(currentDir) - for _, file := range files { - if strings.HasPrefix(file.Name(), "audit-logs-") { - os.Remove(file.Name()) - } - } - os.Remove("test.json") -} - -// test organization user commands - -var ( - inviteUserID = "user_cuid" - createInviteResponseOK = astrocore.CreateUserInviteResponse{ - HTTPResponse: &http.Response{ - StatusCode: 200, - }, - JSON200: &astrocore.Invite{ - InviteId: "astro_invite_id", - UserId: &inviteUserID, - }, - } - errorBody, _ = json.Marshal(astrocore.Error{ - Message: "failed to create invite: test-error", - }) - createInviteResponseError = astrocore.CreateUserInviteResponse{ - HTTPResponse: &http.Response{ - StatusCode: 500, - }, - Body: errorBody, - JSON200: nil, - } - orgRole = "ORGANIZATION_MEMBER" - user1 = astrocore.User{ - CreatedAt: time.Now(), - FullName: "user 1", - Id: "user1-id", - OrgRole: &orgRole, - Username: "user@1.com", - } - users = []astrocore.User{ - user1, - } - teamMembers = []astrocore.TeamMember{{ - UserId: user1.Id, - Username: user1.Username, - FullName: &user1.FullName, - }} - team1 = astrocore.Team{ - CreatedAt: time.Now(), - Name: "team 1", - Description: &description, - Id: "team1-id", - Members: &teamMembers, - } - teams = []astrocore.Team{ - team1, - } - GetUserWithResponseOK = astrocore.GetUserResponse{ - HTTPResponse: &http.Response{ - StatusCode: 200, - }, - JSON200: &user1, - } - GetTeamWithResponseOK = astrocore.GetTeamResponse{ - HTTPResponse: &http.Response{ - StatusCode: 200, - }, - JSON200: &team1, - } - ListOrgUsersResponseOK = astrocore.ListOrgUsersResponse{ - HTTPResponse: &http.Response{ - StatusCode: 200, - }, - JSON200: &astrocore.UsersPaginated{ - Limit: 1, - Offset: 0, - TotalCount: 1, - Users: users, - }, - } - ListOrgTeamsResponseOK = astrocore.ListOrganizationTeamsResponse{ - HTTPResponse: &http.Response{ - StatusCode: 200, - }, - JSON200: &astrocore.TeamsPaginated{ - Limit: 1, - Offset: 0, - TotalCount: 1, - Teams: teams, - }, - } - errorBodyList, _ = json.Marshal(astrocore.Error{ - Message: "failed to list users", - }) - teamRequestErrorBodyList, _ = json.Marshal(astrocore.Error{ - Message: "failed to list teams", - }) - ListOrgUsersResponseError = astrocore.ListOrgUsersResponse{ - HTTPResponse: &http.Response{ - StatusCode: 500, - }, - Body: errorBodyList, - JSON200: nil, - } - ListOrgTeamsResponseError = astrocore.ListOrganizationTeamsResponse{ - HTTPResponse: &http.Response{ - StatusCode: 500, - }, - Body: teamRequestErrorBodyList, - JSON200: nil, - } - MutateOrgUserRoleResponseOK = astrocore.MutateOrgUserRoleResponse{ - HTTPResponse: &http.Response{ - StatusCode: 200, - }, - JSON200: &astrocore.UserRole{ - Role: "ORGANIZATION_MEMBER", - }, - } - UpdateTeamResponseOK = astrocore.UpdateTeamResponse{ - HTTPResponse: &http.Response{ - StatusCode: 200, - }, - JSON200: &team1, - } - - errorBodyUpdate, _ = json.Marshal(astrocore.Error{ - Message: "failed to update user", - }) - teamRequestErrorBodyUpdate, _ = json.Marshal(astrocore.Error{ - Message: "failed to update team", - }) - MutateOrgUserRoleResponseError = astrocore.MutateOrgUserRoleResponse{ - HTTPResponse: &http.Response{ - StatusCode: 500, - }, - Body: errorBodyUpdate, - JSON200: nil, - } - MutateOrgTeamRoleResponseOK = astrocore.MutateOrgTeamRoleResponse{ - HTTPResponse: &http.Response{ - StatusCode: 200, - }, - } - MutateOrgTeamRoleResponseError = astrocore.MutateOrgTeamRoleResponse{ - HTTPResponse: &http.Response{ - StatusCode: 500, - }, - Body: teamRequestErrorBodyUpdate, - } - UpdateTeamResponseError = astrocore.UpdateTeamResponse{ - HTTPResponse: &http.Response{ - StatusCode: 500, - }, - Body: teamRequestErrorBodyUpdate, - JSON200: nil, - } - teamRequestErrorCreate, _ = json.Marshal(astrocore.Error{ - Message: "failed to create team", - }) - CreateTeamResponseError = astrocore.CreateTeamResponse{ - HTTPResponse: &http.Response{ - StatusCode: 500, - }, - Body: teamRequestErrorCreate, - JSON200: nil, - } - CreateTeamResponseOK = astrocore.CreateTeamResponse{ - HTTPResponse: &http.Response{ - StatusCode: 200, - }, - JSON200: &team1, - } - teamRequestErrorDelete, _ = json.Marshal(astrocore.Error{ - Message: "failed to delete team", - }) - DeleteTeamResponseError = astrocore.DeleteTeamResponse{ - HTTPResponse: &http.Response{ - StatusCode: 500, - }, - Body: teamRequestErrorDelete, - } - DeleteTeamResponseOK = astrocore.DeleteTeamResponse{ - HTTPResponse: &http.Response{ - StatusCode: 200, - }, - } - errorBodyUpdateTeamMembership, _ = json.Marshal(astrocore.Error{ - Message: "failed to update team membership", - }) - AddTeamMemberResponseOK = astrocore.AddTeamMembersResponse{ - HTTPResponse: &http.Response{ - StatusCode: 200, - }, - } - AddTeamMemberResponseError = astrocore.AddTeamMembersResponse{ - HTTPResponse: &http.Response{ - StatusCode: 500, - }, - Body: errorBodyUpdateTeamMembership, - } - RemoveTeamMemberResponseOK = astrocore.RemoveTeamMemberResponse{ - HTTPResponse: &http.Response{ - StatusCode: 200, - }, - } - RemoveTeamMemberResponseError = astrocore.RemoveTeamMemberResponse{ - HTTPResponse: &http.Response{ - StatusCode: 500, - }, - Body: errorBodyUpdateTeamMembership, - } - getTokenErrorBodyList, _ = json.Marshal(astrocore.Error{ - Message: "failed to get token", - }) - GetOrganizationAPITokenResponseError = astrocore.GetOrganizationApiTokenResponse{ - HTTPResponse: &http.Response{ - StatusCode: 500, - }, - Body: getTokenErrorBodyList, - JSON200: nil, - } - GetOrganizationAPITokenResponseOK = astrocore.GetOrganizationApiTokenResponse{ - HTTPResponse: &http.Response{ - StatusCode: 200, - }, - JSON200: &apiToken1, - } -) - -func TestUserInvite(t *testing.T) { - expectedHelp := "astro user invite [email] --role [ORGANIZATION_MEMBER, ORGANIZATION_BILLING_ADMIN, ORGANIZATION_OWNER]" - testUtil.InitTestConfig(testUtil.LocalPlatform) - - t.Run("-h prints invite help", func(t *testing.T) { - cmdArgs := []string{"user", "invite", "-h"} - resp, err := execOrganizationCmd(cmdArgs...) - assert.NoError(t, err) - assert.Contains(t, resp, expectedHelp) - }) - t.Run("valid email with no role creates an invite", func(t *testing.T) { - expectedOut := "invite for some@email.com with role ORGANIZATION_MEMBER created" - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("CreateUserInviteWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&createInviteResponseOK, nil).Once() - astroCoreClient = mockClient - cmdArgs := []string{"user", "invite", "some@email.com"} - resp, err := execOrganizationCmd(cmdArgs...) - assert.NoError(t, err) - assert.Contains(t, resp, expectedOut) - mockClient.AssertExpectations(t) - }) - t.Run("valid email with valid role creates an invite", func(t *testing.T) { - expectedOut := "invite for some@email.com with role ORGANIZATION_MEMBER created" - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("CreateUserInviteWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&createInviteResponseOK, nil).Once() - astroCoreClient = mockClient - cmdArgs := []string{"user", "invite", "some@email.com", "--role", "ORGANIZATION_MEMBER"} - resp, err := execOrganizationCmd(cmdArgs...) - assert.NoError(t, err) - assert.Contains(t, resp, expectedOut) - mockClient.AssertExpectations(t) - }) - t.Run("valid email with invalid role returns an error and no invite gets created", func(t *testing.T) { - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - astroCoreClient = mockClient - cmdArgs := []string{"user", "invite", "some@email.com", "--role", "invalid"} - _, err := execOrganizationCmd(cmdArgs...) - assert.ErrorIs(t, err, user.ErrInvalidRole) - mockClient.AssertExpectations(t) - }) - t.Run("any errors from api are returned and no invite gets created", func(t *testing.T) { - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("CreateUserInviteWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&createInviteResponseError, nil).Once() - astroCoreClient = mockClient - cmdArgs := []string{"user", "invite", "some@email.com", "--role", "ORGANIZATION_MEMBER"} - _, err := execOrganizationCmd(cmdArgs...) - assert.EqualError(t, err, "failed to create invite: test-error") - mockClient.AssertExpectations(t) - }) - - t.Run("any context errors from api are returned and no invite gets created", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.Initial) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - astroCoreClient = mockClient - cmdArgs := []string{"user", "invite", "some@email.com", "--role", "ORGANIZATION_MEMBER"} - _, err := execOrganizationCmd(cmdArgs...) - assert.Error(t, err) - mockClient.AssertExpectations(t) - }) - t.Run("command asks for input when no email is passed in as an arg", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.LocalPlatform) - // mock os.Stdin - expectedInput := []byte("test-email-input") - r, w, err := os.Pipe() - assert.NoError(t, err) - _, err = w.Write(expectedInput) - assert.NoError(t, err) - w.Close() - stdin := os.Stdin - // Restore stdin right after the test. - defer func() { os.Stdin = stdin }() - os.Stdin = r - - expectedOut := "invite for test-email-input with role ORGANIZATION_MEMBER created" - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("CreateUserInviteWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&createInviteResponseOK, nil).Once() - astroCoreClient = mockClient - - cmdArgs := []string{"user", "invite"} - resp, err := execOrganizationCmd(cmdArgs...) - assert.NoError(t, err) - assert.Contains(t, resp, expectedOut) - mockClient.AssertExpectations(t) - }) - t.Run("command returns an error when no email is provided", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.LocalPlatform) - // mock os.Stdin - expectedInput := []byte("") - r, w, err := os.Pipe() - assert.NoError(t, err) - _, err = w.Write(expectedInput) - assert.NoError(t, err) - w.Close() - stdin := os.Stdin - // Restore stdin right after the test. - defer func() { os.Stdin = stdin }() - os.Stdin = r - - cmdArgs := []string{"user", "invite"} - _, err = execOrganizationCmd(cmdArgs...) - assert.ErrorIs(t, err, user.ErrInvalidEmail) - }) -} - -func TestUserList(t *testing.T) { - expectedHelp := "List all the users in your Astro Organization" - testUtil.InitTestConfig(testUtil.LocalPlatform) - - t.Run("-h prints list help", func(t *testing.T) { - cmdArgs := []string{"user", "list", "-h"} - resp, err := execOrganizationCmd(cmdArgs...) - assert.NoError(t, err) - assert.Contains(t, resp, expectedHelp) - }) - t.Run("any errors from api are returned and users are not listed", func(t *testing.T) { - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListOrgUsersWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&ListOrgUsersResponseError, nil).Once() - astroCoreClient = mockClient - cmdArgs := []string{"user", "list"} - _, err := execOrganizationCmd(cmdArgs...) - assert.EqualError(t, err, "failed to list users") - mockClient.AssertExpectations(t) - }) - t.Run("any context errors from api are returned and users are not listed", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.Initial) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - astroCoreClient = mockClient - cmdArgs := []string{"user", "list"} - _, err := execOrganizationCmd(cmdArgs...) - assert.Error(t, err) - mockClient.AssertExpectations(t) - }) -} - -func TestUserUpdate(t *testing.T) { - expectedHelp := "astro user update [email] --role [ORGANIZATION_MEMBER, ORGANIZATION_BILLING_ADMIN, ORGANIZATION_OWNER]" - testUtil.InitTestConfig(testUtil.LocalPlatform) - - t.Run("-h prints update help", func(t *testing.T) { - cmdArgs := []string{"user", "update", "-h"} - resp, err := execOrganizationCmd(cmdArgs...) - assert.NoError(t, err) - assert.Contains(t, resp, expectedHelp) - }) - t.Run("valid email with valid role updates user", func(t *testing.T) { - expectedOut := "The user user@1.com role was successfully updated to ORGANIZATION_MEMBER" - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListOrgUsersWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&ListOrgUsersResponseOK, nil).Twice() - mockClient.On("MutateOrgUserRoleWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&MutateOrgUserRoleResponseOK, nil).Once() - astroCoreClient = mockClient - cmdArgs := []string{"user", "update", "user@1.com", "--role", "ORGANIZATION_MEMBER"} - resp, err := execOrganizationCmd(cmdArgs...) - assert.NoError(t, err) - assert.Contains(t, resp, expectedOut) - mockClient.AssertExpectations(t) - }) - t.Run("valid email with invalid role returns an error and role is not update", func(t *testing.T) { - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - astroCoreClient = mockClient - cmdArgs := []string{"user", "update", "user@1.com", "--role", "invalid"} - _, err := execOrganizationCmd(cmdArgs...) - assert.ErrorIs(t, err, user.ErrInvalidRole) - mockClient.AssertExpectations(t) - }) - t.Run("any errors from api are returned and role is not updated", func(t *testing.T) { - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListOrgUsersWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&ListOrgUsersResponseOK, nil).Twice() - mockClient.On("MutateOrgUserRoleWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&MutateOrgUserRoleResponseError, nil).Once() - astroCoreClient = mockClient - cmdArgs := []string{"user", "update", "user@1.com", "--role", "ORGANIZATION_MEMBER"} - _, err := execOrganizationCmd(cmdArgs...) - assert.EqualError(t, err, "failed to update user") - mockClient.AssertExpectations(t) - }) - - t.Run("any context errors from api are returned and role is not updated", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.Initial) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - astroCoreClient = mockClient - cmdArgs := []string{"update", "user@1.com", "--role", "ORGANIZATION_MEMBER"} - _, err := execOrganizationCmd(cmdArgs...) - assert.Error(t, err) - mockClient.AssertExpectations(t) - }) - t.Run("command asks for input when no email is passed in as an arg", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.LocalPlatform) - - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListOrgUsersWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&ListOrgUsersResponseOK, nil).Twice() - - // mock os.Stdin - expectedInput := []byte("1") - r, w, err := os.Pipe() - assert.NoError(t, err) - _, err = w.Write(expectedInput) - assert.NoError(t, err) - w.Close() - stdin := os.Stdin - // Restore stdin right after the test. - defer func() { os.Stdin = stdin }() - os.Stdin = r - - expectedOut := "The user user@1.com role was successfully updated to ORGANIZATION_MEMBER" - mockClient.On("MutateOrgUserRoleWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&MutateOrgUserRoleResponseOK, nil).Once() - astroCoreClient = mockClient - - cmdArgs := []string{"user", "update", "--role", "ORGANIZATION_MEMBER"} - resp, err := execOrganizationCmd(cmdArgs...) - assert.NoError(t, err) - assert.Contains(t, resp, expectedOut) - mockClient.AssertExpectations(t) - }) -} - -func TestTeamList(t *testing.T) { - expectedHelp := "List all the teams in your Astro Organization" - testUtil.InitTestConfig(testUtil.LocalPlatform) - - t.Run("-h prints list help", func(t *testing.T) { - cmdArgs := []string{"team", "list", "-h"} - resp, err := execOrganizationCmd(cmdArgs...) - assert.NoError(t, err) - assert.Contains(t, resp, expectedHelp) - }) - t.Run("any errors from api are returned and teams are not listed", func(t *testing.T) { - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListOrganizationTeamsWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&ListOrgTeamsResponseError, nil).Once() - astroCoreClient = mockClient - cmdArgs := []string{"team", "list"} - _, err := execOrganizationCmd(cmdArgs...) - assert.EqualError(t, err, "failed to list teams") - mockClient.AssertExpectations(t) - }) - t.Run("any context errors from api are returned and teams are not listed", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.Initial) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - astroCoreClient = mockClient - cmdArgs := []string{"team", "list"} - _, err := execOrganizationCmd(cmdArgs...) - assert.Error(t, err) - mockClient.AssertExpectations(t) - }) -} - -func TestTeamUpdate(t *testing.T) { - expectedHelp := "organization team update [team-id]" - testUtil.InitTestConfig(testUtil.LocalPlatform) - - t.Run("-h prints update help", func(t *testing.T) { - cmdArgs := []string{"team", "update", "-h"} - resp, err := execOrganizationCmd(cmdArgs...) - assert.NoError(t, err) - assert.Contains(t, resp, expectedHelp) - }) - - t.Run("valid id with valid name and description updates team", func(t *testing.T) { - expectedOut := fmt.Sprintf("Astro Team %s was successfully updated\n", team1.Name) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("GetTeamWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&GetTeamWithResponseOK, nil).Once() - mockClient.On("UpdateTeamWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&UpdateTeamResponseOK, nil).Once() - astroCoreClient = mockClient - cmdArgs := []string{"team", "update", team1.Id, "--name", team1.Name, "--description", *team1.Description} - resp, err := execOrganizationCmd(cmdArgs...) - assert.NoError(t, err) - assert.Contains(t, resp, expectedOut) - mockClient.AssertExpectations(t) - }) - - t.Run("any errors from api are returned and role is not updated", func(t *testing.T) { - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("GetTeamWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&GetTeamWithResponseOK, nil).Once() - mockClient.On("UpdateTeamWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&UpdateTeamResponseError, nil).Once() - astroCoreClient = mockClient - cmdArgs := []string{"team", "update", team1.Id, "--name", team1.Name} - _, err := execOrganizationCmd(cmdArgs...) - assert.EqualError(t, err, "failed to update team") - mockClient.AssertExpectations(t) - }) - - t.Run("any context errors from api are returned and role is not updated", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.Initial) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - astroCoreClient = mockClient - cmdArgs := []string{"update", team1.Id, "--name", team1.Name} - _, err := execOrganizationCmd(cmdArgs...) - assert.Error(t, err) - mockClient.AssertExpectations(t) - }) - t.Run("command asks for input when no id is passed in as an arg", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.LocalPlatform) - - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListOrganizationTeamsWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&ListOrgTeamsResponseOK, nil).Twice() - // mock os.Stdin - expectedInput := []byte("1") - r, w, err := os.Pipe() - assert.NoError(t, err) - _, err = w.Write(expectedInput) - assert.NoError(t, err) - w.Close() - stdin := os.Stdin - // Restore stdin right after the test. - defer func() { os.Stdin = stdin }() - os.Stdin = r - - expectedOut := fmt.Sprintf("Astro Team %s was successfully updated\n", team1.Name) - mockClient.On("UpdateTeamWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&UpdateTeamResponseOK, nil).Once() - astroCoreClient = mockClient - - cmdArgs := []string{"team", "update", "--name", team1.Name, "--description", *team1.Description} - resp, err := execOrganizationCmd(cmdArgs...) - assert.NoError(t, err) - assert.Contains(t, resp, expectedOut) - mockClient.AssertExpectations(t) - }) - t.Run("valid id with role should update role", func(t *testing.T) { - expectedOut := fmt.Sprintf("Astro Team %s was successfully updated\n", team1.Name) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("GetTeamWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&GetTeamWithResponseOK, nil).Once() - mockClient.On("UpdateTeamWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&UpdateTeamResponseOK, nil).Once() - mockClient.On("MutateOrgTeamRoleWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&MutateOrgTeamRoleResponseOK, nil).Once() - - astroCoreClient = mockClient - cmdArgs := []string{"team", "update", team1.Id, "--role", "ORGANIZATION_OWNER"} - resp, err := execOrganizationCmd(cmdArgs...) - assert.NoError(t, err) - assert.Contains(t, resp, expectedOut) - mockClient.AssertExpectations(t) - }) - t.Run("will error on invalid role", func(t *testing.T) { - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("GetTeamWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&GetTeamWithResponseOK, nil).Once() - mockClient.On("UpdateTeamWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&UpdateTeamResponseOK, nil).Once() - - astroCoreClient = mockClient - cmdArgs := []string{"team", "update", team1.Id, "--role", "WORKSPACE_OPERATOR"} - _, err := execOrganizationCmd(cmdArgs...) - assert.ErrorContains(t, err, "requested role is invalid") - mockClient.AssertExpectations(t) - }) -} - -func TestTeamCreate(t *testing.T) { - expectedHelp := "organization team create" - testUtil.InitTestConfig(testUtil.LocalPlatform) - - t.Run("-h prints update help", func(t *testing.T) { - cmdArgs := []string{"team", "create", "-h"} - resp, err := execOrganizationCmd(cmdArgs...) - assert.NoError(t, err) - assert.Contains(t, resp, expectedHelp) - }) - t.Run("valid id with valid name and description updates team", func(t *testing.T) { - expectedOut := fmt.Sprintf("Astro Team %s was successfully created\n", team1.Name) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("CreateTeamWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&CreateTeamResponseOK, nil).Once() - astroCoreClient = mockClient - cmdArgs := []string{"team", "create", team1.Id, "--role", "ORGANIZATION_MEMBER", "--name", team1.Name, "--description", *team1.Description} - resp, err := execOrganizationCmd(cmdArgs...) - assert.NoError(t, err) - assert.Contains(t, resp, expectedOut) - mockClient.AssertExpectations(t) - }) - t.Run("valid id with valid name and description updates team no role passed in", func(t *testing.T) { - expectedOut := fmt.Sprintf("Astro Team %s was successfully created\n", team1.Name) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("CreateTeamWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&CreateTeamResponseOK, nil).Once() - astroCoreClient = mockClient - cmdArgs := []string{"team", "create", team1.Id, "--name", team1.Name, "--description", *team1.Description} - - // mock os.Stdin - expectedInput := []byte("1") - r, w, err := os.Pipe() - assert.NoError(t, err) - _, err = w.Write(expectedInput) - assert.NoError(t, err) - w.Close() - stdin := os.Stdin - // Restore stdin right after the test. - defer func() { os.Stdin = stdin }() - os.Stdin = r - - resp, err := execOrganizationCmd(cmdArgs...) - assert.NoError(t, err) - assert.Contains(t, resp, expectedOut) - mockClient.AssertExpectations(t) - }) - - t.Run("error no role passed in index out of range", func(t *testing.T) { - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - astroCoreClient = mockClient - cmdArgs := []string{"team", "create", team1.Id, "--name", team1.Name, "--description", *team1.Description} - - // mock os.Stdin - expectedInput := []byte("4") - r, w, err := os.Pipe() - assert.NoError(t, err) - _, err = w.Write(expectedInput) - assert.NoError(t, err) - w.Close() - stdin := os.Stdin - // Restore stdin right after the test. - defer func() { os.Stdin = stdin }() - os.Stdin = r - - _, err = execOrganizationCmd(cmdArgs...) - assert.EqualError(t, err, "invalid organization role selection") - mockClient.AssertExpectations(t) - }) - - t.Run("any errors from api are returned and role is not created", func(t *testing.T) { - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("CreateTeamWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&CreateTeamResponseError, nil).Once() - astroCoreClient = mockClient - cmdArgs := []string{"team", "create", team1.Id, "--role", "ORGANIZATION_MEMBER", "--name", team1.Name} - _, err := execOrganizationCmd(cmdArgs...) - assert.EqualError(t, err, "failed to create team") - mockClient.AssertExpectations(t) - }) - - t.Run("any context errors from api are returned and role is not created", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.Initial) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - astroCoreClient = mockClient - cmdArgs := []string{"create", team1.Id, "--role", "ORGANIZATION_MEMBER", "--name", team1.Name} - _, err := execOrganizationCmd(cmdArgs...) - assert.Error(t, err) - mockClient.AssertExpectations(t) - }) -} - -func TestTeamDelete(t *testing.T) { - expectedHelp := "organization team delete" - testUtil.InitTestConfig(testUtil.LocalPlatform) - - t.Run("-h prints update help", func(t *testing.T) { - cmdArgs := []string{"team", "delete", "-h"} - resp, err := execOrganizationCmd(cmdArgs...) - assert.NoError(t, err) - assert.Contains(t, resp, expectedHelp) - }) - t.Run("valid id with valid name and description updates team", func(t *testing.T) { - expectedOut := fmt.Sprintf("Astro Team %s was successfully deleted\n", team1.Name) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("GetTeamWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&GetTeamWithResponseOK, nil).Once() - mockClient.On("DeleteTeamWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&DeleteTeamResponseOK, nil).Once() - astroCoreClient = mockClient - cmdArgs := []string{"team", "delete", team1.Id} - resp, err := execOrganizationCmd(cmdArgs...) - assert.NoError(t, err) - assert.Contains(t, resp, expectedOut) - mockClient.AssertExpectations(t) - }) - - t.Run("any errors from api are returned and role is not deleted", func(t *testing.T) { - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("GetTeamWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&GetTeamWithResponseOK, nil).Once() - mockClient.On("DeleteTeamWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&DeleteTeamResponseError, nil).Once() - astroCoreClient = mockClient - cmdArgs := []string{"team", "delete", team1.Id} - _, err := execOrganizationCmd(cmdArgs...) - assert.EqualError(t, err, "failed to delete team") - mockClient.AssertExpectations(t) - }) - - t.Run("any context errors from api are returned and role is not deleted", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.Initial) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - astroCoreClient = mockClient - cmdArgs := []string{"delete", team1.Id} - _, err := execOrganizationCmd(cmdArgs...) - assert.Error(t, err) - mockClient.AssertExpectations(t) - }) - t.Run("command asks for input when no id is passed in as an arg", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.LocalPlatform) - - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListOrganizationTeamsWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&ListOrgTeamsResponseOK, nil).Twice() - // mock os.Stdin - expectedInput := []byte("1") - r, w, err := os.Pipe() - assert.NoError(t, err) - _, err = w.Write(expectedInput) - assert.NoError(t, err) - w.Close() - stdin := os.Stdin - // Restore stdin right after the test. - defer func() { os.Stdin = stdin }() - os.Stdin = r - - expectedOut := fmt.Sprintf("Astro Team %s was successfully deleted\n", team1.Name) - mockClient.On("DeleteTeamWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&DeleteTeamResponseOK, nil).Once() - astroCoreClient = mockClient - - cmdArgs := []string{"team", "delete"} - resp, err := execOrganizationCmd(cmdArgs...) - assert.NoError(t, err) - assert.Contains(t, resp, expectedOut) - mockClient.AssertExpectations(t) - }) -} - -func TestAddUser(t *testing.T) { - expectedHelp := "organization team user add" - testUtil.InitTestConfig(testUtil.LocalPlatform) - - t.Run("-h prints update help", func(t *testing.T) { - cmdArgs := []string{"team", "user", "add", "-h"} - resp, err := execOrganizationCmd(cmdArgs...) - assert.NoError(t, err) - assert.Contains(t, resp, expectedHelp) - }) - t.Run("valid team-id with valid user-id updates team membership", func(t *testing.T) { - expectedOut := fmt.Sprintf("Astro User %s was successfully added to team %s \n", user1.Id, team1.Name) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("GetTeamWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&GetTeamWithResponseOK, nil).Once() - mockClient.On("GetUserWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&GetUserWithResponseOK, nil).Once() - mockClient.On("AddTeamMembersWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&AddTeamMemberResponseOK, nil).Once() - astroCoreClient = mockClient - cmdArgs := []string{"team", "user", "add", "--team-id", team1.Id, "--user-id", user1.Id} - resp, err := execOrganizationCmd(cmdArgs...) - assert.NoError(t, err) - assert.Contains(t, resp, expectedOut) - mockClient.AssertExpectations(t) - }) - - t.Run("any errors from api are returned and membership is not added", func(t *testing.T) { - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("GetTeamWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&GetTeamWithResponseOK, nil).Once() - mockClient.On("GetUserWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&GetUserWithResponseOK, nil).Once() - mockClient.On("AddTeamMembersWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&AddTeamMemberResponseError, nil).Once() - astroCoreClient = mockClient - cmdArgs := []string{"team", "user", "add", "--team-id", team1.Id, "--user-id", user1.Id} - _, err := execOrganizationCmd(cmdArgs...) - assert.EqualError(t, err, "failed to update team membership") - mockClient.AssertExpectations(t) - }) - - t.Run("any context errors from api are returned and membership is not added", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.Initial) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - astroCoreClient = mockClient - cmdArgs := []string{"team", "user", "add", "--team-id", team1.Id, "--user-id", user1.Id} - _, err := execOrganizationCmd(cmdArgs...) - assert.Error(t, err) - mockClient.AssertExpectations(t) - }) - - t.Run("command asks for input when no team id is passed in as an arg", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.LocalPlatform) - - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListOrganizationTeamsWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&ListOrgTeamsResponseOK, nil).Twice() - mockClient.On("GetUserWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&GetUserWithResponseOK, nil).Once() - // mock os.Stdin - expectedInput := []byte("1") - r, w, err := os.Pipe() - assert.NoError(t, err) - _, err = w.Write(expectedInput) - assert.NoError(t, err) - w.Close() - stdin := os.Stdin - // Restore stdin right after the test. - defer func() { os.Stdin = stdin }() - os.Stdin = r - - expectedOut := fmt.Sprintf("Astro User %s was successfully added to team %s \n", user1.Id, team1.Name) - mockClient.On("AddTeamMembersWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&AddTeamMemberResponseOK, nil).Once() - astroCoreClient = mockClient - - cmdArgs := []string{"team", "user", "add", "--user-id", user1.Id} - resp, err := execOrganizationCmd(cmdArgs...) - assert.NoError(t, err) - assert.Contains(t, resp, expectedOut) - mockClient.AssertExpectations(t) - }) - - t.Run("command asks for input when no user id is passed in as an arg", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.LocalPlatform) - - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("GetTeamWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&GetTeamWithResponseOK, nil).Once() - mockClient.On("ListOrgUsersWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&ListOrgUsersResponseOK, nil).Twice() - // mock os.Stdin - expectedInput := []byte("1") - r, w, err := os.Pipe() - assert.NoError(t, err) - _, err = w.Write(expectedInput) - assert.NoError(t, err) - w.Close() - stdin := os.Stdin - // Restore stdin right after the test. - defer func() { os.Stdin = stdin }() - os.Stdin = r - - expectedOut := fmt.Sprintf("Astro User %s was successfully added to team %s \n", user1.Id, team1.Name) - mockClient.On("AddTeamMembersWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&AddTeamMemberResponseOK, nil).Once() - astroCoreClient = mockClient - - cmdArgs := []string{"team", "user", "add", "--team-id", team1.Id} - resp, err := execOrganizationCmd(cmdArgs...) - assert.NoError(t, err) - assert.Contains(t, resp, expectedOut) - mockClient.AssertExpectations(t) - }) -} - -func TestRemoveUser(t *testing.T) { - expectedHelp := "organization team user remove" - testUtil.InitTestConfig(testUtil.LocalPlatform) - - t.Run("-h prints update help", func(t *testing.T) { - cmdArgs := []string{"team", "user", "remove", "-h"} - resp, err := execOrganizationCmd(cmdArgs...) - assert.NoError(t, err) - assert.Contains(t, resp, expectedHelp) - }) - t.Run("valid team-id with valid user-id updates team membership", func(t *testing.T) { - expectedOut := fmt.Sprintf("Astro User %s was successfully removed from team %s \n", user1.Id, team1.Name) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("GetTeamWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&GetTeamWithResponseOK, nil).Once() - mockClient.On("RemoveTeamMemberWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&RemoveTeamMemberResponseOK, nil).Once() - astroCoreClient = mockClient - cmdArgs := []string{"team", "user", "remove", "--team-id", team1.Id, "--user-id", user1.Id} - resp, err := execOrganizationCmd(cmdArgs...) - assert.NoError(t, err) - assert.Contains(t, resp, expectedOut) - mockClient.AssertExpectations(t) - }) - - t.Run("any errors from api are returned and membership is not removed", func(t *testing.T) { - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("GetTeamWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&GetTeamWithResponseOK, nil).Once() - mockClient.On("RemoveTeamMemberWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&RemoveTeamMemberResponseError, nil).Once() - astroCoreClient = mockClient - cmdArgs := []string{"team", "user", "remove", "--team-id", team1.Id, "--user-id", user1.Id} - _, err := execOrganizationCmd(cmdArgs...) - assert.EqualError(t, err, "failed to update team membership") - mockClient.AssertExpectations(t) - }) - - t.Run("any context errors from api are returned and membership is not removed", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.Initial) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - astroCoreClient = mockClient - cmdArgs := []string{"team", "user", "remove", "--team-id", team1.Id, "--user-id", user1.Id} - _, err := execOrganizationCmd(cmdArgs...) - assert.Error(t, err) - mockClient.AssertExpectations(t) - }) - t.Run("command asks for input when no team id is passed in as an arg", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.LocalPlatform) - - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListOrganizationTeamsWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&ListOrgTeamsResponseOK, nil).Twice() - // mock os.Stdin - expectedInput := []byte("1") - r, w, err := os.Pipe() - assert.NoError(t, err) - _, err = w.Write(expectedInput) - assert.NoError(t, err) - w.Close() - stdin := os.Stdin - // Restore stdin right after the test. - defer func() { os.Stdin = stdin }() - os.Stdin = r - - expectedOut := fmt.Sprintf("Astro User %s was successfully removed from team %s \n", user1.Id, team1.Name) - mockClient.On("RemoveTeamMemberWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&RemoveTeamMemberResponseOK, nil).Once() - astroCoreClient = mockClient - - cmdArgs := []string{"team", "user", "remove", "--user-id", user1.Id} - resp, err := execOrganizationCmd(cmdArgs...) - assert.NoError(t, err) - assert.Contains(t, resp, expectedOut) - mockClient.AssertExpectations(t) - }) -} - -func TestTeamUserList(t *testing.T) { - expectedHelp := "Lists users in an Astro Team\n" - testUtil.InitTestConfig(testUtil.LocalPlatform) - - t.Run("-h prints list help", func(t *testing.T) { - cmdArgs := []string{"team", "user", "list", "-h"} - resp, err := execOrganizationCmd(cmdArgs...) - assert.NoError(t, err) - assert.Contains(t, resp, expectedHelp) - }) - t.Run("any errors from api are returned and teams are not listed", func(t *testing.T) { - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListOrganizationTeamsWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&ListOrgTeamsResponseError, nil).Once() - astroCoreClient = mockClient - cmdArgs := []string{"team", "user", "list"} - _, err := execOrganizationCmd(cmdArgs...) - assert.EqualError(t, err, "failed to list teams") - mockClient.AssertExpectations(t) - }) - t.Run("any context errors from api are returned and teams are not listed", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.Initial) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - astroCoreClient = mockClient - cmdArgs := []string{"team", "user", "list"} - _, err := execOrganizationCmd(cmdArgs...) - assert.Error(t, err) - mockClient.AssertExpectations(t) - }) -} - -// test organizatio token commands - -var ( - CreateOrganizationAPITokenResponseOK = astrocore.CreateOrganizationApiTokenResponse{ - HTTPResponse: &http.Response{ - StatusCode: 200, - }, - JSON200: &apiToken1, - } - CreateOrganizationAPITokenResponseError = astrocore.CreateOrganizationApiTokenResponse{ - HTTPResponse: &http.Response{ - StatusCode: 500, - }, - Body: errorBodyCreate, - JSON200: nil, - } - RotateOrganizationAPITokenResponseOK = astrocore.RotateOrganizationApiTokenResponse{ - HTTPResponse: &http.Response{ - StatusCode: 200, - }, - JSON200: &apiToken1, - } - RotateOrganizationAPITokenResponseError = astrocore.RotateOrganizationApiTokenResponse{ - HTTPResponse: &http.Response{ - StatusCode: 500, - }, - Body: errorTokenRotate, - JSON200: nil, - } - DeleteOrganizationAPITokenResponseOK = astrocore.DeleteOrganizationApiTokenResponse{ - HTTPResponse: &http.Response{ - StatusCode: 200, - }, - } - DeleteOrganizationAPITokenResponseError = astrocore.DeleteOrganizationApiTokenResponse{ - HTTPResponse: &http.Response{ - StatusCode: 500, - }, - Body: errorTokenDelete, - } -) - -func TestOrganizationTokenRootCommand(t *testing.T) { - testUtil.InitTestConfig(testUtil.LocalPlatform) - buf := new(bytes.Buffer) - cmd := newOrganizationCmd(os.Stdout) - cmd.SetOut(buf) - cmdArgs := []string{"token", "-h"} - _, err := execOrganizationCmd(cmdArgs...) - assert.NoError(t, err) -} - -func TestOrganizationTokenList(t *testing.T) { - expectedHelp := "List all the API tokens in an Astro Organization" - testUtil.InitTestConfig(testUtil.LocalPlatform) - - t.Run("-h prints list help", func(t *testing.T) { - cmdArgs := []string{"token", "list", "-h"} - resp, err := execOrganizationCmd(cmdArgs...) - assert.NoError(t, err) - assert.Contains(t, resp, expectedHelp) - }) - t.Run("any errors from api are returned and tokens are not listed", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.LocalPlatform) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListOrganizationApiTokensWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&ListOrganizationAPITokensResponseError, nil).Once() - astroCoreClient = mockClient - cmdArgs := []string{"token", "list"} - _, err := execOrganizationCmd(cmdArgs...) - assert.ErrorContains(t, err, "failed to list tokens") - mockClient.AssertExpectations(t) - }) - t.Run("any context errors from api are returned and tokens are not listed", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.Initial) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - astroCoreClient = mockClient - cmdArgs := []string{"token", "list"} - _, err := execOrganizationCmd(cmdArgs...) - assert.Error(t, err) - mockClient.AssertExpectations(t) - }) - - t.Run("tokens are listed", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.LocalPlatform) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListOrganizationApiTokensWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&ListOrganizationAPITokensResponseOK, nil).Once() - astroCoreClient = mockClient - cmdArgs := []string{"token", "list"} - _, err := execOrganizationCmd(cmdArgs...) - assert.NoError(t, err) - mockClient.AssertExpectations(t) - }) -} - -func TestOrganizationTokenCreate(t *testing.T) { - expectedHelp := "Create an API token in an Astro Organization" - testUtil.InitTestConfig(testUtil.LocalPlatform) - - t.Run("-h prints list help", func(t *testing.T) { - cmdArgs := []string{"token", "create", "-h"} - resp, err := execOrganizationCmd(cmdArgs...) - assert.NoError(t, err) - assert.Contains(t, resp, expectedHelp) - }) - t.Run("any errors from api are returned and token is not created", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.LocalPlatform) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("CreateOrganizationApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&CreateOrganizationAPITokenResponseError, nil) - astroCoreClient = mockClient - cmdArgs := []string{"token", "create", "--name", "Token 1", "--role", "ORGANIZATION_MEMBER"} - _, err := execOrganizationCmd(cmdArgs...) - assert.ErrorContains(t, err, "failed to create workspace") - mockClient.AssertExpectations(t) - }) - t.Run("any context errors from api are returned and token is not created", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.Initial) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - astroCoreClient = mockClient - cmdArgs := []string{"token", "list"} - _, err := execOrganizationCmd(cmdArgs...) - assert.Error(t, err) - mockClient.AssertExpectations(t) - }) - t.Run("token is created", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.LocalPlatform) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("CreateOrganizationApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&CreateOrganizationAPITokenResponseOK, nil) - astroCoreClient = mockClient - cmdArgs := []string{"token", "create", "--name", "Token 1", "--role", "ORGANIZATION_MEMBER"} - _, err := execOrganizationCmd(cmdArgs...) - assert.NoError(t, err) - mockClient.AssertExpectations(t) - }) - t.Run("token is created with no name provided", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.LocalPlatform) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("CreateOrganizationApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&CreateOrganizationAPITokenResponseOK, nil) - // mock os.Stdin - expectedInput := []byte("Token 1") - r, w, err := os.Pipe() - assert.NoError(t, err) - _, err = w.Write(expectedInput) - assert.NoError(t, err) - w.Close() - stdin := os.Stdin - // Restore stdin right after the test. - defer func() { os.Stdin = stdin }() - os.Stdin = r - astroCoreClient = mockClient - cmdArgs := []string{"token", "create", "--role", "ORGANIZATION_MEMBER"} - _, err = execOrganizationCmd(cmdArgs...) - assert.NoError(t, err) - mockClient.AssertExpectations(t) - }) - t.Run("token is created with no role provided", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.LocalPlatform) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("CreateOrganizationApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&CreateOrganizationAPITokenResponseOK, nil) - // mock os.Stdin - expectedInput := []byte("1") - r, w, err := os.Pipe() - assert.NoError(t, err) - _, err = w.Write(expectedInput) - assert.NoError(t, err) - w.Close() - stdin := os.Stdin - // Restore stdin right after the test. - defer func() { os.Stdin = stdin }() - os.Stdin = r - astroCoreClient = mockClient - cmdArgs := []string{"token", "create", "--name", "Token 1"} - _, err = execOrganizationCmd(cmdArgs...) - assert.NoError(t, err) - mockClient.AssertExpectations(t) - }) -} - -func TestOrganizationTokenUpdate(t *testing.T) { - tokenID = "" - expectedHelp := "Update a Organization or Organaization API token" - testUtil.InitTestConfig(testUtil.LocalPlatform) - - t.Run("-h prints list help", func(t *testing.T) { - cmdArgs := []string{"token", "update", "-h"} - resp, err := execOrganizationCmd(cmdArgs...) - assert.NoError(t, err) - assert.Contains(t, resp, expectedHelp) - }) - t.Run("any errors from api are returned and token is not updated", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.LocalPlatform) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListOrganizationApiTokensWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&ListOrganizationAPITokensResponseOK, nil) - mockClient.On("UpdateOrganizationApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&UpdateOrganizationAPITokenResponseError, nil) - astroCoreClient = mockClient - - mockIamClient := new(astroiamcore_mocks.ClientWithResponsesInterface) - mockIamClient.On("GetApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&GetAPITokensResponseOKOrganizationToken, nil) - astroCoreIamClient = mockIamClient - - cmdArgs := []string{"token", "update", "--name", tokenName1} - _, err := execOrganizationCmd(cmdArgs...) - assert.ErrorContains(t, err, "failed to update token") - mockClient.AssertExpectations(t) - }) - t.Run("any context errors from api are returned and token is not updated", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.Initial) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - astroCoreClient = mockClient - cmdArgs := []string{"token", "update", "--name", tokenName1} - _, err := execOrganizationCmd(cmdArgs...) - assert.Error(t, err) - mockClient.AssertExpectations(t) - }) - t.Run("token is updated -- name provided", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.LocalPlatform) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListOrganizationApiTokensWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&ListOrganizationAPITokensResponseOK, nil) - mockClient.On("UpdateOrganizationApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&UpdateOrganizationAPITokenResponseOK, nil) - astroCoreClient = mockClient - cmdArgs := []string{"token", "update", "--name", tokenName1} - _, err := execOrganizationCmd(cmdArgs...) - assert.NoError(t, err) - mockClient.AssertExpectations(t) - }) - t.Run("token is created with no ID provided", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.LocalPlatform) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListOrganizationApiTokensWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&ListOrganizationAPITokensResponseOK, nil) - mockClient.On("UpdateOrganizationApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&UpdateOrganizationAPITokenResponseOK, nil) - // mock os.Stdin - expectedInput := []byte("1") - r, w, err := os.Pipe() - assert.NoError(t, err) - _, err = w.Write(expectedInput) - assert.NoError(t, err) - w.Close() - stdin := os.Stdin - // Restore stdin right after the test. - defer func() { os.Stdin = stdin }() - os.Stdin = r - astroCoreClient = mockClient - cmdArgs := []string{"token", "update"} - _, err = execOrganizationCmd(cmdArgs...) - assert.NoError(t, err) - mockClient.AssertExpectations(t) - }) -} - -func TestOrganizationTokenRotate(t *testing.T) { - expectedHelp := "Rotate a Organization API token" - testUtil.InitTestConfig(testUtil.LocalPlatform) - - t.Run("-h prints list help", func(t *testing.T) { - cmdArgs := []string{"token", "rotate", "-h"} - resp, err := execOrganizationCmd(cmdArgs...) - assert.NoError(t, err) - assert.Contains(t, resp, expectedHelp) - }) - t.Run("any errors from api are returned and token is not rotated", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.LocalPlatform) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListOrganizationApiTokensWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&ListOrganizationAPITokensResponseOK, nil) - mockClient.On("RotateOrganizationApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&RotateOrganizationAPITokenResponseError, nil) - astroCoreClient = mockClient - cmdArgs := []string{"token", "rotate", "--name", tokenName1, "--force"} - _, err := execOrganizationCmd(cmdArgs...) - assert.ErrorContains(t, err, "failed to rotate token") - mockClient.AssertExpectations(t) - }) - t.Run("any context errors from api are returned and token is not rotated", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.Initial) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - astroCoreClient = mockClient - cmdArgs := []string{"token", "rotate", "--name", tokenName1} - _, err := execOrganizationCmd(cmdArgs...) - assert.Error(t, err) - mockClient.AssertExpectations(t) - }) - t.Run("token is rotated", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.LocalPlatform) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListOrganizationApiTokensWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&ListOrganizationAPITokensResponseOK, nil) - mockClient.On("RotateOrganizationApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&RotateOrganizationAPITokenResponseOK, nil) - astroCoreClient = mockClient - cmdArgs := []string{"token", "rotate", "--name", tokenName1, "--force"} - _, err := execOrganizationCmd(cmdArgs...) - assert.NoError(t, err) - mockClient.AssertExpectations(t) - }) - t.Run("token is rotated with no ID provided", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.LocalPlatform) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListOrganizationApiTokensWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&ListOrganizationAPITokensResponseOK, nil) - mockClient.On("RotateOrganizationApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&RotateOrganizationAPITokenResponseOK, nil) - // mock os.Stdin - expectedInput := []byte("1") - r, w, err := os.Pipe() - assert.NoError(t, err) - _, err = w.Write(expectedInput) - assert.NoError(t, err) - w.Close() - stdin := os.Stdin - // Restore stdin right after the test. - defer func() { os.Stdin = stdin }() - os.Stdin = r - astroCoreClient = mockClient - cmdArgs := []string{"token", "rotate", "--force"} - _, err = execOrganizationCmd(cmdArgs...) - assert.NoError(t, err) - mockClient.AssertExpectations(t) - }) - t.Run("token is rotated with and confirmed", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.LocalPlatform) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListOrganizationApiTokensWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&ListOrganizationAPITokensResponseOK, nil) - mockClient.On("RotateOrganizationApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&RotateOrganizationAPITokenResponseOK, nil) - // mock os.Stdin - expectedInput := []byte("y") - r, w, err := os.Pipe() - assert.NoError(t, err) - _, err = w.Write(expectedInput) - assert.NoError(t, err) - w.Close() - stdin := os.Stdin - // Restore stdin right after the test. - defer func() { os.Stdin = stdin }() - os.Stdin = r - astroCoreClient = mockClient - cmdArgs := []string{"token", "rotate", "--name", tokenName1} - _, err = execOrganizationCmd(cmdArgs...) - assert.NoError(t, err) - mockClient.AssertExpectations(t) - }) -} - -func TestOrganizationTokenDelete(t *testing.T) { - expectedHelp := "Delete a Organization API token or remove an Organization API token from a Organization" - testUtil.InitTestConfig(testUtil.LocalPlatform) - - t.Run("-h prints list help", func(t *testing.T) { - cmdArgs := []string{"token", "delete", "-h"} - resp, err := execOrganizationCmd(cmdArgs...) - assert.NoError(t, err) - assert.Contains(t, resp, expectedHelp) - }) - t.Run("any errors from api are returned and token is not deleted", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.LocalPlatform) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListOrganizationApiTokensWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&ListOrganizationAPITokensResponseOK, nil) - mockClient.On("DeleteOrganizationApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&DeleteOrganizationAPITokenResponseError, nil) - astroCoreClient = mockClient - cmdArgs := []string{"token", "delete", "--name", tokenName1, "--force"} - _, err := execOrganizationCmd(cmdArgs...) - assert.ErrorContains(t, err, "failed to delete token") - mockClient.AssertExpectations(t) - }) - t.Run("any context errors from api are returned and token is not deleted", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.Initial) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - astroCoreClient = mockClient - cmdArgs := []string{"token", "delete", "--name", tokenName1} - _, err := execOrganizationCmd(cmdArgs...) - assert.Error(t, err) - mockClient.AssertExpectations(t) - }) - t.Run("token is deleted", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.LocalPlatform) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListOrganizationApiTokensWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&ListOrganizationAPITokensResponseOK, nil) - mockClient.On("DeleteOrganizationApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&DeleteOrganizationAPITokenResponseOK, nil) - astroCoreClient = mockClient - cmdArgs := []string{"token", "delete", "--name", tokenName1, "--force"} - _, err := execOrganizationCmd(cmdArgs...) - assert.NoError(t, err) - mockClient.AssertExpectations(t) - }) - t.Run("token is deleted with no ID provided", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.LocalPlatform) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListOrganizationApiTokensWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&ListOrganizationAPITokensResponseOK, nil) - mockClient.On("DeleteOrganizationApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&DeleteOrganizationAPITokenResponseOK, nil) - // mock os.Stdin - expectedInput := []byte("1") - r, w, err := os.Pipe() - assert.NoError(t, err) - _, err = w.Write(expectedInput) - assert.NoError(t, err) - w.Close() - stdin := os.Stdin - // Restore stdin right after the test. - defer func() { os.Stdin = stdin }() - os.Stdin = r - astroCoreClient = mockClient - cmdArgs := []string{"token", "delete", "--force"} - _, err = execOrganizationCmd(cmdArgs...) - assert.NoError(t, err) - mockClient.AssertExpectations(t) - }) - t.Run("token is delete with and confirmed", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.LocalPlatform) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListOrganizationApiTokensWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&ListOrganizationAPITokensResponseOK, nil) - mockClient.On("DeleteOrganizationApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&DeleteOrganizationAPITokenResponseOK, nil) - // mock os.Stdin - expectedInput := []byte("y") - r, w, err := os.Pipe() - assert.NoError(t, err) - _, err = w.Write(expectedInput) - assert.NoError(t, err) - w.Close() - stdin := os.Stdin - // Restore stdin right after the test. - defer func() { os.Stdin = stdin }() - os.Stdin = r - astroCoreClient = mockClient - cmdArgs := []string{"token", "delete", "--name", tokenName1} - _, err = execOrganizationCmd(cmdArgs...) - assert.NoError(t, err) - mockClient.AssertExpectations(t) - }) -} - -func TestOrganizationTokenListRoles(t *testing.T) { - expectedHelp := "List roles for an organization API token" - mockTokenID := "mockTokenID" - testUtil.InitTestConfig(testUtil.LocalPlatform) - - t.Run("-h prints list help", func(t *testing.T) { - cmdArgs := []string{"token", "roles", "-h"} - resp, err := execOrganizationCmd(cmdArgs...) - assert.NoError(t, err) - assert.Contains(t, resp, expectedHelp) - }) - t.Run("any errors from api are returned and token roles are not listed", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.LocalPlatform) - - mockIamClient := new(astroiamcore_mocks.ClientWithResponsesInterface) - mockIamClient.On("GetApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&GetAPITokensResponseError, nil) - astroCoreIamClient = mockIamClient - - cmdArgs := []string{"token", "roles", mockTokenID} - _, err := execOrganizationCmd(cmdArgs...) - assert.ErrorContains(t, err, "failed to get token") - mockIamClient.AssertExpectations(t) - }) - t.Run("any context errors from api are returned and token roles are not listed", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.Initial) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - astroCoreClient = mockClient - cmdArgs := []string{"token", "roles", mockTokenID} - _, err := execOrganizationCmd(cmdArgs...) - assert.Error(t, err) - mockClient.AssertExpectations(t) - }) - - t.Run("token roles are listed", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.LocalPlatform) - - mockIamClient := new(astroiamcore_mocks.ClientWithResponsesInterface) - mockIamClient.On("GetApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&GetAPITokensResponseOKOrganizationToken, nil) - astroCoreIamClient = mockIamClient - cmdArgs := []string{"token", "roles", mockTokenID} - _, err := execOrganizationCmd(cmdArgs...) - assert.NoError(t, err) - mockIamClient.AssertExpectations(t) - }) -} - -var ( - role1 = astrocore.Role{ - Name: "role 1", - Description: &description, - Id: "role1-id", - } - role2 = astrocore.Role{ - Name: "role 2", - Description: &description, - Id: "role2-id", - } - roles = []astrocore.Role{ - role1, - role2, - } - defaultRole1 = astrocore.DefaultRole{ - Name: "default role 1", - Description: &description, - } - defaultRole2 = astrocore.DefaultRole{ - Name: "default role 2", - Description: &description, - } - defaultRoles = []astrocore.DefaultRole{ - defaultRole1, - defaultRole2, - } - ListRolesResponseOK = astrocore.ListRolesResponse{ - HTTPResponse: &http.Response{ - StatusCode: 200, - }, - JSON200: &astrocore.RolesPaginated{ - Limit: 1, - Offset: 0, - TotalCount: 1, - Roles: roles, - DefaultRoles: &defaultRoles, - }, - } -) - -func TestListOrganizationRoles(t *testing.T) { - expectedHelp := "organization role list [flags]" - testUtil.InitTestConfig(testUtil.LocalPlatform) - - t.Run("-h prints invite help", func(t *testing.T) { - cmdArgs := []string{"role", "list", "-h"} - resp, err := execOrganizationCmd(cmdArgs...) - assert.NoError(t, err) - assert.Contains(t, resp, expectedHelp) - }) - t.Run("can list org roles", func(t *testing.T) { - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListRolesWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&ListRolesResponseOK, nil).Twice() - astroCoreClient = mockClient - cmdArgs := []string{"role", "list"} - _, err := execOrganizationCmd(cmdArgs...) - assert.NoError(t, err) - mockClient.AssertExpectations(t) - }) -} diff --git a/cmd/cloud/workspace.go b/cmd/cloud/workspace.go index 818564337..c8a1dd68b 100644 --- a/cmd/cloud/workspace.go +++ b/cmd/cloud/workspace.go @@ -1,55 +1,17 @@ package cloud import ( - "fmt" "io" - "os" - "strconv" - "strings" - astrocore "github.com/astronomer/astro-cli/astro-client-core" - "github.com/astronomer/astro-cli/cloud/organization" - "github.com/astronomer/astro-cli/cloud/team" - "github.com/astronomer/astro-cli/cloud/user" "github.com/astronomer/astro-cli/cloud/workspace" - workspacetoken "github.com/astronomer/astro-cli/cloud/workspace-token" - "github.com/astronomer/astro-cli/pkg/input" - "github.com/astronomer/astro-cli/pkg/printutil" - "github.com/pkg/errors" "github.com/spf13/cobra" ) var ( - errInvalidWorkspaceRoleKey = errors.New("invalid workspace role selection") - workspaceID string - addWorkspaceRole string - updateWorkspaceRole string - workspaceName string - workspaceDescription string - enforceCD string - tokenName string - tokenDescription string - tokenRole string - orgTokenName string - tokenID string - orgTokenID string - workspaceTokenID string - cleanTokenOutput bool - forceRotate bool - tokenExpiration int - validWorkspaceRoles []string -) - -const ( - allowedWorkspaceRoleNames = "WORKSPACE_MEMBER, WORKSPACE_AUTHOR, WORKSPACE_OPERATOR, WORKSPACE_OWNER" - allowedWorkspaceRoleNamesProse = "WORKSPACE_MEMBER, WORKSPACE_AUTHOR, WORKSPACE_OPERATOR, and WORKSPACE_OWNER" + workspaceID string ) -func init() { - validWorkspaceRoles = []string{"WORKSPACE_MEMBER", "WORKSPACE_AUTHOR", "WORKSPACE_OPERATOR", "WORKSPACE_OWNER"} -} - func newWorkspaceCmd(out io.Writer) *cobra.Command { cmd := &cobra.Command{ Use: "workspace", @@ -58,31 +20,11 @@ func newWorkspaceCmd(out io.Writer) *cobra.Command { Long: "Create and manage Workspaces on Astro. Workspaces can contain multiple Deployments and can be shared across users.", } cmd.AddCommand( - newWorkspaceListCmd(out), newWorkspaceSwitchCmd(out), - newWorkspaceCreateCmd(out), - newWorkspaceUpdateCmd(out), - newWorkspaceDeleteCmd(out), - newWorkspaceUserRootCmd(out), - newWorkspaceTokenRootCmd(out), - newWorkspaceTeamRootCmd(out), ) return cmd } -func newWorkspaceListCmd(out io.Writer) *cobra.Command { - cmd := &cobra.Command{ - Use: "list", - Aliases: []string{"ls"}, - Short: "List all Astro Workspaces in your organization", - Long: "List all Astro Workspaces in your organization.", - RunE: func(cmd *cobra.Command, args []string) error { - return workspaceList(cmd, out) - }, - } - return cmd -} - func newWorkspaceSwitchCmd(out io.Writer) *cobra.Command { cmd := &cobra.Command{ Use: "switch [workspace name/id]", @@ -97,507 +39,7 @@ func newWorkspaceSwitchCmd(out io.Writer) *cobra.Command { return cmd } -func newWorkspaceCreateCmd(out io.Writer) *cobra.Command { - cmd := &cobra.Command{ - Use: "create", - Aliases: []string{"cr"}, - Short: "Create an Astro Workspace", - Long: "Create an Astro Workspace", - RunE: func(cmd *cobra.Command, args []string) error { - return workspaceCreate(cmd, out) - }, - } - cmd.Flags().StringVarP(&workspaceName, "name", "n", "", "The Workspace's name. If the name contains a space, specify the entire name within quotes \"\" ") - cmd.Flags().StringVarP(&workspaceDescription, "description", "d", "", "Description of the Workspace. If the description contains a space, specify the entire description in quotes \"\"") - cmd.Flags().StringVarP(&enforceCD, "enforce-cicd", "e", "OFF", "Provide this flag either ON/OFF. ON means deploys to deployments must use an API Key or Token. This essentially forces Deploys to happen through CI/CD") - return cmd -} - -func newWorkspaceUpdateCmd(out io.Writer) *cobra.Command { - cmd := &cobra.Command{ - Use: "update [workspace_id]", - Aliases: []string{"up"}, - Short: "Update an Astro Workspace", - Long: "Update an Astro Workspace", - Args: cobra.MaximumNArgs(1), - RunE: func(cmd *cobra.Command, args []string) error { - return workspaceUpdate(cmd, out, args) - }, - } - cmd.Flags().StringVarP(&workspaceName, "name", "n", "", "The Workspace's name. If the name contains a space, specify the entire name within quotes \"\" ") - cmd.Flags().StringVarP(&workspaceDescription, "description", "d", "", "Description of the Workspace. If the description contains a space, specify the entire description in quotes \"\"") - cmd.Flags().StringVarP(&enforceCD, "enforce-cicd", "e", "OFF", "Provide this flag either ON/OFF. ON means deploys to deployments must use an API Key or Token. This essentially forces Deploys to happen through CI/CD") - return cmd -} - -func newWorkspaceDeleteCmd(out io.Writer) *cobra.Command { - cmd := &cobra.Command{ - Use: "delete [workspace_id]", - Aliases: []string{"de"}, - Short: "Delete an Astro Workspace", - Long: "Delete an Astro Workspace", - Args: cobra.MaximumNArgs(1), - RunE: func(cmd *cobra.Command, args []string) error { - return workspaceDelete(cmd, out, args) - }, - } - return cmd -} - -func newWorkspaceUserRootCmd(out io.Writer) *cobra.Command { - cmd := &cobra.Command{ - Use: "user", - Aliases: []string{"us", "users"}, - Short: "Manage users in your Astro Workspace", - Long: "Manage users in your Astro Workspace.", - } - cmd.SetOut(out) - cmd.AddCommand( - newWorkspaceUserListCmd(out), - newWorkspaceUserUpdateCmd(out), - newWorkspaceUserRemoveCmd(out), - newWorkspaceUserAddCmd(out), - ) - cmd.PersistentFlags().StringVar(&workspaceID, "workspace-id", "", "workspace where you'd like to manage users") - - return cmd -} - -func newWorkspaceUserAddCmd(out io.Writer) *cobra.Command { - cmd := &cobra.Command{ - Use: "add [email]", - Short: "Add a user to an Astro Workspace with a specific role", - Long: "Add a user to an Astro Workspace with a specific role\n$astro workspace user add [email] --role [" + allowedWorkspaceRoleNames + "].", - RunE: func(cmd *cobra.Command, args []string) error { - return addWorkspaceUser(cmd, args, out) - }, - } - cmd.Flags().StringVarP(&addWorkspaceRole, "role", "r", "WORKSPACE_MEMBER", "The role for the "+ - "new user. Possible values are "+allowedWorkspaceRoleNamesProse) - return cmd -} - -func newWorkspaceUserListCmd(out io.Writer) *cobra.Command { - cmd := &cobra.Command{ - Use: "list", - Aliases: []string{"ls"}, - Short: "List all the users in an Astro Workspace", - Long: "List all the users in an Astro Workspace", - RunE: func(cmd *cobra.Command, args []string) error { - return listWorkspaceUser(cmd, out) - }, - } - return cmd -} - -func newWorkspaceUserUpdateCmd(out io.Writer) *cobra.Command { - cmd := &cobra.Command{ - Use: "update [email]", - Aliases: []string{"up"}, - Short: "Update a the role of a user in an Astro Workspace", - Long: "Update the role of a user in an Astro Workspace\n$astro workspace user update [email] --role [" + allowedWorkspaceRoleNames + "].", - RunE: func(cmd *cobra.Command, args []string) error { - return updateWorkspaceUser(cmd, args, out) - }, - } - cmd.Flags().StringVarP(&updateWorkspaceRole, "role", "r", "", "The new role for the "+ - "user. Possible values are "+allowedWorkspaceRoleNamesProse) - return cmd -} - -func newWorkspaceUserRemoveCmd(out io.Writer) *cobra.Command { - cmd := &cobra.Command{ - Use: "remove", - Aliases: []string{"rm"}, - Short: "Remove a user from an Astro Workspace", - Long: "Remove a user from an Astro Workspace", - RunE: func(cmd *cobra.Command, args []string) error { - return removeWorkspaceUser(cmd, args, out) - }, - } - return cmd -} - -//nolint:dupl -func newWorkspaceTokenRootCmd(out io.Writer) *cobra.Command { - cmd := &cobra.Command{ - Use: "token", - Aliases: []string{"to"}, - Short: "Manage tokens in your Astro Workspace", - Long: "Manage tokens in your Astro Workspace.", - } - cmd.SetOut(out) - cmd.AddCommand( - newWorkspaceTokenListCmd(out), - newWorkspaceTokenCreateCmd(out), - newWorkspaceTokenUpdateCmd(out), - newWorkspaceTokenRotateCmd(out), - newWorkspaceTokenDeleteCmd(out), - newWorkspaceTokenAddOrgTokenCmd(out), - newWorkspaceOrgTokenManageCmd(out), - ) - cmd.PersistentFlags().StringVar(&workspaceID, "workspace-id", "", "workspace where you would like to manage tokens") - return cmd -} - -//nolint:dupl -func newWorkspaceTokenListCmd(out io.Writer) *cobra.Command { - cmd := &cobra.Command{ - Use: "list", - Aliases: []string{"ls"}, - Short: "List all the API tokens in an Astro Workspace", - Long: "List all the API tokens in an Astro Workspace", - RunE: func(cmd *cobra.Command, args []string) error { - return listWorkspaceToken(cmd, out) - }, - } - return cmd -} - -//nolint:dupl -func newWorkspaceTeamRootCmd(out io.Writer) *cobra.Command { - cmd := &cobra.Command{ - Use: "team", - Aliases: []string{"te", "teams"}, - Short: "Manage teams in your Astro Workspace", - Long: "Manage teams in your Astro Workspace.", - } - cmd.SetOut(out) - cmd.AddCommand( - newWorkspaceTeamListCmd(out), - newWorkspaceTeamUpdateCmd(out), - newWorkspaceTeamRemoveCmd(out), - newWorkspaceTeamAddCmd(out), - ) - return cmd -} - -//nolint:dupl -func newWorkspaceTeamListCmd(out io.Writer) *cobra.Command { - cmd := &cobra.Command{ - Use: "list", - Aliases: []string{"ls"}, - Short: "List all the teams in an Astro Workspace", - Long: "List all the teams in an Astro Workspace", - RunE: func(cmd *cobra.Command, args []string) error { - return listWorkspaceTeam(cmd, out) - }, - } - return cmd -} - -//nolint:dupl -func newWorkspaceTokenCreateCmd(out io.Writer) *cobra.Command { - cmd := &cobra.Command{ - Use: "create", - Aliases: []string{"cr"}, - Short: "Create an API token in an Astro Workspace", - Long: "Create an API token in an Astro Workspace\n$astro workspace token create --name [token name] --role [" + allowedWorkspaceRoleNames + "].", - RunE: func(cmd *cobra.Command, args []string) error { - return createWorkspaceToken(cmd, out) - }, - } - cmd.Flags().StringVarP(&tokenName, "name", "n", "", "The token's name. If the name contains a space, specify the entire name within quotes \"\" ") - cmd.Flags().BoolVarP(&cleanTokenOutput, "clean-output", "c", false, "Print only the token as output. For use of the command in scripts") - cmd.Flags().StringVarP(&tokenDescription, "description", "d", "", "Description of the token. If the description contains a space, specify the entire description within quotes \"\"") - cmd.Flags().StringVarP(&tokenRole, "role", "r", "", "The role for the "+ - "token. Possible values are "+allowedWorkspaceRoleNamesProse) - cmd.Flags().IntVarP(&tokenExpiration, "expiration", "e", 0, "Expiration of the token in days. If the flag isn't used the token won't have an expiration. Must be between 1 and 3650 days. ") - return cmd -} - -//nolint:dupl -func newWorkspaceTokenUpdateCmd(out io.Writer) *cobra.Command { - cmd := &cobra.Command{ - Use: "update [TOKEN_ID]", - Aliases: []string{"up"}, - Short: "Update a Workspace or Organaization API token", - Long: "Update a Workspace or Organaization API token that has a role in an Astro Workspace\n$astro workspace token update [TOKEN_ID] --name [new token name] --role [" + allowedWorkspaceRoleNames + "].", - RunE: func(cmd *cobra.Command, args []string) error { - return updateWorkspaceToken(cmd, args, out) - }, - } - cmd.Flags().StringVarP(&name, "name", "t", "", "The current name of the token. If the name contains a space, specify the entire name within quotes \"\" ") - cmd.Flags().StringVarP(&tokenName, "new-name", "n", "", "The token's new name. If the name contains a space, specify the entire name within quotes \"\" ") - cmd.Flags().StringVarP(&tokenDescription, "description", "d", "", "updated description of the token. If the description contains a space, specify the entire description in quotes \"\"") - cmd.Flags().StringVarP(&tokenRole, "role", "r", "", "The new role for the "+ - "token. Possible values are "+allowedWorkspaceRoleNamesProse) - return cmd -} - -//nolint:dupl -func newWorkspaceTokenRotateCmd(out io.Writer) *cobra.Command { - cmd := &cobra.Command{ - Use: "rotate [TOKEN_ID]", - Aliases: []string{"ro"}, - Short: "Rotate a Workspace API token", - Long: "Rotate a Workspace API token. You can only rotate Workspace API tokens. You cannot rotate Organization API tokens with this command", - RunE: func(cmd *cobra.Command, args []string) error { - return rotateWorkspaceToken(cmd, args, out) - }, - } - cmd.Flags().BoolVarP(&cleanTokenOutput, "clean-output", "c", false, "Print only the token as output. For use of the command in scripts") - cmd.Flags().StringVarP(&name, "name", "t", "", "The name of the token to be rotated. If the name contains a space, specify the entire name within quotes \"\" ") - cmd.Flags().BoolVarP(&forceRotate, "force", "f", false, "Rotate the Workspace API token without showing a warning") - - return cmd -} - -//nolint:dupl -func newWorkspaceTokenDeleteCmd(out io.Writer) *cobra.Command { - cmd := &cobra.Command{ - Use: "delete [TOKEN_ID]", - Aliases: []string{"de"}, - Short: "Delete a Workspace API token or remove an Organization API token from a Workspace", - Long: "Delete a Workspace API token or remove an Organization API token from a Workspace", - RunE: func(cmd *cobra.Command, args []string) error { - return deleteWorkspaceToken(cmd, args, out) - }, - } - cmd.Flags().StringVarP(&name, "name", "t", "", "The name of the token to be deleted. If the name contains a space, specify the entire name within quotes \"\" ") - cmd.Flags().BoolVarP(&forceDelete, "force", "f", false, "Delete or remove the API token without showing a warning") - - return cmd -} - -//nolint:dupl -func newWorkspaceTokenAddOrgTokenCmd(out io.Writer) *cobra.Command { - cmd := &cobra.Command{ - Use: "add [ORG_TOKEN_ID]", - Short: "Add an Organization API token to an Astro Workspace", - Long: "Add an Organization API token to an Astro Workspace\n$astro workspace token add [ORG_TOKEN_ID] --org-token-name [token name] --role [" + allowedWorkspaceRoleNames + "].", - RunE: func(cmd *cobra.Command, args []string) error { - return addOrgTokenToWorkspace(cmd, args, out) - }, - } - cmd.Flags().StringVarP(&orgTokenName, "org-token-name", "n", "", "The name of the Organization API token you want to add to a Workspace. If the name contains a space, specify the entire name within quotes \"\" ") - cmd.Flags().StringVarP(&tokenRole, "role", "r", "", "The Workspace role to grant to the "+ - "Organization API token. Possible values are "+allowedWorkspaceRoleNamesProse) - return cmd -} - -//nolint:dupl -func newWorkspaceOrgTokenManageCmd(out io.Writer) *cobra.Command { - cmd := &cobra.Command{ - Use: "organization-token", - Short: "Manage organization tokens in a workspace", - Long: "Manage organization tokens in a workspace", - } - cmd.SetOut(out) - cmd.AddCommand( - newAddOrganizationTokenWorkspaceRole(out), - newUpdateOrganizationTokenWorkspaceRole(out), - newRemoveOrganizationTokenWorkspaceRole(out), - newListOrganizationTokensInWorkspace(out), - ) - return cmd -} - -func newAddOrganizationTokenWorkspaceRole(out io.Writer) *cobra.Command { - cmd := &cobra.Command{ - Use: "add [ORG_TOKEN_ID]", - Short: "Add an Organization API token to a Workspace", - Long: "Add an Organization API token to a Workspace\n$astro workspace token organization-token add [ORG_TOKEN_ID] --org-token-name [token name] --role [" + allowedWorkspaceRoleNames + "].", - RunE: func(cmd *cobra.Command, args []string) error { - return addOrgTokenWorkspaceRole(cmd, args, out) - }, - } - cmd.Flags().StringVarP(&orgTokenName, "org-token-name", "n", "", "The name of the Organization API token you want to add to a Workspace. If the name contains a space, specify the entire name within quotes \"\" ") - cmd.Flags().StringVarP(&tokenRole, "role", "r", "", "The Workspace role to grant to the "+ - "Organization API token. Possible values are"+allowedWorkspaceRoleNamesProse) - return cmd -} - -func newUpdateOrganizationTokenWorkspaceRole(out io.Writer) *cobra.Command { - cmd := &cobra.Command{ - Use: "update [ORG_TOKEN_ID]", - Short: "Update an Organization API token's Workspace Role", - Long: "Update an Organization API token's Workspace Role\n$astro workspace token organization-token update [ORG_TOKEN_ID] --org-token-name [token name] --role [" + allowedWorkspaceRoleNames + "].", - RunE: func(cmd *cobra.Command, args []string) error { - return updateOrgTokenWorkspaceRole(cmd, args, out) - }, - } - cmd.Flags().StringVarP(&orgTokenName, "org-token-name", "n", "", "The name of the Organization API token you want to update in a Workspace. If the name contains a space, specify the entire name within quotes \"\" ") - cmd.Flags().StringVarP(&tokenRole, "role", "r", "", "The Workspace role to update the "+ - "Organization API token. Possible values are"+allowedWorkspaceRoleNamesProse) - return cmd -} - -func addOrgTokenWorkspaceRole(cmd *cobra.Command, args []string, out io.Writer) error { - // if an id was provided in the args we use it - if len(args) > 0 { - // make sure the id is lowercase - orgTokenID = strings.ToLower(args[0]) - } - if tokenRole == "" { - // no role was provided so ask the user for it - tokenRole = input.Text("Enter a role for the API token. Possible values are " + allowedWorkspaceRoleNamesProse + ": ") - } - cmd.SilenceUsage = true - - return workspacetoken.UpsertOrgTokenWorkspaceRole(orgTokenID, orgTokenName, tokenRole, workspaceID, "create", out, astroCoreClient, astroCoreIamClient) -} - -func updateOrgTokenWorkspaceRole(cmd *cobra.Command, args []string, out io.Writer) error { - // if an id was provided in the args we use it - if len(args) > 0 { - // make sure the id is lowercase - orgTokenID = strings.ToLower(args[0]) - } - if tokenRole == "" { - // no role was provided so ask the user for it - tokenRole = input.Text("Enter a role for the new Workspace API token. Possible values are " + allowedWorkspaceRoleNamesProse + ": ") - } - cmd.SilenceUsage = true - - return workspacetoken.UpsertOrgTokenWorkspaceRole(orgTokenID, orgTokenName, tokenRole, workspaceID, "update", out, astroCoreClient, astroCoreIamClient) -} - -func newRemoveOrganizationTokenWorkspaceRole(out io.Writer) *cobra.Command { - cmd := &cobra.Command{ - Use: "remove [ORG_TOKEN_ID]", - Short: "Remove an Organization API token's Workspace Role", - Long: "Remove an Organization API token's Workspace Role\n$astro workspace token organization-token remove [ORG_TOKEN_ID] --org-token-name [token name].", - RunE: func(cmd *cobra.Command, args []string) error { - return removeOrganizationTokenWorkspaceRole(cmd, args, out) - }, - } - cmd.Flags().StringVarP(&orgTokenName, "org-token-name", "n", "", "The name of the Workspace API token you want to remove from a Deployment. If the name contains a space, specify the entire name within quotes \"\" ") - return cmd -} - -func removeOrganizationTokenWorkspaceRole(cmd *cobra.Command, args []string, out io.Writer) error { - // if an id was provided in the args we use it - if len(args) > 0 { - // make sure the id is lowercase - orgTokenID = strings.ToLower(args[0]) - } - - cmd.SilenceUsage = true - return workspacetoken.RemoveOrgTokenWorkspaceRole(orgTokenID, orgTokenName, workspaceID, out, astroCoreClient, astroCoreIamClient) -} - -func newListOrganizationTokensInWorkspace(out io.Writer) *cobra.Command { - cmd := &cobra.Command{ - Use: "list", - Short: "List all Organization API tokens in a workspace", - Long: "List all Organization API tokens in a workspace\n$astro workspace token organization-token list", - RunE: func(cmd *cobra.Command, args []string) error { - return listOrganizationTokensInWorkspace(cmd, out) - }, - } - return cmd -} - -func listOrganizationTokensInWorkspace(cmd *cobra.Command, out io.Writer) error { - cmd.SilenceUsage = true - tokenTypes := []astrocore.ListWorkspaceApiTokensParamsTokenTypes{ - "ORGANIZATION", - } - return workspacetoken.ListTokens(astroCoreClient, deploymentID, &tokenTypes, out) -} - -func newWorkspaceTeamRemoveCmd(out io.Writer) *cobra.Command { - cmd := &cobra.Command{ - Use: "remove", - Aliases: []string{"rm"}, - Short: "Remove a team from an Astro Workspace", - Long: "Remove a team from an Astro Workspace", - RunE: func(cmd *cobra.Command, args []string) error { - return removeWorkspaceTeam(cmd, args, out) - }, - } - return cmd -} - -func listWorkspaceTeam(cmd *cobra.Command, out io.Writer) error { - cmd.SilenceUsage = true - return team.ListWorkspaceTeams(out, astroCoreClient, "") -} - -func removeWorkspaceTeam(cmd *cobra.Command, args []string, out io.Writer) error { - var id string - - // if an id was provided in the args we use it - if len(args) > 0 { - // make sure the email is lowercase - id = args[0] - } - cmd.SilenceUsage = true - return team.RemoveWorkspaceTeam(id, "", out, astroCoreClient) -} - -func newWorkspaceTeamAddCmd(out io.Writer) *cobra.Command { - cmd := &cobra.Command{ - Use: "add [id]", - Short: "Add a team to an Astro Workspace with a specific role", - Long: "Add a team to an Astro Workspace with a specific role\n$astro workspace team add [id] --role [" + allowedWorkspaceRoleNames + "].", - RunE: func(cmd *cobra.Command, args []string) error { - return addWorkspaceTeam(cmd, args, out) - }, - } - cmd.Flags().StringVarP(&workspaceID, "workspace-id", "w", "", "The Workspace's unique identifier") - cmd.Flags().StringVarP(&addWorkspaceRole, "role", "r", "WORKSPACE_MEMBER", "The role for the "+ - "new team. Possible values are "+allowedWorkspaceRoleNamesProse) - return cmd -} - -func addWorkspaceTeam(cmd *cobra.Command, args []string, out io.Writer) error { - var id string - - // if an email was provided in the args we use it - if len(args) > 0 { - // make sure the email is lowercase - id = args[0] - } - cmd.SilenceUsage = true - return team.AddWorkspaceTeam(id, addWorkspaceRole, workspaceID, out, astroCoreClient) -} - -func newWorkspaceTeamUpdateCmd(out io.Writer) *cobra.Command { - cmd := &cobra.Command{ - Use: "update [id]", - Aliases: []string{"up"}, - Short: "Update a the role of a team in an Astro Workspace", - Long: "Update the role of a team in an Astro Workspace\n$astro workspace team update [id] --role [" + allowedWorkspaceRoleNames + "].", - RunE: func(cmd *cobra.Command, args []string) error { - return updateWorkspaceTeam(cmd, args, out) - }, - } - cmd.Flags().StringVarP(&updateWorkspaceRole, "role", "r", "", "The new role for the "+ - "team. Possible values are "+allowedWorkspaceRoleNamesProse) - return cmd -} - -func updateWorkspaceTeam(cmd *cobra.Command, args []string, out io.Writer) error { - var id string - - // if an id was provided in the args we use it - if len(args) > 0 { - id = args[0] - } - var err error - if updateWorkspaceRole == "" { - // no role was provided so ask the user for it - updateWorkspaceRole, err = selectWorkspaceRole() - if err != nil { - return err - } - } - - cmd.SilenceUsage = true - return team.UpdateWorkspaceTeamRole(id, updateWorkspaceRole, "", out, astroCoreClient) -} - -func workspaceList(cmd *cobra.Command, out io.Writer) error { - // Silence Usage as we have now validated command input - cmd.SilenceUsage = true - return workspace.List(astroCoreClient, out) -} - func workspaceSwitch(cmd *cobra.Command, out io.Writer, args []string) error { - // Silence Usage as we have now validated command input - workspaceNameOrID := "" if len(args) == 1 { @@ -607,155 +49,6 @@ func workspaceSwitch(cmd *cobra.Command, out io.Writer, args []string) error { return workspace.Switch(workspaceNameOrID, astroCoreClient, out) } -func workspaceCreate(cmd *cobra.Command, out io.Writer) error { - cmd.SilenceUsage = true - return workspace.Create(workspaceName, workspaceDescription, enforceCD, out, astroCoreClient) -} - -func workspaceUpdate(cmd *cobra.Command, out io.Writer, args []string) error { - id := "" - - if len(args) == 1 { - id = args[0] - } - cmd.SilenceUsage = true - return workspace.Update(id, workspaceName, workspaceDescription, enforceCD, out, astroCoreClient) -} - -func workspaceDelete(cmd *cobra.Command, out io.Writer, args []string) error { - id := "" - - if len(args) == 1 { - id = args[0] - } - cmd.SilenceUsage = true - return workspace.Delete(id, out, astroCoreClient) -} - -func addWorkspaceUser(cmd *cobra.Command, args []string, out io.Writer) error { - var email string - - // if an email was provided in the args we use it - if len(args) > 0 { - // make sure the email is lowercase - email = strings.ToLower(args[0]) - } - - cmd.SilenceUsage = true - return user.AddWorkspaceUser(email, addWorkspaceRole, workspaceID, out, astroCoreClient) -} - -func listWorkspaceUser(cmd *cobra.Command, out io.Writer) error { - cmd.SilenceUsage = true - return user.ListWorkspaceUsers(out, astroCoreClient, workspaceID) -} - -func updateWorkspaceUser(cmd *cobra.Command, args []string, out io.Writer) error { - var email string - - // if an email was provided in the args we use it - if len(args) > 0 { - // make sure the email is lowercase - email = strings.ToLower(args[0]) - } - - if updateWorkspaceRole == "" { - // no role was provided so ask the user for it - updateWorkspaceRole = input.Text("Enter a user Workspace role(" + allowedWorkspaceRoleNamesProse + ") to update user: ") - } - - cmd.SilenceUsage = true - return user.UpdateWorkspaceUserRole(email, updateWorkspaceRole, workspaceID, out, astroCoreClient) -} - -func removeWorkspaceUser(cmd *cobra.Command, args []string, out io.Writer) error { - var email string - - // if an email was provided in the args we use it - if len(args) > 0 { - // make sure the email is lowercase - email = strings.ToLower(args[0]) - } - - cmd.SilenceUsage = true - return user.RemoveWorkspaceUser(email, workspaceID, out, astroCoreClient) -} - -func listWorkspaceToken(cmd *cobra.Command, out io.Writer) error { - cmd.SilenceUsage = true - return workspacetoken.ListTokens(astroCoreClient, workspaceID, nil, out) -} - -func createWorkspaceToken(cmd *cobra.Command, out io.Writer) error { - if tokenName == "" { - // no role was provided so ask the user for it - tokenName = input.Text("Enter a name for the new Workspace API token: ") - } - if tokenRole == "" { - fmt.Println("select a Workspace Role for the new API token:") - // no role was provided so ask the user for it - var err error - tokenRole, err = selectWorkspaceRole() - if err != nil { - return err - } - } - cmd.SilenceUsage = true - - return workspacetoken.CreateToken(tokenName, tokenDescription, tokenRole, workspaceID, tokenExpiration, cleanTokenOutput, out, astroCoreClient) -} - -func updateWorkspaceToken(cmd *cobra.Command, args []string, out io.Writer) error { - // if an id was provided in the args we use it - if len(args) > 0 { - // make sure the id is lowercase - tokenID = strings.ToLower(args[0]) - } - - cmd.SilenceUsage = true - return workspacetoken.UpdateToken(tokenID, name, tokenName, tokenDescription, tokenRole, workspaceID, out, astroCoreClient, astroCoreIamClient) -} - -func rotateWorkspaceToken(cmd *cobra.Command, args []string, out io.Writer) error { - // if an id was provided in the args we use it - if len(args) > 0 { - // make sure the id is lowercase - tokenID = strings.ToLower(args[0]) - } - cmd.SilenceUsage = true - return workspacetoken.RotateToken(tokenID, name, workspaceID, cleanTokenOutput, forceRotate, out, astroCoreClient, astroCoreIamClient) -} - -func deleteWorkspaceToken(cmd *cobra.Command, args []string, out io.Writer) error { - // if an id was provided in the args we use it - if len(args) > 0 { - // make sure the id is lowercase - tokenID = strings.ToLower(args[0]) - } - - cmd.SilenceUsage = true - return workspacetoken.DeleteToken(tokenID, name, workspaceID, forceDelete, out, astroCoreClient, astroCoreIamClient) -} - -func addOrgTokenToWorkspace(cmd *cobra.Command, args []string, out io.Writer) error { - // if an id was provided in the args we use it - if len(args) > 0 { - // make sure the id is lowercase - orgTokenID = strings.ToLower(args[0]) - } - if tokenRole == "" { - fmt.Println("select a Workspace Role for the Organization Token:") - // no role was provided so ask the user for it - var err error - tokenRole, err = selectWorkspaceRole() - if err != nil { - return err - } - } - cmd.SilenceUsage = true - return organization.AddOrgTokenToWorkspace(orgTokenID, orgTokenName, tokenRole, workspaceID, out, astroCoreClient, astroCoreIamClient) -} - func coalesceWorkspace() (string, error) { wsFlag := workspaceID wsCfg, err := workspace.GetCurrentWorkspace() @@ -773,28 +66,3 @@ func coalesceWorkspace() (string, error) { return "", errors.New("no valid Workspace source found") } - -func selectWorkspaceRole() (string, error) { - tokenRolesMap := map[string]string{} - tab := &printutil.Table{ - Padding: []int{44, 50}, - DynamicPadding: true, - Header: []string{"#", "ROLE"}, - } - for i := range validWorkspaceRoles { - index := i + 1 - tab.AddRow([]string{ - strconv.Itoa(index), - validWorkspaceRoles[i], - }, false) - tokenRolesMap[strconv.Itoa(index)] = validWorkspaceRoles[i] - } - - tab.Print(os.Stdout) - choice := input.Text("\n> ") - selected, ok := tokenRolesMap[choice] - if !ok { - return "", errInvalidWorkspaceRoleKey - } - return selected, nil -} diff --git a/cmd/cloud/workspace_test.go b/cmd/cloud/workspace_test.go index 7308d263d..3ae3b77bd 100644 --- a/cmd/cloud/workspace_test.go +++ b/cmd/cloud/workspace_test.go @@ -2,19 +2,12 @@ package cloud import ( "bytes" - "encoding/json" - "fmt" "net/http" "os" "testing" - "time" astrocore "github.com/astronomer/astro-cli/astro-client-core" astrocore_mocks "github.com/astronomer/astro-cli/astro-client-core/mocks" - astroiamcore "github.com/astronomer/astro-cli/astro-client-iam-core" - astroiamcore_mocks "github.com/astronomer/astro-cli/astro-client-iam-core/mocks" - "github.com/astronomer/astro-cli/cloud/user" - "github.com/astronomer/astro-cli/cloud/workspace" testUtil "github.com/astronomer/astro-cli/pkg/testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" @@ -67,21 +60,6 @@ var ( } ) -func TestWorkspaceList(t *testing.T) { - testUtil.InitTestConfig(testUtil.LocalPlatform) - - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListWorkspacesWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&ListWorkspacesResponseOK, nil).Once() - astroCoreClient = mockClient - - cmdArgs := []string{"list"} - resp, err := execWorkspaceCmd(cmdArgs...) - assert.NoError(t, err) - assert.Contains(t, resp, "workspace-id") - assert.Contains(t, resp, "test-workspace") - mockClient.AssertExpectations(t) -} - func TestWorkspaceSwitch(t *testing.T) { testUtil.InitTestConfig(testUtil.LocalPlatform) @@ -112,1646 +90,3 @@ func TestWorkspaceSwitch(t *testing.T) { assert.Contains(t, resp, "test-workspace") mockClient.AssertExpectations(t) } - -func TestWorkspaceUserRootCommand(t *testing.T) { - testUtil.InitTestConfig(testUtil.LocalPlatform) - buf := new(bytes.Buffer) - cmd := newWorkspaceCmd(os.Stdout) - cmd.SetOut(buf) - cmdArgs := []string{"user", "-h"} - _, err := execWorkspaceCmd(cmdArgs...) - assert.NoError(t, err) -} - -var ( - workspaceRole = "WORKSPACE_MEMBER" - workspaceUser1 = astrocore.User{ - CreatedAt: time.Now(), - FullName: "user 1", - Id: "user1-id", - WorkspaceRole: &workspaceRole, - Username: "user@1.com", - } - workspaceUsers = []astrocore.User{ - workspaceUser1, - } - - workspaceTeams = []astrocore.Team{ - team1, - } - ListWorkspaceUsersResponseOK = astrocore.ListWorkspaceUsersResponse{ - HTTPResponse: &http.Response{ - StatusCode: 200, - }, - JSON200: &astrocore.UsersPaginated{ - Limit: 1, - Offset: 0, - TotalCount: 1, - Users: workspaceUsers, - }, - } - ListWorkspaceUsersResponseError = astrocore.ListWorkspaceUsersResponse{ - HTTPResponse: &http.Response{ - StatusCode: 500, - }, - Body: errorBodyList, - JSON200: nil, - } - MutateWorkspaceUserRoleResponseOK = astrocore.MutateWorkspaceUserRoleResponse{ - HTTPResponse: &http.Response{ - StatusCode: 200, - }, - JSON200: &astrocore.UserRole{ - Role: "WORKSPACE_MEMBER", - }, - } - MutateWorkspaceUserRoleResponseError = astrocore.MutateWorkspaceUserRoleResponse{ - HTTPResponse: &http.Response{ - StatusCode: 500, - }, - Body: errorBodyUpdate, - JSON200: nil, - } - DeleteWorkspaceUserResponseOK = astrocore.DeleteWorkspaceUserResponse{ - HTTPResponse: &http.Response{ - StatusCode: 200, - }, - } - DeleteWorkspaceUserResponseError = astrocore.DeleteWorkspaceUserResponse{ - HTTPResponse: &http.Response{ - StatusCode: 500, - }, - Body: errorBodyUpdate, - } - ListWorkspaceTeamsResponseOK = astrocore.ListWorkspaceTeamsResponse{ - HTTPResponse: &http.Response{ - StatusCode: 200, - }, - JSON200: &astrocore.TeamsPaginated{ - Limit: 1, - Offset: 0, - TotalCount: 1, - Teams: workspaceTeams, - }, - } - ListWorkspaceTeamsResponseError = astrocore.ListWorkspaceTeamsResponse{ - HTTPResponse: &http.Response{ - StatusCode: 500, - }, - Body: teamRequestErrorBodyList, - JSON200: nil, - } - MutateWorkspaceTeamRoleResponseOK = astrocore.MutateWorkspaceTeamRoleResponse{ - HTTPResponse: &http.Response{ - StatusCode: 200, - }, - JSON200: &astrocore.TeamRole{ - Role: "WORKSPACE_MEMBER", - }, - } - MutateWorkspaceTeamRoleResponseError = astrocore.MutateWorkspaceTeamRoleResponse{ - HTTPResponse: &http.Response{ - StatusCode: 500, - }, - Body: teamRequestErrorBodyUpdate, - JSON200: nil, - } - DeleteWorkspaceTeamResponseOK = astrocore.DeleteWorkspaceTeamResponse{ - HTTPResponse: &http.Response{ - StatusCode: 200, - }, - } - DeleteWorkspaceTeamResponseError = astrocore.DeleteWorkspaceTeamResponse{ - HTTPResponse: &http.Response{ - StatusCode: 500, - }, - Body: teamRequestErrorDelete, - } -) - -func TestWorkspaceUserList(t *testing.T) { - expectedHelp := "List all the users in an Astro Workspace" - testUtil.InitTestConfig(testUtil.LocalPlatform) - - t.Run("-h prints list help", func(t *testing.T) { - cmdArgs := []string{"user", "list", "-h"} - resp, err := execWorkspaceCmd(cmdArgs...) - assert.NoError(t, err) - assert.Contains(t, resp, expectedHelp) - }) - t.Run("any errors from api are returned and users are not listed", func(t *testing.T) { - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListWorkspaceUsersWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&ListWorkspaceUsersResponseError, nil).Once() - astroCoreClient = mockClient - cmdArgs := []string{"user", "list"} - _, err := execWorkspaceCmd(cmdArgs...) - assert.EqualError(t, err, "failed to list users") - }) - t.Run("any context errors from api are returned and users are not listed", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.Initial) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListWorkspaceUsersWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&ListWorkspaceUsersResponseError, nil).Once() - astroCoreClient = mockClient - cmdArgs := []string{"user", "list"} - _, err := execWorkspaceCmd(cmdArgs...) - assert.Error(t, err) - }) -} - -func TestWorkspaceUserUpdate(t *testing.T) { - expectedHelp := "astro workspace user update [email] --role [WORKSPACE_MEMBER, WORKSPACE_AUTHOR, WORKSPACE_OPERATOR, WORKSPACE_OWNER]" - testUtil.InitTestConfig(testUtil.LocalPlatform) - - t.Run("-h prints update help", func(t *testing.T) { - cmdArgs := []string{"user", "update", "-h"} - resp, err := execWorkspaceCmd(cmdArgs...) - assert.NoError(t, err) - assert.Contains(t, resp, expectedHelp) - }) - t.Run("valid email with valid role updates user", func(t *testing.T) { - expectedOut := "The workspace user user@1.com role was successfully updated to WORKSPACE_MEMBER" - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListWorkspaceUsersWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&ListWorkspaceUsersResponseOK, nil).Twice() - mockClient.On("MutateWorkspaceUserRoleWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&MutateWorkspaceUserRoleResponseOK, nil).Once() - astroCoreClient = mockClient - cmdArgs := []string{"user", "update", "user@1.com", "--role", "WORKSPACE_MEMBER"} - resp, err := execWorkspaceCmd(cmdArgs...) - assert.NoError(t, err) - assert.Contains(t, resp, expectedOut) - }) - t.Run("valid email with invalid role returns an error and role is not update", func(t *testing.T) { - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListWorkspaceUsersWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&ListWorkspaceUsersResponseOK, nil).Twice() - mockClient.On("MutateWorkspaceUserRoleWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&MutateWorkspaceUserRoleResponseOK, nil).Once() - astroCoreClient = mockClient - cmdArgs := []string{"user", "update", "user@1.com", "--role", "invalid"} - _, err := execWorkspaceCmd(cmdArgs...) - assert.ErrorIs(t, err, user.ErrInvalidWorkspaceRole) - }) - t.Run("any errors from api are returned and role is not updated", func(t *testing.T) { - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListWorkspaceUsersWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&ListWorkspaceUsersResponseOK, nil).Twice() - mockClient.On("MutateWorkspaceUserRoleWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&MutateWorkspaceUserRoleResponseError, nil).Once() - astroCoreClient = mockClient - cmdArgs := []string{"user", "update", "user@1.com", "--role", "WORKSPACE_MEMBER"} - _, err := execWorkspaceCmd(cmdArgs...) - assert.EqualError(t, err, "failed to update user") - }) - - t.Run("any context errors from api are returned and role is not updated", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.Initial) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListWorkspaceUsersWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&ListWorkspaceUsersResponseOK, nil).Twice() - mockClient.On("MutateWorkspaceUserRoleWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&MutateWorkspaceUserRoleResponseOK, nil).Once() - astroCoreClient = mockClient - cmdArgs := []string{"user", "update", "user@1.com", "--role", "WORKSPACE_MEMBER"} - _, err := execWorkspaceCmd(cmdArgs...) - assert.Error(t, err) - }) - t.Run("command asks for input when no email is passed in as an arg", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.LocalPlatform) - - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListWorkspaceUsersWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&ListWorkspaceUsersResponseOK, nil).Twice() - // mock os.Stdin - expectedInput := []byte("1") - r, w, err := os.Pipe() - assert.NoError(t, err) - _, err = w.Write(expectedInput) - assert.NoError(t, err) - w.Close() - stdin := os.Stdin - // Restore stdin right after the test. - defer func() { os.Stdin = stdin }() - os.Stdin = r - - expectedOut := "The workspace user user@1.com role was successfully updated to WORKSPACE_MEMBER" - mockClient.On("MutateWorkspaceUserRoleWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&MutateWorkspaceUserRoleResponseOK, nil).Once() - - cmdArgs := []string{"user", "update", "--role", "WORKSPACE_MEMBER"} - resp, err := execWorkspaceCmd(cmdArgs...) - assert.NoError(t, err) - assert.Contains(t, resp, expectedOut) - }) -} - -func TestWorkspaceUserAdd(t *testing.T) { - expectedHelp := "astro workspace user add [email] --role [WORKSPACE_MEMBER, WORKSPACE_AUTHOR, WORKSPACE_OPERATOR, WORKSPACE_OWNER]" - testUtil.InitTestConfig(testUtil.LocalPlatform) - - t.Run("-h prints add help", func(t *testing.T) { - cmdArgs := []string{"user", "add", "-h"} - resp, err := execWorkspaceCmd(cmdArgs...) - assert.NoError(t, err) - assert.Contains(t, resp, expectedHelp) - }) - t.Run("valid email with valid role adds user", func(t *testing.T) { - expectedOut := "The user user@1.com was successfully added to the workspace with the role WORKSPACE_MEMBER\n" - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListOrgUsersWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&ListOrgUsersResponseOK, nil).Twice() - mockClient.On("MutateWorkspaceUserRoleWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&MutateWorkspaceUserRoleResponseOK, nil).Once() - astroCoreClient = mockClient - cmdArgs := []string{"user", "add", "user@1.com", "--role", "WORKSPACE_MEMBER"} - resp, err := execWorkspaceCmd(cmdArgs...) - assert.NoError(t, err) - assert.Contains(t, resp, expectedOut) - }) - t.Run("valid email with invalid role returns an error and user is not added", func(t *testing.T) { - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListOrgUsersWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&ListOrgUsersResponseOK, nil).Twice() - mockClient.On("MutateWorkspaceUserRoleWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&MutateWorkspaceUserRoleResponseOK, nil).Once() - astroCoreClient = mockClient - cmdArgs := []string{"user", "add", "user@1.com", "--role", "invalid"} - _, err := execWorkspaceCmd(cmdArgs...) - assert.ErrorIs(t, err, user.ErrInvalidWorkspaceRole) - }) - t.Run("any errors from api are returned and user is not added", func(t *testing.T) { - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListOrgUsersWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&ListOrgUsersResponseOK, nil).Twice() - mockClient.On("MutateWorkspaceUserRoleWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&MutateWorkspaceUserRoleResponseError, nil).Once() - astroCoreClient = mockClient - cmdArgs := []string{"user", "add", "user@1.com", "--role", "WORKSPACE_MEMBER"} - _, err := execWorkspaceCmd(cmdArgs...) - assert.EqualError(t, err, "failed to update user") - }) - - t.Run("any context errors from api are returned and role is not added", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.Initial) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListOrgUsersWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&ListOrgUsersResponseOK, nil).Twice() - mockClient.On("MutateWorkspaceUserRoleWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&MutateWorkspaceUserRoleResponseOK, nil).Once() - astroCoreClient = mockClient - cmdArgs := []string{"user", "add", "user@1.com", "--role", "WORKSPACE_MEMBER"} - _, err := execWorkspaceCmd(cmdArgs...) - assert.Error(t, err) - }) - t.Run("command asks for input when no email is passed in as an arg", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.LocalPlatform) - - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListOrgUsersWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&ListOrgUsersResponseOK, nil).Twice() - // mock os.Stdin - expectedInput := []byte("1") - r, w, err := os.Pipe() - assert.NoError(t, err) - _, err = w.Write(expectedInput) - assert.NoError(t, err) - w.Close() - stdin := os.Stdin - // Restore stdin right after the test. - defer func() { os.Stdin = stdin }() - os.Stdin = r - - expectedOut := "The user user@1.com was successfully added to the workspace with the role WORKSPACE_MEMBER\n" - mockClient.On("MutateWorkspaceUserRoleWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&MutateWorkspaceUserRoleResponseOK, nil).Once() - - cmdArgs := []string{"user", "add", "--role", "WORKSPACE_MEMBER"} - resp, err := execWorkspaceCmd(cmdArgs...) - assert.NoError(t, err) - assert.Contains(t, resp, expectedOut) - }) -} - -func TestWorkspaceUserRemove(t *testing.T) { - expectedHelp := "Remove a user from an Astro Workspace" - testUtil.InitTestConfig(testUtil.LocalPlatform) - - t.Run("-h prints remove help", func(t *testing.T) { - cmdArgs := []string{"user", "remove", "-h"} - resp, err := execWorkspaceCmd(cmdArgs...) - assert.NoError(t, err) - assert.Contains(t, resp, expectedHelp) - }) - t.Run("valid email removes user", func(t *testing.T) { - expectedOut := "The user user@1.com was successfully removed from the workspace" - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListWorkspaceUsersWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&ListWorkspaceUsersResponseOK, nil).Twice() - mockClient.On("DeleteWorkspaceUserWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&DeleteWorkspaceUserResponseOK, nil).Once() - astroCoreClient = mockClient - cmdArgs := []string{"user", "remove", "user@1.com"} - resp, err := execWorkspaceCmd(cmdArgs...) - assert.NoError(t, err) - assert.Contains(t, resp, expectedOut) - }) - t.Run("any errors from api are returned and user is not removed", func(t *testing.T) { - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListWorkspaceUsersWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&ListWorkspaceUsersResponseOK, nil).Twice() - mockClient.On("DeleteWorkspaceUserWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&DeleteWorkspaceUserResponseError, nil).Once() - astroCoreClient = mockClient - cmdArgs := []string{"user", "remove", "user@1.com"} - _, err := execWorkspaceCmd(cmdArgs...) - assert.EqualError(t, err, "failed to update user") - }) - t.Run("any context errors from api are returned and the user is not removed", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.Initial) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListWorkspaceUsersWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&ListWorkspaceUsersResponseOK, nil).Twice() - mockClient.On("DeleteWorkspaceUserWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&DeleteWorkspaceUserResponseOK, nil).Once() - astroCoreClient = mockClient - cmdArgs := []string{"user", "remove", "user@1.com"} - _, err := execWorkspaceCmd(cmdArgs...) - assert.Error(t, err) - }) - t.Run("command asks for input when no email is passed in as an arg", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.LocalPlatform) - - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListWorkspaceUsersWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&ListWorkspaceUsersResponseOK, nil).Twice() - // mock os.Stdin - expectedInput := []byte("1") - r, w, err := os.Pipe() - assert.NoError(t, err) - _, err = w.Write(expectedInput) - assert.NoError(t, err) - w.Close() - stdin := os.Stdin - // Restore stdin right after the test. - defer func() { os.Stdin = stdin }() - os.Stdin = r - - expectedOut := "The user user@1.com was successfully removed from the workspace" - mockClient.On("DeleteWorkspaceUserWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&DeleteWorkspaceUserResponseOK, nil).Once() - - cmdArgs := []string{"user", "remove"} - resp, err := execWorkspaceCmd(cmdArgs...) - assert.NoError(t, err) - assert.Contains(t, resp, expectedOut) - }) -} - -var ( - CreateWorkspaceResponseOK = astrocore.CreateWorkspaceResponse{ - HTTPResponse: &http.Response{ - StatusCode: 200, - }, - JSON200: &astrocore.Workspace{ - Name: "workspace-test", - }, - } - - errorBodyCreate, _ = json.Marshal(astrocore.Error{ - Message: "failed to create workspace", - }) - - CreateWorkspaceResponseError = astrocore.CreateWorkspaceResponse{ - HTTPResponse: &http.Response{ - StatusCode: 500, - }, - Body: errorBodyCreate, - JSON200: nil, - } -) - -func TestWorkspaceCreate(t *testing.T) { - expectedHelp := "workspace create [flags]" - testUtil.InitTestConfig(testUtil.LocalPlatform) - - t.Run("-h prints add help", func(t *testing.T) { - cmdArgs := []string{"create", "-h"} - resp, err := execWorkspaceCmd(cmdArgs...) - assert.NoError(t, err) - assert.Contains(t, resp, expectedHelp) - }) - t.Run("valid name with valid enforce", func(t *testing.T) { - expectedOut := "Astro Workspace workspace-test was successfully created\n" - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("CreateWorkspaceWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&CreateWorkspaceResponseOK, nil).Once() - astroCoreClient = mockClient - cmdArgs := []string{"create", "--name", "workspace-test", "--enforce-cicd", "ON"} - resp, err := execWorkspaceCmd(cmdArgs...) - assert.NoError(t, err) - assert.Contains(t, resp, expectedOut) - }) - t.Run("valid name with invalid enforce returns an error and workspace is not created", func(t *testing.T) { - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("CreateWorkspaceWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&CreateWorkspaceResponseOK, nil).Once() - astroCoreClient = mockClient - cmdArgs := []string{"create", "--name", "workspace-test", "--enforce-cicd", "on"} - _, err := execWorkspaceCmd(cmdArgs...) - assert.ErrorIs(t, err, workspace.ErrWrongEnforceInput) - }) - t.Run("any errors from api are returned and workspace is not created", func(t *testing.T) { - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("CreateWorkspaceWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&CreateWorkspaceResponseError, nil).Once() - astroCoreClient = mockClient - cmdArgs := []string{"create", "--name", "workspace-test"} - _, err := execWorkspaceCmd(cmdArgs...) - assert.EqualError(t, err, "failed to create workspace") - }) - - t.Run("any context errors from api are returned and workspace is not created", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.Initial) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("CreateWorkspaceWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&CreateWorkspaceResponseOK, nil).Once() - astroCoreClient = mockClient - cmdArgs := []string{"create", "--name", "workspace-test"} - _, err := execWorkspaceCmd(cmdArgs...) - assert.Error(t, err) - }) -} - -var ( - DeleteWorkspaceResponseOK = astrocore.DeleteWorkspaceResponse{ - HTTPResponse: &http.Response{ - StatusCode: 200, - }, - } - - errorBodyDelete, _ = json.Marshal(astrocore.Error{ - Message: "failed to delete workspace", - }) - - DeleteWorkspaceResponseError = astrocore.DeleteWorkspaceResponse{ - HTTPResponse: &http.Response{ - StatusCode: 500, - }, - Body: errorBodyDelete, - } -) - -func TestWorkspaceDelete(t *testing.T) { - expectedHelp := "workspace delete [workspace_id] [flags]" - testUtil.InitTestConfig(testUtil.LocalPlatform) - - t.Run("-h prints add help", func(t *testing.T) { - cmdArgs := []string{"delete", "-h"} - resp, err := execWorkspaceCmd(cmdArgs...) - assert.NoError(t, err) - assert.Contains(t, resp, expectedHelp) - }) - t.Run("valid id and successful delete", func(t *testing.T) { - expectedOut := "Astro Workspace test-workspace was successfully deleted\n" - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListWorkspacesWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&ListWorkspacesResponseOK, nil).Once() - mockClient.On("DeleteWorkspaceWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&DeleteWorkspaceResponseOK, nil).Once() - astroCoreClient = mockClient - cmdArgs := []string{"delete", "workspace-id"} - resp, err := execWorkspaceCmd(cmdArgs...) - assert.NoError(t, err) - assert.Contains(t, resp, expectedOut) - }) - t.Run("invalid id returns workspace not found", func(t *testing.T) { - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListWorkspacesWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&ListWorkspacesResponseOK, nil).Once() - astroCoreClient = mockClient - cmdArgs := []string{"delete", "workspace-test"} - _, err := execWorkspaceCmd(cmdArgs...) - assert.ErrorIs(t, err, workspace.ErrWorkspaceNotFound) - }) - t.Run("any errors from api are returned and workspace is not deleted", func(t *testing.T) { - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListWorkspacesWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&ListWorkspacesResponseOK, nil).Once() - mockClient.On("DeleteWorkspaceWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&DeleteWorkspaceResponseError, nil).Once() - astroCoreClient = mockClient - cmdArgs := []string{"delete", "workspace-id"} - _, err := execWorkspaceCmd(cmdArgs...) - assert.EqualError(t, err, "failed to delete workspace") - }) - - t.Run("any context errors from api are returned and workspace is not deleted", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.Initial) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListWorkspacesWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&ListWorkspacesResponseOK, nil).Once() - mockClient.On("DeleteWorkspaceWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&DeleteWorkspaceResponseError, nil).Once() - astroCoreClient = mockClient - cmdArgs := []string{"delete", "workspace-id"} - _, err := execWorkspaceCmd(cmdArgs...) - assert.Error(t, err) - }) - - t.Run("command asks for input when no workspace id is passed in as an arg", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.LocalPlatform) - - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListWorkspacesWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&ListWorkspacesResponseOK, nil).Once() - astroCoreClient = mockClient - // mock os.Stdin - expectedInput := []byte("1") - r, w, err := os.Pipe() - assert.NoError(t, err) - _, err = w.Write(expectedInput) - assert.NoError(t, err) - w.Close() - stdin := os.Stdin - // Restore stdin right after the test. - defer func() { os.Stdin = stdin }() - os.Stdin = r - - expectedOut := "Astro Workspace test-workspace was successfully deleted\n" - mockClient.On("DeleteWorkspaceWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&DeleteWorkspaceResponseOK, nil).Once() - - cmdArgs := []string{"delete"} - resp, err := execWorkspaceCmd(cmdArgs...) - assert.NoError(t, err) - assert.Contains(t, resp, expectedOut) - }) -} - -var ( - UpdateWorkspaceResponseOK = astrocore.UpdateWorkspaceResponse{ - HTTPResponse: &http.Response{ - StatusCode: 200, - }, - JSON200: &astrocore.Workspace{ - Name: "workspace-test", - }, - } - - errorBodyWorkspaceUpdate, _ = json.Marshal(astrocore.Error{ - Message: "failed to update workspace", - }) - - UpdateWorkspaceResponseError = astrocore.UpdateWorkspaceResponse{ - HTTPResponse: &http.Response{ - StatusCode: 500, - }, - Body: errorBodyWorkspaceUpdate, - JSON200: nil, - } -) - -func TestWorkspaceUpdate(t *testing.T) { - expectedHelp := "workspace update [workspace_id] [flags]" - testUtil.InitTestConfig(testUtil.LocalPlatform) - - t.Run("-h prints add help", func(t *testing.T) { - cmdArgs := []string{"update", "-h"} - resp, err := execWorkspaceCmd(cmdArgs...) - assert.NoError(t, err) - assert.Contains(t, resp, expectedHelp) - }) - t.Run("valid id and successful update", func(t *testing.T) { - expectedOut := "Astro Workspace test-workspace was successfully updated\n" - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListWorkspacesWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&ListWorkspacesResponseOK, nil).Once() - mockClient.On("UpdateWorkspaceWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&UpdateWorkspaceResponseOK, nil).Once() - astroCoreClient = mockClient - cmdArgs := []string{"update", "workspace-id"} - resp, err := execWorkspaceCmd(cmdArgs...) - assert.NoError(t, err) - assert.Contains(t, resp, expectedOut) - }) - t.Run("invalid id returns workspace not found", func(t *testing.T) { - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListWorkspacesWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&ListWorkspacesResponseOK, nil).Once() - astroCoreClient = mockClient - cmdArgs := []string{"update", "workspace-test"} - _, err := execWorkspaceCmd(cmdArgs...) - assert.ErrorIs(t, err, workspace.ErrWorkspaceNotFound) - }) - t.Run("any errors from api are returned and workspace is not updated", func(t *testing.T) { - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListWorkspacesWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&ListWorkspacesResponseOK, nil).Once() - mockClient.On("UpdateWorkspaceWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&UpdateWorkspaceResponseError, nil).Once() - astroCoreClient = mockClient - cmdArgs := []string{"update", "workspace-id"} - _, err := execWorkspaceCmd(cmdArgs...) - assert.EqualError(t, err, "failed to update workspace") - }) - - t.Run("any context errors from api are returned and workspace is not updated", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.Initial) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListWorkspacesWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&ListWorkspacesResponseOK, nil).Once() - mockClient.On("UpdateWorkspaceWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&UpdateWorkspaceResponseError, nil).Once() - astroCoreClient = mockClient - cmdArgs := []string{"update", "workspace-id"} - _, err := execWorkspaceCmd(cmdArgs...) - assert.Error(t, err) - }) - - t.Run("command asks for input when no workspace id is passed in as an arg", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.LocalPlatform) - - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListWorkspacesWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&ListWorkspacesResponseOK, nil).Once() - astroCoreClient = mockClient - // mock os.Stdin - expectedInput := []byte("1") - r, w, err := os.Pipe() - assert.NoError(t, err) - _, err = w.Write(expectedInput) - assert.NoError(t, err) - w.Close() - stdin := os.Stdin - // Restore stdin right after the test. - defer func() { os.Stdin = stdin }() - os.Stdin = r - - expectedOut := "Astro Workspace test-workspace was successfully updated\n" - mockClient.On("UpdateWorkspaceWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&UpdateWorkspaceResponseOK, nil).Once() - - cmdArgs := []string{"update"} - resp, err := execWorkspaceCmd(cmdArgs...) - assert.NoError(t, err) - assert.Contains(t, resp, expectedOut) - }) -} - -func TestWorkspaceTeamList(t *testing.T) { - expectedHelp := "List all the teams in an Astro Workspace" - testUtil.InitTestConfig(testUtil.LocalPlatform) - - t.Run("-h prints list help", func(t *testing.T) { - cmdArgs := []string{"team", "list", "-h"} - resp, err := execWorkspaceCmd(cmdArgs...) - assert.NoError(t, err) - assert.Contains(t, resp, expectedHelp) - }) - t.Run("any errors from api are returned and teams are not listed", func(t *testing.T) { - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListWorkspaceTeamsWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&ListWorkspaceTeamsResponseError, nil).Once() - astroCoreClient = mockClient - cmdArgs := []string{"team", "list"} - _, err := execWorkspaceCmd(cmdArgs...) - assert.EqualError(t, err, "failed to list teams") - }) - t.Run("any context errors from api are returned and teams are not listed", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.Initial) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListWorkspaceTeamsWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&ListWorkspaceTeamsResponseError, nil).Once() - astroCoreClient = mockClient - cmdArgs := []string{"team", "list"} - _, err := execWorkspaceCmd(cmdArgs...) - assert.Error(t, err) - }) -} - -func TestWorkspaceTeamUpdate(t *testing.T) { - expectedHelp := "astro workspace team update [id] --role [WORKSPACE_MEMBER, WORKSPACE_AUTHOR, WORKSPACE_OPERATOR, WORKSPACE_OWNER]" - testUtil.InitTestConfig(testUtil.LocalPlatform) - - t.Run("-h prints update help", func(t *testing.T) { - cmdArgs := []string{"team", "update", "-h"} - resp, err := execWorkspaceCmd(cmdArgs...) - assert.NoError(t, err) - assert.Contains(t, resp, expectedHelp) - }) - t.Run("valid id with valid role updates team", func(t *testing.T) { - expectedOut := fmt.Sprintf("The workspace team %s role was successfully updated to WORKSPACE_MEMBER", team1.Id) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("GetTeamWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&GetTeamWithResponseOK, nil).Twice() - mockClient.On("MutateWorkspaceTeamRoleWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&MutateWorkspaceTeamRoleResponseOK, nil).Once() - astroCoreClient = mockClient - cmdArgs := []string{"team", "update", team1.Id, "--role", "WORKSPACE_MEMBER"} - resp, err := execWorkspaceCmd(cmdArgs...) - assert.NoError(t, err) - assert.Contains(t, resp, expectedOut) - }) - t.Run("valid id with invalid role returns an error and role is not update", func(t *testing.T) { - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("GetTeamWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&GetTeamWithResponseOK, nil).Twice() - mockClient.On("MutateWorkspaceTeamRoleWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&MutateWorkspaceTeamRoleResponseOK, nil).Once() - astroCoreClient = mockClient - cmdArgs := []string{"team", "update", team1.Id, "--role", "invalid"} - _, err := execWorkspaceCmd(cmdArgs...) - assert.ErrorIs(t, err, user.ErrInvalidWorkspaceRole) - }) - t.Run("any errors from api are returned and role is not updated", func(t *testing.T) { - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("GetTeamWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&GetTeamWithResponseOK, nil).Twice() - mockClient.On("MutateWorkspaceTeamRoleWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&MutateWorkspaceTeamRoleResponseError, nil).Once() - astroCoreClient = mockClient - cmdArgs := []string{"team", "update", team1.Id, "--role", "WORKSPACE_MEMBER"} - _, err := execWorkspaceCmd(cmdArgs...) - assert.EqualError(t, err, "failed to update team") - }) - - t.Run("any context errors from api are returned and role is not updated", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.Initial) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("GetTeamWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&GetTeamWithResponseOK, nil).Twice() - mockClient.On("MutateWorkspaceTeamRoleWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&MutateWorkspaceTeamRoleResponseOK, nil).Once() - astroCoreClient = mockClient - cmdArgs := []string{"team", "update", team1.Id, "--role", "WORKSPACE_MEMBER"} - _, err := execWorkspaceCmd(cmdArgs...) - assert.Error(t, err) - }) - t.Run("command asks for input when no email is passed in as an arg", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.LocalPlatform) - - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListWorkspaceTeamsWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&ListWorkspaceTeamsResponseOK, nil).Twice() - // mock os.Stdin - expectedInput := []byte("1") - r, w, err := os.Pipe() - assert.NoError(t, err) - _, err = w.Write(expectedInput) - assert.NoError(t, err) - w.Close() - stdin := os.Stdin - // Restore stdin right after the test. - defer func() { os.Stdin = stdin }() - os.Stdin = r - - expectedOut := fmt.Sprintf("The workspace team %s role was successfully updated to WORKSPACE_MEMBER", team1.Id) - mockClient.On("MutateWorkspaceTeamRoleWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&MutateWorkspaceTeamRoleResponseOK, nil).Once() - astroCoreClient = mockClient - - cmdArgs := []string{"team", "update", "--role", "WORKSPACE_MEMBER"} - resp, err := execWorkspaceCmd(cmdArgs...) - assert.NoError(t, err) - assert.Contains(t, resp, expectedOut) - }) -} - -func TestWorkspaceTeamAdd(t *testing.T) { - expectedHelp := "astro workspace team add [id] --role [WORKSPACE_MEMBER, WORKSPACE_AUTHOR, WORKSPACE_OPERATOR, WORKSPACE_OWNER]" - testUtil.InitTestConfig(testUtil.LocalPlatform) - - t.Run("-h prints add help", func(t *testing.T) { - cmdArgs := []string{"team", "add", "-h"} - resp, err := execWorkspaceCmd(cmdArgs...) - assert.NoError(t, err) - assert.Contains(t, resp, expectedHelp) - }) - t.Run("valid id with valid role adds team", func(t *testing.T) { - expectedOut := fmt.Sprintf("The team %s was successfully added to the workspace with the role WORKSPACE_MEMBER\n", team1.Id) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("GetTeamWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&GetTeamWithResponseOK, nil).Twice() - mockClient.On("MutateWorkspaceTeamRoleWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&MutateWorkspaceTeamRoleResponseOK, nil).Once() - astroCoreClient = mockClient - cmdArgs := []string{"team", "add", team1.Id, "--role", "WORKSPACE_MEMBER"} - resp, err := execWorkspaceCmd(cmdArgs...) - assert.NoError(t, err) - assert.Contains(t, resp, expectedOut) - }) - - t.Run("can add team with workspace-id flag", func(t *testing.T) { - workspaceIDFromFlag := "mock-workspace-id" - expectedOut := fmt.Sprintf("The team %s was successfully added to the workspace with the role WORKSPACE_MEMBER\n", team1.Id) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("GetTeamWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&GetTeamWithResponseOK, nil).Twice() - mockClient.On("MutateWorkspaceTeamRoleWithResponse", mock.Anything, mock.Anything, workspaceIDFromFlag, mock.Anything, mock.Anything).Return(&MutateWorkspaceTeamRoleResponseOK, nil).Once() - astroCoreClient = mockClient - cmdArgs := []string{"team", "add", team1.Id, "--role", "WORKSPACE_MEMBER", "--workspace-id", workspaceIDFromFlag} - resp, err := execWorkspaceCmd(cmdArgs...) - assert.NoError(t, err) - assert.Contains(t, resp, expectedOut) - }) - - t.Run("valid email with invalid role returns an error and team is not added", func(t *testing.T) { - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("GetTeamWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&GetTeamWithResponseOK, nil).Twice() - mockClient.On("MutateWorkspaceTeamRoleWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&MutateWorkspaceTeamRoleResponseOK, nil).Once() - astroCoreClient = mockClient - cmdArgs := []string{"team", "add", team1.Id, "--role", "invalid"} - _, err := execWorkspaceCmd(cmdArgs...) - assert.ErrorIs(t, err, user.ErrInvalidWorkspaceRole) - }) - t.Run("any errors from api are returned and team is not added", func(t *testing.T) { - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("GetTeamWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&GetTeamWithResponseOK, nil).Twice() - mockClient.On("MutateWorkspaceTeamRoleWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&MutateWorkspaceTeamRoleResponseError, nil).Once() - astroCoreClient = mockClient - cmdArgs := []string{"team", "add", team1.Id, "--role", "WORKSPACE_MEMBER"} - _, err := execWorkspaceCmd(cmdArgs...) - assert.EqualError(t, err, "failed to update team") - }) - - t.Run("any context errors from api are returned and role is not added", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.Initial) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("GetTeamWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&GetTeamWithResponseOK, nil).Twice() - mockClient.On("MutateWorkspaceTeamRoleWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&MutateWorkspaceTeamRoleResponseOK, nil).Once() - astroCoreClient = mockClient - cmdArgs := []string{"team", "add", team1.Id, "--role", "WORKSPACE_MEMBER"} - _, err := execWorkspaceCmd(cmdArgs...) - assert.Error(t, err) - }) - t.Run("command asks for input when no email is passed in as an arg", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.LocalPlatform) - - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListOrganizationTeamsWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&ListOrgTeamsResponseOK, nil).Twice() - // mock os.Stdin - expectedInput := []byte("1") - r, w, err := os.Pipe() - assert.NoError(t, err) - _, err = w.Write(expectedInput) - assert.NoError(t, err) - w.Close() - stdin := os.Stdin - // Restore stdin right after the test. - defer func() { os.Stdin = stdin }() - os.Stdin = r - - expectedOut := fmt.Sprintf("The team %s was successfully added to the workspace with the role WORKSPACE_MEMBER\n", team1.Id) - mockClient.On("MutateWorkspaceTeamRoleWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&MutateWorkspaceTeamRoleResponseOK, nil).Once() - astroCoreClient = mockClient - - cmdArgs := []string{"team", "add", "--role", "WORKSPACE_MEMBER"} - resp, err := execWorkspaceCmd(cmdArgs...) - assert.NoError(t, err) - assert.Contains(t, resp, expectedOut) - }) -} - -func TestWorkspaceTeamRemove(t *testing.T) { - expectedHelp := "Remove a team from an Astro Workspace" - testUtil.InitTestConfig(testUtil.LocalPlatform) - - t.Run("-h prints remove help", func(t *testing.T) { - cmdArgs := []string{"team", "remove", "-h"} - resp, err := execWorkspaceCmd(cmdArgs...) - assert.NoError(t, err) - assert.Contains(t, resp, expectedHelp) - }) - t.Run("valid id removes team", func(t *testing.T) { - expectedOut := fmt.Sprintf("Astro Team %s was successfully removed from workspace ck05r3bor07h40d02y2hw4n4v\n", team1.Name) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("GetTeamWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&GetTeamWithResponseOK, nil).Twice() - mockClient.On("DeleteWorkspaceTeamWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&DeleteWorkspaceTeamResponseOK, nil).Once() - astroCoreClient = mockClient - cmdArgs := []string{"team", "remove", team1.Id} - resp, err := execWorkspaceCmd(cmdArgs...) - assert.NoError(t, err) - assert.Contains(t, resp, expectedOut) - }) - t.Run("any errors from api are returned and team is not removed", func(t *testing.T) { - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("GetTeamWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&GetTeamWithResponseOK, nil).Twice() - mockClient.On("DeleteWorkspaceTeamWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&DeleteWorkspaceTeamResponseError, nil).Once() - astroCoreClient = mockClient - cmdArgs := []string{"team", "remove", team1.Id} - _, err := execWorkspaceCmd(cmdArgs...) - assert.EqualError(t, err, "failed to delete team") - }) - t.Run("any context errors from api are returned and the team is not removed", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.Initial) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("GetTeamWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&GetTeamWithResponseOK, nil).Twice() - mockClient.On("DeleteWorkspaceTeamWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&DeleteWorkspaceTeamResponseOK, nil).Once() - astroCoreClient = mockClient - cmdArgs := []string{"team", "remove", team1.Id} - _, err := execWorkspaceCmd(cmdArgs...) - assert.Error(t, err) - }) - t.Run("command asks for input when no id is passed in as an arg", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.LocalPlatform) - - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListWorkspaceTeamsWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&ListWorkspaceTeamsResponseOK, nil).Twice() - // mock os.Stdin - expectedInput := []byte("1") - r, w, err := os.Pipe() - assert.NoError(t, err) - _, err = w.Write(expectedInput) - assert.NoError(t, err) - w.Close() - stdin := os.Stdin - // Restore stdin right after the test. - defer func() { os.Stdin = stdin }() - os.Stdin = r - - expectedOut := fmt.Sprintf("Astro Team %s was successfully removed from workspace %s", team1.Name, "ck05r3bor07h40d02y2hw4n4v") - mockClient.On("DeleteWorkspaceTeamWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&DeleteWorkspaceTeamResponseOK, nil).Once() - astroCoreClient = mockClient - - cmdArgs := []string{"team", "remove"} - resp, err := execWorkspaceCmd(cmdArgs...) - assert.NoError(t, err) - assert.Contains(t, resp, expectedOut) - }) -} - -// test workspace token commands - -var ( - mockWorkspaceID = "ck05r3bor07h40d02y2hw4n4v" - mockOrganizationID = "orgID" - description1 = "Description 1" - description2 = "Description 2" - fullName1 = "User 1" - fullName2 = "User 2" - token = "token" - tokenName1 = "Token 1" - apiToken1 = astrocore.ApiToken{Id: "token1", Name: tokenName1, Token: &token, Description: description1, Type: "Type 1", Roles: []astrocore.ApiTokenRole{{EntityId: mockWorkspaceID, EntityType: "WORKSPACE", Role: "WORKSPACE_MEMBER"}}, CreatedAt: time.Now(), CreatedBy: &astrocore.BasicSubjectProfile{FullName: &fullName1}} - apiTokens = []astrocore.ApiToken{ - apiToken1, - {Id: "token2", Name: "Token 2", Description: description2, Type: "Type 2", Roles: []astrocore.ApiTokenRole{{EntityId: mockWorkspaceID, EntityType: "WORKSPACE", Role: "WORKSPACE_MEMBER"}}, CreatedAt: time.Now(), CreatedBy: &astrocore.BasicSubjectProfile{FullName: &fullName2}}, - } - iamAPIOrgnaizationToken = astroiamcore.ApiToken{Id: "token1", Name: "Token 1", Token: &token, Description: description1, Type: "ORGANIZATION", Roles: &[]astroiamcore.ApiTokenRole{{EntityType: "ORGANIZATION", EntityId: "test-org-id", Role: "ORGANIZATION_MEMBER"}, {EntityType: "WORKSPACE", EntityId: mockWorkspaceID, Role: "WORKSPACE_AUTHOR"}, {EntityType: "WORKSPACE", EntityId: "WORKSPACE", Role: "WORKSPACE_AUTHOR"}, {EntityType: "DEPLOYMENT", EntityId: "DEPLOYMENT", Role: "DEPLOYMENT_ADMIN"}}, CreatedAt: time.Now(), CreatedBy: &astroiamcore.BasicSubjectProfile{FullName: &fullName1}} - GetAPITokensResponseOKOrganizationToken = astroiamcore.GetApiTokenResponse{ - HTTPResponse: &http.Response{ - StatusCode: 200, - }, - JSON200: &iamAPIOrgnaizationToken, - } - iamAPIWorkspaceToken = astroiamcore.ApiToken{Id: "token1", Name: "Token 1", Token: &token, Description: description1, Type: "WORKSPACE", Roles: &[]astroiamcore.ApiTokenRole{{EntityType: "WORKSPACE", EntityId: mockWorkspaceID, Role: "WORKSPACE_AUTHOR"}, {EntityType: "DEPLOYMENT", EntityId: "DEPLOYMENT", Role: "DEPLOYMENT_ADMIN"}}, CreatedAt: time.Now(), CreatedBy: &astroiamcore.BasicSubjectProfile{FullName: &fullName1}} - - GetAPITokensResponseOKWorkspaceToken = astroiamcore.GetApiTokenResponse{ - HTTPResponse: &http.Response{ - StatusCode: 200, - }, - JSON200: &iamAPIWorkspaceToken, - } - - iamAPIDeploymentToken = astroiamcore.ApiToken{Id: "token1", Name: "Token 1", Token: &token, Description: description1, Type: "DEPLOYMENT", Roles: &[]astroiamcore.ApiTokenRole{{EntityType: "DEPLOYMENT", EntityId: "DEPLOYMENT", Role: "DEPLOYMENT_ADMIN"}}, CreatedAt: time.Now(), CreatedBy: &astroiamcore.BasicSubjectProfile{FullName: &fullName1}} - - GetAPITokensResponseOKDeploymentToken = astroiamcore.GetApiTokenResponse{ - HTTPResponse: &http.Response{ - StatusCode: 200, - }, - JSON200: &iamAPIDeploymentToken, - } - errorTokenGet, _ = json.Marshal(astroiamcore.Error{ - Message: "failed to get token", - }) - GetAPITokensResponseError = astroiamcore.GetApiTokenResponse{ - HTTPResponse: &http.Response{ - StatusCode: 500, - }, - Body: errorTokenGet, - JSON200: nil, - } - ListWorkspaceAPITokensResponseOK = astrocore.ListWorkspaceApiTokensResponse{ - HTTPResponse: &http.Response{ - StatusCode: 200, - }, - JSON200: &astrocore.ListApiTokensPaginated{ - ApiTokens: apiTokens, - Limit: 1, - Offset: 0, - }, - } - errorTokenList, _ = json.Marshal(astrocore.Error{ - Message: "failed to list tokens", - }) - ListWorkspaceAPITokensResponseError = astrocore.ListWorkspaceApiTokensResponse{ - HTTPResponse: &http.Response{ - StatusCode: 500, - }, - Body: errorTokenList, - JSON200: nil, - } - CreateWorkspaceAPITokenResponseOK = astrocore.CreateWorkspaceApiTokenResponse{ - HTTPResponse: &http.Response{ - StatusCode: 200, - }, - JSON200: &apiToken1, - } - CreateWorkspaceAPITokenResponseError = astrocore.CreateWorkspaceApiTokenResponse{ - HTTPResponse: &http.Response{ - StatusCode: 500, - }, - Body: errorBodyCreate, - JSON200: nil, - } - UpdateWorkspaceAPITokenResponseOK = astrocore.UpdateWorkspaceApiTokenResponse{ - HTTPResponse: &http.Response{ - StatusCode: 200, - }, - JSON200: &apiToken1, - } - errorTokenUpdate, _ = json.Marshal(astrocore.Error{ - Message: "failed to update token", - }) - UpdateWorkspaceAPITokenResponseError = astrocore.UpdateWorkspaceApiTokenResponse{ - HTTPResponse: &http.Response{ - StatusCode: 500, - }, - Body: errorTokenUpdate, - JSON200: nil, - } - RotateWorkspaceAPITokenResponseOK = astrocore.RotateWorkspaceApiTokenResponse{ - HTTPResponse: &http.Response{ - StatusCode: 200, - }, - JSON200: &apiToken1, - } - errorTokenRotate, _ = json.Marshal(astrocore.Error{ - Message: "failed to rotate token", - }) - RotateWorkspaceAPITokenResponseError = astrocore.RotateWorkspaceApiTokenResponse{ - HTTPResponse: &http.Response{ - StatusCode: 500, - }, - Body: errorTokenRotate, - JSON200: nil, - } - DeleteWorkspaceAPITokenResponseOK = astrocore.DeleteWorkspaceApiTokenResponse{ - HTTPResponse: &http.Response{ - StatusCode: 200, - }, - } - errorTokenDelete, _ = json.Marshal(astrocore.Error{ - Message: "failed to delete token", - }) - DeleteWorkspaceAPITokenResponseError = astrocore.DeleteWorkspaceApiTokenResponse{ - HTTPResponse: &http.Response{ - StatusCode: 500, - }, - Body: errorTokenDelete, - } -) - -func TestWorkspaceTokenRootCommand(t *testing.T) { - testUtil.InitTestConfig(testUtil.LocalPlatform) - buf := new(bytes.Buffer) - cmd := newWorkspaceCmd(os.Stdout) - cmd.SetOut(buf) - cmdArgs := []string{"token", "-h"} - _, err := execWorkspaceCmd(cmdArgs...) - assert.NoError(t, err) -} - -func TestWorkspaceTokenList(t *testing.T) { - expectedHelp := "List all the API tokens in an Astro Workspace" - testUtil.InitTestConfig(testUtil.LocalPlatform) - - t.Run("-h prints list help", func(t *testing.T) { - cmdArgs := []string{"token", "list", "-h"} - resp, err := execWorkspaceCmd(cmdArgs...) - assert.NoError(t, err) - assert.Contains(t, resp, expectedHelp) - }) - t.Run("any errors from api are returned and tokens are not listed", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.LocalPlatform) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListWorkspaceApiTokensWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&ListWorkspaceAPITokensResponseError, nil).Twice() - astroCoreClient = mockClient - cmdArgs := []string{"token", "list"} - _, err := execWorkspaceCmd(cmdArgs...) - assert.ErrorContains(t, err, "failed to list tokens") - }) - t.Run("any context errors from api are returned and tokens are not listed", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.Initial) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListWorkspaceApiTokensWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&ListWorkspaceAPITokensResponseOK, nil).Twice() - astroCoreClient = mockClient - cmdArgs := []string{"token", "list"} - _, err := execWorkspaceCmd(cmdArgs...) - assert.Error(t, err) - }) - - t.Run("tokens are listed", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.LocalPlatform) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListWorkspaceApiTokensWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&ListWorkspaceAPITokensResponseOK, nil).Twice() - astroCoreClient = mockClient - cmdArgs := []string{"token", "list"} - _, err := execWorkspaceCmd(cmdArgs...) - assert.NoError(t, err) - }) -} - -func TestWorkspaceTokenCreate(t *testing.T) { - expectedHelp := "Create an API token in an Astro Workspace" - testUtil.InitTestConfig(testUtil.LocalPlatform) - - t.Run("-h prints list help", func(t *testing.T) { - cmdArgs := []string{"token", "create", "-h"} - resp, err := execWorkspaceCmd(cmdArgs...) - assert.NoError(t, err) - assert.Contains(t, resp, expectedHelp) - }) - t.Run("any errors from api are returned and token is not created", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.LocalPlatform) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("CreateWorkspaceApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&CreateWorkspaceAPITokenResponseError, nil) - astroCoreClient = mockClient - cmdArgs := []string{"token", "create", "--name", "Token 1", "--role", "WORKSPACE_MEMBER"} - _, err := execWorkspaceCmd(cmdArgs...) - assert.ErrorContains(t, err, "failed to create workspace") - }) - t.Run("any context errors from api are returned and token is not created", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.Initial) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("CreateWorkspaceApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&CreateWorkspaceAPITokenResponseOK, nil) - astroCoreClient = mockClient - cmdArgs := []string{"token", "list"} - _, err := execWorkspaceCmd(cmdArgs...) - assert.Error(t, err) - }) - t.Run("token is created", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.LocalPlatform) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("CreateWorkspaceApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&CreateWorkspaceAPITokenResponseOK, nil) - astroCoreClient = mockClient - cmdArgs := []string{"token", "create", "--name", "Token 1", "--role", "WORKSPACE_MEMBER"} - _, err := execWorkspaceCmd(cmdArgs...) - assert.NoError(t, err) - }) - t.Run("token is created with no name provided", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.LocalPlatform) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("CreateWorkspaceApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&CreateWorkspaceAPITokenResponseOK, nil) - // mock os.Stdin - expectedInput := []byte("Token 1") - r, w, err := os.Pipe() - assert.NoError(t, err) - _, err = w.Write(expectedInput) - assert.NoError(t, err) - w.Close() - stdin := os.Stdin - // Restore stdin right after the test. - defer func() { os.Stdin = stdin }() - os.Stdin = r - astroCoreClient = mockClient - cmdArgs := []string{"token", "create", "--role", "WORKSPACE_MEMBER"} - _, err = execWorkspaceCmd(cmdArgs...) - assert.NoError(t, err) - }) - t.Run("token is created with no role provided", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.LocalPlatform) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("CreateWorkspaceApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&CreateWorkspaceAPITokenResponseOK, nil) - // mock os.Stdin - expectedInput := []byte("1") - r, w, err := os.Pipe() - assert.NoError(t, err) - _, err = w.Write(expectedInput) - assert.NoError(t, err) - w.Close() - stdin := os.Stdin - // Restore stdin right after the test. - defer func() { os.Stdin = stdin }() - os.Stdin = r - astroCoreClient = mockClient - cmdArgs := []string{"token", "create", "--name", "Token 1"} - _, err = execWorkspaceCmd(cmdArgs...) - assert.NoError(t, err) - }) -} - -func TestWorkspaceTokenUpdate(t *testing.T) { - expectedHelp := "Update a Workspace or Organaization API token" - testUtil.InitTestConfig(testUtil.LocalPlatform) - tokenID = "" - - t.Run("-h prints list help", func(t *testing.T) { - cmdArgs := []string{"token", "update", "-h"} - resp, err := execWorkspaceCmd(cmdArgs...) - assert.NoError(t, err) - assert.Contains(t, resp, expectedHelp) - }) - t.Run("any errors from api are returned and token is not updated", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.LocalPlatform) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockIamClient := new(astroiamcore_mocks.ClientWithResponsesInterface) - mockClient.On("ListWorkspaceApiTokensWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&ListWorkspaceAPITokensResponseOK, nil) - mockIamClient.On("GetApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&GetAPITokensResponseOKWorkspaceToken, nil) - mockClient.On("UpdateWorkspaceApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&UpdateWorkspaceAPITokenResponseError, nil) - astroCoreClient = mockClient - astroCoreIamClient = mockIamClient - cmdArgs := []string{"token", "update", "--name", tokenName1} - _, err := execWorkspaceCmd(cmdArgs...) - assert.ErrorContains(t, err, "failed to update token") - }) - t.Run("any context errors from api are returned and token is not updated", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.Initial) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListWorkspaceApiTokensWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&ListWorkspaceAPITokensResponseOK, nil) - mockClient.On("UpdateWorkspaceApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&UpdateWorkspaceAPITokenResponseOK, nil) - astroCoreClient = mockClient - cmdArgs := []string{"token", "update", "--name", tokenName1} - _, err := execWorkspaceCmd(cmdArgs...) - assert.Error(t, err) - }) - t.Run("token is updated", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.LocalPlatform) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListWorkspaceApiTokensWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&ListWorkspaceAPITokensResponseOK, nil) - mockClient.On("UpdateWorkspaceApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&UpdateWorkspaceAPITokenResponseOK, nil) - astroCoreClient = mockClient - cmdArgs := []string{"token", "update", "--name", tokenName1} - _, err := execWorkspaceCmd(cmdArgs...) - assert.NoError(t, err) - }) - t.Run("token is created with no ID provided", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.LocalPlatform) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListWorkspaceApiTokensWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&ListWorkspaceAPITokensResponseOK, nil) - mockClient.On("UpdateWorkspaceApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&UpdateWorkspaceAPITokenResponseOK, nil) - // mock os.Stdin - expectedInput := []byte("1") - r, w, err := os.Pipe() - assert.NoError(t, err) - _, err = w.Write(expectedInput) - assert.NoError(t, err) - w.Close() - stdin := os.Stdin - // Restore stdin right after the test. - defer func() { os.Stdin = stdin }() - os.Stdin = r - astroCoreClient = mockClient - cmdArgs := []string{"token", "update"} - _, err = execWorkspaceCmd(cmdArgs...) - assert.NoError(t, err) - }) -} - -func TestWorkspaceTokenRotate(t *testing.T) { - expectedHelp := "Rotate a Workspace API token" - testUtil.InitTestConfig(testUtil.LocalPlatform) - - t.Run("-h prints list help", func(t *testing.T) { - cmdArgs := []string{"token", "rotate", "-h"} - resp, err := execWorkspaceCmd(cmdArgs...) - assert.NoError(t, err) - assert.Contains(t, resp, expectedHelp) - }) - t.Run("any errors from api are returned and token is not rotated", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.LocalPlatform) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListWorkspaceApiTokensWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&ListWorkspaceAPITokensResponseOK, nil) - mockClient.On("RotateWorkspaceApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&RotateWorkspaceAPITokenResponseError, nil) - astroCoreClient = mockClient - cmdArgs := []string{"token", "rotate", "--name", tokenName1, "--force"} - _, err := execWorkspaceCmd(cmdArgs...) - assert.ErrorContains(t, err, "failed to rotate token") - }) - t.Run("any context errors from api are returned and token is not rotated", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.Initial) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListWorkspaceApiTokensWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&ListWorkspaceAPITokensResponseOK, nil).Twice() - mockClient.On("RotateWorkspaceApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&RotateWorkspaceAPITokenResponseOK, nil) - astroCoreClient = mockClient - cmdArgs := []string{"token", "rotate", "--name", tokenName1} - _, err := execWorkspaceCmd(cmdArgs...) - assert.Error(t, err) - }) - t.Run("token is rotated", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.LocalPlatform) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListWorkspaceApiTokensWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&ListWorkspaceAPITokensResponseOK, nil) - mockClient.On("RotateWorkspaceApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&RotateWorkspaceAPITokenResponseOK, nil) - astroCoreClient = mockClient - cmdArgs := []string{"token", "rotate", "--name", tokenName1, "--force"} - _, err := execWorkspaceCmd(cmdArgs...) - assert.NoError(t, err) - }) - t.Run("token is rotated with no ID provided", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.LocalPlatform) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListWorkspaceApiTokensWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&ListWorkspaceAPITokensResponseOK, nil) - mockClient.On("RotateWorkspaceApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&RotateWorkspaceAPITokenResponseOK, nil) - // mock os.Stdin - expectedInput := []byte("1") - r, w, err := os.Pipe() - assert.NoError(t, err) - _, err = w.Write(expectedInput) - assert.NoError(t, err) - w.Close() - stdin := os.Stdin - // Restore stdin right after the test. - defer func() { os.Stdin = stdin }() - os.Stdin = r - astroCoreClient = mockClient - cmdArgs := []string{"token", "rotate", "--force"} - _, err = execWorkspaceCmd(cmdArgs...) - assert.NoError(t, err) - }) - t.Run("token is rotated with and confirmed", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.LocalPlatform) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListWorkspaceApiTokensWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&ListWorkspaceAPITokensResponseOK, nil) - mockClient.On("RotateWorkspaceApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&RotateWorkspaceAPITokenResponseOK, nil) - // mock os.Stdin - expectedInput := []byte("y") - r, w, err := os.Pipe() - assert.NoError(t, err) - _, err = w.Write(expectedInput) - assert.NoError(t, err) - w.Close() - stdin := os.Stdin - // Restore stdin right after the test. - defer func() { os.Stdin = stdin }() - os.Stdin = r - astroCoreClient = mockClient - cmdArgs := []string{"token", "rotate", "--name", tokenName1} - _, err = execWorkspaceCmd(cmdArgs...) - assert.NoError(t, err) - }) -} - -func TestWorkspaceTokenDelete(t *testing.T) { - expectedHelp := "Delete a Workspace API token or remove an Organization API token from a Workspace" - testUtil.InitTestConfig(testUtil.LocalPlatform) - - t.Run("-h prints list help", func(t *testing.T) { - cmdArgs := []string{"token", "delete", "-h"} - resp, err := execWorkspaceCmd(cmdArgs...) - assert.NoError(t, err) - assert.Contains(t, resp, expectedHelp) - }) - t.Run("any errors from api are returned and token is not deleted", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.LocalPlatform) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListWorkspaceApiTokensWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&ListWorkspaceAPITokensResponseOK, nil) - mockClient.On("DeleteWorkspaceApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&DeleteWorkspaceAPITokenResponseError, nil) - astroCoreClient = mockClient - cmdArgs := []string{"token", "delete", "--name", tokenName1, "--force"} - _, err := execWorkspaceCmd(cmdArgs...) - assert.ErrorContains(t, err, "failed to delete token") - }) - t.Run("any context errors from api are returned and token is not deleted", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.Initial) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListWorkspaceApiTokensWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&ListWorkspaceAPITokensResponseOK, nil).Twice() - mockClient.On("DeleteWorkspaceApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&DeleteWorkspaceAPITokenResponseOK, nil) - astroCoreClient = mockClient - cmdArgs := []string{"token", "delete", "--name", tokenName1} - _, err := execWorkspaceCmd(cmdArgs...) - assert.Error(t, err) - }) - t.Run("token is deleted", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.LocalPlatform) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListWorkspaceApiTokensWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&ListWorkspaceAPITokensResponseOK, nil) - mockClient.On("DeleteWorkspaceApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&DeleteWorkspaceAPITokenResponseOK, nil) - astroCoreClient = mockClient - cmdArgs := []string{"token", "delete", "--name", tokenName1, "--force"} - _, err := execWorkspaceCmd(cmdArgs...) - assert.NoError(t, err) - }) - t.Run("token is deleted with no ID provided", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.LocalPlatform) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListWorkspaceApiTokensWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&ListWorkspaceAPITokensResponseOK, nil) - mockClient.On("DeleteWorkspaceApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&DeleteWorkspaceAPITokenResponseOK, nil) - // mock os.Stdin - expectedInput := []byte("1") - r, w, err := os.Pipe() - assert.NoError(t, err) - _, err = w.Write(expectedInput) - assert.NoError(t, err) - w.Close() - stdin := os.Stdin - // Restore stdin right after the test. - defer func() { os.Stdin = stdin }() - os.Stdin = r - astroCoreClient = mockClient - cmdArgs := []string{"token", "delete", "--force"} - _, err = execWorkspaceCmd(cmdArgs...) - assert.NoError(t, err) - }) - t.Run("token is delete with and confirmed", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.LocalPlatform) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListWorkspaceApiTokensWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&ListWorkspaceAPITokensResponseOK, nil) - mockClient.On("DeleteWorkspaceApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&DeleteWorkspaceAPITokenResponseOK, nil) - // mock os.Stdin - expectedInput := []byte("y") - r, w, err := os.Pipe() - assert.NoError(t, err) - _, err = w.Write(expectedInput) - assert.NoError(t, err) - w.Close() - stdin := os.Stdin - // Restore stdin right after the test. - defer func() { os.Stdin = stdin }() - os.Stdin = r - astroCoreClient = mockClient - cmdArgs := []string{"token", "delete", "--name", tokenName1} - _, err = execWorkspaceCmd(cmdArgs...) - assert.NoError(t, err) - }) -} - -var ( - tokenName2 = "Token 2" - apiToken2 = astrocore.ApiToken{Id: "token1", Name: "Token 1", Token: &token, Description: description1, Type: "Type 1", Roles: []astrocore.ApiTokenRole{{EntityId: mockWorkspaceID, EntityType: "WORKSPACE", Role: "WORKSPACE_MEMBER"}, {EntityId: mockOrganizationID, EntityType: "ORGANIZATION", Role: "ORGANIZATION_MEMBER"}}, CreatedAt: time.Now(), CreatedBy: &astrocore.BasicSubjectProfile{FullName: &fullName1}} - apiTokens2 = []astrocore.ApiToken{ - apiToken2, - {Id: "token2", Name: tokenName2, Description: description2, Type: "Type 2", Roles: []astrocore.ApiTokenRole{{EntityId: mockOrganizationID, EntityType: "ORGANIZATION", Role: "ORGANIZATION_MEMBER"}}, CreatedAt: time.Now(), CreatedBy: &astrocore.BasicSubjectProfile{FullName: &fullName2}}, - } - ListOrganizationAPITokensResponseOK = astrocore.ListOrganizationApiTokensResponse{ - HTTPResponse: &http.Response{ - StatusCode: 200, - }, - JSON200: &astrocore.ListApiTokensPaginated{ - ApiTokens: apiTokens2, - Limit: 1, - Offset: 0, - }, - } - errorOrgTokenList, _ = json.Marshal(astrocore.Error{ - Message: "failed to list tokens", - }) - ListOrganizationAPITokensResponseError = astrocore.ListOrganizationApiTokensResponse{ - HTTPResponse: &http.Response{ - StatusCode: 500, - }, - Body: errorOrgTokenList, - JSON200: nil, - } - UpdateOrganizationAPITokenResponseOK = astrocore.UpdateOrganizationApiTokenResponse{ - HTTPResponse: &http.Response{ - StatusCode: 200, - }, - JSON200: &apiToken1, - } - errorOrgTokenUpdate, _ = json.Marshal(astrocore.Error{ - Message: "failed to update tokens", - }) - UpdateOrganizationAPITokenResponseError = astrocore.UpdateOrganizationApiTokenResponse{ - HTTPResponse: &http.Response{ - StatusCode: 500, - }, - Body: errorOrgTokenUpdate, - JSON200: nil, - } -) - -func TestWorkspaceTokenAdd(t *testing.T) { - expectedHelp := "Add an Organization API token to an Astro Workspace" - orgTokenID = "" - testUtil.InitTestConfig(testUtil.LocalPlatform) - t.Run("-h prints list help", func(t *testing.T) { - cmdArgs := []string{"token", "add", "-h"} - resp, err := execWorkspaceCmd(cmdArgs...) - assert.NoError(t, err) - assert.Contains(t, resp, expectedHelp) - }) - t.Run("any errors from api are returned and token is not added", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.LocalPlatform) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListOrganizationApiTokensWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&ListOrganizationAPITokensResponseError, nil) - astroCoreClient = mockClient - cmdArgs := []string{"token", "add", "--org-token-name", tokenName1, "--role", "WORKSPACE_MEMBER"} - _, err := execWorkspaceCmd(cmdArgs...) - assert.ErrorContains(t, err, "failed to list tokens") - }) - t.Run("any context errors from api are returned and token is not added", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.Initial) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListOrganizationApiTokensWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&ListOrganizationAPITokensResponseOK, nil) - mockClient.On("UpdateOrganizationApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&UpdateOrganizationAPITokenResponseOK, nil) - astroCoreClient = mockClient - cmdArgs := []string{"token", "add", "--org-token-name", tokenName1, "--role", "WORKSPACE_MEMBER"} - _, err := execWorkspaceCmd(cmdArgs...) - assert.Error(t, err) - }) - t.Run("token is added", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.LocalPlatform) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListOrganizationApiTokensWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&ListOrganizationAPITokensResponseOK, nil) - mockClient.On("UpdateOrganizationApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&UpdateOrganizationAPITokenResponseOK, nil) - astroCoreClient = mockClient - mockIamClient := new(astroiamcore_mocks.ClientWithResponsesInterface) - mockIamClient.On("GetApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&GetAPITokensResponseOKOrganizationToken, nil) - astroCoreIamClient = mockIamClient - - cmdArgs := []string{"token", "add", "--org-token-name", tokenName2, "--role", "WORKSPACE_MEMBER"} - _, err := execWorkspaceCmd(cmdArgs...) - assert.NoError(t, err) - }) - t.Run("token is created with no ID provided", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.LocalPlatform) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListOrganizationApiTokensWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&ListOrganizationAPITokensResponseOK, nil) - mockClient.On("UpdateOrganizationApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&UpdateOrganizationAPITokenResponseOK, nil) - // mock os.Stdin - expectedInput := []byte("2") - r, w, err := os.Pipe() - assert.NoError(t, err) - _, err = w.Write(expectedInput) - assert.NoError(t, err) - w.Close() - stdin := os.Stdin - // Restore stdin right after the test. - defer func() { os.Stdin = stdin }() - os.Stdin = r - astroCoreClient = mockClient - cmdArgs := []string{"token", "add", "--role", "WORKSPACE_MEMBER"} - _, err = execWorkspaceCmd(cmdArgs...) - assert.NoError(t, err) - }) - t.Run("token is added with no role provided", func(t *testing.T) { - testUtil.InitTestConfig(testUtil.LocalPlatform) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListOrganizationApiTokensWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&ListOrganizationAPITokensResponseOK, nil) - mockClient.On("UpdateOrganizationApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&UpdateOrganizationAPITokenResponseOK, nil) - mockIamClient := new(astroiamcore_mocks.ClientWithResponsesInterface) - mockIamClient.On("GetApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&GetAPITokensResponseOKOrganizationToken, nil) - astroCoreIamClient = mockIamClient - - // mock os.Stdin - expectedInput := []byte("1") - r, w, err := os.Pipe() - assert.NoError(t, err) - _, err = w.Write(expectedInput) - assert.NoError(t, err) - w.Close() - stdin := os.Stdin - // Restore stdin right after the test. - defer func() { os.Stdin = stdin }() - os.Stdin = r - astroCoreClient = mockClient - cmdArgs := []string{"token", "add", "--org-token-name", tokenName2} - _, err = execWorkspaceCmd(cmdArgs...) - assert.NoError(t, err) - }) -} - -func TestAddOrganizationTokenWorkspaceRole(t *testing.T) { - testUtil.InitTestConfig(testUtil.LocalPlatform) - t.Run("happy path Create", func(t *testing.T) { - mockIamClient := new(astroiamcore_mocks.ClientWithResponsesInterface) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListOrganizationApiTokensWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&ListOrganizationAPITokensResponseOK, nil).Once() - mockClient.On("UpdateOrganizationApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&UpdateOrganizationAPITokenResponseOK, nil).Once() - mockIamClient.On("GetApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&GetAPITokensResponseOKOrganizationToken, nil).Once() - - defer testUtil.MockUserInput(t, "1")() - astroCoreClient = mockClient - astroCoreIamClient = mockIamClient - - cmdArgs := []string{"token", "organization-token", "add", "--role", "DEPLOYMENT_ADMIN"} - _, err := execWorkspaceCmd(cmdArgs...) - assert.NoError(t, err) - }) - - t.Run("happy path with token id passed in", func(t *testing.T) { - mockIamClient := new(astroiamcore_mocks.ClientWithResponsesInterface) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("UpdateOrganizationApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&UpdateOrganizationAPITokenResponseOK, nil).Once() - mockIamClient.On("GetApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&GetAPITokensResponseOKOrganizationToken, nil).Once() - // mock os.Stdin - defer testUtil.MockUserInput(t, "DEPLOYMENT_ADMIN")() - astroCoreClient = mockClient - astroCoreIamClient = mockIamClient - cmdArgs := []string{"token", "organization-token", "add", "token-id"} - _, err := execWorkspaceCmd(cmdArgs...) - assert.NoError(t, err) - }) -} - -func TestUpdateOrganizationTokenWorkspaceRole(t *testing.T) { - testUtil.InitTestConfig(testUtil.LocalPlatform) - - t.Run("happy path Update", func(t *testing.T) { - mockIamClient := new(astroiamcore_mocks.ClientWithResponsesInterface) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListWorkspaceApiTokensWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&ListWorkspaceAPITokensResponseOK, nil).Once() - mockClient.On("UpdateOrganizationApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&UpdateOrganizationAPITokenResponseOK, nil).Once() - mockIamClient.On("GetApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&GetAPITokensResponseOKOrganizationToken, nil).Once() - defer testUtil.MockUserInput(t, "1")() - - astroCoreClient = mockClient - astroCoreIamClient = mockIamClient - cmdArgs := []string{"token", "organization-token", "update", "--role", "DEPLOYMENT_ADMIN"} - _, err := execWorkspaceCmd(cmdArgs...) - assert.NoError(t, err) - }) - - t.Run("happy path with token id passed in", func(t *testing.T) { - mockIamClient := new(astroiamcore_mocks.ClientWithResponsesInterface) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("UpdateOrganizationApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&UpdateOrganizationAPITokenResponseOK, nil).Once() - mockIamClient.On("GetApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&GetAPITokensResponseOKOrganizationToken, nil).Once() - // mock os.Stdin - defer testUtil.MockUserInput(t, "DEPLOYMENT_ADMIN")() - astroCoreClient = mockClient - astroCoreIamClient = mockIamClient - cmdArgs := []string{"token", "organization-token", "update", "token-id"} - _, err := execWorkspaceCmd(cmdArgs...) - assert.NoError(t, err) - }) -} - -func TestRemoveOrganizationTokenWorkspaceRole(t *testing.T) { - testUtil.InitTestConfig(testUtil.LocalPlatform) - t.Run("happy path", func(t *testing.T) { - mockIamClient := new(astroiamcore_mocks.ClientWithResponsesInterface) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListWorkspaceApiTokensWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&ListWorkspaceAPITokensResponseOK, nil).Once() - mockClient.On("UpdateOrganizationApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&UpdateOrganizationAPITokenResponseOK, nil).Once() - mockIamClient.On("GetApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&GetAPITokensResponseOKOrganizationToken, nil).Once() - defer testUtil.MockUserInput(t, "1")() - astroCoreClient = mockClient - astroCoreIamClient = mockIamClient - cmdArgs := []string{"token", "organization-token", "remove"} - _, err := execWorkspaceCmd(cmdArgs...) - assert.NoError(t, err) - }) - - t.Run("happy path with token id passed in", func(t *testing.T) { - mockIamClient := new(astroiamcore_mocks.ClientWithResponsesInterface) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("UpdateOrganizationApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&UpdateOrganizationAPITokenResponseOK, nil).Once() - mockIamClient.On("GetApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&GetAPITokensResponseOKOrganizationToken, nil).Once() - astroCoreClient = mockClient - astroCoreIamClient = mockIamClient - cmdArgs := []string{"token", "organization-token", "remove", "token-id"} - _, err := execWorkspaceCmd(cmdArgs...) - assert.NoError(t, err) - }) -} - -func TestListOrganizationTokenWorkspaceRoles(t *testing.T) { - testUtil.InitTestConfig(testUtil.LocalPlatform) - t.Run("happy path", func(t *testing.T) { - mockIamClient := new(astroiamcore_mocks.ClientWithResponsesInterface) - mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockClient.On("ListWorkspaceApiTokensWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&ListWorkspaceAPITokensResponseOK, nil).Once() - defer testUtil.MockUserInput(t, "1")() - astroCoreClient = mockClient - astroCoreIamClient = mockIamClient - cmdArgs := []string{"token", "organization-token", "list"} - _, err := execWorkspaceCmd(cmdArgs...) - assert.NoError(t, err) - }) -} diff --git a/cmd/root.go b/cmd/root.go index df533b932..2eedf61d0 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -14,11 +14,15 @@ import ( "github.com/astronomer/astro-cli/cmd/utils" "github.com/astronomer/astro-cli/context" "github.com/astronomer/astro-cli/houston" + nexuscmds "github.com/astronomer/astro-cli/internal/nexus" "github.com/astronomer/astro-cli/pkg/ansi" "github.com/astronomer/astro-cli/pkg/httputil" "github.com/astronomer/astro-cli/pkg/telemetry" + + nexusapp "github.com/astronomer/nexus/app" "github.com/sirupsen/logrus" "github.com/spf13/cobra" + "github.com/spf13/pflag" ) var ( @@ -92,11 +96,17 @@ Welcome to the Astro CLI, the modern command line interface for data orchestrati newTelemetrySendCmd(), ) - if context.IsCloudContext() { // Include all the commands to be exposed for cloud users - rootCmd.AddCommand( - cloudCmd.AddCmds(platformCoreClient, astroCoreClient, airflowClient, astroCoreIamClient, os.Stdout)..., + if context.IsCloudContext() { + // Register nexus API commands first (noun-verb format), + // then merge cloud commands so they fill gaps nexus doesn't cover. + nexusCtx, nexusErr := nexusapp.NewContextWithConfigProvider(&nexuscmds.ConfigAdapter{}) + if nexusErr == nil { + nexuscmds.RegisterNounVerb(rootCmd, nexusCtx) + } + mergeCloudCommands(rootCmd, + cloudCmd.AddCmds(platformCoreClient, astroCoreClient, airflowClient, astroCoreIamClient, os.Stdout), ) - } else { // Include all the commands to be exposed for software users + } else { rootCmd.AddCommand( softwareCmd.AddCmds(houstonClient, os.Stdout)..., ) @@ -109,6 +119,50 @@ Welcome to the Astro CLI, the modern command line interface for data orchestrati return rootCmd } +// mergeCloudCommands adds cloud commands to the root, filling gaps that nexus +// didn't cover. For nouns that nexus already registered (e.g. "deployment"), +// cloud's subcommands and metadata are merged onto the existing parent. +// If the cloud command is directly runnable (has RunE) and the nexus noun is +// only a group, the RunE and associated properties are preserved so the +// command remains executable (e.g. "astro deploy " still works alongside +// "astro deploy create/list/..."). +func mergeCloudCommands(rootCmd *cobra.Command, cloudCmds []*cobra.Command) { + for _, cc := range cloudCmds { + existing := nexuscmds.FindSubcommand(rootCmd, cc.Name()) + if existing == nil { + rootCmd.AddCommand(cc) + continue + } + existing.PersistentFlags().AddFlagSet(cc.PersistentFlags()) + if len(existing.Aliases) == 0 { + existing.Aliases = cc.Aliases + } + if existing.Long == "" { + existing.Long = cc.Long + } + if existing.Short == "" { + existing.Short = cc.Short + } + if cc.RunE != nil && existing.RunE == nil && existing.Run == nil { + existing.RunE = cc.RunE + existing.Args = cc.Args + existing.PreRunE = cc.PreRunE + existing.Example = cc.Example + existing.Use = cc.Use + cc.Flags().VisitAll(func(f *pflag.Flag) { + if existing.Flags().Lookup(f.Name) == nil { + existing.Flags().AddFlag(f) + } + }) + } + for _, sub := range cc.Commands() { + if nexuscmds.FindSubcommand(existing, sub.Name()) == nil { + existing.AddCommand(sub) + } + } + } +} + func getResourcesHelpTemplate(houstonVersion, ctx string) string { return fmt.Sprintf(`{{with (or .Long .Short)}}{{. | trimTrailingWhitespaces}} diff --git a/cmd/root_hooks.go b/cmd/root_hooks.go index 5a1e723e3..ee98ae32a 100644 --- a/cmd/root_hooks.go +++ b/cmd/root_hooks.go @@ -13,6 +13,7 @@ import ( softwareCmd "github.com/astronomer/astro-cli/cmd/software" "github.com/astronomer/astro-cli/config" "github.com/astronomer/astro-cli/context" + nexuscmds "github.com/astronomer/astro-cli/internal/nexus" "github.com/astronomer/astro-cli/version" "github.com/spf13/cobra" ) @@ -27,6 +28,10 @@ func SetupLogging(_ *cobra.Command, _ []string) error { // pre-run hook that sets up the context and checks for the latest version. func CreateRootPersistentPreRunE(astroCoreClient astrocore.CoreClient, platformCoreClient astroplatformcore.CoreClient) func(cmd *cobra.Command, args []string) error { return func(cmd *cobra.Command, args []string) error { + if isShellCompletion() { + return nil + } + // Check for latest version if config.CFG.UpgradeMessage.GetBool() { // create http client with 3 second timeout, setting an aggressive timeout since its not mandatory to get a response in each command execution @@ -39,6 +44,9 @@ func CreateRootPersistentPreRunE(astroCoreClient astrocore.CoreClient, platformC } } if context.IsCloudContext() { + if isNexusCommand(cmd) { + return nil + } err := cloudCmd.Setup(cmd, platformCoreClient, astroCoreClient) if err != nil { if strings.Contains(err.Error(), "token is invalid or malformed") { @@ -54,3 +62,17 @@ func CreateRootPersistentPreRunE(astroCoreClient astrocore.CoreClient, platformC return nil } } + +func isShellCompletion() bool { + return len(os.Args) >= 2 && + (os.Args[1] == "__complete" || os.Args[1] == "__completeNoDesc") +} + +func isNexusCommand(cmd *cobra.Command) bool { + for c := cmd; c != nil; c = c.Parent() { + if c.Annotations[nexuscmds.NexusCommandAnnotation] == "true" { + return true + } + } + return false +} diff --git a/config/context.go b/config/context.go index 80dc7b8a5..281c42ad9 100644 --- a/config/context.go +++ b/config/context.go @@ -31,6 +31,7 @@ type Context struct { OrganizationProduct string `mapstructure:"organization_product"` Workspace string `mapstructure:"workspace"` LastUsedWorkspace string `mapstructure:"last_used_workspace"` + Deployment string `mapstructure:"deployment"` Token string `mapstructure:"token"` RefreshToken string `mapstructure:"refreshtoken"` UserEmail string `mapstructure:"user_email"` @@ -129,6 +130,7 @@ func (c *Context) SetContext() error { "organization_product": c.OrganizationProduct, "workspace": c.Workspace, "last_used_workspace": c.Workspace, + "deployment": c.Deployment, "refreshtoken": c.RefreshToken, "user_email": c.UserEmail, } diff --git a/config/context_test.go b/config/context_test.go index 42fefaa55..bcb51fa07 100644 --- a/config/context_test.go +++ b/config/context_test.go @@ -196,7 +196,7 @@ func (s *Suite) TestGetContexts() { initTestConfig() ctxs, err := GetContexts() s.NoError(err) - s.Equal(Contexts{Contexts: map[string]Context{"test_com": {"test.com", "test-org-id", "", "ck05r3bor07h40d02y2hw4n4v", "ck05r3bor07h40d02y2hw4n4v", "token", "", ""}, "example_com": {"example.com", "test-org-id", "", "ck05r3bor07h40d02y2hw4n4v", "ck05r3bor07h40d02y2hw4n4v", "token", "", ""}}}, ctxs) + s.Equal(Contexts{Contexts: map[string]Context{"test_com": {"test.com", "test-org-id", "", "ck05r3bor07h40d02y2hw4n4v", "ck05r3bor07h40d02y2hw4n4v", "", "token", "", ""}, "example_com": {"example.com", "test-org-id", "", "ck05r3bor07h40d02y2hw4n4v", "ck05r3bor07h40d02y2hw4n4v", "", "token", "", ""}}}, ctxs) } func (s *Suite) TestSetContextKey() { diff --git a/go.mod b/go.mod index 250f6f87b..3b44783bf 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/astronomer/astro-cli -go 1.24.0 +go 1.26.0 require ( github.com/Masterminds/semver v1.5.0 @@ -23,12 +23,13 @@ require ( github.com/spf13/cobra v1.8.1 github.com/spf13/viper v1.19.0 github.com/stretchr/testify v1.11.1 - golang.org/x/net v0.38.0 - golang.org/x/sys v0.38.0 + golang.org/x/net v0.50.0 + golang.org/x/sys v0.41.0 ) require ( github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v0.4.1 + github.com/astronomer/nexus v0.0.0-20260306172228-4e964556d2d9 github.com/compose-spec/compose-go/v2 v2.4.5 github.com/distribution/reference v0.6.0 github.com/fatih/camelcase v1.0.0 @@ -48,9 +49,9 @@ require ( github.com/vektra/mockery/v2 v2.50.0 github.com/whilp/git-urls v1.0.0 go.yaml.in/yaml/v4 v4.0.0-rc.4 - golang.org/x/exp v0.0.0-20241210194714-1829a127f884 - golang.org/x/mod v0.22.0 - golang.org/x/term v0.30.0 + golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa + golang.org/x/mod v0.33.0 + golang.org/x/term v0.40.0 gopkg.in/yaml.v3 v3.0.1 ) @@ -72,7 +73,9 @@ require ( github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.0 // indirect github.com/OpenPeeDeeP/depguard/v2 v2.2.0 // indirect github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect + github.com/alecthomas/assert/v2 v2.11.0 // indirect github.com/alecthomas/go-check-sumtype v0.2.0 // indirect + github.com/alecthomas/repr v0.5.2 // indirect github.com/alexkohler/nakedret/v2 v2.0.5 // indirect github.com/alexkohler/prealloc v1.0.0 // indirect github.com/alingse/asasalint v0.0.11 // indirect @@ -108,14 +111,14 @@ require ( github.com/chigopher/pathlib v0.19.1 // indirect github.com/chzyer/readline v1.5.1 // indirect github.com/ckaznocha/intrange v0.2.1 // indirect - github.com/clipperhouse/stringish v0.1.1 // indirect - github.com/clipperhouse/uax29/v2 v2.3.0 // indirect + github.com/clipperhouse/uax29/v2 v2.7.0 // indirect github.com/containerd/containerd/api v1.8.0 // indirect github.com/containerd/errdefs v1.0.0 // indirect github.com/containerd/log v0.1.0 // indirect github.com/containerd/platforms v0.2.1 // indirect github.com/containerd/ttrpc v1.2.7 // indirect github.com/containerd/typeurl/v2 v2.2.3 // indirect + github.com/creack/pty v1.1.24 // indirect github.com/curioswitch/go-reassign v0.3.0 // indirect github.com/cyphar/filepath-securejoin v0.2.4 // indirect github.com/daixiang0/gci v0.13.5 // indirect @@ -198,7 +201,7 @@ require ( github.com/maratori/testableexamples v1.0.0 // indirect github.com/maratori/testpackage v1.1.1 // indirect github.com/matoous/godox v0.0.0-20241202171805-94d1edd68ebb // indirect - github.com/mattn/go-runewidth v0.0.19 // indirect + github.com/mattn/go-runewidth v0.0.20 // indirect github.com/mgechev/revive v1.5.1 // indirect github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect @@ -301,7 +304,7 @@ require ( go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect golang.org/x/exp/typeparams v0.0.0-20241210194714-1829a127f884 // indirect - golang.org/x/tools v0.28.0 // indirect + golang.org/x/tools v0.42.0 // indirect google.golang.org/genproto v0.0.0-20241209162323-e6fa225c2576 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576 // indirect @@ -379,7 +382,7 @@ require ( github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/common v0.61.0 // indirect github.com/prometheus/procfs v0.15.1 // indirect - github.com/spf13/cast v1.7.0 // indirect + github.com/spf13/cast v1.10.0 // indirect github.com/spf13/pflag v1.0.5 github.com/stretchr/objx v0.5.2 // indirect github.com/subosito/gotenv v1.6.0 // indirect @@ -389,10 +392,10 @@ require ( github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/xeipuuv/gojsonschema v1.2.0 // indirect - golang.org/x/crypto v0.36.0 // indirect + golang.org/x/crypto v0.48.0 // indirect golang.org/x/oauth2 v0.27.0 // indirect golang.org/x/sync v0.19.0 // indirect - golang.org/x/text v0.23.0 // indirect + golang.org/x/text v0.34.0 // indirect golang.org/x/time v0.8.0 // indirect google.golang.org/grpc v1.68.1 // indirect google.golang.org/protobuf v1.35.2 // indirect diff --git a/go.sum b/go.sum index 4b9b34479..266b025b0 100644 --- a/go.sum +++ b/go.sum @@ -70,12 +70,12 @@ github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMx github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8= github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo= github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c= -github.com/alecthomas/assert/v2 v2.2.2 h1:Z/iVC0xZfWTaFNE6bA3z07T86hd45Xe2eLt6WVy2bbk= -github.com/alecthomas/assert/v2 v2.2.2/go.mod h1:pXcQ2Asjp247dahGEmsZ6ru0UVwnkhktn7S0bBDLxvQ= +github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= +github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= github.com/alecthomas/go-check-sumtype v0.2.0 h1:Bo+e4DFf3rs7ME9w/0SU/g6nmzJaphduP8Cjiz0gbwY= github.com/alecthomas/go-check-sumtype v0.2.0/go.mod h1:WyYPfhfkdhyrdaligV6svFopZV8Lqdzn5pyVBaV6jhQ= -github.com/alecthomas/repr v0.2.0 h1:HAzS41CIzNW5syS8Mf9UwXhNH1J9aix/BvDRf1Ml2Yk= -github.com/alecthomas/repr v0.2.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= +github.com/alecthomas/repr v0.5.2 h1:SU73FTI9D1P5UNtvseffFSGmdNci/O6RsqzeXJtP0Qs= +github.com/alecthomas/repr v0.5.2/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alexkohler/nakedret/v2 v2.0.5 h1:fP5qLgtwbx9EJE8dGEERT02YwS8En4r9nnZ71RK+EVU= @@ -95,6 +95,8 @@ github.com/ashanbrown/forbidigo v1.6.0 h1:D3aewfM37Yb3pxHujIPSpTf6oQk9sc9WZi8ger github.com/ashanbrown/forbidigo v1.6.0/go.mod h1:Y8j9jy9ZYAEHXdu723cUlraTqbzjKF1MUyfOKL+AjcU= github.com/ashanbrown/makezero v1.2.0 h1:/2Lp1bypdmK9wDIq7uWBlDF1iMUpIIS4A+pF6C9IEUU= github.com/ashanbrown/makezero v1.2.0/go.mod h1:dxlPhHbDMC6N6xICzFBSK+4njQDdK8euNO0qjQMtGY4= +github.com/astronomer/nexus v0.0.0-20260306172228-4e964556d2d9 h1:WV5uN112CnNeky6Vas8oy9P09wJPxSS96Vg5k2H/0Zg= +github.com/astronomer/nexus v0.0.0-20260306172228-4e964556d2d9/go.mod h1:0FNZEWFD5QX/4LOxiKm8y4roGTgEKacLg4FkLyz9nGU= github.com/aws/aws-sdk-go-v2 v1.32.6 h1:7BokKRgRPuGmKkFMhEg/jSul+tB9VvXhcViILtfG8b4= github.com/aws/aws-sdk-go-v2 v1.32.6/go.mod h1:P5WJBrYqqbWVaOxgH0X/FYYD47/nooaPOZPlQdmiN2U= github.com/aws/aws-sdk-go-v2/config v1.28.6 h1:D89IKtGrs/I3QXOLNTH93NJYtDhm8SYa9Q5CsPShmyo= @@ -186,10 +188,8 @@ github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38 github.com/ckaznocha/intrange v0.2.1 h1:M07spnNEQoALOJhwrImSrJLaxwuiQK+hA2DeajBlwYk= github.com/ckaznocha/intrange v0.2.1/go.mod h1:7NEhVyf8fzZO5Ds7CRaqPEm52Ut83hsTiL5zbER/HYk= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs= -github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA= -github.com/clipperhouse/uax29/v2 v2.3.0 h1:SNdx9DVUqMoBuBoW3iLOj4FQv3dN5mDtuqwuhIGpJy4= -github.com/clipperhouse/uax29/v2 v2.3.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g= +github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk= +github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM= github.com/cloudflare/backoff v0.0.0-20161212185259-647f3cdfc87a/go.mod h1:rzgs2ZOiguV6/NpiDgADjRLPNyZlApIWxKpkT+X8SdY= github.com/cloudflare/cfssl v0.0.0-20180223231731-4e2dcbde5004/go.mod h1:yMWuSON2oQp+43nFtAV/uvKQIFpSPerB57DCt9t8sSA= github.com/cloudflare/cfssl v1.4.1 h1:vScfU2DrIUI9VPHBVeeAQ0q5A+9yshO1Gz+3QoUQiKw= @@ -234,8 +234,8 @@ github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSV github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= -github.com/creack/pty v1.1.21 h1:1/QdRyBaHHJP61QkWMXlOIBfsgdDeeKfK8SYVUWJKf0= -github.com/creack/pty v1.1.21/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= +github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s= +github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE= github.com/curioswitch/go-reassign v0.3.0 h1:dh3kpQHuADL3cobV/sSGETA8DOv457dwl+fbBAhrQPs= github.com/curioswitch/go-reassign v0.3.0/go.mod h1:nApPCCTtqLJN/s8HfItCcKV0jIPwluBOvZP+dsJGA88= github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= @@ -649,8 +649,8 @@ github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= -github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw= -github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= +github.com/mattn/go-runewidth v0.0.20 h1:WcT52H91ZUAwy8+HUkdM3THM6gXqXuLJi9O3rjcQQaQ= +github.com/mattn/go-runewidth v0.0.20/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk= github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= github.com/mattn/go-sqlite3 v1.6.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= @@ -921,8 +921,8 @@ github.com/speakeasy-api/openapi-overlay v0.9.0/go.mod h1:f5FloQrHA7MsxYg9djzMD5 github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= github.com/spf13/cast v0.0.0-20150508191742-4d07383ffe94/go.mod h1:r2rcYCSwa1IExKTDiTfzaxqT2FNHs8hODu4LnUfgKEg= -github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w= -github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY= +github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= github.com/spf13/cobra v0.0.1/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= @@ -1104,11 +1104,11 @@ golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9/go.mod h1:jdWPYTVW3xRLrWP golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= -golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= -golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= +golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= +golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20241210194714-1829a127f884 h1:Y/Mj/94zIQQGHVSv1tTtQBDaQaJe62U9bkDZKKyhPCU= -golang.org/x/exp v0.0.0-20241210194714-1829a127f884/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c= +golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa h1:Zt3DZoOFFYkKhDT3v7Lm9FDMEV06GpzjG2jrqW+QTE0= +golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa/go.mod h1:K79w1Vqn7PoiZn+TkNpx3BUWUQksGO3JcVX6qIjytmA= golang.org/x/exp/typeparams v0.0.0-20220428152302-39d4317da171/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/exp/typeparams v0.0.0-20230203172020-98cc5a0785f9/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/exp/typeparams v0.0.0-20241210194714-1829a127f884 h1:1xaZTydL5Gsg78QharTwKfA9FY9CZ1VQj6D/AZEvHR0= @@ -1128,8 +1128,8 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= -golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8= +golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1159,8 +1159,8 @@ golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= -golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= -golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60= +golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M= @@ -1226,8 +1226,8 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= -golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= +golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -1239,8 +1239,8 @@ golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/term v0.14.0/go.mod h1:TySc+nGkYR6qt8km8wUhuFRTVSMIX3XPR58y2lC8vww= -golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= -golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= +golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg= +golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= @@ -1253,8 +1253,8 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= -golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= +golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= +golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg= golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= @@ -1287,8 +1287,12 @@ golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= -golang.org/x/tools v0.28.0 h1:WuB6qZ4RPCQo5aP3WdKZS7i595EdWqWR8vqJTlwTVK8= -golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw= +golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k= +golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0= +golang.org/x/tools/go/expect v0.1.1-deprecated h1:jpBZDwmgPhXsKZC6WhL20P4b/wmnpsEAGHaNy0n/rJM= +golang.org/x/tools/go/expect v0.1.1-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY= +golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated h1:1h2MnaIAIXISqTFKdENegdpAgUXz6NrPEsbIeWaBRvM= +golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated/go.mod h1:RVAQXBGNv1ib0J382/DPCRS/BPnsGebyM1Gj5VSDpG8= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/internal/nexus/commands.go b/internal/nexus/commands.go new file mode 100644 index 000000000..8907b6ac9 --- /dev/null +++ b/internal/nexus/commands.go @@ -0,0 +1,244 @@ +package nexus + +import ( + "os" + "strings" + "sync" + + "github.com/astronomer/nexus/app" + "github.com/astronomer/nexus/restish" + + "github.com/iancoleman/strcase" + "github.com/spf13/cobra" + "github.com/spf13/pflag" +) + +// NexusCommandAnnotation is the cobra annotation key used to mark commands +// powered by nexus/restish. The pre-run hook checks this to skip cloud setup. +const NexusCommandAnnotation = "nexus.command" + +type nexusCLI struct { + ctx *app.Context +} + +type commandEntry struct { + flatName string // original restish name, e.g. "create-deployment" + verb string // e.g. "create" + desc string +} + +// parseNounVerb splits a flat restish command name into (noun, verb). +// e.g. "create-deployment" -> ("deployment", "create"), +// +// "list-deployments" -> ("deployment", "list"). +func parseNounVerb(cmdName string) (noun, verb string, ok bool) { + verb, noun, ok = strings.Cut(cmdName, "-") + if !ok { + return "", "", false + } + noun = strings.TrimSuffix(noun, "s") + return noun, verb, true +} + +// FindSubcommand returns the child of parent with the given Use name, or nil. +func FindSubcommand(parent *cobra.Command, name string) *cobra.Command { + for _, c := range parent.Commands() { + if c.Name() == name { + return c + } + } + return nil +} + +// RegisterNounVerb registers restish API commands in noun-verb format directly +// on the root command (e.g. "astro deployment create" instead of +// "astro nexus create-deployment"). +func RegisterNounVerb(rootCmd *cobra.Command, ctx *app.Context) { + n := &nexusCLI{ctx: ctx} + api := ctx.Config.GetDefaultAPI() + if api == "" { + return + } + + if isCompletionMode() && !restish.IsHelpCached(api, "") { + return + } + + parser, err := restish.NewHelpParser(api, "") + if err != nil { + return + } + + groups := make(map[string][]commandEntry) + for _, cmd := range parser.ParseCommandsSection() { + noun, verb, ok := parseNounVerb(cmd.Name) + if !ok { + continue + } + groups[noun] = append(groups[noun], commandEntry{ + flatName: cmd.Name, + verb: verb, + desc: cmd.Description, + }) + } + + for noun, entries := range groups { + nounCmd := FindSubcommand(rootCmd, noun) + if nounCmd == nil { + nounCmd = &cobra.Command{ + Use: noun, + Short: "Manage " + noun + "s", + } + rootCmd.AddCommand(nounCmd) + } + + for _, e := range entries { + flatName := e.flatName + verbCmd := &cobra.Command{ + Use: e.verb + " [args...]", + Short: e.desc, + Annotations: map[string]string{NexusCommandAnnotation: "true"}, + SilenceErrors: true, + SilenceUsage: true, + DisableFlagParsing: true, + RunE: func(cmd *cobra.Command, args []string) error { + return n.runRestishCommand(flatName, args) + }, + } + + verbCmd.SetHelpFunc(n.makeAPIHelpFunc(api, flatName)) + verbCmd.SetUsageFunc(n.makeAPIUsageFunc(api, flatName)) + verbCmd.ValidArgsFunction = n.makeLazyArgsCompleter(api, flatName) + + nounCmd.AddCommand(verbCmd) + } + } +} + +func isCompletionMode() bool { + return len(os.Args) >= 2 && + (os.Args[1] == "__complete" || os.Args[1] == "__completeNoDesc") +} + +var ( + flagsLoaded = make(map[string]bool) + flagsLoadedMu sync.Mutex +) + +func (n *nexusCLI) loadCommandFlags(api, cmdName string, cmd *cobra.Command) { + flagsLoadedMu.Lock() + key := api + ":" + cmdName + if flagsLoaded[key] { + flagsLoadedMu.Unlock() + return + } + flagsLoaded[key] = true + flagsLoadedMu.Unlock() + + if isCompletionMode() && !restish.IsHelpCached(api, cmdName) { + return + } + + registered := make(map[string]bool) + cmd.Flags().VisitAll(func(f *pflag.Flag) { + registered[f.Name] = true + }) + + schemaParser, err := restish.NewSchemaParser(api, cmdName) + if err == nil { + fields := schemaParser.GetFields(-1) + for _, field := range fields { + flagName := strcase.ToKebab(field.Name) + if registered[flagName] { + continue + } + registered[flagName] = true + cmd.Flags().String(flagName, "", field.Description) + + if field.Enum != "" { + enumValues := strings.Split(field.Enum, ",") + for i := range enumValues { + enumValues[i] = strings.TrimSpace(enumValues[i]) + } + _ = cmd.Flags().SetAnnotation(flagName, "enum", enumValues) + } + } + } + + helpParser, err := restish.NewHelpParser(api, cmdName) + if err == nil { + flags := helpParser.ParseFlagsSection() + for _, flag := range flags { + if registered[flag.Name] { + continue + } + registered[flag.Name] = true + switch flag.FlagType { + case "boolean", "bool": + cmd.Flags().Bool(flag.Name, false, flag.Description) + default: + cmd.Flags().String(flag.Name, "", flag.Description) + } + } + } +} + +func (n *nexusCLI) makeLazyArgsCompleter(api, cmdName string) func(*cobra.Command, []string, string) ([]string, cobra.ShellCompDirective) { + return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + n.loadCommandFlags(api, cmdName, cmd) + + if strings.HasPrefix(toComplete, "-") { + return n.completeFlagNames(cmd, toComplete) + } + + if len(args) > 0 { + prevArg := args[len(args)-1] + if strings.HasPrefix(prevArg, "--") { + return n.completeFlagValues(cmd, strings.TrimPrefix(prevArg, "--")) + } + } + + return nil, cobra.ShellCompDirectiveNoFileComp + } +} + +func (n *nexusCLI) completeFlagNames(cmd *cobra.Command, prefix string) ([]string, cobra.ShellCompDirective) { + search := strings.TrimLeft(prefix, "-") + var completions []string + cmd.Flags().VisitAll(func(f *pflag.Flag) { + if strings.HasPrefix(f.Name, search) { + if f.Usage != "" { + completions = append(completions, "--"+f.Name+"\t"+f.Usage) + } else { + completions = append(completions, "--"+f.Name) + } + } + }) + return completions, cobra.ShellCompDirectiveNoFileComp +} + +func (n *nexusCLI) completeFlagValues(cmd *cobra.Command, flagName string) ([]string, cobra.ShellCompDirective) { + f := cmd.Flags().Lookup(flagName) + if f == nil { + return nil, cobra.ShellCompDirectiveNoFileComp + } + + if enums, ok := f.Annotations["enum"]; ok && len(enums) > 0 { + return enums, cobra.ShellCompDirectiveNoFileComp + } + + return nil, cobra.ShellCompDirectiveNoFileComp +} + +func (n *nexusCLI) makeAPIHelpFunc(api, cmdName string) func(*cobra.Command, []string) { + return func(cmd *cobra.Command, args []string) { + n.showCommandHelp(api, cmdName) + } +} + +func (n *nexusCLI) makeAPIUsageFunc(api, cmdName string) func(*cobra.Command) error { + return func(cmd *cobra.Command) error { + n.showCommandHelp(api, cmdName) + return nil + } +} diff --git a/internal/nexus/config_adapter.go b/internal/nexus/config_adapter.go new file mode 100644 index 000000000..4ffa9ac48 --- /dev/null +++ b/internal/nexus/config_adapter.go @@ -0,0 +1,125 @@ +package nexus + +import ( + "regexp" + "strings" + + astroconfig "github.com/astronomer/astro-cli/config" + "github.com/astronomer/nexus/shared" +) + +// domainEnvRegex extracts the environment suffix (dev, stage, perf) from an +// Astronomer cloud domain. Matches domains like "astronomer-dev.io", +// "cloud.astronomer-stage.io", "pr1234.astronomer-dev.io", etc. +var domainEnvRegex = regexp.MustCompile(`astronomer(?:-(dev|stage|perf))?\.io`) + +// ConfigAdapter implements nexus config.ConfigProvider using astro-cli's +// config system. The API is derived from the current astro-cli context domain, +// and org/workspace/deployment defaults are read from the context. +type ConfigAdapter struct{} + +func (a *ConfigAdapter) GetDefaultAPI() string { + domain, err := astroconfig.GetCurrentDomain() + if err != nil { + return "" + } + return domainToAPI(domain) +} + +func (a *ConfigAdapter) SetDefaultAPI(_ string) error { + return nil +} + +func (a *ConfigAdapter) GetDefaultOrganizationForAPI(_ string) string { + ctx, err := astroconfig.GetCurrentContext() + if err != nil { + return "" + } + return ctx.Organization +} + +func (a *ConfigAdapter) SetDefaultOrganizationForAPI(_, org string) error { + ctx, err := astroconfig.GetCurrentContext() + if err != nil { + return err + } + return ctx.SetContextKey("organization", org) +} + +func (a *ConfigAdapter) GetDefaultWorkspaceForAPI(_ string) string { + ctx, err := astroconfig.GetCurrentContext() + if err != nil { + return "" + } + return ctx.Workspace +} + +func (a *ConfigAdapter) SetDefaultWorkspaceForAPI(_, ws string) error { + ctx, err := astroconfig.GetCurrentContext() + if err != nil { + return err + } + return ctx.SetContextKey("workspace", ws) +} + +func (a *ConfigAdapter) GetDefaultDeploymentForAPI(_ string) string { + ctx, err := astroconfig.GetCurrentContext() + if err != nil { + return "" + } + return ctx.Deployment +} + +func (a *ConfigAdapter) SetDefaultDeploymentForAPI(_, dep string) error { + ctx, err := astroconfig.GetCurrentContext() + if err != nil { + return err + } + return ctx.SetContextKey("deployment", dep) +} + +func (a *ConfigAdapter) GetVariable(_ string) string { + return "" +} + +func (a *ConfigAdapter) GetVariables() map[string]string { + return nil +} + +func (a *ConfigAdapter) SetVariables(_ map[string]string) error { + return nil +} + +func (a *ConfigAdapter) GetArgDefault(argName, _ string) string { + ctx, err := astroconfig.GetCurrentContext() + if err != nil { + return "" + } + + argLower := strings.ToLower(argName) + + if strings.Contains(argLower, shared.ResourceOrganization) { + return ctx.Organization + } + if strings.Contains(argLower, shared.ResourceWorkspace) { + return ctx.Workspace + } + if strings.Contains(argLower, shared.ResourceDeployment) { + return ctx.Deployment + } + + return "" +} + +// domainToAPI maps an astro-cli context domain to a nexus API profile name. +func domainToAPI(domain string) string { + matches := domainEnvRegex.FindStringSubmatch(domain) + if matches == nil { + return "astro-prod" + } + env := matches[1] // captured group: "dev", "stage", "perf", or "" + if env == "" { + return "astro-prod" + } + return "astro-" + env +} diff --git a/internal/nexus/exec.go b/internal/nexus/exec.go new file mode 100644 index 000000000..8644b41e0 --- /dev/null +++ b/internal/nexus/exec.go @@ -0,0 +1,693 @@ +package nexus + +import ( + "bufio" + "bytes" + "encoding/json" + "fmt" + "os" + "strconv" + "strings" + + sharedAPI "github.com/astronomer/nexus/api" + "github.com/astronomer/nexus/app" + "github.com/astronomer/nexus/restish" + "github.com/astronomer/nexus/shared" + + "github.com/iancoleman/strcase" + "golang.org/x/term" +) + +func isTerminal() bool { + return term.IsTerminal(int(os.Stdout.Fd())) +} + +type inlineStatus struct { + hasLine bool + silent bool +} + +func newInlineStatus() *inlineStatus { + return &inlineStatus{silent: !isTerminal()} +} + +func (s *inlineStatus) Update(message string) { + if s.silent { + return + } + if s.hasLine { + fmt.Fprintf(os.Stderr, "\r\033[K") + } + fmt.Fprintf(os.Stderr, " %s", message) + s.hasLine = true +} + +func (s *inlineStatus) Done(message string) { + if s.silent { + return + } + if s.hasLine { + fmt.Fprintf(os.Stderr, "\r\033[K") + } + fmt.Fprintln(os.Stderr, message) + s.hasLine = false +} + +var errSilent = fmt.Errorf("") + +func (n *nexusCLI) runRestishCommand(command string, args []string) error { + api := n.ctx.Config.GetDefaultAPI() + if api == "" { + fmt.Fprintln(os.Stderr, "Error: no API configured. Use 'astro context switch' to select a domain.") + return errSilent + } + + var positionalArgs []string + bodyFields := make(map[string]string) + var rawBody string + var prNumber string + var verbose bool + var forceFlag bool + + for i := 0; i < len(args); i++ { + arg := args[i] + if strings.HasPrefix(arg, "--") || strings.HasPrefix(arg, "-") { + if arg == "--help" || arg == "-h" { + n.showCommandHelp(api, command) + return nil + } + if arg == "--verbose" || arg == "-v" { + verbose = true + continue + } + if arg == "--force" || arg == "-f" { + forceFlag = true + continue + } + + key := strings.TrimPrefix(strings.TrimPrefix(arg, "--"), "-") + var val string + if idx := strings.Index(key, "="); idx != -1 { + val = key[idx+1:] + key = key[:idx] + } else if i+1 < len(args) && !strings.HasPrefix(args[i+1], "-") { + i++ + val = args[i] + } + + switch key { + case "body": + rawBody = val + case "pr": + prNumber = val + case "o", "output": + default: + bodyFields[key] = val + } + } else { + positionalArgs = append(positionalArgs, arg) + } + } + + if positionalArgs == nil { + positionalArgs = []string{} + } + filledArgs, filledFromConfig := n.fillPositionalArgs(api, command, positionalArgs) + if filledArgs == nil { + return errSilent + } + + if !n.confirmDestructiveAction(command, filledArgs, filledFromConfig, forceFlag) { + fmt.Fprintln(os.Stderr, "Canceled.") + return errSilent + } + + status := newInlineStatus() + body := n.buildRequestBody(api, command, rawBody, bodyFields, filledArgs, status) + + status.Update("Executing " + command + "...") + result, err := n.executeRestishCommand(api, command, filledArgs, body, prNumber, verbose) + if err != nil { + status.Done("") + fmt.Fprintln(os.Stderr, formatAPIError(err.Error())) + return errSilent + } + + status.Done("") + + if result != "" && result != "null" { + fmt.Println(formatResult(result)) + } + + n.updateConfigAfterCommand(api, command, result, sharedAPI.GetDeletedID(filledArgs, command)) + return nil +} + +func (n *nexusCLI) confirmDestructiveAction(command string, args []string, filledFromConfig map[string]string, force bool) bool { + if force { + return true + } + if !isTerminal() { + return true + } + + isDelete := strings.HasPrefix(command, "delete-") + isUpdate := strings.HasPrefix(command, "update-") + + // Delete commands always prompt for confirmation + if isDelete { + _, noun, _ := parseNounVerb(command) + if noun == "" { + noun = "resource" + } + if len(args) > 0 { + fmt.Fprintf(os.Stderr, "About to delete %s: %s\n", noun, args[len(args)-1]) + } + fmt.Fprintf(os.Stderr, "Are you sure you want to delete? [y/N]: ") + + var response string + _, _ = fmt.Scanln(&response) + response = strings.TrimSpace(strings.ToLower(response)) + return response == "y" || response == "yes" + } + + // Update commands only prompt when args were auto-filled from config + if isUpdate && len(filledFromConfig) > 0 { + fmt.Fprintln(os.Stderr, "Using defaults from config:") + for resourceType, id := range filledFromConfig { + fmt.Fprintf(os.Stderr, " %s: %s\n", resourceType, id) + } + fmt.Fprintf(os.Stderr, "\nProceed with update? [y/N]: ") + + var response string + _, _ = fmt.Scanln(&response) + response = strings.TrimSpace(strings.ToLower(response)) + return response == "y" || response == "yes" + } + + return true +} + +func (n *nexusCLI) fillPositionalArgs(api, command string, provided []string) (filled []string, fromConfig map[string]string) { + parser, err := restish.NewHelpParser(api, command) + if err != nil { + return provided, nil + } + + expectedArgs := parser.ParseExpectedArgs() + if len(expectedArgs) == 0 || len(provided) >= len(expectedArgs) { + return provided, nil + } + + filled = make([]string, len(provided)) + copy(filled, provided) + fromConfig = make(map[string]string) + + for i := len(provided); i < len(expectedArgs); i++ { + argName := strings.ToLower(expectedArgs[i]) + var defaultVal string + var resourceType string + + switch { + case strings.Contains(argName, shared.ResourceOrganization): + defaultVal = n.ctx.Config.GetDefaultOrganizationForAPI(api) + resourceType = shared.ResourceOrganization + case strings.Contains(argName, shared.ResourceWorkspace): + defaultVal = n.ctx.Config.GetDefaultWorkspaceForAPI(api) + resourceType = shared.ResourceWorkspace + case strings.Contains(argName, shared.ResourceDeployment): + defaultVal = n.ctx.Config.GetDefaultDeploymentForAPI(api) + resourceType = shared.ResourceDeployment + } + + if defaultVal == "" { + fmt.Fprintf(os.Stderr, "Error: missing required argument '%s'\n", expectedArgs[i]) + fmt.Fprintf(os.Stderr, " Set a default with: astro context set %s \n", resourceType) + return nil, nil + } + + filled = append(filled, defaultVal) + if resourceType != "" { + fromConfig[resourceType] = defaultVal + } + } + + return filled, fromConfig +} + +func (n *nexusCLI) createSourceFetcher(api, command string, positionalArgs []string) *app.SourceFetcher { + availableArgs := make(map[string]string) + + // Map positional arg values to their expected names + if len(positionalArgs) > 0 { + parser, err := restish.NewHelpParser(api, command) + if err == nil { + expectedArgs := parser.ParseExpectedArgs() + for i, name := range expectedArgs { + if i < len(positionalArgs) { + availableArgs[name] = positionalArgs[i] + availableArgs[strcase.ToLowerCamel(name)] = positionalArgs[i] + } + } + } + } + + // Fill from config defaults if not already provided + orgID := n.ctx.Config.GetDefaultOrganizationForAPI(api) + if orgID != "" { + if _, ok := availableArgs["organization-id"]; !ok { + availableArgs["organization-id"] = orgID + availableArgs["organizationId"] = orgID + } + } + + fetcher := app.NewSourceFetcher(api, availableArgs) + + var authDomain string + if meta, ok := n.ctx.APIs.Get(api); ok { + authDomain = meta.AuthDomain + } + + fetcher.RegisterFetcher("userinfo", func() map[string]any { + email := sharedAPI.FetchCurrentUserEmail(api, authDomain) + if email == "" { + return nil + } + return map[string]any{"email": email} + }) + return fetcher +} + +func (n *nexusCLI) buildRequestBody(api, command, rawBody string, bodyFields map[string]string, positionalArgs []string, status *inlineStatus) string { + if rawBody != "" { + return rawBody + } + + schemaParser, err := restish.NewSchemaParser(api, command) + if err != nil { + if len(bodyFields) == 0 { + return "" + } + camelFields := make(map[string]string) + for k, v := range bodyFields { + camelFields[strcase.ToLowerCamel(k)] = v + } + bodyJSON, _ := json.Marshal(camelFields) + return string(bodyJSON) + } + + // Resolve variant (oneOf) before loading fields so we get the right schema. + variantIndex := 0 + variants := schemaParser.GetVariants() + if len(variants) > 0 { + variantIndex = resolveVariant(variants, bodyFields) + } + + fields := schemaParser.GetFields(variantIndex) + if len(fields) == 0 && len(bodyFields) == 0 { + return "" + } + + fetcher := n.createSourceFetcher(api, command, positionalArgs) + fetcher.OnStatus = status.Update + registry := n.ctx.APIs + + fieldTypes := make(map[string]string) + for _, f := range fields { + fieldTypes[strings.ToLower(f.Name)] = f.Type + } + + bodyData := make(map[string]any) + + for k, v := range bodyFields { + camelKey := strcase.ToLowerCamel(k) + fieldType := fieldTypes[strings.ToLower(strcase.ToLowerCamel(k))] + bodyData[camelKey] = convertToType(v, fieldType) + } + + configFields := make(map[string]bool) + for _, f := range fields { + camelKey := strcase.ToLowerCamel(f.Name) + if _, exists := bodyData[camelKey]; exists { + continue + } + + if val, ok := registry.GetFieldValue(api, command, f.Name); ok { + configFields[camelKey] = true + defaultVal := fetcher.ResolveValue(val) + if defaultVal != "" { + bodyData[camelKey] = convertToType(defaultVal, f.Type) + continue + } + } + + if defaultVal := n.getConfigDefault(f.Name, api); defaultVal != "" && f.Required { + bodyData[camelKey] = convertToType(defaultVal, f.Type) + continue + } + + if f.Required { + if defaultVal := shared.GetFieldDefaultByType(f.Type, f.Enum); defaultVal != "" { + bodyData[camelKey] = convertToType(defaultVal, f.Type) + } + } + } + + if isTerminal() { + status.Done("") + promptForMissingFields(fields, bodyData) + } + + if len(bodyData) == 0 { + return "" + } + + bodyJSON, err := json.Marshal(bodyData) + if err != nil { + return "" + } + return string(bodyJSON) +} + +// resolveVariant determines which oneOf variant to use. +// If the discriminator value is already in bodyFields, returns its variant index. +// If running in a terminal, prompts for selection. Otherwise defaults to 0. +func resolveVariant(variants []restish.SchemaVariant, bodyFields map[string]string) int { + if len(variants) == 0 { + return 0 + } + + discKey := variants[0].DiscriminatorKey + if discKey == "" { + return 0 + } + + // Check if discriminator was provided via --flags + for k, v := range bodyFields { + if strings.EqualFold(k, discKey) || strings.EqualFold(strcase.ToKebab(k), strcase.ToKebab(discKey)) { + for _, variant := range variants { + if strings.EqualFold(v, variant.Name) { + return variant.Index + } + } + } + } + + if !isTerminal() { + return 0 + } + + // Prompt for variant selection + label := fieldFormatter.FormatSingleField(discKey) + scanner := bufio.NewScanner(os.Stdin) + + fmt.Fprintf(os.Stderr, "\n %s:\n", label) + for i, v := range variants { + fmt.Fprintf(os.Stderr, " %d) %s\n", i+1, v.Name) + } + for { + fmt.Fprintf(os.Stderr, " Select [1-%d]: ", len(variants)) + if !scanner.Scan() { + return 0 + } + input := strings.TrimSpace(scanner.Text()) + if idx, err := strconv.Atoi(input); err == nil && idx >= 1 && idx <= len(variants) { + selected := variants[idx-1] + bodyFields[discKey] = selected.Name + return selected.Index + } + } +} + +func promptForMissingFields(fields []restish.SchemaField, bodyData map[string]any) { + scanner := bufio.NewScanner(os.Stdin) + prompted := false + + for _, f := range fields { + if !f.Required { + continue + } + camelKey := strcase.ToLowerCamel(f.Name) + if _, exists := bodyData[camelKey]; exists { + continue + } + if f.Type == "object" || f.Type == "array" { + continue + } + + if !prompted { + fmt.Fprintln(os.Stderr, "Missing required fields:") + prompted = true + } + + label := fieldFormatter.FormatSingleField(f.Name) + + if f.Enum != "" { + options := strings.Split(f.Enum, ",") + for i := range options { + options[i] = strings.TrimSpace(options[i]) + } + fmt.Fprintf(os.Stderr, "\n %s:\n", label) + for i, opt := range options { + fmt.Fprintf(os.Stderr, " %d) %s\n", i+1, opt) + } + for { + fmt.Fprintf(os.Stderr, " Select [1-%d]: ", len(options)) + if !scanner.Scan() { + return + } + input := strings.TrimSpace(scanner.Text()) + if idx, err := strconv.Atoi(input); err == nil && idx >= 1 && idx <= len(options) { + bodyData[camelKey] = options[idx-1] + break + } + } + } else { + hint := f.Type + if f.Description != "" { + hint = f.Description + } + for { + fmt.Fprintf(os.Stderr, " %s (%s): ", label, hint) + if !scanner.Scan() { + return + } + input := strings.TrimSpace(scanner.Text()) + if input != "" { + bodyData[camelKey] = convertToType(input, f.Type) + break + } + } + } + } + + if prompted { + fmt.Fprintln(os.Stderr) + } +} + +func (n *nexusCLI) getConfigDefault(fieldName, api string) string { + fieldLower := strings.ToLower(fieldName) + if strings.Contains(fieldLower, shared.ResourceWorkspace) && strings.Contains(fieldLower, "id") { + return n.ctx.Config.GetDefaultWorkspaceForAPI(api) + } + return "" +} + +func convertToType(value, fieldType string) any { + switch strings.ToLower(fieldType) { + case "boolean", "bool": + return value == "true" + case "integer", "int", "int64": + if n, err := json.Number(value).Int64(); err == nil { + return n + } + return value + case "number", "float", "float64": + if f, err := json.Number(value).Float64(); err == nil { + return f + } + return value + case "array": + var arr []any + if err := json.Unmarshal([]byte(value), &arr); err == nil { + return arr + } + return value + case "object": + var obj map[string]any + if err := json.Unmarshal([]byte(value), &obj); err == nil { + return obj + } + return value + default: + return value + } +} + +func (n *nexusCLI) executeRestishCommand(api, command string, args []string, body, prNumber string, verbose bool) (string, error) { + restishArgs := []string{api, command} + restishArgs = append(restishArgs, args...) + + if body != "" { + restishArgs = append(restishArgs, body) + } + + if prNumber != "" { + serverURL := n.ctx.APIs.GetPRPreviewServerURL(api, prNumber) + if serverURL != "" { + restishArgs = append(restishArgs, "--rsh-server", serverURL) + } + } + + if verbose { + cmdArgs := []string{api, command} + cmdArgs = append(cmdArgs, args...) + fmt.Fprintf(os.Stderr, "Command: restish %s\n", strings.Join(cmdArgs, " ")) + + if body != "" { + fmt.Fprintln(os.Stderr, "Body:") + var prettyBody bytes.Buffer + if err := json.Indent(&prettyBody, []byte(body), " ", " "); err == nil { + fmt.Fprintln(os.Stderr, " "+prettyBody.String()) + } else { + fmt.Fprintln(os.Stderr, " "+body) + } + } + fmt.Fprintln(os.Stderr) + } + + output, err := sharedAPI.ExecuteSimple(restishArgs) + if err != nil { + return "", fmt.Errorf("%s", output) + } + + trimmed := strings.TrimSpace(output) + + return trimmed, nil +} + +func (n *nexusCLI) updateConfigAfterCommand(apiName, command, result, deletedID string) { + var response map[string]any + if result != "" && result != "null" { + if err := json.Unmarshal([]byte(result), &response); err == nil { + if _, hasMsg := response["message"]; hasMsg { + if _, hasCode := response["statusCode"]; hasCode { + response = nil + } + } + } + } + + updater := sharedAPI.NewConfigUpdater(n.ctx.Config) + updated, resourceType, resourceID := updater.UpdateAfterCommand(apiName, command, response, deletedID) + + if updated { + if strings.HasPrefix(command, "delete-") { + fmt.Fprintf(os.Stderr, "Cleared default %s from config\n", resourceType) + } else { + action := "Set" + if strings.HasPrefix(command, "update-") { + action = "Updated" + } + fmt.Fprintf(os.Stderr, "%s default %s %s in config\n", action, resourceType, resourceID) + } + } +} + +func (n *nexusCLI) showCommandHelp(api, command string) { + fmt.Printf("Help for %s\n\n", command) + + noun, verb, ok := parseNounVerb(command) + if ok { + fmt.Println("Usage:") + fmt.Printf(" astro %s %s [args...] [flags]\n", noun, verb) + } else { + fmt.Println("Usage:") + fmt.Printf(" astro %s [args...] [flags]\n", command) + } + + n.printBodyFields(api, command) + printQueryFlags(api, command) + + fmt.Println() + fmt.Println("Use --body='{}' for raw JSON input.") +} + +func (n *nexusCLI) printBodyFields(api, command string) { + schemaParser, err := restish.NewSchemaParser(api, command) + if err != nil { + return + } + + fields := schemaParser.GetFields(-1) + if len(fields) == 0 { + return + } + + fetcher := n.createSourceFetcher(api, command, nil) + registry := n.ctx.APIs + + fmt.Println() + fmt.Println("Body Fields:") + + for _, f := range fields { + desc := formatFieldDescription(f) + defaultVal := n.resolveFieldDefault(api, command, f.Name, registry, fetcher) + if defaultVal != "" { + desc += " [default: " + defaultVal + "]" + } + + req := "" + if f.Required { + req = " (required)" + } + + fmt.Printf(" --%-25s %s%s\n", strcase.ToKebab(f.Name), desc, req) + } +} + +func (n *nexusCLI) resolveFieldDefault(api, command, fieldName string, registry *app.APIRegistry, fetcher *app.SourceFetcher) string { + if val, ok := registry.GetFieldValue(api, command, fieldName); ok { + if resolved := fetcher.ResolveValue(val); resolved != "" { + return resolved + } + } + + return n.getConfigDefault(fieldName, api) +} + +func formatFieldDescription(f restish.SchemaField) string { + if f.Description != "" { + if f.Enum != "" { + return f.Description + " (one of: " + f.Enum + ")" + } + + return f.Description + } + + if f.Enum != "" { + return "One of: " + f.Enum + } + + return f.Type +} + +func printQueryFlags(api, command string) { + helpParser, err := restish.NewHelpParser(api, command) + if err != nil { + return + } + + flags := helpParser.ParseFlagsSection() + if len(flags) == 0 { + return + } + + fmt.Println() + fmt.Println("Query Flags:") + + for _, f := range flags { + fmt.Printf(" --%-25s %s\n", f.Name, f.Description) + } +} diff --git a/internal/nexus/output.go b/internal/nexus/output.go new file mode 100644 index 000000000..463d11464 --- /dev/null +++ b/internal/nexus/output.go @@ -0,0 +1,54 @@ +package nexus + +import ( + "encoding/json" + "fmt" + "strings" + + "github.com/astronomer/nexus/shared" +) + +var fieldFormatter = shared.NewFieldFormatter() + +// formatAPIError extracts a human-readable message from an API error response. +// If the response is JSON with a "message" field, returns "Error: ". +// Otherwise returns the raw error string. +func formatAPIError(raw string) string { + trimmed := strings.TrimSpace(raw) + if trimmed == "" { + return raw + } + + var errResp struct { + Message string `json:"message"` + StatusCode int `json:"statusCode"` + } + if err := json.Unmarshal([]byte(trimmed), &errResp); err == nil && errResp.Message != "" { + if errResp.StatusCode > 0 { + return fmt.Sprintf("Error (%d): %s", errResp.StatusCode, errResp.Message) + } + return "Error: " + errResp.Message + } + + return raw +} + +// formatResult pretty-prints JSON when running in a terminal, returns raw JSON otherwise. +func formatResult(result string) string { + if !isTerminal() { + return result + } + trimmed := strings.TrimSpace(result) + if trimmed == "" { + return result + } + var raw any + if err := json.Unmarshal([]byte(trimmed), &raw); err != nil { + return result + } + pretty, err := json.MarshalIndent(raw, "", " ") + if err != nil { + return result + } + return string(pretty) +}