├── .github ├── ISSUE_TEMPLATE │ └── issue.md └── workflows │ ├── dist.yml │ ├── generate-docs.yml │ ├── go-releaser.yml │ ├── integration-test.yml │ ├── pr-validation.yml │ └── release-please.yml ├── .gitignore ├── .goreleaser.yaml ├── .octopus ├── deployment_process.ocl ├── deployment_settings.ocl └── schema_version.ocl ├── CHANGELOG.md ├── CODEOWNERS ├── CONTRIBUTING.md ├── DISTRIBUTION.md ├── LICENSE ├── Makefile ├── README.md ├── build ├── cli.nuspec ├── tools │ ├── LICENSE.txt │ ├── VERIFICATION.txt │ ├── chocolateyInstall.ps1 │ └── icon.png └── windows │ ├── octopus.wixproj │ ├── octopus.wxs │ └── ui.wxs ├── cmd ├── gen-docs │ └── main.go └── octopus │ └── main.go ├── examples.md ├── go.mod ├── go.sum ├── logo.png ├── pkg ├── apiclient │ ├── apiclient_test.go │ ├── client_factory.go │ ├── client_factory_test.go │ ├── requester.go │ └── spinner_round_tripper.go ├── cmd │ ├── account │ │ ├── account.go │ │ ├── aws │ │ │ ├── aws.go │ │ │ ├── create │ │ │ │ ├── create.go │ │ │ │ └── create_test.go │ │ │ └── list │ │ │ │ └── list.go │ │ ├── azure-oidc │ │ │ ├── azure-oidc.go │ │ │ ├── create │ │ │ │ ├── create.go │ │ │ │ └── create_test.go │ │ │ └── list │ │ │ │ └── list.go │ │ ├── azure │ │ │ ├── azure.go │ │ │ ├── create │ │ │ │ ├── create.go │ │ │ │ └── create_test.go │ │ │ └── list │ │ │ │ └── list.go │ │ ├── create │ │ │ └── create.go │ │ ├── delete │ │ │ └── delete.go │ │ ├── gcp │ │ │ ├── create │ │ │ │ ├── create.go │ │ │ │ └── create_test.go │ │ │ ├── gcp.go │ │ │ └── list │ │ │ │ └── list.go │ │ ├── generic-oidc │ │ │ ├── create │ │ │ │ ├── create.go │ │ │ │ └── create_test.go │ │ │ ├── generic-oidc.go │ │ │ └── list │ │ │ │ └── list.go │ │ ├── helper │ │ │ └── helper.go │ │ ├── list │ │ │ └── list.go │ │ ├── shared │ │ │ └── shared.go │ │ ├── ssh │ │ │ ├── create │ │ │ │ ├── create.go │ │ │ │ └── create_test.go │ │ │ ├── list │ │ │ │ └── list.go │ │ │ └── ssh.go │ │ ├── token │ │ │ ├── create │ │ │ │ ├── create.go │ │ │ │ └── create_test.go │ │ │ ├── list │ │ │ │ └── list.go │ │ │ └── token.go │ │ └── username │ │ │ ├── create │ │ │ ├── create.go │ │ │ └── create_test.go │ │ │ ├── list │ │ │ └── list.go │ │ │ └── username.go │ ├── buildinformation │ │ ├── build-information.go │ │ ├── bulkdelete │ │ │ └── bulk-delete.go │ │ ├── delete │ │ │ └── delete.go │ │ ├── list │ │ │ └── list.go │ │ ├── shared │ │ │ └── shared.go │ │ ├── upload │ │ │ └── upload.go │ │ └── view │ │ │ └── view.go │ ├── channel │ │ ├── channel.go │ │ └── create │ │ │ └── create.go │ ├── config │ │ ├── config.go │ │ ├── get │ │ │ └── get.go │ │ ├── list │ │ │ └── list.go │ │ └── set │ │ │ └── set.go │ ├── dependencies.go │ ├── environment │ │ ├── create │ │ │ └── create.go │ │ ├── delete │ │ │ └── delete.go │ │ ├── environment.go │ │ └── list │ │ │ └── list.go │ ├── login │ │ ├── login.go │ │ └── login_test.go │ ├── logout │ │ ├── logout.go │ │ └── logout_test.go │ ├── model │ │ └── entity.go │ ├── package │ │ ├── delete │ │ │ └── delete.go │ │ ├── list │ │ │ ├── list.go │ │ │ └── list_test.go │ │ ├── nuget │ │ │ ├── create │ │ │ │ └── create.go │ │ │ └── nuget.go │ │ ├── package.go │ │ ├── support │ │ │ ├── pack.go │ │ │ └── pack_test.go │ │ ├── upload │ │ │ ├── upload.go │ │ │ └── upload_test.go │ │ ├── versions │ │ │ ├── versions.go │ │ │ └── versions_test.go │ │ └── zip │ │ │ ├── create │ │ │ └── create.go │ │ │ └── zip.go │ ├── project │ │ ├── branch │ │ │ ├── branch.go │ │ │ ├── create │ │ │ │ ├── create.go │ │ │ │ └── create_test.go │ │ │ ├── list │ │ │ │ └── list.go │ │ │ └── shared │ │ │ │ └── shared.go │ │ ├── clone │ │ │ └── clone.go │ │ ├── connect │ │ │ └── connect.go │ │ ├── convert │ │ │ ├── convert.go │ │ │ └── convert_test.go │ │ ├── create │ │ │ ├── create.go │ │ │ ├── create_opts.go │ │ │ └── create_test.go │ │ ├── delete │ │ │ └── delete.go │ │ ├── disable │ │ │ └── disable.go │ │ ├── disconnect │ │ │ └── disconnect.go │ │ ├── enable │ │ │ └── enable.go │ │ ├── list │ │ │ └── list.go │ │ ├── project.go │ │ ├── shared │ │ │ ├── shared.go │ │ │ └── shared_test.go │ │ ├── variables │ │ │ ├── create │ │ │ │ ├── create.go │ │ │ │ └── create_test.go │ │ │ ├── delete │ │ │ │ └── delete.go │ │ │ ├── exclude │ │ │ │ └── exclude.go │ │ │ ├── include │ │ │ │ └── include.go │ │ │ ├── list │ │ │ │ └── list.go │ │ │ ├── shared │ │ │ │ ├── input.go │ │ │ │ ├── input_test.go │ │ │ │ └── scopes.go │ │ │ ├── update │ │ │ │ ├── update.go │ │ │ │ └── update_test.go │ │ │ ├── variables.go │ │ │ └── view │ │ │ │ └── view.go │ │ └── view │ │ │ └── view.go │ ├── projectgroup │ │ ├── create │ │ │ └── create.go │ │ ├── delete │ │ │ └── delete.go │ │ ├── list │ │ │ └── list.go │ │ ├── project-group.go │ │ └── view │ │ │ └── view.go │ ├── release │ │ ├── create │ │ │ ├── create.go │ │ │ └── create_test.go │ │ ├── delete │ │ │ ├── delete.go │ │ │ └── delete_test.go │ │ ├── deploy │ │ │ ├── deploy.go │ │ │ └── deploy_test.go │ │ ├── list │ │ │ ├── list.go │ │ │ └── list_test.go │ │ ├── progression │ │ │ ├── allow │ │ │ │ └── allow.go │ │ │ ├── prevent │ │ │ │ └── prevent.go │ │ │ ├── progression.go │ │ │ └── shared │ │ │ │ └── shared.go │ │ └── release.go │ ├── root │ │ ├── help.go │ │ └── root.go │ ├── runbook │ │ ├── delete │ │ │ ├── delete.go │ │ │ └── delete_test.go │ │ ├── list │ │ │ ├── list.go │ │ │ └── list_test.go │ │ ├── run │ │ │ ├── run.go │ │ │ └── run_test.go │ │ ├── runbook.go │ │ ├── shared │ │ │ └── shared.go │ │ └── snapshot │ │ │ ├── create │ │ │ └── create.go │ │ │ ├── list │ │ │ └── list.go │ │ │ ├── publish │ │ │ └── publish.go │ │ │ └── snapshot.go │ ├── space │ │ ├── create │ │ │ ├── create.go │ │ │ └── create_test.go │ │ ├── delete │ │ │ └── delete.go │ │ ├── list │ │ │ └── list.go │ │ ├── space.go │ │ └── view │ │ │ └── view.go │ ├── target │ │ ├── azure-web-app │ │ │ ├── azure-web-app.go │ │ │ ├── create │ │ │ │ ├── create.go │ │ │ │ └── create_test.go │ │ │ ├── list │ │ │ │ └── list.go │ │ │ └── view │ │ │ │ └── view.go │ │ ├── cloud-region │ │ │ ├── cloud-region.go │ │ │ ├── create │ │ │ │ └── create.go │ │ │ ├── list │ │ │ │ └── list.go │ │ │ └── view │ │ │ │ └── view.go │ │ ├── delete │ │ │ └── delete.go │ │ ├── kubernetes │ │ │ ├── create │ │ │ │ ├── create.go │ │ │ │ └── create_test.go │ │ │ ├── kubernetes.go │ │ │ ├── list │ │ │ │ └── list.go │ │ │ └── view │ │ │ │ └── view.go │ │ ├── list │ │ │ └── list.go │ │ ├── listening-tentacle │ │ │ ├── create │ │ │ │ └── create.go │ │ │ ├── list │ │ │ │ └── list.go │ │ │ ├── listening-tentacle.go │ │ │ └── view │ │ │ │ └── view.go │ │ ├── polling-tentacle │ │ │ ├── list │ │ │ │ └── list.go │ │ │ ├── polling-tentacle.go │ │ │ └── view │ │ │ │ └── view.go │ │ ├── shared │ │ │ ├── environment.go │ │ │ ├── environment_test.go │ │ │ ├── role.go │ │ │ ├── role_test.go │ │ │ ├── target.go │ │ │ ├── tenant.go │ │ │ ├── tenant_test.go │ │ │ ├── view.go │ │ │ ├── workerpool.go │ │ │ └── workerpool_test.go │ │ ├── ssh │ │ │ ├── create │ │ │ │ └── create.go │ │ │ ├── list │ │ │ │ └── list.go │ │ │ ├── ssh.go │ │ │ └── view │ │ │ │ └── view.go │ │ ├── target.go │ │ └── view │ │ │ └── view.go │ ├── task │ │ ├── task.go │ │ └── wait │ │ │ ├── task_output_formatter.go │ │ │ ├── wait.go │ │ │ └── wait_test.go │ ├── tenant │ │ ├── clone │ │ │ ├── clone.go │ │ │ └── clone_test.go │ │ ├── connect │ │ │ ├── connect.go │ │ │ └── connect_test.go │ │ ├── create │ │ │ ├── create.go │ │ │ ├── create_opts.go │ │ │ └── create_test.go │ │ ├── delete │ │ │ └── delete.go │ │ ├── disable │ │ │ └── disable.go │ │ ├── disconnect │ │ │ ├── disconnect.go │ │ │ └── disconnect_test.go │ │ ├── enable │ │ │ └── enable.go │ │ ├── list │ │ │ └── list.go │ │ ├── shared │ │ │ └── shared.go │ │ ├── tag │ │ │ ├── tag.go │ │ │ └── tag_opts.go │ │ ├── tenant.go │ │ ├── variables │ │ │ ├── list │ │ │ │ └── list.go │ │ │ ├── update │ │ │ │ ├── update.go │ │ │ │ └── update_test.go │ │ │ └── variables.go │ │ └── view │ │ │ └── view.go │ ├── user │ │ ├── delete │ │ │ └── delete.go │ │ ├── list │ │ │ └── list.go │ │ └── user.go │ ├── version │ │ └── version.go │ ├── worker │ │ ├── delete │ │ │ └── delete.go │ │ ├── list │ │ │ └── list.go │ │ ├── listening-tentacle │ │ │ ├── create │ │ │ │ └── create.go │ │ │ ├── list │ │ │ │ └── list.go │ │ │ ├── listening-tentacle.go │ │ │ └── view │ │ │ │ └── view.go │ │ ├── polling-tentacle │ │ │ ├── list │ │ │ │ └── list.go │ │ │ ├── polling-tentacle.go │ │ │ └── view │ │ │ │ └── view.go │ │ ├── shared │ │ │ ├── view.go │ │ │ ├── worker.go │ │ │ ├── workerpool.go │ │ │ └── workerpool_test.go │ │ ├── ssh │ │ │ ├── create │ │ │ │ └── create.go │ │ │ ├── list │ │ │ │ └── list.go │ │ │ ├── ssh.go │ │ │ └── view │ │ │ │ └── view.go │ │ ├── view │ │ │ └── view.go │ │ └── worker.go │ └── workerpool │ │ ├── delete │ │ └── delete.go │ │ ├── dynamic │ │ ├── create │ │ │ ├── create.go │ │ │ └── create_test.go │ │ ├── dynamic.go │ │ └── view │ │ │ └── view.go │ │ ├── list │ │ └── list.go │ │ ├── shared │ │ ├── view.go │ │ └── workerpool.go │ │ ├── static │ │ ├── create │ │ │ ├── create.go │ │ │ └── create_test.go │ │ ├── static.go │ │ └── view │ │ │ └── view.go │ │ ├── view │ │ └── view.go │ │ └── workerpool.go ├── config │ ├── config.go │ └── provider.go ├── constants │ ├── annotations │ │ └── annotations.go │ └── constants.go ├── errors │ └── errors.go ├── executionscommon │ ├── executionscommon.go │ └── executionscommon_test.go ├── executor │ ├── account.go │ ├── executor.go │ ├── release.go │ └── runbook.go ├── factory │ └── factory.go ├── gitresources │ ├── gitResources.go │ └── gitResources_test.go ├── machinescommon │ ├── comms.go │ ├── machinepolicy.go │ ├── machinepolicy_test.go │ ├── proxy.go │ ├── proxy_test.go │ ├── ssh_common.go │ ├── ssh_common_test.go │ └── web.go ├── output │ ├── color.go │ ├── format.go │ ├── print_array.go │ ├── print_resource.go │ ├── shared.go │ ├── table.go │ └── truncate.go ├── packages │ └── packages.go ├── question │ ├── ask.go │ ├── helpers_test.go │ ├── input.go │ ├── input_test.go │ ├── select.go │ ├── select_test.go │ ├── selectors │ │ ├── channels.go │ │ ├── environments.go │ │ ├── gitCredentialStorage.go │ │ ├── git_references.go │ │ ├── lifecycles.go │ │ ├── projects.go │ │ ├── runbooks.go │ │ ├── selector_test.go │ │ └── selectors.go │ └── shared │ │ └── variables │ │ └── variables.go ├── surveyext │ ├── datepicker.go │ ├── editor.go │ ├── multiselectwithadd.go │ ├── paginate.go │ └── select.go ├── usage │ └── usage.go ├── util │ ├── featuretoggle │ │ └── feature_toggles.go │ ├── flag │ │ └── flag.go │ ├── pflagaliases.go │ ├── pflagaliases_test.go │ ├── pipe.go │ ├── util.go │ └── util_test.go └── validation │ ├── validation.go │ └── validation_test.go ├── releases.json ├── scripts └── install.sh ├── test ├── fixtures │ └── projects.go ├── integration │ ├── account_test.go │ ├── fixtures.go │ ├── integrationtestutil.go │ └── release_test.go └── testutil │ ├── fakefactory.go │ ├── fakeoctopusserver.go │ ├── fakesurvey.go │ └── testutil.go ├── version.go └── version.txt /.github/ISSUE_TEMPLATE/issue.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: For reporting bug/issues 4 | title: "" 5 | labels: kind/bug 6 | assignees: "" 7 | --- 8 | 9 | ## The bug 10 | 11 | 12 | 13 | ## Command to reproduce 14 | 15 | 16 | 17 | ``` 18 | insert command here 19 | ``` 20 | 21 | ## Outcome 22 | 23 | 24 | 25 | ``` 26 | insert output here 27 | ``` 28 | 29 | ## Versions 30 | 31 | **cli**: 32 | 33 | **Octopus Server**: 34 | 35 | ## Links 36 | 37 | 38 | -------------------------------------------------------------------------------- /.github/workflows/dist.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - release-please--** 5 | name: "Update Install Script Version" 6 | jobs: 7 | build: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v3 11 | with: 12 | token: ${{ secrets.INTEGRATIONS_FNM_BOT_TOKEN }} 13 | - name: Update Install Script 14 | run: |- 15 | NEW_VERSION="v$(cat version.txt)" 16 | sed -i "s/\(VERSION:-\)v[0-9]*\.[0-9]*\.[0-9]*/\1${NEW_VERSION}/g" scripts/install.sh 17 | - name: Commit 18 | run: |- 19 | git config --global user.name "team-integrations-fnm-bot" 20 | git config user.email 'integrationsfnmbot@octopus.com' 21 | git add scripts/install.sh 22 | git diff-index --quiet HEAD || (git commit -m "chore: update install script version" && git push origin) 23 | 24 | -------------------------------------------------------------------------------- /.github/workflows/generate-docs.yml: -------------------------------------------------------------------------------- 1 | name: 'Generate Docs' 2 | on: 3 | workflow_dispatch: 4 | env: 5 | DOCS_TOKEN: ${{ secrets.DOCS_TOKEN }} 6 | jobs: 7 | generate-docs: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Calculate branch name 11 | id: branch-name 12 | run: echo "BRANCH_NAME=enh-cliupdate-octopus-$(date +'%Y%m%d-%H%M%S')" >> $GITHUB_ENV 13 | 14 | - name: Checkout CLI 15 | uses: actions/checkout@v3 16 | with: 17 | path: cli 18 | 19 | - name: Checkout docs 20 | uses: actions/checkout@v3 21 | with: 22 | repository: OctopusDeploy/docs 23 | token: ${{ env.DOCS_TOKEN }} 24 | path: docs 25 | 26 | - name: Setup docs repo 27 | working-directory: docs 28 | run: | 29 | git config user.email 'bob@octopus.com' 30 | git config user.name octobob 31 | git checkout main 32 | git checkout -b $BRANCH_NAME 33 | 34 | - name: Set up Go 35 | uses: actions/setup-go@v3 36 | with: 37 | go-version: 1.21 38 | 39 | - name: Generate cli docs 40 | working-directory: cli 41 | run: mkdir -p ../docs/src/pages/docs/octopus-rest-api/cli && go run cmd/gen-docs/main.go --website --doc-path ../docs/src/pages/docs/octopus-rest-api/cli --relative-base-path /docs/octopus-rest-api/cli 42 | 43 | - name: Commit 44 | uses: EndBug/add-and-commit@v9 45 | with: 46 | message: 'Update cli docs for octopus(go)' 47 | author_name: Bob 48 | author_email: bob@octopus.com 49 | committer_name: bob 50 | cwd: docs 51 | add: src/pages/docs/octopus-rest-api/cli 52 | push: false 53 | 54 | - run: git push --repo https://octobob:$DOCS_TOKEN@github.com/OctopusDeploy/docs.git --set-upstream origin $BRANCH_NAME 55 | working-directory: docs 56 | 57 | - name: Create PR 58 | run: | 59 | curl -X POST -H "Accept: application/vnd.github+json" -H "Authorization: Bearer $DOCS_TOKEN" \ 60 | https://api.github.com/repos/OctopusDeploy/docs/pulls \ 61 | -d '{"title":"update go-cli docs","body":"An automated update of the command line docs for octopus CLI\nCreated by GitHub Actions [Generate Docs](https://github.com/OctopusDeploy/cli/actions/workflows/generate-docs.yml)","head":"'$BRANCH_NAME'","base":"main"}' 62 | -------------------------------------------------------------------------------- /.github/workflows/pr-validation.yml: -------------------------------------------------------------------------------- 1 | name: 'PR and Commit Validation' 2 | on: 3 | workflow_dispatch: 4 | push: 5 | paths-ignore: 6 | - '**.md' 7 | - "releases.json" 8 | jobs: 9 | test: 10 | runs-on: ubuntu-latest 11 | 12 | permissions: # https://github.com/dorny/test-reporter/issues/168 13 | statuses: write 14 | checks: write 15 | steps: 16 | - uses: actions/checkout@v3 17 | 18 | - name: Set up Go 19 | uses: actions/setup-go@v3 20 | with: 21 | go-version: 1.21 22 | 23 | # if we just run the unit tests then go doesn't compile the parts of the app that aren't covered by 24 | # unit tests; this forces it 25 | - name: Build binary 26 | run: go build -o bin/octopus cmd/octopus/main.go 27 | 28 | - name: Setup gotestsum 29 | run: go install gotest.tools/gotestsum@latest 30 | 31 | - name: Unit Tests 32 | run: gotestsum --format testname --junitfile ../unit-tests.xml 33 | working-directory: ./pkg 34 | 35 | - name: Test Report 36 | uses: dorny/test-reporter@v1 37 | if: success() || failure() 38 | with: 39 | name: Test Results 40 | path: '*-tests.xml' 41 | reporter: java-junit 42 | -------------------------------------------------------------------------------- /.github/workflows/release-please.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - main 5 | name: release-please 6 | jobs: 7 | release-please-release: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: google-github-actions/release-please-action@v3 11 | id: release 12 | with: 13 | release-type: simple 14 | token: ${{ secrets.INTEGRATIONS_FNM_BOT_TOKEN }} 15 | command: github-release 16 | release-please-pr: 17 | runs-on: ubuntu-latest 18 | needs: 19 | - release-please-release 20 | steps: 21 | - id: release-pr 22 | uses: google-github-actions/release-please-action@v3 23 | with: 24 | token: ${{ secrets.INTEGRATIONS_FNM_BOT_TOKEN }} 25 | release-type: simple 26 | command: release-pr -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | bin/ 8 | 9 | # Test binary, built with `go test -c` 10 | *.test 11 | 12 | # Output of the go coverage tool, specifically when used with LiteIDE 13 | *.out 14 | 15 | # Dependency directories (remove the comment below to include it) 16 | # vendor/ 17 | .vscode 18 | dist/ 19 | .idea 20 | .env 21 | 22 | cmd/octopus/octopus 23 | cmd/gen-docs/gen-docs -------------------------------------------------------------------------------- /.octopus/deployment_settings.ocl: -------------------------------------------------------------------------------- 1 | deployment_changes_template = <<-EOT 2 | #{each release in Octopus.Deployment.Changes} 3 | #{release.ReleaseNotes} 4 | #{/each} 5 | EOT 6 | 7 | connectivity_policy { 8 | } 9 | 10 | versioning_strategy { 11 | 12 | donor_package { 13 | package = "cli" 14 | step = "push-cli-to-chocolatey" 15 | } 16 | } -------------------------------------------------------------------------------- /.octopus/schema_version.ocl: -------------------------------------------------------------------------------- 1 | version = 9 -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @OctopusDeploy/team-integrations-fnm -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) Octopus Deploy and contributors. All rights reserved. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use 4 | these files except in compliance with the License. You may obtain a copy of the 5 | License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software distributed 10 | under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 11 | CONDITIONS OF ANY KIND, either express or implied. See the License for the 12 | specific language governing permissions and limitations under the License. 13 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | EXE= 2 | ifeq ($(GOOS),windows) 3 | EXE = .exe 4 | endif 5 | 6 | RMCMD = rm -rf 7 | ifeq ($(GOOS),windows) 8 | rmdir /s /q bin/ 9 | endif 10 | 11 | ifeq (run,$(firstword $(MAKECMDGOALS))) 12 | RUN_ARGS := $(wordlist 2,$(words $(MAKECMDGOALS)),$(MAKECMDGOALS)) 13 | $(eval $(RUN_ARGS):;@:) 14 | endif 15 | 16 | .PHONY: bin/octopus$(EXE) 17 | bin/octopus$(EXE): 18 | go build -o bin/octopus cmd/octopus/main.go 19 | 20 | .PHONY: run 21 | run: 22 | go run cmd/octopus/main.go $(RUN_ARGS) 23 | 24 | .PHONY: clean 25 | clean: 26 | $(RMCMD) bin/ 27 | 28 | ## Install/Uninstall (*nix Only) 29 | 30 | DESTDIR := 31 | prefix := /usr/local 32 | bindir := ${prefix}/bin 33 | 34 | .PHONY: install 35 | install: bin/octopus 36 | install -d ${DESTDIR}${bindir} 37 | install -m755 bin/octopus ${DESTDIR}${bindir}/ 38 | 39 | .PHONY: uninstall 40 | uninstall: 41 | rm -f ${DESTDIR}${bindir}/octopus 42 | -------------------------------------------------------------------------------- /build/cli.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | octopus-cli 5 | Octopus CLI 6 | $version$ 7 | Octopus Deploy 8 | Octopus Deploy 9 | Command line wrapper for continuous deployments that makes you better at Octopus Deploy. 10 | octopus is Octopus Deploy on the command line. It brings releases, deployments, and other Octopus Deploy concepts to the terminal next to where you are already working with projects and your continuous deployment processes. 11 | 12 | ### Usage 13 | ``` 14 | octopus release [list, create, delete] 15 | octopus account [aws|azure|gcp|ssh|token|username] [list, create] 16 | octopus help 17 | ``` 18 | 19 | https://github.com/OctopusDeploy/cli/releases/latest 20 | en-US 21 | false 22 | Apache-2.0 23 | https://github.com/OctopusDeploy/cli/ 24 | 25 | tools\icon.png 26 | automation deployment 27 | true 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /build/tools/LICENSE.txt: -------------------------------------------------------------------------------- 1 | From: https://github.com/OctopusDeploy/cli/blob/main/LICENSE 2 | 3 | LICENSE 4 | 5 | Copyright (c) Octopus Deploy and contributors. All rights reserved. 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use 8 | these files except in compliance with the License. You may obtain a copy of the 9 | License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software distributed 14 | under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 15 | CONDITIONS OF ANY KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations under the License. 17 | -------------------------------------------------------------------------------- /build/tools/VERIFICATION.txt: -------------------------------------------------------------------------------- 1 | VERIFICATION 2 | Verification is intended to assist the Chocolatey moderators and community 3 | in verifying that this package's contents are trustworthy. 4 | 5 | This package is published by the Octopus Deploy team itself. -------------------------------------------------------------------------------- /build/tools/chocolateyInstall.ps1: -------------------------------------------------------------------------------- 1 | $ErrorActionPreference = 'Stop' 2 | 3 | $toolsDir = "$(Split-Path -parent $MyInvocation.MyCommand.Definition)" 4 | $logMsi = Join-Path -Path $env:TEMP -ChildPath ("{0}-{1}-MsiInstall.log" -f $env:ChocolateyPackageName, $env:chocolateyPackageVersion) 5 | 6 | $packageArgs = @{ 7 | packageName = $env:ChocolateyPackageName 8 | fileType = 'MSI' 9 | silentArgs = "/qn /norestart /log `"$logMsi`"" 10 | file64 = Join-Path -Path $toolsDir -ChildPath "octopus_$($env:ChocolateyPackageVersion)_windows_amd64.msi" 11 | } 12 | 13 | Install-ChocolateyInstallPackage @packageArgs -------------------------------------------------------------------------------- /build/tools/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OctopusDeploy/cli/c42d7d39b1151125e52e1f3bede75fcb107c3c1f/build/tools/icon.png -------------------------------------------------------------------------------- /build/windows/octopus.wixproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Release 5 | x64 6 | 0.1.0 7 | $(MSBuildProjectName) 8 | package 9 | $([MSBuild]::NormalizeDirectory($(MSBuildProjectDirectory)\..\..)) 10 | $(RepoPath)bin\$(Platform)\ 11 | $(RepoPath)bin\obj\$(Platform)\ 12 | 13 | $(DefineConstants); 14 | ProductVersion=$(ProductVersion); 15 | 16 | ICE39 17 | false 18 | $(MSBuildExtensionsPath)\Microsoft\WiX\v3.x\Wix.targets 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OctopusDeploy/cli/c42d7d39b1151125e52e1f3bede75fcb107c3c1f/logo.png -------------------------------------------------------------------------------- /pkg/apiclient/requester.go: -------------------------------------------------------------------------------- 1 | package apiclient 2 | 3 | import ( 4 | "fmt" 5 | version "github.com/OctopusDeploy/cli" 6 | "github.com/OctopusDeploy/cli/pkg/constants" 7 | "github.com/spf13/cobra" 8 | "strings" 9 | ) 10 | 11 | type Requester interface { 12 | GetRequester() string 13 | } 14 | 15 | type RequesterContext struct { 16 | cmd *cobra.Command 17 | } 18 | 19 | type FakeRequesterContext struct { 20 | } 21 | 22 | func NewRequester(c *cobra.Command) *RequesterContext { 23 | return &RequesterContext{ 24 | cmd: c, 25 | } 26 | } 27 | 28 | func (r *FakeRequesterContext) GetRequester() string { return "octopus/0.0.0" } 29 | 30 | func (r *RequesterContext) GetRequester() string { 31 | versionStr := strings.TrimSpace(version.Version) 32 | 33 | if r.cmd == nil { 34 | if versionStr == "" { 35 | return constants.ExecutableName 36 | } 37 | return fmt.Sprintf("%s/%s", constants.ExecutableName, versionStr) 38 | } 39 | 40 | commands := []string{r.cmd.Name()} 41 | var rootCmd string 42 | parentCmd := r.cmd.Parent() 43 | for parentCmd != nil { 44 | name := parentCmd.Name() 45 | if name == constants.ExecutableName && versionStr != "" { 46 | rootCmd = fmt.Sprintf("%s/%s", name, versionStr) 47 | } else { 48 | commands = append([]string{name}, commands...) 49 | } 50 | parentCmd = parentCmd.Parent() 51 | } 52 | return fmt.Sprintf("%s (%s)", rootCmd, strings.Join(commands, ";")) 53 | } 54 | -------------------------------------------------------------------------------- /pkg/apiclient/spinner_round_tripper.go: -------------------------------------------------------------------------------- 1 | package apiclient 2 | 3 | import ( 4 | "net/http" 5 | "time" 6 | 7 | "github.com/briandowns/spinner" 8 | ) 9 | 10 | type SpinnerRoundTripper struct { 11 | Next http.RoundTripper 12 | Spinner *spinner.Spinner 13 | } 14 | 15 | func NewSpinnerRoundTripper() *SpinnerRoundTripper { 16 | return &SpinnerRoundTripper{ 17 | Next: http.DefaultTransport, 18 | Spinner: spinner.New(spinner.CharSets[11], 100*time.Millisecond, spinner.WithColor("cyan")), 19 | } 20 | } 21 | 22 | func (c *SpinnerRoundTripper) RoundTrip(r *http.Request) (*http.Response, error) { 23 | c.Spinner.Start() 24 | defer c.Spinner.Stop() 25 | return c.Next.RoundTrip(r) 26 | } 27 | -------------------------------------------------------------------------------- /pkg/cmd/account/account.go: -------------------------------------------------------------------------------- 1 | package account 2 | 3 | import ( 4 | "github.com/MakeNowJust/heredoc/v2" 5 | cmdAWS "github.com/OctopusDeploy/cli/pkg/cmd/account/aws" 6 | cmdAzure "github.com/OctopusDeploy/cli/pkg/cmd/account/azure" 7 | cmdAzureOidc "github.com/OctopusDeploy/cli/pkg/cmd/account/azure-oidc" 8 | cmdCreate "github.com/OctopusDeploy/cli/pkg/cmd/account/create" 9 | cmdDelete "github.com/OctopusDeploy/cli/pkg/cmd/account/delete" 10 | cmdGCP "github.com/OctopusDeploy/cli/pkg/cmd/account/gcp" 11 | cmdGenericOidc "github.com/OctopusDeploy/cli/pkg/cmd/account/generic-oidc" 12 | cmdList "github.com/OctopusDeploy/cli/pkg/cmd/account/list" 13 | cmdSSH "github.com/OctopusDeploy/cli/pkg/cmd/account/ssh" 14 | cmdToken "github.com/OctopusDeploy/cli/pkg/cmd/account/token" 15 | cmdUsr "github.com/OctopusDeploy/cli/pkg/cmd/account/username" 16 | "github.com/OctopusDeploy/cli/pkg/constants" 17 | "github.com/OctopusDeploy/cli/pkg/constants/annotations" 18 | "github.com/OctopusDeploy/cli/pkg/factory" 19 | "github.com/spf13/cobra" 20 | ) 21 | 22 | func NewCmdAccount(f factory.Factory) *cobra.Command { 23 | cmd := &cobra.Command{ 24 | Use: "account ", 25 | Short: "Manage accounts", 26 | Long: "Manage accounts in Octopus Deploy", 27 | Example: heredoc.Docf("$ %s account list", constants.ExecutableName), 28 | Annotations: map[string]string{ 29 | annotations.IsInfrastructure: "true", 30 | }, 31 | } 32 | 33 | cmd.AddCommand(cmdDelete.NewCmdDelete(f)) 34 | cmd.AddCommand(cmdCreate.NewCmdCreate(f)) 35 | cmd.AddCommand(cmdList.NewCmdList(f)) 36 | cmd.AddCommand(cmdAWS.NewCmdAws(f)) 37 | cmd.AddCommand(cmdAzure.NewCmdAzure(f)) 38 | cmd.AddCommand(cmdAzureOidc.NewCmdAzureOidc(f)) 39 | cmd.AddCommand(cmdGenericOidc.NewCmdGenericOidc(f)) 40 | cmd.AddCommand(cmdGCP.NewCmdGcp(f)) 41 | cmd.AddCommand(cmdSSH.NewCmdSsh(f)) 42 | cmd.AddCommand(cmdUsr.NewCmdUsername(f)) 43 | cmd.AddCommand(cmdToken.NewCmdToken(f)) 44 | return cmd 45 | } 46 | -------------------------------------------------------------------------------- /pkg/cmd/account/aws/aws.go: -------------------------------------------------------------------------------- 1 | package aws 2 | 3 | import ( 4 | "github.com/MakeNowJust/heredoc/v2" 5 | cmdCreate "github.com/OctopusDeploy/cli/pkg/cmd/account/aws/create" 6 | cmdList "github.com/OctopusDeploy/cli/pkg/cmd/account/aws/list" 7 | "github.com/OctopusDeploy/cli/pkg/constants" 8 | "github.com/OctopusDeploy/cli/pkg/factory" 9 | "github.com/spf13/cobra" 10 | ) 11 | 12 | func NewCmdAws(f factory.Factory) *cobra.Command { 13 | cmd := &cobra.Command{ 14 | Use: "aws ", 15 | Short: "Manage AWS accounts", 16 | Long: "Manage AWS accounts in Octopus Deploy", 17 | Example: heredoc.Docf("$ %s account aws list", constants.ExecutableName), 18 | } 19 | 20 | cmd.AddCommand(cmdList.NewCmdList(f)) 21 | cmd.AddCommand(cmdCreate.NewCmdCreate(f)) 22 | return cmd 23 | } 24 | -------------------------------------------------------------------------------- /pkg/cmd/account/aws/list/list.go: -------------------------------------------------------------------------------- 1 | package list 2 | 3 | import ( 4 | "github.com/MakeNowJust/heredoc/v2" 5 | "github.com/OctopusDeploy/cli/pkg/apiclient" 6 | "github.com/OctopusDeploy/cli/pkg/constants" 7 | "github.com/OctopusDeploy/cli/pkg/factory" 8 | "github.com/OctopusDeploy/cli/pkg/output" 9 | "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/accounts" 10 | "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/client" 11 | "github.com/spf13/cobra" 12 | ) 13 | 14 | func NewCmdList(f factory.Factory) *cobra.Command { 15 | cmd := &cobra.Command{ 16 | Use: "list", 17 | Short: "List AWS accounts", 18 | Long: "List AWS accounts in Octopus Deploy", 19 | Example: heredoc.Docf("$ %s account aws list", constants.ExecutableName), 20 | Aliases: []string{"ls"}, 21 | RunE: func(cmd *cobra.Command, _ []string) error { 22 | client, err := f.GetSpacedClient(apiclient.NewRequester(cmd)) 23 | if err != nil { 24 | return err 25 | } 26 | return listAwsAccounts(client, cmd) 27 | }, 28 | } 29 | 30 | return cmd 31 | } 32 | 33 | func listAwsAccounts(client *client.Client, cmd *cobra.Command) error { 34 | accountResources, err := client.Accounts.Get(accounts.AccountsQuery{ 35 | AccountType: accounts.AccountTypeAmazonWebServicesAccount, 36 | }) 37 | if err != nil { 38 | return err 39 | } 40 | items, err := accountResources.GetAllPages(client.Accounts.GetClient()) 41 | if err != nil { 42 | return err 43 | } 44 | 45 | output.PrintArray(items, cmd, output.Mappers[accounts.IAccount]{ 46 | Json: func(item accounts.IAccount) any { 47 | acc := item.(*accounts.AmazonWebServicesAccount) 48 | return &struct { 49 | Id string 50 | Slug string 51 | Name string 52 | AccessKey string 53 | }{ 54 | Id: acc.GetID(), 55 | Slug: acc.GetSlug(), 56 | Name: acc.GetName(), 57 | AccessKey: acc.AccessKey, 58 | } 59 | }, 60 | Table: output.TableDefinition[accounts.IAccount]{ 61 | Header: []string{"NAME", "SLUG", "ACCESS KEY"}, 62 | Row: func(item accounts.IAccount) []string { 63 | acc := item.(*accounts.AmazonWebServicesAccount) 64 | return []string{output.Bold(acc.GetName()), acc.GetSlug(), acc.AccessKey} 65 | }}, 66 | Basic: func(item accounts.IAccount) string { 67 | return item.GetName() 68 | }, 69 | }) 70 | return nil 71 | } 72 | -------------------------------------------------------------------------------- /pkg/cmd/account/azure-oidc/azure-oidc.go: -------------------------------------------------------------------------------- 1 | package azure 2 | 3 | import ( 4 | "github.com/MakeNowJust/heredoc/v2" 5 | cmdCreate "github.com/OctopusDeploy/cli/pkg/cmd/account/azure-oidc/create" 6 | cmdList "github.com/OctopusDeploy/cli/pkg/cmd/account/azure-oidc/list" 7 | "github.com/OctopusDeploy/cli/pkg/constants" 8 | "github.com/OctopusDeploy/cli/pkg/factory" 9 | "github.com/spf13/cobra" 10 | ) 11 | 12 | func NewCmdAzureOidc(f factory.Factory) *cobra.Command { 13 | cmd := &cobra.Command{ 14 | Use: "azure-oidc ", 15 | Short: "Manage Azure OpenID Connect accounts", 16 | Long: "Manage Azure OpenID Connect accounts in Octopus Deploy", 17 | Example: heredoc.Docf("$ %s account azure-oidc list", constants.ExecutableName), 18 | } 19 | 20 | cmd.AddCommand(cmdList.NewCmdList(f)) 21 | cmd.AddCommand(cmdCreate.NewCmdCreate(f)) 22 | 23 | return cmd 24 | } 25 | -------------------------------------------------------------------------------- /pkg/cmd/account/azure/azure.go: -------------------------------------------------------------------------------- 1 | package azure 2 | 3 | import ( 4 | "github.com/MakeNowJust/heredoc/v2" 5 | cmdCreate "github.com/OctopusDeploy/cli/pkg/cmd/account/azure/create" 6 | cmdList "github.com/OctopusDeploy/cli/pkg/cmd/account/azure/list" 7 | "github.com/OctopusDeploy/cli/pkg/constants" 8 | "github.com/OctopusDeploy/cli/pkg/factory" 9 | "github.com/spf13/cobra" 10 | ) 11 | 12 | func NewCmdAzure(f factory.Factory) *cobra.Command { 13 | cmd := &cobra.Command{ 14 | Use: "azure ", 15 | Short: "Manage Azure subscription accounts", 16 | Long: "Manage Azure subscription accounts in Octopus Deploy", 17 | Example: heredoc.Docf("$ %s account azure list", constants.ExecutableName), 18 | } 19 | 20 | cmd.AddCommand(cmdList.NewCmdList(f)) 21 | cmd.AddCommand(cmdCreate.NewCmdCreate(f)) 22 | 23 | return cmd 24 | } 25 | -------------------------------------------------------------------------------- /pkg/cmd/account/gcp/gcp.go: -------------------------------------------------------------------------------- 1 | package gcp 2 | 3 | import ( 4 | "github.com/MakeNowJust/heredoc/v2" 5 | cmdCreate "github.com/OctopusDeploy/cli/pkg/cmd/account/gcp/create" 6 | cmdList "github.com/OctopusDeploy/cli/pkg/cmd/account/gcp/list" 7 | "github.com/OctopusDeploy/cli/pkg/constants" 8 | "github.com/OctopusDeploy/cli/pkg/factory" 9 | "github.com/spf13/cobra" 10 | ) 11 | 12 | func NewCmdGcp(f factory.Factory) *cobra.Command { 13 | cmd := &cobra.Command{ 14 | Use: "gcp ", 15 | Short: "Manage Google Cloud accounts", 16 | Long: "Manage Google Cloud accounts in Octopus Deploy", 17 | Example: heredoc.Docf("$ %s account gcp list", constants.ExecutableName), 18 | } 19 | 20 | cmd.AddCommand(cmdList.NewCmdList(f)) 21 | cmd.AddCommand(cmdCreate.NewCmdCreate(f)) 22 | 23 | return cmd 24 | } 25 | -------------------------------------------------------------------------------- /pkg/cmd/account/gcp/list/list.go: -------------------------------------------------------------------------------- 1 | package list 2 | 3 | import ( 4 | "github.com/MakeNowJust/heredoc/v2" 5 | "github.com/OctopusDeploy/cli/pkg/apiclient" 6 | "github.com/OctopusDeploy/cli/pkg/constants" 7 | "github.com/OctopusDeploy/cli/pkg/factory" 8 | "github.com/OctopusDeploy/cli/pkg/output" 9 | "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/accounts" 10 | "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/client" 11 | "github.com/spf13/cobra" 12 | ) 13 | 14 | func NewCmdList(f factory.Factory) *cobra.Command { 15 | cmd := &cobra.Command{ 16 | Use: "list", 17 | Short: "List Google Cloud accounts", 18 | Long: "List Google Cloud accounts in Octopus Deploy", 19 | Example: heredoc.Docf("$ %s account gcp list", constants.ExecutableName), 20 | Aliases: []string{"ls"}, 21 | RunE: func(cmd *cobra.Command, _ []string) error { 22 | client, err := f.GetSpacedClient(apiclient.NewRequester(cmd)) 23 | if err != nil { 24 | return err 25 | } 26 | return listGcpAccounts(client, cmd) 27 | }, 28 | } 29 | 30 | return cmd 31 | } 32 | 33 | func listGcpAccounts(client *client.Client, cmd *cobra.Command) error { 34 | accountResources, err := client.Accounts.Get(accounts.AccountsQuery{ 35 | AccountType: accounts.AccountTypeGoogleCloudPlatformAccount, 36 | }) 37 | if err != nil { 38 | return err 39 | } 40 | items, err := accountResources.GetAllPages(client.Accounts.GetClient()) 41 | if err != nil { 42 | return err 43 | } 44 | 45 | output.PrintArray(items, cmd, output.Mappers[accounts.IAccount]{ 46 | Json: func(item accounts.IAccount) any { 47 | acc := item.(*accounts.GoogleCloudPlatformAccount) 48 | return &struct { 49 | Id string 50 | Name string 51 | Slug string 52 | }{ 53 | Id: acc.GetID(), 54 | Name: acc.GetName(), 55 | Slug: acc.GetSlug(), 56 | } 57 | }, 58 | Table: output.TableDefinition[accounts.IAccount]{ 59 | Header: []string{"NAME", "SLUG"}, 60 | Row: func(item accounts.IAccount) []string { 61 | acc := item.(*accounts.GoogleCloudPlatformAccount) 62 | return []string{output.Bold(acc.GetName()), acc.GetSlug()} 63 | }}, 64 | Basic: func(item accounts.IAccount) string { 65 | return item.GetName() 66 | }, 67 | }) 68 | return nil 69 | } 70 | -------------------------------------------------------------------------------- /pkg/cmd/account/generic-oidc/generic-oidc.go: -------------------------------------------------------------------------------- 1 | package generic_oidc 2 | 3 | import ( 4 | "github.com/MakeNowJust/heredoc/v2" 5 | cmdCreate "github.com/OctopusDeploy/cli/pkg/cmd/account/generic-oidc/create" 6 | cmdList "github.com/OctopusDeploy/cli/pkg/cmd/account/generic-oidc/list" 7 | "github.com/OctopusDeploy/cli/pkg/constants" 8 | "github.com/OctopusDeploy/cli/pkg/factory" 9 | "github.com/spf13/cobra" 10 | ) 11 | 12 | func NewCmdGenericOidc(f factory.Factory) *cobra.Command { 13 | cmd := &cobra.Command{ 14 | Use: "generic-oidc ", 15 | Short: "Manage Generic OpenID Connect accounts", 16 | Long: "Manage Generic OpenID Connect accounts in Octopus Deploy", 17 | Example: heredoc.Docf("$ %s account generic-oidc list", constants.ExecutableName), 18 | } 19 | 20 | cmd.AddCommand(cmdList.NewCmdList(f)) 21 | cmd.AddCommand(cmdCreate.NewCmdCreate(f)) 22 | 23 | return cmd 24 | } 25 | -------------------------------------------------------------------------------- /pkg/cmd/account/helper/helper.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/client" 7 | "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/environments" 8 | ) 9 | 10 | // ResolveEnvironmentNames takes in an array of names and trys to find an exact match. 11 | // If a match is found it will return its corresponding ID. If no match is found 12 | // it will return the name as is, in assumption it is an ID. 13 | func ResolveEnvironmentNames(envs []string, octopus *client.Client) ([]string, error) { 14 | envIds := make([]string, 0, len(envs)) 15 | loop: 16 | for _, envName := range envs { 17 | matches, err := octopus.Environments.Get(environments.EnvironmentsQuery{ 18 | Name: envName, 19 | }) 20 | if err != nil { 21 | return nil, err 22 | } 23 | allMatches, err := matches.GetAllPages(octopus.Environments.GetClient()) 24 | if err != nil { 25 | return nil, err 26 | } 27 | for _, match := range allMatches { 28 | if strings.EqualFold(envName, match.Name) { 29 | envIds = append(envIds, match.ID) 30 | continue loop 31 | } 32 | } 33 | envIds = append(envIds, envName) 34 | } 35 | return envIds, nil 36 | } 37 | -------------------------------------------------------------------------------- /pkg/cmd/account/shared/shared.go: -------------------------------------------------------------------------------- 1 | package shared 2 | 3 | var AzureEnvMap = map[string]string{ 4 | "Global Cloud (Default)": "AzureCloud", 5 | "China Cloud": "AzureChinaCloud", 6 | "German Cloud": "AzureGermanCloud", 7 | "US Government": "AzureUSGovernment", 8 | } 9 | var AzureADEndpointBaseUri = map[string]string{ 10 | "AzureCloud": "https://login.microsoftonline.com/", 11 | "AzureChinaCloud": "https://login.chinacloudapi.cn/", 12 | "AzureGermanCloud": "https://login.microsoftonline.de/", 13 | "AzureUSGovernment": "https://login.microsoftonline.us/", 14 | } 15 | var AzureResourceManagementBaseUri = map[string]string{ 16 | "AzureCloud": "https://management.azure.com/", 17 | "AzureChinaCloud": "https://management.chinacloudapi.cn/", 18 | "AzureGermanCloud": "https://management.microsoftazure.de/", 19 | "AzureUSGovernment": "https://management.usgovcloudapi.net/", 20 | } 21 | -------------------------------------------------------------------------------- /pkg/cmd/account/ssh/list/list.go: -------------------------------------------------------------------------------- 1 | package list 2 | 3 | import ( 4 | "github.com/MakeNowJust/heredoc/v2" 5 | "github.com/OctopusDeploy/cli/pkg/apiclient" 6 | "github.com/OctopusDeploy/cli/pkg/constants" 7 | "github.com/OctopusDeploy/cli/pkg/factory" 8 | "github.com/OctopusDeploy/cli/pkg/output" 9 | "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/accounts" 10 | "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/client" 11 | "github.com/spf13/cobra" 12 | ) 13 | 14 | func NewCmdList(f factory.Factory) *cobra.Command { 15 | cmd := &cobra.Command{ 16 | Use: "list", 17 | Short: "List SSH Key Pair accounts", 18 | Long: "List SSH Key Pair accounts in Octopus Deploy", 19 | Example: heredoc.Docf("$ %s account ssh list", constants.ExecutableName), 20 | Aliases: []string{"ls"}, 21 | RunE: func(cmd *cobra.Command, _ []string) error { 22 | client, err := f.GetSpacedClient(apiclient.NewRequester(cmd)) 23 | if err != nil { 24 | return err 25 | } 26 | return listSshAccounts(client, cmd) 27 | }, 28 | } 29 | 30 | return cmd 31 | } 32 | 33 | func listSshAccounts(client *client.Client, cmd *cobra.Command) error { 34 | accountResources, err := client.Accounts.Get(accounts.AccountsQuery{ 35 | AccountType: accounts.AccountTypeSSHKeyPair, 36 | }) 37 | if err != nil { 38 | return err 39 | } 40 | items, err := accountResources.GetAllPages(client.Accounts.GetClient()) 41 | if err != nil { 42 | return err 43 | } 44 | 45 | output.PrintArray(items, cmd, output.Mappers[accounts.IAccount]{ 46 | Json: func(item accounts.IAccount) any { 47 | acc := item.(*accounts.SSHKeyAccount) 48 | return &struct { 49 | Id string 50 | Name string 51 | Slug string 52 | Username string 53 | }{ 54 | Id: acc.GetID(), 55 | Name: acc.GetName(), 56 | Slug: acc.GetSlug(), 57 | Username: acc.Username, 58 | } 59 | }, 60 | Table: output.TableDefinition[accounts.IAccount]{ 61 | Header: []string{"NAME", "SLUG", "USERNAME"}, 62 | Row: func(item accounts.IAccount) []string { 63 | acc := item.(*accounts.SSHKeyAccount) 64 | return []string{output.Bold(acc.GetName()), acc.GetSlug(), acc.Username} 65 | }}, 66 | Basic: func(item accounts.IAccount) string { 67 | return item.GetName() 68 | }, 69 | }) 70 | return nil 71 | } 72 | -------------------------------------------------------------------------------- /pkg/cmd/account/ssh/ssh.go: -------------------------------------------------------------------------------- 1 | package ssh 2 | 3 | import ( 4 | "github.com/MakeNowJust/heredoc/v2" 5 | cmdCreate "github.com/OctopusDeploy/cli/pkg/cmd/account/ssh/create" 6 | cmdList "github.com/OctopusDeploy/cli/pkg/cmd/account/ssh/list" 7 | "github.com/OctopusDeploy/cli/pkg/constants" 8 | "github.com/OctopusDeploy/cli/pkg/factory" 9 | "github.com/spf13/cobra" 10 | ) 11 | 12 | func NewCmdSsh(f factory.Factory) *cobra.Command { 13 | cmd := &cobra.Command{ 14 | Use: "ssh ", 15 | Short: "Manage SSH Key Pair accounts", 16 | Long: "Manage SSH Key Pair accounts in Octopus Deploy", 17 | Example: heredoc.Docf("$ %s account ssh list", constants.ExecutableName), 18 | } 19 | 20 | cmd.AddCommand(cmdList.NewCmdList(f)) 21 | cmd.AddCommand(cmdCreate.NewCmdCreate(f)) 22 | 23 | return cmd 24 | } 25 | -------------------------------------------------------------------------------- /pkg/cmd/account/token/list/list.go: -------------------------------------------------------------------------------- 1 | package list 2 | 3 | import ( 4 | "github.com/MakeNowJust/heredoc/v2" 5 | "github.com/OctopusDeploy/cli/pkg/apiclient" 6 | "github.com/OctopusDeploy/cli/pkg/constants" 7 | "github.com/OctopusDeploy/cli/pkg/factory" 8 | "github.com/OctopusDeploy/cli/pkg/output" 9 | "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/accounts" 10 | "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/client" 11 | "github.com/spf13/cobra" 12 | ) 13 | 14 | func NewCmdList(f factory.Factory) *cobra.Command { 15 | cmd := &cobra.Command{ 16 | Use: "list", 17 | Short: "List Token accounts", 18 | Long: "List Token accounts in Octopus Deploy", 19 | Example: heredoc.Docf("$ %s account token list", constants.ExecutableName), 20 | Aliases: []string{"ls"}, 21 | RunE: func(cmd *cobra.Command, _ []string) error { 22 | client, err := f.GetSpacedClient(apiclient.NewRequester(cmd)) 23 | if err != nil { 24 | return err 25 | } 26 | return listTokenAccounts(client, cmd) 27 | }, 28 | } 29 | 30 | return cmd 31 | } 32 | 33 | func listTokenAccounts(client *client.Client, cmd *cobra.Command) error { 34 | accountResources, err := client.Accounts.Get(accounts.AccountsQuery{ 35 | AccountType: accounts.AccountTypeToken, 36 | }) 37 | if err != nil { 38 | return err 39 | } 40 | items, err := accountResources.GetAllPages(client.Accounts.GetClient()) 41 | if err != nil { 42 | return err 43 | } 44 | 45 | output.PrintArray(items, cmd, output.Mappers[accounts.IAccount]{ 46 | Json: func(item accounts.IAccount) any { 47 | acc := item.(*accounts.TokenAccount) 48 | return &struct { 49 | Id string 50 | Name string 51 | Slug string 52 | }{ 53 | Id: acc.GetID(), 54 | Name: acc.GetName(), 55 | Slug: acc.GetSlug(), 56 | } 57 | }, 58 | Table: output.TableDefinition[accounts.IAccount]{ 59 | Header: []string{"NAME", "SLUG"}, 60 | Row: func(item accounts.IAccount) []string { 61 | acc := item.(*accounts.TokenAccount) 62 | return []string{output.Bold(acc.GetName()), acc.GetSlug()} 63 | }}, 64 | Basic: func(item accounts.IAccount) string { 65 | return item.GetName() 66 | }, 67 | }) 68 | return nil 69 | } 70 | -------------------------------------------------------------------------------- /pkg/cmd/account/token/token.go: -------------------------------------------------------------------------------- 1 | package token 2 | 3 | import ( 4 | "fmt" 5 | 6 | cmdCreate "github.com/OctopusDeploy/cli/pkg/cmd/account/token/create" 7 | cmdList "github.com/OctopusDeploy/cli/pkg/cmd/account/token/list" 8 | "github.com/OctopusDeploy/cli/pkg/constants" 9 | "github.com/OctopusDeploy/cli/pkg/factory" 10 | "github.com/spf13/cobra" 11 | ) 12 | 13 | func NewCmdToken(f factory.Factory) *cobra.Command { 14 | cmd := &cobra.Command{ 15 | Use: "token ", 16 | Short: "Manage Token accounts", 17 | Long: "Manage Token accounts in Octopus Deploy", 18 | Example: fmt.Sprintf("$ %s account token list", constants.ExecutableName), 19 | } 20 | 21 | cmd.AddCommand(cmdList.NewCmdList(f)) 22 | cmd.AddCommand(cmdCreate.NewCmdCreate(f)) 23 | 24 | return cmd 25 | } 26 | -------------------------------------------------------------------------------- /pkg/cmd/account/username/list/list.go: -------------------------------------------------------------------------------- 1 | package list 2 | 3 | import ( 4 | "github.com/MakeNowJust/heredoc/v2" 5 | "github.com/OctopusDeploy/cli/pkg/apiclient" 6 | "github.com/OctopusDeploy/cli/pkg/constants" 7 | "github.com/OctopusDeploy/cli/pkg/factory" 8 | "github.com/OctopusDeploy/cli/pkg/output" 9 | "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/accounts" 10 | "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/client" 11 | "github.com/spf13/cobra" 12 | ) 13 | 14 | func NewCmdList(f factory.Factory) *cobra.Command { 15 | cmd := &cobra.Command{ 16 | Use: "list", 17 | Short: "List Username/Password accounts", 18 | Long: "List Username/Password accounts in Octopus Deploy", 19 | Example: heredoc.Docf(` 20 | $ %s account username list" 21 | `, constants.ExecutableName), 22 | Aliases: []string{"ls"}, 23 | RunE: func(cmd *cobra.Command, _ []string) error { 24 | client, err := f.GetSpacedClient(apiclient.NewRequester(cmd)) 25 | if err != nil { 26 | return err 27 | } 28 | return listUsernameAccounts(client, cmd) 29 | }, 30 | } 31 | 32 | return cmd 33 | } 34 | 35 | func listUsernameAccounts(client *client.Client, cmd *cobra.Command) error { 36 | accountResources, err := client.Accounts.Get(accounts.AccountsQuery{ 37 | AccountType: accounts.AccountTypeUsernamePassword, 38 | }) 39 | if err != nil { 40 | return err 41 | } 42 | items, err := accountResources.GetAllPages(client.Accounts.GetClient()) 43 | if err != nil { 44 | return err 45 | } 46 | 47 | output.PrintArray(items, cmd, output.Mappers[accounts.IAccount]{ 48 | Json: func(item accounts.IAccount) any { 49 | acc := item.(*accounts.UsernamePasswordAccount) 50 | return &struct { 51 | Id string 52 | Name string 53 | Slug string 54 | Username string 55 | }{ 56 | Id: acc.GetID(), 57 | Name: acc.GetName(), 58 | Slug: acc.GetSlug(), 59 | Username: acc.Username, 60 | } 61 | }, 62 | Table: output.TableDefinition[accounts.IAccount]{ 63 | Header: []string{"NAME", "SLUG", "USERNAME"}, 64 | Row: func(item accounts.IAccount) []string { 65 | acc := item.(*accounts.UsernamePasswordAccount) 66 | return []string{output.Bold(acc.GetName()), acc.GetSlug(), acc.Username} 67 | }}, 68 | Basic: func(item accounts.IAccount) string { 69 | return item.GetName() 70 | }, 71 | }) 72 | return nil 73 | } 74 | -------------------------------------------------------------------------------- /pkg/cmd/account/username/username.go: -------------------------------------------------------------------------------- 1 | package username 2 | 3 | import ( 4 | "fmt" 5 | 6 | cmdCreate "github.com/OctopusDeploy/cli/pkg/cmd/account/username/create" 7 | cmdList "github.com/OctopusDeploy/cli/pkg/cmd/account/username/list" 8 | "github.com/OctopusDeploy/cli/pkg/constants" 9 | "github.com/OctopusDeploy/cli/pkg/factory" 10 | "github.com/spf13/cobra" 11 | ) 12 | 13 | func NewCmdUsername(f factory.Factory) *cobra.Command { 14 | cmd := &cobra.Command{ 15 | Use: "username ", 16 | Short: "Manage Username/Password accounts", 17 | Long: "Manage Username/Password accounts in Octopus Deploy", 18 | Example: fmt.Sprintf("$ %s account username list", constants.ExecutableName), 19 | } 20 | 21 | cmd.AddCommand(cmdList.NewCmdList(f)) 22 | cmd.AddCommand(cmdCreate.NewCmdCreate(f)) 23 | 24 | return cmd 25 | } 26 | -------------------------------------------------------------------------------- /pkg/cmd/buildinformation/build-information.go: -------------------------------------------------------------------------------- 1 | package buildinformation 2 | 3 | import ( 4 | "fmt" 5 | 6 | cmdBulkDelete "github.com/OctopusDeploy/cli/pkg/cmd/buildinformation/bulkdelete" 7 | cmdDelete "github.com/OctopusDeploy/cli/pkg/cmd/buildinformation/delete" 8 | cmdList "github.com/OctopusDeploy/cli/pkg/cmd/buildinformation/list" 9 | cmdUpload "github.com/OctopusDeploy/cli/pkg/cmd/buildinformation/upload" 10 | cmdView "github.com/OctopusDeploy/cli/pkg/cmd/buildinformation/view" 11 | "github.com/OctopusDeploy/cli/pkg/constants" 12 | "github.com/OctopusDeploy/cli/pkg/constants/annotations" 13 | "github.com/OctopusDeploy/cli/pkg/factory" 14 | "github.com/spf13/cobra" 15 | ) 16 | 17 | func NewCmdBuildInformation(f factory.Factory) *cobra.Command { 18 | cmd := &cobra.Command{ 19 | Use: "build-information ", 20 | Short: "Manage build information", 21 | Long: "Manage build information in Octopus Deploy", 22 | Example: fmt.Sprintf("$ %s build-information upload", constants.ExecutableName), 23 | Aliases: []string{"build-info"}, 24 | Annotations: map[string]string{ 25 | annotations.IsCore: "true", 26 | }, 27 | } 28 | 29 | cmd.AddCommand(cmdBulkDelete.NewCmdBulkDelete(f)) 30 | cmd.AddCommand(cmdDelete.NewCmdDelete(f)) 31 | cmd.AddCommand(cmdView.NewCmdView(f)) 32 | cmd.AddCommand(cmdList.NewCmdList(f)) 33 | cmd.AddCommand(cmdUpload.NewCmdUpload(f)) 34 | return cmd 35 | } 36 | -------------------------------------------------------------------------------- /pkg/cmd/buildinformation/shared/shared.go: -------------------------------------------------------------------------------- 1 | package shared 2 | 3 | type WorkItemAsJson struct { 4 | Id string `json:"Id"` 5 | Source string `json:"Source"` 6 | Description string `json:"Description"` 7 | } 8 | 9 | type CommitAsJson struct { 10 | Id string `json:"Id"` 11 | Comment string `json:"Comment"` 12 | } 13 | 14 | type BuildInfoAsJson struct { 15 | Id string `json:"Id"` 16 | PackageId string `json:"PackageId"` 17 | Version string `json:"Version"` 18 | Branch string `json:"Branch"` 19 | BuildEnvironment string `json:"BuildEnvironment"` 20 | VcsCommitNumber string `json:"VcsCommitNumber"` 21 | VcsType string `json:"VcsType"` 22 | VcsRoot string `json:"VcsRoot"` 23 | Commits []*CommitAsJson `json:"Commits,omitempty"` 24 | WorkItems []*WorkItemAsJson `json:"WorkItems,omitempty"` 25 | } 26 | -------------------------------------------------------------------------------- /pkg/cmd/channel/channel.go: -------------------------------------------------------------------------------- 1 | package channel 2 | 3 | import ( 4 | "github.com/MakeNowJust/heredoc/v2" 5 | cmdCreate "github.com/OctopusDeploy/cli/pkg/cmd/channel/create" 6 | "github.com/OctopusDeploy/cli/pkg/constants" 7 | "github.com/OctopusDeploy/cli/pkg/constants/annotations" 8 | "github.com/OctopusDeploy/cli/pkg/factory" 9 | "github.com/spf13/cobra" 10 | ) 11 | 12 | func NewCmdChannel(f factory.Factory) *cobra.Command { 13 | cmd := &cobra.Command{ 14 | Use: "channel ", 15 | Short: "Manage channels", 16 | Long: "Manage channels in Octopus Deploy", 17 | Example: heredoc.Docf(` 18 | $ %[1]s channel create 19 | `, constants.ExecutableName), 20 | Annotations: map[string]string{ 21 | annotations.IsCore: "true", 22 | }, 23 | } 24 | 25 | cmd.AddCommand(cmdCreate.NewCmdCreate(f)) 26 | 27 | return cmd 28 | } 29 | -------------------------------------------------------------------------------- /pkg/cmd/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | getCmd "github.com/OctopusDeploy/cli/pkg/cmd/config/get" 5 | listCmd "github.com/OctopusDeploy/cli/pkg/cmd/config/list" 6 | setCmd "github.com/OctopusDeploy/cli/pkg/cmd/config/set" 7 | "github.com/OctopusDeploy/cli/pkg/constants/annotations" 8 | "github.com/OctopusDeploy/cli/pkg/factory" 9 | "github.com/spf13/cobra" 10 | ) 11 | 12 | func NewCmdConfig(f factory.Factory) *cobra.Command { 13 | cmd := &cobra.Command{ 14 | Use: "config ", 15 | Short: "Manage CLI configuration", 16 | Long: "Manage the CLI configuration", 17 | Annotations: map[string]string{ 18 | annotations.IsConfiguration: "true", 19 | }, 20 | } 21 | 22 | cmd.AddCommand(getCmd.NewCmdGet(f)) 23 | cmd.AddCommand(setCmd.NewCmdSet(f)) 24 | cmd.AddCommand(listCmd.NewCmdList(f)) 25 | return cmd 26 | } 27 | -------------------------------------------------------------------------------- /pkg/cmd/config/get/get.go: -------------------------------------------------------------------------------- 1 | package get 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | 7 | "github.com/AlecAivazis/survey/v2" 8 | "github.com/OctopusDeploy/cli/pkg/config" 9 | "github.com/OctopusDeploy/cli/pkg/constants" 10 | "github.com/OctopusDeploy/cli/pkg/factory" 11 | "github.com/OctopusDeploy/cli/pkg/question" 12 | "github.com/spf13/cobra" 13 | "github.com/spf13/viper" 14 | ) 15 | 16 | func NewCmdGet(f factory.Factory) *cobra.Command { 17 | cmd := &cobra.Command{ 18 | Use: "get [key]", 19 | Short: "Gets the value of config key for Octopus CLI", 20 | RunE: func(cmd *cobra.Command, args []string) error { 21 | key := "" 22 | if len(args) > 0 { 23 | key = args[0] 24 | } 25 | return getRun(f.IsPromptEnabled(), f.Ask, key, cmd.OutOrStdout()) 26 | 27 | }, 28 | } 29 | return cmd 30 | } 31 | 32 | func getRun(isPromptEnabled bool, ask question.Asker, key string, out io.Writer) error { 33 | if key != "" { 34 | if !config.IsValidKey(key) { 35 | return fmt.Errorf("the key '%s' is not a valid", key) 36 | } 37 | } 38 | value := "" 39 | configFile := viper.New() 40 | configFile.SetConfigFile(viper.ConfigFileUsed()) 41 | configFile.ReadInConfig() 42 | if isPromptEnabled && key == "" { 43 | k, err := promptMissing(ask) 44 | if err != nil { 45 | return err 46 | } 47 | key = k 48 | } 49 | value = configFile.GetString(key) 50 | if value == "" && !configFile.InConfig(key) { 51 | return fmt.Errorf("unable to get value for key: %s", key) 52 | } 53 | 54 | fmt.Fprintln(out, value) 55 | return nil 56 | } 57 | 58 | func promptMissing(ask question.Asker) (string, error) { 59 | keys := []string{ 60 | constants.ConfigApiKey, 61 | constants.ConfigSpace, 62 | constants.ConfigNoPrompt, 63 | constants.ConfigUrl, 64 | constants.ConfigOutputFormat, 65 | constants.ConfigShowOctopus, 66 | constants.ConfigEditor, 67 | // constants.ConfigProxyUrl, 68 | } 69 | 70 | var selectKey string 71 | if err := ask(&survey.Select{ 72 | Options: keys, 73 | Message: "What key you would you would like to see the value of?", 74 | }, &selectKey); err != nil { 75 | return "", err 76 | } 77 | return selectKey, nil 78 | } 79 | -------------------------------------------------------------------------------- /pkg/cmd/dependencies.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/OctopusDeploy/cli/pkg/apiclient" 5 | "io" 6 | 7 | "github.com/OctopusDeploy/cli/pkg/factory" 8 | "github.com/OctopusDeploy/cli/pkg/question" 9 | "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/client" 10 | "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/spaces" 11 | "github.com/spf13/cobra" 12 | ) 13 | 14 | type Dependable interface { 15 | Commit() error 16 | GenerateAutomationCmd() 17 | } 18 | 19 | type Dependencies struct { 20 | Out io.Writer 21 | Client *client.Client 22 | Host string 23 | Space *spaces.Space 24 | NoPrompt bool 25 | Ask question.Asker 26 | CmdPath string 27 | ShowMessagePrefix bool 28 | } 29 | 30 | func NewDependencies(f factory.Factory, cmd *cobra.Command) *Dependencies { 31 | client, err := f.GetSpacedClient(apiclient.NewRequester(cmd)) 32 | if err != nil { 33 | panic(err) 34 | } 35 | 36 | return newDependencies(f, cmd, client) 37 | } 38 | 39 | func NewSystemDependencies(f factory.Factory, cmd *cobra.Command) *Dependencies { 40 | client, err := f.GetSystemClient(apiclient.NewRequester(cmd)) 41 | if err != nil { 42 | panic(err) 43 | } 44 | return newDependencies(f, cmd, client) 45 | } 46 | 47 | func newDependencies(f factory.Factory, cmd *cobra.Command, client *client.Client) *Dependencies { 48 | return &Dependencies{ 49 | Ask: f.Ask, 50 | CmdPath: cmd.CommandPath(), 51 | Out: cmd.OutOrStdout(), 52 | Client: client, 53 | Host: f.GetCurrentHost(), 54 | NoPrompt: !f.IsPromptEnabled(), 55 | Space: f.GetCurrentSpace(), 56 | } 57 | } 58 | 59 | func NewDependenciesFromExisting(opts *Dependencies, cmdPath string) *Dependencies { 60 | return &Dependencies{ 61 | Ask: opts.Ask, 62 | CmdPath: cmdPath, 63 | Out: opts.Out, 64 | Client: opts.Client, 65 | Host: opts.Host, 66 | NoPrompt: opts.NoPrompt, 67 | Space: opts.Space, 68 | ShowMessagePrefix: true, 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /pkg/cmd/environment/environment.go: -------------------------------------------------------------------------------- 1 | package environment 2 | 3 | import ( 4 | "github.com/MakeNowJust/heredoc/v2" 5 | cmdCreate "github.com/OctopusDeploy/cli/pkg/cmd/environment/create" 6 | cmdDelete "github.com/OctopusDeploy/cli/pkg/cmd/environment/delete" 7 | cmdList "github.com/OctopusDeploy/cli/pkg/cmd/environment/list" 8 | "github.com/OctopusDeploy/cli/pkg/constants" 9 | "github.com/OctopusDeploy/cli/pkg/constants/annotations" 10 | "github.com/OctopusDeploy/cli/pkg/factory" 11 | "github.com/spf13/cobra" 12 | ) 13 | 14 | func NewCmdEnvironment(f factory.Factory) *cobra.Command { 15 | cmd := &cobra.Command{ 16 | Use: "environment ", 17 | Short: "Manage environments", 18 | Long: "Manage environments in Octopus Deploy", 19 | Example: heredoc.Docf(` 20 | $ %[1]s environment list 21 | $ %[1]s environment ls 22 | `, constants.ExecutableName), 23 | Annotations: map[string]string{ 24 | annotations.IsInfrastructure: "true", 25 | }, 26 | } 27 | 28 | cmd.AddCommand(cmdList.NewCmdList(f)) 29 | cmd.AddCommand(cmdDelete.NewCmdDelete(f)) 30 | cmd.AddCommand(cmdCreate.NewCmdCreate(f)) 31 | return cmd 32 | } 33 | -------------------------------------------------------------------------------- /pkg/cmd/environment/list/list.go: -------------------------------------------------------------------------------- 1 | package list 2 | 3 | import ( 4 | "github.com/OctopusDeploy/cli/pkg/apiclient" 5 | "strconv" 6 | 7 | "github.com/MakeNowJust/heredoc/v2" 8 | "github.com/OctopusDeploy/cli/pkg/constants" 9 | "github.com/OctopusDeploy/cli/pkg/factory" 10 | "github.com/OctopusDeploy/cli/pkg/output" 11 | "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/environments" 12 | "github.com/spf13/cobra" 13 | ) 14 | 15 | func NewCmdList(f factory.Factory) *cobra.Command { 16 | cmd := &cobra.Command{ 17 | Use: "list", 18 | Short: "List environments", 19 | Long: "List environments in Octopus Deploy", 20 | Example: heredoc.Docf(` 21 | $ %[1]s environment list 22 | $ %[1]s environment ls" 23 | `, constants.ExecutableName), 24 | Aliases: []string{"ls"}, 25 | RunE: func(cmd *cobra.Command, args []string) error { 26 | client, err := f.GetSpacedClient(apiclient.NewRequester(cmd)) 27 | if err != nil { 28 | return err 29 | } 30 | 31 | envResources, err := client.Environments.Get(environments.EnvironmentsQuery{}) 32 | if err != nil { 33 | return err 34 | } 35 | allEnvs, err := envResources.GetAllPages(client.Environments.GetClient()) 36 | if err != nil { 37 | return err 38 | } 39 | 40 | return output.PrintArray(allEnvs, cmd, output.Mappers[*environments.Environment]{ 41 | Json: func(item *environments.Environment) any { 42 | return output.IdAndName{Id: item.GetID(), Name: item.Name} 43 | }, 44 | Table: output.TableDefinition[*environments.Environment]{ 45 | Header: []string{"NAME", "GUIDED FAILURE"}, 46 | Row: func(item *environments.Environment) []string { 47 | 48 | return []string{output.Bold(item.Name), strconv.FormatBool(item.UseGuidedFailure)} 49 | }, 50 | }, 51 | Basic: func(item *environments.Environment) string { 52 | return item.Name 53 | }, 54 | }) 55 | }, 56 | } 57 | 58 | return cmd 59 | } 60 | -------------------------------------------------------------------------------- /pkg/cmd/logout/logout.go: -------------------------------------------------------------------------------- 1 | package logout 2 | 3 | import ( 4 | "github.com/OctopusDeploy/cli/pkg/constants/annotations" 5 | "github.com/OctopusDeploy/cli/pkg/factory" 6 | "github.com/spf13/cobra" 7 | ) 8 | 9 | func NewCmdLogout(f factory.Factory) *cobra.Command { 10 | cmd := &cobra.Command{ 11 | Use: "logout", 12 | Short: "Logout of Octopus", 13 | Long: "Logout of your Octopus server", 14 | RunE: func(cmd *cobra.Command, args []string) error { 15 | return logoutRun(f, cmd) 16 | }, 17 | Annotations: map[string]string{ 18 | annotations.IsConfiguration: "true", 19 | }, 20 | } 21 | 22 | return cmd 23 | } 24 | 25 | func logoutRun(f factory.Factory, cmd *cobra.Command) error { 26 | configProvider, err := f.GetConfigProvider() 27 | 28 | if err != nil { 29 | return err 30 | } 31 | configProvider.Set("Url", "") 32 | configProvider.Set("ApiKey", "") 33 | configProvider.Set("AccessToken", "") 34 | 35 | cmd.Printf("Logout successful") 36 | 37 | return nil 38 | } 39 | -------------------------------------------------------------------------------- /pkg/cmd/logout/logout_test.go: -------------------------------------------------------------------------------- 1 | package logout_test 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | 7 | logoutCmd "github.com/OctopusDeploy/cli/pkg/cmd/logout" 8 | "github.com/OctopusDeploy/cli/pkg/constants" 9 | "github.com/OctopusDeploy/cli/test/testutil" 10 | "github.com/spf13/cobra" 11 | "github.com/spf13/viper" 12 | "github.com/stretchr/testify/assert" 13 | ) 14 | 15 | func TestLogout_SetsConfigCorrectly(t *testing.T) { 16 | api := testutil.NewMockHttpServer() 17 | fac := testutil.NewMockFactory(api) 18 | 19 | configProvider, err := fac.GetConfigProvider() 20 | 21 | assert.Nil(t, err) 22 | 23 | configProvider.Set(constants.ConfigUrl, "https://some.octopus.app") 24 | configProvider.Set(constants.ConfigApiKey, "API-APIKEY01") 25 | configProvider.Set(constants.ConfigUrl, "accesstoken") 26 | 27 | logoutCmd := logoutCmd.NewCmdLogout(fac) 28 | stdout, stderr := &bytes.Buffer{}, &bytes.Buffer{} 29 | logoutCmd.SetOut(stdout) 30 | logoutCmd.SetErr(stderr) 31 | cmdReceiver := testutil.GoBegin2(func() (*cobra.Command, error) { 32 | return logoutCmd.ExecuteC() 33 | }) 34 | 35 | _, err = testutil.ReceivePair(cmdReceiver) 36 | assert.Nil(t, err) 37 | assert.Empty(t, viper.GetString(constants.ConfigUrl)) 38 | assert.Empty(t, viper.GetString(constants.ConfigApiKey)) 39 | assert.Empty(t, viper.GetString(constants.ConfigAccessToken)) 40 | } 41 | -------------------------------------------------------------------------------- /pkg/cmd/model/entity.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | type Entity struct { 4 | Id string `json:"Id"` 5 | Name string `json:"Name"` 6 | } 7 | -------------------------------------------------------------------------------- /pkg/cmd/package/nuget/nuget.go: -------------------------------------------------------------------------------- 1 | package nuget 2 | 3 | import ( 4 | "github.com/MakeNowJust/heredoc/v2" 5 | cmdNugetCreate "github.com/OctopusDeploy/cli/pkg/cmd/package/nuget/create" 6 | "github.com/OctopusDeploy/cli/pkg/constants" 7 | "github.com/OctopusDeploy/cli/pkg/factory" 8 | "github.com/spf13/cobra" 9 | ) 10 | 11 | func NewCmdPackageNuget(f factory.Factory) *cobra.Command { 12 | cmd := &cobra.Command{ 13 | Use: "nuget ", 14 | Short: "Package as NuPkg", 15 | Long: "Package as NuPkg for Octopus Deploy", 16 | Example: heredoc.Docf("$ %s package nuget create", constants.ExecutableName), 17 | } 18 | 19 | cmd.AddCommand(cmdNugetCreate.NewCmdCreate(f)) 20 | 21 | return cmd 22 | } 23 | -------------------------------------------------------------------------------- /pkg/cmd/package/package.go: -------------------------------------------------------------------------------- 1 | package _package 2 | 3 | import ( 4 | "fmt" 5 | 6 | cmdDelete "github.com/OctopusDeploy/cli/pkg/cmd/package/delete" 7 | cmdList "github.com/OctopusDeploy/cli/pkg/cmd/package/list" 8 | cmdNuget "github.com/OctopusDeploy/cli/pkg/cmd/package/nuget" 9 | cmdUpload "github.com/OctopusDeploy/cli/pkg/cmd/package/upload" 10 | cmdVersions "github.com/OctopusDeploy/cli/pkg/cmd/package/versions" 11 | cmdZip "github.com/OctopusDeploy/cli/pkg/cmd/package/zip" 12 | "github.com/OctopusDeploy/cli/pkg/constants" 13 | "github.com/OctopusDeploy/cli/pkg/constants/annotations" 14 | "github.com/OctopusDeploy/cli/pkg/factory" 15 | "github.com/spf13/cobra" 16 | ) 17 | 18 | func NewCmdPackage(f factory.Factory) *cobra.Command { 19 | cmd := &cobra.Command{ 20 | Use: "package ", 21 | Short: "Manage packages", 22 | Long: "Manage packages in Octopus Deploy", 23 | Example: fmt.Sprintf("$ %s package upload", constants.ExecutableName), 24 | Annotations: map[string]string{ 25 | annotations.IsCore: "true", 26 | }, 27 | } 28 | 29 | cmd.AddCommand(cmdUpload.NewCmdUpload(f)) 30 | cmd.AddCommand(cmdList.NewCmdList(f)) 31 | cmd.AddCommand(cmdVersions.NewCmdVersions(f)) 32 | cmd.AddCommand(cmdNuget.NewCmdPackageNuget(f)) 33 | cmd.AddCommand(cmdZip.NewCmdPackageZip(f)) 34 | cmd.AddCommand(cmdDelete.NewCmdDelete(f)) 35 | 36 | return cmd 37 | } 38 | -------------------------------------------------------------------------------- /pkg/cmd/package/zip/zip.go: -------------------------------------------------------------------------------- 1 | package zip 2 | 3 | import ( 4 | "github.com/MakeNowJust/heredoc/v2" 5 | cmdZipCreate "github.com/OctopusDeploy/cli/pkg/cmd/package/zip/create" 6 | "github.com/OctopusDeploy/cli/pkg/constants" 7 | "github.com/OctopusDeploy/cli/pkg/factory" 8 | "github.com/spf13/cobra" 9 | ) 10 | 11 | func NewCmdPackageZip(f factory.Factory) *cobra.Command { 12 | cmd := &cobra.Command{ 13 | Use: "zip ", 14 | Short: "Package as zip", 15 | Long: "Package as zip for Octopus Deploy", 16 | Example: heredoc.Docf("$ %s package zip create", constants.ExecutableName), 17 | } 18 | 19 | cmd.AddCommand(cmdZipCreate.NewCmdCreate(f)) 20 | 21 | return cmd 22 | } 23 | -------------------------------------------------------------------------------- /pkg/cmd/project/branch/branch.go: -------------------------------------------------------------------------------- 1 | package branch 2 | 3 | import ( 4 | "github.com/MakeNowJust/heredoc/v2" 5 | "github.com/OctopusDeploy/cli/pkg/constants" 6 | "github.com/OctopusDeploy/cli/pkg/constants/annotations" 7 | "github.com/OctopusDeploy/cli/pkg/factory" 8 | "github.com/spf13/cobra" 9 | 10 | cmdCreate "github.com/OctopusDeploy/cli/pkg/cmd/project/branch/create" 11 | cmdList "github.com/OctopusDeploy/cli/pkg/cmd/project/branch/list" 12 | ) 13 | 14 | func NewCmdBranch(f factory.Factory) *cobra.Command { 15 | cmd := &cobra.Command{ 16 | Use: "branch ", 17 | Short: "Manage project branches", 18 | Long: "Manage project branches in Octopus Deploy", 19 | Example: heredoc.Docf(` 20 | $ %[1]s project branch list "Deploy Web App" 21 | $ %[1]s project branch create -p "Deploy Web App" --new-branch-name add-name-variable --base-branch refs/heads/main - 22 | `, constants.ExecutableName), 23 | Annotations: map[string]string{ 24 | annotations.IsCore: "true", 25 | }, 26 | } 27 | 28 | cmd.AddCommand(cmdList.NewCmdList(f)) 29 | cmd.AddCommand(cmdCreate.NewCmdCreate(f)) 30 | 31 | return cmd 32 | } 33 | -------------------------------------------------------------------------------- /pkg/cmd/project/branch/shared/shared.go: -------------------------------------------------------------------------------- 1 | package shared 2 | 3 | import ( 4 | "github.com/OctopusDeploy/cli/pkg/cmd" 5 | "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/client" 6 | "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/projectbranches" 7 | "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/projects" 8 | ) 9 | 10 | type GetAllBranchesCallback func(projectId string) ([]*projects.GitReference, error) 11 | 12 | type ProjectBranchCallbacks struct { 13 | GetAllBranchesCallback GetAllBranchesCallback 14 | } 15 | 16 | func NewProjectBranchCallbacks(dependencies *cmd.Dependencies) *ProjectBranchCallbacks { 17 | return &ProjectBranchCallbacks{ 18 | GetAllBranchesCallback: func(projectId string) ([]*projects.GitReference, error) { 19 | return getAllBranches(dependencies.Client, dependencies.Space.GetID(), projectId) 20 | }, 21 | } 22 | } 23 | 24 | func getAllBranches(client *client.Client, spaceId string, projectId string) ([]*projects.GitReference, error) { 25 | branches, err := client.ProjectBranches.Get(spaceId, projectId, projectbranches.ProjectBranchQuery{Skip: 0, Take: 9999}) 26 | if err != nil { 27 | return nil, err 28 | } 29 | return branches.Items, nil 30 | } 31 | -------------------------------------------------------------------------------- /pkg/cmd/project/connect/connect.go: -------------------------------------------------------------------------------- 1 | package connect 2 | 3 | import ( 4 | "github.com/MakeNowJust/heredoc/v2" 5 | "github.com/OctopusDeploy/cli/pkg/cmd" 6 | connectTenant "github.com/OctopusDeploy/cli/pkg/cmd/tenant/connect" 7 | "github.com/OctopusDeploy/cli/pkg/constants" 8 | "github.com/OctopusDeploy/cli/pkg/factory" 9 | "github.com/spf13/cobra" 10 | ) 11 | 12 | func NewCmdConnect(f factory.Factory) *cobra.Command { 13 | connectFlags := connectTenant.NewConnectFlags() 14 | cmd := &cobra.Command{ 15 | Use: "connect", 16 | Short: "Connect a tenant to a project", 17 | Long: "Connect a tenant to a project in Octopus Deploy", 18 | Example: heredoc.Docf(` 19 | $ %[1]s project connect 20 | $ %[1]s project connect --tenant "Bobs Wood Shop" --project "Deploy web site" --environment "Production" 21 | `, constants.ExecutableName), 22 | RunE: func(c *cobra.Command, args []string) error { 23 | opts := connectTenant.NewConnectOptions(connectFlags, cmd.NewDependencies(f, c)) 24 | 25 | return connectTenant.ConnectRun(opts) 26 | }, 27 | } 28 | 29 | connectTenant.ConfigureFlags(cmd, connectFlags) 30 | return cmd 31 | } 32 | -------------------------------------------------------------------------------- /pkg/cmd/project/create/create_test.go: -------------------------------------------------------------------------------- 1 | package create_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/OctopusDeploy/cli/pkg/cmd" 7 | projectCreate "github.com/OctopusDeploy/cli/pkg/cmd/project/create" 8 | "github.com/OctopusDeploy/cli/test/testutil" 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func TestPromptForConfigAsCode_NotUsingCac(t *testing.T) { 13 | pa := []*testutil.PA{ 14 | testutil.NewConfirmPrompt("Would you like to use Config as Code?", "", false), 15 | } 16 | 17 | asker, checkRemainingPrompts := testutil.NewMockAsker(t, pa) 18 | flags := projectCreate.NewCreateFlags() 19 | flags.ConfigAsCode.Value = false 20 | 21 | var convertCallbackCalled bool 22 | opts := projectCreate.NewCreateOptions(flags, &cmd.Dependencies{Ask: asker}) 23 | opts.ConvertProjectCallback = func() (cmd.Dependable, error) { 24 | convertCallbackCalled = true 25 | return nil, nil 26 | } 27 | 28 | _, err := projectCreate.PromptForConfigAsCode(opts) 29 | checkRemainingPrompts() 30 | assert.NoError(t, err) 31 | assert.False(t, convertCallbackCalled) 32 | assert.False(t, opts.ConfigAsCode.Value) 33 | } 34 | -------------------------------------------------------------------------------- /pkg/cmd/project/disable/disable.go: -------------------------------------------------------------------------------- 1 | package disable 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/MakeNowJust/heredoc/v2" 7 | "github.com/OctopusDeploy/cli/pkg/cmd" 8 | "github.com/OctopusDeploy/cli/pkg/constants" 9 | "github.com/OctopusDeploy/cli/pkg/factory" 10 | "github.com/OctopusDeploy/cli/pkg/question/selectors" 11 | "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/projects" 12 | "github.com/spf13/cobra" 13 | ) 14 | 15 | type DisableOptions struct { 16 | *cmd.Dependencies 17 | IdOrName string 18 | } 19 | 20 | func NewDisableOptions(args []string, dependencies *cmd.Dependencies) *DisableOptions { 21 | return &DisableOptions{ 22 | Dependencies: dependencies, 23 | IdOrName: args[0], 24 | } 25 | } 26 | 27 | func NewCmdDisable(f factory.Factory) *cobra.Command { 28 | cmd := &cobra.Command{ 29 | Use: "disable", 30 | Short: "Disable a project", 31 | Long: "Disable a project in Octopus Deploy", 32 | Example: heredoc.Docf("$ %[1]s project disable", constants.ExecutableName), 33 | RunE: func(c *cobra.Command, args []string) error { 34 | if len(args) == 0 { 35 | args = append(args, "") 36 | } 37 | 38 | opts := NewDisableOptions(args, cmd.NewDependencies(f, c)) 39 | return disableRun(opts) 40 | }, 41 | } 42 | 43 | return cmd 44 | } 45 | 46 | func disableRun(opts *DisableOptions) error { 47 | if !opts.NoPrompt { 48 | if err := PromptMissing(opts); err != nil { 49 | return err 50 | } 51 | } 52 | 53 | if opts.IdOrName == "" { 54 | return fmt.Errorf("project identifier is required but was not provided") 55 | } 56 | 57 | projectToDisable, err := projects.GetByIdentifier(opts.Client, opts.Client.GetSpaceID(), opts.IdOrName) 58 | if err != nil { 59 | return err 60 | } 61 | 62 | projectToDisable.IsDisabled = true 63 | _, err = projects.Update(opts.Client, projectToDisable) 64 | if err != nil { 65 | return err 66 | } 67 | 68 | return nil 69 | } 70 | 71 | func PromptMissing(opts *DisableOptions) error { 72 | if opts.IdOrName == "" { 73 | existingProjects, err := opts.Client.Projects.GetAll() 74 | if err != nil { 75 | return err 76 | } 77 | selectedProject, err := selectors.ByName(opts.Ask, existingProjects, "Select the project you wish to disable:") 78 | if err != nil { 79 | return err 80 | } 81 | opts.IdOrName = selectedProject.GetID() 82 | } 83 | 84 | return nil 85 | } 86 | -------------------------------------------------------------------------------- /pkg/cmd/project/disconnect/disconnect.go: -------------------------------------------------------------------------------- 1 | package disconnect 2 | 3 | import ( 4 | "github.com/MakeNowJust/heredoc/v2" 5 | "github.com/OctopusDeploy/cli/pkg/cmd" 6 | tenantDisconnect "github.com/OctopusDeploy/cli/pkg/cmd/tenant/disconnect" 7 | "github.com/OctopusDeploy/cli/pkg/constants" 8 | "github.com/OctopusDeploy/cli/pkg/factory" 9 | "github.com/spf13/cobra" 10 | ) 11 | 12 | func NewCmdDisconnect(f factory.Factory) *cobra.Command { 13 | disconnectFlags := tenantDisconnect.NewDisconnectFlags() 14 | 15 | cmd := &cobra.Command{ 16 | Use: "disconnect", 17 | Short: "Disconnect a tenant from a project", 18 | Long: "Disconnect a tenant from a project in Octopus Deploy", 19 | Example: heredoc.Docf(` 20 | $ %[1]s project disconnect 21 | $ %[1]s project disconnect --tenant "Test Tenant" --project "Deploy web site" --confirm 22 | `, constants.ExecutableName), 23 | RunE: func(c *cobra.Command, args []string) error { 24 | opts := tenantDisconnect.NewDisconnectOptions(disconnectFlags, cmd.NewDependencies(f, c)) 25 | return tenantDisconnect.DisconnectRun(opts) 26 | }, 27 | } 28 | 29 | tenantDisconnect.ConfigureFlags(cmd, disconnectFlags) 30 | return cmd 31 | } 32 | -------------------------------------------------------------------------------- /pkg/cmd/project/enable/enable.go: -------------------------------------------------------------------------------- 1 | package enable 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/MakeNowJust/heredoc/v2" 7 | "github.com/OctopusDeploy/cli/pkg/cmd" 8 | "github.com/OctopusDeploy/cli/pkg/constants" 9 | "github.com/OctopusDeploy/cli/pkg/factory" 10 | "github.com/OctopusDeploy/cli/pkg/question/selectors" 11 | "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/projects" 12 | "github.com/spf13/cobra" 13 | ) 14 | 15 | type EnableOptions struct { 16 | *cmd.Dependencies 17 | IdOrName string 18 | } 19 | 20 | func NewEnableOptions(args []string, dependencies *cmd.Dependencies) *EnableOptions { 21 | return &EnableOptions{ 22 | Dependencies: dependencies, 23 | IdOrName: args[0], 24 | } 25 | } 26 | 27 | func NewCmdEnable(f factory.Factory) *cobra.Command { 28 | cmd := &cobra.Command{ 29 | Use: "enable", 30 | Short: "Enable a project", 31 | Long: "Enable a project in Octopus Deploy", 32 | Example: heredoc.Docf("$ %[1]s project enable", constants.ExecutableName), 33 | RunE: func(c *cobra.Command, args []string) error { 34 | if len(args) == 0 { 35 | args = append(args, "") 36 | } 37 | 38 | opts := NewEnableOptions(args, cmd.NewDependencies(f, c)) 39 | return disableRun(opts) 40 | }, 41 | } 42 | 43 | return cmd 44 | } 45 | 46 | func disableRun(opts *EnableOptions) error { 47 | if !opts.NoPrompt { 48 | if err := PromptMissing(opts); err != nil { 49 | return err 50 | } 51 | } 52 | 53 | if opts.IdOrName == "" { 54 | return fmt.Errorf("project identifier is required but was not provided") 55 | } 56 | 57 | projectToEnable, err := projects.GetByIdentifier(opts.Client, opts.Client.GetSpaceID(), opts.IdOrName) 58 | if err != nil { 59 | return err 60 | } 61 | 62 | projectToEnable.IsDisabled = false 63 | _, err = projects.Update(opts.Client, projectToEnable) 64 | if err != nil { 65 | return err 66 | } 67 | 68 | return nil 69 | } 70 | 71 | func PromptMissing(opts *EnableOptions) error { 72 | if opts.IdOrName == "" { 73 | existingProjects, err := opts.Client.Projects.GetAll() 74 | if err != nil { 75 | return err 76 | } 77 | selectedProject, err := selectors.ByName(opts.Ask, existingProjects, "Select the project you wish to enable:") 78 | if err != nil { 79 | return err 80 | } 81 | opts.IdOrName = selectedProject.GetID() 82 | } 83 | 84 | return nil 85 | } 86 | -------------------------------------------------------------------------------- /pkg/cmd/project/list/list.go: -------------------------------------------------------------------------------- 1 | package list 2 | 3 | import ( 4 | "github.com/MakeNowJust/heredoc/v2" 5 | "github.com/OctopusDeploy/cli/pkg/apiclient" 6 | "github.com/OctopusDeploy/cli/pkg/constants" 7 | "github.com/OctopusDeploy/cli/pkg/factory" 8 | "github.com/OctopusDeploy/cli/pkg/output" 9 | "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/projects" 10 | "github.com/spf13/cobra" 11 | ) 12 | 13 | func NewCmdList(f factory.Factory) *cobra.Command { 14 | cmd := &cobra.Command{ 15 | Use: "list", 16 | Short: "List projects", 17 | Long: "List projects in Octopus Deploy", 18 | Example: heredoc.Docf(` 19 | $ %[1]s project list 20 | $ %[1]s project ls 21 | `, constants.ExecutableName), 22 | Aliases: []string{"ls"}, 23 | RunE: func(cmd *cobra.Command, args []string) error { 24 | return listRun(cmd, f) 25 | }, 26 | } 27 | 28 | return cmd 29 | } 30 | 31 | type ProjectAsJson struct { 32 | Id string `json:"Id"` 33 | Name string `json:"Name"` 34 | Description string `json:"Description"` 35 | } 36 | 37 | func listRun(cmd *cobra.Command, f factory.Factory) error { 38 | client, err := f.GetSpacedClient(apiclient.NewRequester(cmd)) 39 | if err != nil { 40 | return err 41 | } 42 | 43 | allProjects, err := client.Projects.GetAll() 44 | if err != nil { 45 | return err 46 | } 47 | 48 | return output.PrintArray(allProjects, cmd, output.Mappers[*projects.Project]{ 49 | Json: func(p *projects.Project) any { 50 | return ProjectAsJson{ 51 | Id: p.GetID(), 52 | Name: p.GetName(), 53 | Description: p.Description, 54 | } 55 | }, 56 | Table: output.TableDefinition[*projects.Project]{ 57 | Header: []string{"NAME", "DESCRIPTION"}, 58 | Row: func(p *projects.Project) []string { 59 | return []string{output.Bold(p.Name), p.Description} 60 | }, 61 | }, 62 | Basic: func(p *projects.Project) string { 63 | return p.GetName() 64 | }, 65 | }) 66 | } 67 | -------------------------------------------------------------------------------- /pkg/cmd/project/project.go: -------------------------------------------------------------------------------- 1 | package project 2 | 3 | import ( 4 | "github.com/MakeNowJust/heredoc/v2" 5 | cmdBranch "github.com/OctopusDeploy/cli/pkg/cmd/project/branch" 6 | cmdClone "github.com/OctopusDeploy/cli/pkg/cmd/project/clone" 7 | cmdConnect "github.com/OctopusDeploy/cli/pkg/cmd/project/connect" 8 | cmdConvert "github.com/OctopusDeploy/cli/pkg/cmd/project/convert" 9 | cmdCreate "github.com/OctopusDeploy/cli/pkg/cmd/project/create" 10 | cmdDelete "github.com/OctopusDeploy/cli/pkg/cmd/project/delete" 11 | cmdDisable "github.com/OctopusDeploy/cli/pkg/cmd/project/disable" 12 | cmdDisconnect "github.com/OctopusDeploy/cli/pkg/cmd/project/disconnect" 13 | cmdEnable "github.com/OctopusDeploy/cli/pkg/cmd/project/enable" 14 | cmdList "github.com/OctopusDeploy/cli/pkg/cmd/project/list" 15 | cmdVariables "github.com/OctopusDeploy/cli/pkg/cmd/project/variables" 16 | cmdView "github.com/OctopusDeploy/cli/pkg/cmd/project/view" 17 | "github.com/OctopusDeploy/cli/pkg/constants" 18 | "github.com/OctopusDeploy/cli/pkg/constants/annotations" 19 | "github.com/OctopusDeploy/cli/pkg/factory" 20 | "github.com/spf13/cobra" 21 | ) 22 | 23 | func NewCmdProject(f factory.Factory) *cobra.Command { 24 | cmd := &cobra.Command{ 25 | Use: "project ", 26 | Aliases: []string{"proj"}, 27 | Short: "Manage projects", 28 | Long: "Manage projects in Octopus Deploy", 29 | Example: heredoc.Docf(` 30 | $ %[1]s project list 31 | $ %[1]s project ls 32 | `, constants.ExecutableName), 33 | Annotations: map[string]string{ 34 | annotations.IsCore: "true", 35 | }, 36 | } 37 | 38 | cmd.AddCommand(cmdList.NewCmdList(f)) 39 | cmd.AddCommand(cmdView.NewCmdView(f)) 40 | cmd.AddCommand(cmdCreate.NewCmdCreate(f)) 41 | cmd.AddCommand(cmdDelete.NewCmdList(f)) 42 | cmd.AddCommand(cmdDisable.NewCmdDisable(f)) 43 | cmd.AddCommand(cmdEnable.NewCmdEnable(f)) 44 | cmd.AddCommand(cmdConnect.NewCmdConnect(f)) 45 | cmd.AddCommand(cmdDisconnect.NewCmdDisconnect(f)) 46 | cmd.AddCommand(cmdConvert.NewCmdConvert(f)) 47 | cmd.AddCommand(cmdVariables.NewCmdVariables(f)) 48 | cmd.AddCommand(cmdClone.NewCmdClone(f)) 49 | cmd.AddCommand(cmdBranch.NewCmdBranch(f)) 50 | 51 | return cmd 52 | } 53 | -------------------------------------------------------------------------------- /pkg/cmd/project/shared/shared.go: -------------------------------------------------------------------------------- 1 | package shared 2 | 3 | import ( 4 | "github.com/OctopusDeploy/cli/pkg/cmd" 5 | projectGroupCreate "github.com/OctopusDeploy/cli/pkg/cmd/projectgroup/create" 6 | "github.com/OctopusDeploy/cli/pkg/question" 7 | "github.com/OctopusDeploy/cli/pkg/question/selectors" 8 | "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/client" 9 | "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/projectgroups" 10 | ) 11 | 12 | type CreateProjectGroupCallback func() (string, cmd.Dependable, error) 13 | type GetAllGroupsCallback func() ([]*projectgroups.ProjectGroup, error) 14 | 15 | func GetAllGroups(client client.Client) ([]*projectgroups.ProjectGroup, error) { 16 | res, err := client.ProjectGroups.GetAll() 17 | if err != nil { 18 | return nil, err 19 | } 20 | return res, nil 21 | } 22 | 23 | func CreateProjectGroup(dependencies *cmd.Dependencies) (string, cmd.Dependable, error) { 24 | optValues := projectGroupCreate.NewCreateFlags() 25 | projectGroupOpts := cmd.NewDependenciesFromExisting(dependencies, "octopus project-group create") 26 | 27 | projectGroupCreateOpts := projectGroupCreate.NewCreateOptions(optValues, projectGroupOpts) 28 | projectGroupCreate.PromptMissing(projectGroupCreateOpts) 29 | returnValue := projectGroupCreateOpts.Name.Value 30 | return returnValue, projectGroupCreateOpts, nil 31 | } 32 | 33 | func AskProjectGroups(ask question.Asker, value string, getAllGroupsCallback GetAllGroupsCallback, createProjectGroupCallback CreateProjectGroupCallback) (string, cmd.Dependable, error) { 34 | if value != "" { 35 | return value, nil, nil 36 | } 37 | g, shouldCreateNew, err := selectors.SelectOrNew(ask, "You have not specified a Project group for this project. Please select one:", getAllGroupsCallback, func(pg *projectgroups.ProjectGroup) string { 38 | return pg.Name 39 | }) 40 | if err != nil { 41 | return "", nil, err 42 | } 43 | if shouldCreateNew { 44 | return createProjectGroupCallback() 45 | } 46 | return g.Name, nil, nil 47 | } 48 | -------------------------------------------------------------------------------- /pkg/cmd/project/variables/variables.go: -------------------------------------------------------------------------------- 1 | package variables 2 | 3 | import ( 4 | "github.com/MakeNowJust/heredoc/v2" 5 | cmdCreate "github.com/OctopusDeploy/cli/pkg/cmd/project/variables/create" 6 | cmdDelete "github.com/OctopusDeploy/cli/pkg/cmd/project/variables/delete" 7 | cmdExclude "github.com/OctopusDeploy/cli/pkg/cmd/project/variables/exclude" 8 | cmdInclude "github.com/OctopusDeploy/cli/pkg/cmd/project/variables/include" 9 | cmdList "github.com/OctopusDeploy/cli/pkg/cmd/project/variables/list" 10 | cmdUpdate "github.com/OctopusDeploy/cli/pkg/cmd/project/variables/update" 11 | cmdView "github.com/OctopusDeploy/cli/pkg/cmd/project/variables/view" 12 | "github.com/OctopusDeploy/cli/pkg/constants" 13 | "github.com/OctopusDeploy/cli/pkg/constants/annotations" 14 | "github.com/OctopusDeploy/cli/pkg/factory" 15 | "github.com/spf13/cobra" 16 | ) 17 | 18 | func NewCmdVariables(f factory.Factory) *cobra.Command { 19 | cmd := &cobra.Command{ 20 | Use: "variables ", 21 | Aliases: []string{"variable"}, 22 | Short: "Manage project variables", 23 | Long: "Manage project variables in Octopus Deploy", 24 | Example: heredoc.Docf(` 25 | $ %[1]s project variable list "Deploy Web App" 26 | $ %[1]s project variable view --name "DatabaseName" --project Deploy 27 | $ %[1]s project variable update 28 | `, constants.ExecutableName), 29 | Annotations: map[string]string{ 30 | annotations.IsCore: "true", 31 | }, 32 | } 33 | 34 | cmd.AddCommand(cmdUpdate.NewUpdateCmd(f)) 35 | cmd.AddCommand(cmdCreate.NewCreateCmd(f)) 36 | cmd.AddCommand(cmdList.NewCmdList(f)) 37 | cmd.AddCommand(cmdView.NewCmdView(f)) 38 | cmd.AddCommand(cmdDelete.NewDeleteCmd(f)) 39 | cmd.AddCommand(cmdInclude.NewIncludeVariableSetCmd(f)) 40 | cmd.AddCommand(cmdExclude.NewExcludeVariableSetCmd(f)) 41 | 42 | return cmd 43 | } 44 | -------------------------------------------------------------------------------- /pkg/cmd/projectgroup/list/list.go: -------------------------------------------------------------------------------- 1 | package list 2 | 3 | import ( 4 | "github.com/MakeNowJust/heredoc/v2" 5 | "github.com/OctopusDeploy/cli/pkg/apiclient" 6 | "github.com/OctopusDeploy/cli/pkg/constants" 7 | "github.com/OctopusDeploy/cli/pkg/factory" 8 | "github.com/OctopusDeploy/cli/pkg/output" 9 | "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/projectgroups" 10 | "github.com/spf13/cobra" 11 | ) 12 | 13 | type ProjectGroupAsJson struct { 14 | Id string `json:"Id"` 15 | Name string `json:"Name"` 16 | Description string `json:"Description"` 17 | } 18 | 19 | func NewCmdList(f factory.Factory) *cobra.Command { 20 | cmd := &cobra.Command{ 21 | Use: "list", 22 | Short: "List project groups", 23 | Long: "List project groups in Octopus Deploy", 24 | Example: heredoc.Docf(` 25 | $ %[1]s project-group list 26 | $ %[1]s project-group ls 27 | `, constants.ExecutableName), 28 | Aliases: []string{"ls"}, 29 | RunE: func(cmd *cobra.Command, args []string) error { 30 | return listRun(cmd, f) 31 | }, 32 | } 33 | 34 | return cmd 35 | } 36 | 37 | func listRun(cmd *cobra.Command, f factory.Factory) error { 38 | client, err := f.GetSpacedClient(apiclient.NewRequester(cmd)) 39 | if err != nil { 40 | return err 41 | } 42 | 43 | allProjects, err := client.ProjectGroups.GetAll() 44 | if err != nil { 45 | return err 46 | } 47 | 48 | return output.PrintArray(allProjects, cmd, output.Mappers[*projectgroups.ProjectGroup]{ 49 | Json: func(p *projectgroups.ProjectGroup) any { 50 | return ProjectGroupAsJson{ 51 | Id: p.GetID(), 52 | Name: p.Name, 53 | Description: p.Description, 54 | } 55 | }, 56 | Table: output.TableDefinition[*projectgroups.ProjectGroup]{ 57 | Header: []string{"NAME", "DESCRIPTION"}, 58 | Row: func(p *projectgroups.ProjectGroup) []string { 59 | return []string{output.Bold(p.Name), p.Description} 60 | }, 61 | }, 62 | Basic: func(p *projectgroups.ProjectGroup) string { 63 | return p.Name 64 | }, 65 | }) 66 | } 67 | -------------------------------------------------------------------------------- /pkg/cmd/projectgroup/project-group.go: -------------------------------------------------------------------------------- 1 | package projectgroup 2 | 3 | import ( 4 | "github.com/MakeNowJust/heredoc/v2" 5 | createCmd "github.com/OctopusDeploy/cli/pkg/cmd/projectgroup/create" 6 | deleteCmd "github.com/OctopusDeploy/cli/pkg/cmd/projectgroup/delete" 7 | listCmd "github.com/OctopusDeploy/cli/pkg/cmd/projectgroup/list" 8 | viewCmd "github.com/OctopusDeploy/cli/pkg/cmd/projectgroup/view" 9 | "github.com/OctopusDeploy/cli/pkg/constants" 10 | "github.com/OctopusDeploy/cli/pkg/constants/annotations" 11 | "github.com/OctopusDeploy/cli/pkg/factory" 12 | "github.com/spf13/cobra" 13 | ) 14 | 15 | func NewCmdProjectGroup(f factory.Factory) *cobra.Command { 16 | cmd := &cobra.Command{ 17 | Use: "project-group ", 18 | Short: "Manage project groups", 19 | Long: "Manage project groups in Octopus Deploy", 20 | Example: heredoc.Docf(` 21 | $ %[1]s project-group list 22 | $ %[1]s project-group ls 23 | `, constants.ExecutableName), 24 | Annotations: map[string]string{ 25 | annotations.IsCore: "true", 26 | }, 27 | } 28 | 29 | cmd.AddCommand(createCmd.NewCmdCreate(f)) 30 | cmd.AddCommand(listCmd.NewCmdList(f)) 31 | cmd.AddCommand(deleteCmd.NewCmdList(f)) 32 | cmd.AddCommand(viewCmd.NewCmdView(f)) 33 | 34 | return cmd 35 | } 36 | -------------------------------------------------------------------------------- /pkg/cmd/release/progression/progression.go: -------------------------------------------------------------------------------- 1 | package progression 2 | 3 | import ( 4 | "github.com/MakeNowJust/heredoc/v2" 5 | cmdAllow "github.com/OctopusDeploy/cli/pkg/cmd/release/progression/allow" 6 | cmdPrevent "github.com/OctopusDeploy/cli/pkg/cmd/release/progression/prevent" 7 | "github.com/OctopusDeploy/cli/pkg/constants" 8 | "github.com/OctopusDeploy/cli/pkg/factory" 9 | "github.com/spf13/cobra" 10 | ) 11 | 12 | func NewCmdProgression(f factory.Factory) *cobra.Command { 13 | cmd := &cobra.Command{ 14 | Use: "progression ", 15 | Short: "Manage progression of a release", 16 | Long: "Manage progression of a release in Octopus Deploy", 17 | Example: heredoc.Docf(` 18 | $ %[1]s release progression prevent 19 | $ %[1]s release progression allow 20 | `, constants.ExecutableName), 21 | } 22 | 23 | cmd.AddCommand(cmdAllow.NewCmdAllow(f)) 24 | cmd.AddCommand(cmdPrevent.NewCmdPrevent(f)) 25 | 26 | return cmd 27 | } 28 | -------------------------------------------------------------------------------- /pkg/cmd/release/progression/shared/shared.go: -------------------------------------------------------------------------------- 1 | package shared 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/OctopusDeploy/cli/pkg/question" 7 | "github.com/OctopusDeploy/cli/pkg/question/selectors" 8 | "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/client" 9 | "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/projects" 10 | "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/releases" 11 | ) 12 | 13 | func GetReleaseID(octopus *client.Client, spaceID string, projectIdentifier string, version string) (string, error) { 14 | selectedProject, err := selectors.FindProject(octopus, projectIdentifier) 15 | if err != nil { 16 | return "", err 17 | } 18 | 19 | selectedRelease, err := FindRelease(octopus, selectedProject, version) 20 | if err != nil { 21 | return "", err 22 | } 23 | return selectedRelease.GetID(), nil 24 | } 25 | 26 | func SelectRelease(octopus *client.Client, project *projects.Project, ask question.Asker, action string) (*releases.Release, error) { 27 | existingReleases, err := octopus.Projects.GetReleases(project) 28 | if err != nil { 29 | return nil, err 30 | } 31 | 32 | selectedRelease, err := question.SelectMap(ask, fmt.Sprintf("Select Release to %s Progression for", action), existingReleases, func(r *releases.Release) string { 33 | return r.Version 34 | }) 35 | if err != nil { 36 | return nil, err 37 | } 38 | 39 | return selectedRelease, nil 40 | } 41 | 42 | func FindRelease(octopus *client.Client, project *projects.Project, version string) (*releases.Release, error) { 43 | existingRelease, err := releases.GetReleaseInProject(octopus, octopus.GetSpaceID(), project.GetID(), version) 44 | if err != nil { 45 | return nil, err 46 | } 47 | 48 | if existingRelease == nil { 49 | return nil, fmt.Errorf("unable to locate a release with version/release number '%s'", version) 50 | } 51 | 52 | return existingRelease, nil 53 | } 54 | -------------------------------------------------------------------------------- /pkg/cmd/release/release.go: -------------------------------------------------------------------------------- 1 | package release 2 | 3 | import ( 4 | "github.com/MakeNowJust/heredoc/v2" 5 | cmdCreate "github.com/OctopusDeploy/cli/pkg/cmd/release/create" 6 | cmdDelete "github.com/OctopusDeploy/cli/pkg/cmd/release/delete" 7 | cmdDeploy "github.com/OctopusDeploy/cli/pkg/cmd/release/deploy" 8 | cmdList "github.com/OctopusDeploy/cli/pkg/cmd/release/list" 9 | cmdProgression "github.com/OctopusDeploy/cli/pkg/cmd/release/progression" 10 | "github.com/OctopusDeploy/cli/pkg/constants" 11 | "github.com/OctopusDeploy/cli/pkg/constants/annotations" 12 | "github.com/OctopusDeploy/cli/pkg/factory" 13 | "github.com/spf13/cobra" 14 | ) 15 | 16 | func NewCmdRelease(f factory.Factory) *cobra.Command { 17 | cmd := &cobra.Command{ 18 | Use: "release ", 19 | Short: "Manage releases", 20 | Long: "Manage releases in Octopus Deploy", 21 | Example: heredoc.Docf("$ %s release list", constants.ExecutableName), 22 | Annotations: map[string]string{ 23 | annotations.IsCore: "true", 24 | }, 25 | } 26 | 27 | cmd.AddCommand(cmdCreate.NewCmdCreate(f)) 28 | cmd.AddCommand(cmdDeploy.NewCmdDeploy(f)) 29 | cmd.AddCommand(cmdList.NewCmdList(f)) 30 | cmd.AddCommand(cmdDelete.NewCmdDelete(f)) 31 | cmd.AddCommand(cmdProgression.NewCmdProgression(f)) 32 | 33 | return cmd 34 | } 35 | -------------------------------------------------------------------------------- /pkg/cmd/runbook/runbook.go: -------------------------------------------------------------------------------- 1 | package runbook 2 | 3 | import ( 4 | "github.com/MakeNowJust/heredoc/v2" 5 | cmdDelete "github.com/OctopusDeploy/cli/pkg/cmd/runbook/delete" 6 | cmdList "github.com/OctopusDeploy/cli/pkg/cmd/runbook/list" 7 | cmdRun "github.com/OctopusDeploy/cli/pkg/cmd/runbook/run" 8 | cmdSnapshot "github.com/OctopusDeploy/cli/pkg/cmd/runbook/snapshot" 9 | "github.com/OctopusDeploy/cli/pkg/constants" 10 | "github.com/OctopusDeploy/cli/pkg/constants/annotations" 11 | "github.com/OctopusDeploy/cli/pkg/factory" 12 | "github.com/spf13/cobra" 13 | ) 14 | 15 | func NewCmdRunbook(f factory.Factory) *cobra.Command { 16 | cmd := &cobra.Command{ 17 | Use: "runbook ", 18 | Short: "Manage runbooks", 19 | Long: "Manage runbooks in Octopus Deploy", 20 | Example: heredoc.Docf(` 21 | $ %[1]s runbook list 22 | $ %[1]s runbook run 23 | `, constants.ExecutableName), 24 | Annotations: map[string]string{ 25 | annotations.IsCore: "true", 26 | }, 27 | } 28 | 29 | cmd.AddCommand(cmdList.NewCmdList(f)) 30 | cmd.AddCommand(cmdRun.NewCmdRun(f)) 31 | cmd.AddCommand(cmdDelete.NewCmdDelete(f)) 32 | cmd.AddCommand(cmdSnapshot.NewCmdSnapshot(f)) 33 | return cmd 34 | } 35 | -------------------------------------------------------------------------------- /pkg/cmd/runbook/snapshot/snapshot.go: -------------------------------------------------------------------------------- 1 | package snapshot 2 | 3 | import ( 4 | "github.com/MakeNowJust/heredoc/v2" 5 | cmdCreate "github.com/OctopusDeploy/cli/pkg/cmd/runbook/snapshot/create" 6 | cmdList "github.com/OctopusDeploy/cli/pkg/cmd/runbook/snapshot/list" 7 | cmdPublish "github.com/OctopusDeploy/cli/pkg/cmd/runbook/snapshot/publish" 8 | "github.com/OctopusDeploy/cli/pkg/constants" 9 | "github.com/OctopusDeploy/cli/pkg/factory" 10 | "github.com/spf13/cobra" 11 | ) 12 | 13 | func NewCmdSnapshot(f factory.Factory) *cobra.Command { 14 | cmd := &cobra.Command{ 15 | Use: "snapshot ", 16 | Short: "Manage runbook snapshots", 17 | Long: "Manage runbook snapshots in Octopus Deploy", 18 | Example: heredoc.Docf(` 19 | $ %[1]s runbook snapshot create 20 | $ %[1]s runbook snapshot list 21 | `, constants.ExecutableName), 22 | } 23 | 24 | cmd.AddCommand(cmdList.NewCmdList(f)) 25 | cmd.AddCommand(cmdCreate.NewCmdCreate(f)) 26 | cmd.AddCommand(cmdPublish.NewCmdPublish(f)) 27 | return cmd 28 | } 29 | -------------------------------------------------------------------------------- /pkg/cmd/space/list/list.go: -------------------------------------------------------------------------------- 1 | package list 2 | 3 | import ( 4 | "github.com/MakeNowJust/heredoc/v2" 5 | "github.com/OctopusDeploy/cli/pkg/apiclient" 6 | "github.com/OctopusDeploy/cli/pkg/constants" 7 | "github.com/OctopusDeploy/cli/pkg/factory" 8 | "github.com/OctopusDeploy/cli/pkg/output" 9 | "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/spaces" 10 | "github.com/spf13/cobra" 11 | ) 12 | 13 | func NewCmdList(f factory.Factory) *cobra.Command { 14 | cmd := &cobra.Command{ 15 | Use: "list", 16 | Short: "List spaces", 17 | Long: "List spaces in Octopus Deploy", 18 | Example: heredoc.Docf("$ %s space list", constants.ExecutableName), 19 | Aliases: []string{"ls"}, 20 | RunE: func(cmd *cobra.Command, args []string) error { 21 | return listRun(f, cmd) 22 | }, 23 | } 24 | 25 | return cmd 26 | } 27 | 28 | func listRun(f factory.Factory, cmd *cobra.Command) error { 29 | client, err := f.GetSystemClient(apiclient.NewRequester(cmd)) 30 | if err != nil { 31 | return err 32 | } 33 | 34 | allSpaces, err := client.Spaces.GetAll() 35 | if err != nil { 36 | return err 37 | } 38 | 39 | type SpaceAsJson struct { 40 | Id string `json:"Id"` 41 | Name string `json:"Name"` 42 | Description string `json:"Description"` 43 | TaskQueue string `json:"TaskQueue"` 44 | } 45 | 46 | return output.PrintArray(allSpaces, cmd, output.Mappers[*spaces.Space]{ 47 | Json: func(item *spaces.Space) any { 48 | taskQueue := "Running" 49 | if item.TaskQueueStopped { 50 | taskQueue = "Stopped" 51 | } 52 | return SpaceAsJson{ 53 | Id: item.GetID(), 54 | Name: item.Name, 55 | Description: item.Description, 56 | TaskQueue: taskQueue, 57 | } 58 | }, 59 | Table: output.TableDefinition[*spaces.Space]{ 60 | Header: []string{"NAME", "DESCRIPTION", "TASK QUEUE"}, 61 | Row: func(item *spaces.Space) []string { 62 | taskQueue := output.Green("Running") 63 | if item.TaskQueueStopped { 64 | taskQueue = output.Yellow("Stopped") 65 | } 66 | 67 | return []string{output.Bold(item.Name), item.Description, taskQueue} 68 | }, 69 | }, 70 | Basic: func(item *spaces.Space) string { 71 | return item.Name 72 | }, 73 | }) 74 | } 75 | -------------------------------------------------------------------------------- /pkg/cmd/space/space.go: -------------------------------------------------------------------------------- 1 | package space 2 | 3 | import ( 4 | "github.com/MakeNowJust/heredoc/v2" 5 | cmdCreate "github.com/OctopusDeploy/cli/pkg/cmd/space/create" 6 | cmdDelete "github.com/OctopusDeploy/cli/pkg/cmd/space/delete" 7 | cmdList "github.com/OctopusDeploy/cli/pkg/cmd/space/list" 8 | cmdView "github.com/OctopusDeploy/cli/pkg/cmd/space/view" 9 | "github.com/OctopusDeploy/cli/pkg/constants" 10 | "github.com/OctopusDeploy/cli/pkg/constants/annotations" 11 | "github.com/OctopusDeploy/cli/pkg/factory" 12 | "github.com/spf13/cobra" 13 | ) 14 | 15 | func NewCmdSpace(f factory.Factory) *cobra.Command { 16 | cmd := &cobra.Command{ 17 | Use: "space ", 18 | Short: "Manage spaces", 19 | Long: "Manage spaces in Octopus Deploy", 20 | Example: heredoc.Docf(` 21 | $ %[1]s space list 22 | $ %[1]s space view Spaces-302 23 | `, constants.ExecutableName), 24 | Annotations: map[string]string{ 25 | annotations.IsConfiguration: "true", 26 | }, 27 | } 28 | 29 | cmd.AddCommand(cmdCreate.NewCmdCreate(f)) 30 | cmd.AddCommand(cmdList.NewCmdList(f)) 31 | cmd.AddCommand(cmdView.NewCmdView(f)) 32 | cmd.AddCommand(cmdDelete.NewCmdDelete(f)) 33 | 34 | return cmd 35 | } 36 | -------------------------------------------------------------------------------- /pkg/cmd/target/azure-web-app/azure-web-app.go: -------------------------------------------------------------------------------- 1 | package azure_web_app 2 | 3 | import ( 4 | "fmt" 5 | 6 | cmdCreate "github.com/OctopusDeploy/cli/pkg/cmd/target/azure-web-app/create" 7 | cmdList "github.com/OctopusDeploy/cli/pkg/cmd/target/azure-web-app/list" 8 | cmdView "github.com/OctopusDeploy/cli/pkg/cmd/target/azure-web-app/view" 9 | "github.com/OctopusDeploy/cli/pkg/constants" 10 | "github.com/OctopusDeploy/cli/pkg/factory" 11 | "github.com/spf13/cobra" 12 | ) 13 | 14 | func NewCmdAzureWebApp(f factory.Factory) *cobra.Command { 15 | cmd := &cobra.Command{ 16 | Use: "azure-web-app ", 17 | Short: "Manage Azure Web App deployment targets", 18 | Long: "Manage Azure Web App deployment targets in Octopus Deploy", 19 | Example: fmt.Sprintf("$ %s deployment-target azure-web-app list", constants.ExecutableName), 20 | } 21 | 22 | cmd.AddCommand(cmdList.NewCmdList(f)) 23 | cmd.AddCommand(cmdCreate.NewCmdCreate(f)) 24 | cmd.AddCommand(cmdView.NewCmdView(f)) 25 | 26 | return cmd 27 | } 28 | -------------------------------------------------------------------------------- /pkg/cmd/target/azure-web-app/list/list.go: -------------------------------------------------------------------------------- 1 | package list 2 | 3 | import ( 4 | "github.com/MakeNowJust/heredoc/v2" 5 | "github.com/OctopusDeploy/cli/pkg/cmd" 6 | "github.com/OctopusDeploy/cli/pkg/cmd/target/list" 7 | "github.com/OctopusDeploy/cli/pkg/constants" 8 | "github.com/OctopusDeploy/cli/pkg/factory" 9 | "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/machines" 10 | "github.com/spf13/cobra" 11 | ) 12 | 13 | func NewCmdList(f factory.Factory) *cobra.Command { 14 | cmd := &cobra.Command{ 15 | Use: "list", 16 | Short: "List Azure Web App deployment targets", 17 | Long: "List Azure Web App deployment targets in Octopus Deploy", 18 | Example: heredoc.Docf(` 19 | $ %[1]s deployment-target azure-web-app list 20 | $ %[1]s deployment-target azure-web-app ls 21 | `, constants.ExecutableName), 22 | Aliases: []string{"ls"}, 23 | RunE: func(c *cobra.Command, args []string) error { 24 | dependencies := cmd.NewDependencies(f, c) 25 | options := list.NewListOptions(dependencies, c, machines.MachinesQuery{DeploymentTargetTypes: []string{"AzureWebApp"}}) 26 | return list.ListRun(options) 27 | }, 28 | } 29 | 30 | return cmd 31 | } 32 | -------------------------------------------------------------------------------- /pkg/cmd/target/azure-web-app/view/view.go: -------------------------------------------------------------------------------- 1 | package view 2 | 3 | import ( 4 | "fmt" 5 | "github.com/OctopusDeploy/cli/pkg/output" 6 | "strings" 7 | 8 | "github.com/MakeNowJust/heredoc/v2" 9 | "github.com/OctopusDeploy/cli/pkg/cmd" 10 | "github.com/OctopusDeploy/cli/pkg/cmd/target/shared" 11 | "github.com/OctopusDeploy/cli/pkg/constants" 12 | "github.com/OctopusDeploy/cli/pkg/factory" 13 | "github.com/OctopusDeploy/cli/pkg/machinescommon" 14 | "github.com/OctopusDeploy/cli/pkg/usage" 15 | "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/machines" 16 | "github.com/spf13/cobra" 17 | ) 18 | 19 | func NewCmdView(f factory.Factory) *cobra.Command { 20 | flags := shared.NewViewFlags() 21 | cmd := &cobra.Command{ 22 | Args: usage.ExactArgs(1), 23 | Use: "view { | }", 24 | Short: "View an Azure Web App deployment target", 25 | Long: "View an Azure Web App deployment target in Octopus Deploy", 26 | Example: heredoc.Docf(` 27 | $ %[1]s deployment-target azure-web-app view 'Shop Api' 28 | $ %[1]s deployment-target azure-web-app view Machines-100 29 | `, constants.ExecutableName), 30 | RunE: func(c *cobra.Command, args []string) error { 31 | opts := shared.NewViewOptions(flags, cmd.NewDependencies(f, c), args) 32 | return ViewRun(opts) 33 | }, 34 | } 35 | 36 | machinescommon.RegisterWebFlag(cmd, flags.WebFlags) 37 | 38 | return cmd 39 | } 40 | 41 | func ViewRun(opts *shared.ViewOptions) error { 42 | return shared.ViewRun(opts, contributeEndpoint, "Azure Web App") 43 | } 44 | 45 | func contributeEndpoint(opts *shared.ViewOptions, targetEndpoint machines.IEndpoint) ([]*output.DataRow, error) { 46 | data := []*output.DataRow{} 47 | endpoint := targetEndpoint.(*machines.AzureWebAppEndpoint) 48 | accountRows, err := shared.ContributeAccount(opts, endpoint.AccountID) 49 | if err != nil { 50 | return nil, err 51 | } 52 | 53 | data = append(data, accountRows...) 54 | data = append(data, output.NewDataRow("Web App", getWebAppDisplay(endpoint))) 55 | return data, nil 56 | } 57 | 58 | func getWebAppDisplay(endpoint *machines.AzureWebAppEndpoint) string { 59 | builder := &strings.Builder{} 60 | builder.WriteString(endpoint.WebAppName) 61 | if endpoint.WebAppSlotName != "" { 62 | builder.WriteString(fmt.Sprintf("/%s", endpoint.WebAppSlotName)) 63 | } 64 | 65 | return builder.String() 66 | } 67 | -------------------------------------------------------------------------------- /pkg/cmd/target/cloud-region/cloud-region.go: -------------------------------------------------------------------------------- 1 | package cloud_region 2 | 3 | import ( 4 | "github.com/MakeNowJust/heredoc/v2" 5 | cmdCreate "github.com/OctopusDeploy/cli/pkg/cmd/target/cloud-region/create" 6 | cmdList "github.com/OctopusDeploy/cli/pkg/cmd/target/cloud-region/list" 7 | cmdView "github.com/OctopusDeploy/cli/pkg/cmd/target/cloud-region/view" 8 | "github.com/OctopusDeploy/cli/pkg/constants" 9 | "github.com/OctopusDeploy/cli/pkg/factory" 10 | "github.com/spf13/cobra" 11 | ) 12 | 13 | func NewCmdCloudRegion(f factory.Factory) *cobra.Command { 14 | cmd := &cobra.Command{ 15 | Use: "cloud-region ", 16 | Short: "Manage Cloud Region deployment targets", 17 | Long: "Manage Cloud Region deployment targets in Octopus Deploy", 18 | Example: heredoc.Docf("$ %s deployment-target cloud-region list", constants.ExecutableName), 19 | } 20 | 21 | cmd.AddCommand(cmdCreate.NewCmdCreate(f)) 22 | cmd.AddCommand(cmdList.NewCmdList(f)) 23 | cmd.AddCommand(cmdView.NewCmdView(f)) 24 | return cmd 25 | } 26 | -------------------------------------------------------------------------------- /pkg/cmd/target/cloud-region/list/list.go: -------------------------------------------------------------------------------- 1 | package list 2 | 3 | import ( 4 | "github.com/MakeNowJust/heredoc/v2" 5 | "github.com/OctopusDeploy/cli/pkg/cmd" 6 | "github.com/OctopusDeploy/cli/pkg/cmd/target/list" 7 | "github.com/OctopusDeploy/cli/pkg/constants" 8 | "github.com/OctopusDeploy/cli/pkg/factory" 9 | "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/machines" 10 | "github.com/spf13/cobra" 11 | ) 12 | 13 | func NewCmdList(f factory.Factory) *cobra.Command { 14 | cmd := &cobra.Command{ 15 | Use: "list", 16 | Short: "List Cloud Region deployment targets", 17 | Long: "List Cloud Region deployment targets in Octopus Deploy", 18 | Example: heredoc.Docf(` 19 | $ %[1]s deployment-target cloud-region list 20 | $ %[1]s deployment-target cloud-region ls 21 | `, constants.ExecutableName), 22 | Aliases: []string{"ls"}, 23 | RunE: func(c *cobra.Command, args []string) error { 24 | dependencies := cmd.NewDependencies(f, c) 25 | options := list.NewListOptions(dependencies, c, machines.MachinesQuery{DeploymentTargetTypes: []string{"CloudRegion"}}) 26 | return list.ListRun(options) 27 | }, 28 | } 29 | 30 | return cmd 31 | } 32 | -------------------------------------------------------------------------------- /pkg/cmd/target/cloud-region/view/view.go: -------------------------------------------------------------------------------- 1 | package view 2 | 3 | import ( 4 | "github.com/MakeNowJust/heredoc/v2" 5 | "github.com/OctopusDeploy/cli/pkg/cmd" 6 | "github.com/OctopusDeploy/cli/pkg/cmd/target/shared" 7 | "github.com/OctopusDeploy/cli/pkg/constants" 8 | "github.com/OctopusDeploy/cli/pkg/factory" 9 | "github.com/OctopusDeploy/cli/pkg/machinescommon" 10 | "github.com/OctopusDeploy/cli/pkg/usage" 11 | "github.com/spf13/cobra" 12 | ) 13 | 14 | func NewCmdView(f factory.Factory) *cobra.Command { 15 | flags := shared.NewViewFlags() 16 | cmd := &cobra.Command{ 17 | Args: usage.ExactArgs(1), 18 | Use: "view { | }", 19 | Short: "View a Cloud Region deployment target", 20 | Long: "View a Cloud Region deployment target in Octopus Deploy", 21 | Example: heredoc.Docf(` 22 | $ %[1]s deployment-target cloud-region view 'EU' 23 | $ %[1]s deployment-target cloud-region view Machines-100 24 | `, constants.ExecutableName), 25 | RunE: func(c *cobra.Command, args []string) error { 26 | opts := shared.NewViewOptions(flags, cmd.NewDependencies(f, c), args) 27 | return ViewRun(opts) 28 | }, 29 | } 30 | 31 | machinescommon.RegisterWebFlag(cmd, flags.WebFlags) 32 | 33 | return cmd 34 | } 35 | 36 | func ViewRun(opts *shared.ViewOptions) error { 37 | return shared.ViewRun(opts, nil, "Cloud Region") 38 | } 39 | -------------------------------------------------------------------------------- /pkg/cmd/target/kubernetes/kubernetes.go: -------------------------------------------------------------------------------- 1 | package kubernetes 2 | 3 | import ( 4 | "github.com/MakeNowJust/heredoc/v2" 5 | cmdCreate "github.com/OctopusDeploy/cli/pkg/cmd/target/kubernetes/create" 6 | cmdList "github.com/OctopusDeploy/cli/pkg/cmd/target/kubernetes/list" 7 | cmdView "github.com/OctopusDeploy/cli/pkg/cmd/target/kubernetes/view" 8 | "github.com/OctopusDeploy/cli/pkg/constants" 9 | "github.com/OctopusDeploy/cli/pkg/factory" 10 | "github.com/spf13/cobra" 11 | ) 12 | 13 | func NewCmdKubernetes(f factory.Factory) *cobra.Command { 14 | cmd := &cobra.Command{ 15 | Use: "kubernetes ", 16 | Short: "Manage Kubernetes deployment targets", 17 | Long: "Manage Kubernetes deployment targets in Octopus Deploy", 18 | Example: heredoc.Docf("$ %s deployment-target kubernetes create", constants.ExecutableName), 19 | Aliases: []string{"k8s"}, 20 | } 21 | 22 | cmd.AddCommand(cmdCreate.NewCmdCreate(f)) 23 | cmd.AddCommand(cmdList.NewCmdList(f)) 24 | cmd.AddCommand(cmdView.NewCmdView(f)) 25 | 26 | return cmd 27 | } 28 | -------------------------------------------------------------------------------- /pkg/cmd/target/kubernetes/list/list.go: -------------------------------------------------------------------------------- 1 | package list 2 | 3 | import ( 4 | "github.com/MakeNowJust/heredoc/v2" 5 | "github.com/OctopusDeploy/cli/pkg/cmd" 6 | "github.com/OctopusDeploy/cli/pkg/cmd/target/list" 7 | "github.com/OctopusDeploy/cli/pkg/constants" 8 | "github.com/OctopusDeploy/cli/pkg/factory" 9 | "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/machines" 10 | "github.com/spf13/cobra" 11 | ) 12 | 13 | func NewCmdList(f factory.Factory) *cobra.Command { 14 | cmd := &cobra.Command{ 15 | Use: "list", 16 | Short: "List Kubernetes deployment targets", 17 | Long: "List Kubernetes deployment targets in Octopus Deploy", 18 | Example: heredoc.Docf(` 19 | $ %[1]s deployment-target kubernetes list 20 | $ %[1]s deployment-target kubernetes ls 21 | `, constants.ExecutableName), 22 | Aliases: []string{"ls"}, 23 | RunE: func(c *cobra.Command, _ []string) error { 24 | dependencies := cmd.NewDependencies(f, c) 25 | options := list.NewListOptions(dependencies, c, machines.MachinesQuery{DeploymentTargetTypes: []string{"Kubernetes"}}) 26 | return list.ListRun(options) 27 | }, 28 | } 29 | 30 | return cmd 31 | } 32 | -------------------------------------------------------------------------------- /pkg/cmd/target/kubernetes/view/view.go: -------------------------------------------------------------------------------- 1 | package view 2 | 3 | import ( 4 | "github.com/MakeNowJust/heredoc/v2" 5 | "github.com/OctopusDeploy/cli/pkg/cmd" 6 | "github.com/OctopusDeploy/cli/pkg/cmd/target/shared" 7 | "github.com/OctopusDeploy/cli/pkg/constants" 8 | "github.com/OctopusDeploy/cli/pkg/factory" 9 | "github.com/OctopusDeploy/cli/pkg/machinescommon" 10 | "github.com/OctopusDeploy/cli/pkg/output" 11 | "github.com/OctopusDeploy/cli/pkg/usage" 12 | "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/machines" 13 | "github.com/spf13/cobra" 14 | ) 15 | 16 | func NewCmdView(f factory.Factory) *cobra.Command { 17 | flags := shared.NewViewFlags() 18 | cmd := &cobra.Command{ 19 | Args: usage.ExactArgs(1), 20 | Use: "view { | }", 21 | Short: "View a Kubernetes deployment target", 22 | Long: "View a Kubernetes deployment target in Octopus Deploy", 23 | Example: heredoc.Docf(` 24 | $ %[1]s deployment-target kubernetes view 'target-name' 25 | `, constants.ExecutableName), 26 | RunE: func(c *cobra.Command, args []string) error { 27 | opts := shared.NewViewOptions(flags, cmd.NewDependencies(f, c), args) 28 | return ViewRun(opts) 29 | }, 30 | } 31 | 32 | machinescommon.RegisterWebFlag(cmd, flags.WebFlags) 33 | 34 | return cmd 35 | } 36 | 37 | func ViewRun(opts *shared.ViewOptions) error { 38 | return shared.ViewRun(opts, contributeEndpoint, "Kubernetes") 39 | } 40 | 41 | func contributeEndpoint(_ *shared.ViewOptions, targetEndpoint machines.IEndpoint) ([]*output.DataRow, error) { 42 | data := []*output.DataRow{} 43 | endpoint := targetEndpoint.(*machines.KubernetesEndpoint) 44 | 45 | data = append(data, output.NewDataRow("Authentication Type", endpoint.Authentication.GetAuthenticationType())) 46 | 47 | return data, nil 48 | } 49 | -------------------------------------------------------------------------------- /pkg/cmd/target/listening-tentacle/list/list.go: -------------------------------------------------------------------------------- 1 | package list 2 | 3 | import ( 4 | "github.com/MakeNowJust/heredoc/v2" 5 | "github.com/OctopusDeploy/cli/pkg/cmd" 6 | "github.com/OctopusDeploy/cli/pkg/cmd/target/list" 7 | "github.com/OctopusDeploy/cli/pkg/constants" 8 | "github.com/OctopusDeploy/cli/pkg/factory" 9 | "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/machines" 10 | "github.com/spf13/cobra" 11 | ) 12 | 13 | func NewCmdList(f factory.Factory) *cobra.Command { 14 | cmd := &cobra.Command{ 15 | Use: "list", 16 | Short: "List Listening Tentacle deployment targets", 17 | Long: "List Listening Tentacle deployment targets in Octopus Deploy", 18 | Example: heredoc.Docf(` 19 | $ %[1]s deployment-target listening-tentacle list 20 | $ %[1]s deployment-target listening-tentacle ls 21 | `, constants.ExecutableName), 22 | Aliases: []string{"ls"}, 23 | RunE: func(c *cobra.Command, args []string) error { 24 | dependencies := cmd.NewDependencies(f, c) 25 | options := list.NewListOptions(dependencies, c, machines.MachinesQuery{DeploymentTargetTypes: []string{"TentaclePassive"}}) 26 | return list.ListRun(options) 27 | }, 28 | } 29 | 30 | return cmd 31 | } 32 | -------------------------------------------------------------------------------- /pkg/cmd/target/listening-tentacle/listening-tentacle.go: -------------------------------------------------------------------------------- 1 | package listening_tentacle 2 | 3 | import ( 4 | "github.com/MakeNowJust/heredoc/v2" 5 | cmdCreate "github.com/OctopusDeploy/cli/pkg/cmd/target/listening-tentacle/create" 6 | cmdList "github.com/OctopusDeploy/cli/pkg/cmd/target/listening-tentacle/list" 7 | cmdView "github.com/OctopusDeploy/cli/pkg/cmd/target/listening-tentacle/view" 8 | "github.com/OctopusDeploy/cli/pkg/constants" 9 | "github.com/OctopusDeploy/cli/pkg/factory" 10 | "github.com/spf13/cobra" 11 | ) 12 | 13 | func NewCmdListeningTentacle(f factory.Factory) *cobra.Command { 14 | cmd := &cobra.Command{ 15 | Use: "listening-tentacle ", 16 | Short: "Manage Listening Tentacle deployment targets", 17 | Long: "Manage Listening Tentacle deployment targets in Octopus Deploy", 18 | Example: heredoc.Docf("$ %s deployment-target listening-tentacle list", constants.ExecutableName), 19 | } 20 | 21 | cmd.AddCommand(cmdList.NewCmdList(f)) 22 | cmd.AddCommand(cmdCreate.NewCmdCreate(f)) 23 | cmd.AddCommand(cmdView.NewCmdView(f)) 24 | return cmd 25 | } 26 | -------------------------------------------------------------------------------- /pkg/cmd/target/listening-tentacle/view/view.go: -------------------------------------------------------------------------------- 1 | package view 2 | 3 | import ( 4 | "github.com/MakeNowJust/heredoc/v2" 5 | "github.com/OctopusDeploy/cli/pkg/cmd" 6 | "github.com/OctopusDeploy/cli/pkg/cmd/target/shared" 7 | "github.com/OctopusDeploy/cli/pkg/constants" 8 | "github.com/OctopusDeploy/cli/pkg/factory" 9 | "github.com/OctopusDeploy/cli/pkg/machinescommon" 10 | "github.com/OctopusDeploy/cli/pkg/output" 11 | "github.com/OctopusDeploy/cli/pkg/usage" 12 | "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/machines" 13 | "github.com/spf13/cobra" 14 | ) 15 | 16 | func NewCmdView(f factory.Factory) *cobra.Command { 17 | flags := shared.NewViewFlags() 18 | cmd := &cobra.Command{ 19 | Args: usage.ExactArgs(1), 20 | Use: "view { | }", 21 | Short: "View a Listening Tentacle deployment target", 22 | Long: "View a Listening Tentacle deployment target in Octopus Deploy", 23 | Example: heredoc.Docf(` 24 | $ %[1]s deployment-target listening-tentacle view 'EU' 25 | $ %[1]s deployment-target listening-tentacle view Machines-100 26 | `, constants.ExecutableName), 27 | RunE: func(c *cobra.Command, args []string) error { 28 | opts := shared.NewViewOptions(flags, cmd.NewDependencies(f, c), args) 29 | return ViewRun(opts) 30 | }, 31 | } 32 | 33 | machinescommon.RegisterWebFlag(cmd, flags.WebFlags) 34 | 35 | return cmd 36 | } 37 | 38 | func ViewRun(opts *shared.ViewOptions) error { 39 | return shared.ViewRun(opts, contributeEndpoint, "Listening Tentacle") 40 | } 41 | 42 | func contributeEndpoint(opts *shared.ViewOptions, targetEndpoint machines.IEndpoint) ([]*output.DataRow, error) { 43 | data := []*output.DataRow{} 44 | 45 | endpoint := targetEndpoint.(*machines.ListeningTentacleEndpoint) 46 | data = append(data, output.NewDataRow("URI", endpoint.URI.String())) 47 | data = append(data, output.NewDataRow("Tentacle version", endpoint.TentacleVersionDetails.Version)) 48 | 49 | proxyData, err := shared.ContributeProxy(opts, endpoint.ProxyID) 50 | if err != nil { 51 | return nil, err 52 | } 53 | data = append(data, proxyData...) 54 | 55 | return data, nil 56 | } 57 | -------------------------------------------------------------------------------- /pkg/cmd/target/polling-tentacle/list/list.go: -------------------------------------------------------------------------------- 1 | package list 2 | 3 | import ( 4 | "github.com/MakeNowJust/heredoc/v2" 5 | "github.com/OctopusDeploy/cli/pkg/cmd" 6 | "github.com/OctopusDeploy/cli/pkg/cmd/target/list" 7 | "github.com/OctopusDeploy/cli/pkg/constants" 8 | "github.com/OctopusDeploy/cli/pkg/factory" 9 | "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/machines" 10 | "github.com/spf13/cobra" 11 | ) 12 | 13 | func NewCmdList(f factory.Factory) *cobra.Command { 14 | cmd := &cobra.Command{ 15 | Use: "list", 16 | Short: "List Polling Tentacle deployment targets", 17 | Long: "List Polling Tentacle deployment targets in Octopus Deploy", 18 | Example: heredoc.Docf(` 19 | $ %[1]s deployment-target polling-tentacle list 20 | $ %[1]s deployment-target polling-tentacle ls 21 | `, constants.ExecutableName), 22 | Aliases: []string{"ls"}, 23 | RunE: func(c *cobra.Command, args []string) error { 24 | dependencies := cmd.NewDependencies(f, c) 25 | options := list.NewListOptions(dependencies, c, machines.MachinesQuery{DeploymentTargetTypes: []string{"TentacleActive"}}) 26 | return list.ListRun(options) 27 | }, 28 | } 29 | 30 | return cmd 31 | } 32 | -------------------------------------------------------------------------------- /pkg/cmd/target/polling-tentacle/polling-tentacle.go: -------------------------------------------------------------------------------- 1 | package polling_tentacle 2 | 3 | import ( 4 | "github.com/MakeNowJust/heredoc/v2" 5 | cmdList "github.com/OctopusDeploy/cli/pkg/cmd/target/polling-tentacle/list" 6 | cmdView "github.com/OctopusDeploy/cli/pkg/cmd/target/polling-tentacle/view" 7 | "github.com/OctopusDeploy/cli/pkg/constants" 8 | "github.com/OctopusDeploy/cli/pkg/factory" 9 | "github.com/spf13/cobra" 10 | ) 11 | 12 | func NewCmdPollingTentacle(f factory.Factory) *cobra.Command { 13 | cmd := &cobra.Command{ 14 | Use: "polling-tentacle ", 15 | Short: "Manage Polling Tentacle deployment targets", 16 | Long: "Manage Polling Tentacle deployment targets in Octopus Deploy", 17 | Example: heredoc.Docf("$ %s deployment-target polling-tentacle list", constants.ExecutableName), 18 | } 19 | 20 | cmd.AddCommand(cmdList.NewCmdList(f)) 21 | cmd.AddCommand(cmdView.NewCmdView(f)) 22 | return cmd 23 | } 24 | -------------------------------------------------------------------------------- /pkg/cmd/target/polling-tentacle/view/view.go: -------------------------------------------------------------------------------- 1 | package view 2 | 3 | import ( 4 | "github.com/MakeNowJust/heredoc/v2" 5 | "github.com/OctopusDeploy/cli/pkg/cmd" 6 | "github.com/OctopusDeploy/cli/pkg/cmd/target/shared" 7 | "github.com/OctopusDeploy/cli/pkg/constants" 8 | "github.com/OctopusDeploy/cli/pkg/factory" 9 | "github.com/OctopusDeploy/cli/pkg/machinescommon" 10 | "github.com/OctopusDeploy/cli/pkg/output" 11 | "github.com/OctopusDeploy/cli/pkg/usage" 12 | "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/machines" 13 | "github.com/spf13/cobra" 14 | ) 15 | 16 | func NewCmdView(f factory.Factory) *cobra.Command { 17 | flags := shared.NewViewFlags() 18 | cmd := &cobra.Command{ 19 | Args: usage.ExactArgs(1), 20 | Use: "view { | }", 21 | Short: "View a Polling Tentacle deployment target", 22 | Long: "View a Polling Tentacle deployment target in Octopus Deploy", 23 | Example: heredoc.Docf(` 24 | $ %[1]s deployment-target polling-tentacle view 'EU' 25 | $ %[1]s deployment-target polling-tentacle view Machines-100 26 | `, constants.ExecutableName), 27 | RunE: func(c *cobra.Command, args []string) error { 28 | opts := shared.NewViewOptions(flags, cmd.NewDependencies(f, c), args) 29 | return ViewRun(opts) 30 | }, 31 | } 32 | 33 | machinescommon.RegisterWebFlag(cmd, flags.WebFlags) 34 | 35 | return cmd 36 | } 37 | 38 | func ViewRun(opts *shared.ViewOptions) error { 39 | return shared.ViewRun(opts, contributeEndpoint, "Polling Tentacle") 40 | } 41 | 42 | func contributeEndpoint(opts *shared.ViewOptions, targetEndpoint machines.IEndpoint) ([]*output.DataRow, error) { 43 | data := []*output.DataRow{} 44 | 45 | endpoint := targetEndpoint.(*machines.PollingTentacleEndpoint) 46 | data = append(data, output.NewDataRow("URI", endpoint.URI.String())) 47 | data = append(data, output.NewDataRow("Tentacle version", endpoint.TentacleVersionDetails.Version)) 48 | 49 | return data, nil 50 | } 51 | -------------------------------------------------------------------------------- /pkg/cmd/target/shared/environment.go: -------------------------------------------------------------------------------- 1 | package shared 2 | 3 | import ( 4 | "github.com/OctopusDeploy/cli/pkg/cmd" 5 | "github.com/OctopusDeploy/cli/pkg/question/selectors" 6 | "github.com/OctopusDeploy/cli/pkg/util" 7 | "github.com/OctopusDeploy/cli/pkg/util/flag" 8 | "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/environments" 9 | "github.com/spf13/cobra" 10 | ) 11 | 12 | const ( 13 | FlagEnvironment = "environment" 14 | ) 15 | 16 | type CreateTargetEnvironmentFlags struct { 17 | Environments *flag.Flag[[]string] 18 | } 19 | 20 | type CreateTargetEnvironmentOptions struct { 21 | *cmd.Dependencies 22 | selectors.GetAllEnvironmentsCallback 23 | } 24 | 25 | func NewCreateTargetEnvironmentOptions(dependencies *cmd.Dependencies) *CreateTargetEnvironmentOptions { 26 | return &CreateTargetEnvironmentOptions{ 27 | Dependencies: dependencies, 28 | GetAllEnvironmentsCallback: func() ([]*environments.Environment, error) { 29 | return selectors.GetAllEnvironments(dependencies.Client) 30 | }, 31 | } 32 | } 33 | 34 | func NewCreateTargetEnvironmentFlags() *CreateTargetEnvironmentFlags { 35 | return &CreateTargetEnvironmentFlags{ 36 | Environments: flag.New[[]string](FlagEnvironment, false), 37 | } 38 | } 39 | 40 | func RegisterCreateTargetEnvironmentFlags(cmd *cobra.Command, flags *CreateTargetEnvironmentFlags) { 41 | cmd.Flags().StringSliceVar(&flags.Environments.Value, FlagEnvironment, []string{}, "Choose at least one environment for the deployment target.") 42 | } 43 | 44 | func PromptForEnvironments(opts *CreateTargetEnvironmentOptions, flags *CreateTargetEnvironmentFlags) error { 45 | if util.Empty(flags.Environments.Value) { 46 | envs, err := selectors.EnvironmentsMultiSelect(opts.Ask, opts.GetAllEnvironmentsCallback, 47 | "Choose at least one environment for the deployment target.\n", true) 48 | if err != nil { 49 | return err 50 | } 51 | flags.Environments.Value = util.SliceTransform(envs, func(e *environments.Environment) string { return e.Name }) 52 | } 53 | 54 | return nil 55 | } 56 | -------------------------------------------------------------------------------- /pkg/cmd/target/shared/environment_test.go: -------------------------------------------------------------------------------- 1 | package shared_test 2 | 3 | import ( 4 | "github.com/OctopusDeploy/cli/pkg/cmd" 5 | "github.com/OctopusDeploy/cli/pkg/cmd/target/shared" 6 | "github.com/OctopusDeploy/cli/test/testutil" 7 | "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/environments" 8 | "github.com/stretchr/testify/assert" 9 | "testing" 10 | ) 11 | 12 | func TestPromptEnvironments_FlagsSupplied(t *testing.T) { 13 | pa := []*testutil.PA{} 14 | 15 | asker, checkRemainingPrompts := testutil.NewMockAsker(t, pa) 16 | flags := shared.NewCreateTargetEnvironmentFlags() 17 | flags.Environments.Value = []string{"Dev"} 18 | 19 | opts := shared.NewCreateTargetEnvironmentOptions(&cmd.Dependencies{Ask: asker}) 20 | 21 | err := shared.PromptForEnvironments(opts, flags) 22 | checkRemainingPrompts() 23 | 24 | assert.NoError(t, err) 25 | } 26 | 27 | func TestPromptEnvironments_ShouldPrompt(t *testing.T) { 28 | pa := []*testutil.PA{ 29 | testutil.NewMultiSelectPrompt("Choose at least one environment for the deployment target.\n", "", []string{"Dev", "Test"}, []string{"Dev"}), 30 | } 31 | 32 | asker, checkRemainingPrompts := testutil.NewMockAsker(t, pa) 33 | flags := shared.NewCreateTargetEnvironmentFlags() 34 | 35 | opts := shared.NewCreateTargetEnvironmentOptions(&cmd.Dependencies{Ask: asker}) 36 | opts.GetAllEnvironmentsCallback = func() ([]*environments.Environment, error) { 37 | return []*environments.Environment{ 38 | environments.NewEnvironment("Dev"), 39 | environments.NewEnvironment("Test"), 40 | }, nil 41 | } 42 | 43 | err := shared.PromptForEnvironments(opts, flags) 44 | checkRemainingPrompts() 45 | 46 | assert.NoError(t, err) 47 | 48 | assert.Equal(t, []string{"Dev"}, flags.Environments.Value) 49 | } 50 | -------------------------------------------------------------------------------- /pkg/cmd/target/shared/role.go: -------------------------------------------------------------------------------- 1 | package shared 2 | 3 | import ( 4 | "github.com/OctopusDeploy/cli/pkg/cmd" 5 | "github.com/OctopusDeploy/cli/pkg/question" 6 | "github.com/OctopusDeploy/cli/pkg/util" 7 | "github.com/OctopusDeploy/cli/pkg/util/flag" 8 | "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/client" 9 | "github.com/spf13/cobra" 10 | ) 11 | 12 | const ( 13 | FlagRole = "role" 14 | ) 15 | 16 | type GetAllRolesCallback func() ([]string, error) 17 | 18 | type CreateTargetRoleFlags struct { 19 | Roles *flag.Flag[[]string] 20 | } 21 | 22 | type CreateTargetRoleOptions struct { 23 | *cmd.Dependencies 24 | GetAllRolesCallback 25 | } 26 | 27 | func NewCreateTargetRoleOptions(dependencies *cmd.Dependencies) *CreateTargetRoleOptions { 28 | return &CreateTargetRoleOptions{ 29 | Dependencies: dependencies, 30 | 31 | GetAllRolesCallback: func() ([]string, error) { 32 | return getAllMachineRoles(*dependencies.Client) 33 | }, 34 | } 35 | } 36 | 37 | func NewCreateTargetRoleFlags() *CreateTargetRoleFlags { 38 | return &CreateTargetRoleFlags{ 39 | Roles: flag.New[[]string](FlagRole, false), 40 | } 41 | } 42 | 43 | func RegisterCreateTargetRoleFlags(cmd *cobra.Command, commonFlags *CreateTargetRoleFlags) { 44 | cmd.Flags().StringSliceVar(&commonFlags.Roles.Value, FlagRole, []string{}, "Choose at least one role that this deployment target will provide.") 45 | } 46 | 47 | func PromptForRoles(opts *CreateTargetRoleOptions, flags *CreateTargetRoleFlags) error { 48 | 49 | if util.Empty(flags.Roles.Value) { 50 | availableRoles, err := opts.GetAllRolesCallback() 51 | if err != nil { 52 | return err 53 | } 54 | roles, err := question.MultiSelectWithAddMap(opts.Ask, "Choose at least one role for the deployment target.\n", availableRoles, true) 55 | 56 | if err != nil { 57 | return err 58 | } 59 | flags.Roles.Value = roles 60 | } 61 | return nil 62 | } 63 | 64 | func getAllMachineRoles(client client.Client) ([]string, error) { 65 | res, err := client.MachineRoles.GetAll() 66 | if err != nil { 67 | return nil, err 68 | } 69 | 70 | var roles []string 71 | for _, r := range res { 72 | roles = append(roles, *r) 73 | } 74 | return roles, nil 75 | } 76 | -------------------------------------------------------------------------------- /pkg/cmd/target/shared/role_test.go: -------------------------------------------------------------------------------- 1 | package shared_test 2 | 3 | import ( 4 | "github.com/OctopusDeploy/cli/pkg/cmd" 5 | "github.com/OctopusDeploy/cli/pkg/cmd/target/shared" 6 | "github.com/OctopusDeploy/cli/pkg/util" 7 | "github.com/OctopusDeploy/cli/test/testutil" 8 | "github.com/stretchr/testify/assert" 9 | "testing" 10 | ) 11 | 12 | func TestDistinctRoles_EmptyList(t *testing.T) { 13 | result := util.SliceDistinct([]string{}) 14 | assert.Empty(t, result) 15 | } 16 | 17 | func TestDistinctRoles_DuplicateValues(t *testing.T) { 18 | result := util.SliceDistinct([]string{"a", "b", "a"}) 19 | assert.Equal(t, []string{"a", "b"}, result) 20 | } 21 | 22 | func TestPromptRoles_FlagsSupplied(t *testing.T) { 23 | pa := []*testutil.PA{} 24 | 25 | asker, checkRemainingPrompts := testutil.NewMockAsker(t, pa) 26 | flags := shared.NewCreateTargetRoleFlags() 27 | flags.Roles.Value = []string{"Man with hat"} 28 | 29 | opts := shared.NewCreateTargetRoleOptions(&cmd.Dependencies{Ask: asker}) 30 | 31 | err := shared.PromptForRoles(opts, flags) 32 | checkRemainingPrompts() 33 | 34 | assert.NoError(t, err) 35 | } 36 | 37 | func TestPromptRolesAndEnvironments_ShouldPrompt(t *testing.T) { 38 | pa := []*testutil.PA{ 39 | testutil.NewMultiSelectWithAddPrompt("Choose at least one role for the deployment target.\n", "", []string{"Ninja #3", "Girl in crowd"}, []string{"Ninja #3"}), 40 | } 41 | 42 | asker, checkRemainingPrompts := testutil.NewMockAsker(t, pa) 43 | flags := shared.NewCreateTargetRoleFlags() 44 | 45 | opts := shared.NewCreateTargetRoleOptions(&cmd.Dependencies{Ask: asker}) 46 | opts.GetAllRolesCallback = func() ([]string, error) { 47 | return []string{"Ninja #3", "Girl in crowd"}, nil 48 | } 49 | 50 | err := shared.PromptForRoles(opts, flags) 51 | checkRemainingPrompts() 52 | 53 | assert.NoError(t, err) 54 | 55 | assert.Equal(t, []string{"Ninja #3"}, flags.Roles.Value) 56 | } 57 | -------------------------------------------------------------------------------- /pkg/cmd/target/shared/target.go: -------------------------------------------------------------------------------- 1 | package shared 2 | 3 | import ( 4 | "github.com/OctopusDeploy/cli/pkg/cmd" 5 | "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/client" 6 | "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/machines" 7 | "math" 8 | ) 9 | 10 | type GetTargetsCallback func() ([]*machines.DeploymentTarget, error) 11 | 12 | type GetTargetsOptions struct { 13 | GetTargetsCallback 14 | } 15 | 16 | func NewGetTargetsOptions(dependencies *cmd.Dependencies, query machines.MachinesQuery) *GetTargetsOptions { 17 | return &GetTargetsOptions{ 18 | GetTargetsCallback: func() ([]*machines.DeploymentTarget, error) { 19 | return GetAllTargets(*dependencies.Client, query) 20 | }, 21 | } 22 | } 23 | 24 | func NewGetTargetsOptionsForAllTargets(dependencies *cmd.Dependencies) *GetTargetsOptions { 25 | return &GetTargetsOptions{ 26 | GetTargetsCallback: func() ([]*machines.DeploymentTarget, error) { 27 | return GetAllTargets(*dependencies.Client, machines.MachinesQuery{}) 28 | }, 29 | } 30 | } 31 | 32 | func GetAllTargets(client client.Client, query machines.MachinesQuery) ([]*machines.DeploymentTarget, error) { 33 | query.Skip = 0 34 | query.Take = math.MaxInt32 35 | res, err := client.Machines.Get(query) 36 | if err != nil { 37 | return nil, err 38 | } 39 | return res.Items, nil 40 | } 41 | -------------------------------------------------------------------------------- /pkg/cmd/target/ssh/list/list.go: -------------------------------------------------------------------------------- 1 | package list 2 | 3 | import ( 4 | "github.com/MakeNowJust/heredoc/v2" 5 | "github.com/OctopusDeploy/cli/pkg/cmd" 6 | "github.com/OctopusDeploy/cli/pkg/cmd/target/list" 7 | "github.com/OctopusDeploy/cli/pkg/constants" 8 | "github.com/OctopusDeploy/cli/pkg/factory" 9 | "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/machines" 10 | "github.com/spf13/cobra" 11 | ) 12 | 13 | func NewCmdList(f factory.Factory) *cobra.Command { 14 | cmd := &cobra.Command{ 15 | Use: "list", 16 | Short: "List SSH deployment targets", 17 | Long: "List SSH deployment targets in Octopus Deploy", 18 | Example: heredoc.Docf(` 19 | $ %[1]s deployment-target ssh list 20 | $ %[1]s deployment-target ssh ls 21 | `, constants.ExecutableName), 22 | Aliases: []string{"ls"}, 23 | RunE: func(c *cobra.Command, args []string) error { 24 | dependencies := cmd.NewDependencies(f, c) 25 | options := list.NewListOptions(dependencies, c, machines.MachinesQuery{DeploymentTargetTypes: []string{"Ssh"}}) 26 | return list.ListRun(options) 27 | }, 28 | } 29 | 30 | return cmd 31 | } 32 | -------------------------------------------------------------------------------- /pkg/cmd/target/ssh/ssh.go: -------------------------------------------------------------------------------- 1 | package ssh 2 | 3 | import ( 4 | "github.com/MakeNowJust/heredoc/v2" 5 | cmdCreate "github.com/OctopusDeploy/cli/pkg/cmd/target/ssh/create" 6 | cmdList "github.com/OctopusDeploy/cli/pkg/cmd/target/ssh/list" 7 | cmdView "github.com/OctopusDeploy/cli/pkg/cmd/target/ssh/view" 8 | "github.com/OctopusDeploy/cli/pkg/constants" 9 | "github.com/OctopusDeploy/cli/pkg/factory" 10 | "github.com/spf13/cobra" 11 | ) 12 | 13 | func NewCmdSsh(f factory.Factory) *cobra.Command { 14 | cmd := &cobra.Command{ 15 | Use: "ssh ", 16 | Short: "Manage SSH deployment targets", 17 | Long: "Manage SSH deployment targets in Octopus Deploy", 18 | Example: heredoc.Docf("$ %s deployment-target ssh create", constants.ExecutableName), 19 | } 20 | 21 | cmd.AddCommand(cmdCreate.NewCmdCreate(f)) 22 | cmd.AddCommand(cmdList.NewCmdList(f)) 23 | cmd.AddCommand(cmdView.NewCmdView(f)) 24 | return cmd 25 | } 26 | -------------------------------------------------------------------------------- /pkg/cmd/target/ssh/view/view.go: -------------------------------------------------------------------------------- 1 | package view 2 | 3 | import ( 4 | "github.com/MakeNowJust/heredoc/v2" 5 | "github.com/OctopusDeploy/cli/pkg/cmd" 6 | "github.com/OctopusDeploy/cli/pkg/cmd/target/shared" 7 | "github.com/OctopusDeploy/cli/pkg/constants" 8 | "github.com/OctopusDeploy/cli/pkg/factory" 9 | "github.com/OctopusDeploy/cli/pkg/machinescommon" 10 | "github.com/OctopusDeploy/cli/pkg/output" 11 | "github.com/OctopusDeploy/cli/pkg/usage" 12 | "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/machines" 13 | "github.com/spf13/cobra" 14 | ) 15 | 16 | func NewCmdView(f factory.Factory) *cobra.Command { 17 | flags := shared.NewViewFlags() 18 | cmd := &cobra.Command{ 19 | Args: usage.ExactArgs(1), 20 | Use: "view { | }", 21 | Short: "View a SSH deployment target", 22 | Long: "View a SSH deployment target in Octopus Deploy", 23 | Example: heredoc.Docf(` 24 | $ %[1]s deployment-target ssh view 'linux-web-server' 25 | $ %[1]s deployment-target ssh view Machines-100 26 | `, constants.ExecutableName), 27 | RunE: func(c *cobra.Command, args []string) error { 28 | opts := shared.NewViewOptions(flags, cmd.NewDependencies(f, c), args) 29 | return ViewRun(opts) 30 | }, 31 | } 32 | 33 | machinescommon.RegisterWebFlag(cmd, flags.WebFlags) 34 | 35 | return cmd 36 | } 37 | 38 | func ViewRun(opts *shared.ViewOptions) error { 39 | return shared.ViewRun(opts, contributeEndpoint, "SSH") 40 | } 41 | 42 | func contributeEndpoint(opts *shared.ViewOptions, targetEndpoint machines.IEndpoint) ([]*output.DataRow, error) { 43 | data := []*output.DataRow{} 44 | endpoint := targetEndpoint.(*machines.SSHEndpoint) 45 | 46 | data = append(data, output.NewDataRow("URI", endpoint.URI.String())) 47 | data = append(data, output.NewDataRow("Runtime architecture", getRuntimeArchitecture(endpoint))) 48 | accountRows, err := shared.ContributeAccount(opts, endpoint.AccountID) 49 | if err != nil { 50 | return nil, err 51 | } 52 | data = append(data, accountRows...) 53 | 54 | proxy, err := shared.ContributeProxy(opts, endpoint.ProxyID) 55 | data = append(data, proxy...) 56 | if err != nil { 57 | return nil, err 58 | } 59 | 60 | return data, nil 61 | } 62 | 63 | func getRuntimeArchitecture(endpoint *machines.SSHEndpoint) string { 64 | if endpoint.DotNetCorePlatform == "" { 65 | return "Mono" 66 | } 67 | 68 | return endpoint.DotNetCorePlatform 69 | } 70 | -------------------------------------------------------------------------------- /pkg/cmd/target/target.go: -------------------------------------------------------------------------------- 1 | package target 2 | 3 | import ( 4 | "github.com/MakeNowJust/heredoc/v2" 5 | cmdAzureWebApp "github.com/OctopusDeploy/cli/pkg/cmd/target/azure-web-app" 6 | cmdCloudRegion "github.com/OctopusDeploy/cli/pkg/cmd/target/cloud-region" 7 | cmdDelete "github.com/OctopusDeploy/cli/pkg/cmd/target/delete" 8 | cmdKubernetes "github.com/OctopusDeploy/cli/pkg/cmd/target/kubernetes" 9 | cmdList "github.com/OctopusDeploy/cli/pkg/cmd/target/list" 10 | cmdListeningTentacle "github.com/OctopusDeploy/cli/pkg/cmd/target/listening-tentacle" 11 | cmdPollingTentacle "github.com/OctopusDeploy/cli/pkg/cmd/target/polling-tentacle" 12 | cmdSsh "github.com/OctopusDeploy/cli/pkg/cmd/target/ssh" 13 | cmdView "github.com/OctopusDeploy/cli/pkg/cmd/target/view" 14 | "github.com/OctopusDeploy/cli/pkg/constants" 15 | "github.com/OctopusDeploy/cli/pkg/constants/annotations" 16 | "github.com/OctopusDeploy/cli/pkg/factory" 17 | "github.com/spf13/cobra" 18 | ) 19 | 20 | func NewCmdDeploymentTarget(f factory.Factory) *cobra.Command { 21 | cmd := &cobra.Command{ 22 | Use: "deployment-target ", 23 | Short: "Manage deployment targets", 24 | Long: "Manage deployment targets in Octopus Deploy", 25 | Example: heredoc.Docf("$ %s deployment-target list", constants.ExecutableName), 26 | Annotations: map[string]string{ 27 | annotations.IsCore: "true", 28 | }, 29 | } 30 | 31 | cmd.AddCommand(cmdListeningTentacle.NewCmdListeningTentacle(f)) 32 | cmd.AddCommand(cmdPollingTentacle.NewCmdPollingTentacle(f)) 33 | cmd.AddCommand(cmdSsh.NewCmdSsh(f)) 34 | cmd.AddCommand(cmdCloudRegion.NewCmdCloudRegion(f)) 35 | cmd.AddCommand(cmdAzureWebApp.NewCmdAzureWebApp(f)) 36 | cmd.AddCommand(cmdKubernetes.NewCmdKubernetes(f)) 37 | cmd.AddCommand(cmdDelete.NewCmdDelete(f)) 38 | cmd.AddCommand(cmdList.NewCmdList(f)) 39 | cmd.AddCommand(cmdView.NewCmdView(f)) 40 | 41 | return cmd 42 | } 43 | -------------------------------------------------------------------------------- /pkg/cmd/task/task.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | waitCmd "github.com/OctopusDeploy/cli/pkg/cmd/task/wait" 5 | "github.com/OctopusDeploy/cli/pkg/constants/annotations" 6 | "github.com/OctopusDeploy/cli/pkg/factory" 7 | "github.com/spf13/cobra" 8 | ) 9 | 10 | func NewCmdTask(f factory.Factory) *cobra.Command { 11 | cmd := &cobra.Command{ 12 | Use: "task ", 13 | Short: "Manage tasks", 14 | Long: "Manage tasks in Octopus Deploy", 15 | Annotations: map[string]string{ 16 | annotations.IsCore: "true", 17 | }, 18 | } 19 | 20 | cmd.AddCommand(waitCmd.NewCmdWait(f)) 21 | 22 | return cmd 23 | } 24 | -------------------------------------------------------------------------------- /pkg/cmd/tenant/clone/clone_test.go: -------------------------------------------------------------------------------- 1 | package clone_test 2 | 3 | import ( 4 | "github.com/OctopusDeploy/cli/pkg/cmd" 5 | "github.com/OctopusDeploy/cli/pkg/cmd/tenant/clone" 6 | "github.com/OctopusDeploy/cli/test/testutil" 7 | "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/tenants" 8 | "github.com/stretchr/testify/assert" 9 | "testing" 10 | ) 11 | 12 | func TestPromptMissing_AllFlagsSupplied(t *testing.T) { 13 | pa := []*testutil.PA{} 14 | asker, checkRemainingPrompts := testutil.NewMockAsker(t, pa) 15 | flags := clone.NewCloneFlags() 16 | flags.Name.Value = "Cloned tenant" 17 | flags.Description.Value = "the description" 18 | flags.SourceTenant.Value = "source tenant" 19 | 20 | opts := clone.NewCloneOptions(flags, &cmd.Dependencies{Ask: asker}) 21 | 22 | opts.GetTenantCallback = func(identifier string) (*tenants.Tenant, error) { 23 | return tenants.NewTenant("source tenant"), nil 24 | } 25 | 26 | err := clone.PromptMissing(opts) 27 | checkRemainingPrompts() 28 | assert.NoError(t, err) 29 | } 30 | 31 | func TestPromptMissing_NoFlagsSupplied(t *testing.T) { 32 | pa := []*testutil.PA{ 33 | testutil.NewInputPrompt("Name", "A short, memorable, unique name for this Tenant.", "cloned tenant"), 34 | testutil.NewInputPrompt("Description", "A short, memorable, description for this Tenant.", "the description"), 35 | testutil.NewSelectPrompt("You have not specified a source Tenant to clone from. Please select one:", "", []string{"source tenant", "source tenant 2"}, "source tenant"), 36 | } 37 | asker, checkRemainingPrompts := testutil.NewMockAsker(t, pa) 38 | flags := clone.NewCloneFlags() 39 | opts := clone.NewCloneOptions(flags, &cmd.Dependencies{Ask: asker}) 40 | 41 | opts.GetTenantCallback = func(identifier string) (*tenants.Tenant, error) { 42 | return tenants.NewTenant("source tenant"), nil 43 | } 44 | opts.GetAllTenantsCallback= func () ([]*tenants.Tenant, error) { 45 | return []*tenants.Tenant{ 46 | tenants.NewTenant("source tenant"), 47 | tenants.NewTenant("source tenant 2"), 48 | },nil 49 | } 50 | 51 | err := clone.PromptMissing(opts) 52 | checkRemainingPrompts() 53 | assert.NoError(t, err) 54 | 55 | assert.Equal(t, "cloned tenant", flags.Name.Value) 56 | assert.Equal(t, "the description", flags.Description.Value) 57 | assert.Equal(t, "source tenant", flags.SourceTenant.Value) 58 | } -------------------------------------------------------------------------------- /pkg/cmd/tenant/create/create_opts.go: -------------------------------------------------------------------------------- 1 | package create 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/client" 7 | "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/tagsets" 8 | "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/tenants" 9 | 10 | "github.com/OctopusDeploy/cli/pkg/cmd" 11 | "github.com/OctopusDeploy/cli/pkg/output" 12 | "github.com/OctopusDeploy/cli/pkg/util/flag" 13 | ) 14 | 15 | type GetAllTagSetsCallback func() ([]*tagsets.TagSet, error) 16 | 17 | type CreateOptions struct { 18 | *CreateFlags 19 | *cmd.Dependencies 20 | GetAllTagsCallback GetAllTagSetsCallback 21 | } 22 | 23 | func NewCreateOptions(createFlags *CreateFlags, dependencies *cmd.Dependencies) *CreateOptions { 24 | return &CreateOptions{ 25 | CreateFlags: createFlags, 26 | Dependencies: dependencies, 27 | GetAllTagsCallback: getAllTagSetsCallback(dependencies.Client), 28 | } 29 | } 30 | 31 | func (co *CreateOptions) Commit() error { 32 | 33 | tenant := tenants.NewTenant(co.Name.Value) 34 | tenant.Description = co.Description.Value 35 | tenant.TenantTags = co.Tag.Value 36 | 37 | createdTenant, err := co.Client.Tenants.Add(tenant) 38 | if err != nil { 39 | return err 40 | } 41 | 42 | _, err = fmt.Fprintf(co.Out, "\nSuccessfully created tenant %s (%s).\n", createdTenant.Name, createdTenant.ID) 43 | if err != nil { 44 | return err 45 | } 46 | 47 | link := output.Bluef("%s/app#/%s/tenants/%s/overview", co.Host, co.Space.GetID(), createdTenant.GetID()) 48 | fmt.Fprintf(co.Out, "View this tenant on Octopus Deploy: %s\n", link) 49 | 50 | return nil 51 | } 52 | 53 | func (co *CreateOptions) GenerateAutomationCmd() { 54 | if !co.NoPrompt { 55 | autoCmd := flag.GenerateAutomationCmd(co.CmdPath, co.Name, co.Description, co.Tag) 56 | fmt.Fprintf(co.Out, "%s\n", autoCmd) 57 | } 58 | } 59 | 60 | func getAllTagSetsCallback(client *client.Client) GetAllTagSetsCallback { 61 | return func() ([]*tagsets.TagSet, error) { 62 | tagSets, err := client.TagSets.GetAll() 63 | if err != nil { 64 | return nil, err 65 | } 66 | return tagSets, nil 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /pkg/cmd/tenant/create/create_test.go: -------------------------------------------------------------------------------- 1 | package create_test 2 | 3 | import ( 4 | "testing" 5 | 6 | tenantCreate "github.com/OctopusDeploy/cli/pkg/cmd/tenant/create" 7 | "github.com/OctopusDeploy/cli/test/testutil" 8 | "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/tagsets" 9 | ) 10 | 11 | func TestTenantCreate_Tags(t *testing.T) { 12 | tags := []string{ 13 | "foo/bar", 14 | "foo/car", 15 | "bin/bop", 16 | } 17 | pa := []*testutil.PA{ 18 | testutil.NewMultiSelectPrompt("Tags", "", tags, []string{"foo/bar"}), 19 | } 20 | asker, checkRemainingPrompts := testutil.NewMockAsker(t, pa) 21 | getAllTagsCallback := func() ([]*tagsets.TagSet, error) { 22 | fooTagSet := tagsets.NewTagSet("foo") 23 | fooTagSet.Tags = []*tagsets.Tag{ 24 | tagsets.NewTag("bar", "#000000"), 25 | tagsets.NewTag("car", "#0000FF"), 26 | } 27 | fooTagSet.Tags[0].CanonicalTagName = "foo/bar" 28 | fooTagSet.Tags[1].CanonicalTagName = "foo/car" 29 | binTagSet := tagsets.NewTagSet("bin") 30 | binTagSet.Tags = []*tagsets.Tag{ 31 | tagsets.NewTag("bop", "#FF0000"), 32 | } 33 | binTagSet.Tags[0].CanonicalTagName = "bin/bop" 34 | return []*tagsets.TagSet{ 35 | fooTagSet, 36 | binTagSet, 37 | }, nil 38 | } 39 | tenantCreate.AskTags(asker, []string{}, getAllTagsCallback) 40 | checkRemainingPrompts() 41 | } 42 | -------------------------------------------------------------------------------- /pkg/cmd/tenant/disable/disable.go: -------------------------------------------------------------------------------- 1 | package disable 2 | 3 | import ( 4 | "fmt" 5 | "github.com/MakeNowJust/heredoc/v2" 6 | "github.com/OctopusDeploy/cli/pkg/cmd" 7 | "github.com/OctopusDeploy/cli/pkg/constants" 8 | "github.com/OctopusDeploy/cli/pkg/factory" 9 | "github.com/OctopusDeploy/cli/pkg/question/selectors" 10 | "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/tenants" 11 | "github.com/spf13/cobra" 12 | ) 13 | 14 | type DisableOptions struct { 15 | *cmd.Dependencies 16 | IdOrName string 17 | } 18 | 19 | func NewDisableOptions(args []string, dependencies *cmd.Dependencies) *DisableOptions { 20 | return &DisableOptions{ 21 | Dependencies: dependencies, 22 | IdOrName: args[0], 23 | } 24 | } 25 | 26 | func NewCmdDisable(f factory.Factory) *cobra.Command { 27 | cmd := &cobra.Command{ 28 | Use: "disable { | }", 29 | Short: "Disable a tenant", 30 | Long: "Disable a tenant in Octopus Deploy", 31 | Example: heredoc.Docf(` 32 | $ %[1]s tenant disable Tenants-1 33 | $ %[1]s tenant disable 'Tenant' 34 | `, constants.ExecutableName), 35 | RunE: func(c *cobra.Command, args []string) error { 36 | if len(args) == 0 { 37 | args = append(args, "") 38 | } 39 | 40 | opts := NewDisableOptions(args, cmd.NewDependencies(f, c)) 41 | return disableRun(opts) 42 | }, 43 | } 44 | 45 | return cmd 46 | } 47 | 48 | func disableRun(opts *DisableOptions) error { 49 | if !opts.NoPrompt { 50 | if err := PromptMissing(opts); err != nil { 51 | return err 52 | } 53 | } 54 | 55 | if opts.IdOrName == "" { 56 | return fmt.Errorf("tenant identifier is required but was not provided") 57 | } 58 | 59 | tenantToUpdate, err := opts.Client.Tenants.GetByIdentifier(opts.IdOrName) 60 | if err != nil { 61 | return err 62 | } 63 | 64 | tenantToUpdate.IsDisabled = true 65 | _, err = tenants.Update(opts.Client, tenantToUpdate) 66 | if err != nil { 67 | return err 68 | } 69 | 70 | return nil 71 | } 72 | 73 | func PromptMissing(opts *DisableOptions) error { 74 | if opts.IdOrName == "" { 75 | existingTenants, err := opts.Client.Tenants.GetAll() 76 | if err != nil { 77 | return err 78 | } 79 | selectedTenant, err := selectors.ByName(opts.Ask, existingTenants, "Select the tenant you wish to disable:") 80 | if err != nil { 81 | return err 82 | } 83 | opts.IdOrName = selectedTenant.GetID() 84 | } 85 | 86 | return nil 87 | } 88 | -------------------------------------------------------------------------------- /pkg/cmd/tenant/enable/enable.go: -------------------------------------------------------------------------------- 1 | package enable 2 | 3 | import ( 4 | "fmt" 5 | "github.com/MakeNowJust/heredoc/v2" 6 | "github.com/OctopusDeploy/cli/pkg/cmd" 7 | "github.com/OctopusDeploy/cli/pkg/constants" 8 | "github.com/OctopusDeploy/cli/pkg/factory" 9 | "github.com/OctopusDeploy/cli/pkg/question/selectors" 10 | "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/tenants" 11 | "github.com/spf13/cobra" 12 | ) 13 | 14 | type EnableOptions struct { 15 | *cmd.Dependencies 16 | IdOrName string 17 | } 18 | 19 | func NewEnableOptions(args []string, dependencies *cmd.Dependencies) *EnableOptions { 20 | return &EnableOptions{ 21 | Dependencies: dependencies, 22 | IdOrName: args[0], 23 | } 24 | } 25 | 26 | func NewCmdEnable(f factory.Factory) *cobra.Command { 27 | cmd := &cobra.Command{ 28 | Use: "enable { | }", 29 | Short: "Enable a tenant", 30 | Long: "Enable a tenant in Octopus Deploy", 31 | Example: heredoc.Docf(` 32 | $ %[1]s tenant enable Tenants-1 33 | $ %[1]s tenant enable 'Tenant' 34 | `, constants.ExecutableName), 35 | RunE: func(c *cobra.Command, args []string) error { 36 | if len(args) == 0 { 37 | args = append(args, "") 38 | } 39 | 40 | opts := NewEnableOptions(args, cmd.NewDependencies(f, c)) 41 | return enableRun(opts) 42 | }, 43 | } 44 | 45 | return cmd 46 | } 47 | 48 | func enableRun(opts *EnableOptions) error { 49 | if !opts.NoPrompt { 50 | if err := PromptMissing(opts); err != nil { 51 | return err 52 | } 53 | } 54 | 55 | if opts.IdOrName == "" { 56 | return fmt.Errorf("tenant identifier is required but was not provided") 57 | } 58 | 59 | tenantToUpdate, err := opts.Client.Tenants.GetByIdentifier(opts.IdOrName) 60 | if err != nil { 61 | return err 62 | } 63 | 64 | tenantToUpdate.IsDisabled = false 65 | _, err = tenants.Update(opts.Client, tenantToUpdate) 66 | if err != nil { 67 | return err 68 | } 69 | 70 | return nil 71 | } 72 | 73 | func PromptMissing(opts *EnableOptions) error { 74 | if opts.IdOrName == "" { 75 | existingTenants, err := opts.Client.Tenants.GetAll() 76 | if err != nil { 77 | return err 78 | } 79 | selectedTenant, err := selectors.ByName(opts.Ask, existingTenants, "Select the tenant you wish to enable:") 80 | if err != nil { 81 | return err 82 | } 83 | opts.IdOrName = selectedTenant.GetID() 84 | } 85 | 86 | return nil 87 | } 88 | -------------------------------------------------------------------------------- /pkg/cmd/tenant/tenant.go: -------------------------------------------------------------------------------- 1 | package tenant 2 | 3 | import ( 4 | "github.com/MakeNowJust/heredoc/v2" 5 | cmdClone "github.com/OctopusDeploy/cli/pkg/cmd/tenant/clone" 6 | cmdConnect "github.com/OctopusDeploy/cli/pkg/cmd/tenant/connect" 7 | cmdCreate "github.com/OctopusDeploy/cli/pkg/cmd/tenant/create" 8 | cmdDelete "github.com/OctopusDeploy/cli/pkg/cmd/tenant/delete" 9 | cmdDisable "github.com/OctopusDeploy/cli/pkg/cmd/tenant/disable" 10 | cmdDisconnect "github.com/OctopusDeploy/cli/pkg/cmd/tenant/disconnect" 11 | cmdEnable "github.com/OctopusDeploy/cli/pkg/cmd/tenant/enable" 12 | cmdList "github.com/OctopusDeploy/cli/pkg/cmd/tenant/list" 13 | cmdTag "github.com/OctopusDeploy/cli/pkg/cmd/tenant/tag" 14 | cmdVariable "github.com/OctopusDeploy/cli/pkg/cmd/tenant/variables" 15 | cmdView "github.com/OctopusDeploy/cli/pkg/cmd/tenant/view" 16 | "github.com/OctopusDeploy/cli/pkg/constants" 17 | "github.com/OctopusDeploy/cli/pkg/constants/annotations" 18 | "github.com/OctopusDeploy/cli/pkg/factory" 19 | "github.com/spf13/cobra" 20 | ) 21 | 22 | func NewCmdTenant(f factory.Factory) *cobra.Command { 23 | cmd := &cobra.Command{ 24 | Use: "tenant ", 25 | Short: "Manage tenants", 26 | Long: "Manage tenants in Octopus Deploy", 27 | Example: heredoc.Docf(` 28 | $ %[1]s tenant list 29 | $ %[1]s tenant ls 30 | `, constants.ExecutableName), 31 | Annotations: map[string]string{ 32 | annotations.IsCore: "true", 33 | }, 34 | } 35 | 36 | cmd.AddCommand(cmdConnect.NewCmdConnect(f)) 37 | cmd.AddCommand(cmdDisconnect.NewCmdDisconnect(f)) 38 | cmd.AddCommand(cmdList.NewCmdList(f)) 39 | cmd.AddCommand(cmdCreate.NewCmdCreate(f)) 40 | cmd.AddCommand(cmdTag.NewCmdTag(f)) 41 | cmd.AddCommand(cmdClone.NewCmdClone(f)) 42 | cmd.AddCommand(cmdDelete.NewCmdDelete(f)) 43 | cmd.AddCommand(cmdView.NewCmdView(f)) 44 | cmd.AddCommand(cmdVariable.NewCmdVariables(f)) 45 | cmd.AddCommand(cmdEnable.NewCmdEnable(f)) 46 | cmd.AddCommand(cmdDisable.NewCmdDisable(f)) 47 | 48 | return cmd 49 | } 50 | -------------------------------------------------------------------------------- /pkg/cmd/tenant/variables/variables.go: -------------------------------------------------------------------------------- 1 | package variables 2 | 3 | import ( 4 | "github.com/MakeNowJust/heredoc/v2" 5 | cmdList "github.com/OctopusDeploy/cli/pkg/cmd/tenant/variables/list" 6 | cmdUpdate "github.com/OctopusDeploy/cli/pkg/cmd/tenant/variables/update" 7 | "github.com/OctopusDeploy/cli/pkg/constants" 8 | "github.com/OctopusDeploy/cli/pkg/constants/annotations" 9 | "github.com/OctopusDeploy/cli/pkg/factory" 10 | "github.com/spf13/cobra" 11 | ) 12 | 13 | func NewCmdVariables(f factory.Factory) *cobra.Command { 14 | cmd := &cobra.Command{ 15 | Use: "variables ", 16 | Aliases: []string{"variable"}, 17 | Short: "Manage tenant variables", 18 | Long: "Manage tenant variables in Octopus Deploy", 19 | Example: heredoc.Docf(` 20 | $ %[1]s tenant variables list "Bobs Wood Shop" 21 | $ %[1]s tenant variables update --tenant "Bobs Fish Shack" --name "site-name" --value "Bob's Fish Shack" --project "Awesome Web Site" --environment "Test" 22 | `, constants.ExecutableName), 23 | Annotations: map[string]string{ 24 | annotations.IsCore: "true", 25 | }, 26 | } 27 | 28 | //cmd.AddCommand(cmdUpdate.NewUpdateCmd(f)) 29 | //cmd.AddCommand(cmdCreate.NewCreateCmd(f)) 30 | cmd.AddCommand(cmdList.NewCmdList(f)) 31 | cmd.AddCommand(cmdUpdate.NewCmdUpdate(f)) 32 | //cmd.AddCommand(cmdView.NewCmdView(f)) 33 | //cmd.AddCommand(cmdDelete.NewDeleteCmd(f)) 34 | //cmd.AddCommand(cmdInclude.NewIncludeVariableSetCmd(f)) 35 | //cmd.AddCommand(cmdExclude.NewExcludeVariableSetCmd(f)) 36 | 37 | return cmd 38 | } 39 | -------------------------------------------------------------------------------- /pkg/cmd/user/list/list.go: -------------------------------------------------------------------------------- 1 | package list 2 | 3 | import ( 4 | "github.com/MakeNowJust/heredoc/v2" 5 | "github.com/OctopusDeploy/cli/pkg/apiclient" 6 | "github.com/OctopusDeploy/cli/pkg/constants" 7 | "github.com/OctopusDeploy/cli/pkg/factory" 8 | "github.com/OctopusDeploy/cli/pkg/output" 9 | "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/users" 10 | "github.com/spf13/cobra" 11 | ) 12 | 13 | type UserAsJson struct { 14 | Id string `json:"Id"` 15 | Name string `json:"Name"` 16 | UserName string `json:"UserName"` 17 | Description string `json:"Description"` 18 | } 19 | 20 | func NewCmdList(f factory.Factory) *cobra.Command { 21 | cmd := &cobra.Command{ 22 | Use: "list", 23 | Short: "List users", 24 | Long: "List users in Octopus Deploy", 25 | Example: heredoc.Docf(` 26 | $ %[1]s user list 27 | $ %[1]s user ls 28 | `, constants.ExecutableName), 29 | Aliases: []string{"ls"}, 30 | RunE: func(cmd *cobra.Command, args []string) error { 31 | return listRun(cmd, f) 32 | }, 33 | } 34 | 35 | return cmd 36 | } 37 | 38 | func listRun(cmd *cobra.Command, f factory.Factory) error { 39 | client, err := f.GetSpacedClient(apiclient.NewRequester(cmd)) 40 | if err != nil { 41 | return err 42 | } 43 | 44 | allUsers, err := client.Users.GetAll() 45 | if err != nil { 46 | return err 47 | } 48 | 49 | return output.PrintArray(allUsers, cmd, output.Mappers[*users.User]{ 50 | Json: func(t *users.User) any { 51 | return UserAsJson{ 52 | Id: t.GetID(), 53 | Name: t.DisplayName, 54 | UserName: t.Username, 55 | // TODO other fields like isService, etc 56 | } 57 | }, 58 | Table: output.TableDefinition[*users.User]{ 59 | // TODO other fields like isService, etc 60 | Header: []string{"USERNAME", "NAME", "ID"}, 61 | Row: func(t *users.User) []string { 62 | return []string{output.Bold(t.Username), t.DisplayName, t.GetID()} 63 | }, 64 | }, 65 | Basic: func(t *users.User) string { 66 | return t.Username 67 | }, 68 | }) 69 | } 70 | -------------------------------------------------------------------------------- /pkg/cmd/user/user.go: -------------------------------------------------------------------------------- 1 | package user 2 | 3 | import ( 4 | "github.com/MakeNowJust/heredoc/v2" 5 | cmdDelete "github.com/OctopusDeploy/cli/pkg/cmd/user/delete" 6 | cmdList "github.com/OctopusDeploy/cli/pkg/cmd/user/list" 7 | "github.com/OctopusDeploy/cli/pkg/constants" 8 | "github.com/OctopusDeploy/cli/pkg/constants/annotations" 9 | "github.com/OctopusDeploy/cli/pkg/factory" 10 | "github.com/spf13/cobra" 11 | ) 12 | 13 | func NewCmdUser(f factory.Factory) *cobra.Command { 14 | cmd := &cobra.Command{ 15 | Use: "user ", 16 | Short: "Manage users", 17 | Long: "Manage user in Octopus Deploy", 18 | Example: heredoc.Docf(` 19 | $ %[1]s user list 20 | $ %[1]s user ls 21 | `, constants.ExecutableName), 22 | Annotations: map[string]string{ 23 | annotations.IsCore: "true", 24 | }, 25 | } 26 | 27 | cmd.AddCommand(cmdList.NewCmdList(f)) 28 | cmd.AddCommand(cmdDelete.NewCmdDelete(f)) 29 | 30 | return cmd 31 | } 32 | -------------------------------------------------------------------------------- /pkg/cmd/version/version.go: -------------------------------------------------------------------------------- 1 | package version 2 | 3 | import ( 4 | "github.com/MakeNowJust/heredoc/v2" 5 | "github.com/OctopusDeploy/cli/pkg/constants" 6 | "github.com/OctopusDeploy/cli/pkg/factory" 7 | "github.com/spf13/cobra" 8 | ) 9 | 10 | func NewCmdVersion(f factory.Factory) *cobra.Command { 11 | cmd := &cobra.Command{ 12 | Use: "version", 13 | Hidden: true, 14 | Example: heredoc.Docf("$ %s version", constants.ExecutableName), 15 | RunE: func(cmd *cobra.Command, args []string) error { 16 | cmd.Println(f.BuildVersion()) 17 | return nil 18 | }, 19 | } 20 | 21 | return cmd 22 | } 23 | -------------------------------------------------------------------------------- /pkg/cmd/worker/listening-tentacle/list/list.go: -------------------------------------------------------------------------------- 1 | package list 2 | 3 | import ( 4 | "github.com/MakeNowJust/heredoc/v2" 5 | "github.com/OctopusDeploy/cli/pkg/cmd" 6 | "github.com/OctopusDeploy/cli/pkg/cmd/worker/list" 7 | "github.com/OctopusDeploy/cli/pkg/constants" 8 | "github.com/OctopusDeploy/cli/pkg/factory" 9 | "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/machines" 10 | "github.com/spf13/cobra" 11 | ) 12 | 13 | func NewCmdList(f factory.Factory) *cobra.Command { 14 | cmd := &cobra.Command{ 15 | Use: "list", 16 | Short: "List Listening Tentacle workers", 17 | Long: "List Listening Tentacle workers in Octopus Deploy", 18 | Aliases: []string{"ls"}, 19 | Example: heredoc.Docf(` 20 | $ %s worker listening-tentacle list 21 | `, constants.ExecutableName), 22 | RunE: func(c *cobra.Command, args []string) error { 23 | dependencies := cmd.NewDependencies(f, c) 24 | options := list.NewListOptions(dependencies, c, func(worker *machines.Worker) bool { 25 | return worker.Endpoint.GetCommunicationStyle() == "TentaclePassive" 26 | }) 27 | return list.ListRun(options) 28 | }, 29 | } 30 | 31 | return cmd 32 | } 33 | -------------------------------------------------------------------------------- /pkg/cmd/worker/listening-tentacle/listening-tentacle.go: -------------------------------------------------------------------------------- 1 | package listening_tentacle 2 | 3 | import ( 4 | "fmt" 5 | 6 | cmdCreate "github.com/OctopusDeploy/cli/pkg/cmd/worker/listening-tentacle/create" 7 | cmdList "github.com/OctopusDeploy/cli/pkg/cmd/worker/listening-tentacle/list" 8 | cmdView "github.com/OctopusDeploy/cli/pkg/cmd/worker/listening-tentacle/view" 9 | "github.com/OctopusDeploy/cli/pkg/constants" 10 | "github.com/OctopusDeploy/cli/pkg/factory" 11 | "github.com/spf13/cobra" 12 | ) 13 | 14 | func NewCmdListeningTentacle(f factory.Factory) *cobra.Command { 15 | cmd := &cobra.Command{ 16 | Use: "listening-tentacle ", 17 | Short: "Manage Listening Tentacle workers", 18 | Long: "Manage Listening Tentacle workers in Octopus Deploy", 19 | Example: fmt.Sprintf("$ %s worker listening-tentacle list", constants.ExecutableName), 20 | } 21 | 22 | cmd.AddCommand(cmdCreate.NewCmdCreate(f)) 23 | cmd.AddCommand(cmdList.NewCmdList(f)) 24 | cmd.AddCommand(cmdView.NewCmdView(f)) 25 | 26 | return cmd 27 | } 28 | -------------------------------------------------------------------------------- /pkg/cmd/worker/listening-tentacle/view/view.go: -------------------------------------------------------------------------------- 1 | package view 2 | 3 | import ( 4 | "github.com/MakeNowJust/heredoc/v2" 5 | "github.com/OctopusDeploy/cli/pkg/cmd" 6 | "github.com/OctopusDeploy/cli/pkg/cmd/worker/shared" 7 | "github.com/OctopusDeploy/cli/pkg/constants" 8 | "github.com/OctopusDeploy/cli/pkg/factory" 9 | "github.com/OctopusDeploy/cli/pkg/machinescommon" 10 | "github.com/OctopusDeploy/cli/pkg/output" 11 | "github.com/OctopusDeploy/cli/pkg/usage" 12 | "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/machines" 13 | "github.com/spf13/cobra" 14 | ) 15 | 16 | func NewCmdView(f factory.Factory) *cobra.Command { 17 | flags := shared.NewViewFlags() 18 | cmd := &cobra.Command{ 19 | Args: usage.ExactArgs(1), 20 | Use: "view { | }", 21 | Short: "View a Listening Tentacle worker", 22 | Long: "View a Listening Tentacle worker in Octopus Deploy", 23 | Example: heredoc.Docf(` 24 | $ %[1]s worker listening-tentacle view 'WindowsWorker' 25 | $ %[1]s worker listening-tentacle view Machines-100 26 | `, constants.ExecutableName), 27 | RunE: func(c *cobra.Command, args []string) error { 28 | opts := shared.NewViewOptions(flags, cmd.NewDependencies(f, c), args) 29 | return ViewRun(opts) 30 | }, 31 | } 32 | 33 | machinescommon.RegisterWebFlag(cmd, flags.WebFlags) 34 | 35 | return cmd 36 | } 37 | 38 | func ViewRun(opts *shared.ViewOptions) error { 39 | return shared.ViewRun(opts, contributeEndpoint, "Listening Tentacle") 40 | } 41 | 42 | func contributeEndpoint(opts *shared.ViewOptions, end machines.IEndpoint) ([]*output.DataRow, error) { 43 | data := []*output.DataRow{} 44 | 45 | endpoint := end.(*machines.ListeningTentacleEndpoint) 46 | data = append(data, output.NewDataRow("URI", endpoint.URI.String())) 47 | data = append(data, output.NewDataRow("Tentacle version", endpoint.TentacleVersionDetails.Version)) 48 | 49 | proxyData, err := shared.ContributeProxy(opts, endpoint.ProxyID) 50 | if err != nil { 51 | return nil, err 52 | } 53 | data = append(data, proxyData...) 54 | 55 | return data, nil 56 | } 57 | -------------------------------------------------------------------------------- /pkg/cmd/worker/polling-tentacle/list/list.go: -------------------------------------------------------------------------------- 1 | package list 2 | 3 | import ( 4 | "github.com/MakeNowJust/heredoc/v2" 5 | "github.com/OctopusDeploy/cli/pkg/cmd" 6 | "github.com/OctopusDeploy/cli/pkg/cmd/worker/list" 7 | "github.com/OctopusDeploy/cli/pkg/constants" 8 | "github.com/OctopusDeploy/cli/pkg/factory" 9 | "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/machines" 10 | "github.com/spf13/cobra" 11 | ) 12 | 13 | func NewCmdList(f factory.Factory) *cobra.Command { 14 | cmd := &cobra.Command{ 15 | Use: "list", 16 | Short: "List Polling Tentacle workers", 17 | Long: "List Polling Tentacle workers in Octopus Deploy", 18 | Aliases: []string{"ls"}, 19 | Example: heredoc.Docf("$ %s worker polling-tentacle list", constants.ExecutableName), 20 | RunE: func(c *cobra.Command, args []string) error { 21 | dependencies := cmd.NewDependencies(f, c) 22 | options := list.NewListOptions(dependencies, c, func(worker *machines.Worker) bool { 23 | return worker.Endpoint.GetCommunicationStyle() == "TentacleActive" 24 | }) 25 | return list.ListRun(options) 26 | }, 27 | } 28 | 29 | return cmd 30 | } 31 | -------------------------------------------------------------------------------- /pkg/cmd/worker/polling-tentacle/polling-tentacle.go: -------------------------------------------------------------------------------- 1 | package polling_tentacle 2 | 3 | import ( 4 | "fmt" 5 | 6 | cmdList "github.com/OctopusDeploy/cli/pkg/cmd/worker/polling-tentacle/list" 7 | cmdView "github.com/OctopusDeploy/cli/pkg/cmd/worker/polling-tentacle/view" 8 | "github.com/OctopusDeploy/cli/pkg/constants" 9 | "github.com/OctopusDeploy/cli/pkg/factory" 10 | "github.com/spf13/cobra" 11 | ) 12 | 13 | func NewCmdPollingTentacle(f factory.Factory) *cobra.Command { 14 | cmd := &cobra.Command{ 15 | Use: "polling-tentacle ", 16 | Short: "Manage Polling Tentacle workers", 17 | Long: "Manage Polling Tentacle workers in Octopus Deploy", 18 | Example: fmt.Sprintf("$ %s worker polling-tentacle list", constants.ExecutableName), 19 | } 20 | 21 | cmd.AddCommand(cmdList.NewCmdList(f)) 22 | cmd.AddCommand(cmdView.NewCmdView(f)) 23 | 24 | return cmd 25 | } 26 | -------------------------------------------------------------------------------- /pkg/cmd/worker/polling-tentacle/view/view.go: -------------------------------------------------------------------------------- 1 | package view 2 | 3 | import ( 4 | "github.com/MakeNowJust/heredoc/v2" 5 | "github.com/OctopusDeploy/cli/pkg/cmd" 6 | "github.com/OctopusDeploy/cli/pkg/cmd/worker/shared" 7 | "github.com/OctopusDeploy/cli/pkg/constants" 8 | "github.com/OctopusDeploy/cli/pkg/factory" 9 | "github.com/OctopusDeploy/cli/pkg/machinescommon" 10 | "github.com/OctopusDeploy/cli/pkg/output" 11 | "github.com/OctopusDeploy/cli/pkg/usage" 12 | "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/machines" 13 | "github.com/spf13/cobra" 14 | ) 15 | 16 | func NewCmdView(f factory.Factory) *cobra.Command { 17 | flags := shared.NewViewFlags() 18 | cmd := &cobra.Command{ 19 | Args: usage.ExactArgs(1), 20 | Use: "view { | }", 21 | Short: "View a Polling Tentacle worker", 22 | Long: "View a Polling Tentacle worker in Octopus Deploy", 23 | Example: heredoc.Docf(` 24 | $ %[1]s worker polling-tentacle view 'WindowsWorker' 25 | $ %[1]s worker polling-tentacle view Machines-100 26 | `, constants.ExecutableName), 27 | RunE: func(c *cobra.Command, args []string) error { 28 | opts := shared.NewViewOptions(flags, cmd.NewDependencies(f, c), args) 29 | return ViewRun(opts) 30 | }, 31 | } 32 | 33 | machinescommon.RegisterWebFlag(cmd, flags.WebFlags) 34 | 35 | return cmd 36 | } 37 | 38 | func ViewRun(opts *shared.ViewOptions) error { 39 | return shared.ViewRun(opts, contributeEndpoint, "Polling Tentacle") 40 | } 41 | 42 | func contributeEndpoint(opts *shared.ViewOptions, workerEndpoint machines.IEndpoint) ([]*output.DataRow, error) { 43 | data := []*output.DataRow{} 44 | 45 | endpoint := workerEndpoint.(*machines.PollingTentacleEndpoint) 46 | data = append(data, output.NewDataRow("URI", endpoint.URI.String())) 47 | data = append(data, output.NewDataRow("Tentacle version", endpoint.TentacleVersionDetails.Version)) 48 | 49 | return data, nil 50 | } 51 | -------------------------------------------------------------------------------- /pkg/cmd/worker/shared/worker.go: -------------------------------------------------------------------------------- 1 | package shared 2 | 3 | import ( 4 | "github.com/OctopusDeploy/cli/pkg/cmd" 5 | "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/client" 6 | "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/machines" 7 | ) 8 | 9 | type GetWorkersCallback func() ([]*machines.Worker, error) 10 | type GetWorkerCallback func(identifer string) (*machines.Worker, error) 11 | 12 | type GetWorkersOptions struct { 13 | GetWorkersCallback 14 | GetWorkerCallback 15 | } 16 | 17 | func NewGetWorkersOptions(dependencies *cmd.Dependencies, filter func(*machines.Worker) bool) *GetWorkersOptions { 18 | return &GetWorkersOptions{ 19 | GetWorkersCallback: func() ([]*machines.Worker, error) { 20 | return GetWorkers(*dependencies.Client, filter) 21 | }, 22 | GetWorkerCallback: func(identifier string) (*machines.Worker, error) { 23 | return GetWorker(*dependencies.Client, identifier) 24 | }, 25 | } 26 | } 27 | 28 | func NewGetWorkersOptionsForAllWorkers(dependencies *cmd.Dependencies) *GetWorkersOptions { 29 | return &GetWorkersOptions{ 30 | GetWorkersCallback: func() ([]*machines.Worker, error) { 31 | return GetWorkers(*dependencies.Client, nil) 32 | }, 33 | } 34 | } 35 | 36 | func GetWorkers(client client.Client, filter func(*machines.Worker) bool) ([]*machines.Worker, error) { 37 | allWorkers, err := client.Workers.GetAll() 38 | if err != nil { 39 | return nil, err 40 | } 41 | 42 | if filter == nil { 43 | return allWorkers, nil 44 | } 45 | 46 | var workers []*machines.Worker 47 | for _, w := range allWorkers { 48 | filterResult := filter(w) 49 | if filterResult { 50 | workers = append(workers, w) 51 | } 52 | } 53 | 54 | return workers, nil 55 | } 56 | 57 | func GetWorker(client client.Client, identifier string) (*machines.Worker, error) { 58 | worker, err := client.Workers.GetByIdentifier(identifier) 59 | if err != nil { 60 | return nil, err 61 | } 62 | 63 | return worker, nil 64 | } 65 | -------------------------------------------------------------------------------- /pkg/cmd/worker/shared/workerpool_test.go: -------------------------------------------------------------------------------- 1 | package shared_test 2 | 3 | import ( 4 | "github.com/OctopusDeploy/cli/pkg/cmd" 5 | "github.com/OctopusDeploy/cli/pkg/cmd/worker/shared" 6 | "github.com/OctopusDeploy/cli/test/testutil" 7 | "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/workerpools" 8 | "github.com/stretchr/testify/assert" 9 | "testing" 10 | ) 11 | 12 | func TestPromptForWorkerPool_FlagsSupplied(t *testing.T) { 13 | pa := []*testutil.PA{} 14 | 15 | asker, checkRemainingPrompts := testutil.NewMockAsker(t, pa) 16 | flags := shared.NewWorkerPoolFlags() 17 | flags.WorkerPools.Value = []string{"Head lifeguard"} 18 | 19 | opts := shared.NewWorkerPoolOptions(&cmd.Dependencies{Ask: asker}) 20 | err := shared.PromptForWorkerPools(opts, flags) 21 | checkRemainingPrompts() 22 | assert.NoError(t, err) 23 | } 24 | 25 | func TestPromptForWorkerPool_NoFlagsSupplied(t *testing.T) { 26 | pa := []*testutil.PA{ 27 | testutil.NewMultiSelectPrompt("Select the worker pools to assign to the worker", "", []string{"Groundskeeper", "Swim instructor"}, []string{"Groundskeeper", "Swim instructor"}), 28 | } 29 | 30 | asker, checkRemainingPrompts := testutil.NewMockAsker(t, pa) 31 | flags := shared.NewWorkerPoolFlags() 32 | 33 | opts := shared.NewWorkerPoolOptions(&cmd.Dependencies{Ask: asker}) 34 | opts.GetAllWorkerPoolsCallback = func() ([]*workerpools.WorkerPoolListResult, error) { 35 | poolWorker1 := &workerpools.WorkerPoolListResult{ 36 | ID: "WorkerPools-1", 37 | Name: "Groundskeeper", 38 | WorkerPoolType: workerpools.WorkerPoolTypeStatic, 39 | CanAddWorkers: true, 40 | } 41 | poolWorker2 := &workerpools.WorkerPoolListResult{ 42 | ID: "WorkerPools-2", 43 | Name: "Swim instructor", 44 | WorkerPoolType: workerpools.WorkerPoolTypeStatic, 45 | CanAddWorkers: true, 46 | } 47 | return []*workerpools.WorkerPoolListResult{poolWorker1, poolWorker2}, nil 48 | } 49 | err := shared.PromptForWorkerPools(opts, flags) 50 | checkRemainingPrompts() 51 | assert.NoError(t, err) 52 | assert.Equal(t, []string{"Groundskeeper", "Swim instructor"}, flags.WorkerPools.Value) 53 | } 54 | -------------------------------------------------------------------------------- /pkg/cmd/worker/ssh/list/list.go: -------------------------------------------------------------------------------- 1 | package list 2 | 3 | import ( 4 | "github.com/MakeNowJust/heredoc/v2" 5 | "github.com/OctopusDeploy/cli/pkg/cmd" 6 | "github.com/OctopusDeploy/cli/pkg/cmd/worker/list" 7 | "github.com/OctopusDeploy/cli/pkg/constants" 8 | "github.com/OctopusDeploy/cli/pkg/factory" 9 | "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/machines" 10 | "github.com/spf13/cobra" 11 | ) 12 | 13 | func NewCmdList(f factory.Factory) *cobra.Command { 14 | cmd := &cobra.Command{ 15 | Use: "list", 16 | Short: "List SSH workers", 17 | Long: "List SSH workers in Octopus Deploy", 18 | Aliases: []string{"ls"}, 19 | Example: heredoc.Docf("$ %s worker ssh list", constants.ExecutableName), 20 | RunE: func(c *cobra.Command, args []string) error { 21 | dependencies := cmd.NewDependencies(f, c) 22 | options := list.NewListOptions(dependencies, c, func(worker *machines.Worker) bool { 23 | return worker.Endpoint.GetCommunicationStyle() == "Ssh" 24 | }) 25 | return list.ListRun(options) 26 | }, 27 | } 28 | 29 | return cmd 30 | } 31 | -------------------------------------------------------------------------------- /pkg/cmd/worker/ssh/ssh.go: -------------------------------------------------------------------------------- 1 | package ssh 2 | 3 | import ( 4 | "github.com/MakeNowJust/heredoc/v2" 5 | cmdCreate "github.com/OctopusDeploy/cli/pkg/cmd/worker/ssh/create" 6 | cmdList "github.com/OctopusDeploy/cli/pkg/cmd/worker/ssh/list" 7 | cmdView "github.com/OctopusDeploy/cli/pkg/cmd/worker/ssh/view" 8 | "github.com/OctopusDeploy/cli/pkg/constants" 9 | "github.com/OctopusDeploy/cli/pkg/factory" 10 | "github.com/spf13/cobra" 11 | ) 12 | 13 | func NewCmdSsh(f factory.Factory) *cobra.Command { 14 | cmd := &cobra.Command{ 15 | Use: "ssh ", 16 | Short: "Manage SSH workers", 17 | Long: "Manage SSH workers in Octopus Deploy", 18 | Example: heredoc.Docf("$ %s worker SSH list", constants.ExecutableName), 19 | } 20 | 21 | cmd.AddCommand(cmdCreate.NewCmdCreate(f)) 22 | cmd.AddCommand(cmdList.NewCmdList(f)) 23 | cmd.AddCommand(cmdView.NewCmdView(f)) 24 | 25 | return cmd 26 | } 27 | -------------------------------------------------------------------------------- /pkg/cmd/worker/ssh/view/view.go: -------------------------------------------------------------------------------- 1 | package view 2 | 3 | import ( 4 | "github.com/MakeNowJust/heredoc/v2" 5 | "github.com/OctopusDeploy/cli/pkg/cmd" 6 | "github.com/OctopusDeploy/cli/pkg/cmd/worker/shared" 7 | "github.com/OctopusDeploy/cli/pkg/constants" 8 | "github.com/OctopusDeploy/cli/pkg/factory" 9 | "github.com/OctopusDeploy/cli/pkg/machinescommon" 10 | "github.com/OctopusDeploy/cli/pkg/output" 11 | "github.com/OctopusDeploy/cli/pkg/usage" 12 | "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/machines" 13 | "github.com/spf13/cobra" 14 | ) 15 | 16 | func NewCmdView(f factory.Factory) *cobra.Command { 17 | flags := shared.NewViewFlags() 18 | cmd := &cobra.Command{ 19 | Args: usage.ExactArgs(1), 20 | Use: "view { | }", 21 | Short: "View a SSH worker", 22 | Long: "View a SSH worker in Octopus Deploy", 23 | Example: heredoc.Docf(` 24 | $ %[1]s worker ssh view 'linux-worker' 25 | $ %[1]s worker ssh view Machines-100 26 | `, constants.ExecutableName), 27 | RunE: func(c *cobra.Command, args []string) error { 28 | opts := shared.NewViewOptions(flags, cmd.NewDependencies(f, c), args) 29 | return ViewRun(opts) 30 | }, 31 | } 32 | 33 | machinescommon.RegisterWebFlag(cmd, flags.WebFlags) 34 | 35 | return cmd 36 | } 37 | 38 | func ViewRun(opts *shared.ViewOptions) error { 39 | return shared.ViewRun(opts, contributeEndpoint, "SSH") 40 | } 41 | 42 | func contributeEndpoint(opts *shared.ViewOptions, workerEndpoint machines.IEndpoint) ([]*output.DataRow, error) { 43 | data := []*output.DataRow{} 44 | endpoint := workerEndpoint.(*machines.SSHEndpoint) 45 | 46 | data = append(data, output.NewDataRow("URI", endpoint.URI.String())) 47 | data = append(data, output.NewDataRow("Runtime architecture", GetRuntimeArchitecture(endpoint))) 48 | accountRows, err := shared.ContributeAccount(opts, endpoint.AccountID) 49 | if err != nil { 50 | return nil, err 51 | } 52 | data = append(data, accountRows...) 53 | 54 | proxy, err := shared.ContributeProxy(opts, endpoint.ProxyID) 55 | data = append(data, proxy...) 56 | if err != nil { 57 | return nil, err 58 | } 59 | 60 | return data, nil 61 | } 62 | 63 | func GetRuntimeArchitecture(endpoint *machines.SSHEndpoint) string { 64 | if endpoint.DotNetCorePlatform == "" { 65 | return "Mono" 66 | } 67 | 68 | return endpoint.DotNetCorePlatform 69 | } 70 | -------------------------------------------------------------------------------- /pkg/cmd/worker/view/view.go: -------------------------------------------------------------------------------- 1 | package view 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/MakeNowJust/heredoc/v2" 7 | "github.com/OctopusDeploy/cli/pkg/cmd" 8 | listeningTentacle "github.com/OctopusDeploy/cli/pkg/cmd/worker/listening-tentacle/view" 9 | pollingTentacle "github.com/OctopusDeploy/cli/pkg/cmd/worker/polling-tentacle/view" 10 | "github.com/OctopusDeploy/cli/pkg/cmd/worker/shared" 11 | ssh "github.com/OctopusDeploy/cli/pkg/cmd/worker/ssh/view" 12 | "github.com/OctopusDeploy/cli/pkg/constants" 13 | "github.com/OctopusDeploy/cli/pkg/factory" 14 | "github.com/OctopusDeploy/cli/pkg/machinescommon" 15 | "github.com/OctopusDeploy/cli/pkg/usage" 16 | "github.com/spf13/cobra" 17 | ) 18 | 19 | func NewCmdView(f factory.Factory) *cobra.Command { 20 | flags := shared.NewViewFlags() 21 | cmd := &cobra.Command{ 22 | Args: usage.ExactArgs(1), 23 | Use: "view { | }", 24 | Short: "View a worker", 25 | Long: "View a worker in Octopus Deploy", 26 | Example: heredoc.Docf(` 27 | $ %[1]s worker view Machines-100 28 | $ %[1]s worker view 'worker' 29 | `, constants.ExecutableName), 30 | RunE: func(c *cobra.Command, args []string) error { 31 | return ViewRun(shared.NewViewOptions(flags, cmd.NewDependencies(f, c), args)) 32 | }, 33 | } 34 | 35 | machinescommon.RegisterWebFlag(cmd, flags.WebFlags) 36 | 37 | return cmd 38 | } 39 | 40 | func ViewRun(opts *shared.ViewOptions) error { 41 | var worker, err = opts.Client.Workers.GetByIdentifier(opts.IdOrName) 42 | if err != nil { 43 | return err 44 | } 45 | 46 | switch worker.Endpoint.GetCommunicationStyle() { 47 | case "TentaclePassive": 48 | return listeningTentacle.ViewRun(opts) 49 | case "TentacleActive": 50 | return pollingTentacle.ViewRun(opts) 51 | case "Ssh": 52 | return ssh.ViewRun(opts) 53 | } 54 | 55 | return fmt.Errorf("unsupported worker '%s'", worker.Endpoint.GetCommunicationStyle()) 56 | } 57 | -------------------------------------------------------------------------------- /pkg/cmd/worker/worker.go: -------------------------------------------------------------------------------- 1 | package worker 2 | 3 | import ( 4 | "github.com/MakeNowJust/heredoc/v2" 5 | cmdDelete "github.com/OctopusDeploy/cli/pkg/cmd/worker/delete" 6 | cmdList "github.com/OctopusDeploy/cli/pkg/cmd/worker/list" 7 | listeningTentacle "github.com/OctopusDeploy/cli/pkg/cmd/worker/listening-tentacle" 8 | pollingTentacle "github.com/OctopusDeploy/cli/pkg/cmd/worker/polling-tentacle" 9 | "github.com/OctopusDeploy/cli/pkg/cmd/worker/ssh" 10 | cmdView "github.com/OctopusDeploy/cli/pkg/cmd/worker/view" 11 | "github.com/OctopusDeploy/cli/pkg/constants" 12 | "github.com/OctopusDeploy/cli/pkg/constants/annotations" 13 | "github.com/OctopusDeploy/cli/pkg/factory" 14 | "github.com/spf13/cobra" 15 | ) 16 | 17 | func NewCmdWorker(f factory.Factory) *cobra.Command { 18 | cmd := &cobra.Command{ 19 | Use: "worker ", 20 | Short: "Manage workers", 21 | Long: "Manage workers in Octopus Deploy", 22 | Example: heredoc.Docf(` 23 | $ %[1]s worker list 24 | $ %[1]s worker ls 25 | `, constants.ExecutableName), 26 | Annotations: map[string]string{ 27 | annotations.IsCore: "true", 28 | }, 29 | } 30 | 31 | cmd.AddCommand(listeningTentacle.NewCmdListeningTentacle(f)) 32 | cmd.AddCommand(pollingTentacle.NewCmdPollingTentacle(f)) 33 | cmd.AddCommand(ssh.NewCmdSsh(f)) 34 | cmd.AddCommand(cmdList.NewCmdList(f)) 35 | cmd.AddCommand(cmdDelete.NewCmdDelete(f)) 36 | cmd.AddCommand(cmdView.NewCmdView(f)) 37 | 38 | return cmd 39 | } 40 | -------------------------------------------------------------------------------- /pkg/cmd/workerpool/dynamic/create/create_test.go: -------------------------------------------------------------------------------- 1 | package create_test 2 | 3 | import ( 4 | "github.com/OctopusDeploy/cli/pkg/cmd" 5 | "github.com/OctopusDeploy/cli/pkg/cmd/workerpool/dynamic/create" 6 | "github.com/OctopusDeploy/cli/test/testutil" 7 | "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/workerpools" 8 | "github.com/stretchr/testify/assert" 9 | "testing" 10 | ) 11 | 12 | func TestPromptMissing_FlagsSupplied(t *testing.T) { 13 | pa := []*testutil.PA{} 14 | 15 | asker, checkRemainingPrompts := testutil.NewMockAsker(t, pa) 16 | flags := create.NewCreateFlags() 17 | flags.Name.Value = "name" 18 | flags.Description.Value = "description" 19 | flags.Type.Value = "Ubuntu1804" 20 | 21 | opts := create.NewCreateOptions(flags, &cmd.Dependencies{Ask: asker}) 22 | 23 | err := create.PromptMissing(opts) 24 | checkRemainingPrompts() 25 | assert.NoError(t, err) 26 | } 27 | 28 | func TestPromptMissing_ShouldPrompt(t *testing.T) { 29 | pa := []*testutil.PA{ 30 | testutil.NewInputPrompt("Name", "A short, memorable, unique name for this Dynamic Worker Pool.", "name"), 31 | testutil.NewInputPrompt("Description", "A short, memorable, description for this Dynamic Worker Pool.", "description"), 32 | testutil.NewSelectPrompt("Select the worker type to use", "", []string{"Ubuntu (UbuntuDefault)", "Windows (WindowsDefault)"}, "Windows (WindowsDefault)"), 33 | } 34 | 35 | asker, checkRemainingPrompts := testutil.NewMockAsker(t, pa) 36 | flags := create.NewCreateFlags() 37 | opts := create.NewCreateOptions(flags, &cmd.Dependencies{Ask: asker}) 38 | opts.GetDynamicWorkerPoolTypes = func() ([]*workerpools.DynamicWorkerPoolType, error) { 39 | return []*workerpools.DynamicWorkerPoolType{ 40 | &workerpools.DynamicWorkerPoolType{ 41 | ID: "UbuntuDefault", 42 | Type: "UbuntuDefault", 43 | Description: "Ubuntu", 44 | }, 45 | &workerpools.DynamicWorkerPoolType{ 46 | ID: "WindowsDefault", 47 | Type: "WindowsDefault", 48 | Description: "Windows", 49 | }, 50 | }, nil 51 | } 52 | 53 | err := create.PromptMissing(opts) 54 | checkRemainingPrompts() 55 | assert.NoError(t, err) 56 | 57 | assert.Equal(t, "name", flags.Name.Value) 58 | assert.Equal(t, "description", flags.Description.Value) 59 | assert.Equal(t, "WindowsDefault", flags.Type.Value) 60 | } 61 | -------------------------------------------------------------------------------- /pkg/cmd/workerpool/dynamic/dynamic.go: -------------------------------------------------------------------------------- 1 | package dynamic 2 | 3 | import ( 4 | "github.com/MakeNowJust/heredoc/v2" 5 | cmdCreate "github.com/OctopusDeploy/cli/pkg/cmd/workerpool/dynamic/create" 6 | cmdView "github.com/OctopusDeploy/cli/pkg/cmd/workerpool/dynamic/view" 7 | "github.com/OctopusDeploy/cli/pkg/constants" 8 | "github.com/OctopusDeploy/cli/pkg/factory" 9 | "github.com/spf13/cobra" 10 | ) 11 | 12 | func NewCmdSsh(f factory.Factory) *cobra.Command { 13 | cmd := &cobra.Command{ 14 | Use: "dynamic ", 15 | Short: "Manage dynamic worker pools", 16 | Long: "Manage dynamic worker pools in Octopus Deploy", 17 | Example: heredoc.Docf("$ %s worker-pool dynamic view", constants.ExecutableName), 18 | } 19 | 20 | cmd.AddCommand(cmdCreate.NewCmdCreate(f)) 21 | cmd.AddCommand(cmdView.NewCmdView(f)) 22 | 23 | return cmd 24 | } 25 | -------------------------------------------------------------------------------- /pkg/cmd/workerpool/dynamic/view/view.go: -------------------------------------------------------------------------------- 1 | package view 2 | 3 | import ( 4 | "github.com/MakeNowJust/heredoc/v2" 5 | "github.com/OctopusDeploy/cli/pkg/cmd" 6 | "github.com/OctopusDeploy/cli/pkg/cmd/workerpool/shared" 7 | "github.com/OctopusDeploy/cli/pkg/constants" 8 | "github.com/OctopusDeploy/cli/pkg/factory" 9 | "github.com/OctopusDeploy/cli/pkg/machinescommon" 10 | "github.com/OctopusDeploy/cli/pkg/output" 11 | "github.com/OctopusDeploy/cli/pkg/usage" 12 | "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/workerpools" 13 | "github.com/spf13/cobra" 14 | ) 15 | 16 | func NewCmdView(f factory.Factory) *cobra.Command { 17 | flags := shared.NewViewFlags() 18 | cmd := &cobra.Command{ 19 | Args: usage.ExactArgs(1), 20 | Use: "view { | }", 21 | Short: "View a dynamic worker pool", 22 | Long: "View a dynamic worker pool in Octopus Deploy", 23 | Example: heredoc.Docf(` 24 | $ %[1]s worker-pool dynamic view WorkerPools-3 25 | $ %[1]s worker-pool dynamic view 'Hosted Workers' 26 | `, constants.ExecutableName), 27 | RunE: func(c *cobra.Command, args []string) error { 28 | return ViewRun(shared.NewViewOptions(flags, cmd.NewDependencies(f, c), args)) 29 | }, 30 | } 31 | 32 | machinescommon.RegisterWebFlag(cmd, flags.WebFlags) 33 | 34 | return cmd 35 | } 36 | 37 | func ViewRun(opts *shared.ViewOptions) error { 38 | return shared.ViewRun(opts, contributeDetails) 39 | } 40 | 41 | func contributeDetails(opts *shared.ViewOptions, workerPool workerpools.IWorkerPool) ([]*output.DataRow, error) { 42 | workerType := workerPool.(*workerpools.DynamicWorkerPool).WorkerType 43 | 44 | data := []*output.DataRow{} 45 | data = append(data, output.NewDataRow("Worker Type", string(workerType))) 46 | 47 | return data, nil 48 | 49 | } 50 | -------------------------------------------------------------------------------- /pkg/cmd/workerpool/list/list.go: -------------------------------------------------------------------------------- 1 | package list 2 | 3 | import ( 4 | "github.com/MakeNowJust/heredoc/v2" 5 | "github.com/OctopusDeploy/cli/pkg/cmd" 6 | "github.com/OctopusDeploy/cli/pkg/cmd/workerpool/shared" 7 | "github.com/OctopusDeploy/cli/pkg/constants" 8 | "github.com/OctopusDeploy/cli/pkg/factory" 9 | "github.com/OctopusDeploy/cli/pkg/output" 10 | "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/workerpools" 11 | "github.com/spf13/cobra" 12 | ) 13 | 14 | type ListOptions struct { 15 | *shared.GetWorkerPoolsOptions 16 | *cobra.Command 17 | *cmd.Dependencies 18 | } 19 | 20 | func NewListOptions(dependencies *cmd.Dependencies, command *cobra.Command) *ListOptions { 21 | return &ListOptions{ 22 | GetWorkerPoolsOptions: shared.NewGetWorkerPoolsOptions(dependencies), 23 | Command: command, 24 | Dependencies: dependencies, 25 | } 26 | } 27 | 28 | func NewCmdList(f factory.Factory) *cobra.Command { 29 | cmd := &cobra.Command{ 30 | Use: "list", 31 | Short: "List worker pools", 32 | Long: "List worker pools in Octopus Deploy", 33 | Aliases: []string{"ls"}, 34 | Example: heredoc.Docf("$ %s worker-pool list", constants.ExecutableName), 35 | RunE: func(c *cobra.Command, args []string) error { 36 | return ListRun(NewListOptions(cmd.NewDependencies(f, c), c)) 37 | }, 38 | } 39 | 40 | return cmd 41 | } 42 | 43 | func ListRun(opts *ListOptions) error { 44 | allPools, err := opts.GetWorkerPoolsCallback() 45 | if err != nil { 46 | return err 47 | } 48 | 49 | type TargetAsJson struct { 50 | Id string `json:"Id"` 51 | Name string `json:"Name"` 52 | Type string `json:"Type"` 53 | Slug string `json:"Slug"` 54 | } 55 | 56 | return output.PrintArray(allPools, opts.Command, output.Mappers[*workerpools.WorkerPoolListResult]{ 57 | Json: func(item *workerpools.WorkerPoolListResult) any { 58 | 59 | return TargetAsJson{ 60 | Id: item.ID, 61 | Name: item.Name, 62 | Type: string(item.WorkerPoolType), 63 | Slug: item.Slug, 64 | } 65 | }, 66 | Table: output.TableDefinition[*workerpools.WorkerPoolListResult]{ 67 | Header: []string{"NAME", "TYPE", "SLUG"}, 68 | Row: func(item *workerpools.WorkerPoolListResult) []string { 69 | return []string{output.Bold(item.Name), string(item.WorkerPoolType), output.Dim(item.Slug)} 70 | }, 71 | }, 72 | Basic: func(item *workerpools.WorkerPoolListResult) string { 73 | return item.Name 74 | }, 75 | }) 76 | } 77 | -------------------------------------------------------------------------------- /pkg/cmd/workerpool/shared/view.go: -------------------------------------------------------------------------------- 1 | package shared 2 | 3 | import ( 4 | "fmt" 5 | "github.com/OctopusDeploy/cli/pkg/cmd" 6 | "github.com/OctopusDeploy/cli/pkg/machinescommon" 7 | "github.com/OctopusDeploy/cli/pkg/output" 8 | "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/workerpools" 9 | ) 10 | 11 | type ContributeDetailsCallback func(opts *ViewOptions, workerPool workerpools.IWorkerPool) ([]*output.DataRow, error) 12 | 13 | type ViewFlags struct { 14 | *machinescommon.WebFlags 15 | } 16 | 17 | type ViewOptions struct { 18 | *cmd.Dependencies 19 | IdOrName string 20 | *ViewFlags 21 | } 22 | 23 | func NewViewFlags() *ViewFlags { 24 | return &ViewFlags{ 25 | WebFlags: machinescommon.NewWebFlags(), 26 | } 27 | } 28 | 29 | func NewViewOptions(viewFlags *ViewFlags, dependencies *cmd.Dependencies, args []string) *ViewOptions { 30 | return &ViewOptions{ 31 | ViewFlags: viewFlags, 32 | Dependencies: dependencies, 33 | IdOrName: args[0], 34 | } 35 | } 36 | 37 | func ViewRun(opts *ViewOptions, contributeDetails ContributeDetailsCallback) error { 38 | var workerPool, err = opts.Client.WorkerPools.GetByIdentifier(opts.IdOrName) 39 | if err != nil { 40 | return err 41 | } 42 | 43 | data := []*output.DataRow{} 44 | 45 | data = append(data, output.NewDataRow("Name", fmt.Sprintf("%s %s", output.Bold(workerPool.GetName()), output.Dimf("(%s)", workerPool.GetID())))) 46 | data = append(data, output.NewDataRow("Worker Pool Type", getWorkerPoolTypeDescription(workerPool.GetWorkerPoolType()))) 47 | if workerPool.GetIsDefault() { 48 | data = append(data, output.NewDataRow("Default", output.Green("Yes"))) 49 | } 50 | 51 | if contributeDetails != nil { 52 | newRows, err := contributeDetails(opts, workerPool) 53 | if err != nil { 54 | return err 55 | } 56 | for _, r := range newRows { 57 | data = append(data, r) 58 | } 59 | } 60 | 61 | t := output.NewTable(opts.Out) 62 | for _, row := range data { 63 | t.AddRow(row.Name, row.Value) 64 | } 65 | t.Print() 66 | 67 | fmt.Fprintf(opts.Out, "\n") 68 | machinescommon.DoWebForWorkerPools(workerPool, opts.Dependencies, opts.WebFlags) 69 | return nil 70 | 71 | return nil 72 | } 73 | 74 | func getWorkerPoolTypeDescription(poolType workerpools.WorkerPoolType) string { 75 | if poolType == workerpools.WorkerPoolTypeDynamic { 76 | return "Dynamic" 77 | } 78 | 79 | return "Static" 80 | } 81 | -------------------------------------------------------------------------------- /pkg/cmd/workerpool/shared/workerpool.go: -------------------------------------------------------------------------------- 1 | package shared 2 | 3 | import ( 4 | "github.com/OctopusDeploy/cli/pkg/cmd" 5 | "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/client" 6 | "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/workerpools" 7 | ) 8 | 9 | type GetWorkerPoolsCallback func() ([]*workerpools.WorkerPoolListResult, error) 10 | type GetWorkerPoolCallback func(identifer string) (workerpools.IWorkerPool, error) 11 | 12 | type GetWorkerPoolsOptions struct { 13 | GetWorkerPoolsCallback 14 | GetWorkerPoolCallback 15 | } 16 | 17 | func NewGetWorkerPoolsOptions(dependencies *cmd.Dependencies) *GetWorkerPoolsOptions { 18 | return &GetWorkerPoolsOptions{ 19 | GetWorkerPoolsCallback: func() ([]*workerpools.WorkerPoolListResult, error) { 20 | return GetWorkerPools(*dependencies.Client) 21 | }, 22 | GetWorkerPoolCallback: func(identifier string) (workerpools.IWorkerPool, error) { 23 | return GetWorker(*dependencies.Client, identifier) 24 | }, 25 | } 26 | } 27 | 28 | func GetWorkerPools(client client.Client) ([]*workerpools.WorkerPoolListResult, error) { 29 | allWorkerPools, err := client.WorkerPools.GetAll() 30 | if err != nil { 31 | return nil, err 32 | } 33 | 34 | return allWorkerPools, nil 35 | } 36 | 37 | func GetWorker(client client.Client, identifier string) (workerpools.IWorkerPool, error) { 38 | workerPool, err := client.WorkerPools.GetByIdentifier(identifier) 39 | if err != nil { 40 | return nil, err 41 | } 42 | 43 | return workerPool, nil 44 | } 45 | -------------------------------------------------------------------------------- /pkg/cmd/workerpool/static/create/create_test.go: -------------------------------------------------------------------------------- 1 | package create_test 2 | 3 | import ( 4 | "github.com/OctopusDeploy/cli/pkg/cmd" 5 | "github.com/OctopusDeploy/cli/pkg/cmd/workerpool/static/create" 6 | "github.com/OctopusDeploy/cli/test/testutil" 7 | "github.com/stretchr/testify/assert" 8 | "testing" 9 | ) 10 | 11 | func TestPromptMissing_FlagsSupplied(t *testing.T) { 12 | pa := []*testutil.PA{} 13 | 14 | asker, checkRemainingPrompts := testutil.NewMockAsker(t, pa) 15 | flags := create.NewCreateFlags() 16 | flags.Name.Value = "name" 17 | flags.Description.Value = "description" 18 | 19 | opts := create.NewCreateOptions(flags, &cmd.Dependencies{Ask: asker}) 20 | 21 | err := create.PromptMissing(opts) 22 | checkRemainingPrompts() 23 | assert.NoError(t, err) 24 | } 25 | 26 | func TestPromptMissing_ShouldPrompt(t *testing.T) { 27 | pa := []*testutil.PA{ 28 | testutil.NewInputPrompt("Name", "A short, memorable, unique name for this Static Worker Pool.", "name"), 29 | testutil.NewInputPrompt("Description", "A short, memorable, description for this Static Worker Pool.", "description"), 30 | } 31 | 32 | asker, checkRemainingPrompts := testutil.NewMockAsker(t, pa) 33 | flags := create.NewCreateFlags() 34 | opts := create.NewCreateOptions(flags, &cmd.Dependencies{Ask: asker}) 35 | 36 | err := create.PromptMissing(opts) 37 | checkRemainingPrompts() 38 | assert.NoError(t, err) 39 | 40 | assert.Equal(t, "name", flags.Name.Value) 41 | assert.Equal(t, "description", flags.Description.Value) 42 | } 43 | -------------------------------------------------------------------------------- /pkg/cmd/workerpool/static/static.go: -------------------------------------------------------------------------------- 1 | package static 2 | 3 | import ( 4 | "github.com/MakeNowJust/heredoc/v2" 5 | cmdCreate "github.com/OctopusDeploy/cli/pkg/cmd/workerpool/static/create" 6 | cmdView "github.com/OctopusDeploy/cli/pkg/cmd/workerpool/static/view" 7 | "github.com/OctopusDeploy/cli/pkg/constants" 8 | "github.com/OctopusDeploy/cli/pkg/factory" 9 | "github.com/spf13/cobra" 10 | ) 11 | 12 | func NewCmdStatic(f factory.Factory) *cobra.Command { 13 | cmd := &cobra.Command{ 14 | Use: "static ", 15 | Short: "Manage static worker pools", 16 | Long: "Manage static worker pools in Octopus Deploy", 17 | Example: heredoc.Docf("$ %s worker-pool static view", constants.ExecutableName), 18 | } 19 | 20 | cmd.AddCommand(cmdView.NewCmdView(f)) 21 | cmd.AddCommand(cmdCreate.NewCmdCreate(f)) 22 | 23 | return cmd 24 | } 25 | -------------------------------------------------------------------------------- /pkg/cmd/workerpool/view/view.go: -------------------------------------------------------------------------------- 1 | package view 2 | 3 | import ( 4 | "fmt" 5 | dynamicPool "github.com/OctopusDeploy/cli/pkg/cmd/workerpool/dynamic/view" 6 | "github.com/OctopusDeploy/cli/pkg/cmd/workerpool/shared" 7 | staticPool "github.com/OctopusDeploy/cli/pkg/cmd/workerpool/static/view" 8 | "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/workerpools" 9 | 10 | "github.com/MakeNowJust/heredoc/v2" 11 | "github.com/OctopusDeploy/cli/pkg/cmd" 12 | "github.com/OctopusDeploy/cli/pkg/constants" 13 | "github.com/OctopusDeploy/cli/pkg/factory" 14 | "github.com/OctopusDeploy/cli/pkg/machinescommon" 15 | "github.com/OctopusDeploy/cli/pkg/usage" 16 | "github.com/spf13/cobra" 17 | ) 18 | 19 | func NewCmdView(f factory.Factory) *cobra.Command { 20 | flags := shared.NewViewFlags() 21 | cmd := &cobra.Command{ 22 | Args: usage.ExactArgs(1), 23 | Use: "view { | }", 24 | Short: "View a worker pool", 25 | Long: "View a worker pool in Octopus Deploy", 26 | Example: heredoc.Docf(` 27 | $ %[1]s worker-pool view WorkerPools-3 28 | $ %[1]s worker-pool view 'linux workers' 29 | `, constants.ExecutableName), 30 | RunE: func(c *cobra.Command, args []string) error { 31 | return ViewRun(shared.NewViewOptions(flags, cmd.NewDependencies(f, c), args)) 32 | }, 33 | } 34 | 35 | machinescommon.RegisterWebFlag(cmd, flags.WebFlags) 36 | 37 | return cmd 38 | } 39 | 40 | func ViewRun(opts *shared.ViewOptions) error { 41 | var workerPool, err = opts.Client.WorkerPools.GetByIdentifier(opts.IdOrName) 42 | if err != nil { 43 | return err 44 | } 45 | 46 | switch workerPool.GetWorkerPoolType() { 47 | case workerpools.WorkerPoolTypeDynamic: 48 | return dynamicPool.ViewRun(opts) 49 | case workerpools.WorkerPoolTypeStatic: 50 | return staticPool.ViewRun(opts) 51 | } 52 | 53 | return fmt.Errorf("unsupported worker pool '%s'", workerPool.GetWorkerPoolType()) 54 | } 55 | -------------------------------------------------------------------------------- /pkg/cmd/workerpool/workerpool.go: -------------------------------------------------------------------------------- 1 | package workerpool 2 | 3 | import ( 4 | "github.com/MakeNowJust/heredoc/v2" 5 | deleteCmd "github.com/OctopusDeploy/cli/pkg/cmd/workerpool/delete" 6 | dynamicCmd "github.com/OctopusDeploy/cli/pkg/cmd/workerpool/dynamic" 7 | listCmd "github.com/OctopusDeploy/cli/pkg/cmd/workerpool/list" 8 | staticCmd "github.com/OctopusDeploy/cli/pkg/cmd/workerpool/static" 9 | viewCmd "github.com/OctopusDeploy/cli/pkg/cmd/workerpool/view" 10 | "github.com/OctopusDeploy/cli/pkg/constants" 11 | "github.com/OctopusDeploy/cli/pkg/constants/annotations" 12 | "github.com/OctopusDeploy/cli/pkg/factory" 13 | "github.com/spf13/cobra" 14 | ) 15 | 16 | func NewCmdWorkerPool(f factory.Factory) *cobra.Command { 17 | cmd := &cobra.Command{ 18 | Use: "worker-pool ", 19 | Short: "Manage worker pools", 20 | Long: "Manage worker pools in Octopus Deploy", 21 | Example: heredoc.Docf(` 22 | $ %[1]s worker-pool list 23 | $ %[1]s worker-pool ls 24 | `, constants.ExecutableName), 25 | Annotations: map[string]string{ 26 | annotations.IsCore: "true", 27 | }, 28 | } 29 | 30 | cmd.AddCommand(deleteCmd.NewCmdDelete(f)) 31 | cmd.AddCommand(listCmd.NewCmdList(f)) 32 | cmd.AddCommand(viewCmd.NewCmdView(f)) 33 | cmd.AddCommand(staticCmd.NewCmdStatic(f)) 34 | cmd.AddCommand(dynamicCmd.NewCmdSsh(f)) 35 | 36 | return cmd 37 | } 38 | -------------------------------------------------------------------------------- /pkg/config/provider.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "github.com/spf13/viper" 8 | ) 9 | 10 | type IConfigProvider interface { 11 | Get(key string) string 12 | Set(key string, value string) error 13 | } 14 | 15 | type FileConfigProvider struct { 16 | viper *viper.Viper 17 | IConfigProvider 18 | } 19 | 20 | func New(viper *viper.Viper) IConfigProvider { 21 | return &FileConfigProvider{ 22 | viper: viper, 23 | } 24 | } 25 | 26 | func (accessToken *FileConfigProvider) Get(key string) string { 27 | return viper.GetString(key) 28 | } 29 | 30 | func (accessToken *FileConfigProvider) Set(key string, value string) error { 31 | // have to make new viper so it only contains file value, no ENVs or Flags 32 | configPath, err := EnsureConfigPath() 33 | if err != nil { 34 | return err 35 | } 36 | 37 | localViper := viper.New() 38 | SetupConfigFile(localViper, configPath) 39 | 40 | if err := localViper.ReadInConfig(); err != nil { 41 | if _, ok := err.(viper.ConfigFileNotFoundError); ok { 42 | // config file not found, we create it here and recover 43 | if err = localViper.SafeWriteConfig(); err != nil { 44 | return err 45 | } 46 | } else { 47 | return err // any other error is unrecoverable; abort 48 | } 49 | } 50 | if key != "" && !IsValidKey(key) { 51 | return fmt.Errorf("the key '%s' is not a valid", key) 52 | } 53 | key = strings.ToLower(key) 54 | localViper.Set(key, value) 55 | if err := localViper.WriteConfig(); err != nil { 56 | return err 57 | } 58 | return nil 59 | } 60 | -------------------------------------------------------------------------------- /pkg/constants/annotations/annotations.go: -------------------------------------------------------------------------------- 1 | package annotations 2 | 3 | const ( 4 | IsCore = "IsCore" 5 | IsConfiguration = "IsConfiguration" 6 | IsLibrary = "IsLibrary" 7 | IsInfrastructure = "IsInfrastructure" 8 | ) 9 | -------------------------------------------------------------------------------- /pkg/errors/errors.go: -------------------------------------------------------------------------------- 1 | package errors 2 | 3 | import "fmt" 4 | 5 | // OsEnvironmentError is raised when the CLI cannot launch because a required environment variable is not set 6 | type OsEnvironmentError struct{ EnvironmentVariable string } 7 | 8 | func (e *OsEnvironmentError) Error() string { 9 | return fmt.Sprintf("%s environment variable is missing or blank", e.EnvironmentVariable) 10 | } 11 | 12 | // PromptDisabledError is a fallback error if code attempts to prompt the user when prompting is disabled. 13 | // If you see it, it represents a bug because Commands should check IsInteractive before attempting to prompt 14 | type PromptDisabledError struct{} 15 | 16 | func (e *PromptDisabledError) Error() string { 17 | return "prompt disabled" 18 | } 19 | 20 | // ArgumentNullOrEmptyError is a utility error indicating that a required parameter was 21 | // null or blank. This is not a recoverable error; if you observe one this indicates a bug in the code. 22 | type ArgumentNullOrEmptyError struct{ ArgumentName string } 23 | 24 | func (e *ArgumentNullOrEmptyError) Error() string { 25 | return fmt.Sprintf("argument %s was nil or empty", e.ArgumentName) 26 | } 27 | func NewArgumentNullOrEmptyError(argumentName string) *ArgumentNullOrEmptyError { 28 | return &ArgumentNullOrEmptyError{ArgumentName: argumentName} 29 | } 30 | 31 | // InvalidResponseError is a utility error that means the CLI couldn't deal with a response from the server. 32 | // this may represent a bug (missing code path) in the CLI, a bug in the server (wrong response), or a change in server behaviour over time. 33 | type InvalidResponseError struct{ Message string } 34 | 35 | func (e *InvalidResponseError) Error() string { return e.Message } 36 | func NewInvalidResponseError(message string) *InvalidResponseError { 37 | return &InvalidResponseError{Message: message} 38 | } 39 | -------------------------------------------------------------------------------- /pkg/executor/executor.go: -------------------------------------------------------------------------------- 1 | package executor 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/client" 7 | "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/spaces" 8 | ) 9 | 10 | // task type definitions 11 | type TaskType string 12 | 13 | const ( 14 | TaskTypeCreateAccount = TaskType("CreateAccount") 15 | TaskTypeCreateRelease = TaskType("CreateRelease") 16 | TaskTypeDeployRelease = TaskType("DeployRelease") 17 | TaskTypeRunbookRun = TaskType("RunbookRun") 18 | TaskTypeGitRunbookRun = TaskType("GitRunbookRun") 19 | ) 20 | 21 | type Task struct { 22 | // which type of task this is. 23 | Type TaskType 24 | 25 | // task-specific payload (usually a struct containing the data required for this task) 26 | // rememmber pass this as a pointer 27 | Options any 28 | } 29 | 30 | func NewTask(taskType TaskType, options any) *Task { 31 | return &Task{ 32 | Type: taskType, 33 | Options: options, 34 | } 35 | } 36 | 37 | // ProcessTasks iterates over the list of tasks and attempts to run them all. 38 | // If everything goes well, a nil error will be returned. 39 | // On the first failure, the error will be returned and the process will halt. 40 | // TODO some kind of progress/results callback? A Goroutine with channels? 41 | func ProcessTasks(octopus *client.Client, space *spaces.Space, tasks []*Task) error { 42 | for _, task := range tasks { 43 | switch task.Type { 44 | case TaskTypeCreateAccount: 45 | if err := accountCreate(octopus, space, task.Options); err != nil { 46 | return err 47 | } 48 | case TaskTypeCreateRelease: 49 | if err := releaseCreate(octopus, space, task.Options); err != nil { 50 | return err 51 | } 52 | case TaskTypeDeployRelease: 53 | if err := releaseDeploy(octopus, space, task.Options); err != nil { 54 | return err 55 | } 56 | case TaskTypeRunbookRun: 57 | if err := runbookRun(octopus, space, task.Options); err != nil { 58 | return err 59 | } 60 | case TaskTypeGitRunbookRun: 61 | if err := gitRunbookRun(octopus, space, task.Options); err != nil { 62 | return err 63 | } 64 | default: 65 | return fmt.Errorf("unhandled task CommandType %s", task.Type) 66 | } 67 | } 68 | return nil 69 | } 70 | -------------------------------------------------------------------------------- /pkg/machinescommon/comms.go: -------------------------------------------------------------------------------- 1 | package machinescommon 2 | 3 | var CommunicationStyleToDescriptionMap = map[string]string{ 4 | "TentaclePassive": "Listening Tentacle", 5 | "TentacleActive": "Polling Tentacle", 6 | "Ssh": "SSH Connection", 7 | "OfflineDrop": "Offline Package Drop", 8 | "AzureWebApp": "Azure Web App", 9 | "AzureCloudService": "Azure Cloud Service", 10 | "AzureServiceFabricCluster": "Service Fabric Cluster", 11 | "Kubernetes": "Kubernetes Cluster", 12 | "None": "Cloud Region", 13 | "StepPackage": "Step Package", 14 | } 15 | 16 | var CommunicationStyleToDeploymentTargetTypeMap = map[string]string{ 17 | "TentaclePassive": "TentaclePassive", 18 | "TentacleActive": "TentacleActive", 19 | "Ssh": "Ssh", 20 | "OfflineDrop": "OfflineDrop", 21 | "AzureWebApp": "AzureWebApp", 22 | "AzureCloudService": "AzureCloudService", 23 | "AzureServiceFabricCluster": "AzureServiceFabricCluster", 24 | "Kubernetes": "Kubernetes", 25 | "None": "CloudRegion", 26 | } 27 | -------------------------------------------------------------------------------- /pkg/machinescommon/machinepolicy_test.go: -------------------------------------------------------------------------------- 1 | package machinescommon_test 2 | 3 | import ( 4 | "github.com/OctopusDeploy/cli/pkg/cmd" 5 | "github.com/OctopusDeploy/cli/pkg/machinescommon" 6 | "github.com/OctopusDeploy/cli/test/testutil" 7 | "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/machines" 8 | "github.com/stretchr/testify/assert" 9 | "testing" 10 | ) 11 | 12 | func TestMachinePolicyFlagSupplied_ShouldNotPrompt(t *testing.T) { 13 | pa := []*testutil.PA{} 14 | 15 | asker, checkRemainingPrompts := testutil.NewMockAsker(t, pa) 16 | flags := machinescommon.NewCreateTargetMachinePolicyFlags() 17 | flags.MachinePolicy.Value = "MachinePolicy-1" 18 | 19 | opts := machinescommon.NewCreateTargetMachinePolicyOptions(&cmd.Dependencies{Ask: asker}) 20 | 21 | err := machinescommon.PromptForMachinePolicy(opts, flags) 22 | checkRemainingPrompts() 23 | 24 | assert.NoError(t, err) 25 | } 26 | 27 | func TestNoFlag_ShouldPrompt(t *testing.T) { 28 | pa := []*testutil.PA{ 29 | testutil.NewSelectPrompt("Select the machine policy to use", "", []string{"Policy 1", "Policy 2"}, "Policy 2"), 30 | } 31 | 32 | asker, checkRemainingPrompts := testutil.NewMockAsker(t, pa) 33 | flags := machinescommon.NewCreateTargetMachinePolicyFlags() 34 | opts := machinescommon.NewCreateTargetMachinePolicyOptions(&cmd.Dependencies{Ask: asker}) 35 | opts.GetAllMachinePoliciesCallback = func() ([]*machines.MachinePolicy, error) { 36 | return []*machines.MachinePolicy{ 37 | machines.NewMachinePolicy("Policy 1"), 38 | machines.NewMachinePolicy("Policy 2"), 39 | }, nil 40 | } 41 | 42 | err := machinescommon.PromptForMachinePolicy(opts, flags) 43 | checkRemainingPrompts() 44 | assert.NoError(t, err) 45 | assert.Equal(t, "Policy 2", flags.MachinePolicy.Value) 46 | } 47 | -------------------------------------------------------------------------------- /pkg/machinescommon/web.go: -------------------------------------------------------------------------------- 1 | package machinescommon 2 | 3 | import ( 4 | "fmt" 5 | "github.com/OctopusDeploy/cli/pkg/cmd" 6 | "github.com/OctopusDeploy/cli/pkg/output" 7 | "github.com/OctopusDeploy/cli/pkg/util/flag" 8 | "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/machines" 9 | "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/workerpools" 10 | "github.com/pkg/browser" 11 | "github.com/spf13/cobra" 12 | "io" 13 | ) 14 | 15 | const ( 16 | FlagWeb = "web" 17 | ) 18 | 19 | type WebFlags struct { 20 | Web *flag.Flag[bool] 21 | } 22 | 23 | func NewWebFlags() *WebFlags { 24 | return &WebFlags{ 25 | Web: flag.New[bool](FlagWeb, false), 26 | } 27 | } 28 | 29 | func RegisterWebFlag(cmd *cobra.Command, flags *WebFlags) { 30 | cmd.Flags().BoolVarP(&flags.Web.Value, flags.Web.Name, "w", false, "Open in web browser") 31 | } 32 | 33 | func DoWebForTargets(target *machines.DeploymentTarget, dependencies *cmd.Dependencies, flags *WebFlags, description string) { 34 | url := fmt.Sprintf("%s/app#/%s/infrastructure/machines/%s/settings", dependencies.Host, dependencies.Space.GetID(), target.GetID()) 35 | doWeb(url, description, dependencies.Out, flags) 36 | } 37 | 38 | func DoWebForWorkers(worker *machines.Worker, dependencies *cmd.Dependencies, flags *WebFlags, description string) { 39 | url := fmt.Sprintf("%s/app#/%s/infrastructure/workers/%s/settings", dependencies.Host, dependencies.Space.GetID(), worker.GetID()) 40 | doWeb(url, description, dependencies.Out, flags) 41 | } 42 | 43 | func DoWebForWorkerPools(workerPool workerpools.IWorkerPool, dependencies *cmd.Dependencies, flags *WebFlags) { 44 | url := fmt.Sprintf("%s/app#/%s/infrastructure/workerpools/%s/settings", dependencies.Host, dependencies.Space.GetID(), workerPool.GetID()) 45 | doWeb(url, "Worker Pool", dependencies.Out, flags) 46 | } 47 | 48 | func doWeb(url string, description string, out io.Writer, flags *WebFlags) { 49 | link := output.Bluef(url) 50 | fmt.Fprintf(out, "View this %s on Octopus Deploy: %s\n", description, link) 51 | if flags.Web.Value { 52 | browser.OpenURL(url) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /pkg/output/format.go: -------------------------------------------------------------------------------- 1 | package output 2 | 3 | import "strings" 4 | 5 | func FormatAsList(items []string) string { 6 | return strings.Join(items, ", ") 7 | } 8 | -------------------------------------------------------------------------------- /pkg/output/print_array.go: -------------------------------------------------------------------------------- 1 | package output 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "fmt" 7 | "strings" 8 | 9 | "github.com/OctopusDeploy/cli/pkg/constants" 10 | 11 | "github.com/OctopusDeploy/cli/pkg/usage" 12 | 13 | "github.com/spf13/cobra" 14 | "github.com/spf13/viper" 15 | ) 16 | 17 | func PrintArray[T any](items []T, cmd *cobra.Command, mappers Mappers[T]) error { 18 | outputFormat, _ := cmd.Flags().GetString(constants.FlagOutputFormat) 19 | if outputFormat == "" { 20 | outputFormat = viper.GetString(constants.ConfigOutputFormat) 21 | } 22 | 23 | switch strings.ToLower(outputFormat) { 24 | case constants.OutputFormatJson: 25 | jsonMapper := mappers.Json 26 | if jsonMapper == nil { 27 | return errors.New("command does not support output in JSON format") 28 | } 29 | var outputJson []any 30 | for _, e := range items { 31 | outputJson = append(outputJson, jsonMapper(e)) 32 | } 33 | 34 | data, _ := json.MarshalIndent(outputJson, "", " ") 35 | cmd.Println(string(data)) 36 | 37 | case constants.OutputFormatBasic: 38 | textMapper := mappers.Basic 39 | if textMapper == nil { 40 | return errors.New("command does not support output in plain text") 41 | } 42 | for _, e := range items { 43 | cmd.Println(textMapper(e)) 44 | } 45 | 46 | case constants.OutputFormatTable, "": // table is the default of unspecified 47 | tableMapper := mappers.Table 48 | if tableMapper.Row == nil { 49 | return errors.New("command does not support output in table format") 50 | } 51 | 52 | t := NewTable(cmd.OutOrStdout()) 53 | if tableMapper.Header != nil { 54 | for k, v := range tableMapper.Header { 55 | tableMapper.Header[k] = Bold(v) 56 | } 57 | t.AddRow(tableMapper.Header...) 58 | } 59 | 60 | for _, item := range items { 61 | t.AddRow(tableMapper.Row(item)...) 62 | } 63 | 64 | return t.Print() 65 | 66 | default: 67 | return usage.NewUsageError( 68 | fmt.Sprintf("unsupported output format %s. Valid values are 'json', 'table', 'basic'. Defaults to table", outputFormat), 69 | cmd) 70 | } 71 | return nil 72 | } 73 | -------------------------------------------------------------------------------- /pkg/output/print_resource.go: -------------------------------------------------------------------------------- 1 | package output 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "fmt" 7 | "strings" 8 | 9 | "github.com/OctopusDeploy/cli/pkg/constants" 10 | 11 | "github.com/OctopusDeploy/cli/pkg/usage" 12 | 13 | "github.com/spf13/cobra" 14 | "github.com/spf13/viper" 15 | ) 16 | 17 | func PrintResource[T any](item T, cmd *cobra.Command, mappers Mappers[T]) error { 18 | outputFormat, _ := cmd.Flags().GetString(constants.FlagOutputFormat) 19 | if outputFormat == "" { 20 | outputFormat = viper.GetString(constants.ConfigOutputFormat) 21 | } 22 | 23 | switch strings.ToLower(outputFormat) { 24 | case constants.OutputFormatJson: 25 | jsonMapper := mappers.Json 26 | if jsonMapper == nil { 27 | return errors.New("command does not support output in JSON format") 28 | } 29 | var outputJson any 30 | outputJson = jsonMapper(item) 31 | 32 | data, _ := json.MarshalIndent(outputJson, "", " ") 33 | cmd.Println(string(data)) 34 | 35 | case constants.OutputFormatBasic: 36 | textMapper := mappers.Basic 37 | if textMapper == nil { 38 | return errors.New("command does not support output in plain text") 39 | } 40 | cmd.Println(textMapper(item)) 41 | 42 | case constants.OutputFormatTable, "": // table is the default of unspecified 43 | tableMapper := mappers.Table 44 | if tableMapper.Row == nil { 45 | return errors.New("command does not support output in table format") 46 | } 47 | 48 | t := NewTable(cmd.OutOrStdout()) 49 | if tableMapper.Header != nil { 50 | for k, v := range tableMapper.Header { 51 | tableMapper.Header[k] = Bold(v) 52 | } 53 | t.AddRow(tableMapper.Header...) 54 | } 55 | 56 | t.AddRow(tableMapper.Row(item)...) 57 | 58 | return t.Print() 59 | 60 | default: 61 | return usage.NewUsageError( 62 | fmt.Sprintf("unsupported output format %s. Valid values are 'json', 'table', 'basic'. Defaults to table", outputFormat), 63 | cmd) 64 | } 65 | return nil 66 | } 67 | -------------------------------------------------------------------------------- /pkg/output/shared.go: -------------------------------------------------------------------------------- 1 | package output 2 | 3 | // Common struct used for rendering JSON summaries of things that just have an ID and a Name 4 | type IdAndName struct { 5 | Id string `json:"Id"` 6 | Name string `json:"Name"` 7 | } 8 | 9 | type TableDefinition[T any] struct { 10 | Header []string 11 | Row func(item T) []string 12 | } 13 | 14 | // carries conversion functions used by PrintArray and potentially other output code in future 15 | type Mappers[T any] struct { 16 | // A function which will convert T into an output structure suitable for json.Marshal (e.g. IdAndName). 17 | // If you leave this as nil, then the command will simply not support output as JSON and will 18 | // fail if someone asks for it 19 | Json func(item T) any 20 | 21 | // A function which will convert T into ?? suitable for table printing 22 | // If you leave this as nil, then the command will simply not support output as 23 | // a table and will fail if someone asks for it 24 | Table TableDefinition[T] 25 | 26 | // A function which will convert T into a string suitable for basic text display 27 | // If you leave this as nil, then the command will simply not support output as basic text and will 28 | // fail if someone asks for it 29 | Basic func(item T) string 30 | 31 | // NOTE: We might have some kinds of entities where table formatting doesn't make sense, and we want to 32 | // render those as basic text instead. This seems unlikely though, defer it until the issue comes up. 33 | 34 | // NOTE: The structure for printing tables would also work for CSV... perhaps we can have --outputFormat=csv for free? 35 | } 36 | -------------------------------------------------------------------------------- /pkg/output/truncate.go: -------------------------------------------------------------------------------- 1 | package output 2 | 3 | import ( 4 | "github.com/muesli/reflow/ansi" 5 | "github.com/muesli/reflow/truncate" 6 | ) 7 | 8 | const ( 9 | ellipsis = "..." 10 | minWidthForEllipsis = len(ellipsis) + 2 11 | ) 12 | 13 | func Truncate(maxWidth int, s string) string { 14 | w := ansi.PrintableRuneWidth(s) 15 | if w <= maxWidth { 16 | return s 17 | } 18 | 19 | tail := "" 20 | if maxWidth >= minWidthForEllipsis { 21 | tail = ellipsis 22 | } 23 | 24 | ts := truncate.StringWithTail(s, uint(maxWidth), tail) 25 | if ansi.PrintableRuneWidth(ts) < maxWidth { 26 | ts += " " 27 | } 28 | 29 | return ts 30 | } 31 | -------------------------------------------------------------------------------- /pkg/question/ask.go: -------------------------------------------------------------------------------- 1 | package question 2 | 3 | import ( 4 | "github.com/AlecAivazis/survey/v2" 5 | cliErrors "github.com/OctopusDeploy/cli/pkg/errors" 6 | ) 7 | 8 | type Asker func(p survey.Prompt, response interface{}, opts ...survey.AskOpt) error 9 | 10 | // Both the ClientFactory and the main Factory need to reference survey to ask questions, 11 | // but we need a single place to hold the reference to survey so we can switch it off for 12 | // automation mode. This wrapper fills that gap. 13 | 14 | type AskProvider interface { 15 | IsInteractive() bool 16 | DisableInteractive() 17 | Ask(p survey.Prompt, response interface{}, opts ...survey.AskOpt) error 18 | } 19 | 20 | type askWrapper struct { 21 | asker Asker 22 | } 23 | 24 | func NewAskProvider(asker Asker) AskProvider { 25 | return &askWrapper{ 26 | asker: asker, 27 | } 28 | } 29 | 30 | func (a *askWrapper) IsInteractive() bool { 31 | return a.asker != nil 32 | } 33 | 34 | func (a *askWrapper) DisableInteractive() { 35 | a.asker = nil 36 | } 37 | 38 | func (a *askWrapper) Ask(p survey.Prompt, response interface{}, opts ...survey.AskOpt) error { 39 | if a.asker != nil { 40 | return a.asker(p, response, opts...) 41 | } else { 42 | // this shouldn't happen; commands should check IsInteractive before attempting to prompt 43 | return &cliErrors.PromptDisabledError{} 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /pkg/question/helpers_test.go: -------------------------------------------------------------------------------- 1 | package question_test 2 | 3 | -------------------------------------------------------------------------------- /pkg/question/select_test.go: -------------------------------------------------------------------------------- 1 | package question_test 2 | 3 | import ( 4 | "github.com/OctopusDeploy/cli/pkg/question" 5 | "github.com/OctopusDeploy/cli/test/testutil" 6 | "github.com/stretchr/testify/assert" 7 | "testing" 8 | ) 9 | 10 | func TestMultiSelectMap_NoItems(t *testing.T) { 11 | pa := []*testutil.PA{} 12 | mockAsker, _ := testutil.NewMockAsker(t, pa) 13 | selectedItem, err := question.MultiSelectMap(mockAsker, "question", []*string{}, func(item *string) string { return *item }, false) 14 | assert.Nil(t, selectedItem) 15 | assert.Error(t, err) 16 | } 17 | 18 | func TestSelectMap_NoItems(t *testing.T) { 19 | pa := []*testutil.PA{} 20 | mockAsker, _ := testutil.NewMockAsker(t, pa) 21 | selectedItem, err := question.SelectMap(mockAsker, "question", []*string{}, func(item *string) string { return *item }) 22 | assert.Nil(t, selectedItem) 23 | assert.Error(t, err) 24 | } 25 | -------------------------------------------------------------------------------- /pkg/question/selectors/channels.go: -------------------------------------------------------------------------------- 1 | package selectors 2 | 3 | import ( 4 | "fmt" 5 | "github.com/OctopusDeploy/cli/pkg/output" 6 | "github.com/OctopusDeploy/cli/pkg/question" 7 | "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/channels" 8 | octopusApiClient "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/client" 9 | "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/projects" 10 | "io" 11 | "strings" 12 | ) 13 | 14 | func Channel(octopus *octopusApiClient.Client, ask question.Asker, io io.Writer, questionText string, project *projects.Project) (*channels.Channel, error) { 15 | existingChannels, err := octopus.Projects.GetChannels(project) 16 | if len(existingChannels) == 1 { 17 | fmt.Fprintf(io, "Selecting only available channel '%s'.\n", output.Cyan(existingChannels[0].Name)) 18 | return existingChannels[0], nil 19 | } 20 | if err != nil { 21 | return nil, err 22 | } 23 | 24 | return question.SelectMap(ask, questionText, existingChannels, func(p *channels.Channel) string { 25 | return p.Name 26 | }) 27 | } 28 | 29 | func FindChannel(octopus *octopusApiClient.Client, project *projects.Project, channelName string) (*channels.Channel, error) { 30 | foundChannels, err := octopus.Projects.GetChannels(project) // TODO change this to channel partial name search on server; will require go client update 31 | if err != nil { 32 | return nil, err 33 | } 34 | for _, c := range foundChannels { // server doesn't support channel search by exact name so we must emulate it 35 | if strings.EqualFold(c.Name, channelName) { 36 | return c, nil 37 | } 38 | } 39 | return nil, fmt.Errorf("no channel found with name of %s", channelName) 40 | } 41 | -------------------------------------------------------------------------------- /pkg/question/selectors/gitCredentialStorage.go: -------------------------------------------------------------------------------- 1 | package selectors 2 | 3 | import ( 4 | "github.com/OctopusDeploy/cli/pkg/question" 5 | ) 6 | 7 | func GitCredentialStorage(questionText string, ask question.Asker) (string, error) { 8 | options := []*SelectOption[string]{ 9 | {Display: "Project", Value: "project"}, 10 | {Display: "Library", Value: "library"}, 11 | } 12 | 13 | optionsCallback := func() ([]*SelectOption[string], error) { 14 | return options, nil 15 | } 16 | 17 | selectedOption, err := Select(ask, questionText, optionsCallback, func(option *SelectOption[string]) string { 18 | return option.Display 19 | }) 20 | 21 | if err != nil { 22 | return "", err 23 | } 24 | 25 | return selectedOption.Value, nil 26 | } 27 | -------------------------------------------------------------------------------- /pkg/question/selectors/git_references.go: -------------------------------------------------------------------------------- 1 | package selectors 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/OctopusDeploy/cli/pkg/output" 7 | "github.com/OctopusDeploy/cli/pkg/question" 8 | "github.com/OctopusDeploy/cli/pkg/util" 9 | octopusApiClient "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/client" 10 | "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/projects" 11 | ) 12 | 13 | func GitReference(questionText string, octopus *octopusApiClient.Client, ask question.Asker, project *projects.Project) (*projects.GitReference, error) { 14 | branches, err := octopus.Projects.GetGitBranches(project) 15 | if err != nil { 16 | return nil, err 17 | } 18 | 19 | tags, err := octopus.Projects.GetGitTags(project) 20 | 21 | if err != nil { 22 | return nil, err 23 | } 24 | 25 | allRefs := append(branches, tags...) 26 | 27 | defaultBranch := project.PersistenceSettings.(projects.GitPersistenceSettings).DefaultBranch() 28 | 29 | // if the default branch is in the list, move it to the top 30 | defaultBranchInRefsList := util.SliceFilter(allRefs, func(g *projects.GitReference) bool { return g.Name == defaultBranch }) 31 | if len(defaultBranchInRefsList) > 0 { 32 | defaultBranchRef := defaultBranchInRefsList[0] 33 | allRefs = util.SliceExcept(allRefs, func(g *projects.GitReference) bool { return g.Name == defaultBranch }) 34 | allRefs = append([]*projects.GitReference{defaultBranchRef}, allRefs...) 35 | } 36 | 37 | return question.SelectMap(ask, questionText, allRefs, func(g *projects.GitReference) string { 38 | return fmt.Sprintf("%s %s", g.Name, output.Dimf("(%s)", g.Type.Description())) 39 | }) 40 | } 41 | -------------------------------------------------------------------------------- /pkg/question/selectors/lifecycles.go: -------------------------------------------------------------------------------- 1 | package selectors 2 | 3 | import ( 4 | "github.com/OctopusDeploy/cli/pkg/question" 5 | "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/client" 6 | octopusApiClient "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/client" 7 | "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/lifecycles" 8 | ) 9 | 10 | func Lifecycle(questionText string, octopus *client.Client, ask question.Asker) (*lifecycles.Lifecycle, error) { 11 | existingLifecycles, err := octopus.Lifecycles.GetAll() 12 | if err != nil { 13 | return nil, err 14 | } 15 | 16 | return question.SelectMap(ask, questionText, existingLifecycles, func(lc *lifecycles.Lifecycle) string { 17 | return lc.Name 18 | }) 19 | } 20 | 21 | func FindLifecycle(octopus *octopusApiClient.Client, lifecycleIdentifier string) (*lifecycles.Lifecycle, error) { 22 | lifecycle, err := octopus.Lifecycles.GetByIDOrName(lifecycleIdentifier) 23 | if err != nil { 24 | return nil, err 25 | } 26 | 27 | return lifecycle, nil 28 | } 29 | -------------------------------------------------------------------------------- /pkg/question/selectors/projects.go: -------------------------------------------------------------------------------- 1 | package selectors 2 | 3 | import ( 4 | "github.com/OctopusDeploy/cli/pkg/question" 5 | octopusApiClient "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/client" 6 | "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/projects" 7 | ) 8 | 9 | func Project(questionText string, octopus *octopusApiClient.Client, ask question.Asker) (*projects.Project, error) { 10 | existingProjects, err := octopus.Projects.GetAll() 11 | if err != nil { 12 | return nil, err 13 | } 14 | 15 | return question.SelectMap(ask, questionText, existingProjects, func(p *projects.Project) string { 16 | return p.Name 17 | }) 18 | } 19 | 20 | func FindProject(octopus *octopusApiClient.Client, projectIdentifier string) (*projects.Project, error) { 21 | project, err := octopus.Projects.GetByIdentifier(projectIdentifier) 22 | if err != nil { 23 | return nil, err 24 | } 25 | 26 | return project, nil 27 | } 28 | -------------------------------------------------------------------------------- /pkg/question/selectors/runbooks.go: -------------------------------------------------------------------------------- 1 | package selectors 2 | 3 | import ( 4 | "errors" 5 | "math" 6 | 7 | "github.com/OctopusDeploy/cli/pkg/question" 8 | octopusApiClient "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/client" 9 | "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/projects" 10 | "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/runbooks" 11 | ) 12 | 13 | func Runbook(questionText string, client *octopusApiClient.Client, ask question.Asker, projectID string) (*runbooks.Runbook, error) { 14 | existingRunbooks, err := runbooks.List(client, client.GetSpaceID(), projectID, "", math.MaxInt32) 15 | if err != nil { 16 | return nil, err 17 | } 18 | 19 | if len(existingRunbooks.Items) == 0 { 20 | return nil, errors.New("no runbooks found") 21 | } 22 | 23 | if len(existingRunbooks.Items) == 1 { 24 | return existingRunbooks.Items[0], nil 25 | } 26 | 27 | return question.SelectMap(ask, questionText, existingRunbooks.Items, func(r *runbooks.Runbook) string { 28 | return r.Name 29 | }) 30 | } 31 | 32 | func FindRunbook(client *octopusApiClient.Client, project *projects.Project, runbookIdentifier string) (*runbooks.Runbook, error) { 33 | runbook, err := runbooks.GetByID(client, client.GetSpaceID(), runbookIdentifier) 34 | if err != nil { 35 | runbook, err = runbooks.GetByName(client, client.GetSpaceID(), project.GetID(), runbookIdentifier) 36 | if err != nil { 37 | return nil, err 38 | } 39 | } 40 | 41 | return runbook, nil 42 | } 43 | 44 | func FindGitRunbook(client *octopusApiClient.Client, project *projects.Project, runbookIdentifier string, gitRef string) (*runbooks.Runbook, error) { 45 | runbook, err := runbooks.GetGitRunbookByID(client, client.GetSpaceID(), project.ID, gitRef, runbookIdentifier) 46 | if err != nil { 47 | runbook, err = runbooks.GetGitRunbookByName(client, client.GetSpaceID(), project.GetID(), gitRef, runbookIdentifier) 48 | if err != nil { 49 | return nil, err 50 | } 51 | } 52 | 53 | return runbook, nil 54 | } 55 | -------------------------------------------------------------------------------- /pkg/question/selectors/selector_test.go: -------------------------------------------------------------------------------- 1 | package selectors 2 | 3 | import ( 4 | "github.com/AlecAivazis/survey/v2" 5 | "github.com/OctopusDeploy/cli/test/testutil" 6 | "github.com/stretchr/testify/assert" 7 | "testing" 8 | ) 9 | 10 | type Item struct { 11 | id string 12 | name string 13 | } 14 | 15 | func TestSelectForSingleItem(t *testing.T) { 16 | itemsCallback := func() ([]*Item, error) { 17 | return []*Item{ 18 | { 19 | id: "1", 20 | name: "name", 21 | }, 22 | }, nil 23 | } 24 | 25 | selectedItem, err := Select(nil, "question", itemsCallback, func(item *Item) string { return item.name }) 26 | assert.Nil(t, err) 27 | assert.Equal(t, selectedItem.id, "1") 28 | } 29 | 30 | func TestSelectForMultipleItem(t *testing.T) { 31 | items := []*Item{ 32 | { 33 | id: "1", 34 | name: "name", 35 | }, 36 | { 37 | id: "2", 38 | name: "name 2", 39 | }, 40 | } 41 | itemsCallback := func() ([]*Item, error) { 42 | return items, nil 43 | } 44 | pa := []*testutil.PA{ 45 | { 46 | Prompt: &survey.Select{ 47 | Message: "question", 48 | Options: []string{items[0].name, items[1].name}, 49 | }, 50 | Answer: "name 2", 51 | }, 52 | } 53 | mockAsker, checkRemainingPrompts := testutil.NewMockAsker(t, pa) 54 | selectedItem, err := Select(mockAsker, "question", itemsCallback, func(item *Item) string { return item.name }) 55 | checkRemainingPrompts() 56 | assert.Nil(t, err) 57 | assert.Equal(t, selectedItem.id, "2") 58 | } 59 | -------------------------------------------------------------------------------- /pkg/question/selectors/selectors.go: -------------------------------------------------------------------------------- 1 | package selectors 2 | 3 | import ( 4 | "github.com/OctopusDeploy/cli/pkg/question" 5 | ) 6 | 7 | type SelectOption[T any] struct { 8 | Value T 9 | Display string 10 | } 11 | 12 | func NewSelectOption[T any](value any, display string) *SelectOption[T] { 13 | return &SelectOption[T]{ 14 | Value: value.(T), 15 | Display: display, 16 | } 17 | } 18 | 19 | type Nameable interface { 20 | GetName() string 21 | } 22 | 23 | func ByName[T Nameable](ask question.Asker, list []T, message string) (T, error) { 24 | var selectedItem T 25 | selectedItem, err := question.SelectMap(ask, message, list, func(item T) string { 26 | return item.GetName() 27 | }) 28 | if err != nil { 29 | return selectedItem, err 30 | } 31 | return selectedItem, nil 32 | } 33 | 34 | func SelectOptions[T any](ask question.Asker, questionText string, itemsCallback func() []*SelectOption[T]) (*SelectOption[T], error) { 35 | items := itemsCallback() 36 | callback := func() ([]*SelectOption[T], error) { 37 | return items, nil 38 | } 39 | return Select(ask, questionText, callback, func(option *SelectOption[T]) string { return option.Display }) 40 | } 41 | 42 | func Select[T any](ask question.Asker, questionText string, itemsCallback func() ([]T, error), getKey func(item T) string) (T, error) { 43 | items, err := itemsCallback() 44 | if err != nil { 45 | var item T 46 | return item, err 47 | } 48 | if len(items) == 1 { 49 | return items[0], nil 50 | } 51 | 52 | return question.SelectMap(ask, questionText, items, getKey) 53 | } 54 | 55 | // SelectOrNew is the same as Select but show a create new option at the bottom of the list 56 | // When create new is selected the returned bool will be true 57 | func SelectOrNew[T any](ask question.Asker, questionText string, itemsCallback func() ([]T, error), getKey func(item T) string) (T, bool, error) { 58 | items, err := itemsCallback() 59 | if err != nil { 60 | var item T 61 | return item, false, err 62 | } 63 | return question.SelectMapWithNew(ask, questionText, items, getKey) 64 | } 65 | -------------------------------------------------------------------------------- /pkg/surveyext/paginate.go: -------------------------------------------------------------------------------- 1 | package surveyext 2 | 3 | import "github.com/AlecAivazis/survey/v2/core" 4 | 5 | func paginate(pageSize int, choices []core.OptionAnswer, sel int) (choiceList []core.OptionAnswer, cursor int) { 6 | var start, end int 7 | 8 | if len(choices) < pageSize { 9 | // if we dont have enough options to fill a page 10 | start = 0 11 | end = len(choices) 12 | cursor = sel 13 | choiceList = choices[start:end] 14 | 15 | } else if sel < pageSize/2 { 16 | // if we are in the first half page 17 | start = 0 18 | end = pageSize - 1 19 | cursor = sel 20 | choiceList = append(choices[start:end], choices[len(choices)-1]) 21 | 22 | } else if len(choices)-sel-1 < pageSize/2 { 23 | // if we are in the last half page 24 | start = len(choices) - pageSize 25 | end = len(choices) 26 | cursor = sel - start 27 | choiceList = choices[start:end] 28 | } else { 29 | // somewhere in the middle 30 | above := pageSize / 2 31 | below := pageSize - above 32 | 33 | cursor = pageSize / 2 34 | start = sel - above 35 | end = (sel + below) - 1 36 | choiceList = append(choices[start:end], choices[len(choices)-1]) 37 | } 38 | 39 | // return the subset we care about and the index 40 | return choiceList, cursor 41 | } 42 | -------------------------------------------------------------------------------- /pkg/usage/usage.go: -------------------------------------------------------------------------------- 1 | package usage 2 | 3 | import ( 4 | "fmt" 5 | "github.com/spf13/cobra" 6 | ) 7 | 8 | // UsageError indicates the caller has not invoked the CLI properly 9 | // and the root error handler should print the usage/help 10 | type UsageError struct { 11 | s string 12 | 13 | // this needs to carry the command, otherwise the root cmd doesn't know how to print usage 14 | cmd *cobra.Command 15 | } 16 | 17 | func NewUsageError(s string, cmd *cobra.Command) *UsageError { 18 | return &UsageError{s: s, cmd: cmd} 19 | } 20 | 21 | func (e *UsageError) Error() string { 22 | return e.s 23 | } 24 | 25 | func (e *UsageError) Command() *cobra.Command { 26 | return e.cmd 27 | } 28 | 29 | // Argument validation helper which emits a UsageError rather than a plain string error 30 | func ExactArgs(n int) cobra.PositionalArgs { 31 | return func(cmd *cobra.Command, args []string) error { 32 | if len(args) != n { 33 | return NewUsageError( 34 | fmt.Sprintf("accepts %d arg(s), received %d", n, len(args)), 35 | cmd) 36 | } 37 | return nil 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /pkg/util/featuretoggle/feature_toggles.go: -------------------------------------------------------------------------------- 1 | package featuretoggle 2 | 3 | import ( 4 | "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/client" 5 | "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/configuration" 6 | ) 7 | 8 | // IsToggleEnabled retrieves an Octopus feature toggle by name. If an error occurs, it returns false. 9 | func IsToggleEnabled(client *client.Client, toggleName string) (bool, error) { 10 | toggleRequest := &configuration.FeatureToggleConfigurationQuery{ 11 | Name: toggleName, 12 | } 13 | 14 | returnToggleResponse, err := configuration.Get(client, toggleRequest) 15 | 16 | if err != nil { 17 | return false, err 18 | } 19 | 20 | if len(returnToggleResponse.FeatureToggles) == 0 { 21 | return false, err 22 | } 23 | 24 | var toggleValue = returnToggleResponse.FeatureToggles[0] 25 | 26 | // Verify name to avoid error in older versions of Octopus where Name param is not recognised 27 | if toggleValue.Name != toggleName { 28 | return false, err 29 | } 30 | 31 | return toggleValue.IsEnabled, err 32 | } 33 | -------------------------------------------------------------------------------- /pkg/util/pipe.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "bufio" 5 | "os" 6 | ) 7 | 8 | func IsCalledFromPipe() bool { 9 | fileInfo, _ := os.Stdin.Stat() 10 | return fileInfo != nil && fileInfo.Mode()&os.ModeCharDevice == 0 11 | } 12 | 13 | // ReadValuesFromPipe will return an array of strings from the pipe 14 | // input separated by new line. 15 | func ReadValuesFromPipe() []string { 16 | items := []string{} 17 | if IsCalledFromPipe() { 18 | scanner := bufio.NewScanner(bufio.NewReader(os.Stdin)) 19 | for scanner.Scan() { 20 | items = append(items, scanner.Text()) 21 | } 22 | } 23 | return items 24 | } 25 | -------------------------------------------------------------------------------- /pkg/validation/validation.go: -------------------------------------------------------------------------------- 1 | package validation 2 | 3 | import ( 4 | "fmt" 5 | "github.com/AlecAivazis/survey/v2" 6 | uuid "github.com/google/uuid" 7 | "os" 8 | "reflect" 9 | ) 10 | 11 | // NotEquals requires that the string does not equal any of the specified values 12 | func NotEquals(stringsToCheck []string, errorMessage string) survey.Validator { 13 | // return a validator to perform the check 14 | return func(val interface{}) error { 15 | if str, ok := val.(string); ok { 16 | for _, v := range stringsToCheck { 17 | if str == v { 18 | return fmt.Errorf("%s", errorMessage) 19 | } 20 | } 21 | } else { 22 | // otherwise we cannot convert the value into a string and cannot perform check 23 | return fmt.Errorf("cannot check value on response of type %v", reflect.TypeOf(val).Name()) 24 | } 25 | 26 | // the input is fine 27 | return nil 28 | } 29 | } 30 | 31 | // IsUuid requires that the string is a valid UUID 32 | func IsUuid(val interface{}) error { 33 | if str, ok := val.(string); ok { 34 | if _, err := uuid.Parse(str); err != nil { 35 | return fmt.Errorf("not a valid UUID") 36 | } 37 | } else { 38 | // otherwise we cannot convert the value into a string and cannot perform check 39 | return fmt.Errorf("cannot check value on response of type %v", reflect.TypeOf(val).Name()) 40 | } 41 | 42 | // the input is fine 43 | return nil 44 | } 45 | 46 | func IsExistingFile(val interface{}) error { 47 | if str, ok := val.(string); ok { 48 | info, err := os.Stat(str) 49 | if os.IsNotExist(err) { 50 | return fmt.Errorf("\"%s\" is not a valid file path", str) 51 | } 52 | if info.IsDir() { 53 | return fmt.Errorf("\"%s\" is a directory, the path must be a file", str) 54 | } 55 | } else { 56 | return fmt.Errorf("cannot check value on response of type %v", reflect.TypeOf(val).Name()) 57 | } 58 | // path is real file 59 | return nil 60 | } 61 | -------------------------------------------------------------------------------- /pkg/validation/validation_test.go: -------------------------------------------------------------------------------- 1 | package validation 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/google/uuid" 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestNotEquals(t *testing.T) { 11 | testStrings := []string{"foo", "bar", "quxx"} 12 | errorMessage := "this is an error" 13 | notEqualsValidator := NotEquals(testStrings, errorMessage) 14 | 15 | assert.NotNil(t, notEqualsValidator) 16 | 17 | for _, v := range testStrings { 18 | err := notEqualsValidator(v) 19 | assert.Error(t, err) 20 | assert.Equal(t, err.Error(), errorMessage) 21 | } 22 | 23 | test := "xyzzy" 24 | err := notEqualsValidator(test) 25 | assert.NoError(t, err) 26 | } 27 | 28 | func TestIsUUID(t *testing.T) { 29 | testStrings := []string{"foo", "bar", "quxx"} 30 | for _, v := range testStrings { 31 | err := IsUuid(v) 32 | assert.Error(t, err) 33 | } 34 | 35 | testUUID, err := uuid.NewUUID() 36 | assert.NoError(t, err) 37 | assert.NotNil(t, testUUID) 38 | 39 | testUUIDString := testUUID.String() 40 | err = IsUuid(testUUIDString) 41 | assert.NoError(t, err) 42 | } 43 | -------------------------------------------------------------------------------- /version.go: -------------------------------------------------------------------------------- 1 | package version 2 | 3 | import _ "embed" 4 | 5 | //go:embed version.txt 6 | var Version string 7 | -------------------------------------------------------------------------------- /version.txt: -------------------------------------------------------------------------------- 1 | 2.18.0 2 | --------------------------------------------------------------------------------