diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..d921d0ff --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,7 @@ +version: 2 +updates: +- package-ecosystem: gomod + directory: "/" + schedule: + interval: daily + open-pull-requests-limit: 10 diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml new file mode 100644 index 00000000..53af0d95 --- /dev/null +++ b/.github/workflows/coverage.yml @@ -0,0 +1,52 @@ +name: "Unit/Coverage Tests" + +on: pull_request_target + +jobs: + coverage: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Setup Go + uses: actions/setup-go@v2 + with: + go-version: "1.21" + + - name: Run unit tests and coverage test + id: test-coverage + run: | + go test -cover -v > output.txt + + - name: Transform output + id: results + if: always() + run: | + CONTENT=$(cat output.txt) + CONTENT="${CONTENT//'%'/'%25'}" + CONTENT="${CONTENT//$'\n'/'%0A'}" + CONTENT="${CONTENT//$'\r'/'%0D'}" + echo "::set-output name=content::$CONTENT" + + - name: Add Comment + uses: actions/github-script@v5 + if: always() + with: + script: | + const output = `### Unit Tests and Coverage +
Show Output + + \`\`\` + ${{ steps.results.outputs.content }} + \`\`\` +
+ + *Pusher: @${{ github.actor }}, Action: \`${{ github.event_name }}\`*`; + + await github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: output + }) diff --git a/.github/workflows/gochecks.yml b/.github/workflows/gochecks.yml index 6058c4a8..a5334755 100644 --- a/.github/workflows/gochecks.yml +++ b/.github/workflows/gochecks.yml @@ -6,7 +6,7 @@ on: - master jobs: - Go-Lint: + Golangci-Lint: runs-on: ubuntu-latest steps: @@ -14,22 +14,17 @@ jobs: - name: Setup Go uses: actions/setup-go@v2 with: - go-version: '1.14.0' + go-version: "1.21" - name: Install dependencies run: | go version - go get -u golang.org/x/lint/golint + go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.52.2 - - name: Run Lint + - name: Run golangci-lint run: | - golint_files=$(golint cmd) - if [[ -n ${golint_files} ]]; then - echo 'fix the following linting errors:' - echo "${golint_files}" - exit 1 - fi - exit 0 + golangci-lint run cmd/... + Go-Fmt: runs-on: ubuntu-latest @@ -39,7 +34,7 @@ jobs: - name: Setup Go uses: actions/setup-go@v2 with: - go-version: '1.14.0' + go-version: "1.21" - name: Run fmt run: | @@ -49,4 +44,4 @@ jobs: echo "${gofmt_files}" exit 1 fi - exit 0 \ No newline at end of file + exit 0 diff --git a/.github/workflows/notify-issue.yml b/.github/workflows/notify-issue.yml new file mode 100644 index 00000000..8b58c9c4 --- /dev/null +++ b/.github/workflows/notify-issue.yml @@ -0,0 +1,18 @@ +name: notify-issue + +on: + issues: + types: [opened] + +jobs: + issue: + runs-on: ubuntu-latest + name: New Issue Notification + steps: + - run: | + echo "{\"text\":\"Vultr-CLI : New Issue https://github.com/vultr/vultr-cli/issues/${{ github.event.issue.number }} \"}" > mattermost.json + - uses: mattermost/action-mattermost-notify@master + env: + MATTERMOST_WEBHOOK_URL: ${{ secrets.MATTERMOST_WEBHOOK_URL }} + MATTERMOST_USERNAME: ${{ secrets.MATTERMOST_USERNAME}} + MATTERMOST_ICON: ${{ secrets.MATTERMOST_ICON }} diff --git a/.github/workflows/notify-release.yml b/.github/workflows/notify-release.yml deleted file mode 100644 index 25acd165..00000000 --- a/.github/workflows/notify-release.yml +++ /dev/null @@ -1,21 +0,0 @@ -name: notify-release - -on: - push: - tags: v* - -jobs: - release: - runs-on: ubuntu-latest - name: Release Notification - steps: - - name: Get the version - id: get_version - run: echo ::set-output name=VERSION::${GITHUB_REF/refs\/tags\//} - - run: | - echo "{\"text\":\"Vultr-CLI : Release https://github.com/vultr/vultr-cli/releases/tag/${{ steps.get_version.outputs.VERSION }} \"}" > mattermost.json - - uses: mattermost/action-mattermost-notify@master - env: - MATTERMOST_WEBHOOK_URL: ${{ secrets.MATTERMOST_WEBHOOK_URL }} - MATTERMOST_USERNAME: ${{ secrets.MATTERMOST_USERNAME}} - MATTERMOST_ICON: ${{ secrets.MATTERMOST_ICON }} \ No newline at end of file diff --git a/.github/workflows/releaser.yml b/.github/workflows/releaser.yml new file mode 100644 index 00000000..012d4309 --- /dev/null +++ b/.github/workflows/releaser.yml @@ -0,0 +1,95 @@ +name: "Automatic Releaser" + +on: + push: + branches: + - master + +permissions: + contents: write + +jobs: + check-commit: + runs-on: ubuntu-latest + outputs: + msg_check: ${{ steps.check-msg.outputs.match }} + steps: + - name: Check Message + id: check-msg + run: | + pattern="^Release v[0-9]+.[0-9]+.[0-9]+ #(minor|major|patch)$" + if [[ "${{ github.event.head_commit.message }}" =~ ${pattern} ]]; then + echo ::set-output name=match::true + fi + create-tag: + runs-on: ubuntu-latest + if: needs.check-commit.outputs.msg_check == 'true' + needs: check-commit + outputs: + new_tag: ${{ steps.tagger.outputs.new_tag }} + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: '0' + + - name: Bump version and push tag + id: tagger + uses: anothrNick/github-tag-action@1.36.0 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + WITH_V: true + DEFAULT_BUMP: "none" + + goreleaser: + runs-on: ubuntu-latest + needs: create-tag + steps: + - + name: Checkout + uses: actions/checkout@v2 + with: + fetch-depth: 0 + - + name: Set up Go + uses: actions/setup-go@v2 + with: + go-version: "1.21" + - + name: Docker Login + env: + DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} + DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} + run: | + echo "${DOCKER_PASSWORD}" | docker login --username "${DOCKER_USERNAME}" --password-stdin + - + name: Run GoReleaser + uses: goreleaser/goreleaser-action@v2 + with: + distribution: goreleaser + version: latest + args: release --clean + env: + GITHUB_TOKEN: ${{ secrets.VULTRBOT_TOKEN }} + DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} + DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} + - + name: Clear + if: always() + run: | + rm -f ${HOME}/.docker/config.json + + release: + runs-on: ubuntu-latest + needs: ["goreleaser", "create-tag"] + name: Release Notification + steps: + - name: Get the version + id: get_version + run: echo ::set-output name=VERSION::${GITHUB_REF/refs\/tags\//} + - run: | + echo "{\"text\":\"Vultr-CLI : Release https://github.com/${{ github.repository }}/releases/tag/${{ needs.create-tag.outputs.new_tag }} \"}" > mattermost.json + - uses: mattermost/action-mattermost-notify@master + env: + MATTERMOST_WEBHOOK_URL: ${{ secrets.MATTERMOST_WEBHOOK_URL }} + MATTERMOST_USERNAME: ${{ secrets.MATTERMOST_USERNAME}} + MATTERMOST_ICON: ${{ secrets.MATTERMOST_ICON }} diff --git a/.gitignore b/.gitignore index b1fa8748..de9566ff 100644 --- a/.gitignore +++ b/.gitignore @@ -20,4 +20,5 @@ .vscode/ .idea builds/ -dist/ \ No newline at end of file +dist/ +vendor/ diff --git a/.golangci.yaml b/.golangci.yaml new file mode 100644 index 00000000..49485b19 --- /dev/null +++ b/.golangci.yaml @@ -0,0 +1,125 @@ +linters-settings: + dupl: + threshold: 300 + funlen: + lines: -1 # the number of lines (code + empty lines) is not a right metric and leads to code without empty line or one-liner. + statements: 50 + goconst: + min-len: 2 + min-occurrences: 3 + gocritic: + enabled-tags: + - diagnostic + - experimental + - opinionated + - performance + - style + disabled-checks: + - dupImport # https://github.com/go-critic/go-critic/issues/845 + - ifElseChain + - octalLiteral + - whyNoLint + gocyclo: + min-complexity: 15 + goimports: + local-prefixes: github.com/golangci/golangci-lint + gomnd: + # don't include the "operation" and "assign" + checks: + - argument + - case + - condition + - return + ignored-numbers: + - '0' + - '1' + - '2' + - '3' + ignored-functions: + - strings.SplitN + + govet: + check-shadowing: true + settings: + printf: + funcs: + - (github.com/golangci/golangci-lint/pkg/logutils.Log).Infof + - (github.com/golangci/golangci-lint/pkg/logutils.Log).Warnf + - (github.com/golangci/golangci-lint/pkg/logutils.Log).Errorf + - (github.com/golangci/golangci-lint/pkg/logutils.Log).Fatalf + lll: + line-length: 140 + misspell: + locale: US + nolintlint: + allow-unused: false # report any unused nolint directives + require-explanation: false # don't require an explanation for nolint directives + require-specific: false # don't require nolint directives to be specific about which linter is being skipped + revive: + rules: + - name: unexported-return + disabled: true + +linters: + disable-all: true + enable: + - dogsled + - dupl + - errcheck + - exportloopref + - funlen + - gocyclo + - gofmt + - goimports + - gomnd + - goprintffuncname + - gosec + - gosimple + - govet + - ineffassign + - lll + - misspell + - nakedret + - noctx + - nolintlint + - revive + - staticcheck + - stylecheck + - typecheck + - unconvert + - unparam + - unused + - whitespace + + # don't enable: + # - bodyclose + # - depguard + # - asciicheck + # - scopelint + # - gochecknoglobals + # - gocognit + # - gocritic + # - godot + # - godox + # - goerr113 + # - interfacer + # - maligned + # - nestif + # - prealloc + # - testpackage + # - wsl + +issues: + # List of regexps of issue texts to exclude. + # + # But independently of this option we use default exclude patterns, + # it can be disabled by `exclude-use-default: false`. + # To list all excluded by default patterns execute `golangci-lint run --help` + # + # Default: https://golangci-lint.run/usage/false-positives/#default-exclusions + exclude: + - abcdef + - ST1023 + exclude-rules: +run: + timeout: 5m diff --git a/.goreleaser.yml b/.goreleaser.yml index bbaba413..c3502484 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -1,7 +1,3 @@ -before: - hooks: - - go mod download - - go generate ./... builds: - env: @@ -9,13 +5,8 @@ builds: binary: vultr-cli - asmflags: - - -D mysymbol - - all=-trimpath={{.Env.GOPATH}} - - gcflags: - - all=-trimpath={{.Env.GOPATH}} - - ./dontoptimizeme=-N + flags: + - -trimpath #removes all file system paths from the compiled executable goos: - linux @@ -25,15 +16,21 @@ builds: goarch: - amd64 - arm64 + - arm + + goarm: + - 6 + - 7 archives: - - replacements: - darwin: macOs - linux: linux - windows: windows - amd64: 64-bit - arm64: arm64-bit + name_template: >- + {{- .ProjectName }}_v + {{- .Version }}_ + {{- if eq .Os "darwin" }}macOs + {{- else }}{{ .Os }}{{ end }}_ + {{- if eq .Arch "arm" }}arm32-v{{ .Arm }} + {{- else }}{{ .Arch }}{{ end }} format: tar.gz @@ -44,7 +41,6 @@ archives: - goos: windows format: zip - checksum: name_template: "{{ .ProjectName }}_v{{ .Version }}_checksums.txt" algorithm: sha256 @@ -63,11 +59,10 @@ brews: - name: vultr-cli - tap: + repository: owner: vultr name: homebrew-vultr-cli - url_template: "https://github.com/vultr/vultr-cli/releases/download/{{ .Tag }}/{{ .ArtifactName }}" commit_author: @@ -78,8 +73,6 @@ brews: description: "Official command-line tool for Vultr services" - dependencies: - - go test: | output = shell_output("#{bin}/vultr-cli version 2>&1", 1) assert_match "Please export your VULTR API key as an environment variable or add `api-key` to your config file, eg:\nexport VULTR_API_KEY=''\n", output @@ -92,9 +85,12 @@ dockers: - dockerfile: Dockerfile.goreleaser image_templates: - "vultr/vultr-cli:release" + - "vultr/vultr-cli:latest" - "vultr/vultr-cli:{{ .Tag }}" + extra_files: + - scripts/entrypoint.sh release: github: owner: vultr - name: vultr-cli \ No newline at end of file + name: vultr-cli diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 7011790d..00000000 --- a/.travis.yml +++ /dev/null @@ -1,22 +0,0 @@ -# .travis.yml -language: go - -go: - - 1.16.x - -env: - - GO111MODULE=on - -services: - - docker - -after_success: - - docker login -u="$DOCKER_USER" -p="$DOCKER_PASSWORD" - -deploy: - - provider: script - cleanup: false - script: curl -sL https://git.io/goreleaser | bash - on: - tags: true - condition: $TRAVIS_OS_NAME = linux \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 079c61d4..f4f240b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,378 @@ # Change Log +## [v2.21.0](https://github.com/vultr/vultr-cli/compare/v2.20.0...v2.21.0) (2023-11-29) +### Enhancements +* Database: Add usage commands [PR 378](https://github.com/vultr/vultr-cli/pull/378) +* Container Registry: Implemented [PR 380](https://github.com/vultr/vultr-cli/pull/380) +* Bare Metal: Update tags display to use delimiters [PR 372](https://github.com/vultr/vultr-cli/pull/372) +* Instance: Update tags display to use delimiters [PR 372](https://github.com/vultr/vultr-cli/pull/372) +* Database: Add read replica promotion [PR 375](https://github.com/vultr/vultr-cli/pull/375) +* Kubernetes: Add kubeconfig filepath export [PR 361](https://github.com/vultr/vultr-cli/pull/361) + +### Dependencies +* Update govultr to v3.4.1 [PR 376](https://github.com/vultr/vultr-cli/pull/376) +* Bump golang.org/x/oauth2 from 0.14.0 to 0.15.0 [PR 379](https://github.com/vultr/vultr-cli/pull/379) +* Bump github.com/spf13/cobra from 1.7.0 to 1.8.0 [PR 371](https://github.com/vultr/vultr-cli/pull/371) +* Bump govultr to v3.4.0 [PR 374](https://github.com/vultr/vultr-cli/pull/374) +* Bump golang.org/x/oauth2 from 0.13.0 to 0.14.0 [PR 373](https://github.com/vultr/vultr-cli/pull/373) + +## [v2.20.0](https://github.com/vultr/vultr-cli/compare/v2.19.0...v2.20.0) 2023-11-01 +### Enhancements +* Managed Database public/private hostnames, cleanup summarize view [PR 363](https://github.com/vultr/vultr-cli/pull/363) +* Allow some commands to be run without authenticating against the API [PR 364](https://github.com/vultr/vultr-cli/pull/364) +* Add support for the VKE HA control plane option [PR 368](https://github.com/vultr/vultr-cli/pull/368) +* Add Support for DBaaS FerretDB Subscriptions [PR 369](https://github.com/vultr/vultr-cli/pull/369) + +### Bug Fixes +* Adjust DBaaS VPC pointer to detect changes [PR 366](https://github.com/vultr/vultr-cli/pull/366) + +### Dependencies +* Bump golang.org/x/net from 0.15.0 to 0.17.0 [PR 358](https://github.com/vultr/vultr-cli/pull/358) +* Update govultr to v3.3.2 [PR 362](https://github.com/vultr/vultr-cli/pull/362) +* Update govultr to v3.3.3 [PR 365](https://github.com/vultr/vultr-cli/pull/365) +* Update govultr to v3.3.4 [PR 367](https://github.com/vultr/vultr-cli/pull/367) +* Bump golang.org/x/oauth2 from 0.12.0 to 0.13.0 [PR 356](https://github.com/vultr/vultr-cli/pull/356) +* Bump github.com/spf13/viper from 1.16.0 to 1.17.0 [PR 357](https://github.com/vultr/vultr-cli/pull/357) + +## [v2.19.0](https://github.com/vultr/vultr-cli/compare/v2.18.2...v2.19.0) (2023-10-18) +### Enhancements +* Kubernetes: Add summarize list options [PR 348](https://github.com/vultr/vultr-cli/pull/348) +* Database: Add summarize list options [PR 348](https://github.com/vultr/vultr-cli/pull/348) +* Load Balancer: Add summarize list options [PR 348](https://github.com/vultr/vultr-cli/pull/348) +* Rework the printer output code [PR 355](https://github.com/vultr/vultr-cli/pull/355) + +### Documentation +* VPC2: Correct create command example [PR 350](https://github.com/vultr/vultr-cli/pull/350) + +### Bug Fixes +* Remove the useless cobra init help toggle flag [PR 349](https://github.com/vultr/vultr-cli/pull/349) + +### Dependencies +* Bump golang.org/x/oauth2 from 0.11.0 to 0.12.0 [PR 351](https://github.com/vultr/vultr-cli/pull/351) +* Update to go v1.21 [PR 347](https://github.com/vultr/vultr-cli/pull/347) + +### Automation +* Add project name back to the archive file names in goreleaser [PR 346](https://github.com/vultr/vultr-cli/pull/346) +* Add golangci-lint and fix linter errors [PR 353](https://github.com/vultr/vultr-cli/pull/353) +* Remove unnecessary Go dependency from .goreleaser [PR 97](https://github.com/vultr/vultr-cli/pull/97) + +### New Contributors +* @0az made their first contribution in [PR 97](https://github.com/vultr/vultr-cli/pull/97) +* @resmo made their first contribution in [PR 350](https://github.com/vultr/vultr-cli/pull/350) + +## [v2.18.2](https://github.com/vultr/vultr-cli/compare/v2.18.0...v2.18.2) (2023-08-24) +### Automation +* Update how archive names are generated by goreleaser [PR 342](https://github.com/vultr/vultr-cli/pull/342) +* Remove deprecated brews tap command in goreleaser [PR_344](https://github.com/vultr/vultr-cli/pull/344) + +## [v2.18.0](https://github.com/vultr/vultr-cli/compare/v2.17.0...v2.18.0) (2023-08-23) +### Enhancements +* Database: Add VPC support for DBaaS instances [PR 331](https://github.com/vultr/vultr-cli/pull/331) +* Bare Metal: Add support for VPC 2.0 [PR 335](https://github.com/vultr/vultr-cli/pull/335) +* Instance: Add support for VPC 2.0 [PR 335](https://github.com/vultr/vultr-cli/pull/335) +* Application: Add more aliases for the apps command [PR 336](https://github.com/vultr/vultr-cli/pull/336) +* VPC2: Add Nodes Endpoints [PR 339](https://github.com/vultr/vultr-cli/pull/339) +* Database: Managed Database Nesting Refactor [PR 340](https://github.com/vultr/vultr-cli/pull/340) + +### Bug Fixes +* Instance: Fix reserved IPv4 flag docs [PR 337](https://github.com/vultr/vultr-cli/pull/337) + +### Dependencies +* Update govultr to v3.3.0 [PR 334](https://github.com/vultr/vultr-cli/pull/334) +* Update govultr to v3.3.1 [PR 338](https://github.com/vultr/vultr-cli/pull/338) +* Update govultr to v3.1.0 [PR 329](https://github.com/vultr/vultr-cli/pull/329) +* Bump github.com/vultr/govultr/v3 from 3.1.0 to 3.2.0 [PR 330](https://github.com/vultr/vultr-cli/pull/330) +* Bump golang.org/x/oauth2 from 0.9.0 to 0.10.0 [PR 328](https://github.com/vultr/vultr-cli/pull/328) +* Bump golang.org/x/oauth2 from 0.10.0 to 0.11.0 [PR 333](https://github.com/vultr/vultr-cli/pull/333) + +### New Contributors +* @nhooyr made their first contribution in [PR 337](https://github.com/vultr/vultr-cli/pull/337) + +## [v2.17.0](https://github.com/vultr/vultr-cli/compare/v2.16.2...v2.17.0) (2023-06-14) +### Enhancements +* Instances: Add support for attaching and detaching VPC networks [PR 318](https://github.com/vultr/vultr-cli/pull/318) + +### Bug Fixes +* Database: Fix database update errors and remove db engine/version [PR 314](https://github.com/vultr/vultr-cli/pull/314) + +### Documentation +* README: Use a more succinct Homebrew command to tap-and-install [PR 315](https://github.com/vultr/vultr-cli/pull/315) +* README: Fix spelling [PR 324](https://github.com/vultr/vultr-cli/pull/324) +* README: Add docker install/usage instructions [PR 322](https://github.com/vultr/vultr-cli/pull/322) +* README: Mention default config yaml location [PR 325](https://github.com/vultr/vultr-cli/pull/325) + +### Dependencies +* Bump github.com/vultr/govultr/v3 from 3.0.2 to 3.0.3 [PR 320](https://github.com/vultr/vultr-cli/pull/320) +* Bump github.com/spf13/cobra from 1.6.1 to 1.7.0 [PR 310](https://github.com/vultr/vultr-cli/pull/310) +* Bump github.com/spf13/viper from 1.15.0 to 1.16.0 [PR 319](https://github.com/vultr/vultr-cli/pull/319) +* Bump golang.org/x/oauth2 from 0.6.0 to 0.7.0 [PR 312](https://github.com/vultr/vultr-cli/pull/312) +* Bump golang.org/x/oauth2 from 0.7.0 to 0.8.0 [PR 316](https://github.com/vultr/vultr-cli/pull/316) +* Bump golang.org/x/oauth2 from 0.8.0 to 0.9.0 [PR 323](https://github.com/vultr/vultr-cli/pull/323) +* Update Github workflows to go v1.20 [PR 311](https://github.com/vultr/vultr-cli/pull/311) + +### New Contributors +* @ELLIOTTCABLE made their first contribution in [PR 315](https://github.com/vultr/vultr-cli/pull/315) + +## [v2.16.2](https://github.com/vultr/vultr-cli/compare/v2.15.1...v2.16.2) (2023-03-31) +### Enhancements +* Database: Add DBaaS Support [PR 302](https://github.com/vultr/vultr-cli/pull/302) + +### Dependencies +* Update go to 1.20 [PR 303](https://github.com/vultr/vultr-cli/pull/303) +* Update govultr to v3.0.1 [PR 301](https://github.com/vultr/vultr-cli/pull/301) +* Update govultr to v3.0.2 [PR 304](https://github.com/vultr/vultr-cli/pull/304) +* Fix goreleaser configurations [PR 306](https://github.com/vultr/vultr-cli/pull/306) +* Fix github automatic release configurations [PR 308](https://github.com/vultr/vultr-cli/pull/308) + +### New Contributors +* @christhemorse made their first contribution in [PR 302](https://github.com/vultr/vultr-cli/pull/302) + +## [v2.15.1](https://github.com/vultr/vultr-cli/compare/v2.15.0...v2.15.1) (2023-03-09) +### Enhancements +* Update goreleaser to add latest docker image tag [PR 287](https://github.com/vultr/vultr-cli/pull/287) + +### Documentation +* Block Storage: Make cli param examples consistently use = [PR 291](https://github.com/vultr/vultr-cli/pull/291) +* Instances: Make cli param examples consistently use = [PR 291](https://github.com/vultr/vultr-cli/pull/291) +* Regions: Add vcg plan options to docstrings [PR 299](https://github.com/vultr/vultr-cli/pull/299) +* Plans: Add vcg plan options to docstrings [PR 299](https://github.com/vultr/vultr-cli/pull/299) + +### Dependencies +* Bump github.com/spf13/cobra from 1.5.0 to 1.6.0 by [PR 288](https://github.com/vultr/vultr-cli/pull/288) +* Bump github.com/spf13/cobra from 1.6.0 to 1.6.1 by [PR 289](https://github.com/vultr/vultr-cli/pull/289) +* Bump github.com/spf13/viper from 1.13.0 to 1.14.0 [PR 290](https://github.com/vultr/vultr-cli/pull/290) +* Bump golang.org/x/oauth2 from 0.0.0-20221014153046-6fdb5e3db783 to 0.5.0 [PR 296](https://github.com/vultr/vultr-cli/pull/296) +* Bump golang.org/x/oauth2 from 0.5.0 to 0.6.0 [PR 298](https://github.com/vultr/vultr-cli/pull/298) +* Bump github.com/spf13/viper from 1.14.0 to 1.15.0 [PR 293](https://github.com/vultr/vultr-cli/pull/293) +* Bump golang.org/x/net from 0.6.0 to 0.7.0 [PR 297](https://github.com/vultr/vultr-cli/pull/297) + +### New Contributors +* @happytreees made their first contribution in [PR 287](https://github.com/vultr/vultr-cli/pull/287) + +## [v2.15.0](https://github.com/vultr/vultr-cli/compare/v2.14.2...v2.15.0) (2022-10-04) +### Enhancements +* Add arm builds [PR 283](https://github.com/vultr/vultr-cli/pull/283) + +### Dependencies +* Bump github.com/spf13/cobra from 1.4.0 to 1.5.0 [PR 274](https://github.com/vultr/vultr-cli/pull/274) +* Bump go from 1.17 to 1.19 [PR 284]( https://github.com/vultr/vultr-cli/pull/284) + +### Documentation +* Remove extraneous dash from example command-line [PR 279](https://github.com/vultr/vultr-cli/pull/279) + +### New Contributors +* @uplime made their first contribution in [PR 279](https://github.com/vultr/vultr-cli/pull/279) +* @mondragonfx made their first contribution in [PR 284](https://github.com/vultr/vultr-cli/pull/284) + +## [v2.14.2](https://github.com/vultr/vultr-cli/compare/v2.14.1...v2.14.2) (2022-06-14) +### Enhancements +* Reserved IP: Add support for reserved IP label updates [PR 272](https://github.com/vultr/vultr-cli/pull/272) + +### Dependencies +* Bump govultr version from 2.17.1 to 2.17.2 [PR 272](https://github.com/vultr/vultr-cli/pull/272) + +## [v2.14.1](https://github.com/vultr/vultr-cli/compare/v2.14.0...v2.14.1) (2022-06-03) +### Enhancements +* Plans: Add GPU fields [PR 269](https://github.com/vultr/vultr-cli/pull/269) +* Instances: Update `tag` to string pointer [PR 268](https://github.com/vultr/vultr-cli/pull/268) +* Kuberneted: Update `tag` to string pointer [PR 268](https://github.com/vultr/vultr-cli/pull/268) + +### Dependencies +* Bump github.com/spf13/viper from 1.11.0 to 1.12.0 [PR 266](https://github.com/vultr/vultr-cli/pull/266) +* Bump govultr version from 2.16.0 to 2.17.1 [PR 267](https://github.com/vultr/vultr-cli/pull/267) + +## [v2.14.0](https://github.com/vultr/vultr-cli/compare/v2.13.0..v2.14.0) (2022-05-09) +### Enhancements +* Kubernetes : Add support for kubernetes version upgrades on individual clusters [PR 263](https://github.com/vultr/vultr-cli/pull/263) +* Kubernetes : Add support for node pool auto scaler options [PR 261](https://github.com/vultr/vultr-cli/pull/261) +* Firewall Rule : Update IP type option to match API verbiage for firewall rules [PR 262](https://github.com/vultr/vultr-cli/pull/262) +* Baremetal : Add support for multiple tags via the `tags` field [PR 259](https://github.com/vultr/vultr-cli/pull/259) +* Instances : Add support for multiple tags via the `tags` field [PR 259](https://github.com/vultr/vultr-cli/pull/259) + +### Deprecations +* Firewall Rule : The `type` option on firewall rules has been replaced by `ip-type` [PR 262](https://github.com/vultr/vultr-cli/pull/262) +* Baremetal : the `tag` field has been replaced by `tags` which supports multiple tags [PR 259](https://github.com/vultr/vultr-cli/pull/259) +* Instances : the `tag` field has been replaced by `tags` which supports multiple tags [PR 259](https://github.com/vultr/vultr-cli/pull/259) + +### Dependencies +* Bump github.com/vultr/govultr/v2 from 2.15.1 to 2.16.0 [PR 260](https://github.com/vultr/vultr-cli/pull/260) + +### Documentation +* Update BSD install instructions [PR 258](https://github.com/vultr/vultr-cli/pull/258) +* Update README and improve verbiage for snapshots [PR 257](https://github.com/vultr/vultr-cli/pull/257) + +## [v2.13.0](https://github.com/vultr/vultr-cli/compare/v2.12.2..v2.13.0) (2022-04-15) +### Enhancements +* VPC : new commands which will be replacing `network` (private networks) [PR 251](https://github.com/vultr/vultr-cli/pull/251) +* BlockStorage : adding support for new `block_type` field [PR 249](https://github.com/vultr/vultr-cli/pull/249/) +* LoadBalancer : Updating `vpc` functionality added [PR 251](https://github.com/vultr/vultr-cli/pull/251) + +### Deprecations +* Network : These commands have been replaced by `vpc` [PR 251](https://github.com/vultr/vultr-cli/pull/251) +* Instance : The following fields have been deprecated on the `create` command `private-network` and `network`. Please use `vpc-enable` or `vpc-ids` [PR 251](https://github.com/vultr/vultr-cli/pull/251) +* LoadBalancer : The following fields have been deprecated on the `create` command `private-network`. Please use `vpc` instead [PR 251](https://github.com/vultr/vultr-cli/pull/251) + +### Dependencies +* Bump github.com/vultr/govultr/v2 from 2.14.1 to 2.15.1 [PR 249](https://github.com/vultr/vultr-cli/pull/249) +* Bump github.com/spf13/viper from 1.10.1 to 1.11.0 [PR 252](https://github.com/vultr/vultr-cli/pull/252) + +### Documentation +* Add Fedora installation instructions [PR 246](https://github.com/vultr/vultr-cli/pull/246) + +## [v2.12.2](https://github.com/vultr/vultr-cli/compare/v2.12.1..v2.12.2) (2022-04-01) +### Enhancements +* Instances : fix csv flags `ssh-keys` and `network` [PR 244](https://github.com/vultr/vultr-cli/pull/244) @optik-aper +* Plans + Regions: Add new plan types in examples [PR 241](https://github.com/vultr/vultr-cli/pull/241/) @AFatalErrror +* Plans Metal : new command to retrieve just bare metal plans [PR 240](https://github.com/vultr/vultr-cli/pull/240) @optik-aper +* Readme : fix command example [PR 239](https://github.com/vultr/vultr-cli/pull/239) @travispaul + +### Dependencies +* Bump github.com/vultr/govultr/v2 from 2.14.1 to 2.14.2 [PR 238](https://github.com/vultr/vultr-cli/pull/238) +* Bump github.com/spf13/cobra from 1.3.0 to 1.4.0 [PR 236](https://github.com/vultr/vultr-cli/pull/236) +* Bump builds from go 1.16 -> 1.17 [PR 243](https://github.com/vultr/vultr-cli/pull/243) + +## [v2.12.1](https://github.com/vultr/vultr-cli/compare/v2.12.0..v2.12.1) (2022-02-07) +### Dependencies +* Bump github.com/vultr/govultr/v2 from 2.14.0 to 2.14.1 [PR 232](https://github.com/vultr/vultr-cli/pull/232) + +### Enhancements +* Firewall Rule : Add ip type, source and subnet size to firewall rule printer [PR 234](https://github.com/vultr/vultr-cli/pull/234) + +## [v2.12.0](https://github.com/vultr/vultr-cli/compare/v2.11.3..v2.12.0) (2022-01-21) +### Dependencies +* Bump github.com/vultr/govultr/v2 from 2.12.0 to 2.14.0 [PR 230](https://github.com/vultr/vultr-cli/pull/230) +* Bump github.com/spf13/viper from 1.10.0 to 1.10.1 [PR 224](https://github.com/vultr/vultr-cli/pull/224) +* Bump github.com/spf13/cobra from 1.2.1 to 1.3.0 [PR 223](https://github.com/vultr/vultr-cli/pull/223) + +### Enhancements +* Script : Return b64 script when getting script by id [PR 229](https://github.com/vultr/vultr-cli/pull/229) + +### Breaking Changes +* Script : get command will display data vertically now instead of horizontal [PR 229](https://github.com/vultr/vultr-cli/pull/229) + +### Bug Fixes +* Firewalls : change source from int to string [PR 228](https://github.com/vultr/vultr-cli/pull/228) + +## [v2.11.3](https://github.com/vultr/vultr-cli/compare/v2.11.2..v2.11.3) (2021-12-13) +### Dependencies +* Bump github.com/spf13/viper from 1.9.0 to 1.10.0 [PR 219](https://github.com/vultr/vultr-cli/pull/219) + +### Enhancements +* Add OpenBSD install instructions [PR 218](https://github.com/vultr/vultr-cli/pull/218) + +## [v2.11.2](https://github.com/vultr/vultr-cli/compare/v2.11.1..v2.11.2) (2021-12-01) +### Dependencies +* Update GoVultr from 2.11.1 to 2.12.0 [PR 215](https://github.com/vultr/vultr-cli/pull/215) + +## [v2.11.1](https://github.com/vultr/vultr-cli/compare/v2.11.0..v2.11.1) (2021-11-29) +### Dependencies +* Bump github.com/vultr/govultr/v2 from 2.11.0 to 2.11.1 [PR 213](https://github.com/vultr/vultr-cli/pull/213) + +### Bug Fixes +* Load Balancers : Allow SSL certificates to be passed in on Create and Update [PR 213](https://github.com/vultr/vultr-cli/pull/213) + +## [v2.11.0](https://github.com/vultr/vultr-cli/compare/v2.10.0..v2.11.0) (2021-11-23) +### Enhancements +* DNS: Add support for getting a domains dns sec status [PR 211](https://github.com/vultr/vultr-cli/pull/211) +* Instance : Support changing hostname on reinstall [PR 209](https://github.com/vultr/vultr-cli/pull/209) [PR 210](https://github.com/vultr/vultr-cli/pull/210) + +### Dependencies +* Update GoVultr from 2.10.0 to 2.11.0 [PR 209](https://github.com/vultr/vultr-cli/pull/209) + +## [v2.10.0](https://github.com/vultr/vultr-cli/compare/v2.9.0..v2.10.0) (2021-11-04) +### Enhancements +* Billing: Add support for retrieving billing information [PR 203](https://github.com/vultr/vultr-cli/pull/203) + +### Dependencies +* Update GoVultr from 2.9.2 to 2.10.0 [PR 203](https://github.com/vultr/vultr-cli/pull/203) + +## [v2.9.0](https://github.com/vultr/vultr-cli/compare/v2.8.5..v2.9.0) (2021-10-27) +### Bug Fixes +* Allow `go get` and `go install` to work with `github.com/vultr/vultr-cli/v2` [PR 199](https://github.com/vultr/vultr-cli/pull/199) + +## [v2.8.5](https://github.com/vultr/vultr-cli/compare/v2.8.4..v2.8.5) (2021-10-20) +### Dependencies +* Update GoVultr from 2.9.0 to 2.9.1 and update necessary fields [PR 196](https://github.com/vultr/vultr-cli/pull/196) +* Update GoVultr from 2.9.1 to 2.9.2 [PR 197](https://github.com/vultr/vultr-cli/pull/197) + +### Enhancements +* Kubernetes: Add support for adding/modifying tags on Node Pools [PR 196](https://github.com/vultr/vultr-cli/pull/196) + +## [v2.8.4](https://github.com/vultr/vultr-cli/compare/v2.8.3..v2.8.4) (2021-09-28) +### Dependencies +* Update GoVultr from 2.8.1 to 2.9.0 and update necessary fields [PR 192](https://github.com/vultr/vultr-cli/pull/192) + +### Enhancements +* Snapshots: `COMPRESSED SIZE` has been added to printer output [PR 192](https://github.com/vultr/vultr-cli/pull/192) +* Kubernetes: `COUNT` has changed to `NODE QUANTITY` and `PLAN ID` has changed to `PLAN` for kubernetes printer output [PR 192](https://github.com/vultr/vultr-cli/pull/192) + +## [v2.8.3](https://github.com/vultr/vultr-cli/compare/v2.8.2..v2.8.3) (2021-09-20) +### Dependencies +* Bump github.com/spf13/viper from 1.8.1 to 1.9.0 [PR 189](https://github.com/vultr/vultr-cli/pull/189) -## [v2.4.1](https://github.com/vultr/vultr-cli/compare/v2.4.0..v2.4.1) (2021-04-12) ### Bug Fixes -* LoadBalancers: fix update issues [PR 145](https://github.com/vultr/vultr-cli/pull/145) +* Backups: Fix typo in backups alias [PR 188](https://github.com/vultr/vultr-cli/pull/188). Thanks @rmorey for your contribution + +## [v2.8.2](https://github.com/vultr/vultr-cli/compare/v2.8.1..v2.8.2) (2021-09-07) +### Enhancements +* Instances: change default value for notify flag [PR 185](https://github.com/vultr/vultr-cli/pull/185) +* README: add example using boolean flag [PR 186](https://github.com/vultr/vultr-cli/pull/186) + +## [v2.8.1](https://github.com/vultr/vultr-cli/compare/v2.8.0..v2.8.1) (2021-09-01) +### Dependencies +* GoVultr 2.8.0 -> 2.8.1 (added more kubernetes support)[PR 181](https://github.com/vultr/vultr-cli/pull/181) + +### Enhancements +* Kubernetes: Add support for new Kubernetes calls [PR 181](https://github.com/vultr/vultr-cli/pull/181) +* Add User-Agent: [PR 182](https://github.com/vultr/vultr-cli/pull/182) + +## [v2.8.0](https://github.com/vultr/vultr-cli/compare/v2.7.0..v2.8.0) (2021-08-23) +### Dependencies +* GoVultr 2.7.1 -> 2.8.0 (added kubernetes support)[PR 177](https://github.com/vultr/vultr-cli/pull/177) + +### Enhancements +* Kubernetes: Add support for Kubernetes (VKE) [PR 178](https://github.com/vultr/vultr-cli/pull/178) +* README: update commands needed for building from source [PR 173](https://github.com/vultr/vultr-cli/pull/173) +* README: update examples [PR 174](https://github.com/vultr/vultr-cli/pull/174) + +## [v2.7.0](https://github.com/vultr/vultr-cli/compare/v2.6.0..v2.7.0) (2021-07-16) +### Dependencies +* GoVultr 2.6.0 -> 2.7.1 (added image_id support for instance and bare metal updates) [PR 169](https://github.com/vultr/vultr-cli/pull/169) + +### Enhancements +* Instances: Add image_id support [PR 169](https://github.com/vultr/vultr-cli/pull/169) +* Bare-metal: Add image_id support [PR 169](https://github.com/vultr/vultr-cli/pull/169) +* Add documentation for autocompletions in README + +## [v2.6.0](https://github.com/vultr/vultr-cli/compare/v2.5.3..v2.6.0) (2021-07-07) +### Dependencies +* Bump github.com/spf13/viper from 1.7.1 to 1.8.1 [PR 163](https://github.com/vultr/vultr-cli/pull/163) +* GoVultr v2.5.1 -> 2.6.0 (added support for persistent_pxe) [PR 164](https://github.com/vultr/vultr-cli/pull/164) + +### Enhancements +* Bare-metal : Support `persistent_pxe` on create [PR 164](https://github.com/vultr/vultr-cli/pull/164) + +## [v2.5.3](https://github.com/vultr/vultr-cli/compare/v2.5.2..v2.5.3) (2021-06-28) +### Dependencies +* Bump github.com/spf13/viper from 1.7.1 to 1.8.1 [PR 160](https://github.com/vultr/vultr-cli/pull/160) + +## [v2.5.2](https://github.com/vultr/vultr-cli/compare/v2.5.1..v2.5.2) (2021-05-17) +### Enhancement +* Support config files in $XDG_CONFIG_HOME [PR 153](https://github.com/vultr/vultr-cli/pull/153) + +### Documentation +* Add Arch Linux install instructions [PR 154](https://github.com/vultr/vultr-cli/pull/154) + +## [v2.5.1](https://github.com/vultr/vultr-cli/compare/v2.5.0..v2.5.1) (2021-05-12) +### Dependencies +* GoVultr v2.5.0 -> 2.5.1 (fixes issue with backup schedules) [PR 151](https://github.com/vultr/vultr-cli/pull/151) + +## [v2.5.0](https://github.com/vultr/vultr-cli/compare/v2.4.1..v2.5.0) (2021-05-06) +### Enhancement +* LoadBalancers : New Features [149](https://github.com/vultr/vultr-cli/pull/149) + * Ability to attach private networks + * Ability to set firewalls + * Get Firewall Rules + * List Firewall Rules ## [v2.4.0](https://github.com/vultr/vultr-cli/compare/v2.3.0..v2.4.0) (2021-04-01) ### Enhancement @@ -45,7 +415,7 @@ ## [v2.0.0](https://github.com/vultr/vultr-cli/compare/v1.0.0..v2.0.0) (2020-11-24) ### Enhancement -* Vultr-CLI v2.0.0 release +* Vultr-CLI v2.0.0 release ### Changes * Vultr-CLI v2.0.0 is running on API v2 @@ -53,11 +423,11 @@ ## [v1.0.0](https://github.com/vultr/vultr-cli/compare/v0.4.0..v1.0.0) (2020-11-19) ### Enhancement -* Vultr-CLI v1.0.0 release [PR 114](https://github.com/vultr/vultr-cli/pull/114) +* Vultr-CLI v1.0.0 release [PR 114](https://github.com/vultr/vultr-cli/pull/114) ## [v0.4.0](https://github.com/vultr/vultr-cli/compare/v0.3.2..v0.4.0) (2020-09-03) ### Enhancement -* Improve error responses by adding a newline [PR 109](https://github.com/vultr/vultr-cli/pull/109) +* Improve error responses by adding a newline [PR 109](https://github.com/vultr/vultr-cli/pull/109) * Add Server User Data subcommands Get and Set [PR 105](https://github.com/vultr/vultr-cli/pull/105) ### Dependencies @@ -119,7 +489,7 @@ ### Enhancements * Bump GoVultr to v0.1.5 [PR 55](https://github.com/vultr/vultr-cli/pull/55) - + ## [v0.1.6](https://github.com/vultr/vultr-cli/compare/v0.1.5..v0.1.6) (2019-09-04) ### Enhancements * Print the original API error messages in [PR 50](https://github.com/vultr/vultr-cli/pull/50) && [PR 52](https://github.com/vultr/vultr-cli/pull/52) @@ -140,7 +510,7 @@ ## [v0.1.3](https://github.com/vultr/vultr-cli/compare/v0.1.2..v0.1.3) (2019-08-21) ### Bug Fixes * Quote handling on DNS Record Data [PR #41](https://github.com/vultr/vultr-cli/pull/41) - + ## [v0.1.2](https://github.com/vultr/vultr-cli/compare/v0.1.1..v0.1.2) (2019-07-15) ### Dependencies * Updated dependencies [PR #35](https://github.com/vultr/vultr-cli/pull/35) diff --git a/Dockerfile b/Dockerfile index 846bb0c6..3c6ece36 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.14-alpine as builder +FROM golang:1.21-alpine as builder RUN apk add --update \ make @@ -17,4 +17,4 @@ FROM alpine:latest RUN apk add --no-cache ca-certificates COPY --from=builder /go/src/github.com/vultr/vultr-cli/builds/* / -ENTRYPOINT ["/vultr-cli_linux_amd64"] \ No newline at end of file +ENTRYPOINT ["/vultr-cli_linux_amd64"] diff --git a/Makefile b/Makefile index 890911c9..ebe4e025 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ .PHONY: remove format export CGO=0 -export GOFLAGS=-mod=vendor -trimpath +export GOFLAGS=-trimpath DIR?=builds @@ -29,6 +29,9 @@ $(DIR)/vultr-cli_windows_386.exe: $(DIR) $(DIR)/vultr-cli_windows_amd64.exe: $(DIR) env GOOS=windows GOARCH=amd64 go build -o $@ +$(DIR)/vultr-cli_linux_arm: $(DIR) + env GOOS=linux GOARCH=arm go build -o $@ + remove: @rm -rf builds @@ -37,4 +40,4 @@ format: docker: docker build . -t vultr/vultr-cli - docker push vultr/vultr-cli \ No newline at end of file + docker push vultr/vultr-cli diff --git a/vendor/github.com/inconshreveable/mousetrap/LICENSE b/NOTICE similarity index 85% rename from vendor/github.com/inconshreveable/mousetrap/LICENSE rename to NOTICE index 5f0d1fb6..7a4ee3d6 100644 --- a/vendor/github.com/inconshreveable/mousetrap/LICENSE +++ b/NOTICE @@ -1,4 +1,7 @@ -Copyright 2014 Alan Shreve +Vultr-CLI +Copyright © 2024 Vultr + +This product includes software developed at Vultr Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/README.md b/README.md index d3404b4e..756bd5e1 100644 --- a/README.md +++ b/README.md @@ -9,32 +9,37 @@ Usage: vultr-cli [command] Available Commands: - account Retrieve information about your account - api-key retrieve information about the current API key - apps Display all available applications - backups display all available backups - bare-metal bare-metal is used to access bare metal server commands - block-storage block storage commands - dns dns is used to access dns commands - firewall firewall is used to access firewall commands - help Help about any command - iso iso is used to access iso commands - load-balancer load balancer commands - network network interacts with network actions - object-storage object storage commands - operatingSystems grab all available operating systems - plans get information about Vultr plans - regions get regions - reserved-ip reserved-ip lets you interact with reserved-ip - script startup script commands - instance commands to interact with instances on vultr - snapshot snapshot commands - ssh-key ssh-key commands - user user commands - version Display current version of Vultr-cli + account Retrieve information about your account + apps Display all available applications + backups Display backups + bare-metal bare-metal is used to access bare metal server commands + billing Display billing information + block-storage block storage commands + completion Generate the autocompletion script for the specified shell + container-registry commands to interact with container registries + database Commands to interact with managed databases on vultr + dns dns is used to access dns commands + firewall firewall is used to access firewall commands + help Help about any command + instance commands to interact with instances on vultr + iso iso is used to access iso commands + kubernetes kubernetes is used to access kubernetes commands + load-balancer load balancer commands + object-storage object storage commands + os os is used to access os commands + plans get information about Vultr plans + regions get regions + reserved-ip reserved-ip lets you interact with reserved-ip + script startup script commands + snapshot snapshot commands + ssh-key ssh-key commands + user user commands + version Display current version of Vultr-cli + vpc Interact with VPCs + vpc2 Interact with VPC 2.0 networks Flags: - --config string config file (default is $HOME/.vultr-cli.yaml) + --config string config file (default is $HOME/.vultr-cli.yaml) (default "#HOME/.vultr-cli.yaml") -h, --help help for vultr-cli -t, --toggle Help message for toggle @@ -43,85 +48,169 @@ Use "vultr-cli [command] --help" for more information about a command. ## Installation -There are three ways to install `vultr-cli`: +These are the options available to install `vultr-cli`: 1. Download a release from GitHub 2. From source 3. Package Manager + - Arch Linux - Brew + - OpenBSD (-current) - Snap (Coming soon) - Chocolatey (Coming soon) -4. [Docker Hub](https://hub.docker.com/repository/docker/vultr/vultr-cli) - +4. Docker + ### GitHub Release If you are to visit the `vultr-cli` [releases](https://github.com/vultr/vultr-cli/releases) page. You can download a compiled version of `vultr-cli` for you Linux/MacOS/Windows in 64bit. -### Building from source +### Building from source You will need Go installed on your machine in order to work with the source (and make if you decide to pull the repo down). -`go get -u github.com/vultr/vultr-cli` +`go get -u github.com/vultr/vultr-cli/v2` -Another way to build from source is to +Another way to build from source is to -``` +```sh git clone git@github.com:vultr/vultr-cli.git or git clone https://github.com/vultr/vultr-cli.git cd vultr-cli -make build_(pass name of os + arch) +make builds/vultr-cli_(pass name of os + arch, as shown below) ``` The available make build options are - make builds/vultr-cli_darwin_amd64 -- make builds/vultr-cli_darwin_arm64 +- make builds/vultr-cli_darwin_arm64 - make builds/vultr-cli_linux_386 -- make builds/builds/vultr-cli_linux_amd64 -- make builds/builds/vultr-cli_linux_arm64 +- make builds/vultr-cli_linux_amd64 +- make builds/vultr-cli_linux_arm64 - make builds/vultr-cli_windows_386.exe - make builds/vultr-cli_windows_amd64.exe +- make builds/vultr-cli_linux_arm Note that the latter method will install the `vultr-cli` executable in `builds/vultr-cli_(name of os + arch)`. +### Installing on Arch Linux + +```sh +pacman -S vultr-cli +``` + ### Installing via Brew -You will need to tap for formula -``` sh -brew tap vultr/vultr-cli +```sh +brew install vultr/vultr-cli/vultr-cli +``` + +### Installing on Fedora + +```sh +dnf install vultr-cli +``` + +### Installing on OpenBSD + +```sh +pkg_add vultr-cli +``` + +### Docker +You can find the image on [Docker Hub](https://hub.docker.com/repository/docker/vultr/vultr-cli). To install the latest version via `docker`: + +```sh +docker pull vultr/vultr-cli:latest +``` + +To pull an older image, you can pass the version string in the tag. For example: +```sh +docker pull vultr/vultr-cli:v2.15.1 ``` -Then install the formula +The available versions are listed [here](https://github.com/vultr/vultr-cli/releases). + +As described in the next section, you must authenticate in order to use the CLI. To pass the environment variable into docker, you can do so via: -```sh -brew install vultr-cli +```sh +docker run -e VULTR_API_KEY vultr/vultr-cli:latest instance list ``` +This assumes you've already set the environment variable in your shell environment, otherwise, you can pass it in via `-e VULTR_API_KEY=` + ## Using Vultr-cli ### Authentication -In order to use `vultr-cli` you will need to export your [Vultr API KEY](https://my.vultr.com/settings/#settingsapi) +In order to use `vultr-cli` you will need to export your [Vultr API KEY](https://my.vultr.com/settings/#settingsapi) -`export VULTR_API_KEY=your_api_key` +`export VULTR_API_KEY=` ### Examples `vultr-cli` can interact with all of your Vultr resources. Here are some basic examples to get you started: -##### List all available servers -`vultr-cli server list` +##### List all available instances +`vultr-cli instance list` -##### Create a server -`vultr-cli server create --region --plan --os --hostname ` +##### Create an instance +`vultr-cli instance create --region --plan --os --host ` ##### Create a DNS Domain `vultr-cli dns domain create --domain --ip ` +##### Utilizing a boolean flag +You should use = when using a boolean flag. + +`vultr-cli instance create --region --plan --os --host --notify=true` ##### Utilizing the config flag -The config flag can be used to specify the vultr-cli.yaml file path when it's outside the default location. If the file has the `api-key` defined, the CLI will use the vultr-cli.yaml config, otherwise it will default to reading the environment variable for the api key. +The config flag can be used to specify the vultr-cli.yaml file path when it's outside the default location (default is $HOME/.vultr-cli.yaml). If the file has the `api-key` defined, the CLI will use the vultr-cli.yaml config, otherwise it will default to reading the environment variable for the api key. `vultr-cli instance list --config /Users/myuser/vultr-cli.yaml` ### Example vultr-cli.yaml config file + +Currently the only available field that you can use with a config file is `api-key`. Your yaml file will have a single entry which would be: + `api-key: MYKEY` +### CLI Autocompletion +`vultr-cli completion` will return autocompletions, but this feature requires setup. + +Some guides: + +
+

Bash:

+ $ source <(yourprogram completion bash) + + To load completions for each session, execute once: + Linux: + $ yourprogram completion bash > /etc/bash_completion.d/yourprogram + + macOS: + $ yourprogram completion bash > /usr/local/etc/bash_completion.d/yourprogram + +

Zsh:

+ If shell completion is not already enabled in your environment, + you will need to enable it. You can execute the following once: + + $ echo "autoload -U compinit; compinit" >> ~/.zshrc + + To load completions for each session, execute once: + $ yourprogram completion zsh > "${fpath[1]}/_yourprogram" + + You will need to start a new shell for this setup to take effect. + +

fish:

+ $ yourprogram completion fish | source + + To load completions for each session, execute once: + $ yourprogram completion fish > ~/.config/fish/completions/yourprogram.fish + +

PowerShell:

+ PS> yourprogram completion powershell | Out-String | Invoke-Expression + + To load completions for every new session, run: + PS> yourprogram completion powershell > yourprogram.ps1 + and source this file from your PowerShell profile. +
+ ## Contributing Feel free to send pull requests our way! Please see the [contributing guidelines](CONTRIBUTING.md). diff --git a/cmd/account/account.go b/cmd/account/account.go index 1564c6ee..f14322f4 100644 --- a/cmd/account/account.go +++ b/cmd/account/account.go @@ -1,28 +1,14 @@ -// Copyright © 2019 The Vultr-cli Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - +// Package account provides the account functionality for the CLI package account import ( "context" - "fmt" - "os" + "errors" "github.com/spf13/cobra" - "github.com/spf13/viper" - "github.com/vultr/govultr/v2" - "github.com/vultr/vultr-cli/pkg/cli" + "github.com/vultr/govultr/v3" + "github.com/vultr/vultr-cli/v3/cmd/utils" + "github.com/vultr/vultr-cli/v3/pkg/cli" ) var ( @@ -33,34 +19,23 @@ var ( ` ) -// Interface for account -type AccountInterface interface { - Get() (*govultr.Account, error) - validate(cmd *cobra.Command, args []string) -} - -// Options for account -type Options struct { - Base *cli.Base -} - -// NewAccountOptions returns Options struct -func NewAccountOptions(base *cli.Base) *Options { - return &Options{Base: base} -} - // NewCmdAccount creates a cobra command for Account func NewCmdAccount(base *cli.Base) *cobra.Command { - o := NewAccountOptions(base) + o := &options{Base: base} cmd := &cobra.Command{ Use: "account", Short: "get account information", Long: accountLong, Example: accountExample, + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + if !o.Base.HasAuth { + return errors.New(utils.APIKeyError) + } + return nil + }, Run: func(cmd *cobra.Command, args []string) { - o.validate(cmd, args) - account, err := o.Get() + account, err := o.get() o.Base.Printer.Display(&AccountPrinter{Account: account}, err) }, @@ -69,17 +44,11 @@ func NewCmdAccount(base *cli.Base) *cobra.Command { return cmd } -func (o *Options) validate(cmd *cobra.Command, args []string) { - o.Base.Printer.Output = viper.GetString("output") +type options struct { + Base *cli.Base } -// Get account information -func (o *Options) Get() (*govultr.Account, error) { - account, err := o.Base.Client.Account.Get(context.Background()) - if err != nil { - fmt.Printf("Error getting account information : %v\n", err) - os.Exit(1) - } - - return account, nil +func (o *options) get() (*govultr.Account, error) { + account, _, err := o.Base.Client.Account.Get(context.Background()) + return account, err } diff --git a/cmd/account/account_test.go b/cmd/account/account_test.go deleted file mode 100644 index 80118524..00000000 --- a/cmd/account/account_test.go +++ /dev/null @@ -1,75 +0,0 @@ -package account - -import ( - "context" - "reflect" - "testing" - - "github.com/vultr/vultr-cli/pkg/cli" - - "github.com/vultr/govultr/v2" -) - -type mockVultrAccount struct { - client *govultr.Client -} - -func (m mockVultrAccount) Get(ctx context.Context) (*govultr.Account, error) { - return &govultr.Account{ - Balance: 10, - PendingCharges: 100, - Name: "John Smith", - Email: "john@example.com", - ACL: []string{"manage_users", "subscriptions", "billing"}, - }, nil -} - -func TestNewAccountOptions(t *testing.T) { - accountOption := NewAccountOptions(&cli.Base{Client: &govultr.Client{Account: mockVultrAccount{nil}}}) - - ref := reflect.TypeOf(accountOption) - if _, ok := ref.MethodByName("Get"); !ok { - t.Errorf("Missing get function") - } - - if _, ok := ref.MethodByName("validate"); ok { - t.Errorf("validate isn't exported shouldn't be accessible") - } - - aInterface := reflect.TypeOf(new(AccountInterface)).Elem() - if !ref.Implements(aInterface) { - t.Errorf("Options does not implement AccountInterface") - } -} - -func TestNewCmdAccount(t *testing.T) { - cmd := NewCmdAccount(&cli.Base{Client: &govultr.Client{Account: mockVultrAccount{nil}}}) - - if cmd.Short != "get account information" { - t.Errorf("invalid short") - } - - if cmd.Use != "account" { - t.Errorf("invalid account") - } - -} - -func TestOptions_Get(t *testing.T) { - a := NewAccountOptions(&cli.Base{Client: &govultr.Client{Account: mockVultrAccount{nil}}}) - - expectedAccount := &govultr.Account{ - Balance: 10, - PendingCharges: 100, - Name: "John Smith", - Email: "john@example.com", - ACL: []string{"manage_users", "subscriptions", "billing"}, - } - - account, _ := a.Get() - - if !reflect.DeepEqual(account, expectedAccount) { - t.Errorf("OSOptions.list returned %v expected %v", account, expectedAccount) - } - -} diff --git a/cmd/account/printer.go b/cmd/account/printer.go index adb0f0b9..3bc2774a 100644 --- a/cmd/account/printer.go +++ b/cmd/account/printer.go @@ -1,10 +1,11 @@ package account import ( - "encoding/json" + "strconv" - "github.com/go-yaml/yaml" - "github.com/vultr/govultr/v2" + "github.com/vultr/govultr/v3" + "github.com/vultr/vultr-cli/v3/cmd/printer" + "github.com/vultr/vultr-cli/v3/cmd/utils" ) // AccountPrinter ... @@ -13,35 +14,42 @@ type AccountPrinter struct { } // JSON ... -func (s *AccountPrinter) JSON() []byte { - prettyJSON, err := json.MarshalIndent(s, "", " ") - if err != nil { - panic("move this into byte") - } - - return prettyJSON +func (a *AccountPrinter) JSON() []byte { + return printer.MarshalObject(a, "json") } -// Yaml ... -func (s *AccountPrinter) Yaml() []byte { - yam, err := yaml.Marshal(s) - if err != nil { - panic("move this into byte") - } - return yam +// YAML ... +func (a *AccountPrinter) YAML() []byte { + return printer.MarshalObject(a, "yaml") } // Columns ... -func (a *AccountPrinter) Columns() map[int][]interface{} { - return map[int][]interface{}{0: {"BALANCE", "PENDING CHARGES", "LAST PAYMENT DATE", "LAST PAYMENT AMOUNT", "NAME", "EMAIL", "ACLS"}} +func (a *AccountPrinter) Columns() [][]string { + return [][]string{0: { + "BALANCE", + "PENDING CHARGES", + "LAST PAYMENT DATE", + "LAST PAYMENT AMOUNT", + "NAME", + "EMAIL", + "ACLS", + }} } // Data ... -func (a *AccountPrinter) Data() map[int][]interface{} { - return map[int][]interface{}{0: {a.Account.Balance, a.Account.PendingCharges, a.Account.LastPaymentDate, a.Account.LastPaymentAmount, a.Account.Name, a.Account.Email, a.Account.ACL}} +func (a *AccountPrinter) Data() [][]string { + return [][]string{0: { + strconv.FormatFloat(float64(a.Account.Balance), 'f', utils.DecimalPrecision, 32), + strconv.FormatFloat(float64(a.Account.PendingCharges), 'f', utils.DecimalPrecision, 32), + a.Account.LastPaymentDate, + strconv.FormatFloat(float64(a.Account.LastPaymentAmount), 'f', utils.DecimalPrecision, 32), + a.Account.Name, + a.Account.Email, + printer.ArrayOfStringsToString(a.Account.ACL), + }} } // Paging ... -func (a *AccountPrinter) Paging() map[int][]interface{} { +func (a *AccountPrinter) Paging() [][]string { return nil } diff --git a/cmd/applications/applications.go b/cmd/applications/applications.go index 2529930f..38288e60 100644 --- a/cmd/applications/applications.go +++ b/cmd/applications/applications.go @@ -1,28 +1,15 @@ +// Package applications provides the application functionality for the CLI package applications -// Copyright © 2019 The Vultr-cli Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - import ( "context" + "fmt" "github.com/spf13/cobra" - "github.com/spf13/viper" - "github.com/vultr/govultr/v2" - "github.com/vultr/vultr-cli/cmd/printer" - "github.com/vultr/vultr-cli/cmd/utils" - "github.com/vultr/vultr-cli/pkg/cli" + "github.com/vultr/govultr/v3" + "github.com/vultr/vultr-cli/v3/cmd/printer" + "github.com/vultr/vultr-cli/v3/cmd/utils" + "github.com/vultr/vultr-cli/v3/pkg/cli" ) var ( @@ -45,61 +32,60 @@ var ( ` ) -// Interface for regions -type Interface interface { - List() ([]govultr.Application, *govultr.Meta, error) - validate(cmd *cobra.Command, args []string) -} - -// Options for regions -type Options struct { - Base *cli.Base - Printer *printer.Output -} - -func NewApplicationOptions(base *cli.Base) *Options { - return &Options{Base: base} -} - // NewCmdApplications creates cobra command for applications func NewCmdApplications(base *cli.Base) *cobra.Command { - o := NewApplicationOptions(base) + o := &options{Base: base} + cmd := &cobra.Command{ Use: "apps", - Aliases: []string{"a", "application", "applications", "app"}, Short: "display applications", + Aliases: []string{"a", "application", "applications", "app"}, Long: appLong, Example: appExample, + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + utils.SetOptions(o.Base, cmd, args) + return nil + }, } + // List list := &cobra.Command{ Use: "list", - Aliases: []string{"l"}, Short: "list applications", + Aliases: []string{"l"}, Long: listLong, Example: listExample, - Run: func(cmd *cobra.Command, args []string) { - o.validate(cmd, args) - apps, meta, err := o.List() - data := &printer.Applications{Applications: apps, Meta: meta} - o.Printer.Display(data, err) + RunE: func(cmd *cobra.Command, args []string) error { + apps, meta, err := o.list() + if err != nil { + return fmt.Errorf("error retrieving application list : %v", err) + } + + data := &ApplicationsPrinter{Applications: apps, Meta: meta} + o.Base.Printer.Display(data, err) + + return nil }, } list.Flags().StringP("cursor", "c", "", "(optional) Cursor for paging.") - list.Flags().IntP("per-page", "p", 100, "(optional) Number of items requested per page. Default is 100 and Max is 500.") + list.Flags().IntP( + "per-page", + "p", + utils.PerPageDefault, + fmt.Sprintf("(optional) Number of items requested per page. Default is %d and Max is 500.", utils.PerPageDefault), + ) cmd.AddCommand(list) return cmd } -func (o *Options) validate(cmd *cobra.Command, args []string) { - o.Base.Printer.Output = viper.GetString("output") - o.Base.Options = utils.GetPaging(cmd) - o.Base.Args = args +type options struct { + Base *cli.Base + Printer *printer.Output } -// List all applications -func (o *Options) List() ([]govultr.Application, *govultr.Meta, error) { - return o.Base.Client.Application.List(context.Background(), o.Base.Options) +func (o *options) list() ([]govultr.Application, *govultr.Meta, error) { + list, meta, _, err := o.Base.Client.Application.List(context.Background(), o.Base.Options) + return list, meta, err } diff --git a/cmd/applications/applications_test.go b/cmd/applications/applications_test.go deleted file mode 100644 index 3470c373..00000000 --- a/cmd/applications/applications_test.go +++ /dev/null @@ -1,91 +0,0 @@ -package applications - -import ( - "context" - "reflect" - "testing" - - "github.com/vultr/vultr-cli/pkg/cli" - - "github.com/vultr/govultr/v2" -) - -type mockVultrApplications struct { - client *govultr.Client -} - -func (m mockVultrApplications) List(ctx context.Context, options *govultr.ListOptions) ([]govultr.Application, *govultr.Meta, error) { - return []govultr.Application{ - { - ID: 1, - Name: "LEMP", - ShortName: "LEMP", - DeployName: "Lemp on CentOS 6", - }, - }, &govultr.Meta{ - Total: 1, - Links: nil, - }, nil -} - -func TestOptions_List(t *testing.T) { - appOption := NewApplicationOptions(&cli.Base{Client: &govultr.Client{Application: mockVultrApplications{nil}}}) - - expected := []govultr.Application{ - { - ID: 1, - Name: "LEMP", - ShortName: "LEMP", - DeployName: "Lemp on CentOS 6", - }, - } - expectedMeta := &govultr.Meta{ - Total: 1, - Links: nil, - } - - app, meta, _ := appOption.List() - - if !reflect.DeepEqual(expected, app) { - t.Errorf("AppOptions.list returned %v expected %v", app, expected) - } - - if !reflect.DeepEqual(expectedMeta, meta) { - t.Errorf("AppOptions.list returned %v expected %v", meta, expectedMeta) - } -} - -func TestNewApplicationOptions(t *testing.T) { - appOption := NewApplicationOptions(&cli.Base{Client: &govultr.Client{Application: mockVultrApplications{nil}}}) - - ref := reflect.TypeOf(appOption) - if _, ok := ref.MethodByName("List"); !ok { - t.Errorf("Missing list function") - } - - if _, ok := ref.MethodByName("validate"); ok { - t.Errorf("validate isn't exported shouldn't be accessible") - } - - pInterface := reflect.TypeOf(new(Interface)).Elem() - if !ref.Implements(pInterface) { - t.Errorf("Options does not implement Interface") - } -} - -func TestNewCmdApplications(t *testing.T) { - cmd := NewCmdApplications(&cli.Base{Client: &govultr.Client{Application: mockVultrApplications{nil}}}) - - if cmd.Short != "display applications" { - t.Errorf("invalid short") - } - - if cmd.Use != "apps" { - t.Errorf("invalid apps") - } - - alias := []string{"a", "application", "applications", "app"} - if !reflect.DeepEqual(cmd.Aliases, alias) { - t.Errorf("expected alias %v got %v", alias, cmd.Aliases) - } -} diff --git a/cmd/applications/printer.go b/cmd/applications/printer.go new file mode 100644 index 00000000..b24d8613 --- /dev/null +++ b/cmd/applications/printer.go @@ -0,0 +1,66 @@ +package applications + +import ( + "strconv" + + "github.com/vultr/govultr/v3" + "github.com/vultr/vultr-cli/v3/cmd/printer" +) + +// ApplicationsPrinter represents the plans data from the API +type ApplicationsPrinter struct { + Applications []govultr.Application `json:"applications"` + Meta *govultr.Meta `json:"meta"` +} + +// JSON provides the JSON formatted byte data +func (a *ApplicationsPrinter) JSON() []byte { + return printer.MarshalObject(a, "json") +} + +// YAML provides the YAML formatted byte data +func (a *ApplicationsPrinter) YAML() []byte { + return printer.MarshalObject(a, "yaml") +} + +// Columns provides the plan columns for the printer +func (a *ApplicationsPrinter) Columns() [][]string { + return [][]string{0: { + "ID", + "NAME", + "SHORT NAME", + "DEPLOY NAME", + "TYPE", + "VENDOR", + "IMAGE ID", + }} +} + +// Data provides the plan data for the printer +func (a *ApplicationsPrinter) Data() [][]string { + var data [][]string + + if len(a.Applications) == 0 { + data = append(data, []string{"---", "---", "---", "---", "---", "---", "---"}) + return data + } + + for i := range a.Applications { + data = append(data, []string{ + strconv.Itoa(a.Applications[i].ID), + a.Applications[i].Name, + a.Applications[i].ShortName, + a.Applications[i].DeployName, + a.Applications[i].Type, + a.Applications[i].Vendor, + a.Applications[i].ImageID, + }) + } + + return data +} + +// Paging validates and forms the paging data for output +func (a *ApplicationsPrinter) Paging() [][]string { + return printer.NewPaging(a.Meta.Total, &a.Meta.Links.Next, &a.Meta.Links.Prev).Compose() +} diff --git a/cmd/backups.go b/cmd/backups.go deleted file mode 100644 index c99c6576..00000000 --- a/cmd/backups.go +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright © 2019 The Vultr-cli Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package cmd - -import ( - "context" - "errors" - "fmt" - "os" - - "github.com/spf13/cobra" - "github.com/vultr/vultr-cli/cmd/printer" -) - -// Backups represents the application command -func Backups() *cobra.Command { - backupsCmd := &cobra.Command{ - Use: "backups", - Aliases: []string{"a"}, - Short: "Display backups", - } - - backupsCmd.AddCommand(backupsList, backupsGet) - - backupsList.Flags().StringP("cursor", "c", "", "(optional) Cursor for paging.") - backupsList.Flags().IntP("per-page", "p", 100, "(optional) Number of items requested per page. Default is 100 and Max is 500.") - - return backupsCmd -} - -var backupsList = &cobra.Command{ - Use: "list", - Short: "list backups", - Aliases: []string{"l"}, - Run: func(cmd *cobra.Command, args []string) { - options := getPaging(cmd) - backups, meta, err := client.Backup.List(context.TODO(), options) - if err != nil { - fmt.Printf("error getting backups : %v\n", err) - os.Exit(1) - } - - printer.Backups(backups, meta) - }, -} - -var backupsGet = &cobra.Command{ - Use: "get", - Short: "get backup", - Args: func(cmd *cobra.Command, args []string) error { - if len(args) < 1 { - return errors.New("please provide a backupID") - } - return nil - }, - Run: func(cmd *cobra.Command, args []string) { - backup, err := client.Backup.Get(context.TODO(), args[0]) - if err != nil { - fmt.Printf("error getting backup : %v\n", err) - os.Exit(1) - } - - printer.Backup(backup) - }, -} diff --git a/cmd/backups/backups.go b/cmd/backups/backups.go new file mode 100644 index 00000000..8a476f5b --- /dev/null +++ b/cmd/backups/backups.go @@ -0,0 +1,110 @@ +// Package backups provides access to the backups for the CLI +package backups + +import ( + "errors" + "fmt" + "os" + + "github.com/spf13/cobra" + "github.com/vultr/govultr/v3" + "github.com/vultr/vultr-cli/v3/cmd/printer" + "github.com/vultr/vultr-cli/v3/cmd/utils" + "github.com/vultr/vultr-cli/v3/pkg/cli" +) + +var ( + backupsLong = `` + backupsExample = `` + listLong = `` + listExample = `` + getLong = `` + getExample = `` +) + +// NewCmdBackups provides the backup command for the CLI +func NewCmdBackups(base *cli.Base) *cobra.Command { + o := &options{Base: base} + + cmd := &cobra.Command{ + Use: "backups", + Aliases: []string{"backup", "b"}, + Short: "user commands", + Long: backupsLong, + Example: backupsExample, + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + utils.SetOptions(o.Base, cmd, args) + if !o.Base.HasAuth { + return errors.New(utils.APIKeyError) + } + return nil + }, + } + + // List + list := &cobra.Command{ + Use: "list", + Short: "list all backups", + Aliases: []string{"l"}, + Long: listLong, + Example: listExample, + Run: func(cmd *cobra.Command, args []string) { + o.Base.Options = utils.GetPaging(cmd) + backups, meta, err := o.list() + if err != nil { + printer.Error(fmt.Errorf("error retrieving backups list : %v", err)) + os.Exit(1) + } + data := &BackupsPrinter{Backups: backups, Meta: meta} + o.Base.Printer.Display(data, err) + }, + } + + list.Flags().StringP("cursor", "c", "", "(optional) Cursor for paging.") + list.Flags().IntP( + "per-page", + "p", + utils.PerPageDefault, + fmt.Sprintf("(optional) Number of items requested per page. Default is %d and Max is 500.", utils.PerPageDefault), + ) + + // Get + get := &cobra.Command{ + Use: "get", + Short: "get a backup", + Long: getLong, + Example: getExample, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("please provide a backup ID") + } + return nil + }, + Run: func(cmd *cobra.Command, args []string) { + backup, err := o.get() + if err != nil { + panic(fmt.Errorf("error retrieving backup : %v", err)) + } + + data := &BackupPrinter{Backup: backup} + o.Base.Printer.Display(data, err) + }, + } + + cmd.AddCommand(get, list) + return cmd +} + +type options struct { + Base *cli.Base +} + +func (b *options) list() ([]govultr.Backup, *govultr.Meta, error) { + backups, meta, _, err := b.Base.Client.Backup.List(b.Base.Context, b.Base.Options) + return backups, meta, err +} + +func (b *options) get() (*govultr.Backup, error) { + backup, _, err := b.Base.Client.Backup.Get(b.Base.Context, b.Base.Args[0]) + return backup, err +} diff --git a/cmd/backups/printer.go b/cmd/backups/printer.go new file mode 100644 index 00000000..3d123252 --- /dev/null +++ b/cmd/backups/printer.go @@ -0,0 +1,99 @@ +package backups + +import ( + "strconv" + + "github.com/vultr/govultr/v3" + "github.com/vultr/vultr-cli/v3/cmd/printer" +) + +// BackupsPrinter ... +type BackupsPrinter struct { + Backups []govultr.Backup `json:"backups"` + Meta *govultr.Meta `json:"meta"` +} + +// JSON ... +func (b *BackupsPrinter) JSON() []byte { + return printer.MarshalObject(b, "json") +} + +// YAML ... +func (b *BackupsPrinter) YAML() []byte { + return printer.MarshalObject(b, "yaml") +} + +// Columns ... +func (b *BackupsPrinter) Columns() [][]string { + return [][]string{0: { + "ID", + "DATE CREATED", + "DESCRIPTION", + "SIZE", + "STATUS", + }} +} + +// Data ... +func (b *BackupsPrinter) Data() [][]string { + data := [][]string{} + for i := range b.Backups { + data = append(data, []string{ + b.Backups[i].ID, + b.Backups[i].DateCreated, + b.Backups[i].Description, + strconv.Itoa(b.Backups[i].Size), + b.Backups[i].Status, + }) + } + return data +} + +// Paging ... +func (b *BackupsPrinter) Paging() [][]string { + return printer.NewPaging(b.Meta.Total, &b.Meta.Links.Next, &b.Meta.Links.Prev).Compose() +} + +// ====================================== + +// BackupPrinter ... +type BackupPrinter struct { + Backup *govultr.Backup `json:"backup"` +} + +// JSON ... +func (b *BackupPrinter) JSON() []byte { + return printer.MarshalObject(b, "json") +} + +// YAML ... +func (b *BackupPrinter) YAML() []byte { + return printer.MarshalObject(b, "yaml") +} + +// Columns ... +func (b *BackupPrinter) Columns() [][]string { + return [][]string{0: { + "ID", + "DATE CREATED", + "DESCRIPTION", + "SIZE", + "STATUS", + }} +} + +// Data ... +func (b *BackupPrinter) Data() [][]string { + return [][]string{0: { + b.Backup.ID, + b.Backup.DateCreated, + b.Backup.Description, + strconv.Itoa(b.Backup.Size), + b.Backup.Status, + }} +} + +// Paging ... +func (b *BackupPrinter) Paging() [][]string { + return nil +} diff --git a/cmd/bareMetal.go b/cmd/bareMetal.go deleted file mode 100644 index 1f5ddd27..00000000 --- a/cmd/bareMetal.go +++ /dev/null @@ -1,406 +0,0 @@ -// Copyright © 2019 The Vultr-cli Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package cmd - -import ( - "context" - "encoding/base64" - "errors" - "fmt" - "os" - - "github.com/spf13/cobra" - "github.com/vultr/govultr/v2" - "github.com/vultr/vultr-cli/cmd/printer" -) - -// BareMetal represents the baremetal commands -func BareMetal() *cobra.Command { - bareMetalCmd := &cobra.Command{ - Use: "bare-metal", - Short: "bare-metal is used to access bare metal server commands", - Aliases: []string{"bm"}, - } - - bareMetalCmd.AddCommand( - BareMetalApp(), - bareMetalBandwidth, - bareMetalCreate, - bareMetalDelete, - bareMetalHalt, - bareMetalStart, - bareMetalGet, - bareMetalGetVNCUrl, - bareMetalListIPV4, - bareMetalListIPV6, - bareMetalList, - BareMetalOS(), - bareMetalReboot, - bareMetalReinstall, - BareMetalUserData(), - ) - - // create server - bareMetalCreate.Flags().StringP("region", "r", "", "ID of the region where the server will be created.") - bareMetalCreate.MarkFlagRequired("region") - bareMetalCreate.Flags().StringP("plan", "p", "", "ID of the plan that the server will subscribe to.") - bareMetalCreate.MarkFlagRequired("plan") - bareMetalCreate.Flags().IntP("operatingSystems", "o", 0, "ID of the operating system that will be installed on the server.") - bareMetalCreate.Flags().StringP("script", "s", "", "(optional) ID of the startup script that will run after the server is created.") - bareMetalCreate.Flags().StringP("snapshot", "", "", "(optional) ID of the snapshot that the server will be restored from.") - bareMetalCreate.Flags().StringP("ipv6", "i", "", "(optional) Whether IPv6 is enabled on the server. Possible values: 'yes', 'no'. Defaults to 'no'.") - bareMetalCreate.Flags().StringP("label", "l", "", "(optional) The label to assign to the server.") - bareMetalCreate.Flags().StringSliceP("ssh", "k", []string{}, "(optional) Comma separated list of SSH key IDs that will be added to the server.") - bareMetalCreate.Flags().IntP("app", "a", 0, "(optional) ID of the application that will be installed on the server.") - bareMetalCreate.Flags().StringP("userdata", "u", "", "(optional) A generic data store, which some provisioning tools and cloud operating systems use as a configuration file.") - bareMetalCreate.Flags().StringP("notify", "n", "", "(optional) Whether an activation email will be sent when the server is ready. Possible values: 'yes', 'no'. Defaults to 'yes'.") - bareMetalCreate.Flags().StringP("hostname", "m", "", "(optional) The hostname to assign to the server.") - bareMetalCreate.Flags().StringP("tag", "t", "", "(optional) The tag to assign to the server.") - bareMetalCreate.Flags().StringP("ripv4", "v", "", "(optional) IP address of the floating IP to use as the main IP of this server.") - - bareMetalList.Flags().StringP("cursor", "c", "", "(optional) Cursor for paging.") - bareMetalList.Flags().IntP("per-page", "p", 100, "(optional) Number of items requested per page. Default is 100 and Max is 500.") - - bareMetalListIPV4.Flags().StringP("cursor", "c", "", "(optional) Cursor for paging.") - bareMetalListIPV4.Flags().IntP("per-page", "p", 100, "(optional) Number of items requested per page. Default is 100 and Max is 500.") - - bareMetalListIPV6.Flags().StringP("cursor", "c", "", "(optional) Cursor for paging.") - bareMetalListIPV6.Flags().IntP("per-page", "p", 100, "(optional) Number of items requested per page. Default is 100 and Max is 500.") - - return bareMetalCmd -} - -var bareMetalCreate = &cobra.Command{ - Use: "create", - Short: "create a bare metal server", - Aliases: []string{"c"}, - Run: func(cmd *cobra.Command, args []string) { - region, _ := cmd.Flags().GetString("region") - plan, _ := cmd.Flags().GetString("plan") - osID, _ := cmd.Flags().GetInt("operatingSystems") - script, _ := cmd.Flags().GetString("script") - snapshot, _ := cmd.Flags().GetString("snapshot") - ipv6, _ := cmd.Flags().GetString("ipv6") - label, _ := cmd.Flags().GetString("label") - sshKeys, _ := cmd.Flags().GetStringSlice("ssh") - app, _ := cmd.Flags().GetInt("app") - userdata, _ := cmd.Flags().GetString("userdata") - notify, _ := cmd.Flags().GetString("notify") - hostname, _ := cmd.Flags().GetString("hostname") - tag, _ := cmd.Flags().GetString("tag") - ripv4, _ := cmd.Flags().GetString("ripv4") - - options := &govultr.BareMetalCreate{ - StartupScriptID: script, - Plan: plan, - SnapshotID: snapshot, - Label: label, - SSHKeyIDs: sshKeys, - Hostname: hostname, - Tag: tag, - ReservedIPv4: ripv4, - OsID: osID, - Region: region, - AppID: app, - } - - if userdata != "" { - options.UserData = base64.StdEncoding.EncodeToString([]byte(userdata)) - } - - if notify == "yes" { - options.ActivationEmail = govultr.BoolToBoolPtr(true) - } - - if ipv6 == "yes" { - options.EnableIPv6 = govultr.BoolToBoolPtr(true) - } - - osOptions := map[string]interface{}{"app_id": app, "snapshot_id": snapshot, "os_id": osID} - osOption, err := optionCheckBM(osOptions) - - if err != nil { - fmt.Printf("error creating bare metal server : %v\n", err) - os.Exit(1) - } - - // If no osOptions were selected and osID has a real value then set the osOptions to os_id - if osOption == "" && osID == 0 { - fmt.Printf("error creating bare metal server : an app, snapshot, or operatingSystems ID must be provided\n") - os.Exit(1) - } - - bm, err := client.BareMetalServer.Create(context.TODO(), options) - if err != nil { - fmt.Printf("%v\n", err) - os.Exit(1) - } - - printer.BareMetal(bm) - }, -} - -var bareMetalDelete = &cobra.Command{ - Use: "delete ", - Short: "Delete a bare metal server", - Aliases: []string{"destroy"}, - Args: func(cmd *cobra.Command, args []string) error { - if len(args) < 1 { - return errors.New("please provide a bareMetalID") - } - return nil - }, - Run: func(cmd *cobra.Command, args []string) { - if err := client.BareMetalServer.Delete(context.TODO(), args[0]); err != nil { - fmt.Printf("%v\n", err) - os.Exit(1) - } - - fmt.Println("deleted bare metal server") - }, -} - -var bareMetalList = &cobra.Command{ - Use: "list", - Short: "List all bare metal servers.", - Aliases: []string{"l"}, - Run: func(cmd *cobra.Command, args []string) { - options := getPaging(cmd) - list, meta, err := client.BareMetalServer.List(context.TODO(), options) - if err != nil { - fmt.Printf("%v\n", err) - os.Exit(1) - } - - printer.BareMetalList(list, meta) - }, -} - -var bareMetalGet = &cobra.Command{ - Use: "get ", - Short: "Get a bare metal server by ", - Args: func(cmd *cobra.Command, args []string) error { - if len(args) < 1 { - return errors.New("please provide a bareMetalID") - } - return nil - }, - Run: func(cmd *cobra.Command, args []string) { - srv, err := client.BareMetalServer.Get(context.TODO(), args[0]) - if err != nil { - fmt.Printf("%v\n", err) - os.Exit(1) - } - - printer.BareMetal(srv) - }, -} - -var bareMetalGetVNCUrl = &cobra.Command{ - Use: "vnc ", - Short: "Get a bare metal server's VNC url by ", - Args: func(cmd *cobra.Command, args []string) error { - if len(args) < 1 { - return errors.New("please provide a bareMetalID") - } - return nil - }, - Run: func(cmd *cobra.Command, args []string) { - vnc, err := client.BareMetalServer.GetVNCUrl(context.TODO(), args[0]) - if err != nil { - fmt.Printf("%v\n", err) - os.Exit(1) - } - - fmt.Println(vnc.URL) - }, -} - -var bareMetalBandwidth = &cobra.Command{ - Use: "bandwidth ", - Short: "Get a bare metal server's bandwidth usage", - Aliases: []string{"b"}, - Args: func(cmd *cobra.Command, args []string) error { - if len(args) < 1 { - return errors.New("please provide a bareMetalID") - } - return nil - }, - Run: func(cmd *cobra.Command, args []string) { - bw, err := client.BareMetalServer.GetBandwidth(context.TODO(), args[0]) - if err != nil { - fmt.Printf("%v\n", err) - os.Exit(1) - } - - printer.BareMetalBandwidth(bw) - }, -} - -var bareMetalHalt = &cobra.Command{ - Use: "halt ", - Short: "Halt a bare metal server.", - Long: `Halt a bare metal server. This is a hard power off, meaning that the power to the machine is severed. - The data on the machine will not be modified, and you will still be billed for the machine.`, - Aliases: []string{"h"}, - Args: func(cmd *cobra.Command, args []string) error { - if len(args) < 1 { - return errors.New("please provide a bareMetalID") - } - return nil - }, - Run: func(cmd *cobra.Command, args []string) { - if err := client.BareMetalServer.Halt(context.TODO(), args[0]); err != nil { - fmt.Printf("%v\n", err) - os.Exit(1) - } - - fmt.Println("bare metal server halted.") - }, -} - -var bareMetalStart = &cobra.Command{ - Use: "start ", - Short: "Start a bare metal server.", - Long: `Start a bare metal server.`, - Aliases: []string{"h"}, - Args: func(cmd *cobra.Command, args []string) error { - if len(args) < 1 { - return errors.New("please provide a bareMetalID") - } - return nil - }, - Run: func(cmd *cobra.Command, args []string) { - if err := client.BareMetalServer.Start(context.TODO(), args[0]); err != nil { - fmt.Printf("%v\n", err) - os.Exit(1) - } - - fmt.Println("bare metal server started.") - }, -} - -var bareMetalListIPV4 = &cobra.Command{ - Use: "ipv4 ", - Short: "List the IPv4 information of a bare metal server.", - Long: `List the IPv4 information of a bare metal server. IP information is only available for bare metal servers in the "active" state.`, - Args: func(cmd *cobra.Command, args []string) error { - if len(args) < 1 { - return errors.New("please provide a bareMetalID") - } - return nil - }, - Run: func(cmd *cobra.Command, args []string) { - options := getPaging(cmd) - info, meta, err := client.BareMetalServer.ListIPv4s(context.TODO(), args[0], options) - if err != nil { - fmt.Printf("%v\n", err) - os.Exit(1) - } - - printer.BareMetalIPV4Info(info, meta) - }, -} - -var bareMetalListIPV6 = &cobra.Command{ - Use: "ipv6 ", - Short: "List the IPv6 information of a bare metal server.", - Long: `List the IPv6 information of a bare metal server. IP information is only available for bare metal servers in the "active" state.`, - Args: func(cmd *cobra.Command, args []string) error { - if len(args) < 1 { - return errors.New("please provide a bareMetalID") - } - return nil - }, - Run: func(cmd *cobra.Command, args []string) { - options := getPaging(cmd) - info, meta, err := client.BareMetalServer.ListIPv6s(context.TODO(), args[0], options) - if err != nil { - fmt.Printf("%v\n", err) - os.Exit(1) - } - - printer.BareMetalIPV6Info(info, meta) - }, -} - -var bareMetalReboot = &cobra.Command{ - Use: "reboot ", - Short: "Reboot a bare metal server. This is a hard reboot, which means that the server is powered off, then back on.", - Aliases: []string{"r"}, - Args: func(cmd *cobra.Command, args []string) error { - if len(args) < 1 { - return errors.New("please provide a bareMetalID") - } - return nil - }, - Run: func(cmd *cobra.Command, args []string) { - if err := client.BareMetalServer.Reboot(context.TODO(), args[0]); err != nil { - fmt.Printf("%v\n", err) - os.Exit(1) - } - - fmt.Println("bare metal server rebooted.") - }, -} - -var bareMetalReinstall = &cobra.Command{ - Use: "reinstall ", - Short: "Reinstall the operating system on a bare metal server.", - Long: `Reinstall the operating system on a bare metal server. - All data will be permanently lost, but the IP address will remain the same. There is no going back from this call.`, - Args: func(cmd *cobra.Command, args []string) error { - if len(args) < 1 { - return errors.New("please provide a bareMetalID") - } - return nil - }, - Run: func(cmd *cobra.Command, args []string) { - if _, err := client.BareMetalServer.Reinstall(context.TODO(), args[0]); err != nil { - fmt.Printf("%v\n", err) - os.Exit(1) - } - - fmt.Println("bare metal server reinstalled.") - }, -} - -func optionCheckBM(options map[string]interface{}) (string, error) { - result := []string{} - - for k, v := range options { - switch v.(type) { - case int: - if v != 0 { - result = append(result, k) - } - case string: - if v != "" { - result = append(result, k) - } - } - } - - if len(result) > 1 { - return "", fmt.Errorf("Too many options have been selected : %v : please select one", result) - } - - // Return back an empty slice so we can possibly add in osID - if len(result) == 0 { - return "", nil - } - - return result[0], nil -} diff --git a/cmd/bareMetalApp.go b/cmd/bareMetalApp.go deleted file mode 100644 index 0d23fcf0..00000000 --- a/cmd/bareMetalApp.go +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright © 2019 The Vultr-cli Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package cmd - -import ( - "context" - "errors" - "fmt" - "os" - "strconv" - - "github.com/spf13/cobra" - "github.com/vultr/govultr/v2" - "github.com/vultr/vultr-cli/cmd/printer" -) - -// BareMetalApp represents the baremetal app commands -func BareMetalApp() *cobra.Command { - bareMetalAppCmd := &cobra.Command{ - Use: "app", - Short: "app is used to access bare metal server application commands", - Aliases: []string{"a"}, - } - - bareMetalAppCmd.AddCommand(bareMetalAppChange, bareMetalAppChangeList) - - return bareMetalAppCmd -} - -var bareMetalAppChange = &cobra.Command{ - Use: "change ", - Short: "Change a bare metal server's application", - Aliases: []string{"c"}, - Args: func(cmd *cobra.Command, args []string) error { - if len(args) < 2 { - return errors.New("please provide a bareMetalID and appID") - } - return nil - }, - Run: func(cmd *cobra.Command, args []string) { - appID, _ := strconv.Atoi(args[1]) - options := &govultr.BareMetalUpdate{ - AppID: appID, - } - - _, err := client.BareMetalServer.Update(context.TODO(), args[0], options) - if err != nil { - fmt.Printf("%v\n", err) - os.Exit(1) - } - - fmt.Println("bare metal server's application changed") - }, -} - -var bareMetalAppChangeList = &cobra.Command{ - Use: "list ", - Short: "available apps a bare metal server can change to.", - Long: ``, - Args: func(cmd *cobra.Command, args []string) error { - if len(args) < 1 { - return errors.New("please provide an bareMetalID") - } - return nil - }, - Run: func(cmd *cobra.Command, args []string) { - id := args[0] - list, err := client.BareMetalServer.GetUpgrades(context.TODO(), id) - - if err != nil { - fmt.Printf("error listing available applications : %v\n", err) - os.Exit(1) - } - - printer.AppList(list.Applications) - }, -} diff --git a/cmd/bareMetalOS.go b/cmd/bareMetalOS.go deleted file mode 100644 index e945ff48..00000000 --- a/cmd/bareMetalOS.go +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright © 2019 The Vultr-cli Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package cmd - -import ( - "context" - "errors" - "fmt" - "os" - "strconv" - - "github.com/spf13/cobra" - "github.com/vultr/govultr/v2" - "github.com/vultr/vultr-cli/cmd/printer" -) - -// BareMetalOS represents the baremetal operating system commands -func BareMetalOS() *cobra.Command { - bareMetalOSCmd := &cobra.Command{ - Use: "operatingSystems", - Short: "operatingSystems is used to access bare metal server operating system commands", - Aliases: []string{"o"}, - } - - bareMetalOSCmd.AddCommand(bareMetalOSChange, bareMetalOSChangeList) - - return bareMetalOSCmd -} - -var bareMetalOSChange = &cobra.Command{ - Use: "change ", - Short: "Change a bare metal server's operating system", - Aliases: []string{"c"}, - Args: func(cmd *cobra.Command, args []string) error { - if len(args) < 2 { - return errors.New("please provide a bareMetalID and osID") - } - return nil - }, - Run: func(cmd *cobra.Command, args []string) { - osid, _ := strconv.Atoi(args[1]) - options := &govultr.BareMetalUpdate{ - OsID: osid, - } - - if _, err := client.BareMetalServer.Update(context.TODO(), args[0], options); err != nil { - fmt.Printf("%v\n", err) - os.Exit(1) - } - - fmt.Println("bare metal server's operating system changed") - }, -} - -var bareMetalOSChangeList = &cobra.Command{ - Use: "list ", - Short: "available operating systems a bare metal server can change to.", - Long: ``, - Args: func(cmd *cobra.Command, args []string) error { - if len(args) < 1 { - return errors.New("please provide an bareMetalID") - } - return nil - }, - Run: func(cmd *cobra.Command, args []string) { - id := args[0] - list, err := client.BareMetalServer.GetUpgrades(context.TODO(), id) - - if err != nil { - fmt.Printf("error listing available operatingSystems : %v\n", err) - os.Exit(1) - } - - printer.OsList(list.OS) - }, -} diff --git a/cmd/bareMetalUserData.go b/cmd/bareMetalUserData.go deleted file mode 100644 index 9b38cada..00000000 --- a/cmd/bareMetalUserData.go +++ /dev/null @@ -1,95 +0,0 @@ -// Copyright © 2019 The Vultr-cli Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package cmd - -import ( - "context" - "encoding/base64" - "errors" - "fmt" - "io/ioutil" - "os" - - "github.com/spf13/cobra" - "github.com/vultr/govultr/v2" - "github.com/vultr/vultr-cli/cmd/printer" -) - -// BareMetalUserData represents the baremetal userdata commands -func BareMetalUserData() *cobra.Command { - bareMetalUserDataCmd := &cobra.Command{ - Use: "user-data", - Short: "user-data is used to access bare metal server user-data commands", - Aliases: []string{"u"}, - } - - bareMetalSetUserData.Flags().StringP("userdata", "d", "/dev/stdin", "file to read userdata from") - bareMetalUserDataCmd.AddCommand(bareMetalGetUserData, bareMetalSetUserData) - - return bareMetalUserDataCmd -} - -var bareMetalGetUserData = &cobra.Command{ - Use: "get ", - Short: "Get the user-data of a bare metal server.", - Aliases: []string{"g"}, - Args: func(cmd *cobra.Command, args []string) error { - if len(args) < 1 { - return errors.New("please provide a bareMetalID") - } - return nil - }, - Run: func(cmd *cobra.Command, args []string) { - u, err := client.BareMetalServer.GetUserData(context.Background(), args[0]) - if err != nil { - fmt.Printf("%v\n", err) - os.Exit(1) - } - - printer.UserData(u) - }, -} - -var bareMetalSetUserData = &cobra.Command{ - Use: "set ", - Short: "Set the plain text user-data of a bare metal server.", - Args: func(cmd *cobra.Command, args []string) error { - if len(args) < 1 { - return errors.New("please provide a bareMetalID") - } - return nil - }, - Run: func(cmd *cobra.Command, args []string) { - userData, _ := cmd.Flags().GetString("userdata") - - rawData, err := ioutil.ReadFile(userData) - if err != nil { - fmt.Printf("error reading user-data : %v\n", err) - os.Exit(1) - } - - options := &govultr.BareMetalUpdate{ - UserData: base64.StdEncoding.EncodeToString(rawData), - } - - _, err = client.BareMetalServer.Update(context.TODO(), args[0], options) - if err != nil { - fmt.Printf("error setting user-data : %v\n", err) - os.Exit(1) - } - - fmt.Println("Set user-data for bare metal") - }, -} diff --git a/cmd/baremetal/baremetal.go b/cmd/baremetal/baremetal.go new file mode 100644 index 00000000..13f01d22 --- /dev/null +++ b/cmd/baremetal/baremetal.go @@ -0,0 +1,1232 @@ +// Package baremetal provides functionality to perform operations on +// bare metal servers through the CLI +package baremetal + +import ( + "encoding/base64" + "errors" + "fmt" + "os" + "path/filepath" + + "github.com/spf13/cobra" + "github.com/vultr/govultr/v3" + "github.com/vultr/vultr-cli/v3/cmd/applications" + "github.com/vultr/vultr-cli/v3/cmd/ip" + "github.com/vultr/vultr-cli/v3/cmd/operatingsystems" + "github.com/vultr/vultr-cli/v3/cmd/printer" + "github.com/vultr/vultr-cli/v3/cmd/userdata" + "github.com/vultr/vultr-cli/v3/cmd/utils" + "github.com/vultr/vultr-cli/v3/pkg/cli" +) + +var ( + long = `Show all commands available to bare-metal` + example = ` + # Full example + vultr-cli bare-metal + ` + + listLong = `` + listExample = `` + getLong = `` + getExample = `` + createLong = `` + createExample = `` + deleteLong = `` + deleteExample = `` + + haltLong = ` + Halt a bare metal server. This is a hard power off, meaning that the power + to the machine is severed. The data on the machine will not be modified, + and you will still be billed for the machine. + ` + + haltExample = `` + + startLong = `` + startExample = `` + rebootLong = `This is a hard reboot, which means that the server is powered off, then back on.` + rebootExample = `` + + reinstallLong = `Reinstall the operating system on a bare metal server. +All data will be permanently lost, but the IP address will remain the same. +There is no going back from this call.` + reinstallExample = `` + + tagsLong = `Update the tags on a bare metal server` + tagsExample = ` + # Full example + vultr-cli bare-metal tags tags="tag-1,tag-2" + + # Shortened example with aliases + vultr-cli bm tags -t="tag-1,tag-2" + ` + + vpc2AttachLong = `Attaches an existing VPC 2.0 network to the specified bare metal server` + vpc2AttachExample = ` + # Full example + vultr-cli bare-metal vpc2 attach --vpc-id="2126b7d9-5e2a-491e-8840-838aa6b5f294" + ` + vpc2DetachLong = `Detaches an existing VPC 2.0 network from the specified bare metal server` + vpc2DetachExample = ` + # Full example + vultr-cli bare-metal vpc2 detach --vpc-id="2126b7d9-5e2a-491e-8840-838aa6b5f294" + ` + + applicationLong = `` + applicationExample = `` + + applicationChangeLong = `` + applicationChangeExample = `` + + applicationListLong = `` + applicationListExample = `` + + imageLong = `` + imageExample = `` + imageChangeLong = `` + imageChangeExample = `` + + operatingSystemLong = `` + operatingSystemExample = `` + operatingSystemChangeLong = `` + operatingSystemChangeExample = `` + operatingSystemListLong = `` + operatingSystemListExample = `` + + userDataLong = `` + userDataExample = `` + userDataGetLong = `` + userDataGetExample = `` + userDataSetLong = `` + userDataSetExample = `` + + vncLong = `` + vncExample = `` + + bandwidthLong = `` + bandwidthExample = `` + + ipv4Long = `IP information is only available for bare metal servers in the "active" state.` + ipv4Example = `` + + ipv6Long = ` +List the IPv6 information of a bare metal server. IP information is only available for bare metal servers in the "active" state. +` + ipv6Example = `` + + vpc2Long = `` + vpc2ListLong = `` + vpc2ListExample = `` +) + +// NewCmdBareMetal ... +func NewCmdBareMetal(base *cli.Base) *cobra.Command { //nolint:funlen,gocyclo + o := options{Base: base} + + cmd := &cobra.Command{ + Use: "bare-metal", + Short: "Bare metal server commands", + Aliases: []string{"bm"}, + Long: long, + Example: example, + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + utils.SetOptions(o.Base, cmd, args) + if !o.Base.HasAuth { + return errors.New(utils.APIKeyError) + } + return nil + }, + } + + // List + list := &cobra.Command{ + Use: "list", + Short: "List all bare metal servers.", + Aliases: []string{"l"}, + Long: listLong, + Example: listExample, + Run: func(cmd *cobra.Command, args []string) { + o.Base.Options = utils.GetPaging(cmd) + list, meta, err := o.list() + if err != nil { + printer.Error(fmt.Errorf("error retrieving bare metal list : %v", err)) + os.Exit(1) + } + data := &BareMetalsPrinter{BareMetals: list, Meta: meta} + o.Base.Printer.Display(data, err) + }, + } + + list.Flags().StringP("cursor", "c", "", "(optional) cursor for paging.") + list.Flags().IntP( + "per-page", + "p", + utils.PerPageDefault, + fmt.Sprintf("(optional) Number of items requested per page. Default is %d and Max is 500.", utils.PerPageDefault), + ) + + // Get + get := &cobra.Command{ + Use: "get ", + Short: "Get a bare metal server by ID", + Aliases: []string{"l"}, + Long: getLong, + Example: getExample, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("please provide a bare metal ID") + } + return nil + }, + Run: func(cmd *cobra.Command, args []string) { + bm, err := o.get() + if err != nil { + printer.Error(fmt.Errorf("error retrieving bare metal : %v", err)) + os.Exit(1) + } + data := &BareMetalPrinter{BareMetal: *bm} + o.Base.Printer.Display(data, err) + }, + } + + // Create + create := &cobra.Command{ + Use: "create", + Short: "Create a bare metal server", + Aliases: []string{"c"}, + Long: createLong, + Example: createExample, + Run: func(cmd *cobra.Command, args []string) { + o.CreateReq = parseCreateFlags(cmd) + bm, err := o.create() + if err != nil { + printer.Error(fmt.Errorf("error with bare metal create : %v", err)) + os.Exit(1) + } + + data := &BareMetalPrinter{BareMetal: *bm} + o.Base.Printer.Display(data, err) + }, + } + + create.Flags().StringP("region", "r", "", "ID of the region where the server will be created.") + create.Flags().StringP("plan", "p", "", "ID of the plan that the server will subscribe to.") + create.Flags().Int("os", 0, "ID of the operating system that will be installed on the server.") + create.Flags().StringP( + "script", + "s", + "", + "(optional) ID of the startup script that will run after the server is created.", + ) + create.Flags().StringP( + "snapshot", + "", + "", + "(optional) ID of the snapshot that the server will be restored from.", + ) + create.Flags().StringP( + "ipv6", + "i", + "", + "(optional) Whether IPv6 is enabled on the server. Possible values: 'yes', 'no'. Defaults to 'no'.", + ) + create.Flags().StringP("label", "l", "", "(optional) The label to assign to the server.") + create.Flags().StringSliceP( + "ssh", + "k", + []string{}, + "(optional) Comma separated list of SSH key IDs that will be added to the server.", + ) + create.Flags().IntP( + "app", + "a", + 0, + "(optional) ID of the application that will be installed on the server.", + ) + create.Flags().StringP("image", "", "", "(optional) Image ID of the application that will be installed on the server.") + create.Flags().StringP( + "userdata", + "u", + "", + "(optional) A generic data store, which some provisioning tools and cloud operating systems use as a configuration file.", + ) + create.Flags().StringP( + "notify", + "n", + "", + "(optional) Whether an activation email will be sent when the server is ready. Possible values: 'yes', 'no'. Defaults to 'yes'.", + ) + create.Flags().StringP("hostname", "m", "", "(optional) The hostname to assign to the server.") + create.Flags().StringP("tag", "t", "", "Deprecated: use `tags` instead. (optional) The tag to assign to the server.") + create.Flags().StringSliceP("tags", "", []string{}, "(optional) A comma separated list of tags to assign to the server.") + create.Flags().StringP("ripv4", "v", "", "(optional) IP address of the floating IP to use as the main IP of this server.") + create.Flags().BoolP("persistent_pxe", "x", false, "enable persistent_pxe | true or false") + + if err := create.MarkFlagRequired("region"); err != nil { + printer.Error(fmt.Errorf("error marking bare metal create 'region' flag required: %v", err)) + os.Exit(1) + } + + if err := create.MarkFlagRequired("plan"); err != nil { + printer.Error(fmt.Errorf("error marking bare metal create 'plan' flag required: %v", err)) + os.Exit(1) + } + + installFlags := []string{"app", "snapshot", "os", "image"} + create.MarkFlagsMutuallyExclusive(installFlags...) + create.MarkFlagsOneRequired(installFlags...) + + // Delete + del := &cobra.Command{ + Use: "delete ", + Short: "Delete a bare metal server", + Aliases: []string{"destroy"}, + Long: deleteLong, + Example: deleteExample, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("please provide a bare metal ID") + } + return nil + }, + Run: func(cmd *cobra.Command, args []string) { + if err := o.del(); err != nil { + printer.Error(fmt.Errorf("error deleting bare metal : %v", err)) + os.Exit(1) + } + o.Base.Printer.Display(printer.Info("bare metal server has been deleted"), nil) + }, + } + + // Halt + halt := &cobra.Command{ + Use: "halt ", + Short: "Halt a bare metal server.", + Aliases: []string{"h"}, + Long: haltLong, + Example: haltExample, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("please provide a bare metal ID") + } + return nil + }, + Run: func(cmd *cobra.Command, args []string) { + if err := o.halt(); err != nil { + printer.Error(fmt.Errorf("error halting bare metal : %v", err)) + os.Exit(1) + } + o.Base.Printer.Display(printer.Info("bare metal server has been halted"), nil) + }, + } + + // Start + start := &cobra.Command{ + Use: "start ", + Short: "Start a bare metal server.", + Long: startLong, + Example: startExample, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("please provide a bare metal ID") + } + return nil + }, + Run: func(cmd *cobra.Command, args []string) { + if err := o.start(); err != nil { + printer.Error(fmt.Errorf("error starting bare metal : %v", err)) + os.Exit(1) + } + + o.Base.Printer.Display(printer.Info("bare metal server has been started"), nil) + }, + } + + // Reboot + reboot := &cobra.Command{ + Use: "reboot ", + Short: "Reboot a bare metal server.", + Aliases: []string{"r"}, + Long: rebootLong, + Example: rebootExample, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("please provide a bare metal ID") + } + return nil + }, + Run: func(cmd *cobra.Command, args []string) { + if err := o.reboot(); err != nil { + printer.Error(fmt.Errorf("error rebooting bare metal : %v", err)) + os.Exit(1) + } + + o.Base.Printer.Display(printer.Info("bare metal server has been rebooted"), nil) + }, + } + + // Reinstall + reinstall := &cobra.Command{ + Use: "reinstall ", + Short: "Reinstall the operating system on a bare metal server.", + Long: reinstallLong, + Example: reinstallExample, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("please provide a bare metal ID") + } + return nil + }, + Run: func(cmd *cobra.Command, args []string) { + if err := o.reinstall(); err != nil { + printer.Error(fmt.Errorf("error reinstalling bare metal : %v", err)) + os.Exit(1) + } + + o.Base.Printer.Display(printer.Info("bare metal server has initiated reinstallation"), nil) + }, + } + + // Application + application := &cobra.Command{ + Use: "app", + Short: "app is used to access bare metal server application commands", + Aliases: []string{"a", "application"}, + Long: applicationLong, + Example: applicationExample, + } + + // Application Change + applicationChange := &cobra.Command{ + Use: "change ", + Short: "Change a bare metal server application", + Aliases: []string{"c"}, + Long: applicationChangeLong, + Example: applicationChangeExample, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("please provide a bare metal ID") + } + return nil + }, + Run: func(cmd *cobra.Command, args []string) { + appID, err := cmd.Flags().GetInt("app") + if err != nil { + printer.Error(fmt.Errorf("error parsing app flag for bare metal app ID change : %v", err)) + os.Exit(1) + } + + o.UpdateReq = &govultr.BareMetalUpdate{ + AppID: appID, + } + + bm, err := o.update() + if err != nil { + printer.Error(fmt.Errorf("error with bare metal update : %v", err)) + os.Exit(1) + } + + data := &BareMetalPrinter{BareMetal: *bm} + o.Base.Printer.Display(data, err) + }, + } + + applicationChange.Flags().IntP( + "app", + "a", + 0, + "ID of the application that will be installed on the server", + ) + + if err := applicationChange.MarkFlagRequired("app"); err != nil { + printer.Error(fmt.Errorf("error marking bare metal 'app' flag required : %v", err)) + os.Exit(1) + } + + // Application List + applicationList := &cobra.Command{ + Use: "list ", + Short: "Available apps for a bare metal server", + Long: applicationListLong, + Example: applicationListExample, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("please provide an bare metal ID") + } + return nil + }, + Run: func(cmd *cobra.Command, args []string) { + upgrades, err := o.getUpgrades() + if err != nil { + printer.Error(fmt.Errorf("error with bare metal get upgrades : %v", err)) + os.Exit(1) + } + data := &applications.ApplicationsPrinter{Applications: upgrades.Applications} + o.Base.Printer.Display(data, err) + }, + } + + application.AddCommand(applicationChange, applicationList) + + // Image + image := &cobra.Command{ + Use: "image", + Short: "image is used to access bare metal server image commands", + Aliases: []string{"i"}, + Long: imageLong, + Example: imageExample, + } + + // Image Change + imageChange := &cobra.Command{ + Use: "change ", + Short: "Change a bare metal server's image", + Aliases: []string{"c"}, + Long: imageChangeLong, + Example: imageChangeExample, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("please provide a bare metal ID") + } + return nil + }, + Run: func(cmd *cobra.Command, args []string) { + imageID, err := cmd.Flags().GetString("image") + if err != nil { + printer.Error(fmt.Errorf("error parsing image flag for bare metal image change : %v", err)) + os.Exit(1) + } + + o.UpdateReq = &govultr.BareMetalUpdate{ + ImageID: imageID, + } + + bm, err := o.update() + if err != nil { + printer.Error(fmt.Errorf("error with bare metal image update : %v", err)) + os.Exit(1) + } + + data := &BareMetalPrinter{BareMetal: *bm} + o.Base.Printer.Display(data, err) + }, + } + + imageChange.Flags().StringP( + "image", + "i", + "", + "ID of the image that will be installed on the server", + ) + + if err := imageChange.MarkFlagRequired("image"); err != nil { + printer.Error(fmt.Errorf("error marking bare metal 'image' flag required : %v", err)) + os.Exit(1) + } + + image.AddCommand(imageChange) + + // OS + operatingSystem := &cobra.Command{ + Use: "os", + Short: "Server operating system commands", + Aliases: []string{"o"}, + Long: operatingSystemLong, + Example: operatingSystemExample, + } + + // OS Change + operatingSystemChange := &cobra.Command{ + Use: "change ", + Short: "Change a bare metal server's image", + Aliases: []string{"c"}, + Long: operatingSystemChangeLong, + Example: operatingSystemChangeExample, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("please provide a bare metal ID") + } + return nil + }, + Run: func(cmd *cobra.Command, args []string) { + operatingSystemID, err := cmd.Flags().GetInt("os") + if err != nil { + printer.Error(fmt.Errorf("error parsing os flag for bare metal os change : %v", err)) + os.Exit(1) + } + + o.UpdateReq = &govultr.BareMetalUpdate{ + OsID: operatingSystemID, + } + + bm, err := o.update() + if err != nil { + printer.Error(fmt.Errorf("error with bare metal os update : %v", err)) + os.Exit(1) + } + + data := &BareMetalPrinter{BareMetal: *bm} + o.Base.Printer.Display(data, err) + }, + } + + operatingSystemChange.Flags().IntP( + "os", + "o", + 0, + "ID of the operating system that will be installed on the server", + ) + if err := operatingSystemChange.MarkFlagRequired("os"); err != nil { + printer.Error(fmt.Errorf("error marking bare metal 'os' flag required : %v", err)) + os.Exit(1) + } + + // Operating System List + operatingSystemList := &cobra.Command{ + Use: "list ", + Short: "Available operating systems for a bare metal server", + Long: operatingSystemListLong, + Example: operatingSystemListExample, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("please provide an bare metal ID") + } + return nil + }, + Run: func(cmd *cobra.Command, args []string) { + upgrades, err := o.getUpgrades() + if err != nil { + printer.Error(fmt.Errorf("error with bare metal get upgrades : %v", err)) + os.Exit(1) + } + + data := &operatingsystems.OSPrinter{OperatingSystems: upgrades.OS} + o.Base.Printer.Display(data, nil) + }, + } + + operatingSystem.AddCommand(operatingSystemChange, operatingSystemList) + + // User Data + userData := &cobra.Command{ + Use: "user-data", + Short: "Commands for bare metal server user data", + Aliases: []string{"u"}, + Long: userDataLong, + Example: userDataExample, + } + + // User Data Get + userDataGet := &cobra.Command{ + Use: "get ", + Short: "Get the user data of a bare metal server", + Aliases: []string{"g"}, + Long: userDataGetLong, + Example: userDataGetExample, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("please provide an bare metal ID") + } + return nil + }, + Run: func(cmd *cobra.Command, args []string) { + ud, err := o.getUserData() + if err != nil { + printer.Error(fmt.Errorf("error with bare metal get user data : %v", err)) + os.Exit(1) + } + + data := &userdata.UserDataPrinter{UserData: *ud} + o.Base.Printer.Display(data, nil) + }, + } + + // User Data Set + userDataSet := &cobra.Command{ + Use: "set ", + Short: "Set the plain text user-data of a bare metal server", + Long: userDataSetLong, + Example: userDataSetExample, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("please provide an bare metal ID") + } + return nil + }, + Run: func(cmd *cobra.Command, args []string) { + path, err := cmd.Flags().GetString("user-data") + if err != nil { + printer.Error(fmt.Errorf("error parsing user-data flag for bare metal user data set : %v", err)) + os.Exit(1) + } + + rawData, err := os.ReadFile(filepath.Clean(path)) + if err != nil { + printer.Error(fmt.Errorf("error reading user-data : %v", err)) + os.Exit(1) + } + + o.UpdateReq = &govultr.BareMetalUpdate{ + UserData: base64.StdEncoding.EncodeToString(rawData), + } + + _, errUpdate := o.update() + if err != nil { + printer.Error(fmt.Errorf("error updating bare metal user-data : %v", errUpdate)) + os.Exit(1) + } + + o.Base.Printer.Display(printer.Info("bare metal server user data has been set"), nil) + }, + } + + userDataSet.Flags().StringP("user-data", "d", "/dev/stdin", "file to read userdata from") + if err := userDataSet.MarkFlagRequired("user-data"); err != nil { + printer.Error(fmt.Errorf("error marking bare metal 'user-data' flag required: %v", err)) + os.Exit(1) + } + + userData.AddCommand(userDataGet, userDataSet) + + // VNC URL + vnc := &cobra.Command{ + Use: "vnc ", + Short: "get a bare metal server's VNC url", + Long: vncLong, + Example: vncExample, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("please provide a bare metal ID") + } + return nil + }, + Run: func(cmd *cobra.Command, args []string) { + vnc, err := o.getVNCURL() + if err != nil { + printer.Error(fmt.Errorf("error retrieving bare metal VNC URL : %v", err)) + os.Exit(1) + } + + data := &BareMetalVNCPrinter{VNC: *vnc} + o.Base.Printer.Display(data, nil) + }, + } + + // Bandwidth + bandwidth := &cobra.Command{ + Use: "bandwidth ", + Short: "Get a bare metal server's bandwidth usage", + Aliases: []string{"b"}, + Long: bandwidthLong, + Example: bandwidthExample, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("please provide a bare metal ID") + } + return nil + }, + Run: func(cmd *cobra.Command, args []string) { + bw, err := o.getBandwidth() + if err != nil { + printer.Error(fmt.Errorf("error retrieving bare metal bandwidth usage : %v", err)) + os.Exit(1) + } + + data := &BareMetalBandwidthPrinter{Bandwidth: *bw} + o.Base.Printer.Display(data, nil) + }, + } + + // Tags + tags := &cobra.Command{ + Use: "tags ", + Short: "add or modify tags on the bare metal server.", + Long: tagsLong, + Example: tagsExample, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("please provide a bare metal ID") + } + return nil + }, + Run: func(cmd *cobra.Command, args []string) { + tags, _ := cmd.Flags().GetStringSlice("tags") + o.UpdateReq = &govultr.BareMetalUpdate{ + Tags: tags, + } + + _, err := o.update() + if err != nil { + printer.Error(fmt.Errorf("error updating bare metal tags : %v", err)) + os.Exit(1) + } + + o.Base.Printer.Display(printer.Info("bare metal server tags have been updated"), nil) + }, + } + + tags.Flags().StringSliceP("tags", "t", []string{}, "A comma separated list of tags to apply to the server") + if err := tags.MarkFlagRequired("tags"); err != nil { + printer.Error(fmt.Errorf("error marking bare metal 'tags' flag required: %v", err)) + os.Exit(1) + } + + // IPv4 Addresses + ipv4 := &cobra.Command{ + Use: "ipv4 ", + Short: "List the IPv4 information of a bare metal server", + Long: ipv4Long, + Example: ipv4Example, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("please provide a bare metal ID") + } + return nil + }, + Run: func(cmd *cobra.Command, args []string) { + o.Base.Options = utils.GetPaging(cmd) + ipv4, meta, err := o.getIPv4Addresses() + if err != nil { + printer.Error(fmt.Errorf("error retrieving bare metal IPv4 information : %v", err)) + os.Exit(1) + } + data := &ip.IPv4sPrinter{IPv4s: ipv4, Meta: meta} + o.Base.Printer.Display(data, nil) + }, + } + + // IPv6 Addresses + ipv6 := &cobra.Command{ + Use: "ipv6 ", + Short: "list the IPv6 information of a bare metal server.", + Long: ipv6Long, + Example: ipv6Example, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("please provide a bare metal ID") + } + return nil + }, + Run: func(cmd *cobra.Command, args []string) { + o.Base.Options = utils.GetPaging(cmd) + ipv6, meta, err := o.getIPv6Addresses() + if err != nil { + printer.Error(fmt.Errorf("error retrieving bare metal IPv6 information : %v", err)) + os.Exit(1) + } + data := &ip.IPv6sPrinter{IPv6s: ipv6, Meta: meta} + o.Base.Printer.Display(data, nil) + }, + } + + // VPC2 + vpc2 := &cobra.Command{ + Use: "vpc2", + Short: "commands to manage vpc 2.0 on bare metal servers", + Long: vpc2Long, + } + + // VPC2 List + vpc2List := &cobra.Command{ + Use: "list ", + Short: "List all VPC2 networks attached to a server", + Aliases: []string{"l"}, + Long: vpc2ListLong, + Example: vpc2ListExample, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("please provide a bare metal ID") + } + return nil + }, + Run: func(cmd *cobra.Command, args []string) { + vpc2s, err := o.vpc2NetworksList() + if err != nil { + printer.Error(fmt.Errorf("error retrieving bare metal vpc2 information : %v", err)) + os.Exit(1) + } + data := &BareMetalVPC2sPrinter{VPC2s: vpc2s} + o.Base.Printer.Display(data, nil) + }, + } + + // VPC2 Attach + vpc2Attach := &cobra.Command{ + Use: "attach ", + Short: "Attach a VPC2 network to a server", + Long: vpc2AttachLong, + Example: vpc2AttachExample, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("please provide a bare metal ID") + } + return nil + }, + Run: func(cmd *cobra.Command, args []string) { + vpcID, errID := cmd.Flags().GetString("vpc-id") + if errID != nil { + printer.Error(fmt.Errorf("error parsing vpc-id flag for bare metal VPC2 attach : %v", errID)) + os.Exit(1) + } + + IPAddress, errIP := cmd.Flags().GetString("ip-address") + if errIP != nil { + printer.Error(fmt.Errorf("error parsing ip-address flag for bare metal VPC2 attach : %v", errIP)) + os.Exit(1) + } + + o.VPC2Req = &govultr.AttachVPC2Req{ + VPCID: vpcID, + IPAddress: &IPAddress, + } + + if err := o.vpc2NetworksAttach(); err != nil { + printer.Error(fmt.Errorf("error attaching bare metal to VPC2 : %v", err)) + os.Exit(1) + } + + o.Base.Printer.Display(printer.Info("bare metal server has been attached to VPC2 network"), nil) + }, + } + + vpc2Attach.Flags().StringP("vpc-id", "v", "", "the ID of the VPC 2.0 network you wish to attach") + vpc2Attach.Flags().StringP("ip-address", "i", "", "the IP address to use for this server on the attached VPC 2.0 network") + if errVPC := vpc2Attach.MarkFlagRequired("vpc-id"); errVPC != nil { + printer.Error(fmt.Errorf("error marking bare metal 'vpc-id' flag required for attach : %v", errVPC)) + os.Exit(1) + } + + // VPC2 Detach + vpc2Detach := &cobra.Command{ + Use: "detach ", + Short: "Detach a VPC2 network from a server", + Long: vpc2DetachLong, + Example: vpc2DetachExample, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("please provide a bare metal ID") + } + return nil + }, + Run: func(cmd *cobra.Command, args []string) { + vpcID, errID := cmd.Flags().GetString("vpc-id") + if errID != nil { + printer.Error(fmt.Errorf("error parsing vpc-id flag for bare metal VPC2 detach : %v", errID)) + os.Exit(1) + } + + o.VPC2ID = vpcID + if err := o.vpc2NetworksDetach(); err != nil { + printer.Error(fmt.Errorf("error detaching bare metal VPC2 : %v", err)) + os.Exit(1) + } + + o.Base.Printer.Display(printer.Info("bare metal server has been detached from VPC2 network"), nil) + }, + } + + vpc2Detach.Flags().StringP("vpc-id", "v", "", "the ID of the VPC 2.0 network you wish to detach") + if errVPC2 := vpc2Detach.MarkFlagRequired("vpc-id"); errVPC2 != nil { + printer.Error(fmt.Errorf("error marking bare metal 'vpc-id' flag required for detach : %v", errVPC2)) + os.Exit(1) + } + + vpc2.AddCommand(vpc2List, vpc2Attach, vpc2Detach) + + cmd.AddCommand( + get, + list, + create, + del, + halt, + start, + reboot, + reinstall, + application, + image, + operatingSystem, + userData, + vnc, + bandwidth, + tags, + ipv4, + ipv6, + vpc2, + ) + + return cmd +} + +type options struct { + Base *cli.Base + CreateReq *govultr.BareMetalCreate + UpdateReq *govultr.BareMetalUpdate + VPC2Req *govultr.AttachVPC2Req + VPC2ID string +} + +func (b *options) list() ([]govultr.BareMetalServer, *govultr.Meta, error) { + bms, meta, _, err := b.Base.Client.BareMetalServer.List(b.Base.Context, b.Base.Options) + return bms, meta, err +} + +func (b *options) get() (*govultr.BareMetalServer, error) { + bm, _, err := b.Base.Client.BareMetalServer.Get(b.Base.Context, b.Base.Args[0]) + return bm, err +} + +func (b *options) create() (*govultr.BareMetalServer, error) { + bm, _, err := b.Base.Client.BareMetalServer.Create(b.Base.Context, b.CreateReq) + return bm, err +} + +func (b *options) update() (*govultr.BareMetalServer, error) { + bm, _, err := b.Base.Client.BareMetalServer.Update(b.Base.Context, b.Base.Args[0], b.UpdateReq) + return bm, err +} + +func (b *options) del() error { + return b.Base.Client.BareMetalServer.Delete(b.Base.Context, b.Base.Args[0]) +} + +func (b *options) halt() error { + return b.Base.Client.BareMetalServer.Halt(b.Base.Context, b.Base.Args[0]) +} + +func (b *options) start() error { + return b.Base.Client.BareMetalServer.Start(b.Base.Context, b.Base.Args[0]) +} + +func (b *options) reboot() error { + return b.Base.Client.BareMetalServer.Reboot(b.Base.Context, b.Base.Args[0]) +} + +func (b *options) reinstall() error { + _, _, err := b.Base.Client.BareMetalServer.Reinstall(b.Base.Context, b.Base.Args[0]) + return err +} + +func (b *options) getUpgrades() (*govultr.Upgrades, error) { + list, _, err := b.Base.Client.BareMetalServer.GetUpgrades(b.Base.Context, b.Base.Args[0]) + return list, err +} + +func (b *options) getUserData() (*govultr.UserData, error) { + ud, _, err := b.Base.Client.BareMetalServer.GetUserData(b.Base.Context, b.Base.Args[0]) + return ud, err +} + +func (b *options) getVNCURL() (*govultr.VNCUrl, error) { + url, _, err := b.Base.Client.BareMetalServer.GetVNCUrl(b.Base.Context, b.Base.Args[0]) + return url, err +} + +func (b *options) getBandwidth() (*govultr.Bandwidth, error) { + bw, _, err := b.Base.Client.BareMetalServer.GetBandwidth(b.Base.Context, b.Base.Args[0]) + return bw, err +} + +func (b *options) getIPv4Addresses() ([]govultr.IPv4, *govultr.Meta, error) { + ips, meta, _, err := b.Base.Client.BareMetalServer.ListIPv4s(b.Base.Context, b.Base.Args[0], b.Base.Options) + return ips, meta, err +} + +func (b *options) getIPv6Addresses() ([]govultr.IPv6, *govultr.Meta, error) { + ips, meta, _, err := b.Base.Client.BareMetalServer.ListIPv6s(b.Base.Context, b.Base.Args[0], b.Base.Options) + return ips, meta, err +} + +func (b *options) vpc2NetworksList() ([]govultr.VPC2Info, error) { + vpc2s, _, err := b.Base.Client.BareMetalServer.ListVPC2Info(b.Base.Context, b.Base.Args[0]) + return vpc2s, err +} + +func (b *options) vpc2NetworksAttach() error { + return b.Base.Client.BareMetalServer.AttachVPC2(b.Base.Context, b.Base.Args[0], b.VPC2Req) +} + +func (b *options) vpc2NetworksDetach() error { + return b.Base.Client.BareMetalServer.DetachVPC2(b.Base.Context, b.Base.Args[0], b.VPC2ID) +} + +// ============================ + +func parseCreateFlags(cmd *cobra.Command) *govultr.BareMetalCreate { //nolint:funlen,gocyclo + region, err := cmd.Flags().GetString("region") + if err != nil { + printer.Error(fmt.Errorf("error parsing region flag for bare metal create : %v", err)) + os.Exit(1) + } + + plan, err := cmd.Flags().GetString("plan") + if err != nil { + printer.Error(fmt.Errorf("error parsing plan flag for bare metal create : %v", err)) + os.Exit(1) + } + + osID, err := cmd.Flags().GetInt("os") + if err != nil { + printer.Error(fmt.Errorf("error parsing os flag for bare metal create : %v", err)) + os.Exit(1) + } + + script, err := cmd.Flags().GetString("script") + if err != nil { + printer.Error(fmt.Errorf("error parsing script flag bare metal create : %v", err)) + os.Exit(1) + } + + snapshot, err := cmd.Flags().GetString("snapshot") + if err != nil { + printer.Error(fmt.Errorf("error parsing snapshot flag for bare metal create : %v", err)) + os.Exit(1) + } + + ipv6, err := cmd.Flags().GetString("ipv6") + if err != nil { + printer.Error(fmt.Errorf("error parsing ipv6 flag for bare metal create : %v", err)) + os.Exit(1) + } + + label, err := cmd.Flags().GetString("label") + if err != nil { + printer.Error(fmt.Errorf("error parsing label flag for bare metal create : %v", err)) + os.Exit(1) + } + + sshKeys, err := cmd.Flags().GetStringSlice("ssh") + if err != nil { + printer.Error(fmt.Errorf("error parsing ssh flag for bare metal create : %v", err)) + os.Exit(1) + } + + app, err := cmd.Flags().GetInt("app") + if err != nil { + printer.Error(fmt.Errorf("error parsing app flag for bare metal create : %v", err)) + os.Exit(1) + } + + userdata, err := cmd.Flags().GetString("userdata") + if err != nil { + printer.Error(fmt.Errorf("error parsing userdata flag for bare metal create : %v", err)) + os.Exit(1) + } + + notify, err := cmd.Flags().GetString("notify") + if err != nil { + printer.Error(fmt.Errorf("error parsing notify flag for bare metal create : %v", err)) + os.Exit(1) + } + + hostname, err := cmd.Flags().GetString("hostname") + if err != nil { + printer.Error(fmt.Errorf("error parsing hostname flag for bare metal create : %v", err)) + os.Exit(1) + } + + tags, err := cmd.Flags().GetStringSlice("tags") + if err != nil { + printer.Error(fmt.Errorf("error parsing tags flag for bare metal create : %v", err)) + os.Exit(1) + } + + ripv4, err := cmd.Flags().GetString("ripv4") + if err != nil { + printer.Error(fmt.Errorf("error parsing ripv4 flag for bare metal create : %v", err)) + os.Exit(1) + } + + pxe, err := cmd.Flags().GetBool("persistenterrpxe") + if err != nil { + printer.Error(fmt.Errorf("error parsing persistenterrpxe flag for bare metal create : %v", err)) + os.Exit(1) + } + + image, err := cmd.Flags().GetString("image") + if err != nil { + printer.Error(fmt.Errorf("error parsing image flag for bare metal create : %v", err)) + os.Exit(1) + } + + options := &govultr.BareMetalCreate{ + StartupScriptID: script, + Plan: plan, + SnapshotID: snapshot, + AppID: app, + OsID: osID, + ImageID: image, + Label: label, + SSHKeyIDs: sshKeys, + Hostname: hostname, + Tags: tags, + ReservedIPv4: ripv4, + Region: region, + PersistentPxe: govultr.BoolToBoolPtr(pxe), + } + if userdata != "" { + options.UserData = base64.StdEncoding.EncodeToString([]byte(userdata)) + } + + if notify == "yes" { + options.ActivationEmail = govultr.BoolToBoolPtr(true) + } + + if ipv6 == "yes" { + options.EnableIPv6 = govultr.BoolToBoolPtr(true) + } + + return options +} + +func parseUpdateFlags(cmd *cobra.Command) *govultr.BareMetalUpdate { //nolint:unused + osID, err := cmd.Flags().GetInt("os") + if err != nil { + printer.Error(fmt.Errorf("error parsing os flag for bare metal update : %v", err)) + os.Exit(1) + } + + label, err := cmd.Flags().GetString("label") + if err != nil { + printer.Error(fmt.Errorf("error parsing label flag for bare metal update : %v", err)) + os.Exit(1) + } + + app, err := cmd.Flags().GetInt("app") + if err != nil { + printer.Error(fmt.Errorf("error parsing app flag for bare metal update : %v", err)) + os.Exit(1) + } + + userdata, err := cmd.Flags().GetString("userdata") + if err != nil { + printer.Error(fmt.Errorf("error parsing userdata flag for bare metal update : %v", err)) + os.Exit(1) + } + + tags, err := cmd.Flags().GetStringSlice("tags") + if err != nil { + printer.Error(fmt.Errorf("error parsing tags flag for bare metal update : %v", err)) + os.Exit(1) + } + + image, err := cmd.Flags().GetString("image") + if err != nil { + printer.Error(fmt.Errorf("error parsing image flag for bare metal update : %v", err)) + os.Exit(1) + } + + options := &govultr.BareMetalUpdate{ + AppID: app, + OsID: osID, + ImageID: image, + Label: label, + Tags: tags, + } + if userdata != "" { + options.UserData = base64.StdEncoding.EncodeToString([]byte(userdata)) + } + + return options +} diff --git a/cmd/baremetal/printer.go b/cmd/baremetal/printer.go new file mode 100644 index 00000000..1a67c367 --- /dev/null +++ b/cmd/baremetal/printer.go @@ -0,0 +1,262 @@ +package baremetal + +import ( + "strconv" + + "github.com/vultr/govultr/v3" + "github.com/vultr/vultr-cli/v3/cmd/printer" +) + +// BareMetalsPrinter ... +type BareMetalsPrinter struct { + BareMetals []govultr.BareMetalServer `json:"bare_metals"` + Meta *govultr.Meta `json:"meta"` +} + +// JSON ... +func (b *BareMetalsPrinter) JSON() []byte { + return printer.MarshalObject(b, "json") +} + +// YAML ... +func (b *BareMetalsPrinter) YAML() []byte { + return printer.MarshalObject(b, "yaml") +} + +// Columns ... +func (b *BareMetalsPrinter) Columns() [][]string { + return [][]string{0: { + "ID", + "IP", + "TAG", + "MAC ADDRESS", + "LABEL", + "OS", + "STATUS", + "REGION", + "CPU", + "RAM", + "DISK", + "FEATURES", + "TAGS", + }} +} + +// Data ... +func (b *BareMetalsPrinter) Data() [][]string { + var data [][]string + for i := range b.BareMetals { + data = append(data, []string{ + b.BareMetals[i].ID, + b.BareMetals[i].MainIP, + b.BareMetals[i].Tag, //nolint: staticcheck + strconv.Itoa(b.BareMetals[i].MacAddress), + b.BareMetals[i].Label, + b.BareMetals[i].Os, + b.BareMetals[i].Status, + b.BareMetals[i].Region, + strconv.Itoa(b.BareMetals[i].CPUCount), + b.BareMetals[i].RAM, + b.BareMetals[i].Disk, + printer.ArrayOfStringsToString(b.BareMetals[i].Features), + printer.ArrayOfStringsToString(b.BareMetals[i].Tags), + }) + } + return data +} + +// Paging ... +func (b *BareMetalsPrinter) Paging() [][]string { + return printer.NewPaging(b.Meta.Total, &b.Meta.Links.Next, &b.Meta.Links.Prev).Compose() +} + +// ====================================== + +// BareMetalPrinter ... +type BareMetalPrinter struct { + BareMetal govultr.BareMetalServer `json:"bare_metal"` +} + +// JSON ... +func (b *BareMetalPrinter) JSON() []byte { + return printer.MarshalObject(b, "json") +} + +// YAML ... +func (b *BareMetalPrinter) YAML() []byte { + return printer.MarshalObject(b, "yaml") +} + +// Columns ... +func (b *BareMetalPrinter) Columns() [][]string { + return [][]string{0: { + "ID", + "IP", + "TAG", + "MAC ADDRESS", + "LABEL", + "OS", + "STATUS", + "REGION", + "CPU", + "RAM", + "DISK", + "FEATURES", + "TAGS", + }} +} + +// Data ... +func (b *BareMetalPrinter) Data() [][]string { + return [][]string{0: { + b.BareMetal.ID, + b.BareMetal.MainIP, + b.BareMetal.Tag, //nolint: staticcheck + strconv.Itoa(b.BareMetal.MacAddress), + b.BareMetal.Label, + b.BareMetal.Os, + b.BareMetal.Status, + b.BareMetal.Region, + strconv.Itoa(b.BareMetal.CPUCount), + b.BareMetal.RAM, + b.BareMetal.Disk, + printer.ArrayOfStringsToString(b.BareMetal.Features), + printer.ArrayOfStringsToString(b.BareMetal.Tags), + }} +} + +// Paging ... +func (b *BareMetalPrinter) Paging() [][]string { + return nil +} + +// ====================================== + +// BareMetalVNCPrinter ... +type BareMetalVNCPrinter struct { + VNC govultr.VNCUrl `json:"vnc"` +} + +// JSON ... +func (b *BareMetalVNCPrinter) JSON() []byte { + return printer.MarshalObject(b, "json") +} + +// YAML ... +func (b *BareMetalVNCPrinter) YAML() []byte { + return printer.MarshalObject(b, "yaml") +} + +// Columns ... +func (b *BareMetalVNCPrinter) Columns() [][]string { + return [][]string{0: { + "URL", + }} +} + +// Data ... +func (b *BareMetalVNCPrinter) Data() [][]string { + return [][]string{0: { + b.VNC.URL, + }} +} + +// Paging ... +func (b *BareMetalVNCPrinter) Paging() [][]string { + return nil +} + +// ====================================== + +// BareMetalBandwidthPrinter ... +type BareMetalBandwidthPrinter struct { + Bandwidth govultr.Bandwidth `json:"all_bandwidth"` +} + +// JSON ... +func (b *BareMetalBandwidthPrinter) JSON() []byte { + return printer.MarshalObject(b, "json") +} + +// YAML ... +func (b *BareMetalBandwidthPrinter) YAML() []byte { + return printer.MarshalObject(b, "yaml") +} + +// Columns ... +func (b *BareMetalBandwidthPrinter) Columns() [][]string { + return [][]string{0: { + "DATE", + "INCOMING BYTES", + "OUTGOING BYTES", + }} +} + +// Data ... +func (b *BareMetalBandwidthPrinter) Data() [][]string { + var data [][]string + for k := range b.Bandwidth.Bandwidth { + data = append(data, []string{ + k, + strconv.Itoa(b.Bandwidth.Bandwidth[k].IncomingBytes), + strconv.Itoa(b.Bandwidth.Bandwidth[k].OutgoingBytes), + }) + } + + return data +} + +// Paging ... +func (b *BareMetalBandwidthPrinter) Paging() [][]string { + return nil +} + +// ====================================== + +// BareMetalVPC2sPrinter ... +type BareMetalVPC2sPrinter struct { + VPC2s []govultr.VPC2Info `json:"vpcs"` +} + +// JSON ... +func (b *BareMetalVPC2sPrinter) JSON() []byte { + return printer.MarshalObject(b, "json") +} + +// YAML ... +func (b *BareMetalVPC2sPrinter) YAML() []byte { + return printer.MarshalObject(b, "yaml") +} + +// Columns ... +func (b *BareMetalVPC2sPrinter) Columns() [][]string { + return [][]string{0: { + "ID", + "MAC ADDRESS", + "IP ADDRESS", + }} +} + +// Data ... +func (b *BareMetalVPC2sPrinter) Data() [][]string { + var data [][]string + + if len(b.VPC2s) == 0 { + return [][]string{0: {"---", "---", "---"}} + } + + for i := range b.VPC2s { + data = append(data, []string{ + b.VPC2s[i].ID, + b.VPC2s[i].MacAddress, + b.VPC2s[i].IPAddress, + }) + } + + return data +} + +// Paging ... +func (b *BareMetalVPC2sPrinter) Paging() [][]string { + return nil +} diff --git a/cmd/billing/billing.go b/cmd/billing/billing.go new file mode 100644 index 00000000..f618721e --- /dev/null +++ b/cmd/billing/billing.go @@ -0,0 +1,275 @@ +// Package billing provides the billing commands for the CLI +package billing + +import ( + "errors" + "fmt" + "os" + "strconv" + + "github.com/spf13/cobra" + "github.com/vultr/govultr/v3" + "github.com/vultr/vultr-cli/v3/cmd/printer" + "github.com/vultr/vultr-cli/v3/cmd/utils" + "github.com/vultr/vultr-cli/v3/pkg/cli" +) + +var ( + long = `Get all available commands for billing` + example = ` + # Full example + vultr-cli billing + ` + + historyLong = `Get all available commands for billing history` + historyExample = ` + # Full example + vultr-cli billing history + + # Shortened with alias commands + vultr-cli billing h + ` + + historyListLong = `Retrieve a list of all billing history on your account` + historyListExample = ` + # Full example + vultr-cli billing history list + + # Shortened with alias commands + vultr-cli billing h l + ` + + invoicesLong = `Get all available commands for billing invoices` + invoicesExample = ` + # Full example + vultr-cli billing invoice + + # Shortened with alias commands + vultr-cli billing i + ` + + invoiceListLong = `Retrieve a list of all invoices on your account` + invoiceListExample = ` + # Full example + vultr-cli billing invoice list + + # Shortened with alias commands + vultr-cli billing i l + ` + + invoiceGetLong = `Get a specific invoice on your account` + invoiceGetExample = ` + # Full example + vultr-cli billing invoice get 123456 + + # Shortened with alias commands + vultr-cli billing i g 123456 + ` + + invoiceItemsListLong = `Retrieve a list of invoice items from a specific invoice on your account` + invoiceItemsListExample = ` + # Full example + vultr-cli billing invoice items 123456 + + # Shortened with alias commands + vultr-cli billing i i 123456 + ` +) + +func NewCmdBilling(base *cli.Base) *cobra.Command { + o := &options{Base: base} + + cmd := &cobra.Command{ + Use: "billing", + Short: "display billing information", + Long: long, + Example: example, + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + utils.SetOptions(o.Base, cmd, args) + if !o.Base.HasAuth { + return errors.New(utils.APIKeyError) + } + return nil + }, + } + + // Invoice + invoice := &cobra.Command{ + Use: "invoice", + Aliases: []string{"i"}, + Short: "display invoice information", + Long: invoicesLong, + Example: invoicesExample, + } + + // Invoice List + invoicesList := &cobra.Command{ + Use: "list", + Short: "list billing invoices", + Aliases: []string{"l"}, + Long: invoiceListLong, + Example: invoiceListExample, + Run: func(cmd *cobra.Command, args []string) { + o.Base.Options = utils.GetPaging(cmd) + invs, meta, err := o.listInvoices() + if err != nil { + printer.Error(fmt.Errorf("error retrieving billing invoice list : %v", err)) + os.Exit(1) + } + data := &BillingInvoicesPrinter{Invoices: invs, Meta: meta} + o.Base.Printer.Display(data, err) + }, + } + + invoicesList.Flags().StringP("cursor", "c", "", "(optional) Cursor for paging.") + invoicesList.Flags().IntP( + "per-page", + "p", + utils.PerPageDefault, + "(optional) Number of items requested per page. Default is 100 and Max is 500.", + ) + + // Invoice Get + invoiceGet := &cobra.Command{ + Use: "get", + Short: "get invoice", + Aliases: []string{"g"}, + Long: invoiceGetLong, + Example: invoiceGetExample, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("please provide an invoice ID") + } + return nil + }, + Run: func(cmd *cobra.Command, args []string) { + inv, err := o.get() + if err != nil { + printer.Error(fmt.Errorf("error getting invoice : %v", err)) + os.Exit(1) + } + + data := &BillingInvoicePrinter{Invoice: *inv} + o.Base.Printer.Display(data, err) + }, + } + + // Invoice Items List + invoiceItemsList := &cobra.Command{ + Use: "items ", + Short: "list invoice items", + Aliases: []string{"i"}, + Long: invoiceItemsListLong, + Example: invoiceItemsListExample, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("please provide an invoice ID") + } + return nil + }, + Run: func(cmd *cobra.Command, args []string) { + o.Base.Options = utils.GetPaging(cmd) + id, errConv := strconv.Atoi(args[0]) + if errConv != nil { + printer.Error(fmt.Errorf("error converting invoice item id : %v", errConv)) + os.Exit(1) + } + + o.InvoiceItemID = id + + items, meta, err := o.listInvoiceItems() + if err != nil { + printer.Error(fmt.Errorf("error retrieving billing invoice item list : %v", err)) + os.Exit(1) + } + data := &BillingInvoiceItemsPrinter{InvoiceItems: items, Meta: meta} + o.Base.Printer.Display(data, err) + }, + } + + invoiceItemsList.Flags().StringP("cursor", "c", "", "(optional) Cursor for paging.") + invoiceItemsList.Flags().IntP( + "per-page", + "p", + utils.PerPageDefault, + fmt.Sprintf("(optional) Number of items requested per page. Default is %d and Max is 500.", utils.PerPageDefault), + ) + + invoice.AddCommand( + invoicesList, + invoiceGet, + invoiceItemsList, + ) + + // History + history := &cobra.Command{ + Use: "history", + Aliases: []string{"h"}, + Short: "display billing history information", + Long: historyLong, + Example: historyExample, + } + + // History List + historyList := &cobra.Command{ + Use: "list", + Short: "list billing history", + Aliases: []string{"l"}, + Long: historyListLong, + Example: historyListExample, + Run: func(cmd *cobra.Command, args []string) { + o.Base.Options = utils.GetPaging(cmd) + hs, meta, err := o.listHistory() + if err != nil { + printer.Error(fmt.Errorf("error retrieving billing history list : %v", err)) + os.Exit(1) + } + data := &BillingHistoryPrinter{Billing: hs, Meta: meta} + o.Base.Printer.Display(data, err) + }, + } + + historyList.Flags().StringP("cursor", "c", "", "(optional) Cursor for paging.") + historyList.Flags().IntP( + "per-page", + "p", + utils.PerPageDefault, + "(optional) Number of items requested per page. Default is 100 and Max is 500.", + ) + + history.AddCommand( + historyList, + ) + + cmd.AddCommand( + history, + invoice, + ) + + return cmd +} + +type options struct { + Base *cli.Base + InvoiceItemID int +} + +func (b *options) listHistory() ([]govultr.History, *govultr.Meta, error) { + hs, meta, _, err := b.Base.Client.Billing.ListHistory(b.Base.Context, b.Base.Options) + return hs, meta, err +} + +func (b *options) get() (*govultr.Invoice, error) { + inv, _, err := b.Base.Client.Billing.GetInvoice(b.Base.Context, b.Base.Args[0]) + return inv, err +} + +func (b *options) listInvoices() ([]govultr.Invoice, *govultr.Meta, error) { + invs, meta, _, err := b.Base.Client.Billing.ListInvoices(b.Base.Context, b.Base.Options) + return invs, meta, err +} + +func (b *options) listInvoiceItems() ([]govultr.InvoiceItem, *govultr.Meta, error) { + items, meta, _, err := b.Base.Client.Billing.ListInvoiceItems(b.Base.Context, b.InvoiceItemID, b.Base.Options) + return items, meta, err +} diff --git a/cmd/billing/printer.go b/cmd/billing/printer.go new file mode 100644 index 00000000..fdb9a030 --- /dev/null +++ b/cmd/billing/printer.go @@ -0,0 +1,221 @@ +// Package billing provides the account billing operations and +// functionality for the CLI +package billing + +import ( + "strconv" + + "github.com/vultr/govultr/v3" + "github.com/vultr/vultr-cli/v3/cmd/printer" + "github.com/vultr/vultr-cli/v3/cmd/utils" +) + +// BillingHistoryPrinter ... +type BillingHistoryPrinter struct { + Billing []govultr.History `json:"billing_history"` + Meta *govultr.Meta `json:"meta"` +} + +// JSON ... +func (b *BillingHistoryPrinter) JSON() []byte { + return printer.MarshalObject(b, "json") +} + +// YAML ... +func (b *BillingHistoryPrinter) YAML() []byte { + return printer.MarshalObject(b, "yaml") +} + +// Columns ... +func (b *BillingHistoryPrinter) Columns() [][]string { + return [][]string{0: { + "ID", + "DATE", + "TYPE", + "DESCRIPTION", + "AMOUNT", + "BALANCE", + }} +} + +// Data ... +func (b *BillingHistoryPrinter) Data() [][]string { + if len(b.Billing) == 0 { + return [][]string{0: {"---", "---", "---", "---", "---", "---"}} + } + + var data [][]string + for i := range b.Billing { + data = append(data, []string{ + strconv.Itoa(b.Billing[i].ID), + b.Billing[i].Date, + b.Billing[i].Type, + b.Billing[i].Description, + strconv.FormatFloat(float64(b.Billing[i].Amount), 'f', utils.DecimalPrecision, 32), + strconv.FormatFloat(float64(b.Billing[i].Balance), 'f', utils.DecimalPrecision, 32), + }) + } + return data +} + +// Paging ... +func (b *BillingHistoryPrinter) Paging() [][]string { + return printer.NewPaging(b.Meta.Total, &b.Meta.Links.Next, &b.Meta.Links.Prev).Compose() +} + +// ====================================== + +// BillingInvoicesPrinter ... +type BillingInvoicesPrinter struct { + Invoices []govultr.Invoice `json:"billing_invoices"` + Meta *govultr.Meta +} + +// JSON ... +func (b *BillingInvoicesPrinter) JSON() []byte { + return printer.MarshalObject(b, "json") +} + +// YAML ... +func (b *BillingInvoicesPrinter) YAML() []byte { + return printer.MarshalObject(b, "yaml") +} + +// Columns ... +func (b *BillingInvoicesPrinter) Columns() [][]string { + return [][]string{0: { + "ID", + "DATE", + "DESCRIPTION", + "AMOUNT", + "BALANCE", + }} +} + +// Data ... +func (b *BillingInvoicesPrinter) Data() [][]string { + if len(b.Invoices) == 0 { + return [][]string{0: {"---", "---", "---", "---", "---"}} + } + + var data [][]string + for i := range b.Invoices { + data = append(data, []string{ + strconv.Itoa(b.Invoices[i].ID), + b.Invoices[i].Date, + b.Invoices[i].Description, + strconv.FormatFloat(float64(b.Invoices[i].Amount), 'f', utils.DecimalPrecision, 32), + strconv.FormatFloat(float64(b.Invoices[i].Balance), 'f', utils.DecimalPrecision, 32), + }) + } + return data +} + +// Paging ... +func (b *BillingInvoicesPrinter) Paging() [][]string { + return printer.NewPaging(b.Meta.Total, &b.Meta.Links.Next, &b.Meta.Links.Prev).Compose() +} + +// ====================================== + +// BillingInvoicePrinter ... +type BillingInvoicePrinter struct { + Invoice govultr.Invoice `json:"billing_invoice"` +} + +// JSON ... +func (b *BillingInvoicePrinter) JSON() []byte { + return printer.MarshalObject(b, "json") +} + +// YAML ... +func (b *BillingInvoicePrinter) YAML() []byte { + return printer.MarshalObject(b, "yaml") +} + +// Columns ... +func (b *BillingInvoicePrinter) Columns() [][]string { + return [][]string{0: { + "ID", + "DATE", + "DESCRIPTION", + "AMOUNT", + "BALANCE", + }} +} + +// Data ... +func (b *BillingInvoicePrinter) Data() [][]string { + return [][]string{0: { + strconv.Itoa(b.Invoice.ID), + b.Invoice.Date, + b.Invoice.Description, + strconv.FormatFloat(float64(b.Invoice.Amount), 'f', utils.DecimalPrecision, 32), + strconv.FormatFloat(float64(b.Invoice.Balance), 'f', utils.DecimalPrecision, 32), + }} +} + +// Paging ... +func (b *BillingInvoicePrinter) Paging() [][]string { + return nil +} + +// ====================================== + +// BillingInvoiceItemsPrinter ... +type BillingInvoiceItemsPrinter struct { + InvoiceItems []govultr.InvoiceItem `json:"invoice_items"` + Meta *govultr.Meta `json:"meta"` +} + +// JSON ... +func (b *BillingInvoiceItemsPrinter) JSON() []byte { + return printer.MarshalObject(b, "json") +} + +// YAML ... +func (b *BillingInvoiceItemsPrinter) YAML() []byte { + return printer.MarshalObject(b, "yaml") +} + +// Columns ... +func (b *BillingInvoiceItemsPrinter) Columns() [][]string { + return [][]string{0: { + "DESCRIPTION", + "PRODUCT", + "START DATE", + "END DATE", + "UNITS", + "UNIT TYPE", + "UNIT PRICE", + "TOTAL", + }} +} + +// Data ... +func (b *BillingInvoiceItemsPrinter) Data() [][]string { + if len(b.InvoiceItems) == 0 { + return [][]string{0: {"---", "---", "---", "---", "---", "---", "---", "---"}} + } + + var data [][]string + for i := range b.InvoiceItems { + data = append(data, []string{ + b.InvoiceItems[i].Description, + b.InvoiceItems[i].Product, + b.InvoiceItems[i].StartDate, + b.InvoiceItems[i].EndDate, + strconv.Itoa(b.InvoiceItems[i].Units), + b.InvoiceItems[i].UnitType, + strconv.FormatFloat(float64(b.InvoiceItems[i].UnitPrice), 'f', utils.DecimalPrecision, 32), + strconv.FormatFloat(float64(b.InvoiceItems[i].Total), 'f', utils.DecimalPrecision, 32), + }) + } + + return data +} + +// Paging ... +func (b *BillingInvoiceItemsPrinter) Paging() [][]string { + return printer.NewPaging(b.Meta.Total, &b.Meta.Links.Next, &b.Meta.Links.Prev).Compose() +} diff --git a/cmd/blockStorage.go b/cmd/blockStorage.go deleted file mode 100644 index 25e04fed..00000000 --- a/cmd/blockStorage.go +++ /dev/null @@ -1,268 +0,0 @@ -// Copyright © 2019 The Vultr-cli Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package cmd - -import ( - "context" - "errors" - "fmt" - "os" - - "github.com/spf13/cobra" - "github.com/vultr/govultr/v2" - "github.com/vultr/vultr-cli/cmd/printer" -) - -// BlockStorageCmd represents the blockStorage command -func BlockStorageCmd() *cobra.Command { - - bsCmd := &cobra.Command{ - Use: "block-storage", - Aliases: []string{"bs"}, - Short: "block storage commands", - Long: `block-storage is used to interact with the block-storage api`, - } - - bsCmd.AddCommand(bsAttach, bsCreate, bsDelete, bsDetach, bsLabelSet, bsList, bsGet, bsResize) - - // List - bsList.Flags().StringP("cursor", "c", "", "(optional) Cursor for paging.") - bsList.Flags().IntP("per-page", "p", 100, "(optional) Number of items requested per page. Default is 100 and Max is 500.") - - // Attach - bsAttach.Flags().StringP("instance", "i", "", "instance id you want to attach to") - bsAttach.Flags().Bool("live", false, "attach Block Storage without restarting the Instance.") - bsAttach.MarkFlagRequired("instance") - - // Detach - bsDetach.Flags().Bool("live", false, "detach block storage from instance without a restart") - - // Create - bsCreate.Flags().StringP("region", "r", "", "regionID you want to create the block storage in") - bsCreate.MarkFlagRequired("region") - - bsCreate.Flags().IntP("size", "s", 0, "size of the block storage you want to create") - bsCreate.MarkFlagRequired("size") - - bsCreate.Flags().StringP("label", "l", "", "label you want to give the block storage") - - // Label - bsLabelSet.Flags().StringP("label", "l", "", "label you want your block storage to have") - bsLabelSet.MarkFlagRequired("label") - - // Resize - bsResize.Flags().IntP("size", "s", 0, "size you want your block storage to be") - bsResize.MarkFlagRequired("size") - - return bsCmd -} - -var bsAttach = &cobra.Command{ - Use: "attach ", - Short: "attaches a block storage to an instance", - Long: ``, - Args: func(cmd *cobra.Command, args []string) error { - if len(args) < 1 { - return errors.New("please provide a blockStorageID") - } - return nil - }, - Run: func(cmd *cobra.Command, args []string) { - id := args[0] - instance, _ := cmd.Flags().GetString("instance") - live, _ := cmd.Flags().GetBool("live") - - bsAttach := &govultr.BlockStorageAttach{ - InstanceID: instance, - Live: govultr.BoolToBoolPtr(live), - } - - if err := client.BlockStorage.Attach(context.Background(), id, bsAttach); err != nil { - fmt.Printf("error attaching block storage : %v\n", err) - os.Exit(1) - } - - fmt.Println("attached block storage") - }, -} - -var bsCreate = &cobra.Command{ - Use: "create", - Short: "create a new block storage", - Long: ``, - Run: func(cmd *cobra.Command, args []string) { - region, _ := cmd.Flags().GetString("region") - size, _ := cmd.Flags().GetInt("size") - label, _ := cmd.Flags().GetString("label") - - bsCreate := &govultr.BlockStorageCreate{ - Region: region, - SizeGB: size, - Label: label, - } - - bs, err := client.BlockStorage.Create(context.Background(), bsCreate) - if err != nil { - fmt.Printf("error creating block storage : %v\n", err) - os.Exit(1) - } - - printer.SingleBlockStorage(bs) - - }, -} - -var bsDelete = &cobra.Command{ - Use: "delete ", - Short: "delete a block storage", - Aliases: []string{"destroy"}, - Long: ``, - Args: func(cmd *cobra.Command, args []string) error { - if len(args) < 1 { - return errors.New("please provide a blockStorageID") - } - return nil - }, - Run: func(cmd *cobra.Command, args []string) { - id := args[0] - if err := client.BlockStorage.Delete(context.Background(), id); err != nil { - fmt.Printf("error deleting block storage : %v\n", err) - os.Exit(1) - } - - fmt.Println("deleted block storage") - }, -} - -var bsDetach = &cobra.Command{ - Use: "detach ", - Short: "detaches a block storage from an instance", - Long: ``, - Args: func(cmd *cobra.Command, args []string) error { - if len(args) < 1 { - return errors.New("please provide a blockStorageID") - } - return nil - }, - Run: func(cmd *cobra.Command, args []string) { - id := args[0] - live, _ := cmd.Flags().GetBool("live") - - bsDetach := &govultr.BlockStorageDetach{ - Live: govultr.BoolToBoolPtr(live), - } - - if err := client.BlockStorage.Detach(context.Background(), id, bsDetach); err != nil { - fmt.Printf("error detaching block storage : %v\n", err) - os.Exit(1) - } - - fmt.Println("detached block storage") - }, -} - -var bsLabelSet = &cobra.Command{ - Use: "label ", - Short: "sets a label for a block storage", - Long: ``, - Args: func(cmd *cobra.Command, args []string) error { - if len(args) < 1 { - return errors.New("please provide a blockStorageID") - } - return nil - }, - Run: func(cmd *cobra.Command, args []string) { - id := args[0] - label, _ := cmd.Flags().GetString("label") - - options := &govultr.BlockStorageUpdate{ - Label: label, - } - - if err := client.BlockStorage.Update(context.Background(), id, options); err != nil { - fmt.Printf("error setting label : %v\n", err) - os.Exit(1) - } - - fmt.Printf("set label on block storage : %s\n", id) - }, -} - -// List all of individual block storage -var bsList = &cobra.Command{ - Use: "list", - Short: "retrieves a list of active block storage", - Long: ``, - Run: func(cmd *cobra.Command, args []string) { - options := getPaging(cmd) - bs, meta, err := client.BlockStorage.List(context.Background(), options) - if err != nil { - fmt.Printf("error getting block storage : %v\n", err) - os.Exit(1) - } - - printer.BlockStorage(bs, meta) - }, -} - -// Get a block storage -var bsGet = &cobra.Command{ - Use: "get ", - Short: "retrieves a block storage", - Long: ``, - Args: func(cmd *cobra.Command, args []string) error { - if len(args) < 1 { - return errors.New("please provide a blockStorageID") - } - return nil - }, - Run: func(cmd *cobra.Command, args []string) { - id := args[0] - bs, err := client.BlockStorage.Get(context.Background(), id) - if err != nil { - fmt.Printf("error getting block storage : %v\n", err) - os.Exit(1) - } - - printer.SingleBlockStorage(bs) - }, -} - -var bsResize = &cobra.Command{ - Use: "resize ", - Short: "resize a block storage", - Long: ``, - Args: func(cmd *cobra.Command, args []string) error { - if len(args) < 1 { - return errors.New("please provide a blockStorageID") - } - return nil - }, - Run: func(cmd *cobra.Command, args []string) { - id := args[0] - size, _ := cmd.Flags().GetInt("size") - - options := &govultr.BlockStorageUpdate{ - SizeGB: size, - } - - if err := client.BlockStorage.Update(context.Background(), id, options); err != nil { - fmt.Printf("error resizing block storage : %v\n", err) - os.Exit(1) - } - - fmt.Println("resized block storage") - }, -} diff --git a/cmd/blockstorage/blockstorage.go b/cmd/blockstorage/blockstorage.go new file mode 100644 index 00000000..967896b3 --- /dev/null +++ b/cmd/blockstorage/blockstorage.go @@ -0,0 +1,473 @@ +// Package blockstorage provides the block storage functionality for +// the CLI +package blockstorage + +import ( + "errors" + "fmt" + "os" + + "github.com/spf13/cobra" + "github.com/vultr/govultr/v3" + "github.com/vultr/vultr-cli/v3/cmd/printer" + "github.com/vultr/vultr-cli/v3/cmd/utils" + "github.com/vultr/vultr-cli/v3/pkg/cli" +) + +var ( + attachLong = `Attaches a block storage resource to an specified instance` + attachExample = ` + #Full example + vultr-cli block-storage attach 67181686-5455-4ebb-81eb-7299f3506e2c --instance=a7898453-dd9e-4b47-bdab-9dd7a3448f1f + + #Shortened with aliased commands + vultr-cli bs a 67181686-5455-4ebb-81eb-7299f3506e2c -i=a7898453-dd9e-4b47-bdab-9dd7a3448f1f + ` + + createLong = `Create a new block storage resource in a specified region` + createExample = ` + #Full example + vultr-cli block-storage create --region='lax' --size=10 --label='your-label' + + #Full example with block-type + vultr-cli block-storage create --region='lax' --size=10 --block-type='high_perf' + + #Shortened with aliased commands + vultr-cli bs c -r='lax' -s=10 -l='your-label' + + #Shortened with aliased commands and block-type + vultr-cli bs c -r='lax' -s=10 -b='high_perf' + ` + + deleteLong = `Delete a block storage resource` + deleteExample = ` + #Full example + vultr-cli block-storage delete 67181686-5455-4ebb-81eb-7299f3506e2c + + #Shortened with aliased commands + vultr-cli bs d 67181686-5455-4ebb-81eb-7299f3506e2c + ` + + detachLong = `Detach a block storage resource from an instance` + detachExample = ` + #Full example + vultr-cli block-storage detach 67181686-5455-4ebb-81eb-7299f3506e2c + + #Shortened with aliased commands + vultr-cli bs detach 67181686-5455-4ebb-81eb-7299f3506e2c + ` + + labelLong = `Set a label for a block storage resource` + labelExample = ` + #Full example + vultr-cli block-storage label 67181686-5455-4ebb-81eb-7299f3506e2c --label="Example Label" + + #Shortened with aliased commands + vultr-cli bs label 67181686-5455-4ebb-81eb-7299f3506e2c -l="Example Label" + ` + + listLong = `Retrieves a list of active block storage resources` + listExample = ` + #Full example + vultr-cli block-storage list + + #Shortened with aliased commands + vultr-cli bs l + ` + + getLong = `Retrieves a specified block storage resource` + getExample = ` + #Full example + vultr-cli block-storage get 67181686-5455-4ebb-81eb-7299f3506e2c + + #Shortened with aliased commands + vultr-cli bs g 67181686-5455-4ebb-81eb-7299f3506e2c + ` + + resizeLong = `Resizes a specified block storage resource` + resizeExample = ` + #Full example + vultr-cli block-storage resize 67181686-5455-4ebb-81eb-7299f3506e2c --size=20 + + #Shortened with aliased commands + vultr-cli bs r 67181686-5455-4ebb-81eb-7299f3506e2c -s=20 + ` +) + +// NewCmdBlockStorage provides the command for block storage to the CLI +func NewCmdBlockStorage(base *cli.Base) *cobra.Command { //nolint:gocyclo + o := &options{Base: base} + + cmd := &cobra.Command{ + Use: "block-storage", + Aliases: []string{"bs"}, + Short: "block storage commands", + Long: `block-storage is used to interact with the block-storage api`, + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + utils.SetOptions(o.Base, cmd, args) + if !o.Base.HasAuth { + return errors.New(utils.APIKeyError) + } + return nil + }, + } + + // List + list := &cobra.Command{ + Use: "list", + Short: "List block storage", + Aliases: []string{"l"}, + Long: listLong, + Example: listExample, + Run: func(cmd *cobra.Command, args []string) { + o.Base.Options = utils.GetPaging(cmd) + bss, meta, err := o.list() + if err != nil { + printer.Error(fmt.Errorf("error retrieving block storage list : %v", err)) + os.Exit(1) + } + + data := &BlockStoragesPrinter{BlockStorages: bss, Meta: meta} + o.Base.Printer.Display(data, nil) + }, + } + + list.Flags().StringP("cursor", "c", "", "(optional) cursor for paging.") + list.Flags().IntP( + "per-page", + "p", + utils.PerPageDefault, + fmt.Sprintf("(optional) Number of items requested per page. Default is %d and Max is 500.", utils.PerPageDefault), + ) + + // Get + get := &cobra.Command{ + Use: "get ", + Short: "Retrieve a block storage", + Aliases: []string{"g"}, + Long: getLong, + Example: getExample, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("please provide a block storage ID") + } + return nil + }, + Run: func(cmd *cobra.Command, args []string) { + bs, err := o.get() + if err != nil { + printer.Error(fmt.Errorf("error retrieving block storage : %v", err)) + os.Exit(1) + } + + data := &BlockStoragePrinter{BlockStorage: bs} + o.Base.Printer.Display(data, nil) + }, + } + + // Create + create := &cobra.Command{ + Use: "create", + Short: "Create a new block storage", + Aliases: []string{"c"}, + Long: createLong, + Example: createExample, + Run: func(cmd *cobra.Command, args []string) { + reg, errRg := cmd.Flags().GetString("region") + if errRg != nil { + printer.Error(fmt.Errorf("error parsing 'region' flag for block storage create : %v", errRg)) + os.Exit(1) + } + + size, errSz := cmd.Flags().GetInt("size") + if errSz != nil { + printer.Error(fmt.Errorf("error parsing 'size' flag for block storage create : %v", errSz)) + os.Exit(1) + } + + label, errLa := cmd.Flags().GetString("label") + if errLa != nil { + printer.Error(fmt.Errorf("error parsing 'label' flag for block storage create : %v", errLa)) + os.Exit(1) + } + + blockType, errBt := cmd.Flags().GetString("block-type") + if errBt != nil { + printer.Error(fmt.Errorf("error parsing 'block-type' flag for block storage create : %v", errBt)) + os.Exit(1) + } + + o.CreateReq = &govultr.BlockStorageCreate{ + Region: reg, + SizeGB: size, + Label: label, + BlockType: blockType, + } + + bs, err := o.create() + if err != nil { + printer.Error(fmt.Errorf("error creating block storage : %v", err)) + os.Exit(1) + } + + data := &BlockStoragePrinter{BlockStorage: bs} + o.Base.Printer.Display(data, nil) + }, + } + + create.Flags().StringP("region", "r", "", "ID of the region in which to create the block storage") + if err := create.MarkFlagRequired("region"); err != nil { + fmt.Printf("error marking block storage create 'region' flag required: %v\n", err) + os.Exit(1) + } + + create.Flags().IntP("size", "s", 0, "size of the block storage you want to create") + if err := create.MarkFlagRequired("size"); err != nil { + fmt.Printf("error marking block storage create 'size' flag required: %v\n", err) + os.Exit(1) + } + + create.Flags().StringP("label", "l", "", "label you want to give the block storage") + + create.Flags().StringP( + "block-type", + "b", + "", + `(optional) Block type you want to give the block storage. + Possible values: 'high_perf', 'storage_opt'. Currently defaults to 'high_perf'.`, + ) + + // Delete + del := &cobra.Command{ + Use: "delete ", + Short: "Delete a block storage", + Aliases: []string{"d", "destroy"}, + Long: deleteLong, + Example: deleteExample, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("please provide a block storage ID") + } + return nil + }, + Run: func(cmd *cobra.Command, args []string) { + if err := o.del(); err != nil { + printer.Error(fmt.Errorf("error deleting block storage : %v", err)) + os.Exit(1) + } + + o.Base.Printer.Display(printer.Info("block storage has been deleted"), nil) + }, + } + + // Attach + attach := &cobra.Command{ + Use: "attach ", + Short: "Attach a block storage to an instance", + Aliases: []string{"a"}, + Long: attachLong, + Example: attachExample, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("please provide a block storage ID") + } + return nil + }, + Run: func(cmd *cobra.Command, args []string) { + instance, errIe := cmd.Flags().GetString("instance") + if errIe != nil { + printer.Error(fmt.Errorf("error parsing 'instance' flag for block storage attach : %v", errIe)) + os.Exit(1) + } + + live, errLe := cmd.Flags().GetBool("live") + if errLe != nil { + printer.Error(fmt.Errorf("error parsing 'live' flag for block storage attach : %v", errLe)) + os.Exit(1) + } + + o.AttachReq = &govultr.BlockStorageAttach{ + InstanceID: instance, + Live: govultr.BoolToBoolPtr(live), + } + + if err := o.attach(); err != nil { + printer.Error(fmt.Errorf("error attaching block storage : %v", err)) + os.Exit(1) + } + + o.Base.Printer.Display(printer.Info("block storage has been attached"), nil) + }, + } + + attach.Flags().StringP("instance", "i", "", "instance ID to which to attach the block storage") + if err := attach.MarkFlagRequired("instance"); err != nil { + fmt.Printf("error marking block storage attach 'instance' flag required: %v\n", err) + os.Exit(1) + } + + attach.Flags().Bool("live", false, "attach block storage without restarting the instance") + + // Detach + detach := &cobra.Command{ + Use: "detach ", + Short: "Detach a block storage from an instance", + Long: detachLong, + Example: detachExample, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("please provide a block storage ID") + } + return nil + }, + Run: func(cmd *cobra.Command, args []string) { + live, errLe := cmd.Flags().GetBool("live") + if errLe != nil { + printer.Error(fmt.Errorf("error parsing 'live' flag for block storage detach : %v", errLe)) + os.Exit(1) + } + + o.DetachReq = &govultr.BlockStorageDetach{ + Live: govultr.BoolToBoolPtr(live), + } + + if err := o.detach(); err != nil { + printer.Error(fmt.Errorf("error detaching block storage : %v", err)) + os.Exit(1) + } + + o.Base.Printer.Display(printer.Info("block storage has been detached"), nil) + }, + } + + detach.Flags().Bool("live", false, "detach block storage without a restarting instance") + + // Label + label := &cobra.Command{ + Use: "label ", + Short: "Label a block storage", + Long: labelLong, + Example: labelExample, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("please provide a block storage ID") + } + return nil + }, + Run: func(cmd *cobra.Command, args []string) { + label, errLl := cmd.Flags().GetString("label") + if errLl != nil { + printer.Error(fmt.Errorf("error parsing 'label' flag for block storage : %v", errLl)) + os.Exit(1) + } + + o.UpdateReq = &govultr.BlockStorageUpdate{ + Label: label, + } + + if err := o.update(); err != nil { + printer.Error(fmt.Errorf("error updating block storage label : %v", err)) + os.Exit(1) + } + + o.Base.Printer.Display(printer.Info("block storage label has been updated"), nil) + }, + } + + label.Flags().StringP("label", "l", "", "the label to apply to the block storage") + if err := label.MarkFlagRequired("label"); err != nil { + fmt.Printf("error marking block storage label set 'label' flag required: %v\n", err) + os.Exit(1) + } + + // Resize + resize := &cobra.Command{ + Use: "resize ", + Short: "Resize a block storage", + Aliases: []string{"r"}, + Long: resizeLong, + Example: resizeExample, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("please provide a block storage ID") + } + return nil + }, + Run: func(cmd *cobra.Command, args []string) { + size, errSz := cmd.Flags().GetInt("size") + if errSz != nil { + printer.Error(fmt.Errorf("error parsing 'size' flag for block storage resize : %v", errSz)) + os.Exit(1) + } + + o.UpdateReq = &govultr.BlockStorageUpdate{ + SizeGB: size, + } + + if err := o.update(); err != nil { + printer.Error(fmt.Errorf("error resizing block storage : %v", err)) + os.Exit(1) + } + + o.Base.Printer.Display(printer.Info("block storage has been resized"), nil) + }, + } + + resize.Flags().IntP("size", "s", 0, "size you want your block storage to be") + if err := resize.MarkFlagRequired("size"); err != nil { + fmt.Printf("error marking block storage resize 'size' flag required: %v\n", err) + os.Exit(1) + } + + cmd.AddCommand( + list, + get, + create, + del, + label, + attach, + detach, + resize, + ) + + return cmd +} + +type options struct { + Base *cli.Base + CreateReq *govultr.BlockStorageCreate + UpdateReq *govultr.BlockStorageUpdate + AttachReq *govultr.BlockStorageAttach + DetachReq *govultr.BlockStorageDetach +} + +func (o *options) list() ([]govultr.BlockStorage, *govultr.Meta, error) { + bs, meta, _, err := o.Base.Client.BlockStorage.List(o.Base.Context, o.Base.Options) + return bs, meta, err +} + +func (o *options) get() (*govultr.BlockStorage, error) { + bs, _, err := o.Base.Client.BlockStorage.Get(o.Base.Context, o.Base.Args[0]) + return bs, err +} + +func (o *options) create() (*govultr.BlockStorage, error) { + bs, _, err := o.Base.Client.BlockStorage.Create(o.Base.Context, o.CreateReq) + return bs, err +} + +func (o *options) del() error { + return o.Base.Client.BlockStorage.Delete(o.Base.Context, o.Base.Args[0]) +} + +func (o *options) update() error { + return o.Base.Client.BlockStorage.Update(o.Base.Context, o.Base.Args[0], o.UpdateReq) +} + +func (o *options) attach() error { + return o.Base.Client.BlockStorage.Attach(o.Base.Context, o.Base.Args[0], o.AttachReq) +} + +func (o *options) detach() error { + return o.Base.Client.BlockStorage.Detach(o.Base.Context, o.Base.Args[0], o.DetachReq) +} diff --git a/cmd/blockstorage/printer.go b/cmd/blockstorage/printer.go new file mode 100644 index 00000000..6ed35044 --- /dev/null +++ b/cmd/blockstorage/printer.go @@ -0,0 +1,125 @@ +package blockstorage + +import ( + "fmt" + "strconv" + + "github.com/vultr/govultr/v3" + "github.com/vultr/vultr-cli/v3/cmd/printer" +) + +// BlockStoragesPrinter ... +type BlockStoragesPrinter struct { + BlockStorages []govultr.BlockStorage `json:"blocks"` + Meta *govultr.Meta `json:"meta"` +} + +// JSON ... +func (b *BlockStoragesPrinter) JSON() []byte { + return printer.MarshalObject(b, "json") +} + +// YAML ... +func (b *BlockStoragesPrinter) YAML() []byte { + return printer.MarshalObject(b, "yaml") +} + +// Columns ... +func (b *BlockStoragesPrinter) Columns() [][]string { + return [][]string{0: { + "ID", + "REGION ID", + "INSTANCE ID", + "SIZE GB", + "STATUS", + "LABEL", + "BLOCK TYPE", + "DATE CREATED", + "MONTHLY COST", + "MOUNT ID", + }} +} + +// Data ... +func (b *BlockStoragesPrinter) Data() [][]string { + if len(b.BlockStorages) == 0 { + return [][]string{0: {"---", "---", "---", "---", "---", "---", "---", "---", "---", "---"}} + } + + var data [][]string + for i := range b.BlockStorages { + data = append(data, []string{ + b.BlockStorages[i].ID, + b.BlockStorages[i].Region, + b.BlockStorages[i].AttachedToInstance, + strconv.Itoa(b.BlockStorages[i].SizeGB), + b.BlockStorages[i].Status, + b.BlockStorages[i].Label, + b.BlockStorages[i].BlockType, + b.BlockStorages[i].DateCreated, + fmt.Sprintf("$%v", b.BlockStorages[i].Cost), + b.BlockStorages[i].MountID, + }) + } + + return data +} + +// Paging ... +func (b *BlockStoragesPrinter) Paging() [][]string { + return printer.NewPaging(b.Meta.Total, &b.Meta.Links.Next, &b.Meta.Links.Prev).Compose() +} + +// ====================================== + +// BlockStoragePrinter ... +type BlockStoragePrinter struct { + BlockStorage *govultr.BlockStorage `json:"block"` +} + +// JSON ... +func (b *BlockStoragePrinter) JSON() []byte { + return printer.MarshalObject(b, "json") +} + +// YAML ... +func (b *BlockStoragePrinter) YAML() []byte { + return printer.MarshalObject(b, "yaml") +} + +// Columns ... +func (b *BlockStoragePrinter) Columns() [][]string { + return [][]string{0: { + "ID", + "REGION ID", + "INSTANCE ID", + "SIZE GB", + "STATUS", + "LABEL", + "BLOCK TYPE", + "DATE CREATED", + "MONTHLY COST", + "MOUNT ID", + }} +} + +// Data ... +func (b *BlockStoragePrinter) Data() [][]string { + return [][]string{0: { + b.BlockStorage.ID, + b.BlockStorage.Region, + b.BlockStorage.AttachedToInstance, + strconv.Itoa(b.BlockStorage.SizeGB), + b.BlockStorage.Status, + b.BlockStorage.Label, + b.BlockStorage.BlockType, + b.BlockStorage.DateCreated, + fmt.Sprintf("$%v", b.BlockStorage.Cost), + b.BlockStorage.MountID, + }} +} + +// Paging ... +func (b *BlockStoragePrinter) Paging() [][]string { + return nil +} diff --git a/cmd/containerregistry/containerregistry.go b/cmd/containerregistry/containerregistry.go new file mode 100644 index 00000000..6a3ac6c3 --- /dev/null +++ b/cmd/containerregistry/containerregistry.go @@ -0,0 +1,696 @@ +// Package containerregistry provides functionality for the CLI to control +// container registries +package containerregistry + +import ( + "errors" + "fmt" + "os" + + "github.com/spf13/cobra" + "github.com/vultr/govultr/v3" + "github.com/vultr/vultr-cli/v3/cmd/printer" + "github.com/vultr/vultr-cli/v3/cmd/utils" + "github.com/vultr/vultr-cli/v3/pkg/cli" +) + +var ( + long = `Access information about container registries on the account and perform CRUD operations` + example = ` + # Full example + vultr-cli container-registry + ` + createLong = `Create a new container registry with specified options` + createExample = ` + # Full example + vultr-cli container-registry create --region="sjc" --name="my-registry" --public=true --plan="start_up" + + all flags are required + + # Shortened example with aliases + vultr-cli cr c -i="sjc" -n="my-registry" -p=true -l="start_up" + ` + + getLong = `Display information for a specific VPC` + getExample = ` + # Full example + vultr-cli container-registry get e8ba183d-df3b-487a-acbf-f6c06aa32468 + + # Shortened example with aliases + vultr-cli cr g e8ba183d-df3b-487a-acbf-f6c06aa32468 + ` + updateLong = `Update an existing container registry` + updateExample = ` + # Full example + vultr-cli container-registry update 835fd402-e0eb-47aa-a5a9-a9885feea1cf --plan="premium" --public="true" + + # Shortened example with aliases + vultr-cli cr u 835fd402-e0eb-47aa-a5a9-a9885feea1cf -p="premium" -b="true" + ` + deleteLong = `Delete a container registry` + deleteExample = ` + #Full example + vultr-cli container-registry delete b20fa61e-4abb-46c5-92c3-8700150e1f9a + + #Shortened example with aliases + vultr-cli cr d b20fa61e-4abb-46c5-92c3-8700150e1f9a + ` + listLong = `List all container registries on the account` + listExample = ` + # Full example + vultr-cli container-registry list + + # Shortened example with aliases + vultr-cli cr l + ` + credentialsLong = `Commands for accessing the credentials on registries` + //nolint:gosec + credentialsExample = ` + # Full example + vultr-cli container-registry credentials + ` + credentialsDockerLong = `Create the credential string used by docker` + //nolint:gosec + credentialsDockerExample = ` + # Full example + vultr-cli container-registry credentials docker d24cfdcc-0534-4700-bf88-8ee48f20064e + ` + repoLong = `Access commands for individual repositories on a container registry` + repoExample = ` + # Full example + vultr-cli container-registry repository + + # Shortened example with aliases + vultr-cli cr r + ` + repoUpdateLong = `Update the details of registry's repository` + repoUpdateExample = ` + # Full example + vultr-cli container-registry repository update 4dcdc52e-9c63-401e-8c5f-1582490fe09c --image-name="my-thing" --description="new description" + + # Shortened example with aliases + vultr-cli cr r u 4dcdc52e-9c63-401e-8c5f-1582490fe09c -i="my-thing" -d="new description" + ` + repoDeleteLong = `Delete a repository in a registry` + repoDeleteExample = ` + # Full example + vultr-cli container-registry repository delete 4dcdc52e-9c63-401e-8c5f-1582490fe09c --image-name="my-thing" + + # Shortened example with aliases + vultr-cli cr r d 4dcdc52e-9c63-401e-8c5f-1582490fe09c -i="my-thing" + ` + plansLong = `Retrieve the current plan details for container registry` + plansExample = ` + # Full example + vultr-cli container-registry plans + + # Shortened example with aliases + vultr-cli cr p + ` + regionsLong = `Retrieve the available regions for container registries` + regionsExample = ` + # Full example + vultr-cli container-registry regions + + # Shortened example with aliases + vultr-cli cr r + ` +) + +// NewCmdContainerRegistry provides the CLI command functionality for container registry +func NewCmdContainerRegistry(base *cli.Base) *cobra.Command { //nolint:funlen,gocyclo + o := &options{Base: base} + + cmd := &cobra.Command{ + Use: "container-registry", + Short: "Commands to interact with container registries", + Aliases: []string{"cr"}, + Long: long, + Example: example, + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + utils.SetOptions(o.Base, cmd, args) + if !o.Base.HasAuth { + return errors.New(utils.APIKeyError) + } + return nil + }, + } + + // List + list := &cobra.Command{ + Use: "list", + Short: "List all container registries", + Aliases: []string{"l"}, + Long: listLong, + Example: listExample, + RunE: func(cmd *cobra.Command, args []string) error { + o.Base.Options = utils.GetPaging(cmd) + regs, meta, err := o.list() + if err != nil { + return fmt.Errorf("error retrieving container registry list : %v", err) + } + + data := &ContainerRegistriesPrinter{Registries: regs, Meta: meta} + o.Base.Printer.Display(data, nil) + + return nil + }, + } + + list.Flags().StringP("cursor", "c", "", "(optional) cursor for paging.") + list.Flags().IntP( + "per-page", + "p", + utils.PerPageDefault, + fmt.Sprintf("(optional) Number of items requested per page. Default is %d and Max is 500.", utils.PerPageDefault), + ) + + // Get + get := &cobra.Command{ + Use: "get ", + Short: "Get a container registry", + Aliases: []string{"g"}, + Long: getLong, + Example: getExample, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("please provide a container registry ID") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + reg, err := o.get() + if err != nil { + return fmt.Errorf("error retrieving container registry info : %v", err) + } + + data := &ContainerRegistryPrinter{Registry: reg} + o.Base.Printer.Display(data, nil) + + return nil + }, + } + + // Create + create := &cobra.Command{ + Use: "create", + Short: "Create a container registry", + Aliases: []string{"c"}, + Long: createLong, + Example: createExample, + RunE: func(cmd *cobra.Command, args []string) error { + name, errNa := cmd.Flags().GetString("name") + if errNa != nil { + return fmt.Errorf("error parsing 'name' flag for container registry create : %v", errNa) + } + + region, errRe := cmd.Flags().GetString("region") + if errRe != nil { + return fmt.Errorf("error parsing 'region' flag for container registry create : %v", errRe) + } + + public, errPu := cmd.Flags().GetBool("public") + if errPu != nil { + return fmt.Errorf("error parsing 'public' flag for container registry create : %v", errPu) + } + + plan, errPl := cmd.Flags().GetString("plan") + if errPl != nil { + return fmt.Errorf("error parsing 'plan' flag for container registry create : %v", errPl) + } + + o.CreateReq = &govultr.ContainerRegistryReq{ + Name: name, + Region: region, + Public: public, + Plan: plan, + } + + reg, err := o.create() + if err != nil { + return fmt.Errorf("error creating container registry : %v", err) + } + + data := &ContainerRegistryPrinter{Registry: reg} + o.Base.Printer.Display(data, nil) + + return nil + }, + } + + create.Flags().StringP("name", "n", "", "The name to use for the container registry") + if err := create.MarkFlagRequired("name"); err != nil { + printer.Error(fmt.Errorf("error marking container registry create 'name' flag required: %v", err)) + os.Exit(1) + } + + create.Flags().StringP("region", "i", "", "The ID of the region in which to create the container registry") + if err := create.MarkFlagRequired("region"); err != nil { + printer.Error(fmt.Errorf("error marking container registry create 'region' flag required: %v", err)) + os.Exit(1) + } + + create.Flags().BoolP("public", "p", false, "If the registry is publicly available. Should be true | false (default is false)") + if err := create.MarkFlagRequired("public"); err != nil { + printer.Error(fmt.Errorf("error marking container registry create 'public' flag required: %v", err)) + os.Exit(1) + } + + create.Flags().StringP("plan", "l", "", "The type of plan to use for the container registry") + if err := create.MarkFlagRequired("plan"); err != nil { + printer.Error(fmt.Errorf("error marking container registry create 'plan' flag required: %v", err)) + os.Exit(1) + } + + // Update + update := &cobra.Command{ + Use: "update ", + Short: "Update a container registry", + Aliases: []string{"u"}, + Long: updateLong, + Example: updateExample, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("please provide a container registry ID") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + public, errPu := cmd.Flags().GetBool("public") + if errPu != nil { + return fmt.Errorf("error parsing 'public' flag for container registry update : %v", errPu) + } + + plan, errPl := cmd.Flags().GetString("plan") + if errPl != nil { + return fmt.Errorf("error parsing 'plan' flag for container registry update : %v", errPl) + } + + o.UpdateReq = &govultr.ContainerRegistryUpdateReq{ + Plan: govultr.StringToStringPtr(plan), + } + + if cmd.Flags().Changed("public") { + o.UpdateReq.Public = govultr.BoolToBoolPtr(public) + } + + if err := o.update(); err != nil { + return fmt.Errorf("error updating container registry : %v", err) + } + + o.Base.Printer.Display(printer.Info("container registry has been updated"), nil) + + return nil + }, + } + + update.Flags().StringP("plan", "p", "", "Name of the plan used for the container registry") + update.Flags().BoolP("public", "b", false, "The container registry availability status") + + // Delete + del := &cobra.Command{ + Use: "delete ", + Short: "Delete a container registry", + Aliases: []string{"destroy", "d"}, + Long: deleteLong, + Example: deleteExample, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("please provide a container registry ID") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + if err := o.del(); err != nil { + return fmt.Errorf("error deleting container registry : %v", err) + } + + o.Base.Printer.Display(printer.Info("container registry has been deleted"), nil) + + return nil + }, + } + + // Plans + plans := &cobra.Command{ + Use: "plans", + Short: "List the plan names for container registry", + Aliases: []string{"p"}, + Long: plansLong, + Example: plansExample, + RunE: func(cmd *cobra.Command, args []string) error { + plans, err := o.plans() + if err != nil { + return fmt.Errorf("error retrieving plans for container registry : %v", err) + } + + data := &ContainerRegistryPlansPrinter{Plans: plans.Plans} + o.Base.Printer.Display(data, nil) + + return nil + }, + } + + // Regions + regions := &cobra.Command{ + Use: "regions", + Short: "List the available regions for container registry", + Aliases: []string{"i"}, + Long: regionsLong, + Example: regionsExample, + RunE: func(cmd *cobra.Command, args []string) error { + regs, meta, err := o.regions() + if err != nil { + return fmt.Errorf("error retrieving regions for container registry : %v", err) + } + + data := &ContainerRegistryRegionsPrinter{Regions: regs, Meta: meta} + o.Base.Printer.Display(data, nil) + + return nil + }, + } + + // Repository + repository := &cobra.Command{ + Use: "repository", + Short: "Interact with container registry repositories", + Aliases: []string{"r", "repo"}, + Long: repoLong, + Example: repoExample, + } + + // Repository List + repoList := &cobra.Command{ + Use: "list ", + Short: "List all container registries", + Aliases: []string{"l"}, + Long: listLong, + Example: listExample, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("please provide a container registry ID") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + o.Base.Options = utils.GetPaging(cmd) + repos, meta, err := o.repositoryList() + if err != nil { + return fmt.Errorf("error retrieving repositories for container registry : %v", err) + } + + data := &ContainerRegistryRepositoriesPrinter{Repositories: repos, Meta: meta} + o.Base.Printer.Display(data, nil) + + return nil + }, + } + + // Repository Get + repoGet := &cobra.Command{ + Use: "get ", + Short: "Get a container registry repository", + Aliases: []string{"g"}, + Long: getLong, + Example: getExample, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("please provide a container registry ID") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + name, errIm := cmd.Flags().GetString("image-name") + if errIm != nil { + return fmt.Errorf("error parsing 'image-name' flag for container registry repository get : %v", errIm) + } + + o.RepoName = name + + repo, err := o.repositoryGet() + if err != nil { + return fmt.Errorf("error getting container registry repository : %v", err) + } + + data := &ContainerRegistryRepositoryPrinter{Repository: repo} + o.Base.Printer.Display(data, nil) + + return nil + }, + } + + repoGet.Flags().StringP("image-name", "i", "", "The name of the image/repo") + if err := repoGet.MarkFlagRequired("image-name"); err != nil { + printer.Error(fmt.Errorf("error marking get container registry repository 'image-name' flag required: %v", err)) + os.Exit(1) + } + + // Repository Update + repoUpdate := &cobra.Command{ + Use: "update ", + Short: "Update a container registry repository", + Aliases: []string{"u"}, + Long: repoUpdateLong, + Example: repoUpdateExample, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("please provide a container registry ID") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + name, errIm := cmd.Flags().GetString("image-name") + if errIm != nil { + return fmt.Errorf("error parsing 'image-name' flag for container registry repository update : %v", errIm) + } + + description, errDe := cmd.Flags().GetString("description") + if errDe != nil { + return fmt.Errorf("error parsing 'description' flag for container registry repository update : %v", errDe) + } + + o.RepoName = name + o.RepoUpdateReq = &govultr.ContainerRegistryRepoUpdateReq{ + Description: description, + } + + if err := o.repositoryUpdate(); err != nil { + return fmt.Errorf("error updating container registry repository : %v", err) + } + + o.Base.Printer.Display(printer.Info("container registry repository has been updated"), nil) + + return nil + }, + } + + repoUpdate.Flags().StringP("image-name", "i", "", "The name of the image/repo") + if err := repoUpdate.MarkFlagRequired("image-name"); err != nil { + printer.Error(fmt.Errorf("error marking update container registry repository 'image-name' flag required: %v", err)) + os.Exit(1) + } + + repoUpdate.Flags().StringP("description", "d", "", "The description of the image/repo") + if err := repoUpdate.MarkFlagRequired("description"); err != nil { + printer.Error(fmt.Errorf("error marking update container registry repository 'description' flag required: %v", err)) + os.Exit(1) + } + + // Repository Delete + repoDelete := &cobra.Command{ + Use: "delete ", + Short: "Delete a container registry repository", + Aliases: []string{"destroy", "d"}, + Long: repoDeleteLong, + Example: repoDeleteExample, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("please provide a container registry ID") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + name, errIm := cmd.Flags().GetString("image-name") + if errIm != nil { + return fmt.Errorf("error parsing 'image-name' flag for container registry repository delete : %v", errIm) + } + + o.RepoName = name + + if err := o.repositoryDelete(); err != nil { + return fmt.Errorf("error deleting container registry repository : %v", err) + } + + o.Base.Printer.Display(printer.Info("container registry repository has been deleted"), nil) + + return nil + }, + } + + repoDelete.Flags().StringP("image-name", "i", "", "The name of the image/repo") + if err := repoDelete.MarkFlagRequired("image-name"); err != nil { + printer.Error(fmt.Errorf("error marking delete container registry repository 'image-name' flag required: %v", err)) + os.Exit(1) + } + + repository.AddCommand( + repoGet, + repoList, + repoUpdate, + repoDelete, + ) + + // Credentials + credentials := &cobra.Command{ + Use: "credentials", + Short: "Commands for container registry credentials", + Aliases: []string{""}, + Long: credentialsLong, + Example: credentialsExample, + } + + // Credentials Docker + credentialsDocker := &cobra.Command{ + Use: "docker ", + Short: "Create Docker credentials for a container registry", + Aliases: []string{"d"}, + Long: credentialsDockerLong, + Example: credentialsDockerExample, + RunE: func(cmd *cobra.Command, args []string) error { + expiry, errEx := cmd.Flags().GetInt("expiry-seconds") + if errEx != nil { + return fmt.Errorf("error parsing 'expiry-seconds' flag for container registry docker creds : %v", errEx) + } + + access, errAc := cmd.Flags().GetBool("read-write") + if errAc != nil { + return fmt.Errorf("error parsing 'read-write' flag for container registry docker creds : %v", errAc) + } + + o.CredentialsDockerReq = &govultr.DockerCredentialsOpt{ + ExpirySeconds: govultr.IntToIntPtr(expiry), + WriteAccess: govultr.BoolToBoolPtr(access), + } + + cred, err := o.credentialsDocker() + if err != nil { + return fmt.Errorf("error generating container registry repository docker credentials : %v", err) + } + + data := &ContainerRegistryCredentialDockerPrinter{Credential: cred} + o.Base.Printer.Display(data, nil) + + return nil + }, + } + + credentialsDocker.Flags().IntP( + "expiry-seconds", + "e", + 0, + "(optional) The seconds until these credentials expire. Default is 0, never", + ) + + credentialsDocker.Flags().BoolP( + "read-write", + "w", + false, + "(optional) Whether or not these credentials have write access. Should be true or false. Default is false", + ) + + credentials.AddCommand( + credentialsDocker, + ) + + cmd.AddCommand( + list, + get, + create, + update, + del, + plans, + regions, + repository, + credentials, + ) + + return cmd +} + +type options struct { + Base *cli.Base + CreateReq *govultr.ContainerRegistryReq + UpdateReq *govultr.ContainerRegistryUpdateReq + RepoName string + RepoUpdateReq *govultr.ContainerRegistryRepoUpdateReq + CredentialsDockerReq *govultr.DockerCredentialsOpt +} + +func (o *options) list() ([]govultr.ContainerRegistry, *govultr.Meta, error) { + cr, meta, _, err := o.Base.Client.ContainerRegistry.List(o.Base.Context, o.Base.Options) + return cr, meta, err +} + +func (o *options) get() (*govultr.ContainerRegistry, error) { + cr, _, err := o.Base.Client.ContainerRegistry.Get(o.Base.Context, o.Base.Args[0]) + return cr, err +} + +func (o *options) create() (*govultr.ContainerRegistry, error) { + cr, _, err := o.Base.Client.ContainerRegistry.Create(o.Base.Context, o.CreateReq) + return cr, err +} + +func (o *options) update() error { + _, _, err := o.Base.Client.ContainerRegistry.Update(o.Base.Context, o.Base.Args[0], o.UpdateReq) + return err +} + +func (o *options) del() error { + return o.Base.Client.ContainerRegistry.Delete(o.Base.Context, o.Base.Args[0]) +} + +func (o *options) plans() (*govultr.ContainerRegistryPlans, error) { + plans, _, err := o.Base.Client.ContainerRegistry.ListPlans(o.Base.Context) + return plans, err +} + +func (o *options) regions() ([]govultr.ContainerRegistryRegion, *govultr.Meta, error) { + regions, meta, _, err := o.Base.Client.ContainerRegistry.ListRegions(o.Base.Context) + return regions, meta, err +} + +func (o *options) repositoryList() ([]govultr.ContainerRegistryRepo, *govultr.Meta, error) { + repos, meta, _, err := o.Base.Client.ContainerRegistry.ListRepositories(o.Base.Context, o.Base.Args[0], o.Base.Options) + return repos, meta, err +} + +func (o *options) repositoryGet() (*govultr.ContainerRegistryRepo, error) { + repo, _, err := o.Base.Client.ContainerRegistry.GetRepository(o.Base.Context, o.Base.Args[0], o.RepoName) + return repo, err +} + +func (o *options) repositoryUpdate() error { + _, _, err := o.Base.Client.ContainerRegistry.UpdateRepository( + o.Base.Context, + o.Base.Args[0], + o.RepoName, + o.RepoUpdateReq, + ) + + return err +} + +func (o *options) repositoryDelete() error { + return o.Base.Client.ContainerRegistry.DeleteRepository(o.Base.Context, o.Base.Args[0], o.RepoName) +} + +func (o *options) credentialsDocker() (*govultr.ContainerRegistryDockerCredentials, error) { + cred, _, err := o.Base.Client.ContainerRegistry.CreateDockerCredentials( + o.Base.Context, + o.Base.Args[0], + o.CredentialsDockerReq, + ) + return cred, err +} diff --git a/cmd/containerregistry/printer.go b/cmd/containerregistry/printer.go new file mode 100644 index 00000000..098fc94e --- /dev/null +++ b/cmd/containerregistry/printer.go @@ -0,0 +1,376 @@ +package containerregistry + +import ( + "fmt" + "reflect" + "strconv" + + "github.com/vultr/govultr/v3" + "github.com/vultr/vultr-cli/v3/cmd/printer" + "github.com/vultr/vultr-cli/v3/cmd/utils" +) + +// ContainerRegistryPrinter ... +type ContainerRegistryPrinter struct { + Registry *govultr.ContainerRegistry `json:"registry"` +} + +// JSON ... +func (c *ContainerRegistryPrinter) JSON() []byte { + return printer.MarshalObject(c, "json") +} + +// YAML ... +func (c *ContainerRegistryPrinter) YAML() []byte { + return printer.MarshalObject(c, "yaml") +} + +// Columns ... +func (c *ContainerRegistryPrinter) Columns() [][]string { + return nil +} + +// Data ... +func (c *ContainerRegistryPrinter) Data() [][]string { + return [][]string{ + 0: {"ID", c.Registry.ID}, + 1: {"NAME", c.Registry.Name}, + 2: {"PUBLIC", strconv.FormatBool(c.Registry.Public)}, + 3: {"URN", c.Registry.URN}, + 4: {"REGION", c.Registry.Metadata.Region.Name}, + 5: {" "}, + 6: {"ROOT USER"}, + 7: {"ID", strconv.Itoa(c.Registry.RootUser.ID)}, + 8: {"USER NAME", c.Registry.RootUser.UserName}, + 9: {"PASSWORD", c.Registry.RootUser.Password}, + 10: {"CREATED", c.Registry.RootUser.DateCreated}, + 11: {"MODIFIED", c.Registry.RootUser.DateModified}, + 12: {" "}, + 13: {"STORAGE"}, + 14: {"USED", fmt.Sprintf("%vGB", c.Registry.Storage.Used.GigaBytes)}, + 15: {"ALLOWED", fmt.Sprintf("%vGB", c.Registry.Storage.Allowed.GigaBytes)}, + 16: {" "}, + 17: {"BILLING"}, + 18: {"PRICE", + strconv.FormatFloat( + float64(c.Registry.Metadata.Subscription.Billing.MonthlyPrice), + 'f', + utils.FloatPrecision, + utils.FloatBitDepth, + ), + }, + 19: {"CHARGES", + strconv.FormatFloat( + float64(c.Registry.Metadata.Subscription.Billing.PendingCharges), + 'f', + utils.FloatPrecision, + utils.FloatBitDepth, + ), + }, + 20: {" "}, + 21: {"CREATED", c.Registry.DateCreated}, + } +} + +// Paging ... +func (c *ContainerRegistryPrinter) Paging() [][]string { + return nil +} + +// ====================================== + +// ContainerRegistriesPrinter ... +type ContainerRegistriesPrinter struct { + Registries []govultr.ContainerRegistry `json:"registries"` + Meta *govultr.Meta `json:"meta"` +} + +// JSON ... +func (c *ContainerRegistriesPrinter) JSON() []byte { + return printer.MarshalObject(c, "json") +} + +// YAML ... +func (c *ContainerRegistriesPrinter) YAML() []byte { + return printer.MarshalObject(c, "yaml") +} + +// Columns ... +func (c *ContainerRegistriesPrinter) Columns() [][]string { + return [][]string{0: { + "ID", + "NAME", + "URN", + "USED/ALLOWED", + "REGION ID", + "REGION NAME", + "PUBLIC", + }} +} + +// Data ... +func (c *ContainerRegistriesPrinter) Data() [][]string { + if len(c.Registries) == 0 { + return [][]string{0: {"---", "---", "---", "---", "---", "---", "---", "---"}} + } + + var data [][]string + for i := range c.Registries { + usage := fmt.Sprintf("%vGB / %vGB", c.Registries[i].Storage.Used.GigaBytes, c.Registries[i].Storage.Allowed.GigaBytes) + data = append(data, []string{ + c.Registries[i].ID, + c.Registries[i].Name, + c.Registries[i].URN, + usage, + strconv.Itoa(c.Registries[i].Metadata.Region.ID), + c.Registries[i].Metadata.Region.Name, + strconv.FormatBool(c.Registries[i].Public), + }) + } + + return data +} + +// Paging ... +func (c *ContainerRegistriesPrinter) Paging() [][]string { + return printer.NewPaging(c.Meta.Total, &c.Meta.Links.Next, &c.Meta.Links.Prev).Compose() +} + +// ====================================== + +// ContainerRegistryPlansPrinter ... +type ContainerRegistryPlansPrinter struct { + Plans govultr.ContainerRegistryPlanTypes `json:"plans"` +} + +// JSON ... +func (c *ContainerRegistryPlansPrinter) JSON() []byte { + return printer.MarshalObject(c, "json") +} + +// YAML ... +func (c *ContainerRegistryPlansPrinter) YAML() []byte { + return printer.MarshalObject(c, "yaml") +} + +// Columns ... +func (c *ContainerRegistryPlansPrinter) Columns() [][]string { + return [][]string{0: { + "NAME", + "MAX STORAGE", + "MONTHLY PRICE", + }} +} + +// Data ... +func (c *ContainerRegistryPlansPrinter) Data() [][]string { + var data [][]string + topVals := reflect.ValueOf(c.Plans) + for i := 0; i < topVals.NumField(); i++ { + botVals := reflect.ValueOf(topVals.Field(i).Interface()) + + data = append(data, []string{ + botVals.FieldByName("VanityName").String(), + fmt.Sprintf("%vGB", botVals.FieldByName("MaxStorageMB").Int()/1024), //nolint:gomnd + strconv.FormatInt(botVals.FieldByName("MonthlyPrice").Int(), 10), + }) + } + return data +} + +// Paging ... +func (c *ContainerRegistryPlansPrinter) Paging() [][]string { + return nil +} + +// ====================================== + +// ContainerRegistryRegionsPrinter ... +type ContainerRegistryRegionsPrinter struct { + Regions []govultr.ContainerRegistryRegion `json:"regions"` + Meta *govultr.Meta `json:"meta"` +} + +// JSON ... +func (c *ContainerRegistryRegionsPrinter) JSON() []byte { + return printer.MarshalObject(c, "json") +} + +// YAML ... +func (c *ContainerRegistryRegionsPrinter) YAML() []byte { + return printer.MarshalObject(c, "yaml") +} + +// Columns ... +func (c *ContainerRegistryRegionsPrinter) Columns() [][]string { + return [][]string{0: { + "ID", + "NAME", + "URN", + "COUNTRY", + "REGION", + }} +} + +// Data ... +func (c *ContainerRegistryRegionsPrinter) Data() [][]string { + if len(c.Regions) == 0 { + return [][]string{0: {"---", "---", "---", "---", "---", "---"}} + } + + var data [][]string + for i := range c.Regions { + data = append(data, []string{ + strconv.Itoa(c.Regions[i].ID), + c.Regions[i].Name, + c.Regions[i].URN, + c.Regions[i].DataCenter.Country, + c.Regions[i].DataCenter.Region, + }) + } + + return data +} + +// Paging ... +func (c *ContainerRegistryRegionsPrinter) Paging() [][]string { + return printer.NewPaging(c.Meta.Total, &c.Meta.Links.Next, &c.Meta.Links.Prev).Compose() +} + +// ====================================== + +// ContainerRegistryRepositoryPrinter ... +type ContainerRegistryRepositoryPrinter struct { + Repository *govultr.ContainerRegistryRepo `json:"repository"` +} + +// JSON ... +func (c *ContainerRegistryRepositoryPrinter) JSON() []byte { + return printer.MarshalObject(c, "json") +} + +// YAML ... +func (c *ContainerRegistryRepositoryPrinter) YAML() []byte { + return printer.MarshalObject(c, "yaml") +} + +// Columns ... +func (c *ContainerRegistryRepositoryPrinter) Columns() [][]string { + return [][]string{0: { + "NAME", + "IMAGE", + "DESCRIPTION", + "DATE CREATED", + "DATE MODIFIED", + "PULLS", + "ARTIFACTS", + }} +} + +// Data ... +func (c *ContainerRegistryRepositoryPrinter) Data() [][]string { + return [][]string{0: { + c.Repository.Name, + c.Repository.Image, + c.Repository.Description, + c.Repository.DateCreated, + c.Repository.DateModified, + strconv.Itoa(c.Repository.PullCount), + strconv.Itoa(c.Repository.ArtifactCount), + }} +} + +// Paging ... +func (c *ContainerRegistryRepositoryPrinter) Paging() [][]string { + return nil +} + +// ====================================== + +// ContainerRegistryRepositoriesPrinter ... +type ContainerRegistryRepositoriesPrinter struct { + Repositories []govultr.ContainerRegistryRepo `json:"repositories"` + Meta *govultr.Meta `json:"meta"` +} + +// JSON ... +func (c *ContainerRegistryRepositoriesPrinter) JSON() []byte { + return printer.MarshalObject(c, "json") +} + +// YAML ... +func (c *ContainerRegistryRepositoriesPrinter) YAML() []byte { + return printer.MarshalObject(c, "yaml") +} + +// Columns ... +func (c *ContainerRegistryRepositoriesPrinter) Columns() [][]string { + return [][]string{0: { + "NAME", + "IMAGE", + "DESCRIPTION", + "DATE CREATED", + "DATE MODIFIED", + "PULLS", + "ARTIFACTS", + }} +} + +// Data ... +func (c *ContainerRegistryRepositoriesPrinter) Data() [][]string { + if len(c.Repositories) == 0 { + return [][]string{0: {"---", "---", "---", "---", "---", "---", "---"}} + } + + var data [][]string + for i := range c.Repositories { + data = append(data, []string{ + c.Repositories[i].Name, + c.Repositories[i].Image, + c.Repositories[i].Description, + c.Repositories[i].DateCreated, + c.Repositories[i].DateModified, + strconv.Itoa(c.Repositories[i].PullCount), + strconv.Itoa(c.Repositories[i].ArtifactCount), + }) + } + + return data +} + +// Paging ... +func (c *ContainerRegistryRepositoriesPrinter) Paging() [][]string { + return printer.NewPaging(c.Meta.Total, &c.Meta.Links.Next, &c.Meta.Links.Prev).Compose() +} + +// ====================================== + +// ContainerRegistryCredentialDockerPrinter ... +type ContainerRegistryCredentialDockerPrinter struct { + Credential *govultr.ContainerRegistryDockerCredentials `json:"docker_credentials"` +} + +// JSON ... +func (c *ContainerRegistryCredentialDockerPrinter) JSON() []byte { + return printer.MarshalObject(c, "json") +} + +// YAML ... +func (c *ContainerRegistryCredentialDockerPrinter) YAML() []byte { + return printer.MarshalObject(c, "yaml") +} + +// Columns ... +func (c *ContainerRegistryCredentialDockerPrinter) Columns() [][]string { + return nil +} + +// Data ... +func (c *ContainerRegistryCredentialDockerPrinter) Data() [][]string { + return [][]string{0: {c.Credential.String()}} +} + +// Paging ... +func (c *ContainerRegistryCredentialDockerPrinter) Paging() [][]string { + return nil +} diff --git a/cmd/database/database.go b/cmd/database/database.go new file mode 100644 index 00000000..fa989276 --- /dev/null +++ b/cmd/database/database.go @@ -0,0 +1,2612 @@ +// Package database is used by the CLI to control databases +package database + +import ( + "errors" + "fmt" + "os" + + "github.com/spf13/cobra" + "github.com/vultr/govultr/v3" + "github.com/vultr/vultr-cli/v3/cmd/printer" + "github.com/vultr/vultr-cli/v3/cmd/utils" + "github.com/vultr/vultr-cli/v3/pkg/cli" +) + +var ( + long = `Get commands available to database` + example = ` + # Full example + vultr-cli database + ` + listLong = `Get all databases on your Vultr account` + listExample = ` + # Full example + vultr-cli database list + + # Summarized view + vultr-cli database list --summarize + ` + createLong = `Create a new Managed Database with specified plan, region, and database engine/version` + createExample = ` + # Full example + vultr-cli database create --database-engine="mysql" --database-engine-version="8" --region="ewr" \ + --plan="vultr-dbaas-startup-cc-1-55-2" --label="example-db" + + # Full example with custom MySQL settings + vultr-cli database create --database-engine="mysql" --database-engine-version="8" --region="ewr" \ + --plan="vultr-dbaas-startup-cc-1-55-2" --label="example-db" --mysql-slow-query-log="true" --mysql-long-query-time="2" + ` + updateLong = `Updates a Managed Database with the supplied information` + updateExample = ` + # Full example + vultr-cli database update --region="sea" --plan="vultr-dbaas-startup-cc-2-80-4" + + # Full example with custom MySQL settings + vultr-cli database update --mysql-slow-query-log="true" --mysql-long-query-time="2" + ` +) + +// NewCmdDatabase provides the CLI command for database functions +func NewCmdDatabase(base *cli.Base) *cobra.Command { //nolint:funlen,gocyclo + o := &options{Base: base} + + cmd := &cobra.Command{ + Use: "database", + Short: "Access database commands", + Long: long, + Example: example, + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + utils.SetOptions(o.Base, cmd, args) + if !o.Base.HasAuth { + return errors.New(utils.APIKeyError) + } + return nil + }, + } + + // List + list := &cobra.Command{ + Use: "list", + Short: "List databases", + Aliases: []string{"l"}, + Long: listLong, + Example: listExample, + RunE: func(cmd *cobra.Command, args []string) error { + summarize, errSu := cmd.Flags().GetBool("summarize") + if errSu != nil { + return fmt.Errorf("error parsing flag 'summarize' for database list : %v", errSu) + } + + dbs, meta, err := o.list() + if err != nil { + return fmt.Errorf("error retrieving database list : %v", err) + } + + var data printer.ResourceOutput + if summarize { + data = &DBsSummaryPrinter{DBs: dbs, Meta: meta} + } else { + data = &DBsPrinter{DBs: dbs, Meta: meta} + } + + o.Base.Printer.Display(data, nil) + + return nil + }, + } + + list.Flags().BoolP("summarize", "", false, "(optional) Summarize the list output. One line per database") + + // Get + get := &cobra.Command{ + Use: "get ", + Short: "Retrieve a database", + Aliases: []string{"g"}, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("please provide a database ID") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + db, err := o.get() + if err != nil { + return fmt.Errorf("error retrieving database : %v", err) + } + + data := &DBPrinter{DB: db} + o.Base.Printer.Display(data, nil) + + return nil + }, + } + + // Create + create := &cobra.Command{ + Use: "create", + Short: "Create database", + Aliases: []string{"c"}, + Long: createLong, + Example: createExample, + RunE: func(cmd *cobra.Command, args []string) error { + engine, errEn := cmd.Flags().GetString("database-engine") + if errEn != nil { + return fmt.Errorf("error parsing flag 'database-engine' for database create : %v", errEn) + } + + engineVersion, errEg := cmd.Flags().GetString("database-engine-version") + if errEg != nil { + return fmt.Errorf("error parsing flag 'database-engine-version' for database create : %v", errEg) + } + + region, errRe := cmd.Flags().GetString("region") + if errRe != nil { + return fmt.Errorf("error parsing flag 'region' for database create : %v", errRe) + } + + plan, errPl := cmd.Flags().GetString("plan") + if errPl != nil { + return fmt.Errorf("error parsing flag 'plan' for database create : %v", errPl) + } + + label, errLa := cmd.Flags().GetString("label") + if errLa != nil { + return fmt.Errorf("error parsing flag 'label' for database create : %v", errLa) + } + + // Optional + tag, errTa := cmd.Flags().GetString("tag") + if errTa != nil { + return fmt.Errorf("error parsing flag 'tag' for database create : %v", errTa) + } + + vpc, errVp := cmd.Flags().GetString("vpc-id") + if errVp != nil { + return fmt.Errorf("error parsing flag 'vpc-id' for database create : %v", errVp) + } + + maintenanceDOW, errMa := cmd.Flags().GetString("maintenance-dow") + if errMa != nil { + return fmt.Errorf("error parsing flag 'maintenance-dow' for database create : %v", errMa) + } + + maintenanceTime, errMt := cmd.Flags().GetString("maintenance-time") + if errMt != nil { + return fmt.Errorf("error parsing flag 'maintenance-time' for database create : %v", errMt) + } + + trustedIPs, errTr := cmd.Flags().GetStringSlice("trusted-ips") + if errTr != nil { + return fmt.Errorf("error parsing flag 'trusted-ips' for database create : %v", errTr) + } + + mysqlSQLModes, errMy := cmd.Flags().GetStringSlice("mysql-sql-modes") + if errMy != nil { + return fmt.Errorf("error parsing flag 'mysql-sql-modes' for database create : %v", errMy) + } + + mysqlRequirePrimaryKey, errMq := cmd.Flags().GetBool("mysql-require-primary-key") + if errMq != nil { + return fmt.Errorf("error parsing flag 'mysql-require-primary-key' for database create : %v", errMq) + } + + mySQLSlowQueryLog, errMl := cmd.Flags().GetBool("mysql-slow-query-log") + if errMl != nil { + return fmt.Errorf("error parsing flag 'mysql-slow-query-log' for database create : %v", errMl) + } + + mySQLLongQueryTime, errMt := cmd.Flags().GetInt("mysql-long-query-time") + if errMt != nil { + return fmt.Errorf("error parsing flag 'mysql-long-query-time' for database create : %v", errMt) + } + + redisEvictionPolicy, errEe := cmd.Flags().GetString("redis-eviction-policy") + if errEe != nil { + return fmt.Errorf("error parsing flag 'redis-eviction-policy' for database create : %v", errEe) + } + + o.CreateReq = &govultr.DatabaseCreateReq{ + DatabaseEngine: engine, + DatabaseEngineVersion: engineVersion, + Region: region, + Plan: plan, + Label: label, + Tag: tag, + VPCID: vpc, + MaintenanceDOW: maintenanceDOW, + MaintenanceTime: maintenanceTime, + TrustedIPs: trustedIPs, + MySQLSQLModes: mysqlSQLModes, + MySQLRequirePrimaryKey: &mysqlRequirePrimaryKey, + MySQLSlowQueryLog: &mySQLSlowQueryLog, + MySQLLongQueryTime: mySQLLongQueryTime, + RedisEvictionPolicy: redisEvictionPolicy, + } + + db, err := o.create() + if err != nil { + return fmt.Errorf("error creating database : %v", err) + } + + data := &DBPrinter{DB: db} + o.Base.Printer.Display(data, nil) + + return nil + }, + } + + create.Flags().StringP("database-engine", "e", "", "database engine for the new manaaged database") + if err := create.MarkFlagRequired("database-engine"); err != nil { + fmt.Printf("error marking database create 'database-engine' flag required: %v", err) + os.Exit(1) + } + + create.Flags().StringP("database-engine-version", "v", "", "database engine version for the new manaaged database") + if err := create.MarkFlagRequired("database-engine-version"); err != nil { + fmt.Printf("error marking database create 'database-engine-version' flag required: %v", err) + os.Exit(1) + } + + create.Flags().StringP("region", "r", "", "region id for the new managed database") + if err := create.MarkFlagRequired("region"); err != nil { + fmt.Printf("error marking database create 'region' flag required: %v", err) + os.Exit(1) + } + + create.Flags().StringP("plan", "p", "", "plan id for the new managed database") + if err := create.MarkFlagRequired("plan"); err != nil { + fmt.Printf("error marking database create 'plan' flag required: %v", err) + os.Exit(1) + } + + create.Flags().StringP("label", "l", "", "label for the new managed database") + if err := create.MarkFlagRequired("label"); err != nil { + fmt.Printf("error marking database create 'label' flag required: %v", err) + os.Exit(1) + } + + create.Flags().String("tag", "t", "tag for the new managed database") + create.Flags().String("vpc-id", "", "vpc id for the new managed database") + create.Flags().String("maintenance-dow", "", "maintenance day of week for the new managed database") + create.Flags().String("maintenance-time", "", "maintenance time for the new managed database") + create.Flags().StringSlice( + "trusted-ips", + []string{}, + "comma-separated list of trusted ip addresses for the new managed database", + ) + create.Flags().StringSlice("mysql-sql-modes", []string{}, "comma-separated list of sql modes for the new managed database") + create.Flags().Bool("mysql-require-primary-key", true, "enable requiring primary keys for the new mysql managed database") + create.Flags().Bool("mysql-slow-query-log", false, "enable slow query logging for the new mysql managed database") + create.Flags().Int( + "mysql-long-query-time", + 0, + "long query time for the new mysql managed database when slow query logging is enabled", + ) + create.Flags().String("redis-eviction-policy", "", "eviction policy for the new redis managed database") + + // Update + update := &cobra.Command{ + Use: "update ", + Short: "Update a database", + Aliases: []string{"u"}, + Long: updateLong, + Example: updateExample, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("please provide a database ID") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + region, errRe := cmd.Flags().GetString("region") + if errRe != nil { + return fmt.Errorf("error parsing flag 'region' for database update : %v", errRe) + } + + plan, errPl := cmd.Flags().GetString("plan") + if errPl != nil { + return fmt.Errorf("error parsing flag 'plan' for database update : %v", errPl) + } + + label, errLa := cmd.Flags().GetString("label") + if errLa != nil { + return fmt.Errorf("error parsing flag 'label' for database update : %v", errLa) + } + + tag, errTa := cmd.Flags().GetString("tag") + if errTa != nil { + return fmt.Errorf("error parsing flag 'tag' for database update : %v", errTa) + } + + maintenanceDOW, errMa := cmd.Flags().GetString("maintenance-dow") + if errMa != nil { + return fmt.Errorf("error parsing flag 'maintenance-dow' for database update : %v", errMa) + } + + maintenanceTime, errMt := cmd.Flags().GetString("maintenance-time") + if errMt != nil { + return fmt.Errorf("error parsing flag 'maintenance-time' for database update : %v", errMt) + } + + clusterTimeZone, errTz := cmd.Flags().GetString("cluster-time-zone") + if errTz != nil { + return fmt.Errorf("error parsing flag 'cluster-time-zone' for database update : %v", errTz) + } + + trustedIPs, errTr := cmd.Flags().GetStringSlice("trusted-ips") + if errTr != nil { + return fmt.Errorf("error parsing flag 'trusted-ips' for database update : %v", errTr) + } + + mysqlSQLModes, errMy := cmd.Flags().GetStringSlice("mysql-sql-modes") + if errMy != nil { + return fmt.Errorf("error parsing flag 'mysql-sql-modes' for database update : %v", errMy) + } + + mySQLLongQueryTime, errMt := cmd.Flags().GetInt("mysql-long-query-time") + if errMt != nil { + return fmt.Errorf("error parsing flag 'mysql-long-query-time' for database update : %v", errMt) + } + + redisEvictionPolicy, errEe := cmd.Flags().GetString("redis-eviction-policy") + if errEe != nil { + return fmt.Errorf("error parsing flag 'redis-eviction-policy' for database update : %v", errEe) + } + + o.UpdateReq = &govultr.DatabaseUpdateReq{} + + if cmd.Flags().Changed("region") { + o.UpdateReq.Region = region + } + + if cmd.Flags().Changed("plan") { + o.UpdateReq.Plan = plan + } + + if cmd.Flags().Changed("label") { + o.UpdateReq.Label = label + } + + if cmd.Flags().Changed("tag") { + o.UpdateReq.Tag = tag + } + + if cmd.Flags().Changed("maintenance-dow") { + o.UpdateReq.MaintenanceDOW = maintenanceDOW + } + + if cmd.Flags().Changed("maintenance-time") { + o.UpdateReq.MaintenanceTime = maintenanceTime + } + + if cmd.Flags().Changed("cluster-time-zone") { + o.UpdateReq.ClusterTimeZone = clusterTimeZone + } + + if cmd.Flags().Changed("trusted-ips") { + o.UpdateReq.TrustedIPs = trustedIPs + } + + if cmd.Flags().Changed("mysql-sql-modes") { + o.UpdateReq.MySQLSQLModes = mysqlSQLModes + } + + if cmd.Flags().Changed("mysql-long-query-time") { + o.UpdateReq.MySQLLongQueryTime = mySQLLongQueryTime + } + + if cmd.Flags().Changed("redis-eviction-policy") { + o.UpdateReq.RedisEvictionPolicy = redisEvictionPolicy + } + + db, err := o.update() + if err != nil { + return fmt.Errorf("error updating database : %v", err) + } + + data := &DBPrinter{DB: db} + o.Base.Printer.Display(data, nil) + + return nil + }, + } + + update.Flags().StringP("region", "r", "", "region id for the managed database") + update.Flags().StringP("plan", "p", "", "plan id for the managed database") + update.Flags().StringP("label", "l", "", "label for the managed database") + update.Flags().StringP("tag", "t", "", "tag for the managed database") + update.Flags().String("vpc-id", "", "vpc id for the managed database") + update.Flags().String("maintenance-dow", "", "maintenance day of week for the managed database") + update.Flags().String("maintenance-time", "", "maintenance time for the managed database") + update.Flags().String("cluster-time-zone", "", "configured time zone for the managed database") + update.Flags().StringSlice( + "trusted-ips", + []string{}, + "comma-separated list of trusted ip addresses for the managed database", + ) + update.Flags().StringSlice( + "mysql-sql-modes", + []string{}, + "comma-separated list of sql modes for the managed database", + ) + update.Flags().Bool("mysql-require-primary-key", true, "enable requiring primary keys for the mysql managed database") + update.Flags().Bool("mysql-slow-query-log", false, "enable slow query logging for the mysql managed database") + update.Flags().String( + "mysql-long-query-time", + "", + "long query time for the mysql managed database when slow query logging is enabled", + ) + update.Flags().String("redis-eviction-policy", "", "eviction policy for the redis managed database") + + // Delete + del := &cobra.Command{ + Use: "delete ", + Short: "Delete a database", + Aliases: []string{"destroy", "d"}, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("please provide a database ID") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + if err := o.del(); err != nil { + return fmt.Errorf("error deleting database : %v", err) + } + + o.Base.Printer.Display(printer.Info("Database has been deleted"), nil) + + return nil + }, + } + + // Plan + plan := &cobra.Command{ + Use: "plan", + Short: "Commands to access database plans", + } + + // Plan List + planList := &cobra.Command{ + Use: "list", + Short: "List database plans", + Aliases: []string{"l"}, + RunE: func(cmd *cobra.Command, args []string) error { + plans, meta, err := o.listPlans() + if err != nil { + return fmt.Errorf("error retrieving database plans : %v", err) + } + + data := &PlansPrinter{Plans: plans, Meta: meta} + o.Base.Printer.Display(data, nil) + + return nil + }, + } + + plan.AddCommand( + planList, + ) + + // User + user := &cobra.Command{ + Use: "user", + Short: "Commands to handle database users", + } + + // User List + userList := &cobra.Command{ + Use: "list ", + Short: "List database users", + RunE: func(cmd *cobra.Command, args []string) error { + us, meta, err := o.listUsers() + if err != nil { + return fmt.Errorf("error retrieving database users : %v", err) + } + + data := &UsersPrinter{Users: us, Meta: meta} + o.Base.Printer.Display(data, nil) + + return nil + }, + } + + // User Get + userGet := &cobra.Command{ + Use: "get ", + Short: "Get a database user", + Args: func(cmd *cobra.Command, args []string) error { + if len(args) != 2 { + return errors.New("please provide a database ID and a user name") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + us, err := o.getUser() + if err != nil { + return fmt.Errorf("error retrieving database user : %v", err) + } + + data := &UserPrinter{User: us} + o.Base.Printer.Display(data, nil) + + return nil + }, + } + + // User Create + userCreate := &cobra.Command{ + Use: "create ", + Short: "Create a database user", + Args: func(cmd *cobra.Command, args []string) error { + if len(args) != 1 { + return errors.New("please provide a database ID") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + username, errUs := cmd.Flags().GetString("username") + if errUs != nil { + return fmt.Errorf("error parsing flag 'username' for database user create : %v", errUs) + } + + password, errPa := cmd.Flags().GetString("password") + if errPa != nil { + return fmt.Errorf("error parsing flag 'password' for database user create : %v", errPa) + } + + encryption, errEn := cmd.Flags().GetString("encryption") + if errEn != nil { + return fmt.Errorf("error parsing flag 'encryption' for database user create : %v", errEn) + } + + o.UserCreateReq = &govultr.DatabaseUserCreateReq{ + Username: username, + Password: password, + Encryption: encryption, + } + + us, err := o.createUser() + if err != nil { + return fmt.Errorf("error creating database user : %v", err) + } + + data := &UserPrinter{User: us} + o.Base.Printer.Display(data, nil) + + return nil + }, + } + userCreate.Flags().StringP("username", "u", "", "username for the new manaaged database user") + userCreate.Flags().StringP( + "password", + "p", + "", + "password for the new manaaged database user (omit or leave empty to generate a random secure password)", + ) + userCreate.Flags().StringP("encryption", "e", "", "encryption type for the new managed database user (MySQL only)") + + // User Update + userUpdate := &cobra.Command{ + Use: "update ", + Short: "Update a database user", + Args: func(cmd *cobra.Command, args []string) error { + if len(args) != 2 { + return errors.New("please provide a database ID and a user name") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + password, errPa := cmd.Flags().GetString("password") + if errPa != nil { + return fmt.Errorf("error parsing flag 'password' for database user create : %v", errPa) + } + + o.UserUpdateReq = &govultr.DatabaseUserUpdateReq{ + Password: password, + } + + us, err := o.updateUser() + if err != nil { + return fmt.Errorf("error updating database user : %v", err) + } + + data := &UserPrinter{User: us} + o.Base.Printer.Display(data, nil) + + return nil + }, + } + + userUpdate.Flags().StringP( + "password", + "p", + "", + "password for the new manaaged database user (omit or leave empty to generate a random secure password)", + ) + + // User Delete + userDelete := &cobra.Command{ + Use: "delete ", + Short: "Delete a database user", + Args: func(cmd *cobra.Command, args []string) error { + if len(args) != 2 { + return errors.New("please provide a database ID and a user name") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + if err := o.delUser(); err != nil { + return fmt.Errorf("error deleting database user : %v", err) + } + + o.Base.Printer.Display(printer.Info("User deleted"), nil) + + return nil + }, + } + + // User ACL + userACL := &cobra.Command{ + Use: "acl", + Short: "commands to handle managed database user access control (Redis only)", + } + + // User ACL Update + userACLUpdate := &cobra.Command{ + Use: "update ", + Short: "Update a database user ACL", + Args: func(cmd *cobra.Command, args []string) error { + if len(args) != 2 { + return errors.New("please provide a database ID and a user name") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + categories, errCa := cmd.Flags().GetStringSlice("redis-acl-categories") + if errCa != nil { + return fmt.Errorf("error parsing flag 'redis-acl-categories' for database user create : %v", errCa) + } + + channels, errCh := cmd.Flags().GetStringSlice("redis-acl-channels") + if errCh != nil { + return fmt.Errorf("error parsing flag 'redis-acl-channels' for database user create : %v", errCh) + } + + commands, errCo := cmd.Flags().GetStringSlice("redis-acl-commands") + if errCo != nil { + return fmt.Errorf("error parsing flag 'redis-acl-commands' for database user create : %v", errCo) + } + + keys, errKe := cmd.Flags().GetStringSlice("redis-acl-keys") + if errKe != nil { + return fmt.Errorf("error parsing flag 'redis-acl-keys' for database user create : %v", errKe) + } + + o.UserUpdateACLReq = &govultr.DatabaseUserACLReq{} + + if cmd.Flags().Changed("redis-acl-categories") { + o.UserUpdateACLReq.RedisACLCategories = &categories + } + + if cmd.Flags().Changed("redis-acl-channels") { + o.UserUpdateACLReq.RedisACLChannels = &channels + } + + if cmd.Flags().Changed("redis-acl-commands") { + o.UserUpdateACLReq.RedisACLCommands = &commands + } + + if cmd.Flags().Changed("redis-acl-keys") { + o.UserUpdateACLReq.RedisACLKeys = &keys + } + + us, err := o.updateUserACL() + if err != nil { + return fmt.Errorf("error updating database user acl : %v", err) + } + + data := &UserPrinter{User: us} + o.Base.Printer.Display(data, nil) + + return nil + }, + } + + userACLUpdate.Flags().StringSlice( + "redis-acl-categories", + []string{}, + "list of rules for command categories", + ) + userACLUpdate.Flags().StringSlice( + "redis-acl-channels", + []string{}, + "list of publish/subscribe channel patterns", + ) + userACLUpdate.Flags().StringSlice( + "redis-acl-commands", + []string{}, + "list of rules for individual commands", + ) + userACLUpdate.Flags().StringSlice( + "redis-acl-keys", + []string{}, + "list of key access rules", + ) + + userACLUpdate.MarkFlagsOneRequired( + "redis-acl-categories", + "redis-acl-channels", + "redis-acl-commands", + "redis-acl-keys", + ) + + userACL.AddCommand( + userACLUpdate, + ) + + user.AddCommand( + userList, + userGet, + userCreate, + userUpdate, + userDelete, + userACL, + ) + + // Logical Database + db := &cobra.Command{ + Use: "db", + Short: "Commands to handle database logical dbs", + } + + // Logical DB List + dbList := &cobra.Command{ + Use: "list ", + Short: "List logical databases", + Args: func(cmd *cobra.Command, args []string) error { + if len(args) != 1 { + return errors.New("please provide a database ID") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + dbs, meta, err := o.listDBs() + if err != nil { + return fmt.Errorf("error retrieving logical databases: %v", err) + } + + data := &LogicalDBsPrinter{DBs: dbs, Meta: meta} + o.Base.Printer.Display(data, nil) + + return nil + }, + } + + // Logical DB Create + dbCreate := &cobra.Command{ + Use: "create ", + Short: "Create a logical database ", + Args: func(cmd *cobra.Command, args []string) error { + if len(args) != 1 { + return errors.New("please provide a database ID") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + name, errNa := cmd.Flags().GetString("name") + if errNa != nil { + return fmt.Errorf("error parsing flag 'name' for logical database create : %v", errNa) + } + + o.DBCreateReq = &govultr.DatabaseDBCreateReq{ + Name: name, + } + + db, err := o.createDB() + if err != nil { + return fmt.Errorf("error creating a logical database : %v", err) + } + + data := &LogicalDBPrinter{DB: db} + o.Base.Printer.Display(data, nil) + + return nil + }, + } + + dbCreate.Flags().StringP("name", "n", "", "name of the new logical database within the manaaged database") + if err := dbCreate.MarkFlagRequired("name"); err != nil { + fmt.Printf("error marking logical database create 'name' flag required: %v", err) + os.Exit(1) + } + + // Logical DB Delete + dbDel := &cobra.Command{ + Use: "delete ", + Short: "Delete a logical database ", + Args: func(cmd *cobra.Command, args []string) error { + if len(args) != 2 { + return errors.New("please provide a database ID and a DB name") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + if err := o.delDB(); err != nil { + return fmt.Errorf("error deleting logical database : %v", err) + } + + o.Base.Printer.Display(printer.Info("Logical DB deleted"), nil) + + return nil + }, + } + + db.AddCommand( + dbList, + dbCreate, + dbDel, + ) + + // Usage + usage := &cobra.Command{ + Use: "usage", + Short: "Commands to display database usage information", + } + + // Usage Get + usageGet := &cobra.Command{ + Use: "get ", + Short: "Get database usage", + Args: func(cmd *cobra.Command, args []string) error { + if len(args) != 1 { + return errors.New("please provide a database ID") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + us, err := o.getUsage() + if err != nil { + return fmt.Errorf("error retrieving database usage : %v", err) + } + + data := &UsagePrinter{Usage: us} + o.Base.Printer.Display(data, nil) + + return nil + }, + } + + usage.AddCommand( + usageGet, + ) + + // Maintenance + maintenance := &cobra.Command{ + Use: "maintenance", + Short: "Commands to handle database maintenance updates", + } + + // Maintenance List + maintenanceList := &cobra.Command{ + Use: "list ", + Short: "List maintenance updates for a database", + Args: func(cmd *cobra.Command, args []string) error { + if len(args) != 1 { + return errors.New("please provide a database ID") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + upds, err := o.listMaintUpdates() + if err != nil { + return fmt.Errorf("error retrieving database maintenance updates : %v", err) + } + + data := &UpdatesPrinter{Updates: upds} + o.Base.Printer.Display(data, nil) + + return nil + }, + } + + // Maintenance Start + maintenanceStart := &cobra.Command{ + Use: "start ", + Short: "Start database maintenance update", + Args: func(cmd *cobra.Command, args []string) error { + if len(args) != 1 { + return errors.New("please provide a database ID") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + message, err := o.startMaintUpdate() + if err != nil { + return fmt.Errorf("error starting database maintenance update: %v", err) + } + + o.Base.Printer.Display(printer.Info(message), nil) + + return nil + }, + } + + maintenance.AddCommand( + maintenanceList, + maintenanceStart, + ) + + // Alert + alert := &cobra.Command{ + Use: "alert", + Short: "Commands to handle database alerts", + } + + // Alert List + alertList := &cobra.Command{ + Use: "list ", + Short: "List database alerts", + Args: func(cmd *cobra.Command, args []string) error { + if len(args) != 1 { + return errors.New("please provide a database ID") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + period, errPe := cmd.Flags().GetString("period") + if errPe != nil { + return fmt.Errorf("error parsing flag 'period' for alert list : %v", errPe) + } + + o.AlertsReq = &govultr.DatabaseListAlertsReq{ + Period: period, + } + + als, err := o.listAlerts() + if err != nil { + return fmt.Errorf("error retrieving database alerts : %v", err) + } + + data := &AlertsPrinter{Alerts: als} + o.Base.Printer.Display(data, nil) + + return nil + }, + } + + alertList.Flags().StringP( + "period", + "p", + "", + "period (day, week, month, year) for viewing service alerts for a manaaged database", + ) + if err := alertList.MarkFlagRequired("period"); err != nil { + fmt.Printf("error marking alert list 'period' flag required: %v", err) + os.Exit(1) + } + + alert.AddCommand( + alertList, + ) + + // Migration + migration := &cobra.Command{ + Use: "migration", + Short: "Commands to handle database migrations", + } + + // Migration Get + migrationGet := &cobra.Command{ + Use: "get ", + Short: "Get migration status of a database", + Args: func(cmd *cobra.Command, args []string) error { + if len(args) != 1 { + return errors.New("please provide a database ID") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + mig, err := o.getMigrationStatus() + if err != nil { + return fmt.Errorf("error retrieving database migration status : %v", err) + } + + data := &MigrationPrinter{Migration: mig} + o.Base.Printer.Display(data, nil) + + return nil + }, + } + + // Migration Start + migrationStart := &cobra.Command{ + Use: "start ", + Short: "Get migration status of a database", + Args: func(cmd *cobra.Command, args []string) error { + if len(args) != 1 { + return errors.New("please provide a database ID") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + host, errHo := cmd.Flags().GetString("host") + if errHo != nil { + return fmt.Errorf("error parsing flag 'encryption' for migration start : %v", errHo) + } + + port, errPo := cmd.Flags().GetInt("port") + if errPo != nil { + return fmt.Errorf("error parsing flag 'encryption' for migration start : %v", errPo) + } + + username, errUs := cmd.Flags().GetString("username") + if errUs != nil { + return fmt.Errorf("error parsing flag 'encryption' for migration start : %v", errUs) + } + + password, errPa := cmd.Flags().GetString("password") + if errPa != nil { + return fmt.Errorf("error parsing flag 'encryption' for migration start : %v", errPa) + } + + database, errDa := cmd.Flags().GetString("database") + if errDa != nil { + return fmt.Errorf("error parsing flag 'encryption' for migration start : %v", errDa) + } + + ignored, errIg := cmd.Flags().GetString("ignored-dbs") + if errIg != nil { + return fmt.Errorf("error parsing flag 'encryption' for migration start : %v", errIg) + } + + ssl, errSs := cmd.Flags().GetBool("ssl") + if errSs != nil { + return fmt.Errorf("error parsing flag 'encryption' for migration start : %v", errSs) + } + + o.MigrationReq = &govultr.DatabaseMigrationStartReq{ + Host: host, + Port: port, + Username: username, + Password: password, + Database: database, + IgnoredDatabases: ignored, + SSL: &ssl, + } + + mig, err := o.startMigration() + if err != nil { + return fmt.Errorf("error retrieving database migration status : %v", err) + } + + data := &MigrationPrinter{Migration: mig} + o.Base.Printer.Display(data, nil) + + return nil + }, + } + + migrationStart.Flags().String("host", "", "source host for the manaaged database migration") + migrationStart.Flags().Int("port", 0, "source port for the manaaged database migration") + migrationStart.Flags().String( + "username", + "", + "source username for the manaaged database migration (uses `default` for Redis if omitted)", + ) + migrationStart.Flags().String("password", "", "source password for the manaaged database migration") + migrationStart.Flags().String( + "database", + "", + "source database for the manaaged database migration (MySQL/PostgreSQL only)", + ) + migrationStart.Flags().String( + "ignored-dbs", + "", + "comma-separated list of ignored databases for the manaaged database migration (MySQL/PostgreSQL only)", + ) + migrationStart.Flags().Bool("ssl", true, "source ssl requirement for the manaaged database migration") + + // Migration Detach + migrationDetach := &cobra.Command{ + Use: "detach ", + Short: "Detach a migration from a database ", + Args: func(cmd *cobra.Command, args []string) error { + if len(args) != 1 { + return errors.New("please provide a database ID") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + if err := o.detachMigration(); err != nil { + return fmt.Errorf("error detaching migration from database : %v", err) + } + + o.Base.Printer.Display(printer.Info("Migration detached"), nil) + + return nil + }, + } + + migration.AddCommand( + migrationGet, + migrationStart, + migrationDetach, + ) + + // Read Replica + readReplica := &cobra.Command{ + Use: "read-replica", + Short: "Commands to handle database read replicas", + } + + // Read Replica Add + readReplicaCreate := &cobra.Command{ + Use: "create ", + Short: "Create a read-only replica of a database", + Args: func(cmd *cobra.Command, args []string) error { + if len(args) != 1 { + return errors.New("please provide a database ID") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + region, errRe := cmd.Flags().GetString("region") + if errRe != nil { + return fmt.Errorf("error parsing flag 'region' for read-replica create : %v", errRe) + } + + label, errLa := cmd.Flags().GetString("label") + if errLa != nil { + return fmt.Errorf("error parsing flag 'label' for read-replica create : %v", errLa) + } + + o.ReadReplicaCreateReq = &govultr.DatabaseAddReplicaReq{ + Region: region, + Label: label, + } + + rr, err := o.createReadReplica() + if err != nil { + return fmt.Errorf("error creating database read replica: %v", err) + } + + data := &DBPrinter{DB: rr} + o.Base.Printer.Display(data, nil) + + return nil + }, + } + + readReplicaCreate.Flags().StringP("region", "r", "", "region id for the new managed database read replica") + if err := readReplicaCreate.MarkFlagRequired("region"); err != nil { + fmt.Printf("error marking read replica create 'region' flag required: %v", err) + os.Exit(1) + } + + readReplicaCreate.Flags().StringP("label", "l", "", "label for the new managed database read replica") + if err := readReplicaCreate.MarkFlagRequired("label"); err != nil { + fmt.Printf("error marking read replica create 'label' flag required: %v", err) + os.Exit(1) + } + + // Read Replica Promote + readReplicaPromote := &cobra.Command{ + Use: "promote ", + Short: "Promote a read-only replica of a database", + Args: func(cmd *cobra.Command, args []string) error { + if len(args) != 1 { + return errors.New("please provide a database ID") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + if err := o.promoteReadReplica(); err != nil { + return fmt.Errorf("error promoting database read replica: %v", err) + } + + o.Base.Printer.Display(printer.Info("Read replica has been promoted"), nil) + + return nil + }, + } + + readReplica.AddCommand( + readReplicaCreate, + readReplicaPromote, + ) + + // Backup + backup := &cobra.Command{ + Use: "backup", + Short: "Commands to handle database backups, restores and forks", + } + + // Backup Get + backupGet := &cobra.Command{ + Use: "get ", + Short: "Get get latest and oldest database backup", + Args: func(cmd *cobra.Command, args []string) error { + if len(args) != 1 { + return errors.New("please provide a database ID") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + bk, err := o.getBackup() + if err != nil { + return fmt.Errorf("error retrieving database backups : %v", err) + } + + data := &BackupPrinter{Backup: bk} + o.Base.Printer.Display(data, nil) + + return nil + }, + } + + // Backup Restore + backupRestore := &cobra.Command{ + Use: "restore ", + Short: "Restore a database backup ", + Args: func(cmd *cobra.Command, args []string) error { + if len(args) != 1 { + return errors.New("please provide a database ID") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + label, errLa := cmd.Flags().GetString("label") + if errLa != nil { + return fmt.Errorf("error parsing flag 'label' for backup restore : %v", errLa) + } + + rtype, errRt := cmd.Flags().GetString("type") + if errRt != nil { + return fmt.Errorf("error parsing flag 'type' for backup restore : %v", errRt) + } + + date, errDa := cmd.Flags().GetString("date") + if errDa != nil { + return fmt.Errorf("error parsing flag 'date' for backup restore : %v", errDa) + } + + time, errTi := cmd.Flags().GetString("time") + if errTi != nil { + return fmt.Errorf("error parsing flag 'time' for backup restore : %v", errTi) + } + + o.BackupReq = &govultr.DatabaseBackupRestoreReq{ + Label: label, + Type: rtype, + Date: date, + Time: time, + } + + bk, err := o.restoreBackup() + if err != nil { + return fmt.Errorf("error restoring database from backup : %v", err) + } + + data := &DBPrinter{DB: bk} + o.Base.Printer.Display(data, nil) + + return nil + }, + } + + backupRestore.Flags().String("label", "", "label for the new managed database restored from backup") + if err := backupRestore.MarkFlagRequired("label"); err != nil { + fmt.Printf("error marking backup restore 'label' flag required: %v", err) + os.Exit(1) + } + + backupRestore.Flags().String( + "type", + "", + "restoration type: `pitr` for point-in-time recovery or `basebackup` for latest backup (default)", + ) + backupRestore.Flags().String("date", "", "backup date to use for point-in-time recovery") + backupRestore.Flags().String("time", "", "backup time to use for point-in-time recovery") + + // Backup Fork + backupFork := &cobra.Command{ + Use: "fork ", + Short: "Fork a database from backup", + Args: func(cmd *cobra.Command, args []string) error { + if len(args) != 1 { + return errors.New("please provide a database ID") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + region, errDa := cmd.Flags().GetString("region") + if errDa != nil { + return fmt.Errorf("error parsing flag 'region' for backup fork: %v", errDa) + } + + plan, errPl := cmd.Flags().GetString("plan") + if errPl != nil { + return fmt.Errorf("error parsing flag 'time' for backup fork: %v", errPl) + } + + label, errLa := cmd.Flags().GetString("label") + if errLa != nil { + return fmt.Errorf("error parsing flag 'label' for backup fork : %v", errLa) + } + + rtype, errRt := cmd.Flags().GetString("type") + if errRt != nil { + return fmt.Errorf("error parsing flag 'type' for backup fork: %v", errRt) + } + + date, errDa := cmd.Flags().GetString("date") + if errDa != nil { + return fmt.Errorf("error parsing flag 'date' for backup fork: %v", errDa) + } + + time, errTi := cmd.Flags().GetString("time") + if errTi != nil { + return fmt.Errorf("error parsing flag 'time' for backup fork: %v", errTi) + } + + o.ForkReq = &govultr.DatabaseForkReq{ + Label: label, + Region: region, + Plan: plan, + Type: rtype, + Date: date, + Time: time, + } + + db, err := o.fork() + if err != nil { + return fmt.Errorf("error forking database from backup : %v", err) + } + + data := &DBPrinter{DB: db} + o.Base.Printer.Display(data, nil) + + return nil + }, + } + + backupFork.Flags().String("label", "", "label for the new managed database forked from the backup") + if err := backupFork.MarkFlagRequired("label"); err != nil { + fmt.Printf("error marking backup fork 'label' flag required: %v", err) + os.Exit(1) + } + + backupFork.Flags().String("region", "", "region id for the new managed database forked from the backup") + if err := backupFork.MarkFlagRequired("region"); err != nil { + fmt.Printf("error marking backup fork 'region' flag required: %v", err) + os.Exit(1) + } + + backupFork.Flags().String("plan", "", "plan id for the new managed database forked from the backup") + if err := backupFork.MarkFlagRequired("plan"); err != nil { + fmt.Printf("error marking backup fork 'label' flag required: %v", err) + os.Exit(1) + } + + backupFork.Flags().String( + "type", + "", + "restoration type: `pitr` for point-in-time recovery or `basebackup` for latest backup (default)", + ) + backupFork.Flags().String("date", "", "backup date to use for point-in-time recovery") + backupFork.Flags().String("time", "", "backup time to use for point-in-time recovery") + + backup.AddCommand( + backupGet, + backupRestore, + backupFork, + ) + + // Connection Pool + connectionPool := &cobra.Command{ + Use: "connection-pool", + Short: "Commands to handle PostgreSQL database connection pools", + } + + // Connection Pool List + connectionPoolList := &cobra.Command{ + Use: "list ", + Short: "List connection pools within a PostgreSQL managed database", + Args: func(cmd *cobra.Command, args []string) error { + if len(args) != 1 { + return errors.New("please provide a database ID") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + cns, pools, meta, err := o.listConnectionPools() + if err != nil { + return fmt.Errorf("error retrieving connection pool data : %v", err) + } + + data := &ConnectionsPrinter{Connections: cns, ConnectionPools: pools, Meta: meta} + o.Base.Printer.Display(data, nil) + + return nil + }, + } + + // Connection Pool Get + connectionPoolGet := &cobra.Command{ + Use: "get ", + Short: "Get a database connection pool", + Args: func(cmd *cobra.Command, args []string) error { + if len(args) != 2 { + return errors.New("please provide a database ID and a pool name") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + cnp, err := o.getConnectionPool() + if err != nil { + return fmt.Errorf("error retrieving connection pool: %v", err) + } + + data := &ConnectionPoolPrinter{ConnectionPool: cnp} + o.Base.Printer.Display(data, nil) + + return nil + }, + } + + // Connection Pool Create + connectionPoolCreate := &cobra.Command{ + Use: "create ", + Short: "Create a database connection pool", + Args: func(cmd *cobra.Command, args []string) error { + if len(args) != 1 { + return errors.New("please provide a database ID") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + name, errNa := cmd.Flags().GetString("name") + if errNa != nil { + return fmt.Errorf("error parsing flag 'name' for connection pool create : %v", errNa) + } + + database, errDa := cmd.Flags().GetString("database") + if errDa != nil { + return fmt.Errorf("error parsing flag 'database' for connection pool create : %v", errDa) + } + + username, errUs := cmd.Flags().GetString("username") + if errUs != nil { + return fmt.Errorf("error parsing flag 'username' for connection pool create : %v", errUs) + } + + mode, errMo := cmd.Flags().GetString("mode") + if errMo != nil { + return fmt.Errorf("error parsing flag 'mode' for connection pool create : %v", errMo) + } + + size, errSi := cmd.Flags().GetInt("size") + if errSi != nil { + return fmt.Errorf("error parsing flag 'size' for connection pool create : %v", errSi) + } + + o.ConnectionPoolCreateReq = &govultr.DatabaseConnectionPoolCreateReq{ + Name: name, + Database: database, + Username: username, + Mode: mode, + Size: size, + } + + cnp, err := o.createConnectionPool() + if err != nil { + return fmt.Errorf("error creating connection pool: %v", err) + } + + data := &ConnectionPoolPrinter{ConnectionPool: cnp} + o.Base.Printer.Display(data, nil) + + return nil + }, + } + + connectionPoolCreate.Flags().StringP("name", "n", "", "name for the new managed database connection pool") + if err := connectionPoolCreate.MarkFlagRequired("name"); err != nil { + fmt.Printf("error marking connection pool create 'name' flag required: %v", err) + os.Exit(1) + } + + connectionPoolCreate.Flags().StringP("database", "d", "", "database for the new managed database connection pool") + if err := connectionPoolCreate.MarkFlagRequired("database"); err != nil { + fmt.Printf("error marking connection pool create 'database' flag required: %v", err) + os.Exit(1) + } + + connectionPoolCreate.Flags().StringP("username", "u", "", "username for the new managed database connection pool") + if err := connectionPoolCreate.MarkFlagRequired("username"); err != nil { + fmt.Printf("error marking connection pool create 'username' flag required: %v", err) + os.Exit(1) + } + + connectionPoolCreate.Flags().StringP("mode", "m", "", "mode for the new managed database connection pool") + if err := connectionPoolCreate.MarkFlagRequired("mode"); err != nil { + fmt.Printf("error marking connection pool create 'mode' flag required: %v", err) + os.Exit(1) + } + + connectionPoolCreate.Flags().IntP("size", "s", 0, "size for the new managed database connection pool") + if err := connectionPoolCreate.MarkFlagRequired("size"); err != nil { + fmt.Printf("error marking connection pool create 'size' flag required: %v", err) + os.Exit(1) + } + + // Connection Pool Update + connectionPoolUpdate := &cobra.Command{ + Use: "update ", + Short: "Update a database connection pool", + Args: func(cmd *cobra.Command, args []string) error { + if len(args) != 2 { + return errors.New("please provide a database ID pool name") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + database, errDa := cmd.Flags().GetString("database") + if errDa != nil { + return fmt.Errorf("error parsing flag 'database' for connection pool update : %v", errDa) + } + + username, errUs := cmd.Flags().GetString("username") + if errUs != nil { + return fmt.Errorf("error parsing flag 'username' for connection pool update : %v", errUs) + } + + mode, errMo := cmd.Flags().GetString("mode") + if errMo != nil { + return fmt.Errorf("error parsing flag 'mode' for connection pool update : %v", errMo) + } + + size, errSi := cmd.Flags().GetInt("size") + if errSi != nil { + return fmt.Errorf("error parsing flag 'size' for connection pool update : %v", errSi) + } + + o.ConnectionPoolCreateReq = &govultr.DatabaseConnectionPoolCreateReq{} + + if cmd.Flags().Changed("database") { + o.ConnectionPoolCreateReq.Database = database + } + + if cmd.Flags().Changed("username") { + o.ConnectionPoolCreateReq.Username = username + } + + if cmd.Flags().Changed("mode") { + o.ConnectionPoolCreateReq.Mode = mode + } + + if cmd.Flags().Changed("size") { + o.ConnectionPoolCreateReq.Size = size + } + + cnp, err := o.updateConnectionPool() + if err != nil { + return fmt.Errorf("error updating connection pool : %v", err) + } + + data := &ConnectionPoolPrinter{ConnectionPool: cnp} + o.Base.Printer.Display(data, nil) + + return nil + }, + } + + connectionPoolUpdate.Flags().StringP("database", "d", "", "database for the managed database connection pool") + connectionPoolUpdate.Flags().StringP("username", "u", "", "username for the managed database connection pool") + connectionPoolUpdate.Flags().StringP("mode", "m", "", "mode for the managed database connection pool") + connectionPoolUpdate.Flags().IntP("size", "s", 0, "size for the managed database connection pool") + + connectionPoolUpdate.MarkFlagsOneRequired( + "database", + "username", + "mode", + "size", + ) + + // Connection Pool Delete + connectionPoolDelete := &cobra.Command{ + Use: "delete ", + Short: "Delete a database connection pool", + Args: func(cmd *cobra.Command, args []string) error { + if len(args) != 2 { + return errors.New("please provide a database ID and a pool name") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + if err := o.delConnectionPool(); err != nil { + return fmt.Errorf("error deleting connection pool : %v", err) + } + + o.Base.Printer.Display(printer.Info("Connection pool has been deleted"), nil) + + return nil + }, + } + + connectionPool.AddCommand( + connectionPoolList, + connectionPoolGet, + connectionPoolCreate, + connectionPoolUpdate, + connectionPoolDelete, + ) + + // Advanced Option + advancedOption := &cobra.Command{ + Use: "advanced-option", + Short: "Commands to handle PostgreSQL database advanced options", + } + + // Advanced Option List + advancedOptionList := &cobra.Command{ + Use: "list ", + Short: "List all available and configured advanced options for a PostgreSQL database", + Args: func(cmd *cobra.Command, args []string) error { + if len(args) != 1 { + return errors.New("please provide a database ID") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + cur, avail, err := o.listAdvancedOptions() + if err != nil { + return fmt.Errorf("error retrieving database options : %v", err) + } + + data := &AdvancedOptionsPrinter{Configured: cur, Available: avail} + o.Base.Printer.Display(data, nil) + + return nil + }, + } + + // Advanced Option Update + advancedOptionUpdate := &cobra.Command{ + Use: "update ", + Short: "Update advanced options for a PostgreSQL managed database", + Args: func(cmd *cobra.Command, args []string) error { + if len(args) != 1 { + return errors.New("please provide a database ID") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + autovacuumAnalyzeScaleFactor, errAu := cmd.Flags().GetFloat32("autovacuum-analyze-scale-factor") + if errAu != nil { + return fmt.Errorf("error parsing flag 'autovacuum-analyze-scale-factor' for advanced options update : %v", errAu) + } + + autovacuumAnalyzeThreshold, errAt := cmd.Flags().GetInt("autovacuum-analyze-threshold") + if errAt != nil { + return fmt.Errorf("error parsing flag 'autovacuum-analyze-threshold' for advanced options update : %v", errAt) + } + + autovacuumFreezeMaxAge, errAo := cmd.Flags().GetInt("autovacuum-freeze-max-age") + if errAo != nil { + return fmt.Errorf("error parsing flag 'autovacuum-freeze-max-age' for advanced options update : %v", errAo) + } + + autovacuumMaxWorkers, errAv := cmd.Flags().GetInt("autovacuum-max-workers") + if errAv != nil { + return fmt.Errorf("error parsing flag 'autovacuum-max-workers' for advanced options update : %v", errAv) + } + + autovacuumNaptime, errAa := cmd.Flags().GetInt("autovacuum-naptime") + if errAa != nil { + return fmt.Errorf("error parsing flag 'autovacuum-naptime' for advanced options update : %v", errAa) + } + + autovacuumVacuumCostDelay, errAc := cmd.Flags().GetInt("autovacuum-vacuum-cost-delay") + if errAc != nil { + return fmt.Errorf("error parsing flag 'autovacuum-vacuum-cost-delay' for advanced options update : %v", errAc) + } + + autovacuumVacuumCostLimit, errAm := cmd.Flags().GetInt("autovacuum-vacuum-cost-limit") + if errAm != nil { + return fmt.Errorf("error parsing flag 'autovacuum-vacuum-cost-limit' for advanced options update : %v", errAm) + } + + autovacuumVacuumScaleFactor, errAb := cmd.Flags().GetFloat32("autovacuum-vacuum-scale-factor") + if errAb != nil { + return fmt.Errorf("error parsing flag 'autovacuum-vacuum-scale-factor' for advanced options update : %v", errAb) + } + + autovacuumVacuumThreshold, errAz := cmd.Flags().GetInt("autovacuum-vacuum-threshold") + if errAz != nil { + return fmt.Errorf("error parsing flag 'autovacuum-vacuum-threshold' for advanced options update : %v", errAz) + } + + bgwriterDelay, errBg := cmd.Flags().GetInt("bgwriter-delay") + if errBg != nil { + return fmt.Errorf("error parsing flag 'bgwriter-delay' for advanced options update : %v", errBg) + } + + bgwriterFlushAfter, errBw := cmd.Flags().GetInt("bgwriter-flush-after") + if errBw != nil { + return fmt.Errorf("error parsing flag 'bgwriter-flush-after' for advanced options update : %v", errBw) + } + + bgwriterLruMaxpages, errBr := cmd.Flags().GetInt("bgwriter-lru-maxpages") + if errBr != nil { + return fmt.Errorf("error parsing flag 'bgwriter-lru-maxpages' for advanced options update : %v", errBr) + } + + bgwriterLruMultiplier, errBi := cmd.Flags().GetFloat32("bgwriter-lru-multiplier") + if errBi != nil { + return fmt.Errorf("error parsing flag 'bgwriter-lru-multiplier' for advanced options update : %v", errBi) + } + + deadlockTimeout, errDe := cmd.Flags().GetInt("deadlock-timeout") + if errDe != nil { + return fmt.Errorf("error parsing flag 'deadlock-timeout' for advanced options update : %v", errDe) + } + + defaultToastCompression, errDf := cmd.Flags().GetString("default-toast-compression") + if errDf != nil { + return fmt.Errorf("error parsing flag 'default-toast-compression' for advanced options update : %v", errDf) + } + + idleInTransactionSessionTimeout, errIl := cmd.Flags().GetInt("idle-in-transaction-session-timeout") + if errIl != nil { + return fmt.Errorf("error parsing flag 'idle-in-transaction-session-timeout' for advanced options update : %v", errIl) + } + + jit, errJi := cmd.Flags().GetBool("jit") + if errJi != nil { + return fmt.Errorf("error parsing flag 'jit' for advanced options update : %v", errJi) + } + + logAutovacuumMinDuration, errLo := cmd.Flags().GetInt("log-autovacuum-min-duration") + if errLo != nil { + return fmt.Errorf("error parsing flag 'log-autovacuum-min-duration' for advanced options update : %v", errLo) + } + + logErrorVerbosity, errLg := cmd.Flags().GetString("log-error-verbosity") + if errLg != nil { + return fmt.Errorf("error parsing flag 'log-error-verbosity' for advanced options update : %v", errLg) + } + + logLinePrefix, errLl := cmd.Flags().GetString("log-line-prefix") + if errLl != nil { + return fmt.Errorf("error parsing flag 'log-line-prefix' for advanced options update : %v", errLl) + } + + logMinDurationStatement, errLm := cmd.Flags().GetInt("log-min-duration-statement") + if errLm != nil { + return fmt.Errorf("error parsing flag 'log-min-duration-statement' for advanced options update : %v", errLm) + } + + maxFilesPerProcess, errMa := cmd.Flags().GetInt("max-files-per-process") + if errMa != nil { + return fmt.Errorf("error parsing flag 'max-files-per-process' for advanced options update : %v", errMa) + } + + maxLocksPerTransaction, errMx := cmd.Flags().GetInt("max-locks-per-transaction") + if errMx != nil { + return fmt.Errorf("error parsing flag 'max-locks-per-transaction' for advanced options update : %v", errMx) + } + + maxLogicalReplicationWorkers, errMl := cmd.Flags().GetInt("max-logical-replication-workers") + if errMl != nil { + return fmt.Errorf("error parsing flag 'max-logical-replication-workers' for advanced options update : %v", errMl) + } + + maxParallelWorkers, errMo := cmd.Flags().GetInt("max-parallel-workers") + if errMo != nil { + return fmt.Errorf("error parsing flag 'max-parallel-workers' for advanced options update : %v", errMo) + } + + maxParallelWorkersPerGather, errMp := cmd.Flags().GetInt("max-parallel-workers-per-gather") + if errMp != nil { + return fmt.Errorf("error parsing flag 'max-parallel-workers-per-gather' for advanced options update : %v", errMp) + } + + maxPredLocksPerTransaction, errMr := cmd.Flags().GetInt("max-pred-locks-per-transaction") + if errMr != nil { + return fmt.Errorf("error parsing flag 'max-pred-locks-per-transaction' for advanced options update : %v", errMr) + } + + maxPreparedTransactions, errMe := cmd.Flags().GetInt("max-prepared-transactions") + if errMe != nil { + return fmt.Errorf("error parsing flag 'max-prepared-transactions' for advanced options update : %v", errMe) + } + + maxReplicationSlots, errMi := cmd.Flags().GetInt("max-replication-slots") + if errMi != nil { + return fmt.Errorf("error parsing flag 'max-replication-slots' for advanced options update : %v", errMi) + } + + maxStackDepth, errMs := cmd.Flags().GetInt("max-stack-depth") + if errMs != nil { + return fmt.Errorf("error parsing flag 'max-stack-depth' for advanced options update : %v", errMs) + } + + maxStandbyArchiveDelay, errMv := cmd.Flags().GetInt("max-standby-archive-delay") + if errMv != nil { + return fmt.Errorf("error parsing flag 'max-standby-archive-delay' for advanced options update : %v", errMv) + } + + maxStandbyStreamingDelay, errMy := cmd.Flags().GetInt("max-standby-streaming-delay") + if errMy != nil { + return fmt.Errorf("error parsing flag 'max-standby-streaming-delay' for advanced options update : %v", errMy) + } + + maxWalSenders, errMd := cmd.Flags().GetInt("max-wal-senders") + if errMd != nil { + return fmt.Errorf("error parsing flag 'max-wal-senders' for advanced options update : %v", errMd) + } + + maxWorkerProcesses, errMs := cmd.Flags().GetInt("max-worker-processes") + if errMs != nil { + return fmt.Errorf("error parsing flag 'max-worker-processes' for advanced options update : %v", errMs) + } + + pgPartmanBGWInterval, errPg := cmd.Flags().GetInt("pg-partman-bgw-interval") + if errPg != nil { + return fmt.Errorf("error parsing flag 'pg-partman-bgw-interval' for advanced options update : %v", errPg) + } + + pgPartmanBGWRole, errPp := cmd.Flags().GetString("pg-partman-bgw-role") + if errPp != nil { + return fmt.Errorf("error parsing flag 'pg-partman-bgw-role' for advanced options update : %v", errPp) + } + + pgStatStatementsTrack, errPs := cmd.Flags().GetString("pg-stat-statements-track") + if errPs != nil { + return fmt.Errorf("error parsing flag 'pg-stat-statements-track' for advanced options update : %v", errPs) + } + + tempFileLimit, errTe := cmd.Flags().GetInt("temp-file-limit") + if errTe != nil { + return fmt.Errorf("error parsing flag 'temp-file-limit' for advanced options update : %v", errTe) + } + + trackActivityQuerySize, errTr := cmd.Flags().GetInt("track-activity-query-size") + if errTr != nil { + return fmt.Errorf("error parsing flag 'track-activity-query-size' for advanced options update : %v", errTr) + } + + trackCommitTimestamp, errTa := cmd.Flags().GetString("track-commit-timestamp") + if errTa != nil { + return fmt.Errorf("error parsing flag 'track-commit-timestamp' for advanced options update : %v", errTa) + } + + trackFunctions, errTc := cmd.Flags().GetString("track-functions") + if errTc != nil { + return fmt.Errorf("error parsing flag 'track-functions' for advanced options update : %v", errTc) + } + + trackIOTiming, errTi := cmd.Flags().GetString("track-io-timing") + if errTi != nil { + return fmt.Errorf("error parsing flag 'track-io-timing' for advanced options update : %v", errTi) + } + + walSenderTimeout, errWa := cmd.Flags().GetInt("wal-sender-timeout") + if errWa != nil { + return fmt.Errorf("error parsing flag 'wal-sender-timeout' for advanced options update : %v", errWa) + } + + walWriterDelay, errWl := cmd.Flags().GetInt("wal-writer-delay") + if errWl != nil { + return fmt.Errorf("error parsing flag 'wal-writer-delay' for advanced options update : %v", errWl) + } + + o.AdvancedOptionsReq = &govultr.DatabaseAdvancedOptions{} + + if cmd.Flags().Changed("autovacuum-analyze-scale-factor") { + o.AdvancedOptionsReq.AutovacuumAnalyzeScaleFactor = autovacuumAnalyzeScaleFactor + } + + if cmd.Flags().Changed("autovacuum-analyze-threshold") { + o.AdvancedOptionsReq.AutovacuumAnalyzeThreshold = autovacuumAnalyzeThreshold + } + + if cmd.Flags().Changed("autovacuum-freeze-max-age") { + o.AdvancedOptionsReq.AutovacuumFreezeMaxAge = autovacuumFreezeMaxAge + } + + if cmd.Flags().Changed("autovacuum-max-workers") { + o.AdvancedOptionsReq.AutovacuumMaxWorkers = autovacuumMaxWorkers + } + + if cmd.Flags().Changed("autovacuum-naptime") { + o.AdvancedOptionsReq.AutovacuumNaptime = autovacuumNaptime + } + + if cmd.Flags().Changed("autovacuum-vacuum-cost-delay") { + o.AdvancedOptionsReq.AutovacuumVacuumCostDelay = autovacuumVacuumCostDelay + } + + if cmd.Flags().Changed("autovacuum-vacuum-cost-limit") { + o.AdvancedOptionsReq.AutovacuumVacuumCostLimit = autovacuumVacuumCostLimit + } + + if cmd.Flags().Changed("autovacuum-vacuum-scale-factor") { + o.AdvancedOptionsReq.AutovacuumVacuumScaleFactor = autovacuumVacuumScaleFactor + } + + if cmd.Flags().Changed("autovacuum-vacuum-threshold") { + o.AdvancedOptionsReq.AutovacuumVacuumThreshold = autovacuumVacuumThreshold + } + + if cmd.Flags().Changed("bgwriter-delay") { + o.AdvancedOptionsReq.BGWRITERDelay = bgwriterDelay + } + + if cmd.Flags().Changed("bgwriter-flush-after") { + o.AdvancedOptionsReq.BGWRITERFlushAFter = bgwriterFlushAfter + } + + if cmd.Flags().Changed("bgwriter-lru-maxpages") { + o.AdvancedOptionsReq.BGWRITERLRUMaxPages = bgwriterLruMaxpages + } + + if cmd.Flags().Changed("bgwriter-lru-multiplier") { + o.AdvancedOptionsReq.BGWRITERLRUMultiplier = bgwriterLruMultiplier + } + + if cmd.Flags().Changed("deadlock-timeout") { + o.AdvancedOptionsReq.DeadlockTimeout = deadlockTimeout + } + + if cmd.Flags().Changed("default-toast-compression") { + o.AdvancedOptionsReq.DefaultToastCompression = defaultToastCompression + } + + if cmd.Flags().Changed("idle-in-transaction-session-timeout") { + o.AdvancedOptionsReq.IdleInTransactionSessionTimeout = idleInTransactionSessionTimeout + } + + if cmd.Flags().Changed("jit") { + o.AdvancedOptionsReq.Jit = nil + } + + if cmd.Flags().Changed("log-autovacuum-min-duration") { + o.AdvancedOptionsReq.LogAutovacuumMinDuration = logAutovacuumMinDuration + } + + if cmd.Flags().Changed("log-error-verbosity") { + o.AdvancedOptionsReq.LogErrorVerbosity = logErrorVerbosity + } + + if cmd.Flags().Changed("log-line-prefix") { + o.AdvancedOptionsReq.LogLinePrefix = logLinePrefix + } + + if cmd.Flags().Changed("log-min-duration-statement") { + o.AdvancedOptionsReq.LogMinDurationStatement = logMinDurationStatement + } + + if cmd.Flags().Changed("max-files-per-process") { + o.AdvancedOptionsReq.MaxFilesPerProcess = maxFilesPerProcess + } + + if cmd.Flags().Changed("max-locks-per-transaction") { + o.AdvancedOptionsReq.MaxLocksPerTransaction = maxLocksPerTransaction + } + + if cmd.Flags().Changed("max-logical-replication-workers") { + o.AdvancedOptionsReq.MaxLogicalReplicationWorkers = maxLogicalReplicationWorkers + } + + if cmd.Flags().Changed("max-parallel-workers") { + o.AdvancedOptionsReq.MaxParallelWorkers = maxParallelWorkers + } + + if cmd.Flags().Changed("max-parallel-workers-per-gather") { + o.AdvancedOptionsReq.MaxParallelWorkersPerGather = maxParallelWorkersPerGather + } + + if cmd.Flags().Changed("max-pred-locks-per-transaction") { + o.AdvancedOptionsReq.MaxPredLocksPerTransaction = maxPredLocksPerTransaction + } + + if cmd.Flags().Changed("max-prepared-transactions") { + o.AdvancedOptionsReq.MaxPreparedTransactions = maxPreparedTransactions + } + + if cmd.Flags().Changed("max-replication-slots") { + o.AdvancedOptionsReq.MaxReplicationSlots = maxReplicationSlots + } + + if cmd.Flags().Changed("max-stack-depth") { + o.AdvancedOptionsReq.MaxStackDepth = maxStackDepth + } + + if cmd.Flags().Changed("max-standby-archive-delay") { + o.AdvancedOptionsReq.MaxStandbyArchiveDelay = maxStandbyArchiveDelay + } + + if cmd.Flags().Changed("max-standby-streaming-delay") { + o.AdvancedOptionsReq.MaxStandbyStreamingDelay = maxStandbyStreamingDelay + } + + if cmd.Flags().Changed("max-wal-senders") { + o.AdvancedOptionsReq.MaxWalSenders = maxWalSenders + } + + if cmd.Flags().Changed("max-worker-processes") { + o.AdvancedOptionsReq.MaxWorkerProcesses = maxWorkerProcesses + } + + if cmd.Flags().Changed("pg-partman-bgw-interval") { + o.AdvancedOptionsReq.PGPartmanBGWInterval = pgPartmanBGWInterval + } + + if cmd.Flags().Changed("pg-partman-bgw-role") { + o.AdvancedOptionsReq.PGPartmanBGWRole = pgPartmanBGWRole + } + + if cmd.Flags().Changed("pg-stat-statements-track") { + o.AdvancedOptionsReq.PGStateStatementsTrack = pgStatStatementsTrack + } + + if cmd.Flags().Changed("temp-file-limit") { + o.AdvancedOptionsReq.TempFileLimit = tempFileLimit + } + + if cmd.Flags().Changed("track-activity-query-size") { + o.AdvancedOptionsReq.TrackActivityQuerySize = trackActivityQuerySize + } + + if cmd.Flags().Changed("track-commit-timestamp") { + o.AdvancedOptionsReq.TrackCommitTimestamp = trackCommitTimestamp + } + + if cmd.Flags().Changed("track-functions") { + o.AdvancedOptionsReq.TrackFunctions = trackFunctions + } + + if cmd.Flags().Changed("track-io-timing") { + o.AdvancedOptionsReq.TrackIOTiming = trackIOTiming + } + + if cmd.Flags().Changed("wal-sender-timeout") { + o.AdvancedOptionsReq.WALSenderTImeout = walSenderTimeout + } + + if cmd.Flags().Changed("wal-writer-delay") { + o.AdvancedOptionsReq.WALWriterDelay = walWriterDelay + } + + if cmd.Flags().Changed("jit") { + o.AdvancedOptionsReq.Jit = &jit + } + + cur, avail, err := o.updateAdvancedOptions() + if err != nil { + return fmt.Errorf("error updating database advanced options : %v", err) + } + + data := &AdvancedOptionsPrinter{Configured: cur, Available: avail} + o.Base.Printer.Display(data, nil) + + return nil + }, + } + + advancedOptionUpdate.Flags().Float32( + "autovacuum-analyze-scale-factor", + 0, + "set the managed postgresql configuration value for autovacuum_analyze_scale_factor", + ) + advancedOptionUpdate.Flags().Int( + "autovacuum-analyze-threshold", + 0, + "set the managed postgresql configuration value for autovacuum_analyze_threshold", + ) + advancedOptionUpdate.Flags().Int( + "autovacuum-freeze-max-age", + 0, + "set the managed postgresql configuration value for autovacuum_freeze_max_age", + ) + advancedOptionUpdate.Flags().Int( + "autovacuum-max-workers", + 0, + "set the managed postgresql configuration value for autovacuum_max_workers", + ) + advancedOptionUpdate.Flags().Int( + "autovacuum-naptime", + 0, + "set the managed postgresql configuration value for autovacuum_naptime", + ) + advancedOptionUpdate.Flags().Int( + "autovacuum-vacuum-cost-delay", + 0, + "set the managed postgresql configuration value for autovacuum_vacuum_cost_delay", + ) + advancedOptionUpdate.Flags().Int( + "autovacuum-vacuum-cost-limit", + 0, + "set the managed postgresql configuration value for autovacuum_vacuum_cost_limit", + ) + advancedOptionUpdate.Flags().Float32( + "autovacuum-vacuum-scale-factor", + 0, + "set the managed postgresql configuration value for autovacuum_vacuum_scale_factor", + ) + advancedOptionUpdate.Flags().Int( + "autovacuum-vacuum-threshold", + 0, + "set the managed postgresql configuration value for autovacuum_vacuum_threshold", + ) + advancedOptionUpdate.Flags().Int( + "bgwriter-delay", + 0, + "set the managed postgresql configuration value for bgwriter_delay", + ) + advancedOptionUpdate.Flags().Int( + "bgwriter-flush-after", + 0, + "set the managed postgresql configuration value for bgwriter_flush_after", + ) + advancedOptionUpdate.Flags().Int( + "bgwriter-lru-maxpages", + 0, + "set the managed postgresql configuration value for bgwriter_lru_maxpages", + ) + advancedOptionUpdate.Flags().Float32( + "bgwriter-lru-multiplier", + 0, + "set the managed postgresql configuration value for bgwriter_lru_multiplier", + ) + advancedOptionUpdate.Flags().Int( + "deadlock-timeout", + 0, + "set the managed postgresql configuration value for deadlock_timeout", + ) + advancedOptionUpdate.Flags().String( + "default-toast-compression", + "", + "set the managed postgresql configuration value for default_toast_compression", + ) + advancedOptionUpdate.Flags().Int( + "idle-in-transaction-session-timeout", + 0, + "set the managed postgresql configuration value for idle_in_transaction_session_timeout", + ) + advancedOptionUpdate.Flags().Bool( + "jit", + false, + "set the managed postgresql configuration value for jit", + ) + advancedOptionUpdate.Flags().Int( + "log-autovacuum-min-duration", + 0, + "set the managed postgresql configuration value for log_autovacuum_min_duration", + ) + advancedOptionUpdate.Flags().String( + "log-error-verbosity", + "", + "set the managed postgresql configuration value for log_error_verbosity", + ) + advancedOptionUpdate.Flags().String( + "log-line-prefix", + "", + "set the managed postgresql configuration value for log_line_prefix", + ) + advancedOptionUpdate.Flags().Int( + "log-min-duration-statement", + 0, + "set the managed postgresql configuration value for log_min_duration_statement", + ) + advancedOptionUpdate.Flags().Int( + "max-files-per-process", + 0, + "set the managed postgresql configuration value for max_files_per_process", + ) + advancedOptionUpdate.Flags().Int( + "max-locks-per-transaction", + 0, + "set the managed postgresql configuration value for max_locks_per_transaction", + ) + advancedOptionUpdate.Flags().Int( + "max-logical-replication-workers", + 0, + "set the managed postgresql configuration value for max_logical_replication_workers", + ) + advancedOptionUpdate.Flags().Int( + "max-parallel-workers", + 0, + "set the managed postgresql configuration value for max_parallel_workers", + ) + advancedOptionUpdate.Flags().Int( + "max-parallel-workers-per-gather", + 0, + "set the managed postgresql configuration value for max_parallel_workers_per_gather", + ) + advancedOptionUpdate.Flags().Int( + "max-pred-locks-per-transaction", + 0, + "set the managed postgresql configuration value for max_pred_locks_per_transaction", + ) + advancedOptionUpdate.Flags().Int( + "max-prepared-transactions", + 0, + "set the managed postgresql configuration value for max_prepared_transactions", + ) + advancedOptionUpdate.Flags().Int( + "max-replication-slots", + 0, + "set the managed postgresql configuration value for max_replication_slots", + ) + advancedOptionUpdate.Flags().Int( + "max-stack-depth", + 0, + "set the managed postgresql configuration value for max_stack_depth", + ) + advancedOptionUpdate.Flags().Int( + "max-standby-archive-delay", + 0, + "set the managed postgresql configuration value for max_standby_archive_delay", + ) + advancedOptionUpdate.Flags().Int( + "max-standby-streaming-delay", + 0, + "set the managed postgresql configuration value for max_standby_streaming_delay", + ) + advancedOptionUpdate.Flags().Int( + "max-wal-senders", + 0, + "set the managed postgresql configuration value for max_wal_senders", + ) + advancedOptionUpdate.Flags().Int( + "max-worker-processes", + 0, + "set the managed postgresql configuration value for max_worker_processes", + ) + advancedOptionUpdate.Flags().Int( + "pg-partman-bgw-interval", + 0, + "set the managed postgresql configuration value for pg_partman_bgw.interval", + ) + advancedOptionUpdate.Flags().String( + "pg-partman-bgw-role", + "", + "set the managed postgresql configuration value for pg_partman_bgw.role", + ) + advancedOptionUpdate.Flags().String( + "pg-stat-statements-track", + "", + "set the managed postgresql configuration value for pg_stat_statements.track", + ) + advancedOptionUpdate.Flags().Int( + "temp-file-limit", + 0, + "set the managed postgresql configuration value for temp_file_limit", + ) + advancedOptionUpdate.Flags().Int( + "track-activity-query-size", + 0, + "set the managed postgresql configuration value for track_activity_query_size", + ) + advancedOptionUpdate.Flags().String( + "track-commit-timestamp", + "", + "set the managed postgresql configuration value for track_commit_timestamp", + ) + advancedOptionUpdate.Flags().String( + "track-functions", + "", + "set the managed postgresql configuration value for track_functions", + ) + advancedOptionUpdate.Flags().String( + "track-io-timing", + "", + "set the managed postgresql configuration value for track_io_timing", + ) + advancedOptionUpdate.Flags().Int( + "wal-sender-timeout", + 0, + "set the managed postgresql configuration value for wal_sender_timeout", + ) + advancedOptionUpdate.Flags().Int( + "wal-writer-delay", + 0, + "set the managed postgresql configuration value for wal_writer_delay", + ) + + advancedOption.AddCommand( + advancedOptionList, + advancedOptionUpdate, + ) + + // Version + version := &cobra.Command{ + Use: "version", + Short: "Commands to handle database version upgrades", + } + + // Version List + versionList := &cobra.Command{ + Use: "list ", + Short: "List all version upgrades for a database", + Args: func(cmd *cobra.Command, args []string) error { + if len(args) != 1 { + return errors.New("please provide a database ID") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + vs, err := o.listVersions() + if err != nil { + return fmt.Errorf("error retrieving database versions : %v", err) + } + + data := &VersionsPrinter{Versions: vs} + o.Base.Printer.Display(data, nil) + + return nil + }, + } + + // Version Upgrade + versionUpgrade := &cobra.Command{ + Use: "upgrade ", + Short: "Start a version upgrade on a database", + Args: func(cmd *cobra.Command, args []string) error { + if len(args) != 1 { + return errors.New("please provide a database ID") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + version, errVe := cmd.Flags().GetString("version") + if errVe != nil { + return fmt.Errorf("error parsing flag 'version' for database upgrade : %v", errVe) + } + + o.UpgradeReq = &govultr.DatabaseVersionUpgradeReq{ + Version: version, + } + + msg, err := o.upgradeVersion() + if err != nil { + return fmt.Errorf("error starting database version upgrade : %v", err) + } + + o.Base.Printer.Display(printer.Info(msg), nil) + + return nil + }, + } + + versionUpgrade.Flags().StringP("version", "v", "", "version of the manaaged database to upgrade to") + if err := versionUpgrade.MarkFlagRequired("version"); err != nil { + fmt.Printf("error marking version upgrade 'version' flag required: %v", err) + os.Exit(1) + } + + version.AddCommand( + versionList, + versionUpgrade, + ) + + cmd.AddCommand( + list, + get, + create, + update, + del, + user, + db, + usage, + maintenance, + plan, + alert, + migration, + readReplica, + backup, + connectionPool, + advancedOption, + version, + ) + + return cmd +} + +type options struct { + Base *cli.Base + CreateReq *govultr.DatabaseCreateReq + UpdateReq *govultr.DatabaseUpdateReq + UserCreateReq *govultr.DatabaseUserCreateReq + UserUpdateReq *govultr.DatabaseUserUpdateReq + UserUpdateACLReq *govultr.DatabaseUserACLReq + DBCreateReq *govultr.DatabaseDBCreateReq + AlertsReq *govultr.DatabaseListAlertsReq + MigrationReq *govultr.DatabaseMigrationStartReq + ReadReplicaCreateReq *govultr.DatabaseAddReplicaReq + BackupReq *govultr.DatabaseBackupRestoreReq + ForkReq *govultr.DatabaseForkReq + ConnectionPoolCreateReq *govultr.DatabaseConnectionPoolCreateReq + ConnectionPoolUpdateReq *govultr.DatabaseConnectionPoolUpdateReq + AdvancedOptionsReq *govultr.DatabaseAdvancedOptions + UpgradeReq *govultr.DatabaseVersionUpgradeReq +} + +func (o *options) list() ([]govultr.Database, *govultr.Meta, error) { + dbs, meta, _, err := o.Base.Client.Database.List(o.Base.Context, nil) + return dbs, meta, err +} + +func (o *options) get() (*govultr.Database, error) { + db, _, err := o.Base.Client.Database.Get(o.Base.Context, o.Base.Args[0]) + return db, err +} + +func (o *options) create() (*govultr.Database, error) { + db, _, err := o.Base.Client.Database.Create(o.Base.Context, o.CreateReq) + return db, err +} + +func (o *options) update() (*govultr.Database, error) { + db, _, err := o.Base.Client.Database.Update(o.Base.Context, o.Base.Args[0], o.UpdateReq) + return db, err +} + +func (o *options) del() error { + return o.Base.Client.Database.Delete(o.Base.Context, o.Base.Args[0]) +} + +func (o *options) listPlans() ([]govultr.DatabasePlan, *govultr.Meta, error) { + plans, meta, _, err := o.Base.Client.Database.ListPlans(o.Base.Context, nil) + return plans, meta, err +} + +func (o *options) listUsers() ([]govultr.DatabaseUser, *govultr.Meta, error) { + users, meta, _, err := o.Base.Client.Database.ListUsers(o.Base.Context, o.Base.Args[0]) + return users, meta, err +} + +func (o *options) getUser() (*govultr.DatabaseUser, error) { + user, _, err := o.Base.Client.Database.GetUser(o.Base.Context, o.Base.Args[0], o.Base.Args[1]) + return user, err +} + +func (o *options) createUser() (*govultr.DatabaseUser, error) { + user, _, err := o.Base.Client.Database.CreateUser(o.Base.Context, o.Base.Args[0], o.UserCreateReq) + return user, err +} + +func (o *options) updateUser() (*govultr.DatabaseUser, error) { + user, _, err := o.Base.Client.Database.UpdateUser(o.Base.Context, o.Base.Args[0], o.Base.Args[1], o.UserUpdateReq) + return user, err +} + +func (o *options) delUser() error { + return o.Base.Client.Database.DeleteUser(o.Base.Context, o.Base.Args[0], o.Base.Args[1]) +} + +func (o *options) updateUserACL() (*govultr.DatabaseUser, error) { + user, _, err := o.Base.Client.Database.UpdateUserACL(o.Base.Context, o.Base.Args[0], o.Base.Args[1], o.UserUpdateACLReq) + return user, err +} + +func (o *options) listDBs() ([]govultr.DatabaseDB, *govultr.Meta, error) { + dbs, meta, _, err := o.Base.Client.Database.ListDBs(o.Base.Context, o.Base.Args[0]) + return dbs, meta, err +} + +func (o *options) createDB() (*govultr.DatabaseDB, error) { + db, _, err := o.Base.Client.Database.CreateDB(o.Base.Context, o.Base.Args[0], o.DBCreateReq) + return db, err +} + +func (o *options) delDB() error { + return o.Base.Client.Database.DeleteDB(o.Base.Context, o.Base.Args[0], o.Base.Args[1]) +} + +func (o *options) getUsage() (*govultr.DatabaseUsage, error) { + usage, _, err := o.Base.Client.Database.GetUsage(o.Base.Context, o.Base.Args[0]) + return usage, err +} + +func (o *options) listMaintUpdates() ([]string, error) { + updates, _, err := o.Base.Client.Database.ListMaintenanceUpdates(o.Base.Context, o.Base.Args[0]) + return updates, err +} + +func (o *options) startMaintUpdate() (string, error) { + updates, _, err := o.Base.Client.Database.StartMaintenance(o.Base.Context, o.Base.Args[0]) + return updates, err +} + +func (o *options) listAlerts() ([]govultr.DatabaseAlert, error) { + alerts, _, err := o.Base.Client.Database.ListServiceAlerts(o.Base.Context, o.Base.Args[0], o.AlertsReq) + return alerts, err +} + +func (o *options) getMigrationStatus() (*govultr.DatabaseMigration, error) { + status, _, err := o.Base.Client.Database.GetMigrationStatus(o.Base.Context, o.Base.Args[0]) + return status, err +} + +func (o *options) startMigration() (*govultr.DatabaseMigration, error) { + status, _, err := o.Base.Client.Database.StartMigration(o.Base.Context, o.Base.Args[0], o.MigrationReq) + return status, err +} + +func (o *options) detachMigration() error { + return o.Base.Client.Database.DetachMigration(o.Base.Context, o.Base.Args[0]) +} + +func (o *options) createReadReplica() (*govultr.Database, error) { + db, _, err := o.Base.Client.Database.AddReadOnlyReplica(o.Base.Context, o.Base.Args[0], o.ReadReplicaCreateReq) + return db, err +} + +func (o *options) promoteReadReplica() error { + return o.Base.Client.Database.PromoteReadReplica(o.Base.Context, o.Base.Args[0]) +} + +func (o *options) getBackup() (*govultr.DatabaseBackups, error) { + backup, _, err := o.Base.Client.Database.GetBackupInformation(o.Base.Context, o.Base.Args[0]) + return backup, err +} + +func (o *options) restoreBackup() (*govultr.Database, error) { + db, _, err := o.Base.Client.Database.RestoreFromBackup(o.Base.Context, o.Base.Args[0], o.BackupReq) + return db, err +} + +func (o *options) fork() (*govultr.Database, error) { + db, _, err := o.Base.Client.Database.Fork(o.Base.Context, o.Base.Args[0], o.ForkReq) + return db, err +} + +func (o *options) listConnectionPools() (*govultr.DatabaseConnections, []govultr.DatabaseConnectionPool, *govultr.Meta, error) { + cons, pool, meta, _, err := o.Base.Client.Database.ListConnectionPools(o.Base.Context, o.Base.Args[0]) + return cons, pool, meta, err +} + +func (o *options) getConnectionPool() (*govultr.DatabaseConnectionPool, error) { + pool, _, err := o.Base.Client.Database.GetConnectionPool(o.Base.Context, o.Base.Args[0], o.Base.Args[1]) + return pool, err +} + +func (o *options) createConnectionPool() (*govultr.DatabaseConnectionPool, error) { + pool, _, err := o.Base.Client.Database.CreateConnectionPool(o.Base.Context, o.Base.Args[0], o.ConnectionPoolCreateReq) + return pool, err +} + +func (o *options) updateConnectionPool() (*govultr.DatabaseConnectionPool, error) { + pool, _, err := o.Base.Client.Database.UpdateConnectionPool(o.Base.Context, o.Base.Args[0], o.Base.Args[1], o.ConnectionPoolUpdateReq) + return pool, err +} + +func (o *options) delConnectionPool() error { + return o.Base.Client.Database.DeleteConnectionPool(o.Base.Context, o.Base.Args[0], o.Base.Args[1]) +} + +func (o *options) listAdvancedOptions() (*govultr.DatabaseAdvancedOptions, []govultr.AvailableOption, error) { + cur, avail, _, err := o.Base.Client.Database.ListAdvancedOptions(o.Base.Context, o.Base.Args[0]) + return cur, avail, err +} + +func (o *options) updateAdvancedOptions() (*govultr.DatabaseAdvancedOptions, []govultr.AvailableOption, error) { + cur, avail, _, err := o.Base.Client.Database.UpdateAdvancedOptions(o.Base.Context, o.Base.Args[0], o.AdvancedOptionsReq) + return cur, avail, err +} + +func (o *options) listVersions() ([]string, error) { + vers, _, err := o.Base.Client.Database.ListAvailableVersions(o.Base.Context, o.Base.Args[0]) + return vers, err +} + +func (o *options) upgradeVersion() (string, error) { + up, _, err := o.Base.Client.Database.StartVersionUpgrade(o.Base.Context, o.Base.Args[0], o.UpgradeReq) + return up, err +} diff --git a/cmd/database/printer.go b/cmd/database/printer.go new file mode 100644 index 00000000..ccd9f1f6 --- /dev/null +++ b/cmd/database/printer.go @@ -0,0 +1,1318 @@ +package database + +import ( + "reflect" + "strconv" + + "github.com/vultr/govultr/v3" + "github.com/vultr/vultr-cli/v3/cmd/printer" + "github.com/vultr/vultr-cli/v3/cmd/utils" +) + +// DBsPrinter ... +type DBsPrinter struct { + DBs []govultr.Database `json:"databases"` + Meta *govultr.Meta `json:"meta"` +} + +// JSON ... +func (d *DBsPrinter) JSON() []byte { + return printer.MarshalObject(d, "json") +} + +// YAML ... +func (d *DBsPrinter) YAML() []byte { + return printer.MarshalObject(d, "yaml") +} + +// Columns ... +func (d *DBsPrinter) Columns() [][]string { + return nil +} + +// Data ... +func (d *DBsPrinter) Data() [][]string { //nolint:funlen,gocyclo + if len(d.DBs) == 0 { + return [][]string{0: {"No databases"}} + } + + var data [][]string + for i := range d.DBs { + data = append(data, + []string{"ID", d.DBs[i].ID}, + []string{"DATE CREATED", d.DBs[i].DateCreated}, + []string{"PLAN", d.DBs[i].Plan}, + []string{"PLAN DISK", strconv.Itoa(d.DBs[i].PlanDisk)}, + []string{"PLAN RAM", strconv.Itoa(d.DBs[i].PlanRAM)}, + []string{"PLAN VCPUS", strconv.Itoa(d.DBs[i].PlanVCPUs)}, + []string{"PLAN REPLICAS", strconv.Itoa(d.DBs[i].PlanReplicas)}, + []string{"REGION", d.DBs[i].Region}, + []string{"DATABASE ENGINE", d.DBs[i].DatabaseEngine}, + []string{"DATABASE ENGINE VERSION", d.DBs[i].DatabaseEngineVersion}, + []string{"VPC ID", d.DBs[i].VPCID}, + []string{"STATUS", d.DBs[i].Status}, + []string{"LABEL", d.DBs[i].Label}, + []string{"TAG", d.DBs[i].Tag}, + []string{"DB NAME", d.DBs[i].DBName}, + ) + + if d.DBs[i].DatabaseEngine == "ferretpg" { + data = append(data, + []string{" "}, + []string{"FERRETDB CREDENTIALS"}, + []string{"HOST", d.DBs[i].FerretDBCredentials.Host}, + []string{"PORT", strconv.Itoa(d.DBs[i].FerretDBCredentials.Port)}, + []string{"USER", d.DBs[i].FerretDBCredentials.User}, + []string{"PASSWORD", d.DBs[i].FerretDBCredentials.Password}, + []string{"PUBLIC IP", d.DBs[i].FerretDBCredentials.PublicIP}, + ) + + if d.DBs[i].FerretDBCredentials.PrivateIP != "" { + data = append(data, + []string{"PRIVATE IP", d.DBs[i].FerretDBCredentials.PrivateIP}, + ) + } + + data = append(data, []string{" "}) + } + + data = append(data, []string{"HOST", d.DBs[i].Host}) + + if d.DBs[i].PublicHost != "" { + data = append(data, []string{"PUBLIC HOST", d.DBs[i].PublicHost}) + } + + data = append(data, + []string{"USER", d.DBs[i].User}, + []string{"PASSWORD", d.DBs[i].Password}, + []string{"PORT", d.DBs[i].Port}, + []string{"MAINTENANCE DOW", d.DBs[i].MaintenanceDOW}, + []string{"MAINTENANCE TIME", d.DBs[i].MaintenanceTime}, + []string{"LATEST BACKUP", d.DBs[i].LatestBackup}, + []string{"TRUSTED IPS", printer.ArrayOfStringsToString(d.DBs[i].TrustedIPs)}, + ) + + if d.DBs[i].DatabaseEngine == "mysql" { + data = append(data, + []string{"MYSQL SQL MODES", printer.ArrayOfStringsToString(d.DBs[i].MySQLSQLModes)}, + []string{"MYSQL REQUIRE PRIMARY KEY", strconv.FormatBool(*d.DBs[i].MySQLRequirePrimaryKey)}, + []string{"MYSQL SLOW QUERY LOG", strconv.FormatBool(*d.DBs[i].MySQLSlowQueryLog)}, + ) + + if *d.DBs[i].MySQLSlowQueryLog { + data = append(data, + []string{"MYSQL LONG QUERY TIME", strconv.Itoa(d.DBs[i].MySQLLongQueryTime)}, + ) + } + } + + if d.DBs[i].DatabaseEngine == "pg" && len(d.DBs[i].PGAvailableExtensions) > 0 { + data = append(data, + []string{" "}, + []string{"PG AVAILABLE EXTENSIONS"}, + []string{"NAME", "VERSIONS"}, + ) + + for j := range d.DBs[i].PGAvailableExtensions { + if len(d.DBs[i].PGAvailableExtensions[j].Versions) > 0 { + data = append(data, []string{ + d.DBs[i].PGAvailableExtensions[j].Name, + printer.ArrayOfStringsToString(d.DBs[i].PGAvailableExtensions[j].Versions)}) + } else { + data = append(data, []string{d.DBs[i].PGAvailableExtensions[j].Name, ""}) + } + } + data = append(data, []string{" "}) + } + + if d.DBs[i].DatabaseEngine == "redis" { + data = append(data, []string{"REDIS EVICTION POLICY", d.DBs[i].RedisEvictionPolicy}) + } + + data = append(data, []string{"CLUSTER TIME ZONE", d.DBs[i].ClusterTimeZone}) + + if len(d.DBs[i].ReadReplicas) > 0 { + data = append(data, + []string{" "}, + []string{"READ REPLICAS"}, + ) + + for j := range d.DBs[i].ReadReplicas { + data = append(data, + []string{"ID", d.DBs[i].ReadReplicas[j].ID}, + []string{"DATE CREATED", d.DBs[i].ReadReplicas[j].DateCreated}, + []string{"PLAN", d.DBs[i].ReadReplicas[j].Plan}, + []string{"PLAN DISK", strconv.Itoa(d.DBs[i].ReadReplicas[j].PlanDisk)}, + []string{"PLAN RAM", strconv.Itoa(d.DBs[i].ReadReplicas[j].PlanRAM)}, + []string{"PLAN VCPUS", strconv.Itoa(d.DBs[i].ReadReplicas[j].PlanVCPUs)}, + []string{"PLAN REPLICAS", strconv.Itoa(d.DBs[i].ReadReplicas[j].PlanReplicas)}, + []string{"REGION", d.DBs[i].ReadReplicas[j].Region}, + []string{"DATABASE ENGINE", d.DBs[i].ReadReplicas[j].DatabaseEngine}, + []string{"DATABASE ENGINE VERSION", d.DBs[i].ReadReplicas[j].DatabaseEngineVersion}, + []string{"VPC ID", d.DBs[i].ReadReplicas[j].VPCID}, + []string{"STATUS", d.DBs[i].ReadReplicas[j].Status}, + []string{"LABEL", d.DBs[i].ReadReplicas[j].Label}, + []string{"TAG", d.DBs[i].ReadReplicas[j].Tag}, + []string{"DB NAME", d.DBs[i].ReadReplicas[j].DBName}, + ) + + if d.DBs[i].ReadReplicas[j].DatabaseEngine == "ferretpg" { + data = append(data, + []string{" "}, + + []string{"FERRETDB CREDENTIALS"}, + []string{"HOST", d.DBs[i].ReadReplicas[j].FerretDBCredentials.Host}, + []string{"PORT", strconv.Itoa(d.DBs[i].ReadReplicas[j].FerretDBCredentials.Port)}, + []string{"USER", d.DBs[i].ReadReplicas[j].FerretDBCredentials.User}, + []string{"PASSWORD", d.DBs[i].ReadReplicas[j].FerretDBCredentials.Password}, + []string{"PUBLIC IP", d.DBs[i].ReadReplicas[j].FerretDBCredentials.PublicIP}, + ) + + if d.DBs[i].ReadReplicas[j].FerretDBCredentials.PrivateIP != "" { + data = append(data, + []string{"PRIVATE IP", d.DBs[i].ReadReplicas[j].FerretDBCredentials.PrivateIP}, + ) + } + + data = append(data, []string{" "}) + } + + data = append(data, []string{"HOST", d.DBs[i].ReadReplicas[j].Host}) + + if d.DBs[i].ReadReplicas[j].PublicHost != "" { + data = append(data, []string{"PUBLIC HOST", d.DBs[i].ReadReplicas[j].PublicHost}) + } + + data = append(data, + []string{"USER", d.DBs[i].ReadReplicas[j].User}, + []string{"PASSWORD", d.DBs[i].ReadReplicas[j].Password}, + []string{"PORT", d.DBs[i].ReadReplicas[j].Port}, + []string{"MAINTENANCE DOW", d.DBs[i].ReadReplicas[j].MaintenanceDOW}, + []string{"MAINTENANCE TIME", d.DBs[i].ReadReplicas[j].MaintenanceTime}, + []string{"LATEST BACKUP", d.DBs[i].ReadReplicas[j].LatestBackup}, + []string{"TRUSTED IPS", printer.ArrayOfStringsToString(d.DBs[i].ReadReplicas[j].TrustedIPs)}, + ) + + if d.DBs[i].ReadReplicas[j].DatabaseEngine == "mysql" { + data = append(data, + []string{"MYSQL SQL MODES", printer.ArrayOfStringsToString(d.DBs[i].ReadReplicas[j].MySQLSQLModes)}, + []string{"MYSQL REQUIRE PRIMARY KEY", strconv.FormatBool(*d.DBs[i].ReadReplicas[j].MySQLRequirePrimaryKey)}, + []string{"MYSQL SLOW QUERY LOG", strconv.FormatBool(*d.DBs[i].ReadReplicas[j].MySQLSlowQueryLog)}, + ) + + if *d.DBs[i].ReadReplicas[j].MySQLSlowQueryLog { + data = append(data, []string{"MYSQL LONG QUERY TIME", strconv.Itoa(d.DBs[i].ReadReplicas[j].MySQLLongQueryTime)}) + } + } + + if d.DBs[i].ReadReplicas[j].DatabaseEngine == "pg" && len(d.DBs[i].ReadReplicas[j].PGAvailableExtensions) > 0 { + data = append(data, + []string{" "}, + []string{"PG AVAILABLE EXTENSIONS"}, + []string{"NAME", "VERSIONS"}, + ) + + for k := range d.DBs[i].ReadReplicas[j].PGAvailableExtensions { + if len(d.DBs[i].ReadReplicas[j].PGAvailableExtensions[k].Versions) > 0 { + data = append(data, []string{ + d.DBs[i].ReadReplicas[j].PGAvailableExtensions[k].Name, + printer.ArrayOfStringsToString(d.DBs[i].ReadReplicas[j].PGAvailableExtensions[k].Versions), + }) + } else { + data = append(data, []string{d.DBs[i].ReadReplicas[j].PGAvailableExtensions[k].Name, ""}) + } + } + + data = append(data, []string{" "}) + } + + if d.DBs[i].ReadReplicas[j].DatabaseEngine == "redis" { + data = append(data, []string{"REDIS EVICTION POLICY", d.DBs[i].ReadReplicas[j].RedisEvictionPolicy}) + } + + data = append(data, []string{"CLUSTER TIME ZONE", d.DBs[i].ReadReplicas[j].ClusterTimeZone}) + } + } + + data = append(data, []string{"---------------------------"}) + } + + return data +} + +// Paging ... +func (d *DBsPrinter) Paging() [][]string { + paging := &printer.Total{Total: d.Meta.Total} + return paging.Compose() +} + +// ====================================== + +// DBPrinter ... +type DBPrinter struct { + DB *govultr.Database `json:"database"` +} + +// JSON ... +func (d *DBPrinter) JSON() []byte { + return printer.MarshalObject(d, "json") +} + +// YAML ... +func (d *DBPrinter) YAML() []byte { + return printer.MarshalObject(d, "yaml") +} + +// Columns ... +func (d *DBPrinter) Columns() [][]string { + return nil +} + +// Data ... +func (d *DBPrinter) Data() [][]string { //nolint:funlen,gocyclo + var data [][]string + data = append(data, + []string{"ID", d.DB.ID}, + []string{"DATE CREATED", d.DB.DateCreated}, + []string{"PLAN", d.DB.Plan}, + []string{"PLAN DISK", strconv.Itoa(d.DB.PlanDisk)}, + []string{"PLAN RAM", strconv.Itoa(d.DB.PlanRAM)}, + []string{"PLAN VCPUS", strconv.Itoa(d.DB.PlanVCPUs)}, + []string{"PLAN REPLICAS", strconv.Itoa(d.DB.PlanReplicas)}, + []string{"REGION", d.DB.Region}, + []string{"DATABASE ENGINE", d.DB.DatabaseEngine}, + []string{"DATABASE ENGINE VERSION", d.DB.DatabaseEngineVersion}, + []string{"VPC ID", d.DB.VPCID}, + []string{"STATUS", d.DB.Status}, + []string{"LABEL", d.DB.Label}, + []string{"TAG", d.DB.Tag}, + []string{"DB NAME", d.DB.DBName}, + ) + + if d.DB.DatabaseEngine == "ferretpg" { + data = append(data, + []string{" "}, + []string{"FERRETDB CREDENTIALS"}, + []string{"HOST", d.DB.FerretDBCredentials.Host}, + []string{"PORT", strconv.Itoa(d.DB.FerretDBCredentials.Port)}, + []string{"USER", d.DB.FerretDBCredentials.User}, + []string{"PASSWORD", d.DB.FerretDBCredentials.Password}, + []string{"PUBLIC IP", d.DB.FerretDBCredentials.PublicIP}, + ) + + if d.DB.FerretDBCredentials.PrivateIP != "" { + data = append(data, + []string{"PRIVATE IP", d.DB.FerretDBCredentials.PrivateIP}, + ) + } + + data = append(data, []string{" "}) + } + + data = append(data, []string{"HOST", d.DB.Host}) + + if d.DB.PublicHost != "" { + data = append(data, []string{"PUBLIC HOST", d.DB.PublicHost}) + } + + data = append(data, + []string{"USER", d.DB.User}, + []string{"PASSWORD", d.DB.Password}, + []string{"PORT", d.DB.Port}, + []string{"MAINTENANCE DOW", d.DB.MaintenanceDOW}, + []string{"MAINTENANCE TIME", d.DB.MaintenanceTime}, + []string{"LATEST BACKUP", d.DB.LatestBackup}, + []string{"TRUSTED IPS", printer.ArrayOfStringsToString(d.DB.TrustedIPs)}, + ) + + if d.DB.DatabaseEngine == "mysql" { + data = append(data, + []string{"MYSQL SQL MODES", printer.ArrayOfStringsToString(d.DB.MySQLSQLModes)}, + []string{"MYSQL REQUIRE PRIMARY KEY", strconv.FormatBool(*d.DB.MySQLRequirePrimaryKey)}, + []string{"MYSQL SLOW QUERY LOG", strconv.FormatBool(*d.DB.MySQLSlowQueryLog)}, + ) + + if *d.DB.MySQLSlowQueryLog { + data = append(data, + []string{"MYSQL LONG QUERY TIME", strconv.Itoa(d.DB.MySQLLongQueryTime)}, + ) + } + } + + if d.DB.DatabaseEngine == "pg" && len(d.DB.PGAvailableExtensions) > 0 { + data = append(data, + []string{" "}, + []string{"PG AVAILABLE EXTENSIONS"}, + []string{"NAME", "VERSIONS"}, + ) + + for i := range d.DB.PGAvailableExtensions { + if len(d.DB.PGAvailableExtensions[i].Versions) > 0 { + data = append(data, []string{ + d.DB.PGAvailableExtensions[i].Name, + printer.ArrayOfStringsToString(d.DB.PGAvailableExtensions[i].Versions)}) + } else { + data = append(data, []string{d.DB.PGAvailableExtensions[i].Name, ""}) + } + } + data = append(data, []string{" "}) + } + + if d.DB.DatabaseEngine == "redis" { + data = append(data, []string{"REDIS EVICTION POLICY", d.DB.RedisEvictionPolicy}) + } + + data = append(data, []string{"CLUSTER TIME ZONE", d.DB.ClusterTimeZone}) + + if len(d.DB.ReadReplicas) > 0 { + data = append(data, + []string{" "}, + []string{"READ REPLICAS"}, + ) + + for i := range d.DB.ReadReplicas { + data = append(data, + []string{"ID", d.DB.ReadReplicas[i].ID}, + []string{"DATE CREATED", d.DB.ReadReplicas[i].DateCreated}, + []string{"PLAN", d.DB.ReadReplicas[i].Plan}, + []string{"PLAN DISK", strconv.Itoa(d.DB.ReadReplicas[i].PlanDisk)}, + []string{"PLAN RAM", strconv.Itoa(d.DB.ReadReplicas[i].PlanRAM)}, + []string{"PLAN VCPUS", strconv.Itoa(d.DB.ReadReplicas[i].PlanVCPUs)}, + []string{"PLAN REPLICAS", strconv.Itoa(d.DB.ReadReplicas[i].PlanReplicas)}, + []string{"REGION", d.DB.ReadReplicas[i].Region}, + []string{"DATABASE ENGINE", d.DB.ReadReplicas[i].DatabaseEngine}, + []string{"DATABASE ENGINE VERSION", d.DB.ReadReplicas[i].DatabaseEngineVersion}, + []string{"VPC ID", d.DB.ReadReplicas[i].VPCID}, + []string{"STATUS", d.DB.ReadReplicas[i].Status}, + []string{"LABEL", d.DB.ReadReplicas[i].Label}, + []string{"TAG", d.DB.ReadReplicas[i].Tag}, + []string{"DB NAME", d.DB.ReadReplicas[i].DBName}, + ) + + if d.DB.ReadReplicas[i].DatabaseEngine == "ferretpg" { + data = append(data, + []string{" "}, + + []string{"FERRETDB CREDENTIALS"}, + []string{"HOST", d.DB.ReadReplicas[i].FerretDBCredentials.Host}, + []string{"PORT", strconv.Itoa(d.DB.ReadReplicas[i].FerretDBCredentials.Port)}, + []string{"USER", d.DB.ReadReplicas[i].FerretDBCredentials.User}, + []string{"PASSWORD", d.DB.ReadReplicas[i].FerretDBCredentials.Password}, + []string{"PUBLIC IP", d.DB.ReadReplicas[i].FerretDBCredentials.PublicIP}, + ) + + if d.DB.ReadReplicas[i].FerretDBCredentials.PrivateIP != "" { + data = append(data, + []string{"PRIVATE IP", d.DB.ReadReplicas[i].FerretDBCredentials.PrivateIP}, + ) + } + + data = append(data, []string{" "}) + } + + data = append(data, []string{"HOST", d.DB.ReadReplicas[i].Host}) + + if d.DB.ReadReplicas[i].PublicHost != "" { + data = append(data, []string{"PUBLIC HOST", d.DB.ReadReplicas[i].PublicHost}) + } + + data = append(data, + []string{"USER", d.DB.ReadReplicas[i].User}, + []string{"PASSWORD", d.DB.ReadReplicas[i].Password}, + []string{"PORT", d.DB.ReadReplicas[i].Port}, + []string{"MAINTENANCE DOW", d.DB.ReadReplicas[i].MaintenanceDOW}, + []string{"MAINTENANCE TIME", d.DB.ReadReplicas[i].MaintenanceTime}, + []string{"LATEST BACKUP", d.DB.ReadReplicas[i].LatestBackup}, + []string{"TRUSTED IPS", printer.ArrayOfStringsToString(d.DB.ReadReplicas[i].TrustedIPs)}, + ) + + if d.DB.ReadReplicas[i].DatabaseEngine == "mysql" { + data = append(data, + []string{"MYSQL SQL MODES", printer.ArrayOfStringsToString(d.DB.ReadReplicas[i].MySQLSQLModes)}, + []string{"MYSQL REQUIRE PRIMARY KEY", strconv.FormatBool(*d.DB.ReadReplicas[i].MySQLRequirePrimaryKey)}, + []string{"MYSQL SLOW QUERY LOG", strconv.FormatBool(*d.DB.ReadReplicas[i].MySQLSlowQueryLog)}, + ) + + if *d.DB.ReadReplicas[i].MySQLSlowQueryLog { + data = append(data, []string{"MYSQL LONG QUERY TIME", strconv.Itoa(d.DB.ReadReplicas[i].MySQLLongQueryTime)}) + } + } + + if d.DB.ReadReplicas[i].DatabaseEngine == "pg" && len(d.DB.ReadReplicas[i].PGAvailableExtensions) > 0 { + data = append(data, + []string{" "}, + []string{"PG AVAILABLE EXTENSIONS"}, + []string{"NAME", "VERSIONS"}, + ) + + for j := range d.DB.ReadReplicas[i].PGAvailableExtensions { + if len(d.DB.ReadReplicas[i].PGAvailableExtensions[j].Versions) > 0 { + data = append(data, []string{ + d.DB.ReadReplicas[i].PGAvailableExtensions[j].Name, + printer.ArrayOfStringsToString(d.DB.ReadReplicas[i].PGAvailableExtensions[j].Versions), + }) + } else { + data = append(data, []string{d.DB.ReadReplicas[i].PGAvailableExtensions[j].Name, ""}) + } + } + + data = append(data, []string{" "}) + } + + if d.DB.ReadReplicas[i].DatabaseEngine == "redis" { + data = append(data, []string{"REDIS EVICTION POLICY", d.DB.ReadReplicas[i].RedisEvictionPolicy}) + } + + data = append(data, []string{"CLUSTER TIME ZONE", d.DB.ReadReplicas[i].ClusterTimeZone}) + } + } + + return data +} + +// Paging ... +func (d *DBPrinter) Paging() [][]string { + return nil +} + +// ====================================== + +// DBsSummaryPrinter ... +type DBsSummaryPrinter struct { + DBs []govultr.Database `json:"databases"` + Meta *govultr.Meta `json:"meta"` +} + +// JSON ... +func (d *DBsSummaryPrinter) JSON() []byte { + return printer.MarshalObject(d, "json") +} + +// YAML ... +func (d *DBsSummaryPrinter) YAML() []byte { + return printer.MarshalObject(d, "yaml") +} + +// Columns ... +func (d *DBsSummaryPrinter) Columns() [][]string { + return [][]string{0: { + "ID", + "REGION", + "LABEL", + "STATUS", + "ENGINE", + "VERSION", + }} +} + +// Data ... +func (d *DBsSummaryPrinter) Data() [][]string { + if len(d.DBs) == 0 { + return [][]string{0: {"---", "---", "---", "---", "---", "---"}} + } + + var data [][]string + for i := range d.DBs { + data = append(data, []string{ + + d.DBs[i].ID, + d.DBs[i].Region, + d.DBs[i].Label, + d.DBs[i].Status, + d.DBs[i].DatabaseEngine, + d.DBs[i].DatabaseEngineVersion, + }) + } + + return data +} + +// Paging ... +func (d *DBsSummaryPrinter) Paging() [][]string { + paging := &printer.Total{Total: d.Meta.Total} + return paging.Compose() +} + +// ====================================== + +// PlansPrinter ... +type PlansPrinter struct { + Plans []govultr.DatabasePlan `json:"plans"` + Meta *govultr.Meta `json:"meta"` +} + +// JSON ... +func (p *PlansPrinter) JSON() []byte { + return printer.MarshalObject(p, "json") +} + +// YAML ... +func (p *PlansPrinter) YAML() []byte { + return printer.MarshalObject(p, "yaml") +} + +// Columns ... +func (p *PlansPrinter) Columns() [][]string { + return nil +} + +// Data ... +func (p *PlansPrinter) Data() [][]string { + if len(p.Plans) == 0 { + return [][]string{0: {"No database plans available"}} + } + + var data [][]string + for i := range p.Plans { + data = append(data, + []string{"ID", p.Plans[i].ID}, + []string{"NUMBER OF NODES", strconv.Itoa(p.Plans[i].NumberOfNodes)}, + []string{"TYPE", p.Plans[i].Type}, + []string{"VCPU COUNT", strconv.Itoa(p.Plans[i].VCPUCount)}, + []string{"RAM", strconv.Itoa(p.Plans[i].RAM)}, + []string{"DISK", strconv.Itoa(p.Plans[i].Disk)}, + []string{"MONTHLY COST", strconv.Itoa(p.Plans[i].MonthlyCost)}, + + []string{" "}, + + []string{"SUPPORTED ENGINES"}, + []string{"MYSQL", strconv.FormatBool(*p.Plans[i].SupportedEngines.MySQL)}, + []string{"PG", strconv.FormatBool(*p.Plans[i].SupportedEngines.PG)}, + []string{"REDIS", strconv.FormatBool(*p.Plans[i].SupportedEngines.Redis)}, + ) + + if !*p.Plans[i].SupportedEngines.Redis { + data = append(data, + []string{" "}, + []string{"MAX CONNECTIONS"}, + []string{"MYSQL", strconv.Itoa(p.Plans[i].MaxConnections.MySQL)}, + []string{"PG", strconv.Itoa(p.Plans[i].MaxConnections.PG)}, + []string{" "}, + ) + } + + data = append(data, + []string{"LOCATIONS", printer.ArrayOfStringsToString(p.Plans[i].Locations)}, + []string{"---------------------------"}, + ) + } + + return data +} + +// Paging ... +func (p *PlansPrinter) Paging() [][]string { + paging := &printer.Total{Total: p.Meta.Total} + return paging.Compose() +} + +// ====================================== + +// UsagePrinter ... +type UsagePrinter struct { + Usage *govultr.DatabaseUsage `json:"usage"` +} + +// JSON ... +func (u *UsagePrinter) JSON() []byte { + return printer.MarshalObject(u, "json") +} + +// YAML ... +func (u *UsagePrinter) YAML() []byte { + return printer.MarshalObject(u, "yaml") +} + +// Columns ... +func (u *UsagePrinter) Columns() [][]string { + return nil +} + +// Data ... +func (u *UsagePrinter) Data() [][]string { + var data [][]string + data = append(data, + []string{"DISK USAGE"}, + []string{"CURRENT (GB)", strconv.FormatFloat( + float64(u.Usage.Disk.CurrentGB), + 'f', + utils.FloatPrecision, + utils.FloatBitDepth, + )}, + []string{"MAXIMUM (GB)", strconv.FormatFloat( + float64(u.Usage.Disk.MaxGB), + 'f', + utils.FloatPrecision, + utils.FloatBitDepth, + )}, + []string{"PERCENTAGE", strconv.FormatFloat( + float64(u.Usage.Disk.Percentage), + 'f', + utils.FloatPrecision, + utils.FloatBitDepth, + )}, + []string{" "}, + []string{"MEMORY USAGE"}, + []string{"CURRENT (MB)", strconv.FormatFloat( + float64(u.Usage.Memory.CurrentMB), + 'f', + utils.FloatPrecision, + utils.FloatBitDepth, + )}, + []string{"MAXIMUM (MB)", strconv.FormatFloat( + float64(u.Usage.Memory.MaxMB), + 'f', + utils.FloatPrecision, + utils.FloatBitDepth, + )}, + []string{"PERCENTAGE", strconv.FormatFloat( + float64(u.Usage.Memory.Percentage), + 'f', + utils.FloatPrecision, + utils.FloatBitDepth, + )}, + []string{" "}, + []string{"CPU USAGE"}, + []string{"PERCENTAGE", strconv.FormatFloat( + float64(u.Usage.CPU.Percentage), + 'f', + utils.FloatPrecision, + utils.FloatBitDepth, + )}, + ) + + return data +} + +// Paging ... +func (u *UsagePrinter) Paging() [][]string { + return nil +} + +// ====================================== + +// UsersPrinter ... +type UsersPrinter struct { + Users []govultr.DatabaseUser `json:"users"` + Meta *govultr.Meta `json:"meta"` +} + +// JSON ... +func (u *UsersPrinter) JSON() []byte { + return printer.MarshalObject(u, "json") +} + +// YAML ... +func (u *UsersPrinter) YAML() []byte { + return printer.MarshalObject(u, "yaml") +} + +// Columns ... +func (u *UsersPrinter) Columns() [][]string { + return nil +} + +// Data ... +func (u *UsersPrinter) Data() [][]string { + if len(u.Users) == 0 { + return [][]string{0: {"No database users"}} + } + + var data [][]string + for i := range u.Users { + data = append(data, + []string{"USERNAME", u.Users[i].Username}, + []string{"PASSWORD", u.Users[i].Password}, + ) + + if u.Users[i].Encryption != "" { + data = append(data, []string{"ENCRYPTION", u.Users[i].Encryption}) + } + + if u.Users[i].AccessControl != nil { + data = append(data, + []string{"ACCESS CONTROL"}, + []string{"REDIS ACL CATEGORIES", printer.ArrayOfStringsToString(u.Users[i].AccessControl.RedisACLCategories)}, + []string{"REDIS ACL CHANNELS", printer.ArrayOfStringsToString(u.Users[i].AccessControl.RedisACLChannels)}, + []string{"REDIS ACL COMMANDS", printer.ArrayOfStringsToString(u.Users[i].AccessControl.RedisACLCommands)}, + []string{"REDIS ACL KEYS", printer.ArrayOfStringsToString(u.Users[i].AccessControl.RedisACLKeys)}, + ) + } + + data = append(data, []string{"---------------------------"}) + } + + return data +} + +// Paging ... +func (u *UsersPrinter) Paging() [][]string { + paging := &printer.Total{Total: u.Meta.Total} + return paging.Compose() +} + +// ====================================== + +// UserPrinter ... +type UserPrinter struct { + User *govultr.DatabaseUser `json:"user"` +} + +// JSON ... +func (u *UserPrinter) JSON() []byte { + return printer.MarshalObject(u, "json") +} + +// YAML ... +func (u *UserPrinter) YAML() []byte { + return printer.MarshalObject(u, "yaml") +} + +// Columns ... +func (u *UserPrinter) Columns() [][]string { + return nil +} + +// Data ... +func (u *UserPrinter) Data() [][]string { + var data [][]string + data = append(data, + []string{"USERNAME", u.User.Username}, + []string{"PASSWORD", u.User.Password}, + ) + + if u.User.Encryption != "" { + data = append(data, []string{"ENCRYPTION", u.User.Encryption}) + } + + if u.User.AccessControl != nil { + data = append(data, + []string{"ACCESS CONTROL"}, + []string{"REDIS ACL CATEGORIES", printer.ArrayOfStringsToString(u.User.AccessControl.RedisACLCategories)}, + []string{"REDIS ACL CHANNELS", printer.ArrayOfStringsToString(u.User.AccessControl.RedisACLChannels)}, + []string{"REDIS ACL COMMANDS", printer.ArrayOfStringsToString(u.User.AccessControl.RedisACLCommands)}, + []string{"REDIS ACL KEYS", printer.ArrayOfStringsToString(u.User.AccessControl.RedisACLKeys)}, + ) + } + + return data +} + +// Paging ... +func (u *UserPrinter) Paging() [][]string { + return nil +} + +// ====================================== + +// LogicalDBsPrinter ... +type LogicalDBsPrinter struct { + DBs []govultr.DatabaseDB `json:"dbs"` + Meta *govultr.Meta `json:"meta"` +} + +// JSON ... +func (l *LogicalDBsPrinter) JSON() []byte { + return printer.MarshalObject(l, "json") +} + +// YAML ... +func (l *LogicalDBsPrinter) YAML() []byte { + return printer.MarshalObject(l, "yaml") +} + +// Columns ... +func (l *LogicalDBsPrinter) Columns() [][]string { + return [][]string{0: { + "NAME", + }} +} + +// Data ... +func (l *LogicalDBsPrinter) Data() [][]string { + if len(l.DBs) == 0 { + return [][]string{0: {"---"}} + } + + var data [][]string + for i := range l.DBs { + data = append(data, []string{l.DBs[i].Name}) + } + + return data +} + +// Paging ... +func (l *LogicalDBsPrinter) Paging() [][]string { + paging := &printer.Total{Total: l.Meta.Total} + return paging.Compose() +} + +// ====================================== + +// LogicalDBPrinter ... +type LogicalDBPrinter struct { + DB *govultr.DatabaseDB `json:"db"` +} + +// JSON ... +func (l *LogicalDBPrinter) JSON() []byte { + return printer.MarshalObject(l, "json") +} + +// YAML ... +func (l *LogicalDBPrinter) YAML() []byte { + return printer.MarshalObject(l, "yaml") +} + +// Columns ... +func (l *LogicalDBPrinter) Columns() [][]string { + return [][]string{0: { + "NAME", + }} +} + +// Data ... +func (l *LogicalDBPrinter) Data() [][]string { + return [][]string{0: {l.DB.Name}} +} + +// Paging ... +func (l *LogicalDBPrinter) Paging() [][]string { + return nil +} + +// ====================================== + +// UpdatesPrinter ... +type UpdatesPrinter struct { + Updates []string `json:"available_updates"` +} + +// JSON ... +func (u *UpdatesPrinter) JSON() []byte { + return printer.MarshalObject(u, "json") +} + +// YAML ... +func (u *UpdatesPrinter) YAML() []byte { + return printer.MarshalObject(u, "yaml") +} + +// Columns ... +func (u *UpdatesPrinter) Columns() [][]string { + return [][]string{0: {"AVAILABLE UPDATES"}} +} + +// Data ... +func (u *UpdatesPrinter) Data() [][]string { + var data [][]string + + for i := range u.Updates { + data = append(data, []string{u.Updates[i]}) + } + + return data +} + +// Paging ... +func (u *UpdatesPrinter) Paging() [][]string { + return nil +} + +// ====================================== + +// AlertsPrinter ... +type AlertsPrinter struct { + Alerts []govultr.DatabaseAlert `json:"alerts"` +} + +// JSON ... +func (a *AlertsPrinter) JSON() []byte { + return printer.MarshalObject(a, "json") +} + +// YAML ... +func (a *AlertsPrinter) YAML() []byte { + return printer.MarshalObject(a, "yaml") +} + +// Columns ... +func (a *AlertsPrinter) Columns() [][]string { + return [][]string{0: { + "NAME", + }} +} + +// Data ... +func (a *AlertsPrinter) Data() [][]string { + if len(a.Alerts) == 0 { + return [][]string{0: {"No active database alerts"}} + } + + var data [][]string + for i := range a.Alerts { + data = append(data, + []string{"TIMESTAMP", a.Alerts[i].Timestamp}, + []string{"MESSAGE TYPE", a.Alerts[i].MessageType}, + []string{"DESCRIPTION", a.Alerts[i].Description}, + ) + + if a.Alerts[i].Recommendation != "" { + data = append(data, []string{"RECOMMENDATION", a.Alerts[i].Recommendation}) + } + + if a.Alerts[i].MaintenanceScheduled != "" { + data = append(data, []string{"MAINTENANCE SCHEDULED", a.Alerts[i].MaintenanceScheduled}) + } + + if a.Alerts[i].ResourceType != "" { + data = append(data, []string{"RESOURCE TYPE", a.Alerts[i].ResourceType}) + } + + if a.Alerts[i].TableCount != 0 { + data = append(data, []string{"TABLE COUNT", strconv.Itoa(a.Alerts[i].TableCount)}) + } + + data = append(data, []string{"---------------------------"}) + } + + return data +} + +// Paging ... +func (a *AlertsPrinter) Paging() [][]string { + return nil +} + +// ====================================== + +// MigrationPrinter ... +type MigrationPrinter struct { + Migration *govultr.DatabaseMigration `json:"migration"` +} + +// JSON ... +func (m *MigrationPrinter) JSON() []byte { + return printer.MarshalObject(m, "json") +} + +// YAML ... +func (m *MigrationPrinter) YAML() []byte { + return printer.MarshalObject(m, "yaml") +} + +// Columns ... +func (m *MigrationPrinter) Columns() [][]string { + return nil +} + +// Data ... +func (m *MigrationPrinter) Data() [][]string { + var data [][]string + data = append(data, []string{"STATUS", m.Migration.Status}) + + if m.Migration.Method != "" { + data = append(data, []string{"METHOD", m.Migration.Method}) + } + + if m.Migration.Error != "" { + data = append(data, []string{"ERROR", m.Migration.Error}) + } + + data = append(data, + []string{" "}, + []string{"CREDENTIALS"}, + []string{"HOST", m.Migration.Credentials.Host}, + []string{"PORT", strconv.Itoa(m.Migration.Credentials.Port)}, + []string{"USERNAME", m.Migration.Credentials.Username}, + []string{"PASSWORD", m.Migration.Credentials.Password}, + ) + + if m.Migration.Credentials.Database != "" { + data = append(data, []string{"DATABASE", m.Migration.Credentials.Database}) + } + + if m.Migration.Credentials.IgnoredDatabases != "" { + data = append(data, []string{"IGNORED DATABASES", m.Migration.Credentials.IgnoredDatabases}) + } + + data = append(data, []string{"SSL", strconv.FormatBool(*m.Migration.Credentials.SSL)}) + + return data +} + +// Paging ... +func (m *MigrationPrinter) Paging() [][]string { + return nil +} + +// ====================================== + +// BackupPrinter ... +type BackupPrinter struct { + Backup *govultr.DatabaseBackups `json:"backups"` +} + +// JSON ... +func (b *BackupPrinter) JSON() []byte { + return printer.MarshalObject(b, "json") +} + +// YAML ... +func (b *BackupPrinter) YAML() []byte { + return printer.MarshalObject(b, "yaml") +} + +// Columns ... +func (b *BackupPrinter) Columns() [][]string { + return nil +} + +// Data ... +func (b *BackupPrinter) Data() [][]string { + var data [][]string + data = append(data, + []string{"LATEST BACKUP"}, + []string{"DATE", b.Backup.LatestBackup.Date}, + []string{"TIME", b.Backup.LatestBackup.Time}, + []string{" "}, + []string{"OLDEST BACKUP"}, + []string{"DATE", b.Backup.OldestBackup.Date}, + []string{"TIME", b.Backup.OldestBackup.Time}, + ) + + return data +} + +// Paging ... +func (b *BackupPrinter) Paging() [][]string { + return nil +} + +// ====================================== + +// ConnectionsPrinter ... +type ConnectionsPrinter struct { + Connections *govultr.DatabaseConnections `json:"connections"` + ConnectionPools []govultr.DatabaseConnectionPool `json:"connection_pools"` + Meta *govultr.Meta `json:"meta"` +} + +// JSON ... +func (c *ConnectionsPrinter) JSON() []byte { + return printer.MarshalObject(c, "json") +} + +// YAML ... +func (c *ConnectionsPrinter) YAML() []byte { + return printer.MarshalObject(c, "yaml") +} + +// Columns ... +func (c *ConnectionsPrinter) Columns() [][]string { + return nil +} + +// Data ... +func (c *ConnectionsPrinter) Data() [][]string { + var data [][]string + + data = append(data, + []string{"CONNECTIONS"}, + []string{"USED", strconv.Itoa(c.Connections.Used)}, + []string{"AVAILABLE", strconv.Itoa(c.Connections.Available)}, + []string{"MAX", strconv.Itoa(c.Connections.Max)}, + + []string{" "}, + []string{"CONNECTION POOLS"}, + ) + + for i := range c.ConnectionPools { + data = append(data, + []string{"NAME", c.ConnectionPools[i].Name}, + []string{"DATABASE", c.ConnectionPools[i].Database}, + []string{"USERNAME", c.ConnectionPools[i].Username}, + []string{"MODE", c.ConnectionPools[i].Mode}, + []string{"SIZE", strconv.Itoa(c.ConnectionPools[i].Size)}, + []string{"---------------------------"}, + ) + } + + return data +} + +// Paging ... +func (c *ConnectionsPrinter) Paging() [][]string { + paging := &printer.Total{Total: c.Meta.Total} + return paging.Compose() +} + +// ====================================== + +// ConnectionPoolPrinter ... +type ConnectionPoolPrinter struct { + ConnectionPool *govultr.DatabaseConnectionPool `json:"connection_pool"` +} + +// JSON ... +func (c *ConnectionPoolPrinter) JSON() []byte { + return printer.MarshalObject(c, "json") +} + +// YAML ... +func (c *ConnectionPoolPrinter) YAML() []byte { + return printer.MarshalObject(c, "yaml") +} + +// Columns ... +func (c *ConnectionPoolPrinter) Columns() [][]string { + return [][]string{0: { + "NAME", + "DATABASE", + "USERNAME", + "MODE", + "SIZE", + }} +} + +// Data ... +func (c *ConnectionPoolPrinter) Data() [][]string { + return [][]string{0: { + c.ConnectionPool.Name, + c.ConnectionPool.Database, + c.ConnectionPool.Username, + c.ConnectionPool.Mode, + strconv.Itoa(c.ConnectionPool.Size), + }} +} + +// Paging ... +func (c *ConnectionPoolPrinter) Paging() [][]string { + return nil +} + +// ====================================== + +// AdvancedOptionsPrinter ... +type AdvancedOptionsPrinter struct { + Configured *govultr.DatabaseAdvancedOptions `json:"configured_options"` + Available []govultr.AvailableOption `json:"available_options"` +} + +// JSON ... +func (a *AdvancedOptionsPrinter) JSON() []byte { + return printer.MarshalObject(a, "json") +} + +// YAML ... +func (a *AdvancedOptionsPrinter) YAML() []byte { + return printer.MarshalObject(a, "yaml") +} + +// Columns ... +func (a *AdvancedOptionsPrinter) Columns() [][]string { + return nil +} + +// Data ... +func (a *AdvancedOptionsPrinter) Data() [][]string { + var data [][]string + + if a.Configured == (&govultr.DatabaseAdvancedOptions{}) { + data = append(data, []string{"CONFIGURED OPTIONS", "None"}) + } else { + data = append(data, []string{"CONFIGURED OPTIONS"}) + v := reflect.ValueOf(*a.Configured) + for i := 0; i < v.NumField(); i++ { + if !v.Field(i).IsZero() { + if v.Field(i).Kind() == reflect.Pointer { + data = append(data, []string{v.Type().Field(i).Name, v.Field(i).Elem().Interface().(string)}) + } else { + data = append(data, []string{v.Type().Field(i).Name, v.Field(i).Interface().(string)}) + } + } + } + } + + data = append(data, + []string{" "}, + []string{"AVAILABLE OPTIONS"}, + ) + + for i := range a.Available { + data = append(data, + []string{"NAME", a.Available[i].Name}, + []string{"TYPE", a.Available[i].Type}, + ) + + if a.Available[i].Type == "enum" { + data = append(data, + []string{"ENUMERALS", printer.ArrayOfStringsToString(a.Available[i].Enumerals)}, + ) + } + + if a.Available[i].Type == "int" || a.Available[i].Type == "float" { + data = append(data, + []string{"MIN VALUE", strconv.Itoa(*a.Available[i].MinValue)}, + []string{"MAX VALUE", strconv.Itoa(*a.Available[i].MaxValue)}, + ) + } + + if len(a.Available[i].AltValues) > 0 { + data = append(data, []string{"ALT VALUES", printer.ArrayOfIntsToString(a.Available[i].AltValues)}) + } + + if a.Available[i].Units != "" { + data = append(data, []string{"UNITS", a.Available[i].Units}) + } + + data = append(data, []string{"---------------------------"}) + } + + return data +} + +// Paging ... +func (a *AdvancedOptionsPrinter) Paging() [][]string { + return nil +} + +// ====================================== + +// VersionsPrinter ... +type VersionsPrinter struct { + Versions []string `json:"available_versions"` +} + +// JSON ... +func (v *VersionsPrinter) JSON() []byte { + return printer.MarshalObject(v, "json") +} + +// YAML ... +func (v *VersionsPrinter) YAML() []byte { + return printer.MarshalObject(v, "yaml") +} + +// Columns ... +func (v *VersionsPrinter) Columns() [][]string { + return [][]string{0: { + "AVAILABLE VERSIONS", + }} +} + +// Data ... +func (v *VersionsPrinter) Data() [][]string { + var data [][]string + for i := range v.Versions { + data = append(data, []string{v.Versions[i]}) + } + + return data +} + +// Paging ... +func (v *VersionsPrinter) Paging() [][]string { + return nil +} diff --git a/cmd/dns.go b/cmd/dns.go deleted file mode 100644 index f34866b1..00000000 --- a/cmd/dns.go +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright © 2019 The Vultr-cli Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package cmd - -import ( - "github.com/spf13/cobra" -) - -// DNS represents the dns command -func DNS() *cobra.Command { - dnsCmd := &cobra.Command{ - Use: "dns", - Short: "dns is used to access dns commands", - Long: ``, - } - - dnsCmd.AddCommand(DNSDomain()) - dnsCmd.AddCommand(DNSRecord()) - return dnsCmd -} diff --git a/cmd/dns/dns.go b/cmd/dns/dns.go new file mode 100644 index 00000000..98359ca0 --- /dev/null +++ b/cmd/dns/dns.go @@ -0,0 +1,645 @@ +// Package dns provides the functionality for dns commands in the CLI +package dns + +import ( + "errors" + "fmt" + "os" + + "github.com/spf13/cobra" + "github.com/vultr/govultr/v3" + "github.com/vultr/vultr-cli/v3/cmd/printer" + "github.com/vultr/vultr-cli/v3/cmd/utils" + "github.com/vultr/vultr-cli/v3/pkg/cli" +) + +var ( + dnsLong = `` + dnsExample = `` + + createLong = `` + createExample = `` + + domainLong = `` + domainExample = `` +) + +// NewCmdDNS provides the CLI command functionality for DNS +func NewCmdDNS(base *cli.Base) *cobra.Command { //nolint:funlen,gocyclo + o := &options{Base: base} + + cmd := &cobra.Command{ + Use: "dns", + Short: "Commands to control DNS records", + Long: dnsLong, + Example: dnsExample, + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + utils.SetOptions(o.Base, cmd, args) + if !o.Base.HasAuth { + return errors.New(utils.APIKeyError) + } + return nil + }, + } + + domain := &cobra.Command{ + Use: "domain", + Short: "DNS domain commands", + Long: domainLong, + Example: domainExample, + } + + // Domain List + domainList := &cobra.Command{ + Use: "list", + Short: "Get list of domains", + Run: func(cmd *cobra.Command, args []string) { + o.Base.Options = utils.GetPaging(cmd) + + dms, meta, err := o.domainList() + if err != nil { + printer.Error(fmt.Errorf("error retrieving domain list : %v", err)) + os.Exit(1) + } + + data := &DNSDomainsPrinter{Domains: dms, Meta: meta} + o.Base.Printer.Display(data, nil) + }, + } + + domainList.Flags().StringP("cursor", "c", "", "(optional) Cursor for paging.") + domainList.Flags().IntP( + "per-page", + "p", + utils.PerPageDefault, + fmt.Sprintf("(optional) Number of items requested per page. Default is %d and Max is 500.", utils.PerPageDefault), + ) + + // Domain Get + domainGet := &cobra.Command{ + Use: "get ", + Short: "Get a domain", + Long: ``, + Run: func(cmd *cobra.Command, args []string) { + dm, err := o.domainGet() + if err != nil { + printer.Error(fmt.Errorf("error retrieving domain : %v", err)) + os.Exit(1) + } + + data := &DNSDomainPrinter{Domain: *dm} + o.Base.Printer.Display(data, nil) + }, + } + + // Domain Create + domainCreate := &cobra.Command{ + Use: "create", + Short: "Create a domain", + Long: createLong, + Example: createExample, + Run: func(cmd *cobra.Command, args []string) { + domain, errDo := cmd.Flags().GetString("domain") + if errDo != nil { + printer.Error(fmt.Errorf("error parsing 'domain' flag for domain create : %v", errDo)) + os.Exit(1) + } + + ip, errIP := cmd.Flags().GetString("ip") + if errIP != nil { + printer.Error(fmt.Errorf("error parsing 'ip' flag for domain create : %v", errIP)) + os.Exit(1) + } + + o.DomainCreateReq = &govultr.DomainReq{ + Domain: domain, + IP: ip, + } + + dm, err := o.domainCreate() + if err != nil { + printer.Error(fmt.Errorf("error creating dns domain : %v", err)) + os.Exit(1) + } + + data := &DNSDomainPrinter{Domain: *dm} + o.Base.Printer.Display(data, nil) + }, + } + + domainCreate.Flags().StringP("domain", "d", "", "name of the domain") + if err := domainCreate.MarkFlagRequired("domain"); err != nil { + printer.Error(fmt.Errorf("error marking domain create 'domain' flag required: %v", err)) + os.Exit(1) + } + domainCreate.Flags().StringP("ip", "i", "", "instance ip you want to assign this domain to") + + // Domain Delete + domainDelete := &cobra.Command{ + Use: "delete ", + Short: "Delete a domain", + Aliases: []string{"destroy"}, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("please provide a domain name") + } + return nil + }, + Run: func(cmd *cobra.Command, args []string) { + if err := o.domainDelete(); err != nil { + printer.Error(fmt.Errorf("error delete dns domain : %v", err)) + os.Exit(1) + } + + o.Base.Printer.Display(printer.Info("dns domain has been deleted"), nil) + }, + } + + // Domain DNSSEC Update + domainDNSSEC := &cobra.Command{ + Use: "dnssec ", + Short: "enable/disable dnssec", + Long: ``, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("please provide a domain name") + } + return nil + }, + Run: func(cmd *cobra.Command, args []string) { + enabled, errEn := cmd.Flags().GetBool("enabled") + if errEn != nil { + printer.Error(fmt.Errorf("error parsing 'enabled' flag for dnssec : %v", errEn)) + os.Exit(1) + } + + disabled, errDi := cmd.Flags().GetBool("disabled") + if errEn != nil { + printer.Error(fmt.Errorf("error parsing 'disabled' flag for dnssec : %v", errDi)) + os.Exit(1) + } + + if cmd.Flags().Changed("enabled") { + if enabled { + o.DomainDNSSECEnabled = "enabled" + } else { + o.DomainDNSSECEnabled = "disabled" + } + } + + if cmd.Flags().Changed("disabled") { + if disabled { + o.DomainDNSSECEnabled = "disabled" + } else { + o.DomainDNSSECEnabled = "enabled" + } + } + + if err := o.domainUpdate(); err != nil { + printer.Error(fmt.Errorf("error toggling dnssec : %v", err)) + os.Exit(1) + } + + o.Base.Printer.Display(printer.Info("dns domain DNSSEC has been updated"), nil) + }, + } + + domainDNSSEC.Flags().BoolP("enabled", "e", true, "enable dnssec") + domainDNSSEC.Flags().BoolP("disabled", "d", true, "disable dnssec") + domainDNSSEC.MarkFlagsOneRequired("enabled", "disabled") + domainDNSSEC.MarkFlagsMutuallyExclusive("enabled", "disabled") + + // Domain DNSSEC Info + domainDNSSECInfo := &cobra.Command{ + Use: "dnssec-info ", + Short: "Get DNS SEC info", + Long: ``, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("please provide a domain name") + } + return nil + }, + Run: func(cmd *cobra.Command, args []string) { + info, err := o.domainDNSSECGet() + if err != nil { + printer.Error(fmt.Errorf("error getting domain dnssec info : %v", err)) + os.Exit(1) + } + + data := &DNSSECPrinter{SEC: info} + o.Base.Printer.Display(data, nil) + }, + } + + // Domain SOA Info + domainSOAInfo := &cobra.Command{ + Use: "soa-info ", + Short: "Get DNS SOA info", + Long: ``, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("please provide a domain name") + } + return nil + }, + Run: func(cmd *cobra.Command, args []string) { + info, err := o.domainSOAGet() + if err != nil { + printer.Error(fmt.Errorf("error getting domain soa info : %v", err)) + os.Exit(1) + } + + data := &DNSSOAPrinter{SOA: *info} + o.Base.Printer.Display(data, nil) + }, + } + + // Domain SOA Update + domainSOAUpdate := &cobra.Command{ + Use: "soa-update ", + Short: "Update SOA for a domain", + Long: ``, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("please provide a domain name") + } + return nil + }, + Run: func(cmd *cobra.Command, args []string) { + ns, errNs := cmd.Flags().GetString("ns-primary") + if errNs != nil { + printer.Error(fmt.Errorf("error parsing 'ns-primary' flag for domain soa : %v", errNs)) + os.Exit(1) + } + + email, errEm := cmd.Flags().GetString("email") + if errEm != nil { + printer.Error(fmt.Errorf("error parsing 'email' flag for domain soa : %v", errEm)) + os.Exit(1) + } + + o.SOAUpdateReq = &govultr.Soa{ + NSPrimary: ns, + Email: email, + } + + if err := o.domainSOAUpdate(); err != nil { + printer.Error(fmt.Errorf("error updating domain soa : %v", err)) + os.Exit(1) + } + + o.Base.Printer.Display(printer.Info("domain soa has been updated"), nil) + }, + } + + domainSOAUpdate.Flags().StringP("ns-primary", "n", "", "primary nameserver to store in the SOA record") + domainSOAUpdate.Flags().StringP("email", "e", "", "administrative email to store in the SOA record") + + domain.AddCommand( + domainList, + domainGet, + domainCreate, + domainDelete, + domainDNSSEC, + domainDNSSECInfo, + domainSOAInfo, + domainSOAUpdate, + ) + + // Record + record := &cobra.Command{ + Use: "record", + Short: "dns record", + } + + // Record List + recordList := &cobra.Command{ + Use: "list ", + Short: "List all DNS records", + Long: ``, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("please provide a domain name") + } + return nil + }, + Run: func(cmd *cobra.Command, args []string) { + o.Base.Options = utils.GetPaging(cmd) + + recs, meta, err := o.recordList() + if err != nil { + printer.Error(fmt.Errorf("error retrieiving domain records : %v", err)) + os.Exit(1) + } + + data := &DNSRecordsPrinter{Records: recs, Meta: meta} + o.Base.Printer.Display(data, nil) + }, + } + + recordList.Flags().StringP("cursor", "c", "", "(optional) Cursor for paging.") + recordList.Flags().IntP( + "per-page", + "p", + utils.PerPageDefault, + fmt.Sprintf("(optional) Number of items requested per page. Default is %d and Max is 500.", utils.PerPageDefault), + ) + + // Record Get + recordGet := &cobra.Command{ + Use: "get ", + Short: "Get a DNS record", + Long: ``, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 2 { + return errors.New("please provide a domain name and record ID") + } + return nil + }, + Run: func(cmd *cobra.Command, args []string) { + rec, err := o.recordGet() + if err != nil { + printer.Error(fmt.Errorf("error while getting domain record : %v", err)) + os.Exit(1) + } + + data := &DNSRecordPrinter{Record: *rec} + o.Base.Printer.Display(data, nil) + }, + } + + // Record Create + recordCreate := &cobra.Command{ + Use: "create ", + Short: "Create a dns record", + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("please provide a domain name") + } + return nil + }, + Run: func(cmd *cobra.Command, args []string) { + rType, errTy := cmd.Flags().GetString("type") + if errTy != nil { + printer.Error(fmt.Errorf("error parsing 'type' flag for domain record create : %v", errTy)) + os.Exit(1) + } + + name, errNa := cmd.Flags().GetString("name") + if errNa != nil { + printer.Error(fmt.Errorf("error parsing 'name' flag for domain record create : %v", errNa)) + os.Exit(1) + } + + dt, errDa := cmd.Flags().GetString("data") + if errDa != nil { + printer.Error(fmt.Errorf("error parsing 'data' flag for domain record create : %v", errDa)) + os.Exit(1) + } + + ttl, errTt := cmd.Flags().GetInt("ttl") + if errTt != nil { + printer.Error(fmt.Errorf("error parsing 'ttl' flag for domain record create : %v", errTt)) + os.Exit(1) + } + + priority, errPr := cmd.Flags().GetInt("priority") + if errPr != nil { + printer.Error(fmt.Errorf("error parsing 'priority' flag for domain record create : %v", errPr)) + os.Exit(1) + } + + o.RecordReq = &govultr.DomainRecordReq{ + Name: name, + Type: rType, + Data: dt, + TTL: ttl, + Priority: &priority, + } + + rec, err := o.recordCreate() + if err != nil { + printer.Error(fmt.Errorf("error creating domain record : %v", err)) + os.Exit(1) + } + + data := &DNSRecordPrinter{Record: *rec} + o.Base.Printer.Display(data, nil) + }, + } + + recordCreate.Flags().StringP("type", "t", "", "type for the record") + if err := recordCreate.MarkFlagRequired("type"); err != nil { + printer.Error(fmt.Errorf("error marking dns record create 'type' flag required: %v", err)) + os.Exit(1) + } + + recordCreate.Flags().StringP("name", "n", "", "name of the record") + if err := recordCreate.MarkFlagRequired("name"); err != nil { + printer.Error(fmt.Errorf("error marking dns record create 'name' flag required: %v", err)) + os.Exit(1) + } + + recordCreate.Flags().StringP("data", "d", "", "data for the record") + if err := recordCreate.MarkFlagRequired("data"); err != nil { + printer.Error(fmt.Errorf("error marking dns record create 'data' flag required: %v", err)) + os.Exit(1) + } + + recordCreate.Flags().IntP("ttl", "l", 0, "ttl for the record") + if err := recordCreate.MarkFlagRequired("ttl"); err != nil { + printer.Error(fmt.Errorf("error marking dns record create 'ttl' flag required: %v", err)) + os.Exit(1) + } + + recordCreate.Flags().IntP("priority", "p", 0, "only required for MX and SRV") + if err := recordCreate.MarkFlagRequired("priority"); err != nil { + printer.Error(fmt.Errorf("error marking dns record create 'priority' flag required: %v", err)) + os.Exit(1) + } + + // Record Delete + recordDelete := &cobra.Command{ + Use: "delete ", + Short: "Delete DNS record", + Aliases: []string{"destroy"}, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 2 { + return errors.New("please provide a domain name & record ID") + } + return nil + }, + Run: func(cmd *cobra.Command, args []string) { + if err := o.recordDelete(); err != nil { + printer.Error(fmt.Errorf("error deleting domain record : %v", err)) + os.Exit(1) + } + + o.Base.Printer.Display(printer.Info("domain record has been deleted"), nil) + }, + } + + // Record Update + recordUpdate := &cobra.Command{ + Use: "update ", + Short: "Update DNS record", + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 2 { + return errors.New("please provide a domain name & record ID") + } + return nil + }, + Run: func(cmd *cobra.Command, args []string) { + name, errNa := cmd.Flags().GetString("name") + if errNa != nil { + printer.Error(fmt.Errorf("error parsing 'name' flag for domain record update : %v", errNa)) + os.Exit(1) + } + + dt, errDa := cmd.Flags().GetString("data") + if errDa != nil { + printer.Error(fmt.Errorf("error parsing 'data' flag for domain record update : %v", errDa)) + os.Exit(1) + } + + ttl, errTt := cmd.Flags().GetInt("ttl") + if errTt != nil { + printer.Error(fmt.Errorf("error parsing 'ttl' flag for domain record update : %v", errTt)) + os.Exit(1) + } + + priority, errPr := cmd.Flags().GetInt("priority") + if errPr != nil { + printer.Error(fmt.Errorf("error parsing 'priority' flag for domain record update : %v", errPr)) + os.Exit(1) + } + + o.RecordReq = &govultr.DomainRecordReq{} + + if cmd.Flags().Changed("name") { + o.RecordReq.Name = name + } + + if cmd.Flags().Changed("data") { + o.RecordReq.Data = dt + } + + if cmd.Flags().Changed("ttl") { + o.RecordReq.TTL = ttl + } + + if cmd.Flags().Changed("priority") { + o.RecordReq.Priority = govultr.IntToIntPtr(priority) + } + + if err := o.recordUpdate(); err != nil { + printer.Error(fmt.Errorf("error updating domain record : %v", errPr)) + os.Exit(1) + } + + o.Base.Printer.Display(printer.Info("domain record has been updated"), nil) + }, + } + + recordUpdate.Flags().StringP("name", "n", "", "name of record") + recordUpdate.Flags().StringP("data", "d", "", "data for the record") + recordUpdate.Flags().IntP("ttl", "", 0, "time to live for the record") + recordUpdate.Flags().IntP("priority", "p", 0, "only required for MX and SRV") + + record.AddCommand( + recordList, + recordGet, + recordCreate, + recordUpdate, + recordDelete, + ) + + cmd.AddCommand( + domain, + record, + ) + + return cmd +} + +type options struct { + Base *cli.Base + DomainCreateReq *govultr.DomainReq + DomainDNSSECEnabled string + SOAUpdateReq *govultr.Soa + RecordReq *govultr.DomainRecordReq +} + +// domainList ... +func (o *options) domainList() ([]govultr.Domain, *govultr.Meta, error) { + dms, meta, _, err := o.Base.Client.Domain.List(o.Base.Context, o.Base.Options) + return dms, meta, err +} + +// domainGet ... +func (o *options) domainGet() (*govultr.Domain, error) { + dm, _, err := o.Base.Client.Domain.Get(o.Base.Context, o.Base.Args[0]) + return dm, err +} + +// domainCreate ... +func (o *options) domainCreate() (*govultr.Domain, error) { + dm, _, err := o.Base.Client.Domain.Create(o.Base.Context, o.DomainCreateReq) + return dm, err +} + +// domainUpdate ... +func (o *options) domainUpdate() error { + return o.Base.Client.Domain.Update(o.Base.Context, o.Base.Args[0], o.DomainDNSSECEnabled) +} + +// domainDelete ... +func (o *options) domainDelete() error { + return o.Base.Client.Domain.Delete(o.Base.Context, o.Base.Args[0]) +} + +// domainDNSSECGet ... +func (o *options) domainDNSSECGet() ([]string, error) { + sec, _, err := o.Base.Client.Domain.GetDNSSec(o.Base.Context, o.Base.Args[0]) + return sec, err +} + +// domainSOAGet ... +func (o *options) domainSOAGet() (*govultr.Soa, error) { + soa, _, err := o.Base.Client.Domain.GetSoa(o.Base.Context, o.Base.Args[0]) + return soa, err +} + +// domainSOAUpdate ... +func (o *options) domainSOAUpdate() error { + return o.Base.Client.Domain.UpdateSoa(o.Base.Context, o.Base.Args[0], o.SOAUpdateReq) +} + +// recordList ... +func (o *options) recordList() ([]govultr.DomainRecord, *govultr.Meta, error) { + rec, meta, _, err := o.Base.Client.DomainRecord.List(o.Base.Context, o.Base.Args[0], o.Base.Options) + return rec, meta, err +} + +// recordGet ... +func (o *options) recordGet() (*govultr.DomainRecord, error) { + rec, _, err := o.Base.Client.DomainRecord.Get(o.Base.Context, o.Base.Args[0], o.Base.Args[1]) + return rec, err +} + +// recordCreate ... +func (o *options) recordCreate() (*govultr.DomainRecord, error) { + rec, _, err := o.Base.Client.DomainRecord.Create(o.Base.Context, o.Base.Args[0], o.RecordReq) + return rec, err +} + +// recordUpdate ... +func (o *options) recordUpdate() error { + return o.Base.Client.DomainRecord.Update(o.Base.Context, o.Base.Args[0], o.Base.Args[1], o.RecordReq) +} + +// recordDelete ... +func (o *options) recordDelete() error { + return o.Base.Client.DomainRecord.Delete(o.Base.Context, o.Base.Args[0], o.Base.Args[1]) +} diff --git a/cmd/dns/printer.go b/cmd/dns/printer.go new file mode 100644 index 00000000..4dea4dfd --- /dev/null +++ b/cmd/dns/printer.go @@ -0,0 +1,279 @@ +package dns + +import ( + "strconv" + + "github.com/vultr/govultr/v3" + "github.com/vultr/vultr-cli/v3/cmd/printer" +) + +// DNSRecordsPrinter ... +type DNSRecordsPrinter struct { + Records []govultr.DomainRecord `json:"records"` + Meta *govultr.Meta `json:"meta"` +} + +// JSON ... +func (d *DNSRecordsPrinter) JSON() []byte { + return printer.MarshalObject(d, "json") +} + +// YAML ... +func (d *DNSRecordsPrinter) YAML() []byte { + return printer.MarshalObject(d, "yaml") +} + +// Columns ... +func (d *DNSRecordsPrinter) Columns() [][]string { + return [][]string{0: { + "ID", + "TYPE", + "NAME", + "DATA", + "PRIORITY", + "TTL", + }} +} + +// Data ... +func (d *DNSRecordsPrinter) Data() [][]string { + if len(d.Records) == 0 { + return [][]string{0: {"---", "---", "---", "---", "---", "---"}} + } + + var data [][]string + for i := range d.Records { + data = append(data, []string{ + d.Records[i].ID, + d.Records[i].Type, + d.Records[i].Name, + d.Records[i].Data, + strconv.Itoa(d.Records[i].Priority), + strconv.Itoa(d.Records[i].TTL), + }) + } + + return data +} + +// Paging ... +func (d *DNSRecordsPrinter) Paging() [][]string { + return printer.NewPaging(d.Meta.Total, &d.Meta.Links.Next, &d.Meta.Links.Prev).Compose() +} + +// ====================================== + +// DNSRecordPrinter ... +type DNSRecordPrinter struct { + Record govultr.DomainRecord `json:"records"` +} + +// JSON ... +func (d *DNSRecordPrinter) JSON() []byte { + return printer.MarshalObject(d, "json") +} + +// YAML ... +func (d *DNSRecordPrinter) YAML() []byte { + return printer.MarshalObject(d, "yaml") +} + +// Columns ... +func (d *DNSRecordPrinter) Columns() [][]string { + return [][]string{0: { + "ID", + "TYPE", + "NAME", + "DATA", + "PRIORITY", + "TTL", + }} +} + +// Data ... +func (d *DNSRecordPrinter) Data() [][]string { + return [][]string{0: { + d.Record.ID, + d.Record.Type, + d.Record.Name, + d.Record.Data, + strconv.Itoa(d.Record.Priority), + strconv.Itoa(d.Record.TTL), + }} +} + +// Paging ... +func (d *DNSRecordPrinter) Paging() [][]string { + return nil +} + +// ====================================== + +// DNSDomainsPrinter ... +type DNSDomainsPrinter struct { + Domains []govultr.Domain `json:"domains"` + Meta *govultr.Meta `json:"meta"` +} + +// JSON ... +func (d *DNSDomainsPrinter) JSON() []byte { + return printer.MarshalObject(d, "json") +} + +// YAML ... +func (d *DNSDomainsPrinter) YAML() []byte { + return printer.MarshalObject(d, "yaml") +} + +// Columns ... +func (d *DNSDomainsPrinter) Columns() [][]string { + return [][]string{0: { + "DOMAIN", + "DATE CREATED", + "DNSSEC", + }} +} + +// Data ... +func (d *DNSDomainsPrinter) Data() [][]string { + if len(d.Domains) == 0 { + return [][]string{0: {"---", "---", "---"}} + } + + var data [][]string + for i := range d.Domains { + data = append(data, []string{ + d.Domains[i].Domain, + d.Domains[i].DateCreated, + d.Domains[i].DNSSec, + }) + } + + return data +} + +// Paging ... +func (d *DNSDomainsPrinter) Paging() [][]string { + return printer.NewPaging(d.Meta.Total, &d.Meta.Links.Next, &d.Meta.Links.Prev).Compose() +} + +// ====================================== + +// DNSDomainPrinter ... +type DNSDomainPrinter struct { + Domain govultr.Domain `json:"domain"` +} + +// JSON ... +func (d *DNSDomainPrinter) JSON() []byte { + return printer.MarshalObject(d, "json") +} + +// YAML ... +func (d *DNSDomainPrinter) YAML() []byte { + return printer.MarshalObject(d, "yaml") +} + +// Columns ... +func (d *DNSDomainPrinter) Columns() [][]string { + return [][]string{0: { + "DOMAIN", + "DATE CREATED", + "DNS SEC", + }} +} + +// Data ... +func (d *DNSDomainPrinter) Data() [][]string { + return [][]string{0: { + d.Domain.Domain, + d.Domain.DateCreated, + d.Domain.DNSSec, + }} +} + +// Paging ... +func (d *DNSDomainPrinter) Paging() [][]string { + return nil +} + +// ====================================== + +// DNSSOAPrinter ... +type DNSSOAPrinter struct { + SOA govultr.Soa `json:"dns_soa"` +} + +// JSON ... +func (d *DNSSOAPrinter) JSON() []byte { + return printer.MarshalObject(d, "json") +} + +// YAML ... +func (d *DNSSOAPrinter) YAML() []byte { + return printer.MarshalObject(d, "yaml") +} + +// Columns ... +func (d *DNSSOAPrinter) Columns() [][]string { + return [][]string{0: { + "NS PRIMARY", + "EMAIL", + }} +} + +// Data ... +func (d *DNSSOAPrinter) Data() [][]string { + return [][]string{0: { + d.SOA.NSPrimary, + d.SOA.Email, + }} +} + +// Paging ... +func (d *DNSSOAPrinter) Paging() [][]string { + return nil +} + +// ====================================== + +// DNSSECPrinter ... +type DNSSECPrinter struct { + SEC []string `json:"dns_sec"` +} + +// JSON ... +func (d *DNSSECPrinter) JSON() []byte { + return printer.MarshalObject(d, "json") +} + +// YAML ... +func (d *DNSSECPrinter) YAML() []byte { + return printer.MarshalObject(d, "yaml") +} + +// Columns ... +func (d *DNSSECPrinter) Columns() [][]string { + return [][]string{0: { + "DNSSEC INFO", + }} +} + +// Data ... +func (d *DNSSECPrinter) Data() [][]string { + if len(d.SEC) == 0 { + return [][]string{0: {"---"}} + } + + var data [][]string + for i := range d.SEC { + data = append(data, []string{d.SEC[i]}) + } + + return data +} + +// Paging ... +func (d *DNSSECPrinter) Paging() [][]string { + return nil +} diff --git a/cmd/dnsDomain.go b/cmd/dnsDomain.go deleted file mode 100644 index 09b99c61..00000000 --- a/cmd/dnsDomain.go +++ /dev/null @@ -1,229 +0,0 @@ -// Copyright © 2019 The Vultr-cli Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package cmd - -import ( - "context" - "errors" - "fmt" - "os" - - "github.com/spf13/cobra" - "github.com/vultr/govultr/v2" - "github.com/vultr/vultr-cli/cmd/printer" -) - -// DNSDomain represents the domain sub command -func DNSDomain() *cobra.Command { - dnsDomainCmd := &cobra.Command{ - Use: "domain", - Short: "dns domain", - Long: ``, - } - - dnsDomainCmd.AddCommand(domainCreate, domainGet, domainDelete, secEnable, secInfo, domainList, soaInfo, soaUpdate) - - // Create - domainCreate.Flags().StringP("domain", "d", "", "name of the domain") - domainCreate.MarkFlagRequired("domain") - domainCreate.Flags().StringP("ip", "i", "", "instance ip you want to assign this domain to") - - // Dns Sec - secEnable.Flags().StringP("enabled", "e", "", "set whether dns sec is enabled or not. true or false") - secEnable.MarkFlagRequired("enabled") - - // Soa Update - soaUpdate.Flags().StringP("ns-primary", "n", "", "primary nameserver to store in the SOA record") - soaUpdate.MarkFlagRequired("ns-primary") - soaUpdate.Flags().StringP("email", "e", "", "administrative email to store in the SOA record") - - // List - domainList.Flags().StringP("cursor", "c", "", "(optional) Cursor for paging.") - domainList.Flags().IntP("per-page", "p", 100, "(optional) Number of items requested per page. Default is 100 and Max is 500.") - - return dnsDomainCmd -} - -var domainCreate = &cobra.Command{ - Use: "create", - Short: "create a domain", - Long: ``, - Run: func(cmd *cobra.Command, args []string) { - domain, _ := cmd.Flags().GetString("domain") - ip, _ := cmd.Flags().GetString("ip") - - options := &govultr.DomainReq{ - Domain: domain, - IP: ip, - } - - dns, err := client.Domain.Create(context.Background(), options) - if err != nil { - fmt.Printf("error creating dns domain : %v\n", err) - os.Exit(1) - } - - printer.Domain(dns) - }, -} - -var domainDelete = &cobra.Command{ - Use: "delete ", - Short: "delete a domain", - Aliases: []string{"destroy"}, - Long: ``, - Args: func(cmd *cobra.Command, args []string) error { - if len(args) < 1 { - return errors.New("please provide a domain name") - } - return nil - }, - Run: func(cmd *cobra.Command, args []string) { - domain := args[0] - if err := client.Domain.Delete(context.Background(), domain); err != nil { - fmt.Printf("error delete dns domain : %v\n", err) - os.Exit(1) - } - - fmt.Println("deleted dns domain") - }, -} - -var secEnable = &cobra.Command{ - Use: "dnssec ", - Short: "enable/disable dnssec", - Long: ``, - Args: func(cmd *cobra.Command, args []string) error { - if len(args) < 1 { - return errors.New("please provide a domain name") - } - return nil - }, - Run: func(cmd *cobra.Command, args []string) { - domain := args[0] - enabled, _ := cmd.Flags().GetString("enabled") - if err := client.Domain.Update(context.Background(), domain, enabled); err != nil { - fmt.Printf("error toggling dnssec : %v\n", err) - os.Exit(1) - } - - fmt.Println("toggled dns sec") - }, -} - -var secInfo = &cobra.Command{ - Use: "dnssec-info ", - Short: "get dns sec info", - Long: ``, - Args: func(cmd *cobra.Command, args []string) error { - if len(args) < 1 { - return errors.New("please provide a domain name") - } - return nil - }, - Run: func(cmd *cobra.Command, args []string) { - domain := args[0] - info, err := client.Domain.GetDNSSec(context.Background(), domain) - if err != nil { - fmt.Printf("error getting dnssec info : %v\n", err) - os.Exit(1) - } - - printer.SecInfo(info) - }, -} - -var domainGet = &cobra.Command{ - Use: "get ", - Short: "get a domain", - Long: ``, - Run: func(cmd *cobra.Command, args []string) { - id := args[0] - domain, err := client.Domain.Get(context.Background(), id) - if err != nil { - fmt.Printf("error getting domain : %v\n", err) - os.Exit(1) - } - - printer.Domain(domain) - }, -} - -var domainList = &cobra.Command{ - Use: "list", - Short: "get list of domains", - Long: ``, - Run: func(cmd *cobra.Command, args []string) { - options := getPaging(cmd) - list, meta, err := client.Domain.List(context.Background(), options) - if err != nil { - fmt.Printf("error getting domains : %v\n", err) - os.Exit(1) - } - - printer.DomainList(list, meta) - }, -} - -var soaInfo = &cobra.Command{ - Use: "soa-info ", - Short: "get dns soa info", - Long: ``, - Args: func(cmd *cobra.Command, args []string) error { - if len(args) < 1 { - return errors.New("please provide a domain name") - } - return nil - }, - Run: func(cmd *cobra.Command, args []string) { - domain := args[0] - info, err := client.Domain.GetSoa(context.Background(), domain) - if err != nil { - fmt.Printf("error toggling dnssec : %v\n", err) - os.Exit(1) - } - - printer.SoaInfo(info) - }, -} - -var soaUpdate = &cobra.Command{ - Use: "soa-update ", - Short: "update soa for a domain", - Long: ``, - Args: func(cmd *cobra.Command, args []string) error { - if len(args) < 1 { - return errors.New("please provide a domain name") - } - return nil - }, - Run: func(cmd *cobra.Command, args []string) { - domain := args[0] - nsPrimary, _ := cmd.Flags().GetString("ns-primary") - email, _ := cmd.Flags().GetString("email") - - soaUpdate := &govultr.Soa{ - NSPrimary: nsPrimary, - Email: email, - } - - if err := client.Domain.UpdateSoa(context.Background(), domain, soaUpdate); err != nil { - fmt.Printf("error toggling dnssec : %v\n", err) - os.Exit(1) - } - - fmt.Println("updated SOA") - }, -} diff --git a/cmd/dnsRecord.go b/cmd/dnsRecord.go deleted file mode 100644 index f06e0c1f..00000000 --- a/cmd/dnsRecord.go +++ /dev/null @@ -1,223 +0,0 @@ -// Copyright © 2019 The Vultr-cli Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package cmd - -import ( - "context" - "errors" - "fmt" - "os" - "regexp" - - "github.com/spf13/cobra" - "github.com/vultr/govultr/v2" - "github.com/vultr/vultr-cli/cmd/printer" -) - -// DNSRecord represents the dnsRecord command -func DNSRecord() *cobra.Command { - dnsRecordCmd := &cobra.Command{ - Use: "record", - Short: "dns record", - Long: ``, - } - - dnsRecordCmd.AddCommand(recordCreate, recordGet, recordList, recordDelete, recordUpdate) - - // Create - recordCreate.Flags().StringP("domain", "m", "", "name of domain you want to create this record for") - recordCreate.Flags().StringP("type", "t", "", "record type you want to create : Possible values A, AAAA, CNAME, NS, MX, SRV, TXT CAA, SSHFP") - recordCreate.Flags().StringP("name", "n", "", "name of record") - recordCreate.Flags().StringP("data", "d", "", "data for the record") - recordCreate.MarkFlagRequired("domain") - recordCreate.MarkFlagRequired("type") - recordCreate.MarkFlagRequired("name") - recordCreate.MarkFlagRequired("data") - // Create Optional - recordCreate.Flags().IntP("ttl", "", 0, "time to live for the record") - recordCreate.Flags().IntP("priority", "p", 0, "only required for MX and SRV") - - // Update - recordUpdate.Flags().StringP("name", "n", "", "name of record") - recordUpdate.Flags().StringP("data", "d", "", "data for the record") - recordUpdate.Flags().IntP("ttl", "", 0, "time to live for the record") - recordUpdate.Flags().IntP("priority", "p", -1, "only required for MX and SRV") - - // List - recordList.Flags().StringP("cursor", "c", "", "(optional) Cursor for paging.") - recordList.Flags().IntP("per-page", "p", 100, "(optional) Number of items requested per page. Default is 100 and Max is 500.") - - return dnsRecordCmd -} - -// Temporary solution to determine if the record type is TXT, in order to -// add quotes around the value. The API does not accept TXT records without -// quotes. -var regRecordTxt = regexp.MustCompile("([A-Z]|=|_)") - -var recordCreate = &cobra.Command{ - Use: "create", - Short: "create a dns record", - Long: ``, - Run: func(cmd *cobra.Command, args []string) { - domain, _ := cmd.Flags().GetString("domain") - rType, _ := cmd.Flags().GetString("type") - name, _ := cmd.Flags().GetString("name") - data, _ := cmd.Flags().GetString("data") - // Record data for TXT must be enclosed in quotes - if data[0] != '"' && data[len(data)-1] != '"' && regRecordTxt.Match([]byte(data)) { - data = fmt.Sprintf("\"%s\"", data) - } - ttl, _ := cmd.Flags().GetInt("ttl") - priority, _ := cmd.Flags().GetInt("priority") - - options := &govultr.DomainRecordReq{ - Name: name, - Type: rType, - Data: data, - TTL: ttl, - Priority: &priority, - } - - record, err := client.DomainRecord.Create(context.Background(), domain, options) - if err != nil { - fmt.Printf("error while creating dns record : %v\n", err) - os.Exit(1) - } - - printer.DnsRecord(record) - }, -} - -var recordGet = &cobra.Command{ - Use: "get ", - Short: "get dns record", - Long: ``, - Args: func(cmd *cobra.Command, args []string) error { - if len(args) < 2 { - return errors.New("please provide a domain name and recordID") - } - return nil - }, - Run: func(cmd *cobra.Command, args []string) { - domain := args[0] - id := args[1] - - record, err := client.DomainRecord.Get(context.Background(), domain, id) - if err != nil { - fmt.Printf("error while getting dns records : %v\n", err) - os.Exit(1) - } - - printer.DnsRecord(record) - }, -} - -var recordList = &cobra.Command{ - Use: "list ", - Short: "list all dns records", - Long: ``, - Args: func(cmd *cobra.Command, args []string) error { - if len(args) < 1 { - return errors.New("please provide a domain name") - } - return nil - }, - Run: func(cmd *cobra.Command, args []string) { - domain := args[0] - options := getPaging(cmd) - - records, meta, err := client.DomainRecord.List(context.Background(), domain, options) - if err != nil { - fmt.Printf("error while getting dns records : %v\n", err) - os.Exit(1) - } - - printer.DnsRecordsList(records, meta) - }, -} - -var recordDelete = &cobra.Command{ - Use: "delete ", - Short: "delete dns record", - Aliases: []string{"destroy"}, - Long: ``, - Args: func(cmd *cobra.Command, args []string) error { - if len(args) < 2 { - return errors.New("please provide a domainName & recordID") - } - return nil - }, - Run: func(cmd *cobra.Command, args []string) { - domain := args[0] - id := args[1] - - if err := client.DomainRecord.Delete(context.Background(), domain, id); err != nil { - fmt.Printf("error while deleting dns record : %v\n", err) - os.Exit(1) - } - - fmt.Println("deleted dns record") - }, -} - -var recordUpdate = &cobra.Command{ - Use: "update ", - Short: "update dns record", - Long: ``, - Args: func(cmd *cobra.Command, args []string) error { - if len(args) < 2 { - return errors.New("please provide a domainName & recordID") - } - return nil - }, - Run: func(cmd *cobra.Command, args []string) { - domain := args[0] - id := args[1] - name, _ := cmd.Flags().GetString("name") - data, _ := cmd.Flags().GetString("data") - ttl, _ := cmd.Flags().GetInt("ttl") - priority, _ := cmd.Flags().GetInt("priority") - - updates := &govultr.DomainRecordReq{} - - if name != "" { - updates.Name = name - } - - if data != "" { - // Record data for TXT must be enclosed in quotes - if data[0] != '"' && data[len(data)-1] != '"' && regRecordTxt.Match([]byte(data)) { - data = fmt.Sprintf("\"%s\"", data) - } - updates.Data = data - } - - if ttl != 0 { - updates.TTL = ttl - } - - if priority != -1 { - updates.Priority = &priority - } - - if err := client.DomainRecord.Update(context.Background(), domain, id, updates); err != nil { - fmt.Printf("error updating dns record : %v\n", err) - os.Exit(1) - } - - fmt.Println("updated dns record") - }, -} diff --git a/cmd/firewall.go b/cmd/firewall.go deleted file mode 100644 index 23899c32..00000000 --- a/cmd/firewall.go +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright © 2019 The Vultr-cli Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package cmd - -import ( - "github.com/spf13/cobra" -) - -// Firewall represents the firewall command -func Firewall() *cobra.Command { - firewallCmd := &cobra.Command{ - Use: "firewall", - Short: "firewall is used to access firewall commands", - Long: ``, - Aliases: []string{"fw"}, - } - - firewallCmd.AddCommand(FirewallGroup(), FirewallRule()) - - return firewallCmd -} diff --git a/cmd/firewall/firewall.go b/cmd/firewall/firewall.go new file mode 100644 index 00000000..06a3ca04 --- /dev/null +++ b/cmd/firewall/firewall.go @@ -0,0 +1,538 @@ +// Package firewall provides the functionality for firewall commands in the CLI +package firewall + +import ( + "errors" + "fmt" + "os" + "strconv" + + "github.com/spf13/cobra" + "github.com/vultr/govultr/v3" + "github.com/vultr/vultr-cli/v3/cmd/printer" + "github.com/vultr/vultr-cli/v3/cmd/utils" + "github.com/vultr/vultr-cli/v3/pkg/cli" +) + +var ( + ruleLong = `Show commands available for firewall rules` + ruleExample = ` + # Full example + vultr-cli firewall rule + + # Shortened example with aliases + vultr-cli fw r + ` + ruleCreateLong = ` + Create a new firewall rule in the provided firewall group + + If protocol is TCP or UDP, port must be provided. + + An ip-type of v4 or v6 must be supplied for all rules. + ` + ruleCreateExample = ` + # Full examples + vultr-cli firewall rule create --id=f04ae5aa-ff6a-4078-900d-78cc17dca2d5 --ip-type=v4 --protocol=tcp --size=24 \ + --subnet=127.0.0.0 --port=30000 + + vultr-cli firewall rule create --id=f04ae5aa-ff6a-4078-900d-78cc17dca2d5 --ip-type=v4 --protocol=icmp --size=24 --subnet=127.0.0.0 + + # Shortened example with aliases + vultr-cli fw r c -i=f04ae5aa-ff6a-4078-900d-78cc17dca2d5 -t=v4 -p=tcp -z=24 -s=127.0.0.0 -r=30000 + ` + ruleDeleteLong = `Delete a firewall rule in the provided firewall group` + ruleDeleteExample = ` + # Full example + vultr-cli firewall rule delete 704ac064-4ff2-49ca-a6e6-88262cca8f8a f31ade4f-2308-4a58-82c6-2d1bae0837b3 + + # Shortened example with aliases + vultr-cli fw r d 704ac064-4ff2-49ca-a6e6-88262cca8f8a f31ade4f-2308-4a58-82c6-2d1bae0837b3 + ` + ruleGetLong = `Get a firewall rule in the provided firewall group` + ruleGetExample = ` + # Full example + vultr-cli firewall rule get 704ac064-4ff2-49ca-a6e6-88262cca8f8a f31ade4f-2308-4a58-82c6-2d1bae0837b3 + + # Shortened example with aliases + vultr-cli fw r get 704ac064-4ff2-49ca-a6e6-88262cca8f8a f31ade4f-2308-4a58-82c6-2d1bae0837b3 + ` + ruleListLong = `List all firewall rules in the provided firewall group` + ruleListExample = ` + # Full example + vultr-cli firewall rule list 704ac064-4ff2-49ca-a6e6-88262cca8f8a + + # Shortened example with aliases + vultr-cli fw r l 704ac064-4ff2-49ca-a6e6-88262cca8f8a + ` +) + +// NewCmdFirewall provides the CLI command functionality for Firewall +func NewCmdFirewall(base *cli.Base) *cobra.Command { //nolint:gocyclo + o := &options{Base: base} + + cmd := &cobra.Command{ + Use: "firewall", + Short: "Access firewall commands", + Aliases: []string{"fw"}, + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + utils.SetOptions(o.Base, cmd, args) + if !o.Base.HasAuth { + return errors.New(utils.APIKeyError) + } + return nil + }, + } + + // Group + group := &cobra.Command{ + Use: "group", + Short: "Commands to access firewall group functions", + Aliases: []string{"g"}, + } + + // Group List + groupList := &cobra.Command{ + Use: "list", + Short: "List all firewall groups", + Aliases: []string{"l"}, + Run: func(cmd *cobra.Command, args []string) { + o.Base.Options = utils.GetPaging(cmd) + + groups, meta, err := o.listGroups() + if err != nil { + printer.Error(fmt.Errorf("error retrieving firewall group list : %v", err)) + os.Exit(1) + } + + data := &FirewallGroupsPrinter{Groups: groups, Meta: meta} + o.Base.Printer.Display(data, nil) + }, + } + + groupList.Flags().StringP("cursor", "c", "", "(optional) cursor for paging.") + groupList.Flags().IntP( + "per-page", + "p", + utils.PerPageDefault, + fmt.Sprintf("(optional) Number of items requested per page. Default is %d and Max is 500.", utils.PerPageDefault), + ) + + // Group Get + groupGet := &cobra.Command{ + Use: "get ", + Short: "Get firewall group", + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("please provide a firewall group ID") + } + return nil + }, + Run: func(cmd *cobra.Command, args []string) { + group, err := o.getGroup() + if err != nil { + printer.Error(fmt.Errorf("error getting firewall group : %v", err)) + os.Exit(1) + } + + data := &FirewallGroupPrinter{Group: *group} + o.Base.Printer.Display(data, nil) + }, + } + + // Group Create + groupCreate := &cobra.Command{ + Use: "create", + Short: "create a firewall group", + Aliases: []string{"c"}, + Run: func(cmd *cobra.Command, args []string) { + description, errDe := cmd.Flags().GetString("description") + if errDe != nil { + printer.Error(fmt.Errorf("error parsing 'description' flag for firewall group create: %v", errDe)) + os.Exit(1) + } + + o.GroupReq = &govultr.FirewallGroupReq{ + Description: description, + } + + grp, err := o.createGroup() + if err != nil { + printer.Error(fmt.Errorf("error creating firewall group : %v", err)) + os.Exit(1) + } + + data := &FirewallGroupPrinter{Group: *grp} + o.Base.Printer.Display(data, nil) + }, + } + + groupCreate.Flags().StringP("description", "d", "", "(optional) Description of firewall group.") + + // Group Update + groupUpdate := &cobra.Command{ + Use: "update ", + Short: "Update firewall group description", + Aliases: []string{"u"}, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("please provide a firewall group ID") + } + return nil + }, + Run: func(cmd *cobra.Command, args []string) { + description, errDe := cmd.Flags().GetString("description") + if errDe != nil { + printer.Error(fmt.Errorf("error parsing 'description' flag for firewall group update : %v", errDe)) + os.Exit(1) + } + + o.GroupReq = &govultr.FirewallGroupReq{ + Description: description, + } + + if err := o.updateGroup(); err != nil { + printer.Error(fmt.Errorf("error updating firewall group : %v", err)) + os.Exit(1) + } + + o.Base.Printer.Display(printer.Info("firewall group has been updated"), nil) + }, + } + + groupUpdate.Flags().StringP("description", "d", "", "Description of firewall group.") + if err := groupUpdate.MarkFlagRequired("description"); err != nil { + printer.Error(fmt.Errorf("error marking firewall group 'description' flag required: %v", err)) + os.Exit(1) + } + + // Group Delete + groupDelete := &cobra.Command{ + Use: "delete ", + Short: "Delete a firewall group", + Aliases: []string{"d", "destroy"}, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("please provide a firewall group ID") + } + return nil + }, + Run: func(cmd *cobra.Command, args []string) { + if err := o.deleteGroup(); err != nil { + printer.Error(fmt.Errorf("error deleting firewall group : %v", err)) + os.Exit(1) + } + + o.Base.Printer.Display(printer.Info("firewall group has been deleted"), nil) + }, + } + + group.AddCommand( + groupList, + groupGet, + groupCreate, + groupUpdate, + groupDelete, + ) + + // Rule + rule := &cobra.Command{ + Use: "rule", + Short: "Commands to access firewall rules", + Aliases: []string{"r"}, + Long: ruleLong, + Example: ruleExample, + } + + // Rule List + ruleList := &cobra.Command{ + Use: "list ", + Short: "List all firewall rules", + Aliases: []string{"l"}, + Long: ruleListLong, + Example: ruleListExample, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("please provide a firewall group ID") + } + return nil + }, + Run: func(cmd *cobra.Command, args []string) { + o.Base.Options = utils.GetPaging(cmd) + + rules, meta, err := o.listRules() + if err != nil { + printer.Error(fmt.Errorf("error retrieving firewall rule list : %v", err)) + os.Exit(1) + } + + data := &FirewallRulesPrinter{Rules: rules, Meta: meta} + o.Base.Printer.Display(data, nil) + }, + } + + ruleList.Flags().StringP("cursor", "c", "", "(optional) Cursor for paging.") + ruleList.Flags().IntP( + "per-page", + "p", + utils.PerPageDefault, + "(optional) Number of items requested per page. Default is 100 and Max is 500.", + ) + + // Rule Get + ruleGet := &cobra.Command{ + Use: "get ", + Short: "Get firewall rule", + Long: ruleGetLong, + Example: ruleGetExample, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 2 { + return errors.New("please provide a firewall group ID and firewall rule number") + } + return nil + }, + Run: func(cmd *cobra.Command, args []string) { + rule, err := o.getRule() + if err != nil { + printer.Error(fmt.Errorf("error getting firewall rule : %v", err)) + os.Exit(1) + } + + data := &FirewallRulePrinter{Rule: *rule} + o.Base.Printer.Display(data, nil) + }, + } + + // Rule Create + ruleCreate := &cobra.Command{ + Use: "create", + Short: "Create a firewall rule", + Aliases: []string{"c"}, + Long: ruleCreateLong, + Example: ruleCreateExample, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("please provide a firewall group ID") + } + return nil + }, + Run: func(cmd *cobra.Command, args []string) { + protocol, errPr := cmd.Flags().GetString("protocol") + if errPr != nil { + printer.Error(fmt.Errorf("error parsing 'protocol' flag for firewall group create : %v", errPr)) + os.Exit(1) + } + + subnet, errSu := cmd.Flags().GetString("subnet") + if errSu != nil { + printer.Error(fmt.Errorf("error parsing 'subnet' flag for firewall group create : %v", errSu)) + os.Exit(1) + } + + ipType, errIP := cmd.Flags().GetString("ip-type") + if errIP != nil { + printer.Error(fmt.Errorf("error parsing 'ip-type' flag for firewall group create : %v", errIP)) + os.Exit(1) + } + + size, errSi := cmd.Flags().GetInt("size") + if errSi != nil { + printer.Error(fmt.Errorf("error parsing 'size' flag for firewall group create : %v", errSi)) + os.Exit(1) + } + + source, errSo := cmd.Flags().GetString("source") + if errSo != nil { + printer.Error(fmt.Errorf("error parsing 'source' flag for firewall group create : %v", errSo)) + os.Exit(1) + } + + port, errPo := cmd.Flags().GetString("port") + if errPo != nil { + printer.Error(fmt.Errorf("error parsing 'port' flag for firewall group create : %v", errPo)) + os.Exit(1) + } + + notes, errNo := cmd.Flags().GetString("notes") + if errNo != nil { + printer.Error(fmt.Errorf("error parsing 'notes' flag for firewall group create : %v", errNo)) + os.Exit(1) + } + + o.RuleReq = &govultr.FirewallRuleReq{ + Protocol: protocol, + Subnet: subnet, + SubnetSize: size, + Notes: notes, + } + + if port != "" { + o.RuleReq.Port = port + } + + if source != "" { + o.RuleReq.Source = source + } + + if ipType == "" { + printer.Error(fmt.Errorf("a firewall rule requires an IP type. Pass an --ip-type value of v4 or v6")) + os.Exit(1) + } + + if ipType != "" { + o.RuleReq.IPType = ipType + } + + rule, err := o.createRule() + if err != nil { + printer.Error(fmt.Errorf("error creating firewall rule : %v", err)) + os.Exit(1) + } + + data := &FirewallRulePrinter{Rule: *rule} + o.Base.Printer.Display(data, nil) + }, + } + + ruleCreate.Flags().StringP("protocol", "p", "", "Protocol type. Possible values: 'icmp', 'tcp', 'udp', 'gre'.") + if err := ruleCreate.MarkFlagRequired("protocol"); err != nil { + printer.Error(fmt.Errorf("error marking firewall rule create 'protocol' flag required : %v", err)) + os.Exit(1) + } + + ruleCreate.Flags().StringP("subnet", "s", "", "The IPv4 network in CIDR notation.") + if err := ruleCreate.MarkFlagRequired("subnet"); err != nil { + printer.Error(fmt.Errorf("error marking firewall rule create 'subnet' flag required : %v", err)) + os.Exit(1) + } + + ruleCreate.Flags().IntP("size", "z", 0, "The number of bits for the netmask in CIDR notation.") + if err := ruleCreate.MarkFlagRequired("size"); err != nil { + printer.Error(fmt.Errorf("error marking firewall rule create 'size' flag required : %v", err)) + os.Exit(1) + } + + ruleCreate.Flags().StringP( + "source", + "", + "", + "(optional) When empty, uses value from subnet and size. If \"cloudflare\", allows all Cloudflare IP space through firewall.", + ) + + ruleCreate.Flags().StringP("ip-type", "t", "", "The type of IP rule - v4 or v6.") + if err := ruleCreate.MarkFlagRequired("ip-type"); err != nil { + printer.Error(fmt.Errorf("error marking firewall rule create 'ip-type' flag required : %v", err)) + os.Exit(1) + } + + ruleCreate.Flags().StringP( + "port", + "r", + "", + "(optional) TCP/UDP only. This field can be an integer value specifying a port or a colon separated port range.", + ) + + ruleCreate.Flags().StringP("notes", "n", "", "(optional) This field supports notes up to 255 characters.") + + // Rule Delete + ruleDelete := &cobra.Command{ + Use: "delete ", + Short: "Delete a firewall rule", + Aliases: []string{"d", "destroy"}, + Long: ruleDeleteLong, + Example: ruleDeleteExample, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 2 { + return errors.New("please provide a firewall group ID and firewall rule number") + } + return nil + }, + Run: func(cmd *cobra.Command, args []string) { + if err := o.deleteRule(); err != nil { + printer.Error(fmt.Errorf("error deleting firewall rule : %v", err)) + os.Exit(1) + } + + o.Base.Printer.Display(printer.Info("firewall rule deleted"), nil) + }, + } + + rule.AddCommand( + ruleList, + ruleGet, + ruleCreate, + ruleDelete, + ) + + cmd.AddCommand( + group, + rule, + ) + + return cmd +} + +type options struct { + Base *cli.Base + GroupReq *govultr.FirewallGroupReq + RuleReq *govultr.FirewallRuleReq +} + +// listGroups ... +func (o *options) listGroups() ([]govultr.FirewallGroup, *govultr.Meta, error) { + groups, meta, _, err := o.Base.Client.FirewallGroup.List(o.Base.Context, o.Base.Options) + return groups, meta, err +} + +// getGroup ... +func (o *options) getGroup() (*govultr.FirewallGroup, error) { + group, _, err := o.Base.Client.FirewallGroup.Get(o.Base.Context, o.Base.Args[0]) + return group, err +} + +// createGroup ... +func (o *options) createGroup() (*govultr.FirewallGroup, error) { + group, _, err := o.Base.Client.FirewallGroup.Create(o.Base.Context, o.GroupReq) + return group, err +} + +// updateGroup ... +func (o *options) updateGroup() error { + return o.Base.Client.FirewallGroup.Update(o.Base.Context, o.Base.Args[0], o.GroupReq) +} + +// deleteGroup ... +func (o *options) deleteGroup() error { + return o.Base.Client.FirewallGroup.Delete(o.Base.Context, o.Base.Args[0]) +} + +// listRules ... +func (o *options) listRules() ([]govultr.FirewallRule, *govultr.Meta, error) { + rules, meta, _, err := o.Base.Client.FirewallRule.List(o.Base.Context, o.Base.Args[0], o.Base.Options) + return rules, meta, err +} + +// getRule ... +func (o *options) getRule() (*govultr.FirewallRule, error) { + id, errIn := strconv.Atoi(o.Base.Args[1]) + if errIn != nil { + return nil, fmt.Errorf("unable to convert int to string : %v", errIn) + } + + rule, _, err := o.Base.Client.FirewallRule.Get(o.Base.Context, o.Base.Args[0], id) + return rule, err +} + +// createRule ... +func (o *options) createRule() (*govultr.FirewallRule, error) { + rule, _, err := o.Base.Client.FirewallRule.Create(o.Base.Context, o.Base.Args[0], o.RuleReq) + return rule, err +} + +// deleteRule ... +func (o *options) deleteRule() error { + id, err := strconv.Atoi(o.Base.Args[1]) + if err != nil { + return fmt.Errorf("unable to convert int to string : %v", err) + } + return o.Base.Client.FirewallRule.Delete(o.Base.Context, o.Base.Args[0], id) +} diff --git a/cmd/firewall/printer.go b/cmd/firewall/printer.go new file mode 100644 index 00000000..9fc1a53b --- /dev/null +++ b/cmd/firewall/printer.go @@ -0,0 +1,223 @@ +package firewall + +import ( + "strconv" + + "github.com/vultr/govultr/v3" + "github.com/vultr/vultr-cli/v3/cmd/printer" + "github.com/vultr/vultr-cli/v3/cmd/utils" +) + +// FirewallGroupsPrinter ... +type FirewallGroupsPrinter struct { + Groups []govultr.FirewallGroup `json:"firewall_groups"` + Meta *govultr.Meta `json:"meta"` +} + +// JSON ... +func (f *FirewallGroupsPrinter) JSON() []byte { + return printer.MarshalObject(f, "json") +} + +// YAML ... +func (f *FirewallGroupsPrinter) YAML() []byte { + return printer.MarshalObject(f, "yaml") +} + +// Columns ... +func (f *FirewallGroupsPrinter) Columns() [][]string { + return [][]string{0: { + "ID", + "DATE CREATED", + "DATE MODIFIED", + "INSTANCE COUNT", + "RULE COUNT", + "MAX RULE COUNT", + "DESCRIPTION", + }} +} + +// Data ... +func (f *FirewallGroupsPrinter) Data() [][]string { + if len(f.Groups) == 0 { + return [][]string{0: {"---", "---", "---", "---", "---", "---", "---"}} + } + + var data [][]string + for i := range f.Groups { + data = append(data, []string{ + f.Groups[i].ID, + f.Groups[i].DateCreated, + f.Groups[i].DateModified, + strconv.Itoa(f.Groups[i].InstanceCount), + strconv.Itoa(f.Groups[i].RuleCount), + strconv.Itoa(f.Groups[i].MaxRuleCount), + f.Groups[i].Description, + }) + } + + return data +} + +// Paging ... +func (f *FirewallGroupsPrinter) Paging() [][]string { + return printer.NewPaging(f.Meta.Total, &f.Meta.Links.Next, &f.Meta.Links.Prev).Compose() +} + +// ====================================== + +// FirewallGroupPrinter ... +type FirewallGroupPrinter struct { + Group govultr.FirewallGroup `json:"firewall_group"` +} + +// JSON ... +func (f *FirewallGroupPrinter) JSON() []byte { + return printer.MarshalObject(f, "json") +} + +// YAML ... +func (f *FirewallGroupPrinter) YAML() []byte { + return printer.MarshalObject(f, "yaml") +} + +// Columns ... +func (f *FirewallGroupPrinter) Columns() [][]string { + return [][]string{0: { + "ID", + "DATE CREATED", + "DATE MODIFIED", + "INSTANCE COUNT", + "RULE COUNT", + "MAX RULE COUNT", + "DESCRIPTION", + }} +} + +// Data ... +func (f *FirewallGroupPrinter) Data() [][]string { + return [][]string{0: { + f.Group.ID, + f.Group.DateCreated, + f.Group.DateModified, + strconv.Itoa(f.Group.InstanceCount), + strconv.Itoa(f.Group.RuleCount), + strconv.Itoa(f.Group.MaxRuleCount), + f.Group.Description, + }} +} + +// Paging ... +func (f *FirewallGroupPrinter) Paging() [][]string { + return nil +} + +// ====================================== + +// FirewallRulesPrinter ... +type FirewallRulesPrinter struct { + Rules []govultr.FirewallRule `json:"firewall_rules"` + Meta *govultr.Meta `json:"meta"` +} + +// JSON ... +func (f *FirewallRulesPrinter) JSON() []byte { + return printer.MarshalObject(f, "json") +} + +// YAML ... +func (f *FirewallRulesPrinter) YAML() []byte { + return printer.MarshalObject(f, "yaml") +} + +// Columns ... +func (f *FirewallRulesPrinter) Columns() [][]string { + return [][]string{0: { + "RULE NUMBER", + "ACTION", + "TYPE", + "PROTOCOL", + "PORT", + "NETWORK", + "SOURCE", + "NOTES", + }} +} + +// Data ... +func (f *FirewallRulesPrinter) Data() [][]string { + if len(f.Rules) == 0 { + return [][]string{0: {"---", "---", "---", "---", "---", "---", "---", "---"}} + } + + var data [][]string + for i := range f.Rules { + data = append(data, []string{ + strconv.Itoa(f.Rules[i].ID), + f.Rules[i].Action, + f.Rules[i].IPType, + f.Rules[i].Protocol, + f.Rules[i].Port, + utils.FormatFirewallNetwork(f.Rules[i].Subnet, f.Rules[i].SubnetSize), + utils.GetFirewallSource(f.Rules[i].Source), + f.Rules[i].Notes, + }) + } + + return data +} + +// Paging ... +func (f *FirewallRulesPrinter) Paging() [][]string { + return printer.NewPaging(f.Meta.Total, &f.Meta.Links.Next, &f.Meta.Links.Prev).Compose() +} + +// ====================================== + +// FirewallRulePrinter ... +type FirewallRulePrinter struct { + Rule govultr.FirewallRule `json:"firewall_rule"` +} + +// JSON ... +func (f *FirewallRulePrinter) JSON() []byte { + return printer.MarshalObject(f, "json") +} + +// YAML ... +func (f *FirewallRulePrinter) YAML() []byte { + return printer.MarshalObject(f, "yaml") +} + +// Columns ... +func (f *FirewallRulePrinter) Columns() [][]string { + return [][]string{0: { + "RULE NUMBER", + "ACTION", + "TYPE", + "PROTOCOL", + "PORT", + "NETWORK", + "SOURCE", + "NOTES", + }} +} + +// Data ... +func (f *FirewallRulePrinter) Data() [][]string { + return [][]string{0: { + strconv.Itoa(f.Rule.ID), + f.Rule.Action, + f.Rule.IPType, + f.Rule.Protocol, + f.Rule.Port, + utils.FormatFirewallNetwork(f.Rule.Subnet, f.Rule.SubnetSize), + utils.GetFirewallSource(f.Rule.Source), + f.Rule.Notes, + }} +} + +// Paging ... +func (f *FirewallRulePrinter) Paging() [][]string { + return nil +} diff --git a/cmd/firewallGroup.go b/cmd/firewallGroup.go deleted file mode 100644 index a83f5798..00000000 --- a/cmd/firewallGroup.go +++ /dev/null @@ -1,147 +0,0 @@ -// Copyright © 2019 The Vultr-cli Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package cmd - -import ( - "context" - "errors" - "fmt" - "os" - - "github.com/spf13/cobra" - "github.com/vultr/govultr/v2" - "github.com/vultr/vultr-cli/cmd/printer" -) - -// FirewallGroup represents the firewall group commands -func FirewallGroup() *cobra.Command { - firewallGroupCmd := &cobra.Command{ - Use: "group", - Short: "group is used to access firewall group commands", - Long: ``, - Aliases: []string{"g"}, - } - - firewallGroupCmd.AddCommand(firewallGroupCreate, firewallGroupDelete, firewallGroupGet, firewallGroupUpdate, firewallGroupList) - - firewallGroupCreate.Flags().StringP("description", "d", "", "(optional) Description of firewall group.") - - firewallGroupList.Flags().StringP("cursor", "c", "", "(optional) Cursor for paging.") - firewallGroupList.Flags().IntP("per-page", "p", 100, "(optional) Number of items requested per page. Default is 100 and Max is 500.") - - return firewallGroupCmd -} - -var firewallGroupCreate = &cobra.Command{ - Use: "create", - Short: "create a firewall group", - Aliases: []string{"c"}, - Run: func(cmd *cobra.Command, args []string) { - description, _ := cmd.Flags().GetString("description") - options := &govultr.FirewallGroupReq{ - Description: description, - } - - fwg, err := client.FirewallGroup.Create(context.Background(), options) - if err != nil { - fmt.Printf("%v\n", err) - os.Exit(1) - } - - printer.FirewallGroup(fwg) - }, -} - -var firewallGroupDelete = &cobra.Command{ - Use: "delete ", - Short: "Delete a firewall group", - Aliases: []string{"d", "destroy"}, - Args: func(cmd *cobra.Command, args []string) error { - if len(args) < 1 { - return errors.New("please provide a firewallGroupID") - } - return nil - }, - Run: func(cmd *cobra.Command, args []string) { - if err := client.FirewallGroup.Delete(context.Background(), args[0]); err != nil { - fmt.Printf("%v\n", err) - os.Exit(1) - } - - fmt.Println("Firewall group has been deleted") - }, -} - -var firewallGroupUpdate = &cobra.Command{ - Use: "update ", - Short: "Update firewall group description", - Aliases: []string{"u"}, - Args: func(cmd *cobra.Command, args []string) error { - if len(args) < 2 { - return errors.New("please provide a firewallGroupID and description") - } - return nil - }, - Run: func(cmd *cobra.Command, args []string) { - description := args[1] - options := &govultr.FirewallGroupReq{ - Description: description, - } - - if err := client.FirewallGroup.Update(context.Background(), args[0], options); err != nil { - fmt.Printf("%v\n", err) - os.Exit(1) - } - - fmt.Println("Firewall group has been updated") - }, -} - -var firewallGroupGet = &cobra.Command{ - Use: "get ", - Short: "Get firewall group", - Args: func(cmd *cobra.Command, args []string) error { - if len(args) < 1 { - return errors.New("please provide a firewallGroupID") - } - return nil - }, - Run: func(cmd *cobra.Command, args []string) { - options := getPaging(cmd) - list, meta, err := client.FirewallGroup.List(context.Background(), options) - if err != nil { - fmt.Printf("%v\n", err) - os.Exit(1) - } - - printer.FirewallGroups(list, meta) - }, -} - -var firewallGroupList = &cobra.Command{ - Use: "list", - Short: "List all firewall groups", - Aliases: []string{"l"}, - Run: func(cmd *cobra.Command, args []string) { - options := getPaging(cmd) - list, meta, err := client.FirewallGroup.List(context.Background(), options) - if err != nil { - fmt.Printf("%v\n", err) - os.Exit(1) - } - - printer.FirewallGroups(list, meta) - }, -} diff --git a/cmd/firewallRule.go b/cmd/firewallRule.go deleted file mode 100644 index ca9f1d52..00000000 --- a/cmd/firewallRule.go +++ /dev/null @@ -1,162 +0,0 @@ -// Copyright © 2019 The Vultr-cli Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package cmd - -import ( - "context" - "errors" - "fmt" - "os" - "strconv" - - "github.com/spf13/cobra" - "github.com/vultr/govultr/v2" - "github.com/vultr/vultr-cli/cmd/printer" -) - -// FirewallRule represents the firewall rule commands -func FirewallRule() *cobra.Command { - firewallRuleCmd := &cobra.Command{ - Use: "rule", - Short: "rule is used to access firewall rule commands", - Aliases: []string{"r"}, - } - - firewallRuleCmd.AddCommand(firewallRuleCreate, firewallRuleDelete, firewallRuleGet, firewallRuleList) - - firewallRuleCreate.Flags().StringP("id", "i", "", "ID of the target firewall group.") - firewallRuleCreate.Flags().StringP("protocol", "p", "", "Protocol type. Possible values: 'icmp', 'tcp', 'udp', 'gre'.") - firewallRuleCreate.Flags().StringP("subnet", "s", "", "The IPv4 network in CIDR notation.") - firewallRuleCreate.Flags().IntP("size", "z", 0, "The number of bits for the netmask in CIDR notation.") - firewallRuleCreate.Flags().IntP("source", "o", 0, "(optional) When empty, uses value from subnet and size. If \"cloudflare\", allows all Cloudflare IP space through firewall.") - firewallRuleCreate.Flags().StringP("type", "t", "", "The type of IP rule - v4 or v6.") - - firewallRuleCreate.Flags().StringP("port", "r", "", "(optional) TCP/UDP only. This field can be an integer value specifying a port or a colon separated port range.") - firewallRuleCreate.Flags().StringP("notes", "n", "", "(optional) This field supports notes up to 255 characters.") - - firewallRuleCreate.MarkFlagRequired("id") - firewallRuleCreate.MarkFlagRequired("protocol") - firewallRuleCreate.MarkFlagRequired("subnet") - firewallRuleCreate.MarkFlagRequired("size") - firewallRuleCreate.MarkFlagRequired("type") - - firewallRuleList.Flags().StringP("cursor", "c", "", "(optional) Cursor for paging.") - firewallRuleList.Flags().IntP("per-page", "p", 100, "(optional) Number of items requested per page. Default is 100 and Max is 500.") - - return firewallRuleCmd -} - -var firewallRuleCreate = &cobra.Command{ - Use: "create", - Short: "create a firewall rule", - Aliases: []string{"c"}, - Run: func(cmd *cobra.Command, args []string) { - id, _ := cmd.Flags().GetString("id") - protocol, _ := cmd.Flags().GetString("protocol") - subnet, _ := cmd.Flags().GetString("subnet") - ipType, _ := cmd.Flags().GetString("type") - size, _ := cmd.Flags().GetInt("size") - source, _ := cmd.Flags().GetString("source") - port, _ := cmd.Flags().GetString("port") - notes, _ := cmd.Flags().GetString("notes") - - options := &govultr.FirewallRuleReq{ - Protocol: protocol, - IPType: ipType, - Subnet: subnet, - SubnetSize: size, - Notes: notes, - } - - if port != "" { - options.Port = port - } - - if source != "" { - options.Source = source - } - - fwr, err := client.FirewallRule.Create(context.Background(), id, options) - if err != nil { - fmt.Printf("%v\n", err) - os.Exit(1) - } - - printer.FirewallRule(fwr) - }, -} - -var firewallRuleDelete = &cobra.Command{ - Use: "delete ", - Short: "Delete a firewall rule", - Aliases: []string{"d", "destroy"}, - Args: func(cmd *cobra.Command, args []string) error { - if len(args) < 2 { - return errors.New("please provide a firewallGroupID and firewallRuleNumber") - } - return nil - }, - Run: func(cmd *cobra.Command, args []string) { - rule, _ := strconv.Atoi(args[1]) - if err := client.FirewallRule.Delete(context.Background(), args[0], rule); err != nil { - fmt.Printf("%v\n", err) - os.Exit(1) - } - fmt.Println("Firewall rule has been deleted") - }, -} - -var firewallRuleGet = &cobra.Command{ - Use: "get ", - Short: "Get firewall rule", - Args: func(cmd *cobra.Command, args []string) error { - if len(args) < 2 { - return errors.New("please provide a firewallGroupID and firewallRuleNumber") - } - return nil - }, - Run: func(cmd *cobra.Command, args []string) { - ruleNumber, _ := strconv.Atoi(args[1]) - fwRule, err := client.FirewallRule.Get(context.Background(), args[0], ruleNumber) - if err != nil { - fmt.Printf("%v\n", err) - os.Exit(1) - } - - printer.FirewallRule(fwRule) - }, -} - -var firewallRuleList = &cobra.Command{ - Use: "list ", - Short: "List all firewall rules", - Aliases: []string{"l"}, - Args: func(cmd *cobra.Command, args []string) error { - if len(args) < 1 { - return errors.New("please provide a firewallGroupID") - } - return nil - }, - Run: func(cmd *cobra.Command, args []string) { - options := getPaging(cmd) - list, meta, err := client.FirewallRule.List(context.Background(), args[0], options) - if err != nil { - fmt.Printf("%v\n", err) - os.Exit(1) - } - - printer.FirewallRules(list, meta) - }, -} diff --git a/cmd/instance.go b/cmd/instance.go deleted file mode 100644 index b82a5494..00000000 --- a/cmd/instance.go +++ /dev/null @@ -1,1153 +0,0 @@ -// Copyright © 2019 The Vultr-cli Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package cmd - -import ( - "context" - "encoding/base64" - "errors" - "fmt" - "io/ioutil" - "os" - - "github.com/spf13/cobra" - "github.com/vultr/govultr/v2" - "github.com/vultr/vultr-cli/cmd/printer" -) - -// Instance represents the instance command -func Instance() *cobra.Command { - instanceCmd := &cobra.Command{ - Use: "instance", - Short: "commands to interact with instances on vultr", - Long: ``, - } - - instanceCmd.AddCommand(instanceStart, instanceStop, instanceRestart, instanceReinstall, instanceTag, instanceDelete, instanceLabel, instanceBandwidth, instanceList, instanceInfo, updateFwgGroup, instanceRestore, instanceCreate) - - instanceTag.Flags().StringP("tag", "t", "", "tag you want to set for a given instance") - instanceTag.MarkFlagRequired("tag") - - instanceLabel.Flags().StringP("label", "l", "", "label you want to set for a given instance") - instanceLabel.MarkFlagRequired("label") - - updateFwgGroup.Flags().StringP("instance-id", "i", "", "instance id of the instance you want to use") - updateFwgGroup.Flags().StringP("firewall-group-id", "f", "", "firewall group id that you want to assign. 0 Value will unset the firewall-group") - updateFwgGroup.MarkFlagRequired("instance-id") - updateFwgGroup.MarkFlagRequired("firewall-group-id") - - instanceRestore.Flags().StringP("backup", "b", "", "id of backup you wish to restore the instance with") - instanceRestore.Flags().StringP("snapshot", "s", "", "id of snapshot you wish to restore the instance with") - - instanceCreate.Flags().StringP("region", "r", "", "region id you wish to have the instance created in") - instanceCreate.Flags().StringP("plan", "p", "", "plan id you wish the instance to have") - instanceCreate.Flags().IntP("operatingSystems", "o", 0, "operatingSystems id you wish the instance to have") - instanceCreate.MarkFlagRequired("region") - instanceCreate.MarkFlagRequired("plan") - - // Optional Params - instanceCreate.Flags().StringP("ipxe", "", "", "if you've selected the 'custom' operating system, this can be set to chainload the specified URL on bootup") - instanceCreate.Flags().StringP("iso", "", "", "iso ID you want to create the instance with") - instanceCreate.Flags().StringP("snapshot", "", "", "snapshot ID you want to create the instance with") - instanceCreate.Flags().StringP("script-id", "", "", "script id of the startup script") - instanceCreate.Flags().BoolP("ipv6", "", false, "enable ipv6 | true or false") - instanceCreate.Flags().BoolP("private-network", "", false, "enable private network | true or false") - instanceCreate.Flags().StringArrayP("network", "", []string{}, "network IDs you want to assign to the instance") - instanceCreate.Flags().StringP("label", "l", "", "label you want to give this instance") - instanceCreate.Flags().StringArrayP("ssh-keys", "s", []string{}, "ssh keys you want to assign to the instance") - instanceCreate.Flags().BoolP("auto-backup", "b", false, "enable auto backups | true or false") - instanceCreate.Flags().IntP("app", "a", 0, "application ID you want this instance to have") - instanceCreate.Flags().StringP("userdata", "u", "", "plain text userdata you want to give this instance which the CLI will base64 encode") - instanceCreate.Flags().BoolP("notify", "n", true, "notify when instance has been created | true or false") - instanceCreate.Flags().BoolP("ddos", "d", false, "enable ddos protection | true or false") - instanceCreate.Flags().StringP("reserved-ipv4", "", "", "ip address of the floating IP to use as the main IP for this instance") - instanceCreate.Flags().StringP("host", "", "", "The hostname to assign to this instance") - instanceCreate.Flags().StringP("tag", "t", "", "The tag to assign to this instance") - instanceCreate.Flags().StringP("firewall-group", "", "", "The firewall group to assign to this instance") - - instanceList.Flags().StringP("cursor", "c", "", "(optional) Cursor for paging.") - instanceList.Flags().IntP("per-page", "p", 100, "(optional) Number of items requested per page. Default is 100 and Max is 500.") - - instanceIPV4List.Flags().StringP("cursor", "c", "", "(optional) Cursor for paging.") - instanceIPV4List.Flags().IntP("per-page", "p", 100, "(optional) Number of items requested per page. Default is 100 and Max is 500.") - - instanceIPV6List.Flags().StringP("cursor", "c", "", "(optional) Cursor for paging.") - instanceIPV6List.Flags().IntP("per-page", "p", 100, "(optional) Number of items requested per page. Default is 100 and Max is 500.") - - // Sub commands for OS - osCmd := &cobra.Command{ - Use: "operatingSystems", - Short: "update operating system for an instance", - Long: ``, - } - - osCmd.AddCommand(osUpdate, osUpdateList) - osUpdate.Flags().IntP("operatingSystems", "o", 0, "operating system ID you wish to use") - osUpdate.MarkFlagRequired("operatingSystems") - instanceCmd.AddCommand(osCmd) - - // Sub commands for App - appCMD := &cobra.Command{ - Use: "app", - Short: "update application for an instance", - Long: ``, - } - appCMD.AddCommand(appUpdate, appUpdateList) - appUpdate.Flags().IntP("app", "a", 0, "application ID you wish to use") - appUpdate.MarkFlagRequired("app") - instanceCmd.AddCommand(appCMD) - - // Sub commands for Backup - backupCMD := &cobra.Command{ - Use: "backup", - Short: "list and create backup schedules for an instance", - Long: ``, - } - backupCMD.AddCommand(backupGet, backupCreate) - backupCreate.Flags().StringP("type", "t", "", "type string Backup cron type. Can be one of 'daily', 'weekly', 'monthly', 'daily_alt_even', or 'daily_alt_odd'.") - backupCreate.MarkFlagRequired("type") - backupCreate.Flags().IntP("hour", "o", 0, "Hour value (0-23). Applicable to crons: 'daily', 'weekly', 'monthly', 'daily_alt_even', 'daily_alt_odd'") - backupCreate.Flags().IntP("dow", "w", 0, "Day-of-week value (0-6). Applicable to crons: 'weekly'") - backupCreate.Flags().IntP("dom", "m", 0, "Day-of-month value (1-28). Applicable to crons: 'monthly'") - instanceCmd.AddCommand(backupCMD) - - // IPV4 Subcommands - isoCmd := &cobra.Command{ - Use: "iso", - Short: "attach/detach ISOs to a given instance", - Long: ``, - } - isoCmd.AddCommand(isoStatus, isoAttach, isoDetach) - isoAttach.Flags().StringP("iso-id", "i", "", "id of the ISO you wish to attach") - isoAttach.MarkFlagRequired("iso-id") - instanceCmd.AddCommand(isoCmd) - - ipv4Cmd := &cobra.Command{ - Use: "ipv4", - Short: "list/create/delete ipv4 on instance", - Long: ``, - } - ipv4Cmd.AddCommand(instanceIPV4List, createIpv4, deleteIpv4) - createIpv4.Flags().Bool("reboot", false, "whether to reboot instance after adding ipv4 address") - deleteIpv4.Flags().StringP("ipv4", "i", "", "ipv4 address you wish to delete") - deleteIpv4.MarkFlagRequired("ipv4") - instanceCmd.AddCommand(ipv4Cmd) - - // IPV6 Subcommands - ipv6Cmd := &cobra.Command{ - Use: "ipv6", - Short: "commands for ipv6 on instance", - Long: ``, - } - ipv6Cmd.AddCommand(instanceIPV6List) - instanceCmd.AddCommand(ipv6Cmd) - - // Plans SubCommands - plansCmd := &cobra.Command{ - Use: "plan", - Short: "update/list plans for an instance", - Long: ``, - } - plansCmd.AddCommand(upgradePlan, upgradePlanList) - upgradePlan.Flags().StringP("plan", "p", "", "plan id that you wish to upgrade to") - upgradePlan.MarkFlagRequired("plan") - instanceCmd.AddCommand(plansCmd) - - // ReverseDNS SubCommands - reverseCmd := &cobra.Command{ - Use: "reverse-dns", - Short: "commands to handle reverse-dns on an instance", - Long: ``, - } - reverseCmd.AddCommand(defaultIpv4, listIpv6, deleteIpv6, setIpv4, setIpv6) - defaultIpv4.Flags().StringP("ip", "i", "", "iPv4 address used in the reverse DNS update") - defaultIpv4.MarkFlagRequired("ip") - deleteIpv6.Flags().StringP("ip", "i", "", "ipv6 address you wish to delete") - - defaultIpv4.MarkFlagRequired("ip") - setIpv4.Flags().StringP("ip", "i", "", "ip address you wish to set a reverse DNS on") - setIpv4.Flags().StringP("entry", "e", "", "reverse dns entry") - setIpv4.MarkFlagRequired("ip") - setIpv4.MarkFlagRequired("entry") - - setIpv6.Flags().StringP("ip", "i", "", "ip address you wish to set a reverse DNS on") - setIpv6.Flags().StringP("entry", "e", "", "reverse dns entry") - setIpv6.MarkFlagRequired("ip") - setIpv6.MarkFlagRequired("entry") - instanceCmd.AddCommand(reverseCmd) - - userdataCmd := &cobra.Command{ - Use: "user-data", - Short: "commands to handle userdata on an instance", - Long: ``, - } - userdataCmd.AddCommand(setUserData, getUserData) - setUserData.Flags().StringP("userdata", "d", "/dev/stdin", "file to read userdata from") - instanceCmd.AddCommand(userdataCmd) - - return instanceCmd -} - -var instanceStart = &cobra.Command{ - Use: "start ", - Short: "starts an instance", - Long: ``, - Args: func(cmd *cobra.Command, args []string) error { - if len(args) < 1 { - return errors.New("please provide an instanceID") - } - return nil - }, - Run: func(cmd *cobra.Command, args []string) { - id := args[0] - if err := client.Instance.Start(context.Background(), id); err != nil { - fmt.Printf("error starting instance : %v\n", err) - os.Exit(1) - } - - fmt.Println("Started up instance") - }, -} - -var instanceStop = &cobra.Command{ - Use: "stop ", - Short: "stops an instance", - Long: ``, - Args: func(cmd *cobra.Command, args []string) error { - if len(args) < 1 { - return errors.New("please provide an instanceID") - } - return nil - }, - Run: func(cmd *cobra.Command, args []string) { - id := args[0] - if err := client.Instance.Halt(context.Background(), id); err != nil { - fmt.Printf("error stopping instance : %v\n", err) - os.Exit(1) - } - - fmt.Println("Stopped the instance") - }, -} - -var instanceRestart = &cobra.Command{ - Use: "restart ", - Short: "restart an instance", - Long: ``, - Args: func(cmd *cobra.Command, args []string) error { - if len(args) < 1 { - return errors.New("please provide an instanceID") - } - return nil - }, - Run: func(cmd *cobra.Command, args []string) { - id := args[0] - if err := client.Instance.Reboot(context.Background(), id); err != nil { - fmt.Printf("error rebooting instance : %v\n", err) - os.Exit(1) - } - - fmt.Println("Rebooted instance") - }, -} - -var instanceReinstall = &cobra.Command{ - Use: "reinstall ", - Short: "reinstall an instance", - Long: ``, - Args: func(cmd *cobra.Command, args []string) error { - if len(args) < 1 { - return errors.New("please provide an instanceID") - } - return nil - }, - Run: func(cmd *cobra.Command, args []string) { - id := args[0] - if err := client.Instance.Reinstall(context.Background(), id); err != nil { - fmt.Printf("error reinstalling instance : %v\n", err) - os.Exit(1) - } - - fmt.Println("Reinstalled instance") - }, -} - -var instanceTag = &cobra.Command{ - Use: "tag ", - Short: "add/modify tag on instance", - Long: ``, - Args: func(cmd *cobra.Command, args []string) error { - if len(args) < 1 { - return errors.New("please provide an instanceID") - } - return nil - }, - Run: func(cmd *cobra.Command, args []string) { - id := args[0] - tag, _ := cmd.Flags().GetString("tag") - options := &govultr.InstanceUpdateReq{ - Tag: tag, - } - - if err := client.Instance.Update(context.Background(), id, options); err != nil { - fmt.Printf("error adding tag to instance : %v\n", err) - os.Exit(1) - } - - fmt.Printf("Tagged instance with : %s\n", tag) - }, -} - -var instanceDelete = &cobra.Command{ - Use: "delete ", - Short: "delete/destroy an instance", - Aliases: []string{"destroy"}, - Long: ``, - Args: func(cmd *cobra.Command, args []string) error { - if len(args) < 1 { - return errors.New("please provide an instanceID") - } - return nil - }, - Run: func(cmd *cobra.Command, args []string) { - id := args[0] - if err := client.Instance.Delete(context.Background(), id); err != nil { - fmt.Printf("error deleting instance : %v\n", err) - os.Exit(1) - } - - fmt.Println("Deleted instance") - }, -} - -var instanceLabel = &cobra.Command{ - Use: "label ", - Short: "label an instance", - Long: ``, - Args: func(cmd *cobra.Command, args []string) error { - if len(args) < 1 { - return errors.New("please provide an instanceID") - } - return nil - }, - Run: func(cmd *cobra.Command, args []string) { - id := args[0] - label, _ := cmd.Flags().GetString("label") - options := &govultr.InstanceUpdateReq{ - Label: label, - } - - if err := client.Instance.Update(context.Background(), id, options); err != nil { - fmt.Printf("error labeling instance : %v\n", err) - os.Exit(1) - } - - fmt.Printf("Labeled instance with : %s\n", label) - }, -} - -var instanceBandwidth = &cobra.Command{ - Use: "bandwidth ", - Short: "bandwidth for instance", - Long: ``, - Args: func(cmd *cobra.Command, args []string) error { - if len(args) < 1 { - return errors.New("please provide an instanceID") - } - return nil - }, - Run: func(cmd *cobra.Command, args []string) { - id := args[0] - bw, err := client.Instance.GetBandwidth(context.Background(), id) - if err != nil { - fmt.Printf("error getting bandwidth for instance : %v\n", err) - os.Exit(1) - } - - printer.InstanceBandwidth(bw) - }, -} - -var instanceIPV4List = &cobra.Command{ - Use: "list ", - Aliases: []string{"v4"}, - Short: "list ipv4 for an instance", - Long: ``, - Args: func(cmd *cobra.Command, args []string) error { - if len(args) < 1 { - return errors.New("please provide an instanceID") - } - return nil - }, - Run: func(cmd *cobra.Command, args []string) { - id := args[0] - options := getPaging(cmd) - v4, meta, err := client.Instance.ListIPv4(context.Background(), id, options) - if err != nil { - fmt.Printf("error getting ipv4 info : %v\n", err) - os.Exit(1) - } - - printer.InstanceIPV4(v4, meta) - }, -} - -var instanceIPV6List = &cobra.Command{ - Use: "list ", - Aliases: []string{"v6"}, - Short: "list ipv6 for an instance", - Long: ``, - Args: func(cmd *cobra.Command, args []string) error { - if len(args) < 1 { - return errors.New("please provide an instanceID") - } - return nil - }, - Run: func(cmd *cobra.Command, args []string) { - id := args[0] - options := getPaging(cmd) - v6, meta, err := client.Instance.ListIPv6(context.TODO(), id, options) - if err != nil { - fmt.Printf("error getting ipv6 info : %v\n", err) - os.Exit(1) - } - - printer.InstanceIPV6(v6, meta) - }, -} - -var instanceList = &cobra.Command{ - Use: "list", - Aliases: []string{"l"}, - Short: "list all available instances", - Long: ``, - Run: func(cmd *cobra.Command, args []string) { - options := getPaging(cmd) - s, meta, err := client.Instance.List(context.TODO(), options) - if err != nil { - fmt.Printf("error getting list of instances : %v\n", err) - os.Exit(1) - } - - printer.InstanceList(s, meta) - }, -} - -var instanceInfo = &cobra.Command{ - Use: "get ", - Short: "get info about a specific instance", - Long: ``, - Args: func(cmd *cobra.Command, args []string) error { - if len(args) < 1 { - return errors.New("please provide an instanceID") - } - return nil - }, - Run: func(cmd *cobra.Command, args []string) { - id := args[0] - s, err := client.Instance.Get(context.TODO(), id) - if err != nil { - fmt.Printf("error getting instance : %v\n", err) - os.Exit(1) - } - - printer.Instance(s) - }, -} - -var updateFwgGroup = &cobra.Command{ - Use: "update-firewall-group", - Short: "assign a firewall group to instance", - Long: ``, - Run: func(cmd *cobra.Command, args []string) { - id, _ := cmd.Flags().GetString("instance-id") - fwgID, _ := cmd.Flags().GetString("firewall-group-id") - - options := &govultr.InstanceUpdateReq{ - FirewallGroupID: fwgID, - } - - if err := client.Instance.Update(context.TODO(), id, options); err != nil { - fmt.Printf("error setting firewall group : %v\n", err) - os.Exit(1) - } - - fmt.Println("Updated firewall group") - }, -} - -var osUpdate = &cobra.Command{ - Use: "change ", - Short: "changes operating system", - Long: ``, - Args: func(cmd *cobra.Command, args []string) error { - if len(args) < 1 { - return errors.New("please provide an instanceID") - } - return nil - }, - Run: func(cmd *cobra.Command, args []string) { - id := args[0] - osID, _ := cmd.Flags().GetInt("operatingSystems") - - options := &govultr.InstanceUpdateReq{ - OsID: osID, - } - - if err := client.Instance.Update(context.TODO(), id, options); err != nil { - fmt.Printf("error updating operatingSystems : %v\n", err) - os.Exit(1) - } - - fmt.Println("Updated OS") - }, -} - -var osUpdateList = &cobra.Command{ - Use: "list ", - Short: "available operating systems an instance can change to.", - Long: ``, - Args: func(cmd *cobra.Command, args []string) error { - if len(args) < 1 { - return errors.New("please provide an instanceID") - } - return nil - }, - Run: func(cmd *cobra.Command, args []string) { - id := args[0] - list, err := client.Instance.GetUpgrades(context.TODO(), id) - - if err != nil { - fmt.Printf("error listing available operatingSystems : %v\n", err) - os.Exit(1) - } - - printer.OsList(list.OS) - }, -} - -var appUpdate = &cobra.Command{ - Use: "change ", - Short: "changes application", - Long: ``, - Args: func(cmd *cobra.Command, args []string) error { - if len(args) < 1 { - return errors.New("please provide an instanceID") - } - return nil - }, - Run: func(cmd *cobra.Command, args []string) { - id := args[0] - appID, _ := cmd.Flags().GetInt("app") - - options := &govultr.InstanceUpdateReq{ - AppID: appID, - } - - if err := client.Instance.Update(context.TODO(), id, options); err != nil { - fmt.Printf("error updating application : %v\n", err) - os.Exit(1) - } - - fmt.Println("Updated Application") - }, -} - -var appUpdateList = &cobra.Command{ - Use: "list ", - Short: "available apps an instance can change to.", - Long: ``, - Args: func(cmd *cobra.Command, args []string) error { - if len(args) < 1 { - return errors.New("please provide an instanceID") - } - return nil - }, - Run: func(cmd *cobra.Command, args []string) { - id := args[0] - list, err := client.Instance.GetUpgrades(context.TODO(), id) - - if err != nil { - fmt.Printf("error listing available applications : %v\n", err) - os.Exit(1) - } - - printer.AppList(list.Applications) - }, -} - -var backupGet = &cobra.Command{ - Use: "get ", - Short: "get backup schedules on a given instance", - Long: ``, - Args: func(cmd *cobra.Command, args []string) error { - if len(args) < 1 { - return errors.New("please provide an instanceID") - } - return nil - }, - Run: func(cmd *cobra.Command, args []string) { - id := args[0] - info, err := client.Instance.GetBackupSchedule(context.TODO(), id) - if err != nil { - fmt.Printf("error getting application info : %v\n", err) - os.Exit(1) - } - - printer.BackupsGet(info) - }, -} - -var backupCreate = &cobra.Command{ - Use: "create ", - Short: "create backup schedule on a given instance", - Long: ``, - Args: func(cmd *cobra.Command, args []string) error { - if len(args) < 1 { - return errors.New("please provide an instanceID") - } - return nil - }, - Run: func(cmd *cobra.Command, args []string) { - id := args[0] - - crontType, _ := cmd.Flags().GetString("type") - hour, _ := cmd.Flags().GetInt("hour") - dow, _ := cmd.Flags().GetInt("dow") - dom, _ := cmd.Flags().GetInt("dom") - - backup := &govultr.BackupScheduleReq{ - Type: crontType, - Hour: hour, - Dow: dow, - Dom: dom, - } - - if err := client.Instance.SetBackupSchedule(context.TODO(), id, backup); err != nil { - fmt.Printf("error creating backup schedule : %v\n", err) - os.Exit(1) - } - - fmt.Println("Created backup schedule") - }, -} - -var isoStatus = &cobra.Command{ - Use: "status ", - Short: "current ISO state", - Long: ``, - Args: func(cmd *cobra.Command, args []string) error { - if len(args) < 1 { - return errors.New("please provide an instanceID") - } - return nil - }, - Run: func(cmd *cobra.Command, args []string) { - id := args[0] - info, err := client.Instance.ISOStatus(context.TODO(), id) - if err != nil { - fmt.Printf("error getting iso state info : %v\n", err) - os.Exit(1) - } - - printer.IsoStatus(info) - }, -} - -var isoAttach = &cobra.Command{ - Use: "attach ", - Short: "attach ISO to instance", - Long: ``, - Args: func(cmd *cobra.Command, args []string) error { - if len(args) < 1 { - return errors.New("please provide an instanceID") - } - return nil - }, - Run: func(cmd *cobra.Command, args []string) { - id := args[0] - iso, _ := cmd.Flags().GetString("iso-id") - if err := client.Instance.AttachISO(context.TODO(), id, iso); err != nil { - fmt.Printf("error attaching iso : %v\n", err) - os.Exit(1) - } - - fmt.Println("ISO has been attached") - }, -} - -var isoDetach = &cobra.Command{ - Use: "detach ", - Short: "detach ISO from instance", - Long: ``, - Args: func(cmd *cobra.Command, args []string) error { - if len(args) < 1 { - return errors.New("please provide an instanceID") - } - return nil - }, - Run: func(cmd *cobra.Command, args []string) { - id := args[0] - if err := client.Instance.DetachISO(context.TODO(), id); err != nil { - fmt.Printf("error detaching iso : %v\n", err) - os.Exit(1) - } - - fmt.Println("ISO has been detached") - }, -} - -var instanceRestore = &cobra.Command{ - Use: "restore ", - Short: "restore instance from backup/snapshot", - Long: ``, - Args: func(cmd *cobra.Command, args []string) error { - if len(args) < 1 { - return errors.New("please provide an instanceID") - } - return nil - }, - Run: func(cmd *cobra.Command, args []string) { - id := args[0] - - backup, _ := cmd.Flags().GetString("backup") - snapshot, _ := cmd.Flags().GetString("snapshot") - options := &govultr.RestoreReq{} - - if backup == "" && snapshot == "" { - fmt.Println("at least one flag must be provided (snapshot or backup)") - os.Exit(1) - } else if backup != "" && snapshot != "" { - fmt.Println("one flag must be provided not both (snapshot or backup)") - os.Exit(1) - } - - if snapshot != "" { - options.SnapshotID = snapshot - } else { - options.BackupID = backup - } - - if err := client.Instance.Restore(context.TODO(), id, options); err != nil { - fmt.Printf("error restoring instance : %v\n", err) - os.Exit(1) - } - - fmt.Println("Instance has been restored") - }, -} - -var createIpv4 = &cobra.Command{ - Use: "create ", - Short: "create ipv4 for instance", - Long: ``, - Args: func(cmd *cobra.Command, args []string) error { - if len(args) < 1 { - return errors.New("please provide an instanceID") - } - return nil - }, - Run: func(cmd *cobra.Command, args []string) { - id := args[0] - reboot, _ := cmd.Flags().GetBool("reboot") - - _, err := client.Instance.CreateIPv4(context.TODO(), id, govultr.BoolToBoolPtr(reboot)) - if err != nil { - fmt.Printf("error creating ipv4 : %v\n", err) - os.Exit(1) - } - - fmt.Println("IPV4 has been created") - }, -} - -var deleteIpv4 = &cobra.Command{ - Use: "delete ", - Short: "delete ipv4 for instance", - Aliases: []string{"destroy"}, - Long: ``, - Args: func(cmd *cobra.Command, args []string) error { - if len(args) < 1 { - return errors.New("please provide an instanceID") - } - return nil - }, - Run: func(cmd *cobra.Command, args []string) { - id := args[0] - ip, _ := cmd.Flags().GetString("ipv4") - - if err := client.Instance.DeleteIPv4(context.TODO(), id, ip); err != nil { - fmt.Printf("error deleting ipv4 : %v\n", err) - os.Exit(1) - } - - fmt.Println("IPV4 has been deleted") - }, -} - -var upgradePlan = &cobra.Command{ - Use: "upgrade ", - Short: "upgrade plan for instance", - Long: ``, - Args: func(cmd *cobra.Command, args []string) error { - if len(args) < 1 { - return errors.New("please provide an instanceID") - } - return nil - }, - Run: func(cmd *cobra.Command, args []string) { - id := args[0] - plan, _ := cmd.Flags().GetString("plan") - - options := &govultr.InstanceUpdateReq{ - Plan: plan, - } - - if err := client.Instance.Update(context.TODO(), id, options); err != nil { - fmt.Printf("error upgrading plans : %v\n", err) - os.Exit(1) - } - - fmt.Println("Upgraded plan") - }, -} - -var upgradePlanList = &cobra.Command{ - Use: "list ", - Short: "available plans an instance can upgrade to.", - Long: ``, - Args: func(cmd *cobra.Command, args []string) error { - if len(args) < 1 { - return errors.New("please provide an instanceID") - } - return nil - }, - Run: func(cmd *cobra.Command, args []string) { - id := args[0] - list, err := client.Instance.GetUpgrades(context.TODO(), id) - - if err != nil { - fmt.Printf("error listing available plans : %v\n", err) - os.Exit(1) - } - - printer.PlansList(list.Plans) - }, -} - -var defaultIpv4 = &cobra.Command{ - Use: "default-ipv4 ", - Short: "Set a reverse DNS entry for an IPv4 address of an instance to the original setting", - Long: ``, - Args: func(cmd *cobra.Command, args []string) error { - if len(args) < 1 { - return errors.New("please provide an instanceID") - } - return nil - }, - Run: func(cmd *cobra.Command, args []string) { - id := args[0] - ip, _ := cmd.Flags().GetString("ip") - - if err := client.Instance.DefaultReverseIPv4(context.TODO(), id, ip); err != nil { - fmt.Printf("error setting default reverse dns : %v\n", err) - os.Exit(1) - } - - fmt.Println("Set default reserve dns") - }, -} - -var listIpv6 = &cobra.Command{ - Use: "list-ipv6 ", - Short: "List the IPv6 reverse DNS entries for an instance", - Long: ``, - Args: func(cmd *cobra.Command, args []string) error { - if len(args) < 1 { - return errors.New("please provide an instanceID") - } - return nil - }, - Run: func(cmd *cobra.Command, args []string) { - id := args[0] - rip, err := client.Instance.ListReverseIPv6(context.TODO(), id) - if err != nil { - fmt.Printf("error getting the reverse ipv6 list: %v\n", err) - os.Exit(1) - } - printer.ReverseIpv6(rip) - }, -} - -var deleteIpv6 = &cobra.Command{ - Use: "delete-ipv6 ", - Short: "Remove a reverse DNS entry for an IPv6 address for an instance", - Aliases: []string{"destroy-ipv6"}, - Long: ``, - Args: func(cmd *cobra.Command, args []string) error { - if len(args) < 1 { - return errors.New("please provide an instanceID") - } - return nil - }, - Run: func(cmd *cobra.Command, args []string) { - id := args[0] - ip, _ := cmd.Flags().GetString("ip") - if err := client.Instance.DeleteReverseIPv6(context.TODO(), id, ip); err != nil { - fmt.Printf("error deleting reverse ipv6 entry : %v\n", err) - os.Exit(1) - } - - fmt.Println("Deleted reverse DNS IPV6 entry") - }, -} - -var setIpv4 = &cobra.Command{ - Use: "set-ipv4 ", - Short: "Set a reverse DNS entry for an IPv4 address for an instance", - Long: ``, - Args: func(cmd *cobra.Command, args []string) error { - if len(args) < 1 { - return errors.New("please provide an instanceID") - } - return nil - }, - Run: func(cmd *cobra.Command, args []string) { - id := args[0] - ip, _ := cmd.Flags().GetString("ip") - entry, _ := cmd.Flags().GetString("entry") - - options := &govultr.ReverseIP{ - IP: ip, - Reverse: entry, - } - - if err := client.Instance.CreateReverseIPv4(context.TODO(), id, options); err != nil { - fmt.Printf("error setting reverse dns ipv4 entry : %v\n", err) - os.Exit(1) - } - - fmt.Println("Set reverse DNS entry for ipv4 address") - }, -} - -var setIpv6 = &cobra.Command{ - Use: "set-ipv6 ", - Short: "Set a reverse DNS entry for an IPv6 address for an instance", - Long: ``, - Args: func(cmd *cobra.Command, args []string) error { - if len(args) < 1 { - return errors.New("please provide an instanceID") - } - return nil - }, - Run: func(cmd *cobra.Command, args []string) { - id := args[0] - ip, _ := cmd.Flags().GetString("ip") - entry, _ := cmd.Flags().GetString("entry") - - options := &govultr.ReverseIP{ - IP: ip, - Reverse: entry, - } - - if err := client.Instance.CreateReverseIPv6(context.TODO(), id, options); err != nil { - fmt.Printf("error setting reverse dns ipv6 entry : %v\n", err) - os.Exit(1) - } - fmt.Println("Set reverse DNS entry for ipv6 address") - }, -} - -var instanceCreate = &cobra.Command{ - Use: "create", - Short: "Create an instance", - Long: ``, - Run: func(cmd *cobra.Command, args []string) { - region, _ := cmd.Flags().GetString("region") - plan, _ := cmd.Flags().GetString("plan") - osID, _ := cmd.Flags().GetInt("operatingSystems") - - // Optional - ipxe, _ := cmd.Flags().GetString("ipxe") - iso, _ := cmd.Flags().GetString("iso") - snapshot, _ := cmd.Flags().GetString("snapshot") - script, _ := cmd.Flags().GetString("script-id") - ipv6, _ := cmd.Flags().GetBool("ipv6") - privateNetwork, _ := cmd.Flags().GetBool("private-network") - networks, _ := cmd.Flags().GetStringArray("network") - label, _ := cmd.Flags().GetString("label") - ssh, _ := cmd.Flags().GetStringArray("ssh-keys") - backup, _ := cmd.Flags().GetBool("auto-backup") - app, _ := cmd.Flags().GetInt("app") - userData, _ := cmd.Flags().GetString("userdata") - notify, _ := cmd.Flags().GetBool("notify") - ddos, _ := cmd.Flags().GetBool("ddos") - ipv4, _ := cmd.Flags().GetString("reserved-ipv4") - host, _ := cmd.Flags().GetString("host") - tag, _ := cmd.Flags().GetString("tag") - fwg, _ := cmd.Flags().GetString("firewall-group") - - osOptions := map[string]interface{}{"iso_id": iso, "os_id": osID, "app_id": app, "snapshot_id": snapshot} - - if iso != "" { - osOptions["iso_id"] = iso - } - - osOption, err := optionCheck(osOptions) - if err != nil { - fmt.Printf("error creating instance : %v\n", err) - os.Exit(1) - } - - opt := &govultr.InstanceCreateReq{ - Plan: plan, - Region: region, - IPXEChainURL: ipxe, - ISOID: iso, - SnapshotID: snapshot, - ScriptID: script, - AttachPrivateNetwork: networks, - Label: label, - SSHKeys: ssh, - AppID: app, - UserData: userData, - ReservedIPv4: ipv4, - Hostname: host, - Tag: tag, - FirewallGroupID: fwg, - EnableIPv6: govultr.BoolToBoolPtr(false), - DDOSProtection: govultr.BoolToBoolPtr(false), - ActivationEmail: govultr.BoolToBoolPtr(false), - Backups: "disabled", - EnablePrivateNetwork: govultr.BoolToBoolPtr(false), - } - - // If no osOptions were selected and osID has a real value then set the osOptions to os_id - if osOption == "os_id" && osID != 0 { - opt.OsID = osID - } else if osOption == "" && osID == 0 { - fmt.Printf("error creating instance: an os_id, snapshot_id, iso_id, or app_id must be provided\n") - os.Exit(1) - } - - if ipv6 { - opt.EnableIPv6 = govultr.BoolToBoolPtr(true) - } - if ddos { - opt.DDOSProtection = govultr.BoolToBoolPtr(true) - } - if notify { - opt.ActivationEmail = govultr.BoolToBoolPtr(true) - } - if backup { - opt.Backups = "enabled" - } - if privateNetwork { - opt.EnablePrivateNetwork = govultr.BoolToBoolPtr(true) - } - - if userData != "" { - opt.UserData = base64.StdEncoding.EncodeToString([]byte(userData)) - } - - //region, plan, osOpt, opt - instance, err := client.Instance.Create(context.TODO(), opt) - if err != nil { - fmt.Printf("error creating instance : %v\n", err) - os.Exit(1) - } - - printer.Instance(instance) - }, -} - -var setUserData = &cobra.Command{ - Use: "set ", - Short: "Set the plain text user-data of an instance", - Args: func(cmd *cobra.Command, args []string) error { - if len(args) < 1 { - return errors.New("please provide an instanceID") - } - return nil - }, - Run: func(cmd *cobra.Command, args []string) { - userData, _ := cmd.Flags().GetString("userdata") - - rawData, err := ioutil.ReadFile(userData) - if err != nil { - fmt.Printf("error reading user-data : %v\n", err) - os.Exit(1) - } - - options := &govultr.InstanceUpdateReq{ - UserData: base64.StdEncoding.EncodeToString(rawData), - } - - if err = client.Instance.Update(context.TODO(), args[0], options); err != nil { - fmt.Printf("error setting user-data : %v\n", err) - os.Exit(1) - } - - fmt.Println("Set user-data for instance") - }, -} - -var getUserData = &cobra.Command{ - Use: "get ", - Short: "Get the user-data of an instance", - Args: func(cmd *cobra.Command, args []string) error { - if len(args) < 1 { - return errors.New("please provide an instanceID") - } - return nil - }, - Run: func(cmd *cobra.Command, args []string) { - userData, err := client.Instance.GetUserData(context.TODO(), args[0]) - if err != nil { - fmt.Printf("error getting user-data : %v\n", err) - os.Exit(1) - } - - printer.UserData(userData) - }, -} - -func optionCheck(options map[string]interface{}) (string, error) { - var result []string - for k, v := range options { - switch v.(type) { - case int: - if v != 0 { - result = append(result, k) - } - case string: - if v != "" { - result = append(result, k) - } - } - } - - if len(result) > 1 { - return "", fmt.Errorf("too many options have been selected : %v : please select one", result) - } - - // Return back an empty slice so we can possibly add in osID - if len(result) == 0 { - return "", nil - } - - return result[0], nil -} diff --git a/cmd/instance/instance.go b/cmd/instance/instance.go new file mode 100644 index 00000000..e06fff9f --- /dev/null +++ b/cmd/instance/instance.go @@ -0,0 +1,1777 @@ +// Package instance provides the command for the CLI to control instances +package instance + +import ( + "encoding/base64" + "errors" + "fmt" + "os" + "path/filepath" + + "github.com/spf13/cobra" + "github.com/vultr/govultr/v3" + "github.com/vultr/vultr-cli/v3/cmd/ip" + "github.com/vultr/vultr-cli/v3/cmd/printer" + "github.com/vultr/vultr-cli/v3/cmd/userdata" + "github.com/vultr/vultr-cli/v3/cmd/utils" + "github.com/vultr/vultr-cli/v3/pkg/cli" +) + +var ( + long = `Get commands available to instance` + example = ` + # Full example + vultr-cli instance + ` + listLong = `` + listExample = `` + getLong = `` + getExample = `` + createLong = `Create a new instance with specified plan, region and os (from image, snapshot, app or ISO)` + createExample = ` + # Full example + vultr-cli instance create --region="ewr" --plan="vc2-2c-4gb" --os=1743 + + You must pass one of these in addition to the required --region and --plan flags: + --os + --snapshot + --iso + --app + --image + + # Shortened example with aliases + vultr-cli instance c -r="ewr" -p="vc2-2c-4gb" -o=1743 + + # Full example with attached VPCs + vultr-cli instance create --region="ewr" --plan="vc2-2c-4gb" --os=1743 \ + --vpc-ids="08422775-5be0-4371-afba-64b03f9ad22d,13a45caa-9c06-4b5d-8f76-f5281ab172b7" + + # Full example with assigned ssh keys + vultr-cli instance create --region="ewr" --plan="vc2-2c-4gb" --os=1743 \ + --ssh-keys="a14b6539-5583-41e8-a035-c07a76897f2b,be624232-56c7-4d5c-bf87-9bdaae7a1fbd" + ` + deleteLong = `` + deleteExample = `` + tagsLong = `Modify the tags of the specified instance` + tagsExample = ` + # Full example + vultr-cli instance tags --tags="example-tag-1,example-tag-2" + + # Shortened example with aliases + vultr-cli instance tags -t="example-tag-1,example-tag-2" + ` + + userDataSetLong = `` + userDataSetExample = `` + userDataGetLong = `` + userDataGetExample = `` + vpcAttachLong = `Attaches an existing VPC to the specified instance` + vpcAttachExample = ` + # Full example + vultr-cli instance vpc attach --vpc-id="2126b7d9-5e2a-491e-8840-838aa6b5f294" + ` + vpcDetachLong = `Detaches an existing VPC from the specified instance` + vpcDetachExample = ` + # Full example + vultr-cli instance vpc detach --vpc-id="2126b7d9-5e2a-491e-8840-838aa6b5f294" + ` + + vpc2AttachLong = `Attaches an existing VPC 2.0 network to the specified instance` + vpc2AttachExample = ` + # Full example + vultr-cli instance vpc2 attach --vpc-id="2126b7d9-5e2a-491e-8840-838aa6b5f294" + ` + vpc2DetachLong = `Detaches an existing VPC 2.0 network from the specified instance` + vpc2DetachExample = ` + # Full example + vultr-cli instance vpc2 detach --vpc-id="2126b7d9-5e2a-491e-8840-838aa6b5f294" + ` +) + +// NewCmdInstance ... +func NewCmdInstance(base *cli.Base) *cobra.Command { //nolint:funlen,gocyclo + o := &options{Base: base} + + cmd := &cobra.Command{ + Use: "instance", + Short: "commands to interact with instances on vultr", + Long: long, + Example: example, + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + utils.SetOptions(o.Base, cmd, args) + if !o.Base.HasAuth { + return errors.New(utils.APIKeyError) + } + return nil + }, + } + + // List + list := &cobra.Command{ + Use: "list", + Aliases: []string{"l"}, + Short: "List all instances", + Long: listLong, + Example: listExample, + RunE: func(cmd *cobra.Command, args []string) error { + o.Base.Options = utils.GetPaging(cmd) + + instances, meta, err := o.list() + if err != nil { + return fmt.Errorf("error getting instance list : %v", err) + } + + data := &InstancesPrinter{Instances: instances, Meta: meta} + o.Base.Printer.Display(data, nil) + + return nil + }, + } + + list.Flags().StringP("cursor", "c", "", "(optional) cursor for paging.") + list.Flags().IntP( + "per-page", + "p", + utils.PerPageDefault, + fmt.Sprintf("(optional) Number of items requested per page. Default is %d and Max is 500.", utils.PerPageDefault), + ) + + // Get + get := &cobra.Command{ + Use: "get ", + Short: "Get info on an instance", + Long: getLong, + Example: getExample, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("please provide an instance ID") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + instance, err := o.get() + if err != nil { + return fmt.Errorf("error getting instance : %v", err) + } + + data := &InstancePrinter{Instance: instance} + o.Base.Printer.Display(data, nil) + + return nil + }, + } + + // Create + create := &cobra.Command{ + Use: "create", + Short: "Create an instance", + Aliases: []string{"c"}, + Long: createLong, + Example: createExample, + RunE: func(cmd *cobra.Command, args []string) error { + region, errRe := cmd.Flags().GetString("region") + if errRe != nil { + return fmt.Errorf("error parsing flag 'region' for instance create : %v", errRe) + } + + plan, errPl := cmd.Flags().GetString("plan") + if errPl != nil { + return fmt.Errorf("error parsing flag 'plan' for instance create : %v", errPl) + } + + osID, errOs := cmd.Flags().GetInt("os") + if errOs != nil { + return fmt.Errorf("error parsing flag 'os' for instance create : %v", errOs) + } + + ipxe, errIP := cmd.Flags().GetString("ipxe") + if errIP != nil { + return fmt.Errorf("error parsing flag 'ipxe' for instance create : %v", errIP) + } + + iso, errIs := cmd.Flags().GetString("iso") + if errIs != nil { + return fmt.Errorf("error parsing flag 'iso' for instance create : %v", errIs) + } + + snapshot, errSn := cmd.Flags().GetString("snapshot") + if errSn != nil { + return fmt.Errorf("error parsing flag 'snapshot' for instance create : %v", errSn) + } + + script, errSc := cmd.Flags().GetString("script-id") + if errSc != nil { + return fmt.Errorf("error parsing flag 'script' for instance create : %v", errSc) + } + + ipv6, errIv := cmd.Flags().GetBool("ipv6") + if errIv != nil { + return fmt.Errorf("error parsing flag 'ipv6' for instance create : %v", errIv) + } + + vpcEnable, errVp := cmd.Flags().GetBool("vpc-enable") + if errVp != nil { + return fmt.Errorf("error parsing flag 'vpc-enable' for instance create : %v", errVp) + } + + vpcAttach, errVp := cmd.Flags().GetStringSlice("vpc-ids") + if errVp != nil { + return fmt.Errorf("error parsing flag 'vpc-ids' for instance create : %v", errVp) + } + + label, errLa := cmd.Flags().GetString("label") + if errLa != nil { + return fmt.Errorf("error parsing flag 'label' for instance create : %v", errLa) + } + + ssh, errSs := cmd.Flags().GetStringSlice("ssh-keys") + if errSs != nil { + return fmt.Errorf("error parsing flag 'ssh-keys' for instance create : %v", errSs) + } + + backup, errBa := cmd.Flags().GetBool("auto-backup") + if errBa != nil { + return fmt.Errorf("error parsing flag 'auto-backup' for instance create : %v", errBa) + } + + app, errAp := cmd.Flags().GetInt("app") + if errAp != nil { + return fmt.Errorf("error parsing flag 'app' for instance create : %v", errAp) + } + + image, errIm := cmd.Flags().GetString("image") + if errIm != nil { + return fmt.Errorf("error parsing flag 'image' for instance create : %v", errIm) + } + + userData, errUs := cmd.Flags().GetString("userdata") + if errUs != nil { + return fmt.Errorf("error parsing flag 'userData' for instance create : %v", errUs) + } + + notify, errNo := cmd.Flags().GetBool("notify") + if errNo != nil { + return fmt.Errorf("error parsing flag 'notify' for instance create : %v", errNo) + } + + ddos, errDd := cmd.Flags().GetBool("ddos") + if errDd != nil { + return fmt.Errorf("error parsing flag 'ddos' for instance create : %v", errDd) + } + + ipv4, errIi := cmd.Flags().GetString("reserved-ipv4") + if errIi != nil { + return fmt.Errorf("error parsing flag 'reserved-ipv4' for instance create : %v", errIi) + } + + host, errHo := cmd.Flags().GetString("host") + if errHo != nil { + return fmt.Errorf("error parsing flag 'host' for instance create : %v", errHo) + } + + tags, errTa := cmd.Flags().GetStringSlice("tags") + if errTa != nil { + return fmt.Errorf("error parsing flag 'tags' for instance create : %v", errTa) + } + + fwg, errFw := cmd.Flags().GetString("firewall-group") + if errFw != nil { + return fmt.Errorf("error parsing flag 'firewall-group' for instance create : %v", errFw) + } + + o.CreateReq = &govultr.InstanceCreateReq{ + Plan: plan, + Region: region, + OsID: osID, + ISOID: iso, + SnapshotID: snapshot, + AppID: app, + ImageID: image, + IPXEChainURL: ipxe, + ScriptID: script, + Label: label, + SSHKeys: ssh, + UserData: userData, + ReservedIPv4: ipv4, + Hostname: host, + Tags: tags, + FirewallGroupID: fwg, + EnableIPv6: govultr.BoolToBoolPtr(ipv6), + DDOSProtection: govultr.BoolToBoolPtr(ddos), + ActivationEmail: govultr.BoolToBoolPtr(notify), + Backups: "disabled", + EnableVPC: govultr.BoolToBoolPtr(vpcEnable), + AttachVPC: vpcAttach, + } + + if backup { + o.CreateReq.Backups = "enabled" + } + + if userData != "" { + o.CreateReq.UserData = base64.StdEncoding.EncodeToString([]byte(userData)) + } + + instance, err := o.create() + if err != nil { + return fmt.Errorf("error creating instance : %v", err) + } + + data := &InstancePrinter{Instance: instance} + o.Base.Printer.Display(data, nil) + + return nil + }, + } + + create.Flags().StringP("region", "r", "", "The ID of the region in which to create the instance") + if err := create.MarkFlagRequired("region"); err != nil { + fmt.Printf("error marking instance create 'region' flag required: %v", err) + os.Exit(1) + } + + create.Flags().StringP("plan", "p", "", "The plan ID with which to create the instance") + if err := create.MarkFlagRequired("plan"); err != nil { + fmt.Printf("error marking instance create 'plan' flag required: %v", err) + os.Exit(1) + } + + create.Flags().IntP("os", "", 0, "os id you wish the instance to have") + create.Flags().StringP("iso", "", "", "iso ID you want to create the instance with") + create.Flags().StringP("snapshot", "", "", "snapshot ID you want to create the instance with") + create.Flags().IntP("app", "a", 0, "application ID you want this instance to have") + create.Flags().StringP("image", "", "", "image ID of the application that will be installed on the server.") + create.MarkFlagsMutuallyExclusive("os", "iso", "snapshot", "app", "image") + create.MarkFlagsOneRequired("os", "iso", "snapshot", "app", "image") + + create.Flags().StringP( + "ipxe", + "", + "", + "if you've selected the 'custom' operating system, this can be set to chainload the specified URL on bootup", + ) + create.Flags().StringP("script-id", "", "", "script id of the startup script") + create.Flags().BoolP("ipv6", "", false, "enable ipv6 | true or false") + create.Flags().BoolP("vpc-enable", "", false, "enable VPC | true or false") + create.Flags().StringSliceP("vpc-ids", "", []string{}, "VPC IDs you want to assign to the instance") + create.Flags().StringP("label", "l", "", "label you want to give this instance") + create.Flags().StringSliceP("ssh-keys", "s", []string{}, "ssh keys you want to assign to the instance") + create.Flags().BoolP("auto-backup", "b", false, "enable auto backups | true or false") + create.Flags().StringP("userdata", "u", "", "plain text userdata you want to give this instance which the CLI will base64 encode") + create.Flags().BoolP("notify", "n", false, "notify when instance has been created | true or false") + create.Flags().BoolP("ddos", "d", false, "enable ddos protection | true or false") + create.Flags().StringP("reserved-ipv4", "", "", "ID of the floating IP to use as the main IP for this instance") + create.Flags().StringP("host", "", "", "The hostname to assign to this instance") + create.Flags().StringSliceP("tags", "", []string{}, "A comma-separated list of tags to assign to this instance") + create.Flags().StringP("firewall-group", "", "", "The firewall group to assign to this instance") + + // Update + // update := &cobra.Command{} + + // Delete + del := &cobra.Command{ + Use: "delete ", + Short: "Delete an instance", + Aliases: []string{"destroy"}, + Long: deleteLong, + Example: deleteExample, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("please provide an instance ID") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + if err := o.del(); err != nil { + return fmt.Errorf("error deleting instance : %v", err) + } + + o.Base.Printer.Display(printer.Info("Instance has been deleted"), nil) + + return nil + }, + } + + // Label + label := &cobra.Command{ + Use: "label ", + Short: "Label an instance", + Long: ``, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("please provide an instance ID") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + label, errLa := cmd.Flags().GetString("label") + if errLa != nil { + return fmt.Errorf("error parsing flag 'label' for instance update : %v", errLa) + } + + o.UpdateReq = &govultr.InstanceUpdateReq{ + Label: label, + } + + instance, err := o.update() + if err != nil { + return fmt.Errorf("error updating instance label : %v", err) + } + + data := &InstancePrinter{Instance: instance} + o.Base.Printer.Display(data, nil) + + return nil + }, + } + + label.Flags().StringP("label", "l", "", "The label you want to set on an instance") + if err := label.MarkFlagRequired("label"); err != nil { + fmt.Printf("error marking instance label 'label' flag required: %v", err) + os.Exit(1) + } + + // Tags + tags := &cobra.Command{ + Use: "tags ", + Short: "Update tags on an instance", + Long: tagsLong, + Example: tagsExample, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("please provide an instance ID") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + tags, errTa := cmd.Flags().GetStringSlice("tags") + if errTa != nil { + return fmt.Errorf("error parsing flag 'tags' for instance update : %v", errTa) + } + + o.UpdateReq = &govultr.InstanceUpdateReq{ + Tags: tags, + } + + instance, err := o.update() + if err != nil { + return fmt.Errorf("error updating instance tags : %v", err) + } + + data := &InstancePrinter{Instance: instance} + o.Base.Printer.Display(data, nil) + + return nil + }, + } + + tags.Flags().StringSliceP("tags", "t", []string{}, "A comma separated list of tags to apply to the instance") + if err := tags.MarkFlagRequired("tags"); err != nil { + fmt.Printf("error marking instance tags 'tags' flag required: %v", err) + os.Exit(1) + } + + // User Data + userData := &cobra.Command{ + Use: "user-data", + Short: "Commands to handle user data on an instance", + } + + // User Data Get + userDataGet := &cobra.Command{ + Use: "get ", + Short: "Get the user data on an instance", + Long: userDataGetLong, + Example: userDataGetExample, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("please provide an instance ID") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + ud, err := o.userData() + if err != nil { + return fmt.Errorf("error getting instance user data : %v", err) + } + + data := &userdata.UserDataPrinter{UserData: *ud} + o.Base.Printer.Display(data, nil) + + return nil + }, + } + + // User Data Set + userDataSet := &cobra.Command{ + Use: "set ", + Short: "Update user-data on an instance", + Long: userDataSetLong, + Example: userDataSetExample, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("please provide an instance ID") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + userDataPath, errPa := cmd.Flags().GetString("userdata") + if errPa != nil { + return fmt.Errorf("error parsing flag 'userdata' for instance update : %v", errPa) + } + + userDataPath = filepath.Clean(userDataPath) + + rawData, errRe := os.ReadFile(userDataPath) + if errRe != nil { + return fmt.Errorf("error reading user-data : %v", errRe) + } + + o.UpdateReq = &govultr.InstanceUpdateReq{ + UserData: base64.StdEncoding.EncodeToString(rawData), + } + + _, err := o.update() + if err != nil { + return fmt.Errorf("error updating instance user data : %v", err) + } + + o.Base.Printer.Display(printer.Info("Instance user data has been updated"), nil) + + return nil + }, + } + + userDataSet.Flags().StringP("userdata", "d", "/dev/stdin", "The file to read userdata from") + + userData.AddCommand( + userDataGet, + userDataSet, + ) + + // Start + start := &cobra.Command{ + Use: "start ", + Short: "Start an instance", + Long: ``, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("please provide an instance ID") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + if err := o.start(); err != nil { + return fmt.Errorf("error starting instance : %v", err) + } + + o.Base.Printer.Display(printer.Info("Instance started"), nil) + + return nil + }, + } + + // Stop + stop := &cobra.Command{ + Use: "stop ", + Short: "Stop an instance", + Long: ``, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("please provide an instance ID") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + if err := o.stop(); err != nil { + return fmt.Errorf("error stopping instance : %v", err) + } + + o.Base.Printer.Display(printer.Info("Instance stopped"), nil) + + return nil + }, + } + + // Restart + restart := &cobra.Command{ + Use: "restart ", + Short: "Restart an instance", + Long: ``, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("please provide an instance ID") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + if err := o.restart(); err != nil { + return fmt.Errorf("error restarting instance : %v", err) + } + + o.Base.Printer.Display(printer.Info("Instance restarted"), nil) + + return nil + }, + } + + // ISO + iso := &cobra.Command{ + Use: "iso", + Short: "Manage ISOs on an instance", + } + + // ISO Status + isoStatus := &cobra.Command{ + Use: "status ", + Short: "Get ISO status", + Long: ``, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("please provide an instance ID") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + iso, err := o.isoStatus() + if err != nil { + return fmt.Errorf("error getting instance iso status : %v", err) + } + + data := &ISOPrinter{ISO: *iso} + o.Base.Printer.Display(data, nil) + + return nil + }, + } + + // ISO Attach + isoAttach := &cobra.Command{ + Use: "attach ", + Short: "Attach ISO to an instance", + Long: ``, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("please provide an instance ID") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + iso, errIs := cmd.Flags().GetString("iso-id") + if errIs != nil { + return fmt.Errorf("error parsing flag 'iso' for instance iso attach: %v", errIs) + } + + o.ISOAttachID = iso + + if err := o.isoAttach(); err != nil { + return fmt.Errorf("error attaching iso to instance : %v", err) + } + + o.Base.Printer.Display(printer.Info("ISO attached to instance"), nil) + + return nil + }, + } + + isoAttach.Flags().StringP("iso-id", "i", "", "id of the ISO you wish to attach") + if err := isoAttach.MarkFlagRequired("iso-id"); err != nil { + fmt.Printf("error marking instance iso attach 'iso-id' flag required: %v", err) + os.Exit(1) + } + + iso.AddCommand( + isoStatus, + isoAttach, + ) + + // Backup + backup := &cobra.Command{ + Use: "backup", + Short: "List and create backup schedules for an instance", + } + + // Backup Get + backupGet := &cobra.Command{ + Use: "get ", + Short: "Get the backup schedule for an instance", + Long: ``, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("please provide an instance ID") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + bk, err := o.backups() + if err != nil { + return fmt.Errorf("error getting instance backups : %v", err) + } + + data := &BackupPrinter{Backup: *bk} + o.Base.Printer.Display(data, nil) + + return nil + }, + } + + // Backup Create + backupCreate := &cobra.Command{ + Use: "create ", + Short: "Create a backup schedule for an instance", + Long: ``, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("please provide an instance ID") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + crontType, errCr := cmd.Flags().GetString("type") + if errCr != nil { + return fmt.Errorf("error parsing flag 'crontType' for instance backup create : %v", errCr) + } + + hour, errHo := cmd.Flags().GetInt("hour") + if errHo != nil { + return fmt.Errorf("error parsing flag 'hour' for instance backup create : %v", errHo) + } + + dow, errDo := cmd.Flags().GetInt("dow") + if errDo != nil { + return fmt.Errorf("error parsing flag 'dow' for instance backup create : %v", errDo) + } + + dom, errDo := cmd.Flags().GetInt("dom") + if errDo != nil { + return fmt.Errorf("error parsing flag 'dom' for instance backup create : %v", errDo) + } + + o.BackupCreateReq = &govultr.BackupScheduleReq{ + Type: crontType, + Hour: govultr.IntToIntPtr(hour), + Dow: govultr.IntToIntPtr(dow), + Dom: dom, + } + + if err := o.backupCreate(); err != nil { + return fmt.Errorf("error getting instance backups : %v", err) + } + + o.Base.Printer.Display(printer.Info("Instance backup created"), nil) + + return nil + }, + } + + backupCreate.Flags().StringP( + "type", + "t", + "", + "type string Backup cron type. Can be one of 'daily', 'weekly', 'monthly', 'daily_alt_even', or 'daily_alt_odd'.", + ) + if err := backupCreate.MarkFlagRequired("type"); err != nil { + fmt.Printf("error marking instance backup create 'type' flag required: %v", err) + os.Exit(1) + } + backupCreate.Flags().IntP( + "hour", + "", + 0, + "Hour value (0-23). Applicable to crons: 'daily', 'weekly', 'monthly', 'daily_alt_even', 'daily_alt_odd'", + ) + backupCreate.Flags().IntP("dow", "w", 0, "Day-of-week value (0-6). Applicable to crons: 'weekly'") + backupCreate.Flags().IntP("dom", "m", 0, "Day-of-month value (1-28). Applicable to crons: 'monthly'") + + backup.AddCommand( + backupGet, + backupCreate, + ) + + // Restore + restore := &cobra.Command{ + Use: "restore ", + Short: "Restore instance from backup or snapshot", + Long: ``, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("please provide an instance ID") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + backup, errBa := cmd.Flags().GetString("backup") + if errBa != nil { + return fmt.Errorf("error parsing flag 'backup' for instance restore : %v", errBa) + } + + snapshot, errSn := cmd.Flags().GetString("snapshot") + if errSn != nil { + return fmt.Errorf("error parsing flag 'snapshot' for instance restore : %v", errSn) + } + + o.RestoreReq = &govultr.RestoreReq{ + SnapshotID: snapshot, + BackupID: backup, + } + + if err := o.restore(); err != nil { + return fmt.Errorf("error restoring instance : %v", err) + } + + o.Base.Printer.Display(printer.Info("Instance restored"), nil) + + return nil + }, + } + + restore.Flags().StringP("backup", "b", "", "id of backup you wish to restore the instance with") + restore.Flags().StringP("snapshot", "s", "", "id of snapshot you wish to restore the instance with") + restore.MarkFlagsOneRequired("backup", "snapshot") + restore.MarkFlagsMutuallyExclusive("backup", "snapshot") + + // Reinstall + reinstall := &cobra.Command{ + Use: "reinstall ", + Short: "reinstall an instance", + Long: ``, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("please provide an instance ID") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + hostname, errHo := cmd.Flags().GetString("host") + if errHo != nil { + return fmt.Errorf("error parsing flag 'host' for instance reinstall : %v", errHo) + } + + o.ReinstallReq = &govultr.ReinstallReq{} + if cmd.Flags().Changed("host") { + o.ReinstallReq.Hostname = hostname + } + + if err := o.reinstall(); err != nil { + return fmt.Errorf("error reinstalling instance : %v", err) + } + + o.Base.Printer.Display(printer.Info("Instance reinstalled"), nil) + + return nil + }, + } + + reinstall.Flags().StringP("host", "", "", "The hostname to assign to this instance") + + // Operating System + operatingSystem := &cobra.Command{ + Use: "os", + Short: "Operating system commands for an instance", + } + + // OS List + osList := &cobra.Command{ + Use: "list ", + Short: "List available operating systems", + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("please provide an instance ID") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + upgrades, err := o.upgrades() + if err != nil { + return fmt.Errorf("error getting instance os list : %v", err) + } + + data := &OSsPrinter{OSs: upgrades.OS} + o.Base.Printer.Display(data, nil) + + return nil + }, + } + + // OS Change + osChange := &cobra.Command{ + Use: "change ", + Short: "Change operating system", + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("please provide an instance ID") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + osID, errOs := cmd.Flags().GetInt("os") + if errOs != nil { + return fmt.Errorf("error parsing flag 'osID' for instance os change : %v", errOs) + } + + o.UpdateReq = &govultr.InstanceUpdateReq{ + OsID: osID, + } + + _, err := o.update() + if err != nil { + return fmt.Errorf("error updating instance os : %v", err) + } + + o.Base.Printer.Display(printer.Info("OS change complete"), nil) + + return nil + }, + } + + osChange.Flags().IntP("os", "", 0, "operating system ID you wish to use") + if err := osChange.MarkFlagRequired("os"); err != nil { + fmt.Printf("error marking instance os update 'os' flag required: %v", err) + os.Exit(1) + } + + operatingSystem.AddCommand( + osList, + osChange, + ) + + // Application + app := &cobra.Command{ + Use: "app", + Aliases: []string{"image"}, + Short: "Application commands for an instance", + } + + // App List + appList := &cobra.Command{ + Use: "list ", + Short: "List available applications", + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("please provide an instance ID") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + upgrades, err := o.upgrades() + if err != nil { + return fmt.Errorf("error getting instance applications list : %v", err) + } + + data := &AppsPrinter{Apps: upgrades.Applications} + o.Base.Printer.Display(data, nil) + + return nil + }, + } + + // App Change + appChange := &cobra.Command{ + Use: "change ", + Short: "Change application", + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("please provide an instance ID") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + appID, errAp := cmd.Flags().GetInt("app") + if errAp != nil { + return fmt.Errorf("error parsing flag 'app' for instance application change : %v", errAp) + } + + o.UpdateReq = &govultr.InstanceUpdateReq{ + AppID: appID, + } + + _, err := o.update() + if err != nil { + return fmt.Errorf("error updating instance application : %v", err) + } + + o.Base.Printer.Display(printer.Info("Application change complete"), nil) + + return nil + }, + } + + appChange.Flags().IntP("app", "", 0, "Application ID you wish to use") + if err := appChange.MarkFlagRequired("app"); err != nil { + fmt.Printf("error marking instance app update 'app' flag required: %v", err) + os.Exit(1) + } + + app.AddCommand( + appList, + appChange, + ) + + // Plan + plan := &cobra.Command{ + Use: "plan", + Short: "Plan commands for an instance", + } + + // Plan List + planList := &cobra.Command{ + Use: "list ", + Short: "List available plans", + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("please provide an instance ID") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + upgrades, err := o.upgrades() + if err != nil { + return fmt.Errorf("error getting instance applications list : %v", err) + } + + data := &PlansPrinter{Plans: upgrades.Plans} + o.Base.Printer.Display(data, nil) + + return nil + }, + } + + // Plan Upgrade + planUpgrade := &cobra.Command{ + Use: "upgrade ", + Short: "Upgrade instance plan", + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("please provide an instance ID") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + plan, errPl := cmd.Flags().GetString("plan") + if errPl != nil { + return fmt.Errorf("error parsing flag 'plan' for instance plan upgrade : %v", errPl) + } + + o.UpdateReq = &govultr.InstanceUpdateReq{ + Plan: plan, + } + + _, err := o.update() + if err != nil { + return fmt.Errorf("error upgrading plan on instance : %v", err) + } + + o.Base.Printer.Display(printer.Info("Plan upgrade complete"), nil) + + return nil + }, + } + + planUpgrade.Flags().String("plan", "", "The plan ID you wish to use") + if err := planUpgrade.MarkFlagRequired("plan"); err != nil { + fmt.Printf("error marking instance plan upgrade 'plan' flag required: %v", err) + os.Exit(1) + } + + plan.AddCommand( + planList, + planUpgrade, + ) + + // IPv4 + ipv4 := &cobra.Command{ + Use: "ipv4", + Short: "IPv4 instance commands", + } + + // IPv4 List + ipv4List := &cobra.Command{ + Use: "list ", + Aliases: []string{"v4"}, + Short: "List IPv4 for an instance", + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("please provide an instance ID") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + o.Base.Options = utils.GetPaging(cmd) + + v4s, meta, err := o.ipv4s() + if err != nil { + return fmt.Errorf("error getting ipv4 list for instance : %v", err) + } + + data := &ip.IPv4sPrinter{IPv4s: v4s, Meta: meta} + o.Base.Printer.Display(data, nil) + + return nil + }, + } + + ipv4List.Flags().StringP("cursor", "c", "", "(optional) cursor for paging.") + ipv4List.Flags().IntP( + "per-page", + "p", + utils.PerPageDefault, + fmt.Sprintf("(optional) Number of items requested per page. Default is %d and Max is 500.", utils.PerPageDefault), + ) + + // IPv4 Create + ipv4Create := &cobra.Command{ + Use: "create ", + Short: "Create IPv4 for instance", + Long: ``, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("please provide an instance ID") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + reboot, errRe := cmd.Flags().GetBool("reboot") + if errRe != nil { + return fmt.Errorf("error parsing flag 'reboot' for instance ipv4 create: %v", errRe) + } + + if cmd.Flags().Changed("reboot") { + o.Reboot = &reboot + } + + if err := o.ipv4Create(); err != nil { + return fmt.Errorf("error creating instance ipv4 : %v", err) + } + + o.Base.Printer.Display(printer.Info("IPv4 has been created"), nil) + + return nil + }, + } + + ipv4Create.Flags().Bool("reboot", false, "whether to reboot instance after adding ipv4 address") + + // IPv4 Delete + ipv4Delete := &cobra.Command{ + Use: "delete ", + Short: "Delete IPv4 on an instance", + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 2 { + return errors.New("please provide an instance ID and the IP address") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + if err := o.ipv4Delete(); err != nil { + return fmt.Errorf("error deleting ipv4 : %v", err) + } + + o.Base.Printer.Display(printer.Info("IPv4 has been deleted"), nil) + + return nil + }, + } + + ipv4.AddCommand( + ipv4List, + ipv4Create, + ipv4Delete, + ) + + // IPv6 + ipv6 := &cobra.Command{ + Use: "ipv6", + Short: "IPv6 instance commands", + } + + // IPv6 List + ipv6List := &cobra.Command{ + Use: "list ", + Aliases: []string{"v6"}, + Short: "List IPv6 for an instance", + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("please provide an instance ID") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + o.Base.Options = utils.GetPaging(cmd) + + v6s, meta, err := o.ipv6s() + if err != nil { + return fmt.Errorf("error getting ipv6 list for instance : %v", err) + } + + data := &ip.IPv6sPrinter{IPv6s: v6s, Meta: meta} + o.Base.Printer.Display(data, nil) + + return nil + }, + } + + ipv6List.Flags().StringP("cursor", "c", "", "(optional) cursor for paging.") + ipv6List.Flags().IntP( + "per-page", + "p", + utils.PerPageDefault, + fmt.Sprintf("(optional) Number of items requested per page. Default is %d and Max is 500.", utils.PerPageDefault), + ) + + ipv6.AddCommand( + ipv6List, + ) + + // Reverse DNS + reverseDNS := &cobra.Command{ + Use: "reverse-dns", + Short: "Commands to handle reverse DNS on an instance", + } + + rDNSIPv4Default := &cobra.Command{ + Use: "default-ipv4 ", + Short: "Set a reverse DNS entry for an IPv4 address of an instance to the original setting", + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 2 { + return errors.New("please provide an instance ID and IP address") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + if err := o.reverseDNSDefault(); err != nil { + return fmt.Errorf("error setting default reverse dns ipv4 : %v", err) + } + + o.Base.Printer.Display(printer.Info("Reverse DNS defaulse IPv4 has been set"), nil) + + return nil + }, + } + + // Reverse DNS IPv4 Set + rDNSIPv4Set := &cobra.Command{ + Use: "set-ipv4 ", + Short: "Set a reverse DNS IPv4 address entry for an instance", + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 2 { + return errors.New("please provide an instance ID and an IPv4 address") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + entry, errEn := cmd.Flags().GetString("entry") + if errEn != nil { + return fmt.Errorf("error parsing flag 'entry' for instance reverse dns ipv4 set : %v", errEn) + } + + o.ReverseDNSReq = &govultr.ReverseIP{ + IP: o.Base.Args[1], + Reverse: entry, + } + + if err := o.reverseDNSIPv4Create(); err != nil { + return fmt.Errorf("error creating reverse dns ipv4 : %v", err) + } + + o.Base.Printer.Display(printer.Info("Reverse DNS IPv4 has been set"), nil) + + return nil + }, + } + + rDNSIPv4Set.Flags().StringP("entry", "e", "", "reverse dns entry") + if err := rDNSIPv4Set.MarkFlagRequired("entry"); err != nil { + fmt.Printf("error marking instance reverse-dns set-ipv4 'entry' flag required: %v", err) + os.Exit(1) + } + + // Reverse DNS IPv6 List + rDNSIPv6List := &cobra.Command{ + Use: "list-ipv6 ", + Short: "List the IPv6 reverse DNS entries for an instance", + Long: ``, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("please provide an instance ID") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + ips, err := o.reverseDNSIPv6List() + if err != nil { + return fmt.Errorf("error retrieving list of reverse dns ipv6 : %v", err) + } + + data := &ReverseIPsPrinter{ReverseIPs: ips} + o.Base.Printer.Display(data, nil) + + return nil + }, + } + + // Reverse DNS IPv6 Set + rDNSIPv6Set := &cobra.Command{ + Use: "set-ipv6 ", + Short: "Set a reverse DNS IPv6 address entry for an instance", + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 2 { + return errors.New("please provide an instance ID and an IPv6 address") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + entry, errEn := cmd.Flags().GetString("entry") + if errEn != nil { + return fmt.Errorf("error parsing flag 'entry' for instance reverse dns ipv6 set : %v", errEn) + } + + o.ReverseDNSReq = &govultr.ReverseIP{ + IP: o.Base.Args[1], + Reverse: entry, + } + + if err := o.reverseDNSIPv6Create(); err != nil { + return fmt.Errorf("error creating reverse dns ipv6 : %v", err) + } + + o.Base.Printer.Display(printer.Info("Reverse DNS IPv6 has been set"), nil) + + return nil + }, + } + + rDNSIPv6Set.Flags().StringP("entry", "e", "", "reverse dns entry") + if err := rDNSIPv6Set.MarkFlagRequired("entry"); err != nil { + fmt.Printf("error marking instance reverse-dns set-ipv6 'entry' flag required: %v", err) + os.Exit(1) + } + + // Reverse DNS IPv6 Delete + rDNSIPv6Delete := &cobra.Command{ + Use: "delete-ipv6 , ", + Short: "Remove a reverse DNS IPv6 address entry for an instance", + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("please provide an instance ID and an IPv6 address") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + if err := o.reverseDNSIPv6Delete(); err != nil { + return fmt.Errorf("error deleting reverse dns ipv6 : %v", err) + } + + o.Base.Printer.Display(printer.Info("Reverse DNS IPv6 has been deleted"), nil) + + return nil + }, + } + + reverseDNS.AddCommand( + rDNSIPv4Default, + rDNSIPv4Set, + rDNSIPv6List, + rDNSIPv6Delete, + rDNSIPv6Set, + ) + + // Firewall Group + firewallGroup := &cobra.Command{ + Use: "update-firewall-group", + Short: "Assign a firewall group to instance", + RunE: func(cmd *cobra.Command, args []string) error { + fwgID, errID := cmd.Flags().GetString("firewall-group-id") + if errID != nil { + return fmt.Errorf("error parsing flag 'firewall-group-id' for instance firewall group assignment : %v", errID) + } + + o.UpdateReq = &govultr.InstanceUpdateReq{ + FirewallGroupID: fwgID, + } + + if _, err := o.update(); err != nil { + return fmt.Errorf("error updating fire wall group on instance : %v", err) + } + + o.Base.Printer.Display(printer.Info("Firewall group assigned to instance"), nil) + + return nil + }, + } + + firewallGroup.Flags().StringP( + "firewall-group-id", + "f", + "", + "firewall group id that you want to assign. 0 Value will unset the firewall-group", + ) + if err := firewallGroup.MarkFlagRequired("firewall-group-id"); err != nil { + fmt.Printf("error marking instance firewall group 'firewall-group-id' flag required: %v", err) + os.Exit(1) + } + + // VPC + vpc := &cobra.Command{ + Use: "vpc", + Short: "Commands to handle vpcs on an instance", + } + + // VPC Attach + vpcAttach := &cobra.Command{ + Use: "attach ", + Short: "Attach a VPC to an instance", + Long: vpcAttachLong, + Example: vpcAttachExample, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 2 { + return errors.New("please provide an instance ID and a VPC ID") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + if err := o.vpcAttach(); err != nil { + return fmt.Errorf("error attaching vpc to instance : %v", err) + } + + o.Base.Printer.Display(printer.Info("VPC attached to instance"), nil) + + return nil + }, + } + + // VPC Detach + vpcDetach := &cobra.Command{ + Use: "detach ", + Short: "Detach a VPC from an instance", + Long: vpcDetachLong, + Example: vpcDetachExample, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 2 { + return errors.New("please provide an instance ID and a VPC ID") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + if err := o.vpcDetach(); err != nil { + return fmt.Errorf("error detaching vpc from instance : %v", err) + } + + o.Base.Printer.Display(printer.Info("VPC detached from instance"), nil) + + return nil + }, + } + + vpc.AddCommand( + vpcAttach, + vpcDetach, + ) + + // VPC2 + vpc2 := &cobra.Command{ + Use: "vpc2", + Short: "Commands to handle vpc2s on an instance", + } + + // VPC List + vpc2List := &cobra.Command{ + Use: "list ", + Aliases: []string{"l"}, + Short: "List all VPC2 networks attached to an instance", + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("please provide an instance ID") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + o.Base.Options = utils.GetPaging(cmd) + + vpc2s, meta, err := o.vpc2s() + if err != nil { + return fmt.Errorf("error getting vpc2 list for instance : %v", err) + } + + data := &VPC2sPrinter{VPC2s: vpc2s, Meta: meta} + o.Base.Printer.Display(data, nil) + + return nil + }, + } + + // VPC2 Attach + vpc2Attach := &cobra.Command{ + Use: "attach , ", + Short: "Attach a VPC2 to an instance", + Long: vpc2AttachLong, + Example: vpc2AttachExample, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 2 { + return errors.New("please provide an instance ID and a VPC ID") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + ip, errIP := cmd.Flags().GetString("ip-address") + if errIP != nil { + return fmt.Errorf("error parsing flag 'ip-address' for vpc2 instance attach : %v", errIP) + } + + o.VPC2Req = &govultr.AttachVPC2Req{ + VPCID: o.Base.Args[1], + IPAddress: &ip, + } + + if err := o.vpc2Attach(); err != nil { + return fmt.Errorf("error attaching vpc2 to instance : %v", err) + } + + o.Base.Printer.Display(printer.Info("VPC2 attached to instance"), nil) + + return nil + }, + } + + vpc2Attach.Flags().StringP( + "ip-address", + "i", + "", + "the IP address to use for this instance on the attached VPC 2.0 network", + ) + + // VPC2 Detach + vpc2Detach := &cobra.Command{ + Use: "detach ", + Short: "Detach a VPC2 from an instance", + Long: vpc2DetachLong, + Example: vpc2DetachExample, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 2 { + return errors.New("please provide an instance ID and a VPC2 ID") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + if err := o.vpc2Detach(); err != nil { + return fmt.Errorf("error detaching vpc2 from instance : %v", err) + } + + o.Base.Printer.Display(printer.Info("VPC2 detached from instance"), nil) + + return nil + }, + } + + vpc2.AddCommand( + vpc2List, + vpc2Attach, + vpc2Detach, + ) + + // Bandwidth + bandwidth := &cobra.Command{ + Use: "bandwidth ", + Short: "Get bandwidth usage ", + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("please provide an instance ID") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + bw, err := o.bandwidth() + if err != nil { + return fmt.Errorf("error getting bandwidth details : %v", err) + } + + data := &BandwidthPrinter{Bandwidth: *bw} + o.Base.Printer.Display(data, nil) + + return nil + }, + } + + cmd.AddCommand( + list, + get, + create, + del, + label, + tags, + userData, + start, + stop, + restart, + iso, + backup, + restore, + reinstall, + operatingSystem, + app, + plan, + ipv4, + ipv6, + reverseDNS, + firewallGroup, + vpc, + vpc2, + bandwidth, + ) + + return cmd +} + +type options struct { + Base *cli.Base + CreateReq *govultr.InstanceCreateReq + UpdateReq *govultr.InstanceUpdateReq + BackupCreateReq *govultr.BackupScheduleReq + RestoreReq *govultr.RestoreReq + ReinstallReq *govultr.ReinstallReq + ISOAttachID string + Reboot *bool + ReverseDNSReq *govultr.ReverseIP + VPC2Req *govultr.AttachVPC2Req +} + +func (o *options) list() ([]govultr.Instance, *govultr.Meta, error) { + insts, meta, _, err := o.Base.Client.Instance.List(o.Base.Context, o.Base.Options) + return insts, meta, err +} + +func (o *options) get() (*govultr.Instance, error) { + inst, _, err := o.Base.Client.Instance.Get(o.Base.Context, o.Base.Args[0]) + return inst, err +} + +func (o *options) create() (*govultr.Instance, error) { + inst, _, err := o.Base.Client.Instance.Create(o.Base.Context, o.CreateReq) + return inst, err +} + +func (o *options) update() (*govultr.Instance, error) { + inst, _, err := o.Base.Client.Instance.Update(o.Base.Context, o.Base.Args[0], o.UpdateReq) + return inst, err +} + +func (o *options) del() error { + return o.Base.Client.Instance.Delete(o.Base.Context, o.Base.Args[0]) +} + +func (o *options) userData() (*govultr.UserData, error) { + ud, _, err := o.Base.Client.Instance.GetUserData(o.Base.Context, o.Base.Args[0]) + return ud, err +} + +func (o *options) start() error { + return o.Base.Client.Instance.Start(o.Base.Context, o.Base.Args[0]) +} + +func (o *options) stop() error { + return o.Base.Client.Instance.Halt(o.Base.Context, o.Base.Args[0]) +} + +func (o *options) restart() error { + return o.Base.Client.Instance.Reboot(o.Base.Context, o.Base.Args[0]) +} + +func (o *options) backups() (*govultr.BackupSchedule, error) { + bk, _, err := o.Base.Client.Instance.GetBackupSchedule(o.Base.Context, o.Base.Args[0]) + return bk, err +} + +func (o *options) backupCreate() error { + _, err := o.Base.Client.Instance.SetBackupSchedule(o.Base.Context, o.Base.Args[0], o.BackupCreateReq) + return err +} + +func (o *options) restore() error { + _, err := o.Base.Client.Instance.Restore(o.Base.Context, o.Base.Args[0], o.RestoreReq) + return err +} + +func (o *options) reinstall() error { + _, _, err := o.Base.Client.Instance.Reinstall(o.Base.Context, o.Base.Args[0], o.ReinstallReq) + return err +} + +func (o *options) isoStatus() (*govultr.Iso, error) { + iso, _, err := o.Base.Client.Instance.ISOStatus(o.Base.Context, o.Base.Args[0]) + return iso, err +} + +func (o *options) isoAttach() error { + _, err := o.Base.Client.Instance.AttachISO(o.Base.Context, o.Base.Args[0], o.ISOAttachID) + return err +} + +func (o *options) upgrades() (*govultr.Upgrades, error) { + oss, _, err := o.Base.Client.Instance.GetUpgrades(o.Base.Context, o.Base.Args[0]) + return oss, err +} + +func (o *options) ipv4s() ([]govultr.IPv4, *govultr.Meta, error) { + ips, meta, _, err := o.Base.Client.Instance.ListIPv4(o.Base.Context, o.Base.Args[0], o.Base.Options) + return ips, meta, err +} + +func (o *options) ipv4Create() error { + _, _, err := o.Base.Client.Instance.CreateIPv4(o.Base.Context, o.Base.Args[0], o.Reboot) + return err +} + +func (o *options) ipv4Delete() error { + return o.Base.Client.Instance.DeleteIPv4(o.Base.Context, o.Base.Args[0], o.Base.Args[1]) +} + +func (o *options) ipv6s() ([]govultr.IPv6, *govultr.Meta, error) { + ips, meta, _, err := o.Base.Client.Instance.ListIPv6(o.Base.Context, o.Base.Args[0], o.Base.Options) + return ips, meta, err +} + +func (o *options) reverseDNSDefault() error { + return o.Base.Client.Instance.DefaultReverseIPv4(o.Base.Context, o.Base.Args[0], o.Base.Args[1]) +} + +func (o *options) reverseDNSIPv4Create() error { + return o.Base.Client.Instance.CreateReverseIPv4(o.Base.Context, o.Base.Args[0], o.ReverseDNSReq) +} + +func (o *options) reverseDNSIPv6List() ([]govultr.ReverseIP, error) { + ips, _, err := o.Base.Client.Instance.ListReverseIPv6(o.Base.Context, o.Base.Args[0]) + return ips, err +} + +func (o *options) reverseDNSIPv6Create() error { + return o.Base.Client.Instance.CreateReverseIPv6(o.Base.Context, o.Base.Args[0], o.ReverseDNSReq) +} + +func (o *options) reverseDNSIPv6Delete() error { + return o.Base.Client.Instance.DeleteReverseIPv6(o.Base.Context, o.Base.Args[0], o.Base.Args[1]) +} + +func (o *options) vpcAttach() error { + return o.Base.Client.Instance.AttachVPC(o.Base.Context, o.Base.Args[0], o.Base.Args[1]) +} + +func (o *options) vpcDetach() error { + return o.Base.Client.Instance.DetachVPC(o.Base.Context, o.Base.Args[0], o.Base.Args[1]) +} + +func (o *options) vpc2s() ([]govultr.VPC2Info, *govultr.Meta, error) { + vpc2s, meta, _, err := o.Base.Client.Instance.ListVPC2Info(o.Base.Context, o.Base.Args[0], o.Base.Options) + return vpc2s, meta, err +} + +func (o *options) vpc2Attach() error { + return o.Base.Client.Instance.AttachVPC2(o.Base.Context, o.Base.Args[0], o.VPC2Req) +} + +func (o *options) vpc2Detach() error { + return o.Base.Client.Instance.DetachVPC2(o.Base.Context, o.Base.Args[0], o.Base.Args[1]) +} + +func (o *options) bandwidth() (*govultr.Bandwidth, error) { + bw, _, err := o.Base.Client.Instance.GetBandwidth(o.Base.Context, o.Base.Args[0]) + return bw, err +} diff --git a/cmd/instance/printer.go b/cmd/instance/printer.go new file mode 100644 index 00000000..b232694d --- /dev/null +++ b/cmd/instance/printer.go @@ -0,0 +1,513 @@ +package instance + +import ( + "strconv" + + "github.com/vultr/govultr/v3" + "github.com/vultr/vultr-cli/v3/cmd/printer" +) + +// InstancesPrinter ... +type InstancesPrinter struct { + Instances []govultr.Instance `json:"instances"` + Meta *govultr.Meta `json:"meta"` +} + +// JSON ... +func (i *InstancesPrinter) JSON() []byte { + return printer.MarshalObject(i, "json") +} + +// YAML ... +func (i *InstancesPrinter) YAML() []byte { + return printer.MarshalObject(i, "yaml") +} + +// Columns ... +func (i *InstancesPrinter) Columns() [][]string { + return [][]string{0: { + "ID", + "IP", + "LABEL", + "OS", + "STATUS", + "REGION", + "CPU", + "RAM", + "DISK", + "BANDWIDTH", + "TAGS", + }} +} + +// Data ... +func (i *InstancesPrinter) Data() [][]string { + if len(i.Instances) == 0 { + return [][]string{0: {"---", "---", "---", "---", "---", "---", "---", "---", "---", "---", "---"}} + } + + var data [][]string + for j := range i.Instances { + data = append(data, []string{ + i.Instances[j].ID, + i.Instances[j].MainIP, + i.Instances[j].Label, + i.Instances[j].Os, + i.Instances[j].Status, + i.Instances[j].Region, + strconv.Itoa(i.Instances[j].VCPUCount), + strconv.Itoa(i.Instances[j].RAM), + strconv.Itoa(i.Instances[j].Disk), + strconv.Itoa(i.Instances[j].AllowedBandwidth), + printer.ArrayOfStringsToString(i.Instances[j].Tags), + }) + } + return data +} + +// Paging ... +func (i *InstancesPrinter) Paging() [][]string { + return printer.NewPaging(i.Meta.Total, &i.Meta.Links.Next, &i.Meta.Links.Prev).Compose() +} + +// ====================================== + +// InstancePrinter ... +type InstancePrinter struct { + Instance *govultr.Instance `json:"instance"` +} + +// JSON ... +func (i *InstancePrinter) JSON() []byte { + return printer.MarshalObject(i, "json") +} + +// YAML ... +func (i *InstancePrinter) YAML() []byte { + return printer.MarshalObject(i, "yaml") +} + +// Columns ... +func (i *InstancePrinter) Columns() [][]string { + return [][]string{0: {"INSTANCE INFO"}} +} + +// Data ... +func (i *InstancePrinter) Data() [][]string { + var data [][]string + data = append(data, + []string{"ID", i.Instance.ID}, + []string{"OS", i.Instance.Os}, + []string{"OS ID", strconv.Itoa(i.Instance.OsID)}, + []string{"APP ID", strconv.Itoa(i.Instance.AppID)}, + []string{"RAM", strconv.Itoa(i.Instance.RAM)}, + []string{"DISK", strconv.Itoa(i.Instance.Disk)}, + []string{"MAIN IP", i.Instance.MainIP}, + []string{"VCPU COUNT", strconv.Itoa(i.Instance.VCPUCount)}, + []string{"REGION", i.Instance.Region}, + []string{"DATE CREATED", i.Instance.DateCreated}, + []string{"STATUS", i.Instance.Status}, + []string{"ALLOWED BANDWIDTH", strconv.Itoa(i.Instance.AllowedBandwidth)}, + []string{"NETMASK V4", i.Instance.NetmaskV4}, + []string{"GATEWAY V4", i.Instance.GatewayV4}, + []string{"POWER STATUS", i.Instance.PowerStatus}, + []string{"SERVER STATE", i.Instance.ServerStatus}, + []string{"PLAN", i.Instance.Plan}, + []string{"LABEL", i.Instance.Label}, + []string{"INTERNAL IP", i.Instance.InternalIP}, + []string{"KVM URL", i.Instance.KVM}, + []string{"FIREWALL GROUP ID", i.Instance.FirewallGroupID}, + []string{"V6 MAIN IP", i.Instance.V6MainIP}, + []string{"V6 NETWORK", i.Instance.V6Network}, + []string{"V6 NETWORK SIZE", strconv.Itoa(i.Instance.V6NetworkSize)}, + []string{"FEATURES", printer.ArrayOfStringsToString(i.Instance.Features)}, + []string{"TAGS", printer.ArrayOfStringsToString(i.Instance.Tags)}, + ) + + return data +} + +// Paging ... +func (i *InstancePrinter) Paging() [][]string { + return nil +} + +// ====================================== + +// BandwidthPrinter ... +type BandwidthPrinter struct { + Bandwidth govultr.Bandwidth `json:"bandwidth"` +} + +// JSON ... +func (b *BandwidthPrinter) JSON() []byte { + return printer.MarshalObject(b, "json") +} + +// YAML ... +func (b *BandwidthPrinter) YAML() []byte { + return printer.MarshalObject(b, "yaml") +} + +// Columns ... +func (b *BandwidthPrinter) Columns() [][]string { + return [][]string{0: { + "DATE", + "INCOMING BYTES", + "OUTGOING BYTES", + }} +} + +// Data ... +func (b *BandwidthPrinter) Data() [][]string { + var data [][]string + for i := range b.Bandwidth.Bandwidth { + data = append(data, []string{ + i, + strconv.Itoa(b.Bandwidth.Bandwidth[i].IncomingBytes), + strconv.Itoa(b.Bandwidth.Bandwidth[i].OutgoingBytes), + }) + } + + return data +} + +// Paging ... +func (b *BandwidthPrinter) Paging() [][]string { + return nil +} + +// ====================================== + +// BackupPrinter ... +type BackupPrinter struct { + Backup govultr.BackupSchedule `json:"backup_schedule"` +} + +// JSON ... +func (b *BackupPrinter) JSON() []byte { + return printer.MarshalObject(b, "json") +} + +// YAML ... +func (b *BackupPrinter) YAML() []byte { + return printer.MarshalObject(b, "yaml") +} + +// Columns ... +func (b *BackupPrinter) Columns() [][]string { + return [][]string{0: { + "ENABLED", + "CRON TYPE", + "NEXT RUN", + "HOUR", + "DOW", + "DOM", + }} +} + +// Data ... +func (b *BackupPrinter) Data() [][]string { + return [][]string{0: { + strconv.FormatBool(*b.Backup.Enabled), + b.Backup.Type, + b.Backup.NextScheduleTimeUTC, + strconv.Itoa(b.Backup.Hour), + strconv.Itoa(b.Backup.Dow), + strconv.Itoa(b.Backup.Dom), + }} +} + +// Paging ... +func (b *BackupPrinter) Paging() [][]string { + return nil +} + +// ====================================== + +// ISOPrinter ... +type ISOPrinter struct { + ISO govultr.Iso `json:"iso_status"` +} + +// JSON ... +func (i *ISOPrinter) JSON() []byte { + return printer.MarshalObject(i, "json") +} + +// YAML ... +func (i *ISOPrinter) YAML() []byte { + return printer.MarshalObject(i, "yaml") +} + +// Columns ... +func (i *ISOPrinter) Columns() [][]string { + return [][]string{0: { + "ISO ID", + "STATE", + }} +} + +// Data ... +func (i *ISOPrinter) Data() [][]string { + return [][]string{0: { + i.ISO.IsoID, + i.ISO.State, + }} +} + +// Paging ... +func (i *ISOPrinter) Paging() [][]string { + return nil +} + +// ====================================== + +// OSsPrinter ... +type OSsPrinter struct { + OSs []govultr.OS `json:"operating_systems"` +} + +// JSON ... +func (o *OSsPrinter) JSON() []byte { + return printer.MarshalObject(o, "json") +} + +// YAML ... +func (o *OSsPrinter) YAML() []byte { + return printer.MarshalObject(o, "yaml") +} + +// Columns ... +func (o *OSsPrinter) Columns() [][]string { + return [][]string{0: { + "ID", + "NAME", + "ARCH", + "FAMILY", + }} +} + +// Data ... +func (o *OSsPrinter) Data() [][]string { + if len(o.OSs) == 0 { + return [][]string{0: {"---", "---", "---", "---"}} + } + + var data [][]string + for i := range o.OSs { + data = append(data, []string{ + strconv.Itoa(o.OSs[i].ID), + o.OSs[i].Name, + o.OSs[i].Arch, + o.OSs[i].Family, + }) + } + + return data +} + +// Paging ... +func (o *OSsPrinter) Paging() [][]string { + return nil +} + +// ====================================== + +// AppsPrinter ... +type AppsPrinter struct { + Apps []govultr.Application `json:"applications"` +} + +// JSON ... +func (a *AppsPrinter) JSON() []byte { + return printer.MarshalObject(a, "json") +} + +// YAML ... +func (a *AppsPrinter) YAML() []byte { + return printer.MarshalObject(a, "yaml") +} + +// Columns ... +func (a *AppsPrinter) Columns() [][]string { + return [][]string{0: { + "ID", + "NAME", + "SHORT NAME", + "DEPLOY NAME", + "TYPE", + "VENDOR", + "IMAGE ID", + }} +} + +// Data ... +func (a *AppsPrinter) Data() [][]string { + if len(a.Apps) == 0 { + return [][]string{0: {"---", "---", "---", "---", "---", "---", "---"}} + } + + var data [][]string + for i := range a.Apps { + data = append(data, []string{ + strconv.Itoa(a.Apps[i].ID), + a.Apps[i].Name, + a.Apps[i].ShortName, + a.Apps[i].DeployName, + a.Apps[i].Type, + a.Apps[i].Vendor, + a.Apps[i].ImageID, + }) + } + + return data +} + +// Paging ... +func (a *AppsPrinter) Paging() [][]string { + return nil +} + +// ====================================== + +// PlansPrinter ... +type PlansPrinter struct { + Plans []string `json:"plans"` +} + +// JSON ... +func (p *PlansPrinter) JSON() []byte { + return printer.MarshalObject(p, "json") +} + +// YAML ... +func (p *PlansPrinter) YAML() []byte { + return printer.MarshalObject(p, "yaml") +} + +// Columns ... +func (p *PlansPrinter) Columns() [][]string { + return [][]string{0: { + "PLAN NAME", + }} +} + +// Data ... +func (p *PlansPrinter) Data() [][]string { + if len(p.Plans) == 0 { + return [][]string{0: {"---"}} + } + + var data [][]string + for i := range p.Plans { + data = append(data, []string{ + p.Plans[i], + }) + } + + return data +} + +// Paging ... +func (p *PlansPrinter) Paging() [][]string { + return nil +} + +// ====================================== + +// ReverseIPsPrinter ... +type ReverseIPsPrinter struct { + ReverseIPs []govultr.ReverseIP `json:"reverse_ips"` +} + +// JSON ... +func (r *ReverseIPsPrinter) JSON() []byte { + return printer.MarshalObject(r, "json") +} + +// YAML ... +func (r *ReverseIPsPrinter) YAML() []byte { + return printer.MarshalObject(r, "yaml") +} + +// Columns ... +func (r *ReverseIPsPrinter) Columns() [][]string { + return [][]string{0: { + "IP", + "REVERSE", + }} +} + +// Data ... +func (r *ReverseIPsPrinter) Data() [][]string { + if len(r.ReverseIPs) == 0 { + return [][]string{0: {"---", "---"}} + } + + var data [][]string + for j := range r.ReverseIPs { + data = append(data, []string{ + r.ReverseIPs[j].IP, + r.ReverseIPs[j].Reverse, + }) + } + + return data +} + +// Paging ... +func (r *ReverseIPsPrinter) Paging() [][]string { + return nil +} + +// ====================================== + +// VPC2sPrinter ... +type VPC2sPrinter struct { + VPC2s []govultr.VPC2Info `json:"vpcs"` + Meta *govultr.Meta `json:"meta"` +} + +// JSON ... +func (v *VPC2sPrinter) JSON() []byte { + return printer.MarshalObject(v, "json") +} + +// YAML ... +func (v *VPC2sPrinter) YAML() []byte { + return printer.MarshalObject(v, "yaml") +} + +// Columns ... +func (v *VPC2sPrinter) Columns() [][]string { + return [][]string{0: { + "ID", + "MAC ADDRESS", + "IP ADDRESS", + }} +} + +// Data ... +func (v *VPC2sPrinter) Data() [][]string { + var data [][]string + + if len(v.VPC2s) == 0 { + return [][]string{0: {"---", "---", "---"}} + } + + for i := range v.VPC2s { + data = append(data, []string{ + v.VPC2s[i].ID, + v.VPC2s[i].MacAddress, + v.VPC2s[i].IPAddress, + }) + } + + return data +} + +// Paging ... +func (v *VPC2sPrinter) Paging() [][]string { + return printer.NewPaging(v.Meta.Total, &v.Meta.Links.Next, &v.Meta.Links.Prev).Compose() +} diff --git a/cmd/ip/printer.go b/cmd/ip/printer.go new file mode 100644 index 00000000..f05a5272 --- /dev/null +++ b/cmd/ip/printer.go @@ -0,0 +1,103 @@ +// Package ip provides printers for server network addresses +package ip + +import ( + "strconv" + + "github.com/vultr/govultr/v3" + "github.com/vultr/vultr-cli/v3/cmd/printer" +) + +// IPv4sPrinter ... +type IPv4sPrinter struct { + IPv4s []govultr.IPv4 `json:"ipv4s"` + Meta *govultr.Meta `json:"meta"` +} + +// JSON ... +func (i *IPv4sPrinter) JSON() []byte { + return printer.MarshalObject(i, "json") +} + +// YAML ... +func (i *IPv4sPrinter) YAML() []byte { + return printer.MarshalObject(i, "yaml") +} + +// Columns ... +func (i *IPv4sPrinter) Columns() [][]string { + return [][]string{0: { + "IP", + "NETMASK", + "GATEWAY", + "TYPE", + }} +} + +// Data ... +func (i *IPv4sPrinter) Data() [][]string { + var data [][]string + for j := range i.IPv4s { + data = append(data, []string{ + i.IPv4s[j].IP, + i.IPv4s[j].Netmask, + i.IPv4s[j].Gateway, + i.IPv4s[j].Type, + }) + } + + return data +} + +// Paging ... +func (i *IPv4sPrinter) Paging() [][]string { + return printer.NewPaging(i.Meta.Total, &i.Meta.Links.Next, &i.Meta.Links.Prev).Compose() +} + +// ====================================== + +// IPv6sPrinter ... +type IPv6sPrinter struct { + IPv6s []govultr.IPv6 `json:"ipv6s"` + Meta *govultr.Meta `json:"meta"` +} + +// JSON ... +func (i *IPv6sPrinter) JSON() []byte { + return printer.MarshalObject(i, "json") +} + +// YAML ... +func (i *IPv6sPrinter) YAML() []byte { + return printer.MarshalObject(i, "yaml") +} + +// Columns ... +func (i *IPv6sPrinter) Columns() [][]string { + return [][]string{0: { + "IP", + "NETWORK", + "NETWORK SIZE", + "TYPE", + }} +} + +// Data ... +func (i *IPv6sPrinter) Data() [][]string { + var data [][]string + for j := range i.IPv6s { + data = append(data, []string{ + i.IPv6s[j].IP, + i.IPv6s[j].Network, + strconv.Itoa(i.IPv6s[j].NetworkSize), + i.IPv6s[j].Type, + }) + } + + return data +} + +// Paging ... +func (i *IPv6sPrinter) Paging() [][]string { + return printer.NewPaging(i.Meta.Total, &i.Meta.Links.Next, &i.Meta.Links.Prev).Compose() +} diff --git a/cmd/iso.go b/cmd/iso.go deleted file mode 100644 index d472a851..00000000 --- a/cmd/iso.go +++ /dev/null @@ -1,144 +0,0 @@ -// Copyright © 2019 The Vultr-cli Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package cmd - -import ( - "context" - "errors" - "fmt" - "os" - - "github.com/vultr/govultr/v2" - "github.com/vultr/vultr-cli/cmd/printer" - - "github.com/spf13/cobra" -) - -// ISO represents the iso command -func ISO() *cobra.Command { - isoCmd := &cobra.Command{ - Use: "iso", - Short: "iso is used to access iso commands", - Long: ``, - } - - isoCmd.AddCommand(isoCreate, isoDelete, isoPrivateGet, isoPrivateList, isoPublic) - isoCreate.Flags().StringP("url", "u", "", "url from where the ISO will be downloaded") - isoCreate.MarkFlagRequired("url") - - isoPrivateList.Flags().StringP("cursor", "c", "", "(optional) Cursor for paging.") - isoPrivateList.Flags().IntP("per-page", "p", 100, "(optional) Number of items requested per page. Default is 100 and Max is 500.") - - isoPublic.Flags().StringP("cursor", "c", "", "(optional) Cursor for paging.") - isoPublic.Flags().IntP("per-page", "p", 100, "(optional) Number of items requested per page. Default is 100 and Max is 500.") - - return isoCmd -} - -var isoPrivateGet = &cobra.Command{ - Use: "get ", - Short: "get private ISO ", - Long: ``, - Args: func(cmd *cobra.Command, args []string) error { - if len(args) < 1 { - return errors.New("please provide an ISO id") - } - return nil - }, - Run: func(cmd *cobra.Command, args []string) { - id := args[0] - iso, err := client.ISO.Get(context.Background(), id) - if err != nil { - fmt.Printf("error getting ISO : %v\n", err) - os.Exit(1) - } - - printer.IsoPrivate(iso) - }, -} - -var isoPrivateList = &cobra.Command{ - Use: "list", - Short: "list all private ISOs available", - Long: ``, - Run: func(cmd *cobra.Command, args []string) { - options := getPaging(cmd) - isos, meta, err := client.ISO.List(context.Background(), options) - if err != nil { - fmt.Printf("error getting private ISOs : %v\n", err) - os.Exit(1) - } - - printer.IsoPrivates(isos, meta) - }, -} - -var isoPublic = &cobra.Command{ - Use: "public", - Short: "list all public ISOs available", - Long: ``, - Run: func(cmd *cobra.Command, args []string) { - options := getPaging(cmd) - isos, meta, err := client.ISO.ListPublic(context.Background(), options) - if err != nil { - fmt.Printf("error getting public ISOs : %v\n", err) - os.Exit(1) - } - - printer.IsoPublic(isos, meta) - }, -} - -var isoCreate = &cobra.Command{ - Use: "create", - Short: "create iso from url", - Long: ``, - Run: func(cmd *cobra.Command, args []string) { - url, _ := cmd.Flags().GetString("url") - options := &govultr.ISOReq{ - URL: url, - } - - iso, err := client.ISO.Create(context.Background(), options) - if err != nil { - fmt.Printf("error creating ISOs : %v\n", err) - os.Exit(1) - } - - printer.IsoPrivate(iso) - }, -} - -var isoDelete = &cobra.Command{ - Use: "delete ", - Short: "delete a private iso", - Aliases: []string{"destroy"}, - Long: ``, - Args: func(cmd *cobra.Command, args []string) error { - if len(args) < 1 { - return errors.New("please provide an isoID") - } - return nil - }, - Run: func(cmd *cobra.Command, args []string) { - id := args[0] - if err := client.ISO.Delete(context.Background(), id); err != nil { - fmt.Printf("error deleting ISOs : %v\n", err) - os.Exit(1) - } - - fmt.Println("ISO has been deleted") - }, -} diff --git a/cmd/iso/iso.go b/cmd/iso/iso.go new file mode 100644 index 00000000..cf7d4936 --- /dev/null +++ b/cmd/iso/iso.go @@ -0,0 +1,190 @@ +// Package iso provides the ISO related commands to the CLI +package iso + +import ( + "errors" + "fmt" + "os" + + "github.com/spf13/cobra" + "github.com/vultr/govultr/v3" + "github.com/vultr/vultr-cli/v3/cmd/printer" + "github.com/vultr/vultr-cli/v3/cmd/utils" + "github.com/vultr/vultr-cli/v3/pkg/cli" +) + +// NewCmdISO provides the CLI command for ISO functions +func NewCmdISO(base *cli.Base) *cobra.Command { + o := &options{Base: base} + + cmd := &cobra.Command{ + Use: "iso", + Short: "iso is used to access iso commands", + Long: ``, + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + utils.SetOptions(o.Base, cmd, args) + if !o.Base.HasAuth { + return errors.New(utils.APIKeyError) + } + return nil + }, + } + + // List + list := &cobra.Command{ + Use: "list", + Short: "list all private ISOs available", + Long: ``, + RunE: func(cmd *cobra.Command, args []string) error { + o.Base.Options = utils.GetPaging(cmd) + + isos, meta, err := o.list() + if err != nil { + return fmt.Errorf("error retrieving private ISO list : %v", err) + } + + data := &ISOsPrinter{ISOs: isos, Meta: meta} + o.Base.Printer.Display(data, nil) + + return nil + }, + } + + list.Flags().StringP("cursor", "c", "", "(optional) Cursor for paging.") + list.Flags().IntP( + "per-page", + "p", + utils.PerPageDefault, + "(optional) Number of items requested per page. Default is 100 and Max is 500.", + ) + + // Get + get := &cobra.Command{ + Use: "get ", + Short: "get private ISO by ID", + Long: ``, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("please provide an ISO ID") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + iso, err := o.get() + if err != nil { + return fmt.Errorf("error getting ISO : %v", err) + } + + data := &ISOPrinter{ISO: *iso} + o.Base.Printer.Display(data, nil) + + return nil + }, + } + + // Create + create := &cobra.Command{ + Use: "create", + Short: "create ISO from url", + Long: ``, + RunE: func(cmd *cobra.Command, args []string) error { + url, errUR := cmd.Flags().GetString("url") + if errUR != nil { + return fmt.Errorf("error parsing flag 'url' for ISO create : %v", errUR) + } + + o.CreateReq = &govultr.ISOReq{URL: url} + + iso, err := o.create() + if err != nil { + return fmt.Errorf("error creating ISO : %v", err) + } + + data := &ISOPrinter{ISO: *iso} + o.Base.Printer.Display(data, nil) + + return nil + }, + } + + create.Flags().StringP("url", "u", "", "url from where the ISO will be downloaded") + if err := create.MarkFlagRequired("url"); err != nil { + printer.Error(fmt.Errorf("error marking iso create 'url' flag required : %v", err)) + os.Exit(1) + } + + // Delete + del := &cobra.Command{ + Use: "delete ", + Short: "delete a private ISO", + Aliases: []string{"destroy"}, + Long: ``, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("please provide an ISO ID") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + if err := o.del(); err != nil { + return fmt.Errorf("error deleting ISO : %v", err) + } + + o.Base.Printer.Display(printer.Info("ISO has been deleted"), nil) + return nil + }, + } + + // Public ISOs + public := &cobra.Command{ + Use: "public", + Short: "list all public ISOs available", + Long: ``, + RunE: func(cmd *cobra.Command, args []string) error { + o.Base.Options = utils.GetPaging(cmd) + + isos, meta, err := o.listPublic() + if err != nil { + return fmt.Errorf("error retrieving public ISO list : %v", err) + } + + data := &PublicISOsPrinter{ISOs: isos, Meta: meta} + o.Base.Printer.Display(data, nil) + + return nil + }, + } + + cmd.AddCommand(list, get, create, del, public) + + return cmd +} + +type options struct { + Base *cli.Base + CreateReq *govultr.ISOReq +} + +func (o *options) list() ([]govultr.ISO, *govultr.Meta, error) { + isos, meta, _, err := o.Base.Client.ISO.List(o.Base.Context, o.Base.Options) + return isos, meta, err +} + +func (o *options) get() (*govultr.ISO, error) { + iso, _, err := o.Base.Client.ISO.Get(o.Base.Context, o.Base.Args[0]) + return iso, err +} + +func (o *options) create() (*govultr.ISO, error) { + iso, _, err := o.Base.Client.ISO.Create(o.Base.Context, o.CreateReq) + return iso, err +} + +func (o *options) del() error { + return o.Base.Client.ISO.Delete(o.Base.Context, o.Base.Args[0]) +} + +func (o *options) listPublic() ([]govultr.PublicISO, *govultr.Meta, error) { + isos, meta, _, err := o.Base.Client.ISO.ListPublic(o.Base.Context, o.Base.Options) + return isos, meta, err +} diff --git a/cmd/iso/printer.go b/cmd/iso/printer.go new file mode 100644 index 00000000..ccb36b3a --- /dev/null +++ b/cmd/iso/printer.go @@ -0,0 +1,158 @@ +package iso + +import ( + "strconv" + + "github.com/vultr/govultr/v3" + "github.com/vultr/vultr-cli/v3/cmd/printer" +) + +// ISOsPrinter ... +type ISOsPrinter struct { + ISOs []govultr.ISO `json:"isos"` + Meta *govultr.Meta `json:"meta"` +} + +// JSON ... +func (i *ISOsPrinter) JSON() []byte { + return printer.MarshalObject(i, "json") +} + +// YAML ... +func (i *ISOsPrinter) YAML() []byte { + return printer.MarshalObject(i, "yaml") +} + +// Columns ... +func (i *ISOsPrinter) Columns() [][]string { + return [][]string{0: { + "ID", + "FILE NAME", + "SIZE", + "STATUS", + "MD5SUM", + "SHA512SUM", + "DATE CREATED", + }} +} + +// Data ... +func (i *ISOsPrinter) Data() [][]string { + if len(i.ISOs) == 0 { + return [][]string{0: {"---", "---", "---", "---", "---", "---", "---"}} + } + + var data [][]string + for n := range i.ISOs { + data = append(data, []string{ + i.ISOs[n].ID, + i.ISOs[n].FileName, + strconv.Itoa(i.ISOs[n].Size), + i.ISOs[n].Status, + i.ISOs[n].MD5Sum, + i.ISOs[n].SHA512Sum, + i.ISOs[n].DateCreated, + }) + } + + return data +} + +// Paging ... +func (i *ISOsPrinter) Paging() [][]string { + return printer.NewPaging(i.Meta.Total, &i.Meta.Links.Next, &i.Meta.Links.Prev).Compose() +} + +// ====================================== + +// ISOPrinter ... +type ISOPrinter struct { + ISO govultr.ISO `json:"iso"` +} + +// JSON ... +func (i *ISOPrinter) JSON() []byte { + return printer.MarshalObject(i, "json") +} + +// YAML ... +func (i *ISOPrinter) YAML() []byte { + return printer.MarshalObject(i, "yaml") +} + +// Columns ... +func (i *ISOPrinter) Columns() [][]string { + return [][]string{0: { + "ID", + "FILE NAME", + "SIZE", + "STATUS", + "MD5SUM", + "SHA512SUM", + "DATE CREATED", + }} +} + +// Data ... +func (i *ISOPrinter) Data() [][]string { + return [][]string{0: { + i.ISO.ID, + i.ISO.FileName, + strconv.Itoa(i.ISO.Size), + i.ISO.Status, + i.ISO.MD5Sum, + i.ISO.SHA512Sum, + i.ISO.DateCreated, + }} +} + +// Paging ... +func (i *ISOPrinter) Paging() [][]string { + return nil +} + +// ====================================== + +// PublicISOsPrinter ... +type PublicISOsPrinter struct { + ISOs []govultr.PublicISO `json:"public_isos"` + Meta *govultr.Meta `json:"meta"` +} + +// JSON ... +func (i *PublicISOsPrinter) JSON() []byte { + return printer.MarshalObject(i, "json") +} + +// YAML ... +func (i *PublicISOsPrinter) YAML() []byte { + return printer.MarshalObject(i, "yaml") +} + +// Columns ... +func (i *PublicISOsPrinter) Columns() [][]string { + return [][]string{0: {"ID", "NAME", "DESCRIPTION"}} +} + +// Data ... +func (i *PublicISOsPrinter) Data() [][]string { + if len(i.ISOs) == 0 { + return [][]string{0: {"---", "---", "---"}} + } + + var data [][]string + for n := range i.ISOs { + data = append(data, []string{ + i.ISOs[n].ID, + i.ISOs[n].Name, + i.ISOs[n].Description, + }) + } + + return data +} + +// Paging ... +func (i *PublicISOsPrinter) Paging() [][]string { + return printer.NewPaging(i.Meta.Total, &i.Meta.Links.Next, &i.Meta.Links.Prev).Compose() +} diff --git a/cmd/kubernetes/kubernetes.go b/cmd/kubernetes/kubernetes.go new file mode 100644 index 00000000..8b06a038 --- /dev/null +++ b/cmd/kubernetes/kubernetes.go @@ -0,0 +1,1143 @@ +// Package kubernetes provides functionality for the CLI to control VKE clusters +package kubernetes + +import ( + "encoding/base64" + "errors" + "fmt" + "os" + "path/filepath" + "strconv" + "strings" + + "github.com/spf13/cobra" + "github.com/vultr/govultr/v3" + "github.com/vultr/vultr-cli/v3/cmd/printer" + "github.com/vultr/vultr-cli/v3/cmd/utils" + "github.com/vultr/vultr-cli/v3/pkg/cli" +) + +var ( + long = `Get all available commands for Kubernetes` + example = ` + # Full example + vultr-cli kubernetes + ` + + createLong = `Create kubernetes cluster on your Vultr account` + createExample = ` + # Full Example + vultr-cli kubernetes create --label="my-cluster" --region="ewr" --version="v1.29.1+1" \ + --node-pools="quantity:3,plan:vc2-2c-4gb,label:my-nodepool,tag:my-tag" + + # Shortened with alias commands + vultr-cli k c -l="my-cluster" -r="ewr" -v="v1.29.1+1" -n="quantity:3,plan:vc2-2c-4gb,label:my-nodepool,tag:my-tag" + ` + + getLong = `Get a single kubernetes cluster from your account` + getExample = ` + # Full example + vultr-cli kubernetes get ffd31f18-5f77-454c-9064-212f942c3c34 + + # Shortened with alias commands + vultr-cli k g ffd31f18-5f77-454c-9064-212f942c3c34 + ` + + listLong = `Get all kubernetes clusters available on your Vultr account` + listExample = ` + # Full example + vultr-cli kubernetes list + + # Full example with paging + vultr-cli kubernetes list --per-page=1 --cursor="bmV4dF9fQU1T" + + # Shortened with alias commands + vultr-cli k l + + # Summarized view + vultr-cli kubernetes list --summarize + ` + + updateLong = `Update a specific kubernetes cluster on your Vultr Account` + updateExample = ` + # Full example + vultr-cli kubernetes update ffd31f18-5f77-454c-9065-212f942c3c35 --label="updated-label" + + # Shortened with alias commands + vultr-cli k u ffd31f18-5f77-454c-9065-212f942c3c35 -l="updated-label" + ` + + deleteLong = `Delete a specific kubernetes cluster off your Vultr Account` + deleteExample = ` + # Full example + vultr-cli kubernetes delete ffd31f18-5f77-454c-9065-212f942c3c35 + + # Shortened with alias commands + vultr-cli k d ffd31f18-5f77-454c-9065-212f942c3c35' + + # Delete a specific kubernetes cluster and all linked load balancers and block storages off your Vultr Account + vultr-cli kubernetes delete-with-resources ffd31f18-5f77-454c-9065-212f942c3c35 + ` + getConfigLong = `Returns a base64 encoded config of a specified kubernetes cluster on your Vultr Account` + getConfigExample = ` + + # Full example + vultr-cli kubernetes config ffd31f18-5f77-454c-9065-212f942c3c35 + vultr-cli kubernetes config ffd31f18-5f77-454c-9065-212f942c3c35 --output-file /your/path/ + + # Shortened with alias commands + vultr-cli k config ffd31f18-5f77-454c-9065-212f942c3c35 + vultr-cli k config ffd31f18-5f77-454c-9065-212f942c3c35 -o /your/path/ + ` + + getVersionsLong = `Returns a list of supported kubernetes versions you can deploy` + getVersionsExample = ` + # Full example + vultr-cli kubernetes versions + + # Shortened with alias commands + vultr-cli k v + ` + + upgradesLong = `Display available kubernetes upgrade commands` + upgradesExample = ` + # Full example + vultr-cli kubernetes upgrades + + # Shortened example with aliases + vultr-cli k e + ` + + getUpgradesLong = `Returns a list of available kubernetes version the cluster can be upgraded to` + getUpgradesExample = ` + # Full example + vultr-cli kubernetes upgrades list d4908765-b82a-4e7d-83d9-c0bc4c6a36d0 + + # Shortened with alias commands + vultr-cli k e l d4908765-b82a-4e7d-83d9-c0bc4c6a36d0 + ` + + upgradeLong = `Initiate an upgrade of the kubernetes version on a given cluster` + upgradeExample = ` + # Full example + vultr-cli kubernetes upgrades start d4908765-b82a-4e7d-83d9-c0bc4c6a36d0 --version="v1.23.5+3" + + # Shortened with alias commands + vultr-cli k e s d4908765-b82a-4e7d-83d9-c0bc4c6a36d0 -v="v1.23.5+3" + ` + + nodepoolLong = `Get all available commands for Kubernetes node pools` + nodepoolExample = ` + # Full example + vultr-cli kubernetes node-pool + + # Shortened with alias commands + vultr-cli k n + ` + + createNPLong = `Create node pool for your kubernetes cluster on your Vultr account` + createNPExample = ` + # Full Example + vultr-cli kubernetes node-pool create ffd31f18-5f77-454c-9064-212f942c3c34 --label="nodepool" --quantity=3 --plan="vc2-1c-2gb" + + # Shortened with alias commands + vultr-cli k n c ffd31f18-5f77-454c-9064-212f942c3c34 -l="nodepool" -q=3 -p="vc2-1c-2gb" + ` + + getNPLong = `Get a node pool in a single kubernetes cluster from your account` + getNPExample = ` + # Full example + vultr-cli kubernetes node-pool get ffd31f18-5f77-454c-9064-212f942c3c34 abd31f18-3f77-454c-9064-212f942c3c34 + # Shortened with alias commands + vultr-cli k n g ffd31f18-5f77-454c-9064-212f942c3c34 abd31f18-3f77-454c-9064-212f942c3c34 + ` + + listNPLong = `Get all nodepools from a kubernetes cluster on your Vultr account` + listNPExample = ` + # Full example + vultr-cli kubernetes node-pool list ffd31f18-5f77-454c-9064-212f942c3c34 + + # Full example with paging + vultr-cli kubernetes node-pool list ffd31f18-5f77-454c-9064-212f942c3c34 --per-page=1 --cursor="bmV4dF9fQU1T" + + # Shortened with alias commands + vultr-cli k n l ffd31f18-5f77-454c-9064-212f942c3c34 + ` + + updateNPLong = `Update a specific node pool in a kubernetes cluster on your Vultr Account` + updateNPExample = ` + # Full example + vultr-cli kubernetes node-pool update ffd31f18-5f77-454c-9064-212f942c3c34 abd31f18-3f77-454c-9064-212f942c3c34 --quantity=4 + + # Shortened with alias commands + vultr-cli k n u ffd31f18-5f77-454c-9065-212f942c3c35 abd31f18-3f77-454c-9064-212f942c3c34 --q=4 + ` + + deleteNPLong = `Delete a specific node pool in a kubernetes cluster off your Vultr Account` + deleteNPExample = ` + # Full example + vultr-cli kubernetes node-pool delete ffd31f18-5f77-454c-9065-212f942c3c35 abd31f18-3f77-454c-9064-212f942c3c34 + + # Shortened with alias commands + vultr-cli k n d ffd31f18-5f77-454c-9065-212f942c3c35 abd31f18-3f77-454c-9064-212f942c3c34' + ` + + nodeLong = `Get all available commands for Kubernetes node pool nodes` + nodeExample = ` + # Full example + vultr-cli kubernetes node-pool node + + # Shortened with alias commands + vultr-cli k n node + ` + + nodeDeleteLong = `Delete a specific node pool node in a kubernetes cluster` + nodeDeleteExample = ` + # Full example + vultr-cli kubernetes node-pool node delete ffd31f18-5f77-454c-9065-212f942c3c35 + + # Shortened with alias commands + vultr-cli k n node d ffd31f18-5f77-454c-9065-212f942c3c35 + ` + + nodeRecycleLong = `Recycles a specific node pool node in a kubernetes cluster` + nodeRecycleExample = ` + # Full example + vultr-cli kubernetes node-pool node recycle ffd31f18-5f77-454c-9065-212f942c3c35 + + # Shortened with alias commands + vultr-cli k n node r ffd31f18-5f77-454c-9065-212f942c3c35 + ` +) + +const ( + kubeconfigFilePermission = 0600 + kubeconfigDirPermission = 0755 +) + +// NewCmdKubernetes provides the CLI command for VKE functions +func NewCmdKubernetes(base *cli.Base) *cobra.Command { //nolint:funlen,gocyclo + o := &options{Base: base} + + cmd := &cobra.Command{ + Use: "kubernetes", + Aliases: []string{"k"}, + Short: "Access kubernetes cluster commands", + Long: long, + Example: example, + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + utils.SetOptions(o.Base, cmd, args) + if !o.Base.HasAuth { + return errors.New(utils.APIKeyError) + } + return nil + }, + } + + // List + list := &cobra.Command{ + Use: "list", + Short: "List kubernetes clusters", + Aliases: []string{"l"}, + Long: listLong, + Example: listExample, + RunE: func(cmd *cobra.Command, args []string) error { + o.Base.Options = utils.GetPaging(cmd) + + summarize, errSu := cmd.Flags().GetBool("summarize") + if errSu != nil { + return fmt.Errorf("error parsing flag 'summarize' for kubernetes list : %v", errSu) + } + + k8s, meta, err := o.list() + if err != nil { + return fmt.Errorf("error retrieving kubernetes clusters list : %v", err) + } + + var data printer.ResourceOutput + if summarize { + data = &ClustersSummaryPrinter{Clusters: k8s, Meta: meta} + } else { + data = &ClustersPrinter{Clusters: k8s, Meta: meta} + } + + o.Base.Printer.Display(data, nil) + + return nil + }, + } + + list.Flags().StringP("cursor", "c", "", "(optional) cursor for paging.") + list.Flags().IntP( + "per-page", + "p", + utils.PerPageDefault, + fmt.Sprintf("(optional) Number of items requested per page. Default is %d and Max is 500.", utils.PerPageDefault), + ) + list.Flags().BoolP("summarize", "", false, "(optional) Summarize the list output. One line per cluster.") + + // Get + get := &cobra.Command{ + Use: "get ", + Short: "Retrieves a kubernetes cluster", + Long: getLong, + Example: getExample, + Aliases: []string{"g"}, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("please provide a cluster ID") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + k8, err := o.get() + if err != nil { + return fmt.Errorf("error retrieving kubernetes cluster : %v", err) + } + + data := &ClusterPrinter{Cluster: k8} + o.Base.Printer.Display(data, nil) + + return nil + }, + } + + // Create + create := &cobra.Command{ + Use: "create", + Short: "Create kubernetes cluster", + Long: createLong, + Example: createExample, + Aliases: []string{"c"}, + RunE: func(cmd *cobra.Command, args []string) error { + label, errLa := cmd.Flags().GetString("label") + if errLa != nil { + return fmt.Errorf("error parsing flag 'label' for kubernetes cluster create : %v", errLa) + } + + region, errRe := cmd.Flags().GetString("region") + if errRe != nil { + return fmt.Errorf("error parsing flag 'region' for kubernetes cluster create : %v", errRe) + } + + nodepools, errNP := cmd.Flags().GetStringArray("node-pools") + if errNP != nil { + return fmt.Errorf("error parsing flag 'node-pools' for kubernetes cluster create : %v", errNP) + } + + version, errVe := cmd.Flags().GetString("version") + if errVe != nil { + return fmt.Errorf("error parsing flag 'version' for kubernetes cluster create : %v", errVe) + } + + ha, errHi := cmd.Flags().GetBool("high-avail") + if errHi != nil { + return fmt.Errorf("error parsing flag 'high-avail' for kubernetes cluster create : %v", errHi) + } + + nps, errFm := formatNodePools(nodepools) + if errFm != nil { + return fmt.Errorf("error in node pool formating : %v", errFm) + } + + o.CreateReq = &govultr.ClusterReq{ + Label: label, + Region: region, + NodePools: nps, + Version: version, + HAControlPlanes: ha, + } + + k8, err := o.create() + if err != nil { + return fmt.Errorf("error creating kubernetes cluster : %v", err) + } + + data := &ClusterPrinter{Cluster: k8} + o.Base.Printer.Display(data, nil) + + return nil + }, + } + + create.Flags().StringP("label", "l", "", "label for your kubernetes cluster") + if err := create.MarkFlagRequired("label"); err != nil { + fmt.Printf("error marking kubernetes create 'label' flag required: %v", err) + os.Exit(1) + } + + create.Flags().StringP("region", "r", "", "region you want your kubernetes cluster to be located in") + if err := create.MarkFlagRequired("region"); err != nil { + fmt.Printf("error marking kubernetes create 'region' flag required: %v", err) + os.Exit(1) + } + + create.Flags().StringP("version", "v", "", "the kubernetes version you want for your cluster") + if err := create.MarkFlagRequired("version"); err != nil { + fmt.Printf("error marking kubernetes create 'version' flag required: %v", err) + os.Exit(1) + } + + create.Flags().Bool( + "high-avail", + false, + `(optional, default false) whether or not the cluster should be deployed with multiple, +highly available, control planes`, + ) + + create.Flags().StringArrayP( + "node-pools", + "n", + []string{}, + `a comma-separated, key-value pair list of node pools. At least one node pool is required. At least one node is +required in node pool. Use / between each new node pool. E.g: +'plan:vhf-8c-32gb,label:mynodepool,tag:my-tag,quantity:3/plan:vhf-8c-32gb,label:mynodepool2,quantity:3`, + ) + if err := create.MarkFlagRequired("node-pools"); err != nil { + fmt.Printf("error marking kubernetes create 'ns-primary' flag required: %v", err) + os.Exit(1) + } + + // Update + update := &cobra.Command{ + Use: "update ", + Short: "Updates a kubernetes cluster", + Aliases: []string{"u"}, + Long: updateLong, + Example: updateExample, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("please provide a cluster ID") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + label, errLa := cmd.Flags().GetString("label") + if errLa != nil { + return fmt.Errorf("error parsing flag 'label' for kubernetes cluster update : %v", errLa) + } + + o.UpdateReq = &govultr.ClusterReqUpdate{ + Label: label, + } + + if err := o.update(); err != nil { + return fmt.Errorf("error updating kubernetes cluster : %v", err) + } + + o.Base.Printer.Display(printer.Info("Kubernetes cluster has been updated"), nil) + + return nil + }, + } + + update.Flags().StringP("label", "l", "", "label for your kubernetes cluster") + if err := update.MarkFlagRequired("label"); err != nil { + fmt.Printf("error marking kubernetes update 'label' flag required: %v", err) + os.Exit(1) + } + + // Delete + del := &cobra.Command{ + Use: "delete ", + Short: "Delete a kubernetes cluster", + Aliases: []string{"destroy", "d"}, + Long: deleteLong, + Example: deleteExample, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("please provide a cluster ID") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + withRes, errRe := cmd.Flags().GetBool("delete-resources") + if errRe != nil { + return fmt.Errorf("error parsing flag 'delete-resource' for kubernetes cluster delete: %v", errRe) + } + + if withRes { + if err := o.delWithRes(); err != nil { + return fmt.Errorf("error deleting kubernetes cluster and resources : %v", err) + } + } else { + if err := o.del(); err != nil { + return fmt.Errorf("error deleting kubernetes cluster : %v", err) + } + } + + o.Base.Printer.Display(printer.Info("Kubernetes cluster has been deleted"), nil) + + return nil + }, + } + + del.Flags().BoolP("delete-resources", "r", false, "delete a kubernetes cluster and related resources") + + // Config + config := &cobra.Command{ + Use: "config ", + Short: "Get a kubernetes cluster's config", + Long: getConfigLong, + Example: getConfigExample, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("please provide a clusterID") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + path, errPa := cmd.Flags().GetString("output-file") + if errPa != nil { + return fmt.Errorf("error parsing flag 'output-file' for kubernetes cluster config : %v", errPa) + } + + config, err := o.config() + if err != nil { + return fmt.Errorf("error retrieving kubernetes cluster config : %v", err) + } + + if path != "" { + dir := filepath.Dir(path) + if errDi := os.MkdirAll(dir, kubeconfigDirPermission); errDi != nil { + return fmt.Errorf("error creating directory for kubeconfig : %v", errDi) + } + + kubeConfigData, errDe := base64.StdEncoding.DecodeString(config.KubeConfig) + if errDe != nil { + return fmt.Errorf("error decoding kubeconfig : %v", errDe) + } + + if errWr := os.WriteFile(path, kubeConfigData, kubeconfigFilePermission); errWr != nil { + return fmt.Errorf("error writing kubeconfig to %s : %v", path, errWr) + } + } else { + data := &ConfigPrinter{Config: config} + o.Base.Printer.Display(data, nil) + } + + return nil + }, + } + + config.Flags().StringP("output-file", "o", "", "(optional) the file path to write kubeconfig to") + + // Versions + versions := &cobra.Command{ + Use: "versions", + Short: "List supported kubernetes versions", + Long: getVersionsLong, + Example: getVersionsExample, + Aliases: []string{"v"}, + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + // override parent pre-run auth check + utils.SetOptions(o.Base, cmd, args) + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + versions, err := o.versions() + if err != nil { + return fmt.Errorf("error retrieving list of kubernetes versions : %v", err) + } + + data := &VersionsPrinter{Versions: versions.Versions} + o.Base.Printer.Display(data, nil) + + return nil + }, + } + + // Upgrades + upgrades := &cobra.Command{ + Use: "upgrades", + Aliases: []string{"upgrade", "e"}, + Short: `Commands for kubernetes version upgrades`, + Long: upgradesLong, + Example: upgradesExample, + } + + // Upgrades List + upgradesList := &cobra.Command{ + Use: "list ", + Short: "Get available upgrades for a cluster", + Long: getUpgradesLong, + Example: getUpgradesExample, + Aliases: []string{"l"}, + RunE: func(cmd *cobra.Command, args []string) error { + upgrades, err := o.upgrades() + if err != nil { + return fmt.Errorf("error retrieving the available kubernetes upgrades : %v", err) + } + + data := &UpgradesPrinter{Upgrades: upgrades} + o.Base.Printer.Display(data, nil) + + return nil + }, + } + + // Upgrade start + upgradeStart := &cobra.Command{ + Use: "start ", + Short: "Perform upgrade on a cluster", + Long: upgradeLong, + Example: upgradeExample, + Aliases: []string{"s"}, + RunE: func(cmd *cobra.Command, args []string) error { + version, errVe := cmd.Flags().GetString("version") + if errVe != nil { + return fmt.Errorf("error parsing flag 'version' for kubernetes upgrade start : %v", errVe) + } + + o.UpgradeReq = &govultr.ClusterUpgradeReq{ + UpgradeVersion: version, + } + + if err := o.upgrade(); err != nil { + return fmt.Errorf("error starting the kubernetes upgrade : %v", err) + } + + o.Base.Printer.Display(printer.Info("Kubernetes upgrade has been intiated"), nil) + + return nil + }, + } + + upgradeStart.Flags().StringP("version", "v", "", "the version to upgrade the cluster to") + if err := upgradeStart.MarkFlagRequired("version"); err != nil { + fmt.Printf("error marking kubernetes upgrade 'version' flag required: %v", err) + os.Exit(1) + } + + upgrades.AddCommand( + upgradesList, + upgradeStart, + ) + + // Node Pools + nodepool := &cobra.Command{ + Use: "node-pool", + Aliases: []string{"n"}, + Short: "Commands for kubernetes cluster node pools", + Long: nodepoolLong, + Example: nodepoolExample, + } + + // Node Pools List + npList := &cobra.Command{ + Use: "list ", + Short: "List node pools", + Aliases: []string{"l"}, + Long: listNPLong, + Example: listNPExample, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("please provide a cluster ID") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + o.Base.Options = utils.GetPaging(cmd) + + nps, meta, err := o.nodePools() + if err != nil { + return fmt.Errorf("error getting node pool list : %v", err) + } + + data := &NodePoolsPrinter{NodePools: nps, Meta: meta} + o.Base.Printer.Display(data, nil) + + return nil + }, + } + + npList.Flags().StringP("cursor", "c", "", "(optional) cursor for paging.") + npList.Flags().IntP( + "per-page", + "p", + utils.PerPageDefault, + "(optional) Number of items requested per page. Default is 100 and Max is 500.", + ) + + // Node Pool Get + npGet := &cobra.Command{ + Use: "get ", + Short: "Get a node pool in kubernetes cluster", + Aliases: []string{"g"}, + Long: getNPLong, + Example: getNPExample, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 2 { + return errors.New("please provide a cluster ID and node pool ID") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + np, err := o.nodePool() + if err != nil { + return fmt.Errorf("error getting node pool : %v", err) + } + + data := &NodePoolPrinter{NodePool: np} + o.Base.Printer.Display(data, nil) + + return nil + }, + } + + // Node Pool Create + npCreate := &cobra.Command{ + Use: "create ", + Short: "Create a node pool in a kubernetes cluster", + Aliases: []string{"c"}, + Long: createNPLong, + Example: createNPExample, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("please provide a cluster ID") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + quantity, errQu := cmd.Flags().GetInt("quantity") + if errQu != nil { + return fmt.Errorf("error parsing flag 'quantity' for kubernetes cluster node pool create : %v", errQu) + } + + label, errLa := cmd.Flags().GetString("label") + if errLa != nil { + return fmt.Errorf("error parsing flag 'label' for kubernetes cluster node pool create : %v", errLa) + } + + tag, errTa := cmd.Flags().GetString("tag") + if errTa != nil { + return fmt.Errorf("error parsing flag 'tag' for kubernetes cluster node pool create : %v", errTa) + } + + plan, errPl := cmd.Flags().GetString("plan") + if errPl != nil { + return fmt.Errorf("error parsing flag 'plan' for kubernetes cluster node pool create : %v", errPl) + } + + autoscaler, errAu := cmd.Flags().GetBool("auto-scaler") + if errAu != nil { + return fmt.Errorf("error parsing flag 'auto-scaler' for kubernetes cluster node pool create : %v", errAu) + } + + minNodes, errMi := cmd.Flags().GetInt("min-nodes") + if errMi != nil { + return fmt.Errorf("error parsing flag 'min-nodes' for kubernetes cluster node pool create : %v", errMi) + } + + maxNodes, errMa := cmd.Flags().GetInt("max-nodes") + if errMa != nil { + return fmt.Errorf("error parsing flag 'max-nodes' for kubernetes cluster node pool create : %v", errMa) + } + + o.npCreateReq = &govultr.NodePoolReq{ + NodeQuantity: quantity, + Label: label, + Plan: plan, + Tag: tag, + AutoScaler: govultr.BoolToBoolPtr(false), + MinNodes: minNodes, + MaxNodes: maxNodes, + } + + if autoscaler { + o.npCreateReq.AutoScaler = govultr.BoolToBoolPtr(true) + } + + np, err := o.nodePoolCreate() + if err != nil { + return fmt.Errorf("error creating kubernetes cluster node pool : %v", err) + } + + data := &NodePoolPrinter{NodePool: np} + o.Base.Printer.Display(data, nil) + + return nil + }, + } + + npCreate.Flags().StringP("label", "l", "", "label you want for your node pool.") + if err := npCreate.MarkFlagRequired("label"); err != nil { + fmt.Printf("error marking kubernetes node-pool create 'label' flag required: %v\n", err) + os.Exit(1) + } + + npCreate.Flags().StringP("tag", "t", "", "tag you want for your node pool.") + + npCreate.Flags().StringP("plan", "p", "", "the plan you want for your node pool.") + if err := npCreate.MarkFlagRequired("plan"); err != nil { + fmt.Printf("error marking kubernetes node-pool create 'plan' flag required: %v\n", err) + os.Exit(1) + } + + npCreate.Flags().IntP("quantity", "q", 1, "Number of nodes in your node pool. Note that at least one node is required for a node pool.") + if err := npCreate.MarkFlagRequired("quantity"); err != nil { + fmt.Printf("error marking kubernetes node-pool create 'quantity' flag required: %v\n", err) + os.Exit(1) + } + + npCreate.Flags().BoolP("auto-scaler", "", false, "Enable the auto scaler with your cluster") + npCreate.Flags().IntP("min-nodes", "", 1, "Minimum nodes for auto scaler") + npCreate.Flags().IntP("max-nodes", "", 1, "Maximum nodes for auto scaler") + + // Node Pool Update + npUpdate := &cobra.Command{ + Use: "update ", + Short: "Update a cluster's node pool", + Aliases: []string{"u"}, + Long: updateNPLong, + Example: updateNPExample, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 2 { + return errors.New("please provide a cluster ID and node pool ID") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + quantity, errQu := cmd.Flags().GetInt("quantity") + if errQu != nil { + return fmt.Errorf("error parsing flag 'quantity' for kubernetes cluster node pool update : %v", errQu) + } + + tag, errTa := cmd.Flags().GetString("tag") + if errTa != nil { + return fmt.Errorf("error parsing flag 'tag' for kubernetes cluster node pool update : %v", errTa) + } + + autoscaler, errAu := cmd.Flags().GetBool("auto-scaler") + if errAu != nil { + return fmt.Errorf("error parsing flag 'auto-scaler' for kubernetes cluster node pool update : %v", errAu) + } + + minNodes, errMi := cmd.Flags().GetInt("min-nodes") + if errMi != nil { + return fmt.Errorf("error parsing flag 'min-nodes' for kubernetes cluster node pool update : %v", errMi) + } + + maxNodes, errMa := cmd.Flags().GetInt("max-nodes") + if errMa != nil { + return fmt.Errorf("error parsing flag 'max-nodes' for kubernetes cluster node pool update : %v", errMa) + } + + o.npUpdateReq = &govultr.NodePoolReqUpdate{} + + if cmd.Flags().Changed("quantity") { + o.npUpdateReq.NodeQuantity = quantity + } + + if cmd.Flags().Changed("auto-scaler") { + o.npUpdateReq.AutoScaler = govultr.BoolToBoolPtr(autoscaler) + } + + if cmd.Flags().Changed("tag") { + o.npUpdateReq.Tag = govultr.StringToStringPtr(tag) + } + + if cmd.Flags().Changed("min-nodes") { + o.npUpdateReq.MinNodes = minNodes + } + + if cmd.Flags().Changed("max-nodes") { + o.npUpdateReq.MaxNodes = maxNodes + } + + np, err := o.nodePoolUpdate() + if err != nil { + return fmt.Errorf("error updating kubernetes cluster node pool : %v", err) + } + + data := &NodePoolPrinter{NodePool: np} + o.Base.Printer.Display(data, nil) + + return nil + }, + } + + npUpdate.Flags().IntP( + "quantity", + "q", + 1, + "Number of nodes in your node pool. Note that at least one node is required for a node pool.", + ) + npUpdate.Flags().StringP("tag", "t", "", "The tag the node pool") + npUpdate.Flags().BoolP("auto-scaler", "", false, "Enable the auto scaler with your cluster") + npUpdate.Flags().IntP("min-nodes", "", 1, "Minimum nodes for auto scaler") + npUpdate.Flags().IntP("max-nodes", "", 1, "Maximum nodes for auto scaler") + + npUpdate.MarkFlagsOneRequired("quantity", "tag", "auto-scaler", "min-nodes", "max-nodes") + + // Node Pool Delete + npDelete := &cobra.Command{ + Use: "delete ", + Short: "Delete a cluster node pool", + Aliases: []string{"destroy", "d"}, + Long: deleteNPLong, + Example: deleteNPExample, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 2 { + return errors.New("please provide a cluster ID and node pool ID") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + if err := o.nodePoolDelete(); err != nil { + return fmt.Errorf("error deleting kubernetes cluster node pool : %v", err) + } + + o.Base.Printer.Display(printer.Info("Kubernetes node pool has been deleted"), nil) + + return nil + }, + } + + // Node + node := &cobra.Command{ + Use: "node", + Short: "delete/recycle instances in a cluster's node pool", + Long: nodeLong, + Example: nodeExample, + } + + // Node Pool Node Delete + nodeDelete := &cobra.Command{ + Use: "delete ", + Short: "Delete a node in a cluster node pool", + Aliases: []string{"destroy", "d"}, + Long: nodeDeleteLong, + Example: nodeDeleteExample, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 3 { + return errors.New("please provide a cluster ID, a node pool ID and a node ID") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + if err := o.nodePoolNodeDelete(); err != nil { + return fmt.Errorf("error deleting kubernetes cluster node pool node : %v", err) + } + + o.Base.Printer.Display(printer.Info("Kubernetes node pool node has been deleted"), nil) + + return nil + }, + } + + // Node Pool Node Recycle + nodeRecycle := &cobra.Command{ + Use: "recycle ", + Short: "Recycle a node in a cluster node pool", + Aliases: []string{"r"}, + Long: nodeRecycleLong, + Example: nodeRecycleExample, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 3 { + return errors.New("please provide a cluster ID, a node pool ID and a node ID") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + if err := o.nodePoolNodeRecycle(); err != nil { + return fmt.Errorf("error recycling kubernetes cluster node pool node : %v", err) + } + + o.Base.Printer.Display(printer.Info("Kubernetes node pool node has been recycled"), nil) + + return nil + }, + } + + node.AddCommand( + nodeDelete, + nodeRecycle, + ) + + nodepool.AddCommand( + npList, + npGet, + npCreate, + npUpdate, + npDelete, + node, + ) + + cmd.AddCommand( + list, + get, + create, + update, + del, + config, + nodepool, + versions, + upgrades, + ) + + return cmd +} + +// formatNodePools parses node pools into proper format +func formatNodePools(nodePools []string) ([]govultr.NodePoolReq, error) { + var formattedList []govultr.NodePoolReq + npList := strings.Split(nodePools[0], "/") + + for _, r := range npList { + nodeData := strings.Split(r, ",") + + if len(nodeData) < 3 || len(nodeData) > 7 { + return nil, fmt.Errorf( + `unable to format node pool. each node pool must include label, quantity, and plan. + Optionally you can include tag, auto-scaler, min-nodes and max-nodes`, + ) + } + + formattedNodeData, errFormat := formatNodeData(nodeData) + if errFormat != nil { + return nil, errFormat + } + + formattedList = append(formattedList, *formattedNodeData) + } + + return formattedList, nil +} + +// formatNodeData loops over the parse strings for a node and returns the formatted struct +func formatNodeData(node []string) (*govultr.NodePoolReq, error) { + nodeData := &govultr.NodePoolReq{} + for _, f := range node { + nodeDataKeyVal := strings.Split(f, ":") + + if len(nodeDataKeyVal) != 2 && len(nodeDataKeyVal) != 3 { + return nil, fmt.Errorf("invalid node pool format") + } + + field := nodeDataKeyVal[0] + val := nodeDataKeyVal[1] + + switch { + case field == "plan": + nodeData.Plan = val + case field == "quantity": + port, err := strconv.Atoi(val) + if err != nil { + return nil, fmt.Errorf("invalid value for node pool quantity: %v", err) + } + nodeData.NodeQuantity = port + case field == "label": + nodeData.Label = val + case field == "tag": + nodeData.Tag = val + case field == "auto-scaler": + v, err := strconv.ParseBool(val) + if err != nil { + return nil, fmt.Errorf("invalid value for node pool auto-scaler: %v", err) + } + nodeData.AutoScaler = govultr.BoolToBoolPtr(v) + case field == "min-nodes": + v, err := strconv.Atoi(val) + if err != nil { + return nil, fmt.Errorf("invalid value for node pool min-nodes: %v", err) + } + nodeData.MinNodes = v + case field == "max-nodes": + v, err := strconv.Atoi(val) + if err != nil { + return nil, fmt.Errorf("invalid value for max-nodes: %v", err) + } + nodeData.MaxNodes = v + } + } + + return nodeData, nil +} + +type options struct { + Base *cli.Base + CreateReq *govultr.ClusterReq + UpdateReq *govultr.ClusterReqUpdate + UpgradeReq *govultr.ClusterUpgradeReq + npCreateReq *govultr.NodePoolReq + npUpdateReq *govultr.NodePoolReqUpdate +} + +func (o *options) list() ([]govultr.Cluster, *govultr.Meta, error) { + k8s, meta, _, err := o.Base.Client.Kubernetes.ListClusters(o.Base.Context, o.Base.Options) + return k8s, meta, err +} + +func (o *options) get() (*govultr.Cluster, error) { + k8, _, err := o.Base.Client.Kubernetes.GetCluster(o.Base.Context, o.Base.Args[0]) + return k8, err +} + +func (o *options) create() (*govultr.Cluster, error) { + k8, _, err := o.Base.Client.Kubernetes.CreateCluster(o.Base.Context, o.CreateReq) + return k8, err +} + +func (o *options) update() error { + return o.Base.Client.Kubernetes.UpdateCluster(o.Base.Context, o.Base.Args[0], o.UpdateReq) +} + +func (o *options) del() error { + return o.Base.Client.Kubernetes.DeleteCluster(o.Base.Context, o.Base.Args[0]) +} + +func (o *options) delWithRes() error { + return o.Base.Client.Kubernetes.DeleteClusterWithResources(o.Base.Context, o.Base.Args[0]) +} + +func (o *options) config() (*govultr.KubeConfig, error) { + kc, _, err := o.Base.Client.Kubernetes.GetKubeConfig(o.Base.Context, o.Base.Args[0]) + return kc, err +} + +func (o *options) versions() (*govultr.Versions, error) { + versions, _, err := o.Base.Client.Kubernetes.GetVersions(o.Base.Context) + return versions, err +} + +func (o *options) upgrades() ([]string, error) { + ups, _, err := o.Base.Client.Kubernetes.GetUpgrades(o.Base.Context, o.Base.Args[0]) + return ups, err +} + +func (o *options) upgrade() error { + return o.Base.Client.Kubernetes.Upgrade(o.Base.Context, o.Base.Args[0], o.UpgradeReq) +} + +func (o *options) nodePools() ([]govultr.NodePool, *govultr.Meta, error) { + nps, meta, _, err := o.Base.Client.Kubernetes.ListNodePools(o.Base.Context, o.Base.Args[0], o.Base.Options) + return nps, meta, err +} + +func (o *options) nodePool() (*govultr.NodePool, error) { + np, _, err := o.Base.Client.Kubernetes.GetNodePool(o.Base.Context, o.Base.Args[0], o.Base.Args[1]) + return np, err +} + +func (o *options) nodePoolCreate() (*govultr.NodePool, error) { + np, _, err := o.Base.Client.Kubernetes.CreateNodePool(o.Base.Context, o.Base.Args[0], o.npCreateReq) + return np, err +} + +func (o *options) nodePoolUpdate() (*govultr.NodePool, error) { + np, _, err := o.Base.Client.Kubernetes.UpdateNodePool(o.Base.Context, o.Base.Args[0], o.Base.Args[1], o.npUpdateReq) + return np, err +} + +func (o *options) nodePoolDelete() error { + return o.Base.Client.Kubernetes.DeleteNodePool(o.Base.Context, o.Base.Args[0], o.Base.Args[1]) +} + +func (o *options) nodePoolNodeDelete() error { + return o.Base.Client.Kubernetes.DeleteNodePoolInstance(o.Base.Context, o.Base.Args[0], o.Base.Args[1], o.Base.Args[2]) +} + +func (o *options) nodePoolNodeRecycle() error { + return o.Base.Client.Kubernetes.RecycleNodePoolInstance(o.Base.Context, o.Base.Args[0], o.Base.Args[1], o.Base.Args[2]) +} diff --git a/cmd/kubernetes/printer.go b/cmd/kubernetes/printer.go new file mode 100644 index 00000000..e567e5bc --- /dev/null +++ b/cmd/kubernetes/printer.go @@ -0,0 +1,504 @@ +package kubernetes + +import ( + "strconv" + + "github.com/vultr/govultr/v3" + "github.com/vultr/vultr-cli/v3/cmd/printer" +) + +// ClustersSummaryPrinter ... +type ClustersSummaryPrinter struct { + Clusters []govultr.Cluster `json:"vke_clusters"` + Meta *govultr.Meta `json:"meta"` +} + +// JSON ... +func (c *ClustersSummaryPrinter) JSON() []byte { + return printer.MarshalObject(c, "json") +} + +// YAML ... +func (c *ClustersSummaryPrinter) YAML() []byte { + return printer.MarshalObject(c, "yaml") +} + +// Columns ... +func (c *ClustersSummaryPrinter) Columns() [][]string { + return [][]string{0: { + "ID", + "LABEL", + "STATUS", + "REGION", + "VERSION", + "NODEPOOL#", + "NODE#", + }} +} + +// Data ... +func (c *ClustersSummaryPrinter) Data() [][]string { + if len(c.Clusters) == 0 { + return [][]string{0: {"---", "---", "---", "---", "---", "---", "---"}} + } + + var data [][]string + for i := range c.Clusters { + nodePoolCount := len(c.Clusters[i].NodePools) + var nodeCount int = 0 + + for j := range c.Clusters[i].NodePools { + nodeCount += len(c.Clusters[i].NodePools[j].Nodes) + } + + data = append(data, []string{ + c.Clusters[i].ID, + c.Clusters[i].Label, + c.Clusters[i].Status, + c.Clusters[i].Region, + c.Clusters[i].Version, + strconv.Itoa(nodePoolCount), + strconv.Itoa(nodeCount), + }) + } + + return data +} + +// Paging ... +func (c *ClustersSummaryPrinter) Paging() [][]string { + return printer.NewPaging(c.Meta.Total, &c.Meta.Links.Next, &c.Meta.Links.Prev).Compose() +} + +// ====================================== + +// ClustersPrinter ... +type ClustersPrinter struct { + Clusters []govultr.Cluster `json:"vke_clusters"` + Meta *govultr.Meta `json:"meta"` +} + +// JSON ... +func (c *ClustersPrinter) JSON() []byte { + return printer.MarshalObject(c, "json") +} + +// YAML ... +func (c *ClustersPrinter) YAML() []byte { + return printer.MarshalObject(c, "yaml") +} + +// Columns ... +func (c *ClustersPrinter) Columns() [][]string { + return nil +} + +// Data ... +func (c *ClustersPrinter) Data() [][]string { + if len(c.Clusters) == 0 { + return [][]string{0: {"No active kubernetes clusters"}} + } + + var data [][]string + for i := range c.Clusters { + data = append(data, + []string{"---------------------------"}, + []string{"ID", c.Clusters[i].ID}, + []string{"LABEL", c.Clusters[i].Label}, + []string{"DATE CREATED", c.Clusters[i].DateCreated}, + []string{"CLUSTER SUBNET", c.Clusters[i].ClusterSubnet}, + []string{"SERVICE SUBNET", c.Clusters[i].ServiceSubnet}, + []string{"IP", c.Clusters[i].IP}, + []string{"ENDPOINT", c.Clusters[i].Endpoint}, + []string{"HIGH AVAIL", strconv.FormatBool(c.Clusters[i].HAControlPlanes)}, + []string{"VERSION", c.Clusters[i].Version}, + []string{"REGION", c.Clusters[i].Region}, + []string{"STATUS", c.Clusters[i].Status}, + []string{" "}, + []string{"NODE POOLS"}, + ) + + for j := range c.Clusters[i].NodePools { + data = append(data, + []string{"ID", c.Clusters[i].NodePools[j].ID}, + []string{"DATE CREATED", c.Clusters[i].NodePools[j].DateCreated}, + []string{"DATE UPDATED", c.Clusters[i].NodePools[j].DateUpdated}, + []string{"LABEL", c.Clusters[i].NodePools[j].Label}, + []string{"TAG", c.Clusters[i].NodePools[j].Tag}, + []string{"PLAN", c.Clusters[i].NodePools[j].Plan}, + []string{"STATUS", c.Clusters[i].NodePools[j].Status}, + []string{"NODE QUANTITY", strconv.Itoa(c.Clusters[i].NodePools[j].NodeQuantity)}, + []string{"AUTO SCALER", strconv.FormatBool(c.Clusters[i].NodePools[j].AutoScaler)}, + []string{"MIN NODES", strconv.Itoa(c.Clusters[i].NodePools[j].MinNodes)}, + []string{"MAX NODES", strconv.Itoa(c.Clusters[i].NodePools[j].MaxNodes)}, + []string{" "}, + []string{"NODES"}, + ) + + if len(c.Clusters[i].NodePools[j].Nodes) != 0 { + // Shouldn't ever be zero + data = append(data, []string{"ID", "DATE CREATED", "LABEL", "STATUS"}) + } + + for k := range c.Clusters[i].NodePools[j].Nodes { + data = append(data, + []string{ + c.Clusters[i].NodePools[j].Nodes[k].ID, + c.Clusters[i].NodePools[j].Nodes[k].DateCreated, + c.Clusters[i].NodePools[j].Nodes[k].Label, + c.Clusters[i].NodePools[j].Nodes[k].Status, + }, + ) + } + + data = append(data, []string{" "}) + } + } + + return data +} + +// Paging ... +func (c *ClustersPrinter) Paging() [][]string { + return printer.NewPaging(c.Meta.Total, &c.Meta.Links.Next, &c.Meta.Links.Prev).Compose() +} + +// ====================================== + +// ClusterPrinter ... +type ClusterPrinter struct { + Cluster *govultr.Cluster `json:"vke_cluster"` +} + +// JSON ... +func (c *ClusterPrinter) JSON() []byte { + return printer.MarshalObject(c, "json") +} + +// YAML ... +func (c *ClusterPrinter) YAML() []byte { + return printer.MarshalObject(c, "yaml") +} + +// Columns ... +func (c *ClusterPrinter) Columns() [][]string { + return nil +} + +// Data ... +func (c *ClusterPrinter) Data() [][]string { + var data [][]string + data = append(data, + []string{"ID", c.Cluster.ID}, + []string{"LABEL", c.Cluster.Label}, + []string{"DATE CREATED", c.Cluster.DateCreated}, + []string{"CLUSTER SUBNET", c.Cluster.ClusterSubnet}, + []string{"SERVICE SUBNET", c.Cluster.ServiceSubnet}, + []string{"IP", c.Cluster.IP}, + []string{"ENDPOINT", c.Cluster.Endpoint}, + []string{"HIGH AVAIL", strconv.FormatBool(c.Cluster.HAControlPlanes)}, + []string{"VERSION", c.Cluster.Version}, + []string{"REGION", c.Cluster.Region}, + []string{"STATUS", c.Cluster.Status}, + []string{" "}, + []string{"NODE POOLS"}, + ) + + for i := range c.Cluster.NodePools { + data = append(data, + []string{"ID", c.Cluster.NodePools[i].ID}, + []string{"DATE CREATED", c.Cluster.NodePools[i].DateCreated}, + []string{"DATE UPDATED", c.Cluster.NodePools[i].DateUpdated}, + []string{"LABEL", c.Cluster.NodePools[i].Label}, + []string{"TAG", c.Cluster.NodePools[i].Tag}, + []string{"PLAN", c.Cluster.NodePools[i].Plan}, + []string{"STATUS", c.Cluster.NodePools[i].Status}, + []string{"NODE QUANTITY", strconv.Itoa(c.Cluster.NodePools[i].NodeQuantity)}, + []string{"AUTO SCALER", strconv.FormatBool(c.Cluster.NodePools[i].AutoScaler)}, + []string{"MIN NODES", strconv.Itoa(c.Cluster.NodePools[i].MinNodes)}, + []string{"MAX NODES", strconv.Itoa(c.Cluster.NodePools[i].MaxNodes)}, + []string{" "}, + []string{"NODES"}, + ) + + if len(c.Cluster.NodePools[i].Nodes) != 0 { + // Shouldn't ever be zero + data = append(data, []string{"ID", "DATE CREATED", "LABEL", "STATUS"}) + } + + for j := range c.Cluster.NodePools[i].Nodes { + data = append(data, + []string{ + c.Cluster.NodePools[i].Nodes[j].ID, + c.Cluster.NodePools[i].Nodes[j].DateCreated, + c.Cluster.NodePools[i].Nodes[j].Label, + c.Cluster.NodePools[i].Nodes[j].Status, + }, + ) + } + + data = append(data, []string{" "}) + } + + return data +} + +// Paging ... +func (c *ClusterPrinter) Paging() [][]string { + return nil +} + +// ====================================== + +// NodePoolsPrinter ... +type NodePoolsPrinter struct { + NodePools []govultr.NodePool `json:"node_pools"` + Meta *govultr.Meta `json:"meta"` +} + +// JSON ... +func (n *NodePoolsPrinter) JSON() []byte { + return printer.MarshalObject(n, "json") +} + +// YAML ... +func (n *NodePoolsPrinter) YAML() []byte { + return printer.MarshalObject(n, "yaml") +} + +// Columns ... +func (n *NodePoolsPrinter) Columns() [][]string { + return nil +} + +// Data ... +func (n *NodePoolsPrinter) Data() [][]string { + if len(n.NodePools) == 0 { + // this shouldn't be possible since at least one nodepool is required + return [][]string{0: {"No active nodepools on cluster"}} + } + + var data [][]string + for i := range n.NodePools { + data = append(data, + []string{"---------------------------"}, + []string{"ID", n.NodePools[i].ID}, + []string{"DATE CREATED", n.NodePools[i].DateCreated}, + []string{"DATE UPDATED", n.NodePools[i].DateUpdated}, + []string{"LABEL", n.NodePools[i].Label}, + []string{"TAG", n.NodePools[i].Tag}, + []string{"PLAN", n.NodePools[i].Plan}, + []string{"STATUS", n.NodePools[i].Status}, + []string{"NODE QUANTITY", strconv.Itoa(n.NodePools[i].NodeQuantity)}, + []string{"AUTO SCALER", strconv.FormatBool(n.NodePools[i].AutoScaler)}, + []string{"MIN NODES", strconv.Itoa(n.NodePools[i].MinNodes)}, + []string{"MAX NODES", strconv.Itoa(n.NodePools[i].MaxNodes)}, + []string{" "}, + []string{"NODES"}, + ) + + if len(n.NodePools[i].Nodes) != 0 { + // Shouldn't ever be zero + data = append(data, []string{"ID", "DATE CREATED", "LABEL", "STATUS"}) + } + + for j := range n.NodePools[i].Nodes { + data = append(data, + []string{ + n.NodePools[i].Nodes[j].ID, + n.NodePools[i].Nodes[j].DateCreated, + n.NodePools[i].Nodes[j].Label, + n.NodePools[i].Nodes[j].Status, + }, + ) + } + } + + return data +} + +// Paging ... +func (n *NodePoolsPrinter) Paging() [][]string { + return printer.NewPaging(n.Meta.Total, &n.Meta.Links.Next, &n.Meta.Links.Prev).Compose() +} + +// ====================================== + +// NodePoolPrinter ... +type NodePoolPrinter struct { + NodePool *govultr.NodePool `json:"node_pool"` +} + +// JSON ... +func (n *NodePoolPrinter) JSON() []byte { + return printer.MarshalObject(n, "json") +} + +// YAML ... +func (n *NodePoolPrinter) YAML() []byte { + return printer.MarshalObject(n, "yaml") +} + +// Columns ... +func (n *NodePoolPrinter) Columns() [][]string { + return nil +} + +// Data ... +func (n *NodePoolPrinter) Data() [][]string { + var data [][]string + data = append(data, + []string{"ID", n.NodePool.ID}, + []string{"DATE CREATED", n.NodePool.DateCreated}, + []string{"DATE UPDATED", n.NodePool.DateUpdated}, + []string{"LABEL", n.NodePool.Label}, + []string{"TAG", n.NodePool.Tag}, + []string{"PLAN", n.NodePool.Plan}, + []string{"STATUS", n.NodePool.Status}, + []string{"NODE QUANTITY", strconv.Itoa(n.NodePool.NodeQuantity)}, + []string{"AUTO SCALER", strconv.FormatBool(n.NodePool.AutoScaler)}, + []string{"MIN NODES", strconv.Itoa(n.NodePool.MinNodes)}, + []string{"MAX NODES", strconv.Itoa(n.NodePool.MaxNodes)}, + []string{" "}, + []string{"NODES"}, + ) + + if len(n.NodePool.Nodes) != 0 { + // Shouldn't ever be zero + data = append(data, []string{"ID", "DATE CREATED", "LABEL", "STATUS"}) + } + + for i := range n.NodePool.Nodes { + data = append(data, + []string{ + n.NodePool.Nodes[i].ID, + n.NodePool.Nodes[i].DateCreated, + n.NodePool.Nodes[i].Label, + n.NodePool.Nodes[i].Status, + }, + ) + } + + return data +} + +// Paging ... +func (n *NodePoolPrinter) Paging() [][]string { + return nil +} + +// ====================================== + +// VersionsPrinter ... +type VersionsPrinter struct { + Versions []string `json:"versions"` +} + +// JSON ... +func (v *VersionsPrinter) JSON() []byte { + return printer.MarshalObject(v, "json") +} + +// YAML ... +func (v *VersionsPrinter) YAML() []byte { + return printer.MarshalObject(v, "yaml") +} + +// Columns ... +func (v *VersionsPrinter) Columns() [][]string { + return [][]string{0: {"VERSIONS"}} +} + +// Data ... +func (v *VersionsPrinter) Data() [][]string { + if len(v.Versions) == 0 { + return [][]string{0: {"---"}} + } + + var data [][]string + + for i := range v.Versions { + data = append(data, []string{v.Versions[i]}) + } + + return data +} + +// Paging ... +func (v *VersionsPrinter) Paging() [][]string { + return nil +} + +// ====================================== + +// UpgradesPrinter ... +type UpgradesPrinter struct { + Upgrades []string `json:"available_upgrades"` +} + +// JSON ... +func (u *UpgradesPrinter) JSON() []byte { + return printer.MarshalObject(u, "json") +} + +// YAML ... +func (u *UpgradesPrinter) YAML() []byte { + return printer.MarshalObject(u, "yaml") +} + +// Columns ... +func (u *UpgradesPrinter) Columns() [][]string { + return [][]string{0: {"UPGRADES"}} +} + +// Data ... +func (u *UpgradesPrinter) Data() [][]string { + if len(u.Upgrades) == 0 { + return [][]string{0: {"---"}} + } + + var data [][]string + + for i := range u.Upgrades { + data = append(data, []string{u.Upgrades[i]}) + } + + return data +} + +// Paging ... +func (u *UpgradesPrinter) Paging() [][]string { + return nil +} + +// ====================================== + +// ConfigPrinter ... +type ConfigPrinter struct { + Config *govultr.KubeConfig +} + +// JSON ... +func (c *ConfigPrinter) JSON() []byte { + return printer.MarshalObject(c, "json") +} + +// YAML ... +func (c *ConfigPrinter) YAML() []byte { + return printer.MarshalObject(c, "yaml") +} + +// Columns ... +func (c *ConfigPrinter) Columns() [][]string { + return nil +} + +// Data ... +func (c *ConfigPrinter) Data() [][]string { + return [][]string{0: {c.Config.KubeConfig}} +} + +// Paging ... +func (c *ConfigPrinter) Paging() [][]string { + return nil +} diff --git a/cmd/loadBalancer.go b/cmd/loadBalancer.go deleted file mode 100644 index 019d9220..00000000 --- a/cmd/loadBalancer.go +++ /dev/null @@ -1,538 +0,0 @@ -// Copyright © 2020 The Vultr-cli Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package cmd - -import ( - "context" - "errors" - "fmt" - "os" - "strconv" - "strings" - - "github.com/spf13/cobra" - "github.com/vultr/govultr/v2" - "github.com/vultr/vultr-cli/cmd/printer" -) - -// LoadBalancer represents the load-balancer command -func LoadBalancer() *cobra.Command { - - lbCmd := &cobra.Command{ - Use: "load-balancer", - Aliases: []string{"lb"}, - Short: "load balancer commands", - Long: `load-balancer is used to interact with the load-balancer api`, - } - - lbCmd.AddCommand(lbCreate, lbDelete, lbGet, lbList, lbUpdate) - - // Create - lbCreate.Flags().StringP("region", "r", "", "region id you wish to have the load balancer created in") - lbCreate.MarkFlagRequired("region") - - lbCreate.Flags().StringP("balancing-algorithm", "b", "roundrobin", "(optional) balancing algorithm that determines server selection | roundrobin or leastconn") - lbCreate.Flags().StringP("ssl-redirect", "s", "", "(optional) if true, this will redirect HTTP traffic to HTTPS. You must have an HTTPS rule and SSL certificate installed on the load balancer to enable this option.") - lbCreate.Flags().StringP("proxy-protocol", "p", "", "(optional) if true, you must configure backend nodes to accept Proxy protocol.") - lbCreate.Flags().StringArrayP("forwarding-rules", "f", []string{}, "(optional) a comma-separated, key-value pair list of forwarding rules. Use - between each new rule. E.g: `frontend_port:80,frontend_protocol:http,backend_port:80,backend_protocol:http-frontend_port:81,frontend_protocol:http,backend_port:81,backend_protocol:http`") - - lbCreate.Flags().String("protocol", "http", "(optional) the protocol to use for health checks. | https, http, tcp") - lbCreate.Flags().Int("port", 80, "(optional) the port to use for health checks.") - lbCreate.Flags().String("path", "/", "(optional) HTTP Path to check. only applies if protocol is HTTP or HTTPS.") - lbCreate.Flags().IntP("check-interval", "c", 15, "(optional) interval between health checks.") - lbCreate.Flags().IntP("response-timeout", "t", 15, "(optional) timeout before health check fails.") - lbCreate.Flags().IntP("unhealthy-threshold", "u", 15, "(optional) number times a check must fail before becoming unhealthy.") - lbCreate.Flags().Int("healthy-threshold", 15, "(optional) number times a check must succeed before returning to healthy status.") - - lbCreate.Flags().String("cookie-name", "", "(optional) the cookie name to make sticky.") - - lbCreate.Flags().String("private-key", "", "(optional) the private key component for a ssl certificate.") - lbCreate.Flags().String("certificate", "", "(optional) the SSL certificate.") - lbCreate.Flags().String("certificate-chain", "", "(optional) the certificate chain for a ssl certificate.") - - lbCreate.Flags().StringP("label", "l", "", "(optional) the label for your load balancer.") - lbCreate.Flags().StringSliceP("instances", "i", []string{}, "(optional) an array of instances IDs that you want attached to the load balancer.") - - // List - lbList.Flags().StringP("cursor", "c", "", "(optional) cursor for paging.") - lbList.Flags().IntP("per-page", "p", 100, "(optional) Number of items requested per page. Default is 100 and Max is 500.") - - // Update - lbUpdate.Flags().StringP("balancing-algorithm", "b", "roundrobin", "(optional) balancing algorithm that determines server selection | roundrobin or leastconn") - lbUpdate.Flags().StringP("ssl-redirect", "s", "", "(optional) if true, this will redirect HTTP traffic to HTTPS. You must have an HTTPS rule and SSL certificate installed on the load balancer to enable this option.") - lbUpdate.Flags().StringP("proxy-protocol", "p", "", "(optional) if true, you must configure backend nodes to accept Proxy protocol.") - lbUpdate.Flags().StringArrayP("forwarding-rules", "f", []string{}, "(optional) a comma-separated, key-value pair list of forwarding rules. Use - between each new rule. E.g: `frontend_port:80,frontend_protocol:http,backend_port:80,backend_protocol:http-frontend_port:81,frontend_protocol:http,backend_port:81,backend_protocol:http`") - - lbUpdate.Flags().String("protocol", "", "(optional) the protocol to use for health checks. | https, http, tcp") - lbUpdate.Flags().Int("port", 0, "(optional) the port to use for health checks.") - lbUpdate.Flags().String("path", "", "(optional) HTTP Path to check. only applies if protocol is HTTP or HTTPS.") - lbUpdate.Flags().IntP("check-interval", "c", 0, "(optional) interval between health checks.") - lbUpdate.Flags().IntP("response-timeout", "t", 0, "(optional) timeout before health check fails.") - lbUpdate.Flags().IntP("unhealthy-threshold", "u", 0, "(optional) number times a check must fail before becoming unhealthy.") - lbUpdate.Flags().Int("healthy-threshold", 0, "(optional) number times a check must succeed before returning to healthy status.") - - lbUpdate.Flags().String("cookie-name", "", "(optional) the cookie name to make sticky.") - - lbUpdate.Flags().String("private-key", "", "(optional) the private key component for a ssl certificate.") - lbUpdate.Flags().String("certificate", "", "(optional) the SSL certificate.") - lbUpdate.Flags().String("certificate-chain", "", "(optional) the certificate chain for a ssl certificate.") - - lbUpdate.Flags().StringP("label", "l", "", "(optional) the label for your load balancer.") - lbUpdate.Flags().StringSliceP("instances", "i", []string{}, "(optional) an array of instances IDs that you want attached to the load balancer.") - - // Rules SubCommands - rulesCmd := &cobra.Command{ - Use: "rule", - Short: "create/delete/list rules for an load balancer", - Long: ``, - } - - // rule list - ruleList.Flags().StringP("cursor", "c", "", "(optional) cursor for paging.") - ruleList.Flags().IntP("per-page", "p", 100, "(optional) Number of items requested per page. Default is 100 and Max is 500.") - - // rule create - ruleCreate.Flags().String("frontend-protocol", "http", "the protocol on the Load Balancer to forward to the backend. | HTTP, HTTPS, TCP") - ruleCreate.Flags().String("backend-protocol", "http", "the protocol destination on the backend server. | HTTP, HTTPS, TCP") - ruleCreate.Flags().Int("frontend-port", 80, "the port number on the Load Balancer to forward to the backend.") - ruleCreate.Flags().Int("backend-port", 80, "the port number destination on the backend server.") - - ruleCreate.MarkFlagRequired("frontend-protocol") - ruleCreate.MarkFlagRequired("backend-protocol") - ruleCreate.MarkFlagRequired("frontend-port") - ruleCreate.MarkFlagRequired("backend-port") - - rulesCmd.AddCommand(ruleCreate, ruleDelete, ruleGet, ruleList) - lbCmd.AddCommand(rulesCmd) - - return lbCmd -} - -var lbCreate = &cobra.Command{ - Use: "create", - Short: "create a load balancer", - Long: ``, - Run: func(cmd *cobra.Command, args []string) { - region, _ := cmd.Flags().GetString("region") - label, _ := cmd.Flags().GetString("label") - - algorithm, _ := cmd.Flags().GetString("balancing-algorithm") - sslRedirect, _ := cmd.Flags().GetString("ssl-redirect") - proxyProtocol, _ := cmd.Flags().GetString("proxy-protocol") - - fwRules, _ := cmd.Flags().GetStringArray("forwarding-rules") - - protocol, _ := cmd.Flags().GetString("protocol") - port, _ := cmd.Flags().GetInt("port") - path, _ := cmd.Flags().GetString("path") - checkInterval, _ := cmd.Flags().GetInt("check-interval") - responseTimeout, _ := cmd.Flags().GetInt("response-timeout") - unhealthyThreshold, _ := cmd.Flags().GetInt("unhealthy-threshold") - healthyThreshold, _ := cmd.Flags().GetInt("healthy-threshold") - - privateKey, _ := cmd.Flags().GetString("private-key") - certificate, _ := cmd.Flags().GetString("certificate") - certificateChain, _ := cmd.Flags().GetString("certificate-chain") - - cookieName, _ := cmd.Flags().GetString("cookie-name") - instances, _ := cmd.Flags().GetStringSlice("instances") - - healthCheck := &govultr.HealthCheck{ - Protocol: protocol, - Path: path, - Port: port, - CheckInterval: checkInterval, - ResponseTimeout: responseTimeout, - UnhealthyThreshold: unhealthyThreshold, - HealthyThreshold: healthyThreshold, - } - - ssl := &govultr.SSL{ - PrivateKey: privateKey, - Certificate: certificate, - Chain: certificateChain, - } - - options := &govultr.LoadBalancerReq{ - Region: region, - Label: label, - BalancingAlgorithm: algorithm, - HealthCheck: healthCheck, - SSL: ssl, - } - - if cookieName != "" { - options.StickySessions = &govultr.StickySessions{ - CookieName: cookieName, - } - } - - rules, err := formatFWRules(fwRules) - if err != nil { - fmt.Printf("error creating load balancer : %v\n", err) - os.Exit(1) - } - - if len(rules) > 0 { - options.ForwardingRules = rules - } - - if sslRedirect == "yes" { - options.SSLRedirect = govultr.BoolToBoolPtr(true) - } - - if proxyProtocol == "yes" { - options.ProxyProtocol = govultr.BoolToBoolPtr(true) - } - - if len(instances) > 0 { - options.Instances = instances - } - - lb, err := client.LoadBalancer.Create(context.Background(), options) - if err != nil { - fmt.Printf("error creating load balancer : %v\n", err) - os.Exit(1) - } - - printer.LoadBalancer(lb) - }, -} - -var lbDelete = &cobra.Command{ - Use: "delete ", - Short: "deletes a load balancer", - Long: ``, - Args: func(cmd *cobra.Command, args []string) error { - if len(args) < 1 { - return errors.New("please provide a loadBalancerID") - } - return nil - }, - Run: func(cmd *cobra.Command, args []string) { - id := args[0] - if err := client.LoadBalancer.Delete(context.Background(), id); err != nil { - fmt.Printf("error deleting load balancer : %v\n", err) - os.Exit(1) - } - - fmt.Println("Deleted load balancer") - }, -} - -var lbGet = &cobra.Command{ - Use: "get ", - Short: "retrieves a load balancer", - Long: ``, - Args: func(cmd *cobra.Command, args []string) error { - if len(args) < 1 { - return errors.New("please provide a loadBalancerID") - } - return nil - }, - Run: func(cmd *cobra.Command, args []string) { - id := args[0] - lb, err := client.LoadBalancer.Get(context.Background(), id) - if err != nil { - fmt.Printf("error getting load balancer : %v\n", err) - os.Exit(1) - } - - printer.LoadBalancer(lb) - }, -} - -var lbList = &cobra.Command{ - Use: "list", - Short: "retrieves a list of active load balancers", - Long: ``, - Run: func(cmd *cobra.Command, args []string) { - options := getPaging(cmd) - list, meta, err := client.LoadBalancer.List(context.Background(), options) - if err != nil { - fmt.Printf("error listing load balancers : %v\n", err) - os.Exit(1) - } - - printer.LoadBalancerList(list, meta) - }, -} - -var lbUpdate = &cobra.Command{ - Use: "update ", - Short: "updates a load balancer", - Long: ``, - Args: func(cmd *cobra.Command, args []string) error { - if len(args) < 1 { - return errors.New("please provide a loadBalancerID") - } - return nil - }, - Run: func(cmd *cobra.Command, args []string) { - id := args[0] - label, _ := cmd.Flags().GetString("label") - - algorithm, _ := cmd.Flags().GetString("balancing-algorithm") - sslRedirect, _ := cmd.Flags().GetString("ssl-redirect") - proxyProtocol, _ := cmd.Flags().GetString("proxy-protocol") - cookieName, _ := cmd.Flags().GetString("cookie-name") - - fwRules, _ := cmd.Flags().GetStringArray("forwarding-rules") - - protocol, _ := cmd.Flags().GetString("protocol") - port, _ := cmd.Flags().GetInt("port") - path, _ := cmd.Flags().GetString("path") - checkInterval, _ := cmd.Flags().GetInt("check-interval") - responseTimeout, _ := cmd.Flags().GetInt("response-timeout") - unhealthyThreshold, _ := cmd.Flags().GetInt("unhealthy-threshold") - healthyThreshold, _ := cmd.Flags().GetInt("healthy-threshold") - - privateKey, _ := cmd.Flags().GetString("private-key") - certificate, _ := cmd.Flags().GetString("certificate") - certificateChain, _ := cmd.Flags().GetString("certificate-chain") - - instances, _ := cmd.Flags().GetStringSlice("instances") - - options := &govultr.LoadBalancerReq{} - - if len(fwRules) > 0 { - rules, err := formatFWRules(fwRules) - if err != nil { - fmt.Printf("error updating load balancer : %v\n", err) - os.Exit(1) - } - - if len(rules) > 0 { - options.ForwardingRules = rules - } - } - - // Health - if port != 0 || protocol != "" || path != "" || checkInterval != 0 || responseTimeout != 0 || unhealthyThreshold != 0 || healthyThreshold != 0 { - options.HealthCheck = &govultr.HealthCheck{} - } - - if port != 0 { - options.HealthCheck.Port = port - } - - if protocol != "" { - options.HealthCheck.Protocol = protocol - } - - if path != "" { - options.HealthCheck.Path = path - } - - if checkInterval != 0 { - options.HealthCheck.CheckInterval = checkInterval - } - - if responseTimeout != 0 { - options.HealthCheck.ResponseTimeout = responseTimeout - } - - if unhealthyThreshold != 0 { - options.HealthCheck.UnhealthyThreshold = unhealthyThreshold - } - - if healthyThreshold != 0 { - options.HealthCheck.HealthyThreshold = healthyThreshold - } - - // SSL - if privateKey != "" && certificate != "" { - options.SSL = &govultr.SSL{ - PrivateKey: privateKey, - Certificate: certificate, - Chain: certificateChain, - } - } - - // Generic Info - if label != "" { - options.Label = label - } - - if proxyProtocol == "yes" { - options.ProxyProtocol = govultr.BoolToBoolPtr(true) - } else if proxyProtocol == "no" { - options.ProxyProtocol = govultr.BoolToBoolPtr(false) - } - - if sslRedirect == "yes" { - options.SSLRedirect = govultr.BoolToBoolPtr(true) - } else if sslRedirect == "no" { - options.SSLRedirect = govultr.BoolToBoolPtr(false) - } - - if cookieName != "" { - options.StickySessions = &govultr.StickySessions{ - CookieName: cookieName, - } - } - - if algorithm != "" { - options.BalancingAlgorithm = algorithm - } - - if len(instances) > 0 { - options.Instances = instances - } - - if err := client.LoadBalancer.Update(context.Background(), id, options); err != nil { - fmt.Printf("error updating load balancer : %v\n", err) - os.Exit(1) - } - - fmt.Println("Updated load balancer") - }, -} - -var ruleList = &cobra.Command{ - Use: "list rule ", - Short: "lists a load balancers forwarding rules", - Long: ``, - Args: func(cmd *cobra.Command, args []string) error { - if len(args) < 1 { - return errors.New("please provide a loadBalancerID") - } - return nil - }, - Run: func(cmd *cobra.Command, args []string) { - id := args[0] - options := getPaging(cmd) - rules, meta, err := client.LoadBalancer.ListForwardingRules(context.Background(), id, options) - if err != nil { - fmt.Printf("error listing load balancer rules : %v\n", err) - os.Exit(1) - } - - printer.LoadBalancerRuleList(rules, meta) - }, -} - -var ruleCreate = &cobra.Command{ - Use: "create rule ", - Short: "creates a load balancer forwarding rule", - Long: ``, - Args: func(cmd *cobra.Command, args []string) error { - if len(args) < 1 { - return errors.New("please provide a loadBalancerID") - } - return nil - }, - Run: func(cmd *cobra.Command, args []string) { - id := args[0] - options := &govultr.ForwardingRule{} - rule, err := client.LoadBalancer.CreateForwardingRule(context.Background(), id, options) - if err != nil { - fmt.Printf("error listing load balancer rules : %v\n", err) - os.Exit(1) - } - - printer.LoadBalancerRule(rule) - }, -} - -var ruleGet = &cobra.Command{ - Use: "get rule ", - Short: "Gets a load balancer forwarding rule", - Long: ``, - Args: func(cmd *cobra.Command, args []string) error { - if len(args) < 2 { - return errors.New("please provide a loadBalancerID and ruleID") - } - return nil - }, - Run: func(cmd *cobra.Command, args []string) { - id := args[0] - ruleID := args[1] - rule, err := client.LoadBalancer.GetForwardingRule(context.Background(), id, ruleID) - if err != nil { - fmt.Printf("error getting load balancer rule : %v\n", err) - os.Exit(1) - } - - printer.LoadBalancerRule(rule) - }, -} - -var ruleDelete = &cobra.Command{ - Use: "delete rule ", - Short: "deletes a load balancer forwarding rule", - Long: ``, - Aliases: []string{"destroy"}, - Args: func(cmd *cobra.Command, args []string) error { - if len(args) < 2 { - return errors.New("please provide a loadBalancerID and ruleID") - } - return nil - }, - Run: func(cmd *cobra.Command, args []string) { - id := args[0] - ruleID := args[1] - - if err := client.LoadBalancer.DeleteForwardingRule(context.Background(), id, ruleID); err != nil { - fmt.Printf("error deleting load balancer rule : %v\n", err) - os.Exit(1) - } - - fmt.Println("Deleted load balancer rule") - }, -} - -// formatFWRules parses forwarding rules into proper format -func formatFWRules(rules []string) ([]govultr.ForwardingRule, error) { - var formattedList []govultr.ForwardingRule - rulesList := strings.Split(rules[0], "-") - - for _, r := range rulesList { - rule := govultr.ForwardingRule{} - fwRule := strings.Split(r, ",") - - if len(fwRule) != 4 { - return nil, fmt.Errorf("unable to format forwarding rules. each rule must include frontend and backend ports and protocols") - } - - for _, f := range fwRule { - ruleKeyVal := strings.Split(f, ":") - - if len(ruleKeyVal) != 2 { - return nil, fmt.Errorf("invalid forwarding rule format") - } - - field := ruleKeyVal[0] - val := ruleKeyVal[1] - - switch true { - case field == "frontend_protocol": - rule.FrontendProtocol = val - case field == "frontend_port": - port, _ := strconv.Atoi(val) - rule.FrontendPort = port - case field == "backend_protocol": - rule.BackendProtocol = val - case field == "backend_port": - port, _ := strconv.Atoi(val) - rule.BackendPort = port - } - } - - formattedList = append(formattedList, rule) - } - - return formattedList, nil -} diff --git a/cmd/loadbalancer/loadbalancer.go b/cmd/loadbalancer/loadbalancer.go new file mode 100644 index 00000000..4951239f --- /dev/null +++ b/cmd/loadbalancer/loadbalancer.go @@ -0,0 +1,1086 @@ +// Package loadbalancer provides CLI command to control load balancers +package loadbalancer + +import ( + "errors" + "fmt" + "os" + "strconv" + "strings" + + "github.com/spf13/cobra" + "github.com/vultr/govultr/v3" + "github.com/vultr/vultr-cli/v3/cmd/printer" + "github.com/vultr/vultr-cli/v3/cmd/utils" + "github.com/vultr/vultr-cli/v3/pkg/cli" +) + +var ( + long = `Get commands available to Load Balancers` + example = ` + # Full example + vultr-cli load-balancer + ` + + listLong = `Get all load balancers on your Vultr account` + listExample = ` + # Full example + vultr-cli load-balancer list + + # Full example with paging + vultr-cli load-balancer list --per-page=1 --cursor="bmV4dF9fQU1T" + + # Shortened with alias commands + vultr-cli lb l + + # Summarized view + vultr-cli load-balancer list --summarize + ` + + createLong = `Create a new Load Balancer with the desired settings` + createExample = ` + # Full example + vultr-cli load-balancer create --region="lax" --balancing-algorithm="roundrobin" --label="Example Load Balancer" \ + --port=80 --check-interval=10 --healthy-threshold=15 + + You must pass --region; other arguments are optional + + #Shortened example with aliases + vultr-cli lb c -r="lax" -b="roundrobin" -l="Example Load Balancer" -p=80 -c=10 + + #Full example with attached VPC + vultr-cli load-balancer create --region="lax" --label="Example Load Balancer with VPC" \ + --vpc="e951822b-10b2-4c5e-b333-bf38033e7175" --balancing-algorithm="leastconn" + ` + updateLong = `Update a Load Balancer with the desired settings` + updateExample = ` + # Full example + vultr-cli load-balancer update 57539f6f-66a2-4580-936b-d0af934bce5d --label="Updated Load Balancer Label" \ + --balancing-algorithm="leastconn" --unhealthy-threshold=20 + + #Shortened example with aliases + vultr-cli lb u 57539f6f-66a2-4580-936b-d0af934bce5d -l="Updated Load Balancer Label" -b="leastconn" -u=20 + + #Full example with attached VPC + vultr-cli load-balancer update 57539f6f-66a2-4580-936b-d0af934bce5d --vpc="bff36707-977e-4357-8f30-bef3339155cc" + ` +) + +// NewCmdLoadBalancer provides the CLI command for load balancers +func NewCmdLoadBalancer(base *cli.Base) *cobra.Command { //nolint:funlen,gocyclo + o := &options{Base: base} + + cmd := &cobra.Command{ + Use: "load-balancer", + Short: "Load balancer commands", + Long: long, + Example: example, + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + utils.SetOptions(o.Base, cmd, args) + if !o.Base.HasAuth { + return errors.New(utils.APIKeyError) + } + return nil + }, + } + + // List + list := &cobra.Command{ + Use: "list ", + Short: "List load balancers", + Aliases: []string{"l"}, + Long: listLong, + Example: listExample, + RunE: func(cmd *cobra.Command, args []string) error { + o.Base.Options = utils.GetPaging(cmd) + + summarize, errSu := cmd.Flags().GetBool("summarize") + if errSu != nil { + return fmt.Errorf("error parsing flag 'summarize' for load balancer list : %v", errSu) + } + + lbs, meta, err := o.list() + if err != nil { + return fmt.Errorf("error getting load balancer : %v", err) + } + + var data printer.ResourceOutput + if summarize { + data = &LBsSummaryPrinter{LBs: lbs, Meta: meta} + } else { + data = &LBsPrinter{LBs: lbs, Meta: meta} + } + + o.Base.Printer.Display(data, nil) + + return nil + }, + } + + list.Flags().StringP("cursor", "c", "", "(optional) cursor for paging.") + list.Flags().IntP( + "per-page", + "p", + utils.PerPageDefault, + fmt.Sprintf("(optional) Number of items requested per page. Default is %d and Max is 500.", utils.PerPageDefault), + ) + list.Flags().BoolP("summarize", "", false, "(optional) Summarize the list output. One line per load balancer.") + + // Get + get := &cobra.Command{ + Use: "get ", + Short: "Retrieve a load balancer", + Aliases: []string{"g"}, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("please provide a load balancer ID") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + lb, err := o.get() + if err != nil { + return fmt.Errorf("error getting load balancer : %v", err) + } + + o.Base.Printer.Display(&LBPrinter{LB: lb}, nil) + + return nil + }, + } + + // Create + create := &cobra.Command{ + Use: "create", + Short: "Create a load balancer", + Aliases: []string{"c"}, + Long: createLong, + Example: createExample, + RunE: func(cmd *cobra.Command, args []string) error { + region, errRg := cmd.Flags().GetString("region") + if errRg != nil { + return fmt.Errorf("error parsing flag 'region' for load balancer create : %v", errRg) + } + + label, errLa := cmd.Flags().GetString("label") + if errLa != nil { + return fmt.Errorf("error parsing flag 'label' for load balancer create : %v", errLa) + } + + algorithm, errAl := cmd.Flags().GetString("balancing-algorithm") + if errAl != nil { + return fmt.Errorf("error parsing flag 'balancing-algorithm' for load balancer create : %v", errAl) + } + + sslRedirect, errSs := cmd.Flags().GetBool("ssl-redirect") + if errSs != nil { + return fmt.Errorf("error parsing flag 'ssl-redirect' for load balancer create : %v", errSs) + } + + proxyProtocol, errPr := cmd.Flags().GetBool("proxy-protocol") + if errPr != nil { + return fmt.Errorf("error parsing flag 'proxy-protocol' for load balancer create : %v", errPr) + } + + cookieName, errCo := cmd.Flags().GetString("cookie-name") + if errCo != nil { + return fmt.Errorf("error parsing flag 'cookie-name' for load balancer create : %v", errCo) + } + + vpc, errVp := cmd.Flags().GetString("vpc") + if errVp != nil { + return fmt.Errorf("error parsing flag 'vpc' for load balancer create : %v", errVp) + } + + rulesInForward, errFw := cmd.Flags().GetStringArray("forwarding-rules") + if errFw != nil { + return fmt.Errorf("error parsing flag 'forwarding-rules' for load balancer create : %v", errFw) + } + + rulesInFire, errFi := cmd.Flags().GetStringArray("firewall-rules") + if errFi != nil { + return fmt.Errorf("error parsing flag 'firewall-rules' for load balancer create : %v", errFi) + } + + protocol, errPo := cmd.Flags().GetString("protocol") + if errPo != nil { + return fmt.Errorf("error parsing flag 'protocol' for load balancer create : %v", errPo) + } + + port, errPo := cmd.Flags().GetInt("port") + if errPo != nil { + return fmt.Errorf("error parsing flag 'port' for load balancer create : %v", errPo) + } + + path, errPa := cmd.Flags().GetString("path") + if errPa != nil { + return fmt.Errorf("error parsing flag 'path' for load balancer create : %v", errPa) + } + + checkInterval, errCh := cmd.Flags().GetInt("check-interval") + if errCh != nil { + return fmt.Errorf("error parsing flag 'check-interval' for load balancer create : %v", errCh) + } + + responseTimeout, errRe := cmd.Flags().GetInt("response-timeout") + if errRe != nil { + return fmt.Errorf("error parsing flag 'response-timeout' for load balancer create : %v", errRe) + } + + unhealthyThreshold, errUn := cmd.Flags().GetInt("unhealthy-threshold") + if errUn != nil { + return fmt.Errorf("error parsing flag 'unhealthy-threshold' for load balancer create : %v", errUn) + } + + healthyThreshold, errHe := cmd.Flags().GetInt("healthy-threshold") + if errHe != nil { + return fmt.Errorf("error parsing flag 'healthy-threshold' for load balancer create : %v", errHe) + } + + privateKey, errPi := cmd.Flags().GetString("private-key") + if errPi != nil { + return fmt.Errorf("error parsing flag 'private-key' for load balancer create : %v", errPi) + } + + certificate, errCe := cmd.Flags().GetString("certificate") + if errCe != nil { + return fmt.Errorf("error parsing flag 'certificate' for load balancer create : %v", errCe) + } + + certificateChain, errCr := cmd.Flags().GetString("certificate-chain") + if errCr != nil { + return fmt.Errorf("error parsing flag 'certificate-chain' for load balancer create : %v", errCr) + } + + instances, errIn := cmd.Flags().GetStringSlice("instances") + if errIn != nil { + return fmt.Errorf("error parsing flag 'instances' for load balancer create : %v", errIn) + } + + o.CreateReq = &govultr.LoadBalancerReq{ + Region: region, + Label: label, + VPC: &vpc, + ProxyProtocol: &proxyProtocol, + SSLRedirect: &sslRedirect, + BalancingAlgorithm: algorithm, + Instances: instances, + HealthCheck: &govultr.HealthCheck{ + Port: port, + Protocol: protocol, + Path: path, + CheckInterval: checkInterval, + ResponseTimeout: responseTimeout, + UnhealthyThreshold: unhealthyThreshold, + HealthyThreshold: healthyThreshold, + }, + SSL: &govultr.SSL{ + PrivateKey: privateKey, + Certificate: certificate, + Chain: certificateChain, + }, + } + + if cmd.Flags().Changed("cookie-name") { + o.CreateReq.StickySessions = &govultr.StickySessions{CookieName: cookieName} + } + + if len(rulesInForward) > 0 { + rulesFo, errFo := formatForwardingRules(rulesInForward) + if errFo != nil { + return fmt.Errorf("error creating load balancer : %v", errFo) + } + + if len(rulesFo) > 0 { + o.CreateReq.ForwardingRules = rulesFo + } + } + + if len(rulesInFire) > 0 { + rulesFi, errFi := formatFirewallRules(rulesInFire) + if errFi != nil { + return fmt.Errorf("error creating load balancer : %v", errFi) + } + + if len(rulesFi) > 0 { + o.CreateReq.FirewallRules = rulesFi + } + } + + lb, err := o.create() + if err != nil { + return fmt.Errorf("error creating load balancer : %v", err) + } + + o.Base.Printer.Display(&LBPrinter{LB: lb}, nil) + + return nil + }, + } + + create.Flags().StringP("region", "r", "", "region id you wish to have the load balancer created in") + if err := create.MarkFlagRequired("region"); err != nil { + fmt.Printf("error marking load-balancer create 'region' flag required: %v", err) + os.Exit(1) + } + + create.Flags().StringP( + "balancing-algorithm", + "b", + "roundrobin", + "(optional) balancing algorithm that determines server selection | roundrobin or leastconn", + ) + create.Flags().BoolP( + "ssl-redirect", + "s", + false, + `(optional) if true, this will redirect HTTP traffic to HTTPS. + You must have an HTTPS rule and SSL certificate installed on the load balancer to enable this option.`, + ) + create.Flags().StringP("proxy-protocol", "p", "", "(optional) if true, you must configure backend nodes to accept Proxy protocol.") + create.Flags().StringArrayP( + "forwarding-rules", + "f", + []string{}, + `(optional) a comma-separated, key-value pair list of forwarding rules. Use - between each new rule. + E.g: "frontend_port:80,frontend_protocol:http,backend_port:80,backend_protocol:http-frontend_port:81, + frontend_protocol:http,backend_port:81,backend_protocol:http"`, + ) + create.Flags().StringP( + "vpc", + "v", + "", + "(optional) the VPC ID to attach to your load balancer. When not provided, load balancer defaults to public network.", + ) + + create.Flags().StringArrayP( + "firewall-rules", + "", + []string{}, + `(optional) a comma-separated, key-value pair list of firewall rules. Use - between each new rule. + E.g: "port:80,ip_type:v4,source:0.0.0.0/0-port:8080,ip_type:v4,source:1.1.1.1/4"`, + ) + + create.Flags().String("protocol", "http", "(optional) the protocol to use for health checks. | https, http, tcp") + create.Flags().Int("port", 80, "(optional) the port to use for health checks.") //nolint: gomnd + create.Flags().String("path", "/", "(optional) HTTP Path to check. only applies if protocol is HTTP or HTTPS.") + create.Flags().IntP("check-interval", "c", 15, "(optional) interval between health checks.") //nolint: gomnd + create.Flags().IntP("response-timeout", "t", 15, "(optional) timeout before health check fails.") //nolint: gomnd + + //nolint: gomnd + create.Flags().IntP( + "unhealthy-threshold", + "u", + 15, + "(optional) number times a check must fail before becoming unhealthy.", + ) + + //nolint: gomnd + create.Flags().Int( + "healthy-threshold", + 15, + "(optional) number times a check must succeed before returning to healthy status.", + ) + + create.Flags().String("cookie-name", "", "(optional) the cookie name to make sticky.") + + create.Flags().String("private-key", "", "(optional) the private key component for a ssl certificate.") + create.Flags().String("certificate", "", "(optional) the SSL certificate.") + create.Flags().String("certificate-chain", "", "(optional) the certificate chain for a ssl certificate.") + + create.Flags().StringP("label", "l", "", "(optional) the label for your load balancer.") + create.Flags().StringSliceP( + "instances", + "i", + []string{}, + "(optional) an array of instances IDs that you want attached to the load balancer.", + ) + + // Update + update := &cobra.Command{ + Use: "update ", + Short: "Updates a load balancer", + Aliases: []string{"u"}, + Long: updateLong, + Example: updateExample, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("please provide a load balancer ID") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + label, errLa := cmd.Flags().GetString("label") + if errLa != nil { + return fmt.Errorf("error parsing flag 'label' for load balancer update : %v", errLa) + } + + algorithm, errAl := cmd.Flags().GetString("balancing-algorithm") + if errAl != nil { + return fmt.Errorf("error parsing flag 'balancing-algorithm' for load balancer update : %v", errAl) + } + + sslRedirect, errSs := cmd.Flags().GetBool("ssl-redirect") + if errSs != nil { + return fmt.Errorf("error parsing flag 'ssl-redirect' for load balancer update : %v", errSs) + } + + proxyProtocol, errPr := cmd.Flags().GetBool("proxy-protocol") + if errPr != nil { + return fmt.Errorf("error parsing flag 'proxy-protocol' for load balancer update : %v", errPr) + } + + cookieName, errCo := cmd.Flags().GetString("cookie-name") + if errCo != nil { + return fmt.Errorf("error parsing flag 'cookie-name' for load balancer update : %v", errCo) + } + + vpc, errVp := cmd.Flags().GetString("vpc") + if errVp != nil { + return fmt.Errorf("error parsing flag 'vpc' for load balancer update : %v", errVp) + } + + rulesInForward, errFw := cmd.Flags().GetStringArray("forwarding-rules") + if errFw != nil { + return fmt.Errorf("error parsing flag 'forwarding-rules' for load balancer update : %v", errFw) + } + + rulesInFire, errFi := cmd.Flags().GetStringArray("firewall-rules") + if errFi != nil { + return fmt.Errorf("error parsing flag 'firewall-rules' for load balancer update : %v", errFi) + } + + protocol, errPo := cmd.Flags().GetString("protocol") + if errPo != nil { + return fmt.Errorf("error parsing flag 'protocol' for load balancer update : %v", errPo) + } + + port, errPo := cmd.Flags().GetInt("port") + if errPo != nil { + return fmt.Errorf("error parsing flag 'port' for load balancer update : %v", errPo) + } + + path, errPa := cmd.Flags().GetString("path") + if errPa != nil { + return fmt.Errorf("error parsing flag 'path' for load balancer update : %v", errPa) + } + + checkInterval, errCh := cmd.Flags().GetInt("check-interval") + if errCh != nil { + return fmt.Errorf("error parsing flag 'check-interval' for load balancer update : %v", errCh) + } + + responseTimeout, errRe := cmd.Flags().GetInt("response-timeout") + if errRe != nil { + return fmt.Errorf("error parsing flag 'response-timeout' for load balancer update : %v", errRe) + } + + unhealthyThreshold, errUn := cmd.Flags().GetInt("unhealthy-threshold") + if errUn != nil { + return fmt.Errorf("error parsing flag 'unhealthy-threshold' for load balancer update : %v", errUn) + } + + healthyThreshold, errHe := cmd.Flags().GetInt("healthy-threshold") + if errHe != nil { + return fmt.Errorf("error parsing flag 'healthy-threshold' for load balancer update : %v", errHe) + } + + privateKey, errPi := cmd.Flags().GetString("private-key") + if errPi != nil { + return fmt.Errorf("error parsing flag 'private-key' for load balancer update : %v", errPi) + } + + certificate, errCe := cmd.Flags().GetString("certificate") + if errCe != nil { + return fmt.Errorf("error parsing flag 'certificate' for load balancer update : %v", errCe) + } + + certificateChain, errCr := cmd.Flags().GetString("certificate-chain") + if errCr != nil { + return fmt.Errorf("error parsing flag 'certificate-chain' for load balancer update : %v", errCr) + } + + instances, errIn := cmd.Flags().GetStringSlice("instances") + if errIn != nil { + return fmt.Errorf("error parsing flag 'instances' for load balancer update : %v", errIn) + } + + o.UpdateReq = &govultr.LoadBalancerReq{} + + if len(rulesInForward) > 0 { + rulesFo, errFo := formatForwardingRules(rulesInForward) + if errFo != nil { + return fmt.Errorf("error updating load balancer : %v", errFo) + } + + if len(rulesFo) > 0 { + o.UpdateReq.ForwardingRules = rulesFo + } + } + + if len(rulesInFire) > 0 { + rulesFi, errFi := formatFirewallRules(rulesInFire) + if errFi != nil { + return fmt.Errorf("error updating load balancer : %v", errFi) + } + + if len(rulesFi) > 0 { + o.UpdateReq.FirewallRules = rulesFi + } + } + + // Health + if port != 0 || protocol != "" || path != "" || checkInterval != 0 || responseTimeout != 0 || unhealthyThreshold != 0 || healthyThreshold != 0 { //nolint: lll + o.UpdateReq.HealthCheck = &govultr.HealthCheck{} + } + + if port != 0 { + o.UpdateReq.HealthCheck.Port = port + } + + if protocol != "" { + o.UpdateReq.HealthCheck.Protocol = protocol + } + + if path != "" { + o.UpdateReq.HealthCheck.Path = path + } + + if checkInterval != 0 { + o.UpdateReq.HealthCheck.CheckInterval = checkInterval + } + + if responseTimeout != 0 { + o.UpdateReq.HealthCheck.ResponseTimeout = responseTimeout + } + + if unhealthyThreshold != 0 { + o.UpdateReq.HealthCheck.UnhealthyThreshold = unhealthyThreshold + } + + if healthyThreshold != 0 { + o.UpdateReq.HealthCheck.HealthyThreshold = healthyThreshold + } + + // SSL + if privateKey != "" && certificate != "" { + o.UpdateReq.SSL = &govultr.SSL{ + PrivateKey: privateKey, + Certificate: certificate, + Chain: certificateChain, + } + } + + // Generic Info + if cmd.Flags().Changed("label") { + o.UpdateReq.Label = label + } + + if cmd.Flags().Changed("vpc") { + o.UpdateReq.VPC = govultr.StringToStringPtr(vpc) + } + + if cmd.Flags().Changed("proxy-protocol") { + o.UpdateReq.ProxyProtocol = &proxyProtocol + } + + if cmd.Flags().Changed("ssl-redirect") { + o.UpdateReq.SSLRedirect = &sslRedirect + } + + if cmd.Flags().Changed("cookie-name") { + o.UpdateReq.StickySessions = &govultr.StickySessions{ + CookieName: cookieName, + } + } + + if cmd.Flags().Changed("balancing-algorithm") { + o.UpdateReq.BalancingAlgorithm = algorithm + } + + if len(instances) > 0 { + o.UpdateReq.Instances = instances + } + + if err := o.update(); err != nil { + return fmt.Errorf("error updating load balancer : %v", err) + } + + o.Base.Printer.Display(printer.Info("Load balancer has been updated"), nil) + + return nil + }, + } + + update.Flags().StringP( + "balancing-algorithm", + "b", + "roundrobin", + "(optional) balancing algorithm that determines server selection | roundrobin or leastconn", + ) + update.Flags().BoolP( + "ssl-redirect", + "s", + false, + `(optional) if true, this will redirect HTTP traffic to HTTPS. You must have an HTTPS rule + and SSL certificate installed on the load balancer to enable this option.`, + ) + update.Flags().StringP("proxy-protocol", "p", "", "(optional) if true, you must configure backend nodes to accept Proxy protocol.") + update.Flags().StringArrayP( + "forwarding-rules", + "f", + []string{}, + `(optional) a comma-separated, key-value pair list of forwarding rules. Use - between each new rule. + E.g: "frontend_port:80,frontend_protocol:http,backend_port:80,backend_protocol:http-frontend_port:81, + frontend_protocol:http,backend_port:81,backend_protocol:http"`, + ) + update.Flags().StringArrayP( + "firewall-rules", + "", + []string{}, + `(optional) a comma-separated, key-value pair list of firewall rules. Use - between each new rule. + E.g: "port:80,ip_type:v4,source:0.0.0.0/0-port:8080,ip_type:v4,source:1.1.1.1/4"`, + ) + update.Flags().StringP("vpc", "v", "", "(optional) the VPC ID to attach to your load balancer.") + + update.Flags().String("protocol", "", "(optional) the protocol to use for health checks. | https, http, tcp") + update.Flags().Int("port", 0, "(optional) the port to use for health checks.") + update.Flags().String("path", "", "(optional) HTTP Path to check. only applies if protocol is HTTP or HTTPS.") + update.Flags().IntP("check-interval", "c", 0, "(optional) interval between health checks.") + update.Flags().IntP("response-timeout", "t", 0, "(optional) timeout before health check fails.") + update.Flags().IntP("unhealthy-threshold", "u", 0, "(optional) number times a check must fail before becoming unhealthy.") + update.Flags().Int("healthy-threshold", 0, "(optional) number times a check must succeed before returning to healthy status.") + + update.Flags().String("cookie-name", "", "(optional) the cookie name to make sticky.") + + update.Flags().String("private-key", "", "(optional) the private key component for a ssl certificate.") + update.Flags().String("certificate", "", "(optional) the SSL certificate.") + update.Flags().String("certificate-chain", "", "(optional) the certificate chain for a ssl certificate.") + + update.Flags().StringP("label", "l", "", "(optional) the label for your load balancer.") + update.Flags().StringSliceP( + "instances", + "i", + []string{}, + "(optional) an array of instances IDs that you want attached to the load balancer.", + ) + + // Delete + del := &cobra.Command{ + Use: "delete ", + Short: "Delete a load balancer", + Aliases: []string{"destroy", "d"}, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("please provide a load balancer ID") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + if err := o.del(); err != nil { + return fmt.Errorf("error deleting load balancer : %v", err) + } + + o.Base.Printer.Display(printer.Info("Load balancer has been deleted"), nil) + + return nil + }, + } + + // Forwarding Rules + forwarding := &cobra.Command{ + Use: "forwarding", + Short: "Access forwarding rules for a load balancer", + } + + // List Forwarding Rules + listForwardingRules := &cobra.Command{ + Use: "list ", + Short: "List all forwarding rules", + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("please provide a load balancer ID") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + rules, meta, err := o.listForwardingRules() + if err != nil { + return fmt.Errorf("error listing load balancer forwarding rules : %v", err) + } + + data := &LBRulesPrinter{LBRules: rules, Meta: meta} + o.Base.Printer.Display(data, nil) + + return nil + }, + } + + listForwardingRules.Flags().StringP("cursor", "c", "", "(optional) cursor for paging.") + listForwardingRules.Flags().IntP( + "per-page", + "p", + utils.PerPageDefault, + fmt.Sprintf("(optional) Number of items requested per page. Default is %d and Max is 500.", utils.PerPageDefault), + ) + + // Get Forwarding Rule + getForwardingRule := &cobra.Command{ + Use: "get ", + Short: "Get a forwarding rule", + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 2 { + return errors.New("please provide a load balancer ID and a rule ID") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + rule, err := o.getForwardingRule() + if err != nil { + return fmt.Errorf("error getting load balancer forwarding rule : %v", err) + } + + data := &LBRulePrinter{LBRule: *rule} + o.Base.Printer.Display(data, nil) + + return nil + }, + } + + // Create Forwarding Rule + createForwardingRule := &cobra.Command{ + Use: "create ", + Short: "Create a forwarding rule", + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("please provide a load balancer ID") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + frontProtocol, errFr := cmd.Flags().GetString("frontend-protocol") + if errFr != nil { + return fmt.Errorf("error parsing flag 'frontend-protocol' for forwarding rule create : %v", errFr) + } + + backProtocol, errBa := cmd.Flags().GetString("backend-protocol") + if errBa != nil { + return fmt.Errorf("error parsing flag 'backend-protocol' for forwarding rule create : %v", errBa) + } + + frontPort, errFp := cmd.Flags().GetInt("frontend-port") + if errFp != nil { + return fmt.Errorf("error parsing flag 'frontend-port' for forwarding rule create : %v", errFp) + } + + backPort, errBp := cmd.Flags().GetInt("backend-port") + if errBp != nil { + return fmt.Errorf("error parsing flag 'backend-port' for forwarding rule create : %v", errBp) + } + + o.RuleCreateReq = &govultr.ForwardingRule{ + FrontendProtocol: frontProtocol, + FrontendPort: frontPort, + BackendProtocol: backProtocol, + BackendPort: backPort, + } + + rule, err := o.createForwardingRule() + if err != nil { + return fmt.Errorf("error creating load balancer forwarding rule : %v", err) + } + + data := &LBRulePrinter{LBRule: *rule} + o.Base.Printer.Display(data, nil) + + return nil + }, + } + + createForwardingRule.Flags().String( + "frontend-protocol", + "http", + "the protocol on the Load Balancer to forward to the backend. | HTTP, HTTPS, TCP", + ) + if err := createForwardingRule.MarkFlagRequired("frontend-protocol"); err != nil { + fmt.Printf("error marking load-balancer rule create 'frontend-protocol' flag required: %v", err) + os.Exit(1) + } + + createForwardingRule.Flags().String("backend-protocol", "http", "the protocol destination on the backend server. | HTTP, HTTPS, TCP") + if err := createForwardingRule.MarkFlagRequired("backend-protocol"); err != nil { + fmt.Printf("error marking load-balancer rule create 'backend-protocol' flag required: %v", err) + os.Exit(1) + } + + createForwardingRule.Flags().Int("frontend-port", 80, "the port number on the Load Balancer to forward to the backend.") //nolint: gomnd + if err := createForwardingRule.MarkFlagRequired("frontend-port"); err != nil { + fmt.Printf("error marking load-balancer rule create 'frontend-port' flag required: %v", err) + os.Exit(1) + } + + createForwardingRule.Flags().Int("backend-port", 80, "the port number destination on the backend server.") //nolint: gomnd + if err := createForwardingRule.MarkFlagRequired("backend-port"); err != nil { + fmt.Printf("error marking load-balancer rule create 'backend-port' flag required: %v", err) + os.Exit(1) + } + + // Delete Forwarding Rule + deleteForwardingRule := &cobra.Command{ + Use: "delete ", + Short: "Delete a forwarding rule", + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 2 { + return errors.New("please provide a load balancer ID and a rule ID") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + if err := o.deleteForwardingRule(); err != nil { + return fmt.Errorf("error deleting load balancer forwarding rule : %v", err) + } + + o.Base.Printer.Display(printer.Info("Forwarding rule has been deleted"), nil) + + return nil + }, + } + + forwarding.AddCommand( + listForwardingRules, + getForwardingRule, + createForwardingRule, + deleteForwardingRule, + ) + + // Firewall + firewall := &cobra.Command{ + Use: "firewall", + Short: "Access firewall rules on a load balancer", + } + + // List Firewall Rules + listFirewallRules := &cobra.Command{ + Use: "list ", + Short: "List all firewall rules", + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("please provide a load balancer ID") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + rules, meta, err := o.listFirewallRules() + if err != nil { + return fmt.Errorf("error listing load balancer firewall rules : %v", err) + } + + data := &FWRulesPrinter{Rules: rules, Meta: meta} + o.Base.Printer.Display(data, nil) + + return nil + }, + } + + listFirewallRules.Flags().StringP("cursor", "c", "", "(optional) cursor for paging.") + listFirewallRules.Flags().IntP( + "per-page", + "p", + utils.PerPageDefault, + fmt.Sprintf("(optional) Number of items requested per page. Default is %d and Max is 500.", utils.PerPageDefault), + ) + + // Get Firewall Rule + getFirewallRule := &cobra.Command{ + Use: "get ", + Short: "Get a firewall rule", + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 2 { + return errors.New("please provide a load balancer ID and a rule ID") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + rule, err := o.getFirewallRule() + if err != nil { + return fmt.Errorf("error getting load balancer firewall rule : %v", err) + } + + data := &FWRulePrinter{Rule: *rule} + o.Base.Printer.Display(data, nil) + + return nil + }, + } + + firewall.AddCommand( + listFirewallRules, + getFirewallRule, + ) + + cmd.AddCommand( + list, + get, + create, + update, + del, + forwarding, + firewall, + ) + + return cmd +} + +type options struct { + Base *cli.Base + CreateReq *govultr.LoadBalancerReq + UpdateReq *govultr.LoadBalancerReq + RuleCreateReq *govultr.ForwardingRule +} + +func (o *options) list() ([]govultr.LoadBalancer, *govultr.Meta, error) { + lbs, meta, _, err := o.Base.Client.LoadBalancer.List(o.Base.Context, o.Base.Options) + return lbs, meta, err +} + +func (o *options) get() (*govultr.LoadBalancer, error) { + lb, _, err := o.Base.Client.LoadBalancer.Get(o.Base.Context, o.Base.Args[0]) + return lb, err +} + +func (o *options) create() (*govultr.LoadBalancer, error) { + lb, _, err := o.Base.Client.LoadBalancer.Create(o.Base.Context, o.CreateReq) + return lb, err +} + +func (o *options) update() error { + return o.Base.Client.LoadBalancer.Update(o.Base.Context, o.Base.Args[0], o.UpdateReq) +} + +func (o *options) del() error { + return o.Base.Client.LoadBalancer.Delete(o.Base.Context, o.Base.Args[0]) +} + +func (o *options) listForwardingRules() ([]govultr.ForwardingRule, *govultr.Meta, error) { + rs, meta, _, err := o.Base.Client.LoadBalancer.ListForwardingRules(o.Base.Context, o.Base.Args[0], o.Base.Options) + return rs, meta, err +} + +func (o *options) getForwardingRule() (*govultr.ForwardingRule, error) { + r, _, err := o.Base.Client.LoadBalancer.GetForwardingRule(o.Base.Context, o.Base.Args[0], o.Base.Args[1]) + return r, err +} + +func (o *options) createForwardingRule() (*govultr.ForwardingRule, error) { + r, _, err := o.Base.Client.LoadBalancer.CreateForwardingRule(o.Base.Context, o.Base.Args[0], o.RuleCreateReq) + return r, err +} + +func (o *options) deleteForwardingRule() error { + return o.Base.Client.LoadBalancer.DeleteForwardingRule(o.Base.Context, o.Base.Args[0], o.Base.Args[1]) +} + +func (o *options) listFirewallRules() ([]govultr.LBFirewallRule, *govultr.Meta, error) { + rs, meta, _, err := o.Base.Client.LoadBalancer.ListFirewallRules(o.Base.Context, o.Base.Args[0], o.Base.Options) + return rs, meta, err +} + +func (o *options) getFirewallRule() (*govultr.LBFirewallRule, error) { + r, _, err := o.Base.Client.LoadBalancer.GetFirewallRule(o.Base.Context, o.Base.Args[0], o.Base.Args[1]) + return r, err +} + +// ====================================== + +// formatFirewallRules parses forwarding rules into proper format +func formatFirewallRules(rules []string) ([]govultr.LBFirewallRule, error) { + var formattedList []govultr.LBFirewallRule + rulesList := strings.Split(rules[0], "-") + + for i := range rulesList { + rule := govultr.LBFirewallRule{} + fwRule := strings.Split(rulesList[i], ",") + + if len(fwRule) != 3 { + return nil, fmt.Errorf("unable to format firewall rules. each rule must include ip_type, source, and port") + } + + for j := range fwRule { + ruleKeyVal := strings.Split(fwRule[j], ":") + + if len(ruleKeyVal) != 2 { + return nil, fmt.Errorf("invalid firewall rule format") + } + + field := ruleKeyVal[0] + val := ruleKeyVal[1] + + switch { + case field == "ip_type": + rule.IPType = val + case field == "port": + port, errCon := strconv.Atoi(val) + if errCon != nil { + return nil, fmt.Errorf("unable to parse firewall rule port value") + } + rule.Port = port + case field == "source": + rule.Source = val + } + } + + formattedList = append(formattedList, rule) + } + + return formattedList, nil +} + +// formatForwardingRules parses forwarding rules into proper format +func formatForwardingRules(rules []string) ([]govultr.ForwardingRule, error) { + var formattedList []govultr.ForwardingRule + var rulePartNum = 4 + rulesList := strings.Split(rules[0], "-") + + for i := range rulesList { + rule := govultr.ForwardingRule{} + fwRule := strings.Split(rulesList[i], ",") + + if len(fwRule) != rulePartNum { + return nil, fmt.Errorf("unable to format forwarding rules. each rule must include frontend and backend ports and protocols") + } + + for j := range fwRule { + ruleKeyVal := strings.Split(fwRule[j], ":") + + if len(ruleKeyVal) != 2 { + return nil, fmt.Errorf("invalid forwarding rule format") + } + + field := ruleKeyVal[0] + val := ruleKeyVal[1] + + switch { + case field == "frontend_protocol": + rule.FrontendProtocol = val + case field == "frontend_port": + port, errCon := strconv.Atoi(val) + if errCon != nil { + return nil, fmt.Errorf("unable to parse fowarding rule frontend port value") + } + rule.FrontendPort = port + case field == "backend_protocol": + rule.BackendProtocol = val + case field == "backend_port": + port, errCon := strconv.Atoi(val) + if errCon != nil { + return nil, fmt.Errorf("unable to parse fowarding rule backend port value") + } + rule.BackendPort = port + } + } + + formattedList = append(formattedList, rule) + } + + return formattedList, nil +} diff --git a/cmd/loadbalancer/printer.go b/cmd/loadbalancer/printer.go new file mode 100644 index 00000000..73636361 --- /dev/null +++ b/cmd/loadbalancer/printer.go @@ -0,0 +1,491 @@ +package loadbalancer + +import ( + "strconv" + + "github.com/vultr/govultr/v3" + "github.com/vultr/vultr-cli/v3/cmd/printer" +) + +// LBsPrinter ... +type LBsPrinter struct { + LBs []govultr.LoadBalancer `json:"load_balancers"` + Meta *govultr.Meta `json:"meta"` +} + +// JSON ... +func (l *LBsPrinter) JSON() []byte { + return printer.MarshalObject(l, "json") +} + +// YAML ... +func (l *LBsPrinter) YAML() []byte { + return printer.MarshalObject(l, "yaml") +} + +// Columns ... +func (l *LBsPrinter) Columns() [][]string { + return nil +} + +// Data ... +func (l *LBsPrinter) Data() [][]string { + if len(l.LBs) == 0 { + return [][]string{0: {"No active load balancers"}} + } + + var data [][]string + for i := range l.LBs { + data = append(data, + []string{"---------------------------"}, + []string{"ID", l.LBs[i].ID}, + []string{"DATE CREATED", l.LBs[i].DateCreated}, + []string{"REGION", l.LBs[i].Region}, + []string{"LABEL", l.LBs[i].Label}, + []string{"STATUS", l.LBs[i].Status}, + []string{"IPV4", l.LBs[i].IPV4}, + []string{"IPV6", l.LBs[i].IPV6}, + []string{"HAS SSL", strconv.FormatBool(*l.LBs[i].SSLInfo)}, + []string{"INSTANCES", printer.ArrayOfStringsToString(l.LBs[i].Instances)}, + + []string{" "}, + []string{"HEALTH CHECKS"}, + []string{"PROTOCOL", "PORT", "PATH", "CHECK INTERVAL", "RESPONSE TIMEOUT", "UNHEALTHY THRESHOLD", "HEALTHY THRESHOLD"}, + []string{ + l.LBs[i].HealthCheck.Protocol, + strconv.Itoa(l.LBs[i].HealthCheck.Port), + l.LBs[i].HealthCheck.Path, + strconv.Itoa(l.LBs[i].HealthCheck.CheckInterval), + strconv.Itoa(l.LBs[i].HealthCheck.ResponseTimeout), + strconv.Itoa(l.LBs[i].HealthCheck.UnhealthyThreshold), + strconv.Itoa(l.LBs[i].HealthCheck.HealthyThreshold), + }, + + []string{" "}, + []string{"GENERIC INFO"}, + []string{"BALANCING ALGORITHM", "SSL REDIRECT", "COOKIE NAME", "PROXY PROTOCOL", "VPC"}, + []string{ + l.LBs[i].GenericInfo.BalancingAlgorithm, + strconv.FormatBool(*l.LBs[i].GenericInfo.SSLRedirect), + l.LBs[i].GenericInfo.StickySessions.CookieName, + strconv.FormatBool(*l.LBs[i].GenericInfo.ProxyProtocol), + l.LBs[i].GenericInfo.VPC, + }, + + []string{" "}, + []string{"FORWARDING RULES"}, + []string{"RULEID", "FRONTEND PROTOCOL", "FRONTEND PORT", "BACKEND PROTOCOL", "BACKEND PORT"}, + ) + + if len(l.LBs[i].ForwardingRules) == 0 { + data = append(data, []string{"---", "---", "---", "---", "---"}) + } else { + for j := range l.LBs[i].ForwardingRules { + data = append(data, + []string{ + l.LBs[i].ForwardingRules[j].RuleID, + l.LBs[i].ForwardingRules[j].FrontendProtocol, + strconv.Itoa(l.LBs[i].ForwardingRules[j].FrontendPort), + l.LBs[i].ForwardingRules[j].BackendProtocol, + strconv.Itoa(l.LBs[i].ForwardingRules[j].BackendPort), + }, + ) + } + } + + data = append(data, + []string{" "}, + []string{"FIREWALL RULES"}, + []string{"RULEID", "PORT", "SOURCE", "IP_TYPE"}, + ) + + if len(l.LBs[i].FirewallRules) == 0 { + data = append(data, []string{"---", "---", "---", "---"}) + } else { + for j := range l.LBs[i].FirewallRules { + data = append(data, + []string{ + l.LBs[i].FirewallRules[j].RuleID, + strconv.Itoa(l.LBs[i].FirewallRules[j].Port), + l.LBs[i].FirewallRules[j].Source, + l.LBs[i].FirewallRules[j].IPType, + }, + ) + } + } + + data = append(data, []string{" "}) + } + + return data +} + +// Paging ... +func (l *LBsPrinter) Paging() [][]string { + return printer.NewPaging(l.Meta.Total, &l.Meta.Links.Next, &l.Meta.Links.Prev).Compose() +} + +// ====================================== + +// LBPrinter ... +type LBPrinter struct { + LB *govultr.LoadBalancer `json:"load_balancer"` +} + +// JSON ... +func (l *LBPrinter) JSON() []byte { + return printer.MarshalObject(l, "json") +} + +// YAML ... +func (l *LBPrinter) YAML() []byte { + return printer.MarshalObject(l, "yaml") +} + +// Columns ... +func (l *LBPrinter) Columns() [][]string { + return nil +} + +// Data ... +func (l *LBPrinter) Data() [][]string { + var data [][]string + data = append(data, + []string{"ID", l.LB.ID}, + []string{"DATE CREATED", l.LB.DateCreated}, + []string{"REGION", l.LB.Region}, + []string{"LABEL", l.LB.Label}, + []string{"STATUS", l.LB.Status}, + []string{"IPV4", l.LB.IPV4}, + []string{"IPV6", l.LB.IPV6}, + []string{"HAS SSL", strconv.FormatBool(*l.LB.SSLInfo)}, + []string{"INSTANCES", printer.ArrayOfStringsToString(l.LB.Instances)}, + + []string{" "}, + []string{"HEALTH CHECKS"}, + []string{"PROTOCOL", "PORT", "PATH", "CHECK INTERVAL", "RESPONSE TIMEOUT", "UNHEALTHY THRESHOLD", "HEALTHY THRESHOLD"}, + []string{ + l.LB.HealthCheck.Protocol, + strconv.Itoa(l.LB.HealthCheck.Port), + l.LB.HealthCheck.Path, + strconv.Itoa(l.LB.HealthCheck.CheckInterval), + strconv.Itoa(l.LB.HealthCheck.ResponseTimeout), + strconv.Itoa(l.LB.HealthCheck.UnhealthyThreshold), + strconv.Itoa(l.LB.HealthCheck.HealthyThreshold), + }, + + []string{" "}, + []string{"GENERIC INFO"}, + []string{"BALANCING ALGORITHM", "SSL REDIRECT", "COOKIE NAME", "PROXY PROTOCOL", "VPC"}, + []string{ + l.LB.GenericInfo.BalancingAlgorithm, + strconv.FormatBool(*l.LB.GenericInfo.SSLRedirect), + l.LB.GenericInfo.StickySessions.CookieName, + strconv.FormatBool(*l.LB.GenericInfo.ProxyProtocol), + l.LB.GenericInfo.VPC, + }, + + []string{" "}, + []string{"FORWARDING RULES"}, + []string{"RULEID", "FRONTEND PROTOCOL", "FRONTEND PORT", "BACKEND PROTOCOL", "BACKEND PORT"}, + ) + + if len(l.LB.ForwardingRules) == 0 { + data = append(data, []string{"---", "---", "---", "---", "---"}) + } else { + for i := range l.LB.ForwardingRules { + data = append(data, + []string{ + l.LB.ForwardingRules[i].RuleID, + l.LB.ForwardingRules[i].FrontendProtocol, + strconv.Itoa(l.LB.ForwardingRules[i].FrontendPort), + l.LB.ForwardingRules[i].BackendProtocol, + strconv.Itoa(l.LB.ForwardingRules[i].BackendPort), + }, + ) + } + } + + data = append(data, + []string{" "}, + []string{"FIREWALL RULES"}, + []string{"RULEID", "PORT", "SOURCE", "IP_TYPE"}, + ) + + if len(l.LB.FirewallRules) == 0 { + data = append(data, []string{"---", "---", "---", "---"}) + } else { + for i := range l.LB.FirewallRules { + data = append(data, + []string{ + l.LB.FirewallRules[i].RuleID, + strconv.Itoa(l.LB.FirewallRules[i].Port), + l.LB.FirewallRules[i].Source, + l.LB.FirewallRules[i].IPType, + }, + ) + } + } + + return data +} + +// Paging ... +func (l *LBPrinter) Paging() [][]string { + return nil +} + +// ====================================== + +// LBsSummaryPrinter ... +type LBsSummaryPrinter struct { + LBs []govultr.LoadBalancer `json:"load_balancers"` + Meta *govultr.Meta `json:"meta"` +} + +// JSON ... +func (l *LBsSummaryPrinter) JSON() []byte { + return printer.MarshalObject(l, "json") +} + +// YAML ... +func (l *LBsSummaryPrinter) YAML() []byte { + return printer.MarshalObject(l, "yaml") +} + +// Columns ... +func (l *LBsSummaryPrinter) Columns() [][]string { + return [][]string{0: { + "ID", + "LABEL", + "STATUS", + "REGION", + "INSTANCE#", + "FORWARD#", + "FIREWALL#", + }} +} + +// Data ... +func (l *LBsSummaryPrinter) Data() [][]string { + if len(l.LBs) == 0 { + return [][]string{0: {"---", "---", "---", "---", "---", "---", "---"}} + } + + var data [][]string + for i := range l.LBs { + forwardRuleCount := len(l.LBs[i].ForwardingRules) + firewallRuleCount := len(l.LBs[i].FirewallRules) + instanceCount := len(l.LBs[i].Instances) + + data = append(data, []string{ + + l.LBs[i].ID, + l.LBs[i].Label, + l.LBs[i].Status, + l.LBs[i].Region, + strconv.Itoa(instanceCount), + strconv.Itoa(forwardRuleCount), + strconv.Itoa(firewallRuleCount), + }) + } + + return data +} + +// Paging ... +func (l *LBsSummaryPrinter) Paging() [][]string { + return printer.NewPaging(l.Meta.Total, &l.Meta.Links.Next, &l.Meta.Links.Prev).Compose() +} + +// ====================================== + +// LBRulesPrinter ... +type LBRulesPrinter struct { + LBRules []govultr.ForwardingRule `json:"forwarding_rules"` + Meta *govultr.Meta `json:"meta"` +} + +// JSON ... +func (l *LBRulesPrinter) JSON() []byte { + return printer.MarshalObject(l, "json") +} + +// YAML ... +func (l *LBRulesPrinter) YAML() []byte { + return printer.MarshalObject(l, "yaml") +} + +// Columns ... +func (l *LBRulesPrinter) Columns() [][]string { + return [][]string{0: { + "RULEID", + "FRONTEND PROTOCOL", + "FRONTEND PORT", + "BACKEND PROTOCOL", + "BACKEND PORT", + }} +} + +// Data ... +func (l *LBRulesPrinter) Data() [][]string { + if len(l.LBRules) == 0 { + return [][]string{0: {"---", "---", "---", "---", "---"}} + } + + var data [][]string + for i := range l.LBRules { + data = append(data, []string{ + l.LBRules[i].RuleID, + l.LBRules[i].FrontendProtocol, + strconv.Itoa(l.LBRules[i].FrontendPort), + l.LBRules[i].BackendProtocol, + strconv.Itoa(l.LBRules[i].BackendPort), + }) + } + + return data +} + +// Paging ... +func (l *LBRulesPrinter) Paging() [][]string { + return printer.NewPaging(l.Meta.Total, &l.Meta.Links.Next, &l.Meta.Links.Prev).Compose() +} + +// ====================================== + +// LBRulePrinter ... +type LBRulePrinter struct { + LBRule govultr.ForwardingRule `json:"forwarding_rule"` +} + +// JSON ... +func (l *LBRulePrinter) JSON() []byte { + return printer.MarshalObject(l, "json") +} + +// YAML ... +func (l *LBRulePrinter) YAML() []byte { + return printer.MarshalObject(l, "yaml") +} + +// Columns ... +func (l *LBRulePrinter) Columns() [][]string { + return [][]string{0: { + "RULEID", + "FRONTEND PROTOCOL", + "FRONTEND PORT", + "BACKEND PROTOCOL", + "BACKEND PORT", + }} +} + +// Data ... +func (l *LBRulePrinter) Data() [][]string { + return [][]string{0: { + l.LBRule.RuleID, + l.LBRule.FrontendProtocol, + strconv.Itoa(l.LBRule.FrontendPort), + l.LBRule.BackendProtocol, + strconv.Itoa(l.LBRule.BackendPort), + }} +} + +// Paging ... +func (l *LBRulePrinter) Paging() [][]string { + return nil +} + +// ====================================== + +// FWRulesPrinter ... +type FWRulesPrinter struct { + Rules []govultr.LBFirewallRule `json:"firewall_rules"` + Meta *govultr.Meta `json:"meta"` +} + +// JSON ... +func (f *FWRulesPrinter) JSON() []byte { + return printer.MarshalObject(f, "json") +} + +// YAML ... +func (f *FWRulesPrinter) YAML() []byte { + return printer.MarshalObject(f, "yaml") +} + +// Columns ... +func (f *FWRulesPrinter) Columns() [][]string { + return [][]string{0: { + "RULEID", + "PORT", + "SOURCE", + "IP_TYPE", + }} +} + +// Data ... +func (f *FWRulesPrinter) Data() [][]string { + if len(f.Rules) == 0 { + return [][]string{0: {"---", "---", "---", "---"}} + } + + var data [][]string + for i := range f.Rules { + data = append(data, []string{ + f.Rules[i].RuleID, + strconv.Itoa(f.Rules[i].Port), + f.Rules[i].Source, + f.Rules[i].IPType, + }) + } + + return data +} + +// Paging ... +func (f *FWRulesPrinter) Paging() [][]string { + return printer.NewPaging(f.Meta.Total, &f.Meta.Links.Next, &f.Meta.Links.Prev).Compose() +} + +// ====================================== + +// FWRulePrinter ... +type FWRulePrinter struct { + Rule govultr.LBFirewallRule `json:"firewall_rule"` +} + +// JSON ... +func (f *FWRulePrinter) JSON() []byte { + return printer.MarshalObject(f, "json") +} + +// YAML ... +func (f *FWRulePrinter) YAML() []byte { + return printer.MarshalObject(f, "yaml") +} + +// Columns ... +func (f *FWRulePrinter) Columns() [][]string { + return [][]string{0: { + "RULEID", + "PORT", + "SOURCE", + "IP_TYPE", + }} +} + +// Data ... +func (f *FWRulePrinter) Data() [][]string { + return [][]string{0: { + f.Rule.RuleID, + strconv.Itoa(f.Rule.Port), + f.Rule.Source, + f.Rule.IPType, + }} +} + +// Paging ... +func (f *FWRulePrinter) Paging() [][]string { + return nil +} diff --git a/cmd/marketplace/marketplace.go b/cmd/marketplace/marketplace.go new file mode 100644 index 00000000..29d64888 --- /dev/null +++ b/cmd/marketplace/marketplace.go @@ -0,0 +1,99 @@ +// Package marketplace provides the command for the CLI to access marketplace +// functionality +package marketplace + +import ( + "errors" + "fmt" + + "github.com/spf13/cobra" + "github.com/vultr/govultr/v3" + "github.com/vultr/vultr-cli/v3/cmd/utils" + "github.com/vultr/vultr-cli/v3/pkg/cli" +) + +var ( + long = `Get commands available to marketplace` + example = ` + # Full example + vultr-cli marketplace + ` + appLong = `Get commands available to marketplace apps` + appExample = ` + # Full example + vultr-cli marketplace app + ` + listAppVariablesLong = `List all user-supplied variables for a given marketplace app` + listAppVariablesExample = ` + # Full example + vultr-cli marketplace app list-variables drupal + ` +) + +// NewCmdMarketplace provides the CLI command for marketplace functions +func NewCmdMarketplace(base *cli.Base) *cobra.Command { + o := &options{Base: base} + + cmd := &cobra.Command{ + Use: "marketplace", + Short: "Commands to interact with marketplace functions", + Long: long, + Example: example, + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + utils.SetOptions(o.Base, cmd, args) + return nil + }, + } + + // App + app := &cobra.Command{ + Use: "app", + Short: "Commands to interact with vultr marketplace apps", + Long: appLong, + Example: appExample, + } + + // List Variables + listVariables := &cobra.Command{ + Use: "list-variables", + Short: "List all user-supplied variables for a marketplace app", + Aliases: []string{"l"}, + Long: listAppVariablesLong, + Example: listAppVariablesExample, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("please provide an image ID") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + vars, err := o.listVariables() + if err != nil { + return fmt.Errorf("error getting list of marketplace app variables : %v", err) + } + + o.Base.Printer.Display(&VariablesPrinter{Variables: vars}, nil) + + return nil + }, + } + + app.AddCommand( + listVariables, + ) + + cmd.AddCommand( + app, + ) + + return cmd +} + +type options struct { + Base *cli.Base +} + +func (o *options) listVariables() ([]govultr.MarketplaceAppVariable, error) { + vars, _, err := o.Base.Client.Marketplace.ListAppVariables(o.Base.Context, o.Base.Args[0]) + return vars, err +} diff --git a/cmd/marketplace/printer.go b/cmd/marketplace/printer.go new file mode 100644 index 00000000..40e8b6cc --- /dev/null +++ b/cmd/marketplace/printer.go @@ -0,0 +1,55 @@ +package marketplace + +import ( + "strconv" + + "github.com/vultr/govultr/v3" + "github.com/vultr/vultr-cli/v3/cmd/printer" +) + +// VariablesPrinter ... +type VariablesPrinter struct { + Variables []govultr.MarketplaceAppVariable `json:"variables"` +} + +// JSON ... +func (v *VariablesPrinter) JSON() []byte { + return printer.MarshalObject(v, "json") +} + +// YAML ... +func (v *VariablesPrinter) YAML() []byte { + return printer.MarshalObject(v, "yaml") +} + +// Columns ... +func (v *VariablesPrinter) Columns() [][]string { + return [][]string{0: { + "NAME", + "DESCRIPTION", + "REQUIRED", + }} +} + +// Data ... +func (v *VariablesPrinter) Data() [][]string { + if len(v.Variables) == 0 { + return [][]string{0: {"---", "---", "---"}} + } + + var data [][]string + for i := range v.Variables { + data = append(data, []string{ + v.Variables[i].Name, + v.Variables[i].Description, + strconv.FormatBool(*v.Variables[i].Required), + }) + } + + return data +} + +// Paging ... +func (v *VariablesPrinter) Paging() [][]string { + return nil +} diff --git a/cmd/network.go b/cmd/network.go deleted file mode 100644 index e48a6dae..00000000 --- a/cmd/network.go +++ /dev/null @@ -1,137 +0,0 @@ -// Copyright © 2019 The Vultr-cli Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package cmd - -import ( - "context" - "errors" - "fmt" - "os" - - "github.com/spf13/cobra" - "github.com/vultr/govultr/v2" - "github.com/vultr/vultr-cli/cmd/printer" -) - -// Network represents the network command -func Network() *cobra.Command { - networkCmd := &cobra.Command{ - Use: "network", - Short: "network interacts with network actions", - Long: ``, - } - - networkCmd.AddCommand(networkGet, networkList, networkDelete, networkCreate) - networkCreate.Flags().StringP("region-id", "r", "", "id of the region you wish to create the network") - networkCreate.Flags().StringP("description", "d", "", "description of the network") - networkCreate.Flags().StringP("subnet", "s", "", "The IPv4 network in CIDR notation.") - networkCreate.Flags().IntP("size", "z", 0, "The number of bits for the netmask in CIDR notation.") - networkCreate.MarkFlagRequired("region-id") - networkCreate.MarkFlagRequired("description") - networkCreate.MarkFlagRequired("subnet") - networkCreate.MarkFlagRequired("size") - - networkList.Flags().StringP("cursor", "c", "", "(optional) Cursor for paging.") - networkList.Flags().IntP("per-page", "p", 100, "(optional) Number of items requested per page. Default is 100 and Max is 500.") - - return networkCmd -} - -var networkGet = &cobra.Command{ - Use: "get ", - Short: "get a private network", - Long: ``, - Args: func(cmd *cobra.Command, args []string) error { - if len(args) < 1 { - return errors.New("please provide a networkID") - } - return nil - }, - Run: func(cmd *cobra.Command, args []string) { - id := args[0] - network, err := client.Network.Get(context.Background(), id) - if err != nil { - fmt.Printf("error getting network : %v\n", err) - os.Exit(1) - } - - printer.Network(network) - }, -} - -var networkList = &cobra.Command{ - Use: "list", - Short: "list all private networks", - Long: ``, - Run: func(cmd *cobra.Command, args []string) { - options := getPaging(cmd) - network, meta, err := client.Network.List(context.Background(), options) - if err != nil { - fmt.Printf("error getting network list : %v\n", err) - os.Exit(1) - } - - printer.NetworkList(network, meta) - }, -} - -var networkDelete = &cobra.Command{ - Use: "delete ", - Short: "delete a private network", - Aliases: []string{"destroy"}, - Long: ``, - Args: func(cmd *cobra.Command, args []string) error { - if len(args) < 1 { - return errors.New("please provide a networkID") - } - return nil - }, - Run: func(cmd *cobra.Command, args []string) { - id := args[0] - if err := client.Network.Delete(context.Background(), id); err != nil { - fmt.Printf("error deleting network : %v\n", err) - os.Exit(1) - } - - fmt.Println("Deleted network") - }, -} - -var networkCreate = &cobra.Command{ - Use: "create", - Short: "create a private network", - Long: ``, - Run: func(cmd *cobra.Command, args []string) { - region, _ := cmd.Flags().GetString("region-id") - description, _ := cmd.Flags().GetString("description") - subnet, _ := cmd.Flags().GetString("subnet") - size, _ := cmd.Flags().GetInt("size") - - options := &govultr.NetworkReq{ - Region: region, - Description: description, - V4Subnet: subnet, - V4SubnetMask: size, - } - - network, err := client.Network.Create(context.Background(), options) - if err != nil { - fmt.Printf("error creating network : %v\n", err) - os.Exit(1) - } - - printer.Network(network) - }, -} diff --git a/cmd/objectStorage.go b/cmd/objectStorage.go deleted file mode 100644 index 109d7c63..00000000 --- a/cmd/objectStorage.go +++ /dev/null @@ -1,198 +0,0 @@ -// Copyright © 2019 The Vultr-cli Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package cmd - -import ( - "context" - "errors" - "fmt" - "os" - - "github.com/spf13/cobra" - "github.com/vultr/vultr-cli/cmd/printer" -) - -// ObjectStorageCmd represents the objStorageCmd command -func ObjectStorageCmd() *cobra.Command { - objStorageCmd := &cobra.Command{ - Use: "object-storage", - Aliases: []string{"objStorage"}, - Short: "object storage commands", - Long: `object-storage is used to interact with the object-storage api`, - } - - objStorageCmd.AddCommand(objStorageCreate, objStorageLabelSet, objStorageList, objStorageGet, objStorageClusterList, objStorageS3KeyRegenerate, objStorageDestroy) - - // Create - objStorageCreate.Flags().StringP("label", "l", "", "label you want your object storage to have") - objStorageCreate.Flags().IntP("obj-store-clusterid", "o", 0, "obj-store-clusterid you want to create the object storage in") - objStorageCreate.MarkFlagRequired("obj-store-clusterid") - - // Label - objStorageLabelSet.Flags().StringP("label", "l", "", "label you want your object storage to have") - objStorageLabelSet.MarkFlagRequired("label") - - // List - objStorageList.Flags().StringP("cursor", "c", "", "(optional) Cursor for paging.") - objStorageList.Flags().IntP("per-page", "p", 100, "(optional) Number of items requested per page. Default is 100 and Max is 500.") - - // Regenerate - objStorageS3KeyRegenerate.Flags().StringP("s3-access-key", "s", "", "access key for a given object storage subscription") - - // Cluster List - objStorageClusterList.Flags().StringP("cursor", "c", "", "(optional) Cursor for paging.") - objStorageClusterList.Flags().IntP("per-page", "p", 100, "(optional) Number of items requested per page. Default is 100 and Max is 500.") - - return objStorageCmd -} - -var objStorageCreate = &cobra.Command{ - Use: "create", - Short: "create a new object storage subscription", - Long: ``, - Run: func(cmd *cobra.Command, args []string) { - objectStoreClusterID, _ := cmd.Flags().GetInt("obj-store-clusterid") - label, _ := cmd.Flags().GetString("label") - - objStorage, err := client.ObjectStorage.Create(context.TODO(), objectStoreClusterID, label) - if err != nil { - fmt.Printf("error creating object storage : %v\n", err) - os.Exit(1) - } - - printer.SingleObjectStorage(objStorage) - }, -} - -var objStorageLabelSet = &cobra.Command{ - Use: "label ", - Short: "change the label for object storage subscription", - Long: ``, - Args: func(cmd *cobra.Command, args []string) error { - if len(args) < 1 { - return errors.New("please provide an objectStorageID") - } - return nil - }, - Run: func(cmd *cobra.Command, args []string) { - id := args[0] - label, _ := cmd.Flags().GetString("label") - - err := client.ObjectStorage.Update(context.TODO(), id, label) - if err != nil { - fmt.Printf("error setting label : %v\n", err) - os.Exit(1) - } - - fmt.Printf("set label on object storage : %v\n", id) - }, -} - -var objStorageList = &cobra.Command{ - Use: "list", - Short: "retrieves a list of active object storage subscriptions", - Long: ``, - Run: func(cmd *cobra.Command, args []string) { - options := getPaging(cmd) - objStorage, meta, err := client.ObjectStorage.List(context.TODO(), options) - if err != nil { - fmt.Printf("error getting object storage : %v\n", err) - os.Exit(1) - } - - printer.ObjectStorages(objStorage, meta) - }, -} - -var objStorageGet = &cobra.Command{ - Use: "get ", - Short: "retrieves a given object storage subscription", - Long: ``, - Args: func(cmd *cobra.Command, args []string) error { - if len(args) < 1 { - return errors.New("please provide an objectStorageID") - } - return nil - }, - Run: func(cmd *cobra.Command, args []string) { - id := args[0] - objStorage, err := client.ObjectStorage.Get(context.TODO(), id) - if err != nil { - fmt.Printf("error getting object storage : %v\n", err) - os.Exit(1) - } - - printer.SingleObjectStorage(objStorage) - }, -} - -var objStorageClusterList = &cobra.Command{ - Use: "list-cluster", - Short: "retrieves a list of object storage clusters", - Long: ``, - Run: func(cmd *cobra.Command, args []string) { - options := getPaging(cmd) - cluster, meta, err := client.ObjectStorage.ListCluster(context.TODO(), options) - if err != nil { - fmt.Printf("error getting object storage clusters : %v\n", err) - os.Exit(1) - } - - printer.ObjectStorageClusterList(cluster, meta) - }, -} - -var objStorageS3KeyRegenerate = &cobra.Command{ - Use: "s3key-regenerate ", - Short: "regenerate the S3 API keys of an object storage subscription", - Long: ``, - Args: func(cmd *cobra.Command, args []string) error { - if len(args) < 1 { - return errors.New("please provide an objectStorageID") - } - return nil - }, - Run: func(cmd *cobra.Command, args []string) { - id := args[0] - s3Keys, err := client.ObjectStorage.RegenerateKeys(context.TODO(), id) - if err != nil { - fmt.Printf("error regenerating object storage keys : %v\n", err) - os.Exit(1) - } - - printer.ObjStorageS3KeyRegenerate(s3Keys) - }, -} - -var objStorageDestroy = &cobra.Command{ - Use: "delete ", - Short: "deletes an object storage subscription", - Long: ``, - Args: func(cmd *cobra.Command, args []string) error { - if len(args) < 1 { - return errors.New("please provide an objectStorageID") - } - return nil - }, - Run: func(cmd *cobra.Command, args []string) { - id := args[0] - if err := client.ObjectStorage.Delete(context.TODO(), id); err != nil { - fmt.Printf("error destroying object storage subscription : %v\n", err) - os.Exit(1) - } - - fmt.Println("destroyed object storage subscription") - }, -} diff --git a/cmd/objectstorage/objectstorage.go b/cmd/objectstorage/objectstorage.go new file mode 100644 index 00000000..f26fc9c9 --- /dev/null +++ b/cmd/objectstorage/objectstorage.go @@ -0,0 +1,272 @@ +// Package objectstorage provides the object storage commands for the CLI +package objectstorage + +import ( + "errors" + "fmt" + "os" + + "github.com/spf13/cobra" + "github.com/vultr/govultr/v3" + "github.com/vultr/vultr-cli/v3/cmd/printer" + "github.com/vultr/vultr-cli/v3/cmd/utils" + "github.com/vultr/vultr-cli/v3/pkg/cli" +) + +// NewCmdObjectStorage provides the CLI command for object storage functions +func NewCmdObjectStorage(base *cli.Base) *cobra.Command { //nolint:gocyclo + o := &options{Base: base} + + cmd := &cobra.Command{ + Use: "object-storage", + Short: "object storage commands", + Long: `object-storage is used to interact with object storages`, + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + utils.SetOptions(o.Base, cmd, args) + if !o.Base.HasAuth { + return errors.New(utils.APIKeyError) + } + return nil + }, + } + + // List + list := &cobra.Command{ + Use: "list", + Short: "retrieves a list of active object storages", + Long: ``, + RunE: func(cmd *cobra.Command, args []string) error { + o.Base.Options = utils.GetPaging(cmd) + + oss, meta, err := o.list() + if err != nil { + return fmt.Errorf("error retrieving object storage list : %v", err) + } + + data := &ObjectStoragesPrinter{ObjectStorages: oss, Meta: meta} + o.Base.Printer.Display(data, nil) + + return nil + }, + } + + list.Flags().StringP("cursor", "c", "", "(optional) Cursor for paging.") + list.Flags().IntP( + "per-page", + "p", + utils.PerPageDefault, + "(optional) Number of items requested per page. Default is 100 and Max is 500.", + ) + + // Get + get := &cobra.Command{ + Use: "get ", + Short: "retrieves a given object storage", + Long: ``, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("please provide an object storage ID") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + os, err := o.get() + if err != nil { + return fmt.Errorf("error getting object storage info : %v", err) + } + + data := &ObjectStoragePrinter{ObjectStorage: os} + o.Base.Printer.Display(data, nil) + + return nil + }, + } + + // Create + create := &cobra.Command{ + Use: "create", + Short: "create a new object storage", + Long: ``, + RunE: func(cmd *cobra.Command, args []string) error { + clusterID, errCl := cmd.Flags().GetInt("cluster-id") + if errCl != nil { + return fmt.Errorf("error parsing flag 'cluster-id' for object storage create : %v", errCl) + } + + label, errLa := cmd.Flags().GetString("label") + if errLa != nil { + return fmt.Errorf("error parsing flag 'label' for object storage create : %v", errLa) + } + + o.ClusterID = clusterID + o.Label = label + + os, err := o.create() + if err != nil { + return fmt.Errorf("error creating object storage : %v", err) + } + + data := &ObjectStoragePrinter{ObjectStorage: os} + o.Base.Printer.Display(data, nil) + + return nil + }, + } + + create.Flags().StringP("label", "l", "", "label you want your object storage to have") + create.Flags().IntP("cluster-id", "i", 0, "ID of the cluster in which to create the object storage") + if err := create.MarkFlagRequired("cluster-id"); err != nil { + printer.Error(fmt.Errorf("error marking object storage create 'cluster-id' flag required : %v", err)) + os.Exit(1) + } + + // Label + label := &cobra.Command{ + Use: "label ", + Short: "change the label for object storage subscription", + Long: ``, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("please provide an object storage ID") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + label, errLa := cmd.Flags().GetString("label") + if errLa != nil { + return fmt.Errorf("error parsing flag 'label' for object storage label : %v", errLa) + } + + o.Label = label + if err := o.update(); err != nil { + return fmt.Errorf("error updating object storage label : %v", err) + } + + o.Base.Printer.Display(printer.Info("object storage label has been set"), nil) + return nil + }, + } + + label.Flags().StringP("label", "l", "", "label you want your object storage to have") + if err := label.MarkFlagRequired("label"); err != nil { + printer.Error(fmt.Errorf("error marking object storage update 'label' flag required: %v", err)) + os.Exit(1) + } + + // Delete + del := &cobra.Command{ + Use: "delete ", + Short: "delete specified object storage", + Aliases: []string{"destroy"}, + Long: ``, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("please provide an object storage ID") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + if err := o.del(); err != nil { + return fmt.Errorf("unable to delete object storage : %v", err) + } + + o.Base.Printer.Display(printer.Info("object storage has been deleted"), nil) + return nil + }, + } + + // Regenerate Keys + regenerateKeys := &cobra.Command{ + Use: "regenerate-keys ", + Short: "regenerate the S3 API keys for object storage", + Long: ``, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("please provide an object storage ID") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + key, err := o.regenerateKeys() + if err != nil { + return fmt.Errorf("unable to regenerate keys for object storage : %v", err) + } + + data := &ObjectStorageKeysPrinter{Keys: key} + o.Base.Printer.Display(data, nil) + + return nil + }, + } + + // List Clusters + listClusters := &cobra.Command{ + Use: "list-clusters", + Short: "retrieve a list of all available object storage clusters", + Long: ``, + RunE: func(cmd *cobra.Command, args []string) error { + o.Base.Options = utils.GetPaging(cmd) + + clusters, meta, err := o.listClusters() + if err != nil { + return fmt.Errorf("error retrieving object storage cluster list : %v", err) + } + + data := &ObjectStorageClustersPrinter{Clusters: clusters, Meta: meta} + o.Base.Printer.Display(data, nil) + + return nil + }, + } + + cmd.AddCommand( + list, + get, + create, + label, + del, + regenerateKeys, + listClusters, + ) + + return cmd +} + +type options struct { + Base *cli.Base + ClusterID int + Label string +} + +func (o *options) list() ([]govultr.ObjectStorage, *govultr.Meta, error) { + oss, meta, _, err := o.Base.Client.ObjectStorage.List(o.Base.Context, o.Base.Options) + return oss, meta, err +} + +func (o *options) get() (*govultr.ObjectStorage, error) { + os, _, err := o.Base.Client.ObjectStorage.Get(o.Base.Context, o.Base.Args[0]) + return os, err +} + +func (o *options) create() (*govultr.ObjectStorage, error) { + os, _, err := o.Base.Client.ObjectStorage.Create(o.Base.Context, o.ClusterID, o.Label) + return os, err +} + +func (o *options) update() error { + return o.Base.Client.ObjectStorage.Update(o.Base.Context, o.Base.Args[0], o.Label) +} + +func (o *options) del() error { + return o.Base.Client.ObjectStorage.Delete(o.Base.Context, o.Base.Args[0]) +} + +func (o *options) listClusters() ([]govultr.ObjectStorageCluster, *govultr.Meta, error) { + clusters, meta, _, err := o.Base.Client.ObjectStorage.ListCluster(o.Base.Context, o.Base.Options) + return clusters, meta, err +} + +func (o *options) regenerateKeys() (*govultr.S3Keys, error) { + keys, _, err := o.Base.Client.ObjectStorage.RegenerateKeys(o.Base.Context, o.Base.Args[0]) + return keys, err +} diff --git a/cmd/objectstorage/printer.go b/cmd/objectstorage/printer.go new file mode 100644 index 00000000..e1989167 --- /dev/null +++ b/cmd/objectstorage/printer.go @@ -0,0 +1,212 @@ +package objectstorage + +import ( + "strconv" + + "github.com/vultr/govultr/v3" + "github.com/vultr/vultr-cli/v3/cmd/printer" +) + +// ObjectStoragesPrinter ... +type ObjectStoragesPrinter struct { + ObjectStorages []govultr.ObjectStorage `json:"object_storages"` + Meta *govultr.Meta `json:"meta"` +} + +// JSON ... +func (o *ObjectStoragesPrinter) JSON() []byte { + return printer.MarshalObject(o, "json") +} + +// YAML ... +func (o *ObjectStoragesPrinter) YAML() []byte { + return printer.MarshalObject(o, "yaml") +} + +// Columns ... +func (o *ObjectStoragesPrinter) Columns() [][]string { + return [][]string{0: { + "ID", + "REGION", + "CLUSTER ID", + "STATUS", + "LABEL", + "DATE CREATED", + "S3 HOSTNAME", + "S3 ACCESS KEY", + "S3 SECRET KEY", + }} +} + +// Data ... +func (o *ObjectStoragesPrinter) Data() [][]string { + if len(o.ObjectStorages) == 0 { + return [][]string{0: {"---", "---", "---", "---", "---", "---", "---", "---", "---"}} + } + + var data [][]string + for i := range o.ObjectStorages { + data = append(data, []string{ + o.ObjectStorages[i].ID, + o.ObjectStorages[i].Region, + strconv.Itoa(o.ObjectStorages[i].ObjectStoreClusterID), + o.ObjectStorages[i].Status, + o.ObjectStorages[i].Label, + o.ObjectStorages[i].DateCreated, + o.ObjectStorages[i].S3Keys.S3Hostname, + o.ObjectStorages[i].S3Keys.S3AccessKey, + o.ObjectStorages[i].S3Keys.S3SecretKey, + }) + } + + return data +} + +// Paging ... +func (o *ObjectStoragesPrinter) Paging() [][]string { + return printer.NewPaging(o.Meta.Total, &o.Meta.Links.Next, &o.Meta.Links.Prev).Compose() +} + +// ====================================== + +// ObjectStoragePrinter ... +type ObjectStoragePrinter struct { + ObjectStorage *govultr.ObjectStorage `json:"object_storage"` +} + +// JSON ... +func (o *ObjectStoragePrinter) JSON() []byte { + return printer.MarshalObject(o, "json") +} + +// YAML ... +func (o *ObjectStoragePrinter) YAML() []byte { + return printer.MarshalObject(o, "yaml") +} + +// Columns ... +func (o *ObjectStoragePrinter) Columns() [][]string { + return [][]string{0: { + "ID", + "REGION", + "CLUSTER ID", + "STATUS", + "LABEL", + "DATE CREATED", + "S3 HOSTNAME", + "S3 ACCESS KEY", + "S3 SECRET KEY", + }} +} + +// Data ... +func (o *ObjectStoragePrinter) Data() [][]string { + return [][]string{0: { + o.ObjectStorage.ID, + o.ObjectStorage.Region, + strconv.Itoa(o.ObjectStorage.ObjectStoreClusterID), + o.ObjectStorage.Status, + o.ObjectStorage.Label, + o.ObjectStorage.DateCreated, + o.ObjectStorage.S3Keys.S3Hostname, + o.ObjectStorage.S3Keys.S3AccessKey, + o.ObjectStorage.S3Keys.S3SecretKey, + }} +} + +// Paging ... +func (o *ObjectStoragePrinter) Paging() [][]string { + return nil +} + +// ====================================== + +// ObjectStorageClustersPrinter ... +type ObjectStorageClustersPrinter struct { + Clusters []govultr.ObjectStorageCluster `json:"clusters"` + Meta *govultr.Meta `json:"meta"` +} + +// JSON ... +func (o *ObjectStorageClustersPrinter) JSON() []byte { + return printer.MarshalObject(o, "json") +} + +// YAML ... +func (o *ObjectStorageClustersPrinter) YAML() []byte { + return printer.MarshalObject(o, "yaml") +} + +// Columns ... +func (o *ObjectStorageClustersPrinter) Columns() [][]string { + return [][]string{0: { + "CLUSTER ID", + "REGION ID", + "HOSTNAME", + "DEPLOY", + }} +} + +// Data ... +func (o *ObjectStorageClustersPrinter) Data() [][]string { + if len(o.Clusters) == 0 { + return [][]string{0: {"---", "---", "---", "---"}} + } + + var data [][]string + for i := range o.Clusters { + data = append(data, []string{ + strconv.Itoa(o.Clusters[i].ID), + o.Clusters[i].Region, + o.Clusters[i].Hostname, + o.Clusters[i].Deploy, + }) + } + + return data +} + +// Paging ... +func (o *ObjectStorageClustersPrinter) Paging() [][]string { + return printer.NewPaging(o.Meta.Total, &o.Meta.Links.Next, &o.Meta.Links.Prev).Compose() +} + +// ====================================== + +// ObjectStorageKeysPrinter ... +type ObjectStorageKeysPrinter struct { + Keys *govultr.S3Keys `json:"s3_credentials"` +} + +// JSON ... +func (o *ObjectStorageKeysPrinter) JSON() []byte { + return printer.MarshalObject(o, "json") +} + +// YAML ... +func (o *ObjectStorageKeysPrinter) YAML() []byte { + return printer.MarshalObject(o, "yaml") +} + +// Columns ... +func (o *ObjectStorageKeysPrinter) Columns() [][]string { + return [][]string{0: { + "S3 HOSTNAME", + "S3 ACCESS KEY", + "S3 SECRET KEY", + }} +} + +// Data ... +func (o *ObjectStorageKeysPrinter) Data() [][]string { + return [][]string{0: { + o.Keys.S3Hostname, + o.Keys.S3AccessKey, + o.Keys.S3SecretKey, + }} +} + +// Paging ... +func (o *ObjectStorageKeysPrinter) Paging() [][]string { + return nil +} diff --git a/cmd/operatingSystems/operatingSystems.go b/cmd/operatingSystems/operatingSystems.go deleted file mode 100644 index fb5cb828..00000000 --- a/cmd/operatingSystems/operatingSystems.go +++ /dev/null @@ -1,109 +0,0 @@ -// Copyright © 2019 The Vultr-cli Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package operatingSystems - -import ( - "context" - "fmt" - - "github.com/spf13/cobra" - "github.com/spf13/viper" - "github.com/vultr/govultr/v2" - "github.com/vultr/vultr-cli/cmd/printer" - "github.com/vultr/vultr-cli/cmd/utils" - "github.com/vultr/vultr-cli/pkg/cli" -) - -var ( - long = `OS will retrieve available operating systems that can be deployed` - example = ` - # Example - vultr-cli os - ` - - listLong = `List all operating systems available to be deployed on Vultr` - listExample = ` - # Full example - vultr-cli os list - - # Full example with paging - vultr-cli os list --per-page=1 --cursor="bmV4dF9fMTI0" - - # Shortened with alias commands - vultr-cli o l - ` -) - -// Interface for os -type Interface interface { - validate(cmd *cobra.Command, args []string) - List() ([]govultr.OS, *govultr.Meta, error) -} - -// Options for os -type Options struct { - Base *cli.Base -} - -// NewOSOptions returns Options struct -func NewOSOptions(base *cli.Base) *Options { - return &Options{Base: base} -} - -// NewCmdOS creates cobra command for OS -func NewCmdOS(base *cli.Base) *cobra.Command { - o := NewOSOptions(base) - - cmd := &cobra.Command{ - Use: "os", - Short: "list available operating systems", - Aliases: []string{"o"}, - Long: long, - Example: example, - } - - list := &cobra.Command{ - Use: "list", - Short: "list all available operating systems", - Aliases: []string{"l"}, - Long: listLong, - Example: listExample, - Run: func(cmd *cobra.Command, args []string) { - o.validate(cmd, args) - os, meta, err := o.List() - data := &printer.OS{OS: os, Meta: meta} - - fmt.Println(o.Base.Printer.Output) - o.Base.Printer.Display(data, err) - }, - } - - list.Flags().StringP("cursor", "c", "", "(optional) Cursor for paging.") - list.Flags().IntP("per-page", "p", 100, "(optional) Number of items requested per page. Default is 100 and Max is 500.") - - cmd.AddCommand(list) - return cmd -} - -func (o *Options) validate(cmd *cobra.Command, args []string) { - o.Base.Args = args - o.Base.Options = utils.GetPaging(cmd) - o.Base.Printer.Output = viper.GetString("output") -} - -// List all os -func (o *Options) List() ([]govultr.OS, *govultr.Meta, error) { - return o.Base.Client.OS.List(context.Background(), o.Base.Options) -} diff --git a/cmd/operatingSystems/operatingSystems_test.go b/cmd/operatingSystems/operatingSystems_test.go deleted file mode 100644 index 2400ff1e..00000000 --- a/cmd/operatingSystems/operatingSystems_test.go +++ /dev/null @@ -1,87 +0,0 @@ -package operatingSystems - -import ( - "context" - "reflect" - "testing" - - "github.com/vultr/govultr/v2" - "github.com/vultr/vultr-cli/pkg/cli" -) - -type mockVultrOS struct { - client *govultr.Client -} - -func (m mockVultrOS) List(ctx context.Context, options *govultr.ListOptions) ([]govultr.OS, *govultr.Meta, error) { - return []govultr.OS{{ - ID: 127, - Name: "CentOS 6 x64", - Arch: "x64", - Family: "centos", - }}, &govultr.Meta{ - Total: 0, - Links: nil, - }, nil -} - -func TestOptions_List(t *testing.T) { - os := NewOSOptions(&cli.Base{Client: &govultr.Client{OS: mockVultrOS{nil}}}) - - expectedOS := []govultr.OS{{ - ID: 127, - Name: "CentOS 6 x64", - Arch: "x64", - Family: "centos", - }} - - expectedMeta := &govultr.Meta{ - Total: 0, - Links: nil, - } - - osList, meta, _ := os.List() - - if !reflect.DeepEqual(osList, expectedOS) { - t.Errorf("OSOptions.list returned %v expected %v", osList, expectedMeta) - } - - if !reflect.DeepEqual(expectedMeta, meta) { - t.Errorf("PlanOptions.metal returned %v expected %v", meta, expectedMeta) - } -} - -func TestNewCmdOS(t *testing.T) { - cmd := NewCmdOS(&cli.Base{Client: &govultr.Client{OS: mockVultrOS{nil}}}) - - if cmd.Short != "list available operating systems" { - t.Errorf("invalid short") - } - - if cmd.Use != "os" { - t.Errorf("invalid os") - } - - alias := []string{"o"} - if !reflect.DeepEqual(cmd.Aliases, alias) { - t.Errorf("expected alias %v got %v", alias, cmd.Aliases) - } -} - -func TestNewOSOptions(t *testing.T) { - options := NewOSOptions(&cli.Base{Client: &govultr.Client{OS: mockVultrOS{nil}}}) - - ref := reflect.TypeOf(options) - if _, ok := ref.MethodByName("List"); !ok { - t.Errorf("Missing list function") - } - - if _, ok := ref.MethodByName("validate"); ok { - t.Errorf("validate isn't exported shouldn't be accessible") - } - - oInterface := reflect.TypeOf(new(Interface)).Elem() - if !ref.Implements(oInterface) { - t.Errorf("Options does not implement Interface") - } -} diff --git a/cmd/operatingsystems/operatingsystems.go b/cmd/operatingsystems/operatingsystems.go new file mode 100644 index 00000000..d7254af0 --- /dev/null +++ b/cmd/operatingsystems/operatingsystems.go @@ -0,0 +1,90 @@ +// Package operatingsystems provides the operating systems functionality for +// the CLI +package operatingsystems + +import ( + "context" + "fmt" + + "github.com/spf13/cobra" + "github.com/vultr/govultr/v3" + "github.com/vultr/vultr-cli/v3/cmd/utils" + "github.com/vultr/vultr-cli/v3/pkg/cli" +) + +var ( + long = `OS will retrieve available operating systems that can be deployed` + example = ` + # Example + vultr-cli os + ` + + listLong = `List all operating systems available to be deployed on Vultr` + listExample = ` + # Full example + vultr-cli os list + + # Full example with paging + vultr-cli os list --per-page=1 --cursor="bmV4dF9fMTI0" + + # Shortened with alias commands + vultr-cli o l + ` +) + +// NewCmdOS provides the command for operating systems to the CLI +func NewCmdOS(base *cli.Base) *cobra.Command { + o := &options{Base: base} + + cmd := &cobra.Command{ + Use: "os", + Short: "list available operating systems", + Aliases: []string{"o"}, + Long: long, + Example: example, + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + utils.SetOptions(o.Base, cmd, args) + return nil + }, + } + + // List + list := &cobra.Command{ + Use: "list", + Short: "list all available operating systems", + Aliases: []string{"l"}, + Long: listLong, + Example: listExample, + RunE: func(cmd *cobra.Command, args []string) error { + os, meta, err := o.list() + if err != nil { + return fmt.Errorf("error getting operating systems : %v", err) + } + + data := &OSPrinter{OperatingSystems: os, Meta: meta} + o.Base.Printer.Display(data, err) + + return nil + }, + } + + list.Flags().StringP("cursor", "c", "", "(optional) Cursor for paging.") + list.Flags().IntP( + "per-page", + "p", + utils.PerPageDefault, + fmt.Sprintf("(optional) Number of items requested per page. Default is %d and Max is 500.", utils.PerPageDefault), + ) + + cmd.AddCommand(list) + return cmd +} + +type options struct { + Base *cli.Base +} + +func (o *options) list() ([]govultr.OS, *govultr.Meta, error) { + list, meta, _, err := o.Base.Client.OS.List(context.Background(), o.Base.Options) + return list, meta, err +} diff --git a/cmd/operatingsystems/printer.go b/cmd/operatingsystems/printer.go new file mode 100644 index 00000000..49dcd751 --- /dev/null +++ b/cmd/operatingsystems/printer.go @@ -0,0 +1,60 @@ +package operatingsystems + +import ( + "strconv" + + "github.com/vultr/govultr/v3" + "github.com/vultr/vultr-cli/v3/cmd/printer" +) + +// OSPrinter represents the plans data from the API +type OSPrinter struct { + OperatingSystems []govultr.OS `json:"os"` + Meta *govultr.Meta `json:"meta"` +} + +// JSON provides the JSON formatted byte data +func (o *OSPrinter) JSON() []byte { + return printer.MarshalObject(o, "json") +} + +// YAML provides the YAML formatted byte data +func (o *OSPrinter) YAML() []byte { + return printer.MarshalObject(o, "yaml") +} + +// Columns provides the plan columns for the printer +func (o *OSPrinter) Columns() [][]string { + return [][]string{0: { + "ID", + "NAME", + "ARCH", + "FAMILY", + }} +} + +// Data provides the plan data for the printer +func (o *OSPrinter) Data() [][]string { + var data [][]string + + if len(o.OperatingSystems) == 0 { + data = append(data, []string{"---", "---", "---", "---"}) + return data + } + + for i := range o.OperatingSystems { + data = append(data, []string{ + strconv.Itoa(o.OperatingSystems[i].ID), + o.OperatingSystems[i].Name, + o.OperatingSystems[i].Arch, + o.OperatingSystems[i].Family, + }) + } + + return data +} + +// Paging validates and forms the paging data for output +func (o *OSPrinter) Paging() [][]string { + return printer.NewPaging(o.Meta.Total, &o.Meta.Links.Next, &o.Meta.Links.Prev).Compose() +} diff --git a/cmd/plans/plans.go b/cmd/plans/plans.go index 29348aba..4297d7a1 100644 --- a/cmd/plans/plans.go +++ b/cmd/plans/plans.go @@ -1,28 +1,14 @@ -// Copyright © 2019 The Vultr-cli Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - +// Package plans provides the functionality for the CLI to access plans package plans import ( "context" + "fmt" "github.com/spf13/cobra" - "github.com/spf13/viper" - "github.com/vultr/govultr/v2" - "github.com/vultr/vultr-cli/cmd/printer" - "github.com/vultr/vultr-cli/cmd/utils" - "github.com/vultr/vultr-cli/pkg/cli" + "github.com/vultr/govultr/v3" + "github.com/vultr/vultr-cli/v3/cmd/utils" + "github.com/vultr/vultr-cli/v3/pkg/cli" ) var ( @@ -44,8 +30,8 @@ var ( vultr-cli p l ` - metalLong = `List all available bare metal plans on Vultr.` - metalExample = ` + metalListLong = `Get plans for bare-metal servers` + metalListExample = ` # Full example vultr-cli plans metal @@ -57,27 +43,9 @@ var ( ` ) -// PlanOptionsInterface interface -type PlanOptionsInterface interface { - validate(cmd *cobra.Command, args []string) - List() ([]govultr.Plan, *govultr.Meta, error) - MetalList() ([]govultr.BareMetalPlan, *govultr.Meta, error) -} - -// PlanOptions struct specific for plans -type PlanOptions struct { - Base *cli.Base - PlanType string -} - -// NewPlanOptions returns a PlanOptions struct -func NewPlanOptions(Base *cli.Base) *PlanOptions { - return &PlanOptions{Base: Base} -} - // NewCmdPlan returns the cobra command for Plans -func NewCmdPlan(Base *cli.Base) *cobra.Command { - p := NewPlanOptions(Base) +func NewCmdPlan(base *cli.Base) *cobra.Command { + o := &options{Base: base} cmd := &cobra.Command{ Use: "plans", @@ -85,72 +53,101 @@ func NewCmdPlan(Base *cli.Base) *cobra.Command { Aliases: []string{"p", "plan"}, Long: planLong, Example: planExample, + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + utils.SetOptions(o.Base, cmd, args) + return nil + }, } list := &cobra.Command{ Use: "list", - Short: "list all instance plans", + Short: "List all instance plans", Aliases: []string{"l"}, Long: listLong, Example: listExample, - Run: func(cmd *cobra.Command, args []string) { - p.validate(cmd, args) - plans, meta, err := p.List() - data := &printer.Plans{Plan: plans, Meta: meta} - p.Base.Printer.Display(data, err) + RunE: func(cmd *cobra.Command, args []string) error { + o.Base.Options = utils.GetPaging(cmd) + + planType, errTy := cmd.Flags().GetString("type") + if errTy != nil { + return fmt.Errorf("error parsing flag 'type' for plan list: %v", errTy) + } + + o.PlanType = planType + + plans, meta, err := o.list() + if err != nil { + return fmt.Errorf("error getting plans : %v", err) + } + + data := &PlansPrinter{Plans: plans, Meta: meta} + o.Base.Printer.Display(data, err) + + return nil }, } list.Flags().StringP("cursor", "c", "", "(optional) Cursor for paging.") - list.Flags().IntP("per-page", "p", 100, "(optional) Number of items requested per page. Default is 100 and Max is 500.") - list.Flags().StringP("type", "t", "", "(optional) The type of plans to return. Possible values: 'vc2', 'vdc', 'vhf', 'dedicated'. Defaults to all Instances plans.") + list.Flags().IntP( + "per-page", + "p", + utils.PerPageDefault, + fmt.Sprintf("(optional) Number of items requested per page. Default is %d and Max is 500.", utils.PerPageDefault), + ) + list.Flags().StringP( + "type", + "t", + "", + "(optional) The type of plans to return. Possible values: 'vc2', 'vdc', 'vhf', 'dedicated'. Defaults to all Instances plans.", + ) metal := &cobra.Command{ Use: "metal", - Short: "list all bare metal plans", + Short: "List all bare metal plans", Aliases: []string{"m"}, - Long: metalLong, - Example: metalExample, - Run: func(cmd *cobra.Command, args []string) { - p.validate(cmd, args) - m, meta, err := p.MetalList() - data := &printer.BaremetalPlans{ - Plan: m, - Meta: meta, + Long: metalListLong, + Example: metalListExample, + RunE: func(cmd *cobra.Command, args []string) error { + o.Base.Options = utils.GetPaging(cmd) + + m, meta, err := o.metalList() + if err != nil { + return fmt.Errorf("error getting bare metal plans : %v", err) } - p.Base.Printer.Display(data, err) + + data := &MetalPlansPrinter{Plans: m, Meta: meta} + o.Base.Printer.Display(data, err) + + return nil }, } metal.Flags().StringP("cursor", "c", "", "(optional) Cursor for paging.") - metal.Flags().IntP("per-page", "p", 100, "(optional) Number of items requested per page. Default is 100 and Max is 500.") + metal.Flags().IntP( + "per-page", + "p", + utils.PerPageDefault, + fmt.Sprintf("(optional) Number of items requested per page. Default is %d and Max is 500.", utils.PerPageDefault), + ) cmd.AddCommand(list, metal) return cmd } -func (p *PlanOptions) validate(cmd *cobra.Command, args []string) { - p.Base.Args = args - p.Base.Options = utils.GetPaging(cmd) - p.PlanType, _ = cmd.Flags().GetString("type") - p.Base.Printer.Output = viper.GetString("output") +type options struct { + Base *cli.Base + PlanType string } -// List retrieves all available instance plans -func (p *PlanOptions) List() ([]govultr.Plan, *govultr.Meta, error) { - plans, meta, err := p.Base.Client.Plan.List(context.Background(), p.PlanType, p.Base.Options) - if err != nil { - return nil, nil, err - } - - return plans, meta, nil +func (o *options) validate(cmd *cobra.Command, args []string) { + o.Base.Args = args } -// MetalList retrieves all available bare metal plans -func (p *PlanOptions) MetalList() ([]govultr.BareMetalPlan, *govultr.Meta, error) { - plans, meta, err := p.Base.Client.Plan.ListBareMetal(context.Background(), p.Base.Options) - if err != nil { - return nil, nil, err - } +func (o *options) list() ([]govultr.Plan, *govultr.Meta, error) { + plans, meta, _, err := o.Base.Client.Plan.List(context.Background(), o.PlanType, o.Base.Options) + return plans, meta, err +} - return plans, meta, nil +func (o *options) metalList() ([]govultr.BareMetalPlan, *govultr.Meta, error) { + plans, meta, _, err := o.Base.Client.Plan.ListBareMetal(context.Background(), o.Base.Options) + return plans, meta, err } diff --git a/cmd/plans/plans_test.go b/cmd/plans/plans_test.go deleted file mode 100644 index f5a74554..00000000 --- a/cmd/plans/plans_test.go +++ /dev/null @@ -1,165 +0,0 @@ -package plans - -import ( - "context" - "reflect" - "testing" - - "github.com/vultr/govultr/v2" - "github.com/vultr/vultr-cli/pkg/cli" -) - -type mockVultrPlans struct { - client *govultr.Client -} - -func (m mockVultrPlans) List(ctx context.Context, planType string, options *govultr.ListOptions) ([]govultr.Plan, *govultr.Meta, error) { - plans := []govultr.Plan{ - { - ID: "vhf-8c-32gb", - VCPUCount: 8, - RAM: 32768, - Disk: 512, - DiskCount: 1, - Bandwidth: 6144, - MonthlyCost: 192, - Type: "vhf", - Locations: []string{"ewr"}, - }, - } - - meta := &govultr.Meta{ - Total: 1, - Links: nil, - } - - return plans, meta, nil -} - -func (m mockVultrPlans) ListBareMetal(ctx context.Context, options *govultr.ListOptions) ([]govultr.BareMetalPlan, *govultr.Meta, error) { - metalPlans := []govultr.BareMetalPlan{ - { - ID: "vbm-4c-32gb", - CPUCount: 4, - CPUModel: "E3-1270v6", - CPUThreads: 8, - RAM: 8, - Disk: 20, - DiskCount: 1, - Bandwidth: 10, - MonthlyCost: 120, - Type: "NVMe", - Locations: []string{"ewr"}, - }, - } - meta := &govultr.Meta{ - Total: 1, - Links: nil, - } - return metalPlans, meta, nil -} - -func TestPlanOptions_List(t *testing.T) { - planOption := NewPlanOptions(&cli.Base{Client: &govultr.Client{Plan: mockVultrPlans{nil}}}) - - expectedPlan := []govultr.Plan{ - { - ID: "vhf-8c-32gb", - VCPUCount: 8, - RAM: 32768, - Disk: 512, - DiskCount: 1, - Bandwidth: 6144, - MonthlyCost: 192, - Type: "vhf", - Locations: []string{"ewr"}, - }, - } - expectedMeta := &govultr.Meta{ - Total: 1, - Links: nil, - } - - plan, meta, _ := planOption.List() - - if !reflect.DeepEqual(expectedPlan, plan) { - t.Errorf("PlanOptions.list returned %v expected %v", plan, expectedPlan) - } - - if !reflect.DeepEqual(expectedMeta, meta) { - t.Errorf("PlanOptions.list returned %v expected %v", plan, expectedPlan) - } -} - -func TestPlanOptions_Metal(t *testing.T) { - planMetal := NewPlanOptions(&cli.Base{Client: &govultr.Client{Plan: mockVultrPlans{nil}}}) - - expectedMetal := []govultr.BareMetalPlan{ - { - ID: "vbm-4c-32gb", - CPUCount: 4, - CPUModel: "E3-1270v6", - CPUThreads: 8, - RAM: 8, - Disk: 20, - DiskCount: 1, - Bandwidth: 10, - MonthlyCost: 120, - Type: "NVMe", - Locations: []string{"ewr"}, - }, - } - expectedMeta := &govultr.Meta{ - Total: 1, - Links: nil, - } - - plan, meta, _ := planMetal.MetalList() - - if !reflect.DeepEqual(expectedMetal, plan) { - t.Errorf("PlanOptions.list returned %v expected %v", plan, expectedMetal) - } - - if !reflect.DeepEqual(expectedMeta, meta) { - t.Errorf("PlanOptions.metal returned %v expected %v", meta, expectedMeta) - } -} - -func TestNewCmdPlan(t *testing.T) { - planOptions := NewPlanOptions(&cli.Base{Client: &govultr.Client{Plan: mockVultrPlans{nil}}}) - - ref := reflect.TypeOf(planOptions) - if _, ok := ref.MethodByName("List"); !ok { - t.Errorf("Missing list function") - } - - if _, ok := ref.MethodByName("MetalList"); !ok { - t.Errorf("Missing metal function") - } - - if _, ok := ref.MethodByName("validate"); ok { - t.Errorf("validate isn't exported shouldn't be accessible") - } - - pInterface := reflect.TypeOf(new(PlanOptionsInterface)).Elem() - if !ref.Implements(pInterface) { - t.Errorf("PlanOptions does not implement PlanOptionsInterface") - } -} - -func TestNewPlanOptions(t *testing.T) { - cmd := NewCmdPlan(&cli.Base{Client: &govultr.Client{Plan: mockVultrPlans{nil}}}) - - if cmd.Short != "get information about Vultr plans" { - t.Errorf("invalid short") - } - - if cmd.Use != "plans" { - t.Errorf("invalid plans") - } - - alias := []string{"p", "plan"} - if !reflect.DeepEqual(cmd.Aliases, alias) { - t.Errorf("expected alias %v got %v", alias, cmd.Aliases) - } -} diff --git a/cmd/plans/printer.go b/cmd/plans/printer.go new file mode 100644 index 00000000..ae4c9acc --- /dev/null +++ b/cmd/plans/printer.go @@ -0,0 +1,161 @@ +package plans + +import ( + "encoding/json" + "strconv" + + "github.com/vultr/govultr/v3" + "github.com/vultr/vultr-cli/v3/cmd/printer" + "github.com/vultr/vultr-cli/v3/cmd/utils" + "gopkg.in/yaml.v3" +) + +// PlansPrinter represents the plans data from the API +type PlansPrinter struct { + Plans []govultr.Plan `json:"plans"` + Meta *govultr.Meta `json:"meta"` +} + +// JSON provides the JSON formatted byte data +func (p *PlansPrinter) JSON() []byte { + js, err := json.MarshalIndent(p, "", " ") + if err != nil { + panic(err.Error()) + } + + return js +} + +// YAML provides the YAML formatted byte data +func (p *PlansPrinter) YAML() []byte { + yml, err := yaml.Marshal(p) + if err != nil { + panic(err.Error()) + } + return yml +} + +// Columns provides the plan columns for the printer +func (p *PlansPrinter) Columns() [][]string { + return [][]string{0: { + "ID", + "VCPU COUNT", + "RAM", + "DISK", + "DISK COUNT", + "BANDWIDTH GB", + "PRICE PER MONTH", + "TYPE", + "GPU VRAM", + "GPU TYPE", + "REGIONS", + }} +} + +// Data provides the plan data for the printer +func (p *PlansPrinter) Data() [][]string { + data := [][]string{} + + if len(p.Plans) == 0 { + data = append(data, []string{"---", "---", "---", "---", "---", "---", "---", "---", "---", "---", "---"}) + return data + } + + for i := range p.Plans { + data = append(data, []string{ + p.Plans[i].ID, + strconv.Itoa(p.Plans[i].VCPUCount), + strconv.Itoa(p.Plans[i].RAM), + strconv.Itoa(p.Plans[i].Disk), + strconv.Itoa(p.Plans[i].DiskCount), + strconv.Itoa(p.Plans[i].Bandwidth), + strconv.FormatFloat(float64(p.Plans[i].MonthlyCost), 'f', utils.DecimalPrecision, 32), + p.Plans[i].Type, + strconv.Itoa(p.Plans[i].GPUVRAM), + p.Plans[i].GPUType, + printer.ArrayOfStringsToString(p.Plans[i].Locations), + }) + } + + return data +} + +// Paging validates and forms the paging data for output +func (p *PlansPrinter) Paging() [][]string { + return printer.NewPaging(p.Meta.Total, &p.Meta.Links.Next, &p.Meta.Links.Prev).Compose() +} + +// MetalPlansPrinter represents the bare metal plans data from the API +type MetalPlansPrinter struct { + Plans []govultr.BareMetalPlan `json:"plans_metal"` + Meta *govultr.Meta `json:"meta"` +} + +// JSON provides the JSON formatted byte data +func (m *MetalPlansPrinter) JSON() []byte { + js, err := json.MarshalIndent(m, "", " ") + if err != nil { + panic(err.Error()) + } + + return js +} + +// YAML provides the YAML formatted byte data +func (m *MetalPlansPrinter) YAML() []byte { + yml, err := yaml.Marshal(m) + if err != nil { + panic(err.Error()) + } + return yml +} + +// Columns provides the plan columns for the printer +func (m *MetalPlansPrinter) Columns() [][]string { + return [][]string{0: { + "ID", + "CPU COUNT", + "CPU MODEL", + "CPU THREADS", + "RAM", + "DISK", + "DISK COUNT", + "BANDWIDTH GB", + "PRICE PER MONTH", + "TYPE", + "REGIONS", + }} +} + +// Data provides the plan data for the printer +func (m *MetalPlansPrinter) Data() [][]string { + data := [][]string{} + + if len(data) == 0 { + data = append(data, []string{"---", "---", "---", "---", "---", "---", "---", "---", "---", "---", "---"}) + return data + } + + for i := range m.Plans { + data = append(data, []string{ + m.Plans[i].ID, + strconv.Itoa(m.Plans[i].CPUCount), + m.Plans[i].CPUModel, + strconv.Itoa(m.Plans[i].CPUThreads), + strconv.Itoa(m.Plans[i].RAM), + strconv.Itoa(m.Plans[i].Disk), + strconv.Itoa(m.Plans[i].DiskCount), + strconv.Itoa(m.Plans[i].Bandwidth), + strconv.FormatFloat(float64(m.Plans[i].MonthlyCost), 'f', utils.DecimalPrecision, 32), + m.Plans[i].Type, + printer.ArrayOfStringsToString(m.Plans[i].Locations), + }) + } + + return data +} + +// Paging validates and forms the paging data for output +func (m *MetalPlansPrinter) Paging() [][]string { + return printer.NewPaging(m.Meta.Total, &m.Meta.Links.Next, &m.Meta.Links.Prev).Compose() +} diff --git a/cmd/printer/application.go b/cmd/printer/application.go deleted file mode 100644 index 9752b4e2..00000000 --- a/cmd/printer/application.go +++ /dev/null @@ -1,52 +0,0 @@ -package printer - -import ( - "encoding/json" - - "github.com/go-yaml/yaml" - "github.com/vultr/govultr/v2" -) - -var _ ResourceOutput = &Applications{} - -type Applications struct { - Applications []govultr.Application `json:"applications"` - Meta *govultr.Meta -} - -func (a *Applications) JSON() []byte { - prettyJSON, err := json.MarshalIndent(a, "", " ") - if err != nil { - panic("move this into byte") - } - - return prettyJSON -} - -func (a *Applications) Yaml() []byte { - yam, err := yaml.Marshal(a) - if err != nil { - panic("move this into byte") - } - return yam -} - -func (a *Applications) Columns() map[int][]interface{} { - return map[int][]interface{}{0: {"ID", "NAME", "SHORT NAME", "DEPLOY NAME"}} -} - -func (a *Applications) Data() map[int][]interface{} { - data := map[int][]interface{}{} - for k, a := range a.Applications { - data[k] = []interface{}{a.ID, a.Name, a.ShortName, a.DeployName} - } - return data -} - -func (a *Applications) Paging() map[int][]interface{} { - return map[int][]interface{}{ - 0: {"======================================"}, - 1: {"TOTAL", "NEXT PAGE", "PREV PAGE"}, - 2: {a.Meta.Total, a.Meta.Links.Next, a.Meta.Links.Prev}, - } -} diff --git a/cmd/printer/backup.go b/cmd/printer/backup.go deleted file mode 100644 index cd272d44..00000000 --- a/cmd/printer/backup.go +++ /dev/null @@ -1,23 +0,0 @@ -package printer - -import ( - "github.com/vultr/govultr/v2" -) - -func Backups(bs []govultr.Backup, meta *govultr.Meta) { - col := columns{"ID", "DATE CREATED", "DESCRIPTION", "SIZE", "STATUS"} - display(col) - for _, b := range bs { - display(columns{b.ID, b.DateCreated, b.Description, b.Size, b.Status}) - } - - Meta(meta) - flush() -} - -func Backup(bs *govultr.Backup) { - col := columns{"ID", "DATE CREATED", "DESCRIPTION", "SIZE", "STATUS"} - display(col) - - flush() -} diff --git a/cmd/printer/bareMetal.go b/cmd/printer/bareMetal.go deleted file mode 100644 index 59e86e99..00000000 --- a/cmd/printer/bareMetal.go +++ /dev/null @@ -1,60 +0,0 @@ -package printer - -import ( - "github.com/vultr/govultr/v2" -) - -func BareMetal(b *govultr.BareMetalServer) { - col := columns{"ID", "IP", "TAG", "MAC ADDRESS", "LABEL", "OS", "STATUS", "REGION", "CPU", "RAM", "DISK", "FEATURES"} - display(col) - - display(columns{b.ID, b.MainIP, b.Tag, b.MacAddress, b.Label, b.Os, b.Status, b.Region, b.CPUCount, b.RAM, b.Disk, b.Features}) - - flush() -} - -func BareMetalList(bms []govultr.BareMetalServer, meta *govultr.Meta) { - col := columns{"ID", "IP", "TAG", "MAC ADDRESS", "LABEL", "OS", "STATUS", "REGION", "CPU", "RAM", "DISK", "FEATURES"} - display(col) - for _, b := range bms { - display(columns{b.ID, b.MainIP, b.Tag, b.MacAddress, b.Label, b.Os, b.Status, b.Region, b.CPUCount, b.RAM, b.Disk, b.Features}) - } - - Meta(meta) - - flush() -} - -func BareMetalBandwidth(bw *govultr.Bandwidth) { - display(columns{"DATE", "INCOMING BYTES", "OUTGOING BYTES"}) - for k, b := range bw.Bandwidth { - display(columns{k, b.IncomingBytes, b.OutgoingBytes}) - } - flush() -} - -func BareMetalIPV4Info(info []govultr.IPv4, meta *govultr.Meta) { - display(columns{"IP", "NETMASK", "GATEWAY", "TYPE"}) - for _, i := range info { - display(columns{i.IP, i.Netmask, i.Gateway, i.Type}) - } - - Meta(meta) - flush() -} - -func BareMetalIPV6Info(info []govultr.IPv6, meta *govultr.Meta) { - display(columns{"IP", "NETWORK", "NETWORK SIZE", "TYPE"}) - for _, i := range info { - display(columns{i.IP, i.Network, i.NetworkSize, i.Type}) - } - - Meta(meta) - flush() -} - -func BareMetalVNCUrl(vnc *govultr.VNCUrl) { - display(columns{"VNC URL"}) - display(columns{vnc.URL}) - flush() -} diff --git a/cmd/printer/blockStorage.go b/cmd/printer/blockStorage.go deleted file mode 100644 index 21c2b081..00000000 --- a/cmd/printer/blockStorage.go +++ /dev/null @@ -1,27 +0,0 @@ -package printer - -import ( - "fmt" - - "github.com/vultr/govultr/v2" -) - -func BlockStorage(bs []govultr.BlockStorage, meta *govultr.Meta) { - col := columns{"ID", "REGION ID", "INSTANCE ID", "SIZE GB", "STATUS", "LABEL", "DATE CREATED", "MONTHLY COST", "MOUNT ID"} - display(col) - for _, b := range bs { - cost := fmt.Sprintf("$%v", b.Cost) - display(columns{b.ID, b.Region, b.AttachedToInstance, b.SizeGB, b.Status, b.Label, b.DateCreated, cost, b.MountID}) - } - - Meta(meta) - flush() -} - -func SingleBlockStorage(b *govultr.BlockStorage) { - col := columns{"ID", "REGION ID", "INSTANCE ID", "SIZE GB", "STATUS", "LABEL", "DATE CREATED", "MONTHLY COST", "MOUNT ID"} - display(col) - cost := fmt.Sprintf("$%v", b.Cost) - display(columns{b.ID, b.Region, b.AttachedToInstance, b.SizeGB, b.Status, b.Label, b.DateCreated, cost, b.MountID}) - flush() -} diff --git a/cmd/printer/dnsDomain.go b/cmd/printer/dnsDomain.go deleted file mode 100644 index 1798dee3..00000000 --- a/cmd/printer/dnsDomain.go +++ /dev/null @@ -1,38 +0,0 @@ -package printer - -import "github.com/vultr/govultr/v2" - -func SecInfo(info []string) { - col := columns{"DNSSEC INFO"} - display(col) - for _, i := range info { - display(columns{i}) - } - flush() -} - -func DomainList(domain []govultr.Domain, meta *govultr.Meta) { - col := columns{"DOMAIN", "DATE CREATED"} - display(col) - for _, d := range domain { - display(columns{d.Domain, d.DateCreated}) - } - - Meta(meta) - flush() -} - -func Domain(domain *govultr.Domain) { - col := columns{"DOMAIN", "DATE CREATED"} - display(col) - display(columns{domain.Domain, domain.DateCreated}) - - flush() -} - -func SoaInfo(soa *govultr.Soa) { - col := columns{"NS PRIMARY", "EMAIL"} - display(col) - display(columns{soa.NSPrimary, soa.Email}) - flush() -} diff --git a/cmd/printer/dnsRecord.go b/cmd/printer/dnsRecord.go deleted file mode 100644 index e014abd4..00000000 --- a/cmd/printer/dnsRecord.go +++ /dev/null @@ -1,22 +0,0 @@ -package printer - -import "github.com/vultr/govultr/v2" - -func DnsRecordsList(records []govultr.DomainRecord, meta *govultr.Meta) { - col := columns{"ID", "TYPE", "NAME", "DATA", "PRIORITY", "TTL"} - display(col) - for _, r := range records { - display(columns{r.ID, r.Type, r.Name, r.Data, r.Priority, r.TTL}) - } - - Meta(meta) - flush() -} - -func DnsRecord(record *govultr.DomainRecord) { - col := columns{"ID", "TYPE", "NAME", "DATA", "PRIORITY", "TTL"} - display(col) - - display(columns{record.ID, record.Type, record.Name, record.Data, record.Priority, record.TTL}) - flush() -} diff --git a/cmd/printer/error.go b/cmd/printer/error.go index d4c6da4b..c147565f 100644 --- a/cmd/printer/error.go +++ b/cmd/printer/error.go @@ -2,6 +2,7 @@ package printer import ( "encoding/json" + "fmt" "os" ) @@ -11,20 +12,22 @@ type T struct { } func Error(err error) { - t := errorToStruct(err) + // TODO make errors uniform + // t := errorToStruct(err) col := columns{"ERROR MESSAGE", "STATUS CODE"} display(col) - display(columns{t.Error, t.Status}) + // display(columns{t.Error, t.Status}) + fmt.Printf("%v", err) flush() os.Exit(1) } -func errorToStruct(err error) *T { +func errorToStruct(err error) *T { //nolint:unused t := &T{} - if err := json.Unmarshal([]byte(err.Error()), t); err != nil { - panic(err) + if errMar := json.Unmarshal([]byte(err.Error()), t); errMar != nil { + panic(errMar) } return t } diff --git a/cmd/printer/firewallGroup.go b/cmd/printer/firewallGroup.go deleted file mode 100644 index b0f6f85a..00000000 --- a/cmd/printer/firewallGroup.go +++ /dev/null @@ -1,24 +0,0 @@ -package printer - -import ( - "github.com/vultr/govultr/v2" -) - -func FirewallGroups(fwg []govultr.FirewallGroup, meta *govultr.Meta) { - col := columns{"ID", "DATE CREATED", "DATE MODIFIED", "INSTANCE COUNT", "RULE COUNT", "MAX RULE COUNT", "DESCRIPTION"} - display(col) - for _, f := range fwg { - display(columns{f.ID, f.DateCreated, f.DateModified, f.InstanceCount, f.RuleCount, f.MaxRuleCount, f.Description}) - } - - Meta(meta) - flush() -} - -func FirewallGroup(fwg *govultr.FirewallGroup) { - col := columns{"ID", "DATE CREATED", "DATE MODIFIED", "INSTANCE COUNT", "RULE COUNT", "MAX RULE COUNT", "DESCRIPTION"} - display(col) - - display(columns{fwg.ID, fwg.DateCreated, fwg.DateModified, fwg.InstanceCount, fwg.RuleCount, fwg.MaxRuleCount, fwg.Description}) - flush() -} diff --git a/cmd/printer/firewallRule.go b/cmd/printer/firewallRule.go deleted file mode 100644 index e1244bbb..00000000 --- a/cmd/printer/firewallRule.go +++ /dev/null @@ -1,24 +0,0 @@ -package printer - -import ( - "github.com/vultr/govultr/v2" -) - -func FirewallRules(fwr []govultr.FirewallRule, meta *govultr.Meta) { - col := columns{"RULE NUMBER", "ACTION", "PROTOCOL", "PORT", "NETWORK", "NOTES"} - display(col) - for _, f := range fwr { - display(columns{f.ID, f.Action, f.Protocol, f.Port, f.Subnet, f.Notes}) - } - - Meta(meta) - flush() -} - -func FirewallRule(fwr *govultr.FirewallRule) { - col := columns{"RULE NUMBER", "ACTION", "PROTOCOL", "PORT", "NETWORK", "NOTES"} - display(col) - - display(columns{fwr.ID, fwr.Action, fwr.Protocol, fwr.Port, fwr.Subnet, fwr.Notes}) - flush() -} diff --git a/cmd/printer/generic.go b/cmd/printer/generic.go index 843ff786..079ab32b 100644 --- a/cmd/printer/generic.go +++ b/cmd/printer/generic.go @@ -3,15 +3,17 @@ package printer import ( "encoding/json" - "github.com/go-yaml/yaml" + "gopkg.in/yaml.v3" ) var _ ResourceOutput = &Generic{} +// Generic ... type Generic struct { - Message string + Message string `json:"message"` } +// JSON ... func (g *Generic) JSON() []byte { prettyJSON, err := json.MarshalIndent(g, "", " ") if err != nil { @@ -21,7 +23,8 @@ func (g *Generic) JSON() []byte { return prettyJSON } -func (g *Generic) Yaml() []byte { +// YAML ... +func (g *Generic) YAML() []byte { yam, err := yaml.Marshal(g) if err != nil { panic(err.Error()) @@ -29,14 +32,17 @@ func (g *Generic) Yaml() []byte { return yam } -func (g *Generic) Columns() map[int][]interface{} { - return map[int][]interface{}{0: {"message"}} +// Columns ... +func (g *Generic) Columns() [][]string { + return [][]string{0: {"MESSAGE"}} } -func (g *Generic) Data() map[int][]interface{} { - return map[int][]interface{}{0: {g.Message}} +// Data ... +func (g *Generic) Data() [][]string { + return [][]string{0: {g.Message}} } -func (g *Generic) Paging() map[int][]interface{} { +// Paging ... +func (g *Generic) Paging() [][]string { return nil } diff --git a/cmd/printer/info.go b/cmd/printer/info.go new file mode 100644 index 00000000..48cbbff9 --- /dev/null +++ b/cmd/printer/info.go @@ -0,0 +1,6 @@ +package printer + +// Info intializes and returns a generic resource output struct +func Info(msg string) *Generic { + return &Generic{Message: msg} +} diff --git a/cmd/printer/instance.go b/cmd/printer/instance.go deleted file mode 100644 index 75f9efc8..00000000 --- a/cmd/printer/instance.go +++ /dev/null @@ -1,128 +0,0 @@ -package printer - -import "github.com/vultr/govultr/v2" - -func InstanceBandwidth(bandwidth *govultr.Bandwidth) { - col := columns{"DATE", "INCOMING BYTES", "OUTGOING BYTES"} - display(col) - for k, b := range bandwidth.Bandwidth { - display(columns{k, b.IncomingBytes, b.OutgoingBytes}) - } - flush() -} - -func InstanceIPV4(ip []govultr.IPv4, meta *govultr.Meta) { - col := columns{"IP", "NETMASK", "GATEWAY", "TYPE", "REVERSE"} - display(col) - for _, i := range ip { - display(columns{i.IP, i.Netmask, i.Gateway, i.Type, i.Reverse}) - } - - Meta(meta) - flush() -} - -func InstanceIPV6(ip []govultr.IPv6, meta *govultr.Meta) { - col := columns{"IP", "NETWORK", "NETWORK SIZE", "TYPE"} - display(col) - for _, i := range ip { - display(columns{i.IP, i.Network, i.NetworkSize, i.Type}) - } - - Meta(meta) - flush() -} - -func InstanceList(instance []govultr.Instance, meta *govultr.Meta) { - col := columns{"ID", "IP", "LABEL", "OS", "STATUS", "Region", "CPU", "RAM", "DISK", "BANDWIDTH"} - display(col) - for _, s := range instance { - display(columns{s.ID, s.MainIP, s.Label, s.Os, s.Status, s.Region, s.VCPUCount, s.RAM, s.Disk, s.AllowedBandwidth}) - } - - Meta(meta) - flush() -} - -func Instance(instance *govultr.Instance) { - col := columns{"INSTANCE INFO"} - display(col) - display(columns{"ID", instance.ID}) - display(columns{"Os", instance.Os}) - display(columns{"RAM", instance.RAM}) - display(columns{"DISK", instance.Disk}) - display(columns{"MAIN IP", instance.MainIP}) - display(columns{"VCPU COUNT", instance.VCPUCount}) - display(columns{"REGION", instance.Region}) - display(columns{"DATE CREATED", instance.DateCreated}) - display(columns{"STATUS", instance.Status}) - display(columns{"ALLOWED BANDWIDTH", instance.AllowedBandwidth}) - display(columns{"NETMASK V4", instance.NetmaskV4}) - display(columns{"GATEWAY V4", instance.GatewayV4}) - display(columns{"POWER STATUS", instance.PowerStatus}) - display(columns{"SERVER STATE", instance.ServerStatus}) - display(columns{"PLAN", instance.Plan}) - display(columns{"LABEL", instance.Label}) - display(columns{"INTERNAL IP", instance.InternalIP}) - display(columns{"KVM URL", instance.KVM}) - display(columns{"TAG", instance.Tag}) - display(columns{"OsID", instance.OsID}) - display(columns{"AppID", instance.AppID}) - display(columns{"FIREWALL GROUP ID", instance.FirewallGroupID}) - display(columns{"V6 MAIN IP", instance.V6MainIP}) - display(columns{"V6 NETWORK", instance.V6Network}) - display(columns{"V6 NETWORK SIZE", instance.V6NetworkSize}) - display(columns{"FEATURES", instance.Features}) - - flush() -} - -func OsList(os []govultr.OS) { - col := columns{"ID", "NAME", "ARCH", "FAMILY"} - display(col) - for _, o := range os { - display(columns{o.ID, o.Name, o.Arch, o.Family}) - } - flush() -} - -func AppList(app []govultr.Application) { - col := columns{"ID", "NAME", "SHORT NAME", "DEPLOY NAME"} - display(col) - for _, a := range app { - display(columns{a.ID, a.Name, a.ShortName, a.DeployName}) - } - flush() -} - -func BackupsGet(b *govultr.BackupSchedule) { - col := columns{"ENABLED", "CRON TYPE", "NEXT RUN", "HOUR", "DOW", "DOM"} - display(col) - display(columns{*b.Enabled, b.Type, b.NextScheduleTimeUTC, b.Hour, b.Dow, b.Dom}) - flush() -} - -func IsoStatus(iso *govultr.Iso) { - col := columns{"ISO ID", "STATE"} - display(col) - display(columns{iso.IsoID, iso.State}) - flush() -} - -func PlansList(plans []string) { - col := columns{"PLAN NAME"} - display(col) - for _, p := range plans { - display(columns{p}) - } - flush() -} - -func ReverseIpv6(rip []govultr.ReverseIP) { - col := columns{"IP", "REVERSE"} - display(col) - for _, r := range rip { - display(columns{r.IP, r.Reverse}) - } - flush() -} diff --git a/cmd/printer/iso.go b/cmd/printer/iso.go deleted file mode 100644 index 276311f1..00000000 --- a/cmd/printer/iso.go +++ /dev/null @@ -1,32 +0,0 @@ -package printer - -import "github.com/vultr/govultr/v2" - -func IsoPrivates(iso []govultr.ISO, meta *govultr.Meta) { - col := columns{"ID", "FILE NAME", "SIZE", "STATUS", "MD5SUM", "SHA512SUM", "DATE CREATED"} - display(col) - for _, i := range iso { - display(columns{i.ID, i.FileName, i.Size, i.Status, i.MD5Sum, i.SHA512Sum, i.DateCreated}) - } - - Meta(meta) - flush() -} - -func IsoPrivate(iso *govultr.ISO) { - col := columns{"ID", "FILE NAME", "SIZE", "STATUS", "MD5SUM", "SHA512SUM", "DATE CREATED"} - display(col) - display(columns{iso.ID, iso.FileName, iso.Size, iso.Status, iso.MD5Sum, iso.SHA512Sum, iso.DateCreated}) - flush() -} - -func IsoPublic(iso []govultr.PublicISO, meta *govultr.Meta) { - col := columns{"ID", "NAME", "DESCRIPTION"} - display(col) - for _, i := range iso { - display(columns{i.ID, i.Name, i.Description}) - } - - Meta(meta) - flush() -} diff --git a/cmd/printer/loadBalancer.go b/cmd/printer/loadBalancer.go deleted file mode 100644 index d1a6173f..00000000 --- a/cmd/printer/loadBalancer.go +++ /dev/null @@ -1,88 +0,0 @@ -package printer - -import ( - "github.com/vultr/govultr/v2" -) - -func LoadBalancerList(loadbalancer []govultr.LoadBalancer, meta *govultr.Meta) { - for _, lb := range loadbalancer { - display(columns{"ID", lb.ID}) - display(columns{"DATE CREATED", lb.DateCreated}) - display(columns{"REGION", lb.Region}) - display(columns{"LABEL", lb.Label}) - display(columns{"STATUS", lb.Status}) - display(columns{"IPV4", lb.IPV4}) - display(columns{"IPV6", lb.IPV6}) - display(columns{"HAS SSL", *lb.SSLInfo}) - display(columns{"INSTANCES", lb.Instances}) - - display(columns{" "}) - display(columns{"HEALTH CHECKS"}) - display(columns{"PROTOCOL", "PORT", "PATH", "CHECK INTERVAL", "RESPONSE TIMEOUT", "UNHEALTHY THRESHOLD", "HEALTHY THRESHOLD"}) - display(columns{lb.HealthCheck.Protocol, lb.HealthCheck.Port, lb.HealthCheck.Path, lb.HealthCheck.CheckInterval, lb.HealthCheck.ResponseTimeout, lb.HealthCheck.UnhealthyThreshold, lb.HealthCheck.HealthyThreshold}) - - display(columns{" "}) - display(columns{"GENERIC INFO"}) - display(columns{"BALANCING ALGORITHM", "SSL REDIRECT", "COOKIE NAME", "PROXY PROTOCOL"}) - display(columns{lb.GenericInfo.BalancingAlgorithm, *lb.GenericInfo.SSLRedirect, lb.GenericInfo.StickySessions.CookieName, *lb.GenericInfo.ProxyProtocol}) - - display(columns{" "}) - display(columns{"FORWARDING RULES"}) - display(columns{"RULEID", "FRONTEND PROTOCOL", "FRONTEND PORT", "BACKEND PROTOCOL", "BACKEND PORT"}) - for _, r := range lb.ForwardingRules { - display(columns{r.RuleID, r.FrontendProtocol, r.FrontendPort, r.BackendProtocol, r.BackendPort}) - } - display(columns{"---------------------------"}) - } - - Meta(meta) - flush() -} - -func LoadBalancer(lb *govultr.LoadBalancer) { - display(columns{"ID", lb.ID}) - display(columns{"DATE CREATED", lb.DateCreated}) - display(columns{"REGION", lb.Region}) - display(columns{"LABEL", lb.Label}) - display(columns{"STATUS", lb.Status}) - display(columns{"IPV4", lb.IPV4}) - display(columns{"IPV6", lb.IPV6}) - display(columns{"HAS SSL", *lb.SSLInfo}) - display(columns{"INSTANCES", lb.Instances}) - - display(columns{" "}) - display(columns{"HEALTH CHECKS"}) - display(columns{"PROTOCOL", "PORT", "PATH", "CHECK INTERVAL", "RESPONSE TIMEOUT", "UNHEALTHY THRESHOLD", "HEALTHY THRESHOLD"}) - display(columns{lb.HealthCheck.Protocol, lb.HealthCheck.Port, lb.HealthCheck.Path, lb.HealthCheck.CheckInterval, lb.HealthCheck.ResponseTimeout, lb.HealthCheck.UnhealthyThreshold, lb.HealthCheck.HealthyThreshold}) - - display(columns{" "}) - display(columns{"GENERIC INFO"}) - display(columns{"BALANCING ALGORITHM", "SSL REDIRECT", "COOKIE NAME", "PROXY PROTOCOL"}) - display(columns{lb.GenericInfo.BalancingAlgorithm, *lb.GenericInfo.SSLRedirect, lb.GenericInfo.StickySessions.CookieName, *lb.GenericInfo.ProxyProtocol}) - - display(columns{" "}) - display(columns{"FORWARDING RULES"}) - display(columns{"RULEID", "FRONTEND PROTOCOL", "FRONTEND PORT", "BACKEND PROTOCOL", "BACKEND PORT"}) - for _, r := range lb.ForwardingRules { - display(columns{r.RuleID, r.FrontendProtocol, r.FrontendPort, r.BackendProtocol, r.BackendPort}) - } - flush() -} - -func LoadBalancerRuleList(rules []govultr.ForwardingRule, meta *govultr.Meta) { - display(columns{"RULEID", "FRONTEND PROTOCOL", "FRONTEND PORT", "BACKEND PROTOCOL", "BACKEND PORT"}) - - for _, r := range rules { - display(columns{r.RuleID, r.FrontendProtocol, r.FrontendPort, r.BackendProtocol, r.BackendPort}) - } - - Meta(meta) - flush() -} - -func LoadBalancerRule(rule *govultr.ForwardingRule) { - display(columns{"RULEID", "FRONTEND PROTOCOL", "FRONTEND PORT", "BACKEND PROTOCOL", "BACKEND PORT"}) - display(columns{rule.RuleID, rule.FrontendProtocol, rule.FrontendPort, rule.BackendProtocol, rule.BackendPort}) - - flush() -} diff --git a/cmd/printer/network.go b/cmd/printer/network.go deleted file mode 100644 index ab846a3b..00000000 --- a/cmd/printer/network.go +++ /dev/null @@ -1,21 +0,0 @@ -package printer - -import "github.com/vultr/govultr/v2" - -func NetworkList(network []govultr.Network, meta *govultr.Meta) { - col := columns{"ID", "REGION", "DESCRIPTION", "V4 SUBNET", "V4 SUBNET MASK", "DATE CREATED"} - display(col) - for _, n := range network { - display(columns{n.NetworkID, n.Region, n.Description, n.V4Subnet, n.V4SubnetMask, n.DateCreated}) - } - - Meta(meta) - flush() -} - -func Network(network *govultr.Network) { - display(columns{"ID", "REGION", "DESCRIPTION", "V4 SUBNET", "V4 SUBNET MASK", "DATE CREATED"}) - display(columns{network.NetworkID, network.Region, network.Description, network.V4Subnet, network.V4SubnetMask, network.DateCreated}) - - flush() -} diff --git a/cmd/printer/objectStorage.go b/cmd/printer/objectStorage.go deleted file mode 100644 index b7105b3c..00000000 --- a/cmd/printer/objectStorage.go +++ /dev/null @@ -1,39 +0,0 @@ -package printer - -import ( - "github.com/vultr/govultr/v2" -) - -func ObjectStorages(obj []govultr.ObjectStorage, meta *govultr.Meta) { - display(columns{"ID", "REGION", "OBJSTORECLUSTER ID", "STATUS", "LABEL", "DATE CREATED", "S3 HOSTNAME", "S3 ACCESS KEY", "S3 SECRET KEY"}) - for _, o := range obj { - vals := columns{o.ID, o.Region, o.ObjectStoreClusterID, o.Status, o.Label, o.DateCreated, o.S3Keys.S3Hostname, o.S3Keys.S3AccessKey, o.S3Keys.S3SecretKey} - display(vals) - } - - Meta(meta) - flush() -} - -func SingleObjectStorage(obj *govultr.ObjectStorage) { - display(columns{"ID", "REGION", "OBJSTORECLUSTER ID", "STATUS", "LABEL", "DATE CREATED", "S3 HOSTNAME", "S3 ACCESS KEY", "S3 SECRET KEY"}) - display(columns{obj.ID, obj.Region, obj.ObjectStoreClusterID, obj.Status, obj.Label, obj.DateCreated, obj.S3Keys.S3Hostname, obj.S3Keys.S3AccessKey, obj.S3Keys.S3SecretKey}) - - flush() -} - -func ObjectStorageClusterList(cluster []govultr.ObjectStorageCluster, meta *govultr.Meta) { - display(columns{"OBJSTORECLUSTER", "REGION ID", "HOSTNAME", "DEPLOY"}) - for _, c := range cluster { - display(columns{c.ID, c.Region, c.Hostname, c.Deploy}) - } - - Meta(meta) - flush() -} - -func ObjStorageS3KeyRegenerate(key *govultr.S3Keys) { - display(columns{"S3 HOSTNAME", "S3 ACCESS KEY", "S3 SECRET KEY"}) - display(columns{key.S3Hostname, key.S3AccessKey, key.S3SecretKey}) - flush() -} diff --git a/cmd/printer/os.go b/cmd/printer/os.go deleted file mode 100644 index 3d9eb359..00000000 --- a/cmd/printer/os.go +++ /dev/null @@ -1,50 +0,0 @@ -package printer - -import ( - "encoding/json" - "github.com/go-yaml/yaml" - "github.com/vultr/govultr/v2" -) - -var _ ResourceOutput = &OS{} - -type OS struct { - OS []govultr.OS - Meta *govultr.Meta -} - -func (o *OS) JSON() []byte { - prettyJSON, err := json.MarshalIndent(o, "", " ") - if err != nil { - panic("move this into byte") - } - - return prettyJSON -} - -func (o *OS) Yaml() []byte { - yam, err := yaml.Marshal(o) - if err != nil { - panic("move this into byte") - } - return yam -} - -func (o *OS) Columns() map[int][]interface{} { - return map[int][]interface{}{0: {"ID", "NAME", "ARCH", "FAMILY"}} -} -func (o *OS) Data() map[int][]interface{} { - data := map[int][]interface{}{} - for k, os := range o.OS { - data[k] = []interface{}{os.ID, os.Name, os.Arch, os.Family} - } - return data -} - -func (o *OS) Paging() map[int][]interface{} { - return map[int][]interface{}{ - 0: {"======================================"}, - 1: {"TOTAL", "NEXT PAGE", "PREV PAGE"}, - 2: {o.Meta.Total, o.Meta.Links.Next, o.Meta.Links.Prev}, - } -} diff --git a/cmd/printer/plans.go b/cmd/printer/plans.go deleted file mode 100644 index 7e6865a3..00000000 --- a/cmd/printer/plans.go +++ /dev/null @@ -1,96 +0,0 @@ -package printer - -import ( - "encoding/json" - - "github.com/go-yaml/yaml" - "github.com/vultr/govultr/v2" -) - -var _ ResourceOutput = &Plans{} - -type Plans struct { - Plan []govultr.Plan - Meta *govultr.Meta -} - -func (p *Plans) JSON() []byte { - prettyJSON, err := json.MarshalIndent(p, "", " ") - if err != nil { - panic("move this into byte") - } - - return prettyJSON -} - -func (p *Plans) Yaml() []byte { - yam, err := yaml.Marshal(p) - if err != nil { - panic("move this into byte") - } - return yam -} - -func (p *Plans) Columns() map[int][]interface{} { - return map[int][]interface{}{0: {"ID", "VCPU COUNT", "RAM", "DISK", "DISK COUNT", "BANDWIDTH GB", "PRICE PER MONTH", "TYPE", "REGIONS"}} -} - -func (p *Plans) Data() map[int][]interface{} { - data := map[int][]interface{}{} - for k, p := range p.Plan { - data[k] = []interface{}{p.ID, p.VCPUCount, p.RAM, p.Disk, p.DiskCount, p.Bandwidth, p.MonthlyCost, p.Type, p.Locations} - } - return data -} - -func (p *Plans) Paging() map[int][]interface{} { - return map[int][]interface{}{ - 0: {"======================================"}, - 1: {"TOTAL", "NEXT PAGE", "PREV PAGE"}, - 2: {p.Meta.Total, p.Meta.Links.Next, p.Meta.Links.Prev}, - } -} - -var _ ResourceOutput = &BaremetalPlans{} - -type BaremetalPlans struct { - Plan []govultr.BareMetalPlan - Meta *govultr.Meta -} - -func (b *BaremetalPlans) JSON() []byte { - prettyJSON, err := json.MarshalIndent(b, "", " ") - if err != nil { - panic("move this into byte") - } - - return prettyJSON -} - -func (b *BaremetalPlans) Yaml() []byte { - yam, err := yaml.Marshal(b) - if err != nil { - panic("move this into byte") - } - return yam -} - -func (b *BaremetalPlans) Columns() map[int][]interface{} { - return map[int][]interface{}{0: {"ID", "CPU COUNT", "CPU MODEL", "CPU THREADS", "RAM", "DISK", "DISK COUNT", "BANDWIDTH GB", "PRICE PER MONTH", "TYPE", "REGIONS"}} -} - -func (b *BaremetalPlans) Data() map[int][]interface{} { - data := map[int][]interface{}{} - for k, p := range b.Plan { - data[k] = []interface{}{p.ID, p.CPUCount, p.CPUModel, p.CPUThreads, p.RAM, p.Disk, p.DiskCount, p.Bandwidth, p.MonthlyCost, p.Type, p.Locations} - } - return data -} - -func (b *BaremetalPlans) Paging() map[int][]interface{} { - return map[int][]interface{}{ - 0: {"======================================"}, - 1: {"TOTAL", "NEXT PAGE", "PREV PAGE"}, - 2: {b.Meta.Total, b.Meta.Links.Next, b.Meta.Links.Prev}, - } -} diff --git a/cmd/printer/printer.go b/cmd/printer/printer.go index d0a9ecb7..d135d8f3 100644 --- a/cmd/printer/printer.go +++ b/cmd/printer/printer.go @@ -1,20 +1,34 @@ +// Package printer provides the console printing functionality for the CLI package printer import ( + "encoding/json" "fmt" "os" + "strconv" "strings" "text/tabwriter" - "github.com/vultr/govultr/v2" + "github.com/vultr/govultr/v3" + "gopkg.in/yaml.v3" +) + +const ( + twMinWidth int = 0 + twTabWidth int = 8 + twPadding int = 2 + twPadChar byte = '\t' + twFlags uint = 0 + emptyPlaceholder string = "---" + JSONIndent string = " " ) type ResourceOutput interface { JSON() []byte - Yaml() []byte - Columns() map[int][]interface{} - Data() map[int][]interface{} - Paging() map[int][]interface{} + YAML() []byte + Columns() [][]string + Data() [][]string + Paging() [][]string } type Printer interface { @@ -28,16 +42,26 @@ type Output struct { Output string } -type columns2 map[int][]interface{} type columns []interface{} var tw = new(tabwriter.Writer) func init() { - tw.Init(os.Stdout, 0, 8, 2, '\t', 0) + tw.Init( + os.Stdout, + twMinWidth, + twTabWidth, + twPadding, + twPadChar, + twFlags, + ) } +// Display confirms the output format then displays the ResourceOutput data to +// the CLI. If there is an error, that is displayed instead via Error func (o *Output) Display(r ResourceOutput, err error) { + defer o.flush() + if err != nil { //todo move this so it can follow the flow of the other printers and support json/yaml Error(err) @@ -45,10 +69,10 @@ func (o *Output) Display(r ResourceOutput, err error) { if strings.ToLower(o.Output) == "json" { o.displayNonText(r.JSON()) - os.Exit(1) + os.Exit(0) } else if strings.ToLower(o.Output) == "yaml" { - o.displayNonText(r.Yaml()) - os.Exit(1) + o.displayNonText(r.YAML()) + os.Exit(0) } o.display(r.Columns()) @@ -56,33 +80,140 @@ func (o *Output) Display(r ResourceOutput, err error) { if r.Paging() != nil { o.display(r.Paging()) } - defer o.flush() } -func (o *Output) display(d columns2) { - for _, values := range d { - for i, value := range values { +func (o *Output) display(d [][]string) { + for n := range d { + for i := range d[n] { format := "\t%s" if i == 0 { format = "%s" } - fmt.Fprintf(tw, format, fmt.Sprintf("%v", value)) + fmt.Fprintf(tw, format, fmt.Sprintf("%v", d[n][i])) } fmt.Fprintf(tw, "\n") } } func (o *Output) flush() { - tw.Flush() + if err := tw.Flush(); err != nil { + panic(fmt.Errorf("unable to flush display : %v", err)) + } } func (o *Output) displayNonText(data []byte) { fmt.Printf("%s\n", string(data)) } -//////////////////////////////////////////////////////////////// -func display(values columns) { +// Paging struct holds the values used by the Meta section in the printer +// output +type Paging struct { + Total int + CursorNext string + CursorPrev string +} + +// NewPaging validates and intializes the paging data +func NewPaging(total int, next, prev *string) *Paging { + p := new(Paging) + p.Total = total + + if next != nil { + p.CursorNext = *next + } else { + p.CursorNext = emptyPlaceholder + } + + if prev != nil { + p.CursorPrev = *prev + } else { + p.CursorPrev = emptyPlaceholder + } + + return p +} + +// Compose returns the paging data for output +func (p *Paging) Compose() [][]string { + var display [][]string + display = append(display, + []string{"======================================"}, + []string{"TOTAL", "NEXT PAGE", "PREV PAGE"}, + []string{strconv.Itoa(p.Total), p.CursorNext, p.CursorPrev}, + ) + + return display +} +// Total holds the values used by the Meta section in the printer +// output for outputs that don't include paging cursors +type Total struct { + Total int +} + +// Compose returns the total data for output +func (t *Total) Compose() [][]string { + var display [][]string + display = append(display, + []string{"======================================"}, + []string{"TOTAL"}, + []string{strconv.Itoa(t.Total)}, + ) + + return display +} + +// MarshalObject ... +func MarshalObject(input interface{}, format string) []byte { + var output []byte + if format == "json" { + j, errJ := json.MarshalIndent(input, "", JSONIndent) + if errJ != nil { + panic(fmt.Errorf("error marshaling JSON : %v", errJ)) + } + output = j + } else if format == "yaml" { + y, errY := yaml.Marshal(input) + if errY != nil { + panic(fmt.Errorf("error marshaling YAML : %v", errY)) + } + output = y + } + + return output +} + +// ArrayOfStringsToString will build a delimited string from an array for +// display in the printer functions. It defaults to comma-delimited and +// enclosed in square brackets to maintain consistency with array Fprintf +func ArrayOfStringsToString(a []string) string { + delimiter := ", " + var sb strings.Builder + sb.WriteString("[") + sb.WriteString(strings.Join(a, delimiter)) + sb.WriteString("]") + + return sb.String() +} + +// ArrayOfIntsToString will build a delimited string from an array for +// display in the printer functions. It defaults to comma-delimited and +// enclosed in square brackets to maintain consistency with array Fprintf +func ArrayOfIntsToString(a []int) string { + delimiter := ", " + var sb strings.Builder + sb.WriteString("[") + for i := range a { + sb.WriteString(strconv.Itoa(a[i])) + sb.WriteString(delimiter) + } + sb.WriteString("]") + + return sb.String() +} + +// OLD funcs to be re-written ////////////////////////////////////////////////////////////// +func display(values columns) { for i, value := range values { format := "\t%s" if i == 0 { @@ -93,14 +224,43 @@ func display(values columns) { fmt.Fprintf(tw, "\n") } +// displayString will `Fprintln` a string to the tabwriter +func displayString(message string) { + fmt.Fprintln(tw, message) +} + func flush() { - tw.Flush() + if err := tw.Flush(); err != nil { + panic("could not flush buffer") + } } +// Meta prints out the pagination details TODO: old func Meta(meta *govultr.Meta) { - display(columns{"======================================"}) - col := columns{"TOTAL", "NEXT PAGE", "PREV PAGE"} - display(col) + var pageNext string + var pagePrev string + + if meta.Links.Next == "" { + pageNext = "---" + } else { + pageNext = meta.Links.Next + } + + if meta.Links.Prev == "" { + pagePrev = "---" + } else { + pagePrev = meta.Links.Prev + } + + displayString("======================================") + display(columns{"TOTAL", "NEXT PAGE", "PREV PAGE"}) + display(columns{meta.Total, pageNext, pagePrev}) +} + +// MetaDBaaS prints out the pagination details used by database commands +func MetaDBaaS(meta *govultr.Meta) { + displayString("======================================") + display(columns{"TOTAL"}) - display(columns{meta.Total, meta.Links.Next, meta.Links.Prev}) + display(columns{meta.Total}) } diff --git a/cmd/printer/regions.go b/cmd/printer/regions.go deleted file mode 100644 index 31e0eff8..00000000 --- a/cmd/printer/regions.go +++ /dev/null @@ -1,88 +0,0 @@ -package printer - -import ( - "encoding/json" - - "github.com/go-yaml/yaml" - "github.com/vultr/govultr/v2" -) - -var _ ResourceOutput = &Regions{} - -type Regions struct { - Regions []govultr.Region `json:"regions"` - Meta *govultr.Meta -} - -func (r *Regions) JSON() []byte { - prettyJSON, err := json.MarshalIndent(r, "", " ") - if err != nil { - panic("move this into byte") - } - - return prettyJSON -} - -func (r *Regions) Yaml() []byte { - yam, err := yaml.Marshal(r) - if err != nil { - panic("move this into byte") - } - return yam -} - -func (r *Regions) Columns() map[int][]interface{} { - return map[int][]interface{}{0: {"ID", "CITY", "COUNTRY", "CONTINENT", "OPTIONS"}} -} -func (r *Regions) Data() map[int][]interface{} { - data := map[int][]interface{}{} - for k, r := range r.Regions { - data[k] = []interface{}{r.ID, r.City, r.Country, r.Continent, r.Options} - } - return data -} - -func (r *Regions) Paging() map[int][]interface{} { - return map[int][]interface{}{ - 0: {"======================================"}, - 1: {"TOTAL", "NEXT PAGE", "PREV PAGE"}, - 2: {r.Meta.Total, r.Meta.Links.Next, r.Meta.Links.Prev}, - } -} - -type RegionsAvailability struct { - AvailablePlans *govultr.PlanAvailability `json:"available_plans"` -} - -func (r *RegionsAvailability) JSON() []byte { - prettyJSON, err := json.MarshalIndent(r.AvailablePlans, "", " ") - if err != nil { - panic("move this into byte") - } - - return prettyJSON -} - -func (r *RegionsAvailability) Yaml() []byte { - yam, err := yaml.Marshal(r.AvailablePlans) - if err != nil { - panic("move this into byte") - } - return yam -} - -func (r *RegionsAvailability) Columns() map[int][]interface{} { - return map[int][]interface{}{0: {"AVAILABLE PLANS"}} -} - -func (r *RegionsAvailability) Data() map[int][]interface{} { - data := map[int][]interface{}{} - for k, r := range r.AvailablePlans.AvailablePlans { - data[k] = []interface{}{r} - } - return data -} - -func (r RegionsAvailability) Paging() map[int][]interface{} { - return nil -} diff --git a/cmd/printer/reservedIP.go b/cmd/printer/reservedIP.go deleted file mode 100644 index 89c45b4e..00000000 --- a/cmd/printer/reservedIP.go +++ /dev/null @@ -1,22 +0,0 @@ -package printer - -import "github.com/vultr/govultr/v2" - -func ReservedIPList(reservedIP []govultr.ReservedIP, meta *govultr.Meta) { - col := columns{"ID", "REGION", "IP TYPE", "SUBNET", "SUBNET SIZE", "LABEL", "ATTACHED TO"} - display(col) - for _, r := range reservedIP { - display(columns{r.ID, r.Region, r.IPType, r.Subnet, r.SubnetSize, r.Label, r.InstanceID}) - } - - Meta(meta) - flush() -} - -func ReservedIP(reservedIP *govultr.ReservedIP) { - col := columns{"ID", "REGION", "IP TYPE", "SUBNET", "SUBNET SIZE", "LABEL", "ATTACHED TO"} - display(col) - display(columns{reservedIP.ID, reservedIP.Region, reservedIP.IPType, reservedIP.Subnet, reservedIP.SubnetSize, reservedIP.Label, reservedIP.InstanceID}) - - flush() -} diff --git a/cmd/printer/script.go b/cmd/printer/script.go deleted file mode 100644 index 25204dc5..00000000 --- a/cmd/printer/script.go +++ /dev/null @@ -1,22 +0,0 @@ -package printer - -import ( - "github.com/vultr/govultr/v2" -) - -func ScriptList(script []govultr.StartupScript, meta *govultr.Meta) { - col := columns{"ID", "DATE CREATED", "DATE MODIFIED", "TYPE", "NAME"} - display(col) - for _, s := range script { - display(columns{s.ID, s.DateCreated, s.DateModified, s.Type, s.Name}) - } - - Meta(meta) - flush() -} - -func Script(script *govultr.StartupScript) { - display(columns{"ID", "DATE CREATED", "DATE MODIFIED", "TYPE", "NAME"}) - display(columns{script.ID, script.DateCreated, script.DateModified, script.Type, script.Name}) - flush() -} diff --git a/cmd/printer/snapshot.go b/cmd/printer/snapshot.go deleted file mode 100644 index 9526474b..00000000 --- a/cmd/printer/snapshot.go +++ /dev/null @@ -1,23 +0,0 @@ -package printer - -import ( - "github.com/vultr/govultr/v2" -) - -func Snapshot(snapshot *govultr.Snapshot) { - display(columns{"ID", "DATE CREATED", "SIZE", "STATUS", "OSID", "APPID", "DESCRIPTION"}) - display(columns{snapshot.ID, snapshot.DateCreated, snapshot.Size, snapshot.Status, snapshot.OsID, snapshot.AppID, snapshot.Description}) - - flush() -} - -func Snapshots(snapshot []govultr.Snapshot, meta *govultr.Meta) { - col := columns{"ID", "DATE CREATED", "SIZE", "STATUS", "OSID", "APPID", "DESCRIPTION"} - display(col) - for _, s := range snapshot { - display(columns{s.ID, s.DateCreated, s.Size, s.Status, s.OsID, s.AppID, s.Description}) - } - - Meta(meta) - flush() -} diff --git a/cmd/printer/user.go b/cmd/printer/user.go deleted file mode 100644 index 6b15966e..00000000 --- a/cmd/printer/user.go +++ /dev/null @@ -1,23 +0,0 @@ -package printer - -import ( - "github.com/vultr/govultr/v2" -) - -func Users(user []govultr.User, meta *govultr.Meta) { - col := columns{"ID", "NAME", "EMAIL", "API", "ACL"} - display(col) - for _, u := range user { - display(columns{u.ID, u.Name, u.Email, *u.APIEnabled, u.ACL}) - } - - Meta(meta) - flush() -} - -func User(user *govultr.User) { - display(columns{"ID", "NAME", "EMAIL", "API", "ACL"}) - display(columns{user.ID, user.Name, user.Email, *user.APIEnabled, user.ACL}) - - flush() -} diff --git a/cmd/printer/userData.go b/cmd/printer/userData.go deleted file mode 100644 index 365e2723..00000000 --- a/cmd/printer/userData.go +++ /dev/null @@ -1,20 +0,0 @@ -package printer - -import ( - "encoding/base64" - "fmt" - "os" - - "github.com/vultr/govultr/v2" -) - -func UserData(u *govultr.UserData) { - display(columns{"USERDATA"}) - data, err := base64.StdEncoding.DecodeString(u.Data) - if err != nil { - fmt.Printf("Error decoding user-data: %v\n", err) - os.Exit(1) - } - display(columns{string(data)}) - flush() -} diff --git a/cmd/printer/version.go b/cmd/printer/version.go deleted file mode 100644 index c55a9039..00000000 --- a/cmd/printer/version.go +++ /dev/null @@ -1,44 +0,0 @@ -package printer - -import ( - "encoding/json" - - "github.com/go-yaml/yaml" -) - -var _ ResourceOutput = &Version{} - -type Version struct { - Version string -} - -func (v *Version) JSON() []byte { - prettyJSON, err := json.MarshalIndent(v, "", " ") - if err != nil { - panic("move this into byte") - } - - return prettyJSON -} - -func (v *Version) Yaml() []byte { - yam, err := yaml.Marshal(v) - if err != nil { - panic("move this into byte") - } - return yam -} - -func (v *Version) Columns() map[int][]interface{} { - return map[int][]interface{}{0: {"version"}} - -} - -func (v *Version) Data() map[int][]interface{} { - return map[int][]interface{}{0: {v.Version}} - -} - -func (v Version) Paging() map[int][]interface{} { - return nil -} diff --git a/cmd/regions/printer.go b/cmd/regions/printer.go new file mode 100644 index 00000000..34d2724c --- /dev/null +++ b/cmd/regions/printer.go @@ -0,0 +1,111 @@ +package regions + +import ( + "github.com/vultr/govultr/v3" + "github.com/vultr/vultr-cli/v3/cmd/printer" +) + +// RegionsPrinter represents the regions data from the API and contains the +// methods to format and print the data via the ResourceOutput interface +type RegionsPrinter struct { + Regions []govultr.Region `json:"regions"` + Meta *govultr.Meta `json:"meta"` +} + +// JSON provides the JSON formatted byte data +func (r *RegionsPrinter) JSON() []byte { + return printer.MarshalObject(r, "json") +} + +// YAML provides the YAML formatted byte data +func (r *RegionsPrinter) YAML() []byte { + return printer.MarshalObject(r, "yaml") +} + +// Columns provides the columns for the printer +func (r *RegionsPrinter) Columns() [][]string { + return [][]string{0: { + "ID", + "CITY", + "COUNTRY", + "CONTINENT", + "OPTIONS", + }} +} + +// Data provides the data for the printer +func (r *RegionsPrinter) Data() [][]string { + data := [][]string{} + + if len(r.Regions) == 0 { + data = append(data, []string{"---", "---", "---", "---", "---"}) + return data + } + + for i := range r.Regions { + data = append(data, []string{ + r.Regions[i].ID, + r.Regions[i].City, + r.Regions[i].Country, + r.Regions[i].Continent, + printer.ArrayOfStringsToString(r.Regions[i].Options), + }) + } + + return data +} + +// Paging validates and forms the paging data for output +func (r *RegionsPrinter) Paging() [][]string { + return printer.NewPaging(r.Meta.Total, &r.Meta.Links.Next, &r.Meta.Links.Prev).Compose() +} + +// ====================================== + +// RegionsAvailabilityPrinter represents the plan availability data for a +// region from the API and contains the methods to format and print the data +// via the ResourceOutput interface +type RegionsAvailabilityPrinter struct { + Plans *govultr.PlanAvailability `json:"available_plans"` + Meta *govultr.Meta `json:"meta"` +} + +// JSON provides the JSON formatted byte data +func (r *RegionsAvailabilityPrinter) JSON() []byte { + return printer.MarshalObject(r, "json") +} + +// YAML provides the YAML formatted byte data +func (r *RegionsAvailabilityPrinter) YAML() []byte { + return printer.MarshalObject(r, "yaml") +} + +// Columns provides the available plans columns for the printer +func (r *RegionsAvailabilityPrinter) Columns() [][]string { + return [][]string{0: { + "AVAILABLE PLANS", + }} +} + +// Data provides the region availability plan data for the printer +func (r *RegionsAvailabilityPrinter) Data() [][]string { + data := [][]string{} + + if len(r.Plans.AvailablePlans) == 0 { + data = append(data, []string{"---"}) + return data + } + + for i := range r.Plans.AvailablePlans { + data = append(data, []string{ + r.Plans.AvailablePlans[i], + }) + } + + return data +} + +// Paging validates and forms the paging data for output +func (r *RegionsAvailabilityPrinter) Paging() [][]string { + return printer.NewPaging(r.Meta.Total, &r.Meta.Links.Next, &r.Meta.Links.Prev).Compose() +} diff --git a/cmd/regions/regions.go b/cmd/regions/regions.go index 62024286..e185c7a9 100644 --- a/cmd/regions/regions.go +++ b/cmd/regions/regions.go @@ -1,29 +1,15 @@ -// Copyright © 2019 The Vultr-cli Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - +// Package regions provides the functionality for the CLI to access regions package regions import ( "context" "errors" + "fmt" "github.com/spf13/cobra" - "github.com/spf13/viper" - "github.com/vultr/govultr/v2" - "github.com/vultr/vultr-cli/cmd/printer" - "github.com/vultr/vultr-cli/cmd/utils" - "github.com/vultr/vultr-cli/pkg/cli" + "github.com/vultr/govultr/v3" + "github.com/vultr/vultr-cli/v3/cmd/utils" + "github.com/vultr/vultr-cli/v3/pkg/cli" ) var ( @@ -58,90 +44,101 @@ var ( ` ) -// Interface for regions -type Interface interface { - Availability() (*govultr.PlanAvailability, error) - List() ([]govultr.Region, *govultr.Meta, error) - validate(cmd *cobra.Command, args []string) -} - -// Options for regions -type Options struct { - Base *cli.Base - PlanType string -} - -// NewRegionOptions returns Options struct -func NewRegionOptions(base *cli.Base) *Options { - return &Options{Base: base} -} - // NewCmdRegion creates a cobra command for Regions func NewCmdRegion(base *cli.Base) *cobra.Command { - o := NewRegionOptions(base) + o := &options{Base: base} cmd := &cobra.Command{ Use: "regions", - Short: "get regions", + Short: "Get regions", Aliases: []string{"r", "region"}, Long: regionLong, Example: regionExample, + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + utils.SetOptions(o.Base, cmd, args) + return nil + }, } list := &cobra.Command{ Use: "list", - Short: "list regions", + Short: "List regions", Aliases: []string{"l"}, Long: listLong, Example: listExample, - Run: func(cmd *cobra.Command, args []string) { - o.validate(cmd, args) + RunE: func(cmd *cobra.Command, args []string) error { o.Base.Options = utils.GetPaging(cmd) - regions, meta, err := o.List() - data := &printer.Regions{Regions: regions, Meta: meta} + + regions, meta, err := o.list() + if err != nil { + return fmt.Errorf("error retrieving region list : %v", err) + } + + data := &RegionsPrinter{Regions: regions, Meta: meta} o.Base.Printer.Display(data, err) + + return nil }, } - list.Flags().StringP("cursor", "c", "", "(optional) Cursor for paging.") - list.Flags().IntP("per-page", "p", 100, "(optional) Number of items requested per page. Default is 100 and Max is 500.") + list.Flags().StringP("cursor", "c", "", "(optional) cursor for paging.") + list.Flags().IntP( + "per-page", + "p", + utils.PerPageDefault, + fmt.Sprintf("(optional) Number of items requested per page. Default is %d and Max is 500.", utils.PerPageDefault), + ) availability := &cobra.Command{ - Use: "availability ", - Short: "list available plans in region", + Use: "availability ", + Short: "List available plans in region", Aliases: []string{"a"}, Long: availLong, Example: availExample, Args: func(cmd *cobra.Command, args []string) error { if len(args) < 1 { - return errors.New("please provide a regionID") + return errors.New("please provide a region ID") } return nil }, - Run: func(cmd *cobra.Command, args []string) { - o.validate(cmd, args) - avail, err := o.Availability() - data := &printer.RegionsAvailability{AvailablePlans: avail} + RunE: func(cmd *cobra.Command, args []string) error { + avail, err := o.availability() + if err != nil { + return fmt.Errorf("error retrieving region availability : %v", err) + } + + data := &RegionsAvailabilityPrinter{Plans: avail} o.Base.Printer.Display(data, err) + + return nil }, } - availability.Flags().StringP("type", "t", "", "type of plans for which to include availability. Possible values: 'vc2', 'vdc, 'vhf', 'vbm'. Defaults to all Instances plans.") + availability.Flags().StringP( + "type", + "t", + "", + `type of plans for which to include availability. Possible values: +'vc2', 'vdc, 'vhf', 'vbm'. Defaults to all Instances plans.`, + ) + + cmd.AddCommand( + list, + availability, + ) - cmd.AddCommand(list, availability) return cmd } -func (o *Options) validate(cmd *cobra.Command, args []string) { - o.Base.Args = args - o.PlanType, _ = cmd.Flags().GetString("type") - o.Base.Printer.Output = viper.GetString("output") +type options struct { + Base *cli.Base + PlanType string } -// List all regions -func (o *Options) List() ([]govultr.Region, *govultr.Meta, error) { - return o.Base.Client.Region.List(context.Background(), o.Base.Options) +func (o *options) list() ([]govultr.Region, *govultr.Meta, error) { + list, meta, _, err := o.Base.Client.Region.List(context.Background(), o.Base.Options) + return list, meta, err } -// Availability returns all available plans for a given region -func (o *Options) Availability() (*govultr.PlanAvailability, error) { - return o.Base.Client.Region.Availability(context.Background(), o.Base.Args[0], o.PlanType) +func (o *options) availability() (*govultr.PlanAvailability, error) { + avail, _, err := o.Base.Client.Region.Availability(context.Background(), o.Base.Args[0], o.PlanType) + return avail, err } diff --git a/cmd/regions/regions_test.go b/cmd/regions/regions_test.go deleted file mode 100644 index 22598a9e..00000000 --- a/cmd/regions/regions_test.go +++ /dev/null @@ -1,102 +0,0 @@ -package regions - -import ( - "context" - "reflect" - "testing" - - "github.com/vultr/vultr-cli/pkg/cli" - - "github.com/vultr/govultr/v2" -) - -type mockVultrRegions struct { - client *govultr.Client -} - -func (m mockVultrRegions) Availability(ctx context.Context, regionID string, planType string) (*govultr.PlanAvailability, error) { - return &govultr.PlanAvailability{AvailablePlans: []string{"1"}}, nil -} - -func (m mockVultrRegions) List(ctx context.Context, options *govultr.ListOptions) ([]govultr.Region, *govultr.Meta, error) { - return []govultr.Region{{ - ID: "ewr", - City: "NJ", - Country: "US", - Continent: "NA", - Options: []string{"test"}, - }}, &govultr.Meta{ - Total: 0, - Links: nil, - }, nil -} - -func TestOptions_List(t *testing.T) { - avail := NewRegionOptions(&cli.Base{Client: &govultr.Client{Region: mockVultrRegions{nil}}}) - - expected := []govultr.Region{{ - ID: "ewr", - City: "NJ", - Country: "US", - Continent: "NA", - Options: []string{"test"}, - }} - expectedMeta := &govultr.Meta{ - Total: 0, - Links: nil, - } - - regions, meta, _ := avail.List() - if !reflect.DeepEqual(expected, regions) { - t.Errorf("RegionOptions.list returned %v expected %v", regions, expected) - } - - if !reflect.DeepEqual(expectedMeta, meta) { - t.Errorf("RegionOptions.list returned %v expected %v", meta, expectedMeta) - } -} - -func TestOptions_Availability(t *testing.T) { - avail := NewRegionOptions(&cli.Base{Client: &govultr.Client{Region: mockVultrRegions{nil}}, Args: []string{"test"}}) - expected := &govultr.PlanAvailability{AvailablePlans: []string{"1"}} - - a, _ := avail.Availability() - if !reflect.DeepEqual(expected, a) { - t.Errorf("RegionAvailability.list returned %v expected %v", a, expected) - } -} - -func TestNewCmdRegion(t *testing.T) { - cmd := NewCmdRegion(&cli.Base{Client: &govultr.Client{Region: mockVultrRegions{nil}}}) - - if cmd.Short != "get regions" { - t.Errorf("invalid short") - } - - if cmd.Use != "regions" { - t.Errorf("invalid regions") - } - - alias := []string{"r", "region"} - if !reflect.DeepEqual(cmd.Aliases, alias) { - t.Errorf("expected alias %v got %v", alias, cmd.Aliases) - } -} - -func TestNewRegionOptions(t *testing.T) { - options := NewRegionOptions(&cli.Base{Client: &govultr.Client{Region: mockVultrRegions{nil}}}) - - ref := reflect.TypeOf(options) - if _, ok := ref.MethodByName("List"); !ok { - t.Errorf("Missing list function") - } - - if _, ok := ref.MethodByName("validate"); ok { - t.Errorf("validate isn't exported shouldn't be accessible") - } - - rInterface := reflect.TypeOf(new(Interface)).Elem() - if !ref.Implements(rInterface) { - t.Errorf("Options does not implement Interface") - } -} diff --git a/cmd/reservedIP.go b/cmd/reservedIP.go deleted file mode 100644 index b3c41dae..00000000 --- a/cmd/reservedIP.go +++ /dev/null @@ -1,210 +0,0 @@ -// Copyright © 2019 The Vultr-cli Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package cmd - -import ( - "context" - "errors" - "fmt" - "os" - - "github.com/spf13/cobra" - "github.com/vultr/govultr/v2" - "github.com/vultr/vultr-cli/cmd/printer" -) - -// ReservedIP represents the reservedip command -func ReservedIP() *cobra.Command { - reservedIPCmd := &cobra.Command{ - Use: "reserved-ip", - Aliases: []string{"rip"}, - Short: "reserved-ip lets you interact with reserved-ip ", - Long: ``, - } - - reservedIPCmd.AddCommand(reservedIPGet, reservedIPList, reservedIPDelete, reservedIPAttach, reservedIPDetach, reservedIPConvert, reservedIPCreate) - - // List - reservedIPList.Flags().StringP("cursor", "c", "", "(optional) Cursor for paging.") - reservedIPList.Flags().IntP("per-page", "p", 100, "(optional) Number of items requested per page. Default is 100 and Max is 500.") - - // Attach - reservedIPAttach.Flags().StringP("instance-id", "i", "", "id of instance you want to attach") - reservedIPAttach.MarkFlagRequired("instance-id") - - // Convert - reservedIPConvert.Flags().StringP("ip", "i", "", "ip you wish to convert") - reservedIPConvert.MarkFlagRequired("ip") - reservedIPConvert.Flags().StringP("label", "l", "", "label") - - // Create - reservedIPCreate.Flags().StringP("region", "r", "", "id of region") - reservedIPCreate.MarkFlagRequired("region") - reservedIPCreate.Flags().StringP("type", "t", "", "type of IP : v4 or v6") - reservedIPCreate.MarkFlagRequired("type") - reservedIPCreate.Flags().StringP("label", "l", "", "label") - - return reservedIPCmd -} - -var reservedIPGet = &cobra.Command{ - Use: "get ", - Short: "delete a reserved ip", - Aliases: []string{"destroy"}, - Long: ``, - Args: func(cmd *cobra.Command, args []string) error { - if len(args) < 1 { - return errors.New("please provide a reservedIP ID") - } - return nil - }, - Run: func(cmd *cobra.Command, args []string) { - ip := args[0] - if err := client.ReservedIP.Delete(context.Background(), ip); err != nil { - fmt.Printf("error getting reserved IPs : %v\n", err) - os.Exit(1) - } - - fmt.Println("Deleted reserved ip") - }, -} - -var reservedIPAttach = &cobra.Command{ - Use: "attach ", - Short: "attach a reservedIP to an instance", - Long: ``, - Args: func(cmd *cobra.Command, args []string) error { - if len(args) < 1 { - return errors.New("please provide a reservedIP ID") - } - return nil - }, - Run: func(cmd *cobra.Command, args []string) { - ip := args[0] - instance, _ := cmd.Flags().GetString("instance-id") - if err := client.ReservedIP.Attach(context.Background(), ip, instance); err != nil { - fmt.Printf("error attaching reserved IPs : %v\n", err) - os.Exit(1) - } - - fmt.Println("Attached reserved ip") - }, -} - -var reservedIPDetach = &cobra.Command{ - Use: "detach ", - Short: "detach a reservedIP to an instance", - Long: ``, - Args: func(cmd *cobra.Command, args []string) error { - if len(args) < 1 { - return errors.New("please provide a reservedIP ID") - } - return nil - }, - Run: func(cmd *cobra.Command, args []string) { - ip := args[0] - if err := client.ReservedIP.Detach(context.Background(), ip); err != nil { - fmt.Printf("error detaching reserved IPs : %v\n", err) - os.Exit(1) - } - - fmt.Println("Detached reserved ip") - }, -} - -var reservedIPConvert = &cobra.Command{ - Use: "convert ", - Short: "convert IP address to reservedIP", - Long: ``, - Run: func(cmd *cobra.Command, args []string) { - ip, _ := cmd.Flags().GetString("ip") - label, _ := cmd.Flags().GetString("label") - options := &govultr.ReservedIPConvertReq{ - IPAddress: ip, - Label: label, - } - - r, err := client.ReservedIP.Convert(context.Background(), options) - if err != nil { - fmt.Printf("error converting IP to reserved IPs : %v\n", err) - os.Exit(1) - } - - printer.ReservedIP(r) - }, -} - -var reservedIPCreate = &cobra.Command{ - Use: "create ", - Short: "create reservedIP", - Long: ``, - Run: func(cmd *cobra.Command, args []string) { - region, _ := cmd.Flags().GetString("region") - ipType, _ := cmd.Flags().GetString("type") - label, _ := cmd.Flags().GetString("label") - - options := &govultr.ReservedIPReq{ - Region: region, - IPType: ipType, - Label: label, - } - - r, err := client.ReservedIP.Create(context.Background(), options) - if err != nil { - fmt.Printf("error creating reserved IPs : %v\n", err) - os.Exit(1) - } - - printer.ReservedIP(r) - }, -} diff --git a/cmd/reservedip/printer.go b/cmd/reservedip/printer.go new file mode 100644 index 00000000..00e5056c --- /dev/null +++ b/cmd/reservedip/printer.go @@ -0,0 +1,114 @@ +package reservedip + +import ( + "strconv" + + "github.com/vultr/govultr/v3" + "github.com/vultr/vultr-cli/v3/cmd/printer" +) + +// ReservedIPsPrinter ... +type ReservedIPsPrinter struct { + IPs []govultr.ReservedIP `json:"reserved_ips"` + Meta *govultr.Meta `json:"meta"` +} + +// JSON ... +func (r *ReservedIPsPrinter) JSON() []byte { + return printer.MarshalObject(r, "json") +} + +// YAML ... +func (r *ReservedIPsPrinter) YAML() []byte { + return printer.MarshalObject(r, "yaml") +} + +// Columns ... +func (r *ReservedIPsPrinter) Columns() [][]string { + return [][]string{0: { + "ID", + "REGION", + "IP TYPE", + "SUBNET", + "SUBNET SIZE", + "LABEL", + "ATTACHED TO", + }} +} + +// Data ... +func (r *ReservedIPsPrinter) Data() [][]string { + if len(r.IPs) == 0 { + return [][]string{0: {"---", "---", "---", "---", "---", "---", "---"}} + } + + var data [][]string + for i := range r.IPs { + data = append(data, []string{ + r.IPs[i].ID, + r.IPs[i].Region, + r.IPs[i].IPType, + r.IPs[i].Subnet, + strconv.Itoa(r.IPs[i].SubnetSize), + r.IPs[i].Label, + r.IPs[i].InstanceID, + }) + } + + return data +} + +// Paging ... +func (r *ReservedIPsPrinter) Paging() [][]string { + return printer.NewPaging(r.Meta.Total, &r.Meta.Links.Next, &r.Meta.Links.Prev).Compose() +} + +// ====================================== + +// ReservedIPPrinter ... +type ReservedIPPrinter struct { + IP *govultr.ReservedIP `json:"reserved_ip"` +} + +// JSON ... +func (r *ReservedIPPrinter) JSON() []byte { + return printer.MarshalObject(r, "json") +} + +// YAML ... +func (r *ReservedIPPrinter) YAML() []byte { + return printer.MarshalObject(r, "yaml") +} + +// Columns ... +func (r *ReservedIPPrinter) Columns() [][]string { + return [][]string{0: { + "ID", + "REGION", + "IP TYPE", + "SUBNET", + "SUBNET SIZE", + "LABEL", + "ATTACHED TO", + }} +} + +// Data ... +func (r *ReservedIPPrinter) Data() [][]string { + return [][]string{0: { + r.IP.ID, + r.IP.Region, + r.IP.IPType, + r.IP.Subnet, + strconv.Itoa(r.IP.SubnetSize), + r.IP.Label, + r.IP.InstanceID, + }} +} + +// Paging ... +func (r *ReservedIPPrinter) Paging() [][]string { + return nil +} + +// ====================================== diff --git a/cmd/reservedip/reservedip.go b/cmd/reservedip/reservedip.go new file mode 100644 index 00000000..acecffa4 --- /dev/null +++ b/cmd/reservedip/reservedip.go @@ -0,0 +1,449 @@ +// Package reservedip provides the reserved-ip commands to the CLI +package reservedip + +import ( + "errors" + "fmt" + "os" + + "github.com/spf13/cobra" + "github.com/vultr/govultr/v3" + "github.com/vultr/vultr-cli/v3/cmd/printer" + "github.com/vultr/vultr-cli/v3/cmd/utils" + "github.com/vultr/vultr-cli/v3/pkg/cli" +) + +var ( + long = `Get all available commands for reserved IPs` + example = ` + # Full example + vultr-cli reserved-ip + + # Shortened with aliased commands + vultr-cli rip + ` + + createLong = `Create a reserved IP on your Vultr account` + createExample = ` + # Full Example + vultr-cli reserved-ip create --region="yto" --type="v4" --label="new IP" + + # Shortened with alias commands + vultr-cli rip c -r="yto" -t="v4" -l="new IP" + ` + + getLong = `Get info for a reserved IP on your Vultr account` + getExample = ` + # Full example + vultr-cli reserved-ip get 6a31648d-ebfa-4d43-9a00-9c9f0e5048f5 + + # Shortened with alias commands + vultr-cli rip g 6a31648d-ebfa-4d43-9a00-9c9f0e5048f5 + ` + + listLong = `List all reserved IPs on your Vultr account` + listExample = ` + # Full example + vultr-cli reserved-ip list + + # Shortened with alias commands + vultr-cli rip l + ` + + attachLong = `Attach a reserved IP to an instance on your Vultr account` + attachExample = ` + # Full example + vultr-cli reserved-ip attach 6a31648d-ebfa-4d43-9a00-9c9f0e5048f5 --instance-id="2b9bf5fb-1644-4e0a-b706-1116ab64d783" + + # Shortened with alias commands + vultr-cli rip a 6a31648d-ebfa-4d43-9a00-9c9f0e5048f5 -i="2b9bf5fb-1644-4e0a-b706-1116ab64d783" + ` + + detachLong = `Detach a reserved IP from an instance on your Vultr account` + detachExample = ` + # Full example + vultr-cli reserved-ip detach 6a31648d-ebfa-4d43-9a00-9c9f0e5048f5 + + # Shortened with alias commands + vultr-cli rip d 6a31648d-ebfa-4d43-9a00-9c9f0e5048f5 + ` + + convertLong = `Convert an instance IP to a reserved IP on your Vultr account` + convertExample = ` + # Full example + vultr-cli reserved-ip convert --ip="192.0.2.123" --label="new label converted" + + # Shortened with alias commands + vultr-cli rip v -i="192.0.2.123" -l="new label converted" + ` + + updateLong = `Update a reserved IP on your Vultr account` + updateExample = ` + # Full example + vultr-cli reserved-ip update 6a31648d-ebfa-4d43-9a00-9c9f0e5048f5 --label="new label" + + # Shortened with alias commands + vultr-cli rip u 6a31648d-ebfa-4d43-9a00-9c9f0e5048f5 -l="new label" + ` + + deleteLong = `Delete a reserved IP from your Vultr account` + deleteExample = ` + # Full example + vultr-cli reserved-ip delete 6a31648d-ebfa-4d43-9a00-9c9f0e5048f5 + ` +) + +// NewCmdReservedIP provides the CLI command for reserved IP functions +func NewCmdReservedIP(base *cli.Base) *cobra.Command { //nolint:gocyclo + o := &options{Base: base} + + cmd := &cobra.Command{ + Use: "reserved-ip", + Short: "Commands to interact with reserved IPs", + Aliases: []string{"rip"}, + Long: long, + Example: example, + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + utils.SetOptions(o.Base, cmd, args) + if !o.Base.HasAuth { + return errors.New(utils.APIKeyError) + } + return nil + }, + } + + // List + list := &cobra.Command{ + Use: "list", + Short: "List all reserved IPs", + Aliases: []string{"l"}, + Long: listLong, + Example: listExample, + RunE: func(cmd *cobra.Command, args []string) error { + o.Base.Options = utils.GetPaging(cmd) + + ips, meta, err := o.list() + if err != nil { + return fmt.Errorf("error retrieving reserved IP list : %v", err) + } + + data := &ReservedIPsPrinter{IPs: ips, Meta: meta} + o.Base.Printer.Display(data, nil) + + return nil + }, + } + + list.Flags().StringP("cursor", "c", "", "(optional) Cursor for paging.") + list.Flags().IntP( + "per-page", + "p", + utils.PerPageDefault, + fmt.Sprintf("(optional) Number of items requested per page. Default is %d and Max is 500.", utils.PerPageDefault), + ) + + // Get + get := &cobra.Command{ + Use: "get ", + Short: "Get a reserved IP", + Long: getLong, + Example: getExample, + Aliases: []string{"g"}, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("please provide a reserved IP ID") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + ip, err := o.get() + if err != nil { + return fmt.Errorf("error getting reserved IP : %v", err) + } + + data := &ReservedIPPrinter{IP: ip} + o.Base.Printer.Display(data, nil) + + return nil + }, + } + + // Create + create := &cobra.Command{ + Use: "create ", + Short: "Create reserved IP", + Aliases: []string{"c"}, + Long: createLong, + Example: createExample, + RunE: func(cmd *cobra.Command, args []string) error { + region, errRe := cmd.Flags().GetString("region") + if errRe != nil { + return fmt.Errorf("error parsing flag 'region' for reserved-ip create : %v", errRe) + } + + ipType, errIP := cmd.Flags().GetString("type") + if errIP != nil { + return fmt.Errorf("error parsing flag 'type' for reserved-ip create : %v", errIP) + } + + label, errLa := cmd.Flags().GetString("label") + if errLa != nil { + return fmt.Errorf("error parsing flag 'label' for reserved-ip create : %v", errLa) + } + + o.CreateReq = &govultr.ReservedIPReq{ + Region: region, + IPType: ipType, + Label: label, + } + + ip, err := o.create() + if err != nil { + return fmt.Errorf("error creating reserved IP : %v", err) + } + + data := &ReservedIPPrinter{IP: ip} + o.Base.Printer.Display(data, nil) + + return nil + }, + } + + create.Flags().StringP("region", "r", "", "id of region") + if err := create.MarkFlagRequired("region"); err != nil { + fmt.Printf("error marking reserved-ip create 'region' flag required: %v", err) + os.Exit(1) + } + create.Flags().StringP("type", "t", "", "type of IP : v4 or v6") + if err := create.MarkFlagRequired("type"); err != nil { + fmt.Printf("error marking reserved-ip create 'type' flag required: %v", err) + os.Exit(1) + } + create.Flags().StringP("label", "l", "", "label") + + // Update + update := &cobra.Command{ + Use: "update ", + Short: "Update reserved IP", + Aliases: []string{"u"}, + Long: updateLong, + Example: updateExample, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("please provide a reserved IP ID") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + label, errLa := cmd.Flags().GetString("label") + if errLa != nil { + return fmt.Errorf("error parsing flag 'label' for reserved-ip update : %v", errLa) + } + + o.UpdateReq = &govultr.ReservedIPUpdateReq{ + Label: govultr.StringToStringPtr(label), + } + + ip, err := o.update() + if err != nil { + return fmt.Errorf("error updating reserved IP : %v", err) + } + + data := &ReservedIPPrinter{IP: ip} + o.Base.Printer.Display(data, nil) + + return nil + }, + } + + update.Flags().StringP("label", "l", "", "label") + if err := update.MarkFlagRequired("label"); err != nil { + fmt.Printf("error marking reserved-ip update 'label' flag required: %v", err) + os.Exit(1) + } + + // Attach + attach := &cobra.Command{ + Use: "attach ", + Short: "Attach a reserved IP to an instance", + Aliases: []string{"a"}, + Long: attachLong, + Example: attachExample, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("please provide a reserved IP ID") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + instanceID, errIn := cmd.Flags().GetString("instance-id") + if errIn != nil { + return fmt.Errorf("error parsing flag 'instance-id' for reserved-ip attach : %v", errIn) + } + + o.InstanceID = instanceID + + if err := o.attach(); err != nil { + return fmt.Errorf("error attaching reserved IP : %v", err) + } + + o.Base.Printer.Display(printer.Info("reserved IP has been attached to instance"), nil) + + return nil + }, + } + + attach.Flags().StringP("instance-id", "i", "", "id of instance you want to attach") + if err := attach.MarkFlagRequired("instance-id"); err != nil { + fmt.Printf("error marking reserved-ip attach 'instance-id' flag required: %v", err) + os.Exit(1) + } + + // Detach + detach := &cobra.Command{ + Use: "detach ", + Short: "Detach a reserved IP from an instance", + Aliases: []string{"d"}, + Long: detachLong, + Example: detachExample, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("please provide a reserved IP ID") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + if err := o.detach(); err != nil { + return fmt.Errorf("error detaching reserved IP : %v", err) + } + + o.Base.Printer.Display(printer.Info("reserved IP has been detached"), nil) + + return nil + }, + } + + // Convert + convert := &cobra.Command{ + Use: "convert ", + Short: "Convert IP address to reserved IP", + Aliases: []string{"v"}, + Long: convertLong, + Example: convertExample, + RunE: func(cmd *cobra.Command, args []string) error { + ip, errIP := cmd.Flags().GetString("ip") + if errIP != nil { + return fmt.Errorf("error parsing flag 'ip' for reserved-ip convert : %v", errIP) + } + + label, errLa := cmd.Flags().GetString("label") + if errLa != nil { + return fmt.Errorf("error parsing flag 'label' for reserved-ip convert : %v", errLa) + } + + o.ConvertReq = &govultr.ReservedIPConvertReq{ + IPAddress: ip, + Label: label, + } + + newIP, err := o.convert() + if err != nil { + return fmt.Errorf("error converting reserved IP : %v", err) + } + + data := &ReservedIPPrinter{IP: newIP} + o.Base.Printer.Display(data, nil) + + return nil + }, + } + + convert.Flags().StringP("ip", "i", "", "ip you wish to convert") + if err := convert.MarkFlagRequired("ip"); err != nil { + fmt.Printf("error marking reserved-ip convert 'ip' flag required: %v", err) + os.Exit(1) + } + convert.Flags().StringP("label", "l", "", "label") + + // Delete + del := &cobra.Command{ + Use: "delete ", + Short: "delete a reserved ip", + Long: deleteLong, + Example: deleteExample, + Aliases: []string{"destroy"}, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("please provide a reserved IP ID") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + if err := o.detach(); err != nil { + return fmt.Errorf("error detaching reserved IP : %v", err) + } + + o.Base.Printer.Display(printer.Info("reserved IP has been detached"), nil) + + return nil + }, + } + + cmd.AddCommand( + list, + get, + create, + update, + attach, + detach, + convert, + del, + ) + + return cmd +} + +type options struct { + Base *cli.Base + CreateReq *govultr.ReservedIPReq + UpdateReq *govultr.ReservedIPUpdateReq + ConvertReq *govultr.ReservedIPConvertReq + InstanceID string +} + +func (o *options) list() ([]govultr.ReservedIP, *govultr.Meta, error) { + rips, meta, _, err := o.Base.Client.ReservedIP.List(o.Base.Context, o.Base.Options) + return rips, meta, err +} + +func (o *options) get() (*govultr.ReservedIP, error) { + rip, _, err := o.Base.Client.ReservedIP.Get(o.Base.Context, o.Base.Args[0]) + return rip, err +} + +func (o *options) create() (*govultr.ReservedIP, error) { + rip, _, err := o.Base.Client.ReservedIP.Create(o.Base.Context, o.CreateReq) + return rip, err +} + +func (o *options) update() (*govultr.ReservedIP, error) { + rip, _, err := o.Base.Client.ReservedIP.Update(o.Base.Context, o.Base.Args[0], o.UpdateReq) + return rip, err +} + +func (o *options) attach() error { + return o.Base.Client.ReservedIP.Attach(o.Base.Context, o.Base.Args[0], o.InstanceID) +} + +func (o *options) detach() error { + return o.Base.Client.ReservedIP.Detach(o.Base.Context, o.Base.Args[0]) +} + +func (o *options) convert() (*govultr.ReservedIP, error) { + ip, _, err := o.Base.Client.ReservedIP.Convert(o.Base.Context, o.ConvertReq) + return ip, err +} + +func (o *options) del() error { //nolint:unused + return o.Base.Client.ReservedIP.Delete(o.Base.Context, o.Base.Args[0]) +} diff --git a/cmd/root.go b/cmd/root.go index 51af2d55..1f164892 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -1,100 +1,122 @@ -// Copyright © 2019 The Vultr-cli Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - +// Package cmd implements the command line commands relevant to the vultr-cli package cmd import ( "fmt" "os" - - "github.com/vultr/vultr-cli/cmd/account" - "github.com/vultr/vultr-cli/cmd/operatingSystems" - "github.com/vultr/vultr-cli/cmd/sshkeys" + "path/filepath" "github.com/spf13/cobra" "github.com/spf13/viper" - "github.com/vultr/govultr/v2" - "github.com/vultr/vultr-cli/cmd/applications" - "github.com/vultr/vultr-cli/cmd/plans" - "github.com/vultr/vultr-cli/cmd/regions" - "github.com/vultr/vultr-cli/cmd/users" - "github.com/vultr/vultr-cli/cmd/version" - "github.com/vultr/vultr-cli/pkg/cli" + "github.com/vultr/vultr-cli/v3/cmd/account" + "github.com/vultr/vultr-cli/v3/cmd/applications" + "github.com/vultr/vultr-cli/v3/cmd/backups" + "github.com/vultr/vultr-cli/v3/cmd/baremetal" + "github.com/vultr/vultr-cli/v3/cmd/billing" + "github.com/vultr/vultr-cli/v3/cmd/blockstorage" + "github.com/vultr/vultr-cli/v3/cmd/containerregistry" + "github.com/vultr/vultr-cli/v3/cmd/database" + "github.com/vultr/vultr-cli/v3/cmd/dns" + "github.com/vultr/vultr-cli/v3/cmd/firewall" + "github.com/vultr/vultr-cli/v3/cmd/instance" + "github.com/vultr/vultr-cli/v3/cmd/iso" + "github.com/vultr/vultr-cli/v3/cmd/kubernetes" + "github.com/vultr/vultr-cli/v3/cmd/loadbalancer" + "github.com/vultr/vultr-cli/v3/cmd/marketplace" + "github.com/vultr/vultr-cli/v3/cmd/objectstorage" + "github.com/vultr/vultr-cli/v3/cmd/operatingsystems" + "github.com/vultr/vultr-cli/v3/cmd/plans" + "github.com/vultr/vultr-cli/v3/cmd/regions" + "github.com/vultr/vultr-cli/v3/cmd/reservedip" + "github.com/vultr/vultr-cli/v3/cmd/script" + "github.com/vultr/vultr-cli/v3/cmd/snapshot" + "github.com/vultr/vultr-cli/v3/cmd/sshkeys" + "github.com/vultr/vultr-cli/v3/cmd/users" + "github.com/vultr/vultr-cli/v3/cmd/version" + "github.com/vultr/vultr-cli/v3/cmd/vpc" + "github.com/vultr/vultr-cli/v3/cmd/vpc2" + "github.com/vultr/vultr-cli/v3/pkg/cli" +) + +const ( + userAgent = "vultr-cli/" + version.Version + perPageDefault int = 100 ) var ( cfgFile string output string - base *cli.Base - client *govultr.Client ) // rootCmd represents the base command when called without any subcommands var rootCmd = &cobra.Command{ - Use: "vultr-cli", - Short: "vultr-cli is a command line interface for the Vultr API", - Long: ``, - Aliases: []string{"vultrctl"}, + Use: "vultr-cli", + Short: "vultr-cli is a command line interface for the Vultr API", + Long: ``, + SilenceUsage: true, } // Execute adds all child commands to the root command and sets flags appropriately. // This is called by main.main(). It only needs to happen once to the rootCmd. func Execute() { if err := rootCmd.Execute(); err != nil { - fmt.Println(err) os.Exit(1) } } func init() { + // init the config file with viper initConfig() rootCmd.PersistentFlags().StringVar(&cfgFile, "config", configHome(), "config file (default is $HOME/.vultr-cli.yaml)") - viper.BindPFlag("config", rootCmd.PersistentFlags().Lookup("config")) - - rootCmd.PersistentFlags().StringVar(&output, "output", "text", "out of data json | yaml | text. text is default") - viper.BindPFlag("output", rootCmd.PersistentFlags().Lookup("output")) - - rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") - rootCmd.AddCommand(account.NewCmdAccount(base)) - rootCmd.AddCommand(applications.NewCmdApplications(base)) - rootCmd.AddCommand(Backups()) - rootCmd.AddCommand(BareMetal()) - rootCmd.AddCommand(BlockStorageCmd()) - rootCmd.AddCommand(DNS()) - rootCmd.AddCommand(Firewall()) - rootCmd.AddCommand(ISO()) - rootCmd.AddCommand(LoadBalancer()) - rootCmd.AddCommand(Network()) - rootCmd.AddCommand(operatingSystems.NewCmdOS(base)) - rootCmd.AddCommand(ObjectStorageCmd()) - rootCmd.AddCommand(plans.NewCmdPlan(base)) - rootCmd.AddCommand(regions.NewCmdRegion(base)) - rootCmd.AddCommand(ReservedIP()) - rootCmd.AddCommand(Script()) - rootCmd.AddCommand(Instance()) - rootCmd.AddCommand(Snapshot()) - rootCmd.AddCommand(sshkeys.NewCmdSSHKey(base)) - rootCmd.AddCommand(users.NewCmdUser(base)) - rootCmd.AddCommand(version.NewCmdVersion()) - cobra.OnInitialize(initConfig) + if err := viper.BindPFlag("config", rootCmd.PersistentFlags().Lookup("config")); err != nil { + fmt.Printf("error binding root pflag 'config': %v\n", err) + } + rootCmd.PersistentFlags().StringVarP(&output, "output", "o", "text", "output format [ text | json | yaml ]") + if err := viper.BindPFlag("output", rootCmd.PersistentFlags().Lookup("output")); err != nil { + fmt.Printf("error binding root pflag 'output': %v\n", err) + } + + base := cli.NewCLIBase( + os.Getenv("VULTR_API_KEY"), + userAgent, + output, + ) + + rootCmd.AddCommand( + account.NewCmdAccount(base), + applications.NewCmdApplications(base), + backups.NewCmdBackups(base), + baremetal.NewCmdBareMetal(base), + billing.NewCmdBilling(base), + blockstorage.NewCmdBlockStorage(base), + containerregistry.NewCmdContainerRegistry(base), + database.NewCmdDatabase(base), + dns.NewCmdDNS(base), + firewall.NewCmdFirewall(base), + iso.NewCmdISO(base), + kubernetes.NewCmdKubernetes(base), + loadbalancer.NewCmdLoadBalancer(base), + marketplace.NewCmdMarketplace(base), + operatingsystems.NewCmdOS(base), + objectstorage.NewCmdObjectStorage(base), + plans.NewCmdPlan(base), + regions.NewCmdRegion(base), + reservedip.NewCmdReservedIP(base), + script.NewCmdScript(base), + instance.NewCmdInstance(base), + snapshot.NewCmdSnapshot(base), + sshkeys.NewCmdSSHKey(base), + users.NewCmdUser(base), + version.NewCmdVersion(base), + vpc.NewCmdVPC(base), + vpc2.NewCmdVPC2(base), + ) } // initConfig reads in config file and ENV variables if set. func initConfig() { - var token string configPath := viper.GetString("config") if configPath == "" { @@ -112,53 +134,41 @@ func initConfig() { if err := viper.ReadInConfig(); err != nil { fmt.Println("Error Reading in file:", viper.ConfigFileUsed()) } - - token = viper.GetString("api-key") - if token == "" { - token = os.Getenv("VULTR_API_KEY") - } - - if token == "" { - fmt.Println("Please export your VULTR API key as an environment variable or add `api-key` to your config file, eg:") - fmt.Println("export VULTR_API_KEY=''") - os.Exit(1) - } - - base = cli.NewCLIBase(token, viper.GetString("output")) } -func getPaging(cmd *cobra.Command) *govultr.ListOptions { - options := &govultr.ListOptions{} - - cursor, _ := cmd.Flags().GetString("cursor") - perPage, _ := cmd.Flags().GetInt("per-page") - - if cursor != "" { - options.Cursor = cursor +func configHome() string { + // check for a config file at ~/.config/vultr-cli.yaml + configFolder, errConfig := os.UserConfigDir() + if errConfig != nil { + os.Exit(1) } - if perPage != 0 { - options.PerPage = perPage + configFile := fmt.Sprintf("%s/vultr-cli.yaml", configFolder) + if _, err := os.Stat(configFile); err == nil { + // if one exists, return the path + return configFile } - return options -} - -func configHome() string { - configHome, err := os.UserHomeDir() - if err != nil { + // check for a config file at ~/.vultr-cli.yaml + configFolder, errHome := os.UserHomeDir() + if errHome != nil { os.Exit(1) } - configHome = fmt.Sprintf("%s/.vultr-cli.yaml", configHome) - if _, err := os.Stat(configHome); err != nil { - f, err := os.Create(configHome) + configFile = fmt.Sprintf("%s/.vultr-cli.yaml", configFolder) + if _, err := os.Stat(configFile); err != nil { + // if it doesn't exist, create one + f, err := os.Create(filepath.Clean(configFile)) if err != nil { os.Exit(1) } - defer f.Close() + defer func() { + if errCls := f.Close(); errCls != nil { + fmt.Printf("failed to close config file.. error: %v", errCls) + } + }() } - return configHome + return configFile } diff --git a/cmd/script.go b/cmd/script.go deleted file mode 100644 index 6521a1ce..00000000 --- a/cmd/script.go +++ /dev/null @@ -1,183 +0,0 @@ -// Copyright © 2019 The Vultr-cli Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package cmd - -import ( - "context" - "errors" - "fmt" - "os" - - "github.com/spf13/cobra" - "github.com/vultr/govultr/v2" - "github.com/vultr/vultr-cli/cmd/printer" -) - -// Script represents the script command -func Script() *cobra.Command { - cmd := &cobra.Command{ - Use: "script", - Aliases: []string{"ss"}, - Short: "startup script commands", - Long: `script is used to access startup script commands`, - } - - cmd.AddCommand(scriptCreate, scriptGet, scriptDelete, scriptList, scriptUpdate) - - scriptCreate.Flags().StringP("name", "n", "", "Name of the newly created startup script.") - scriptCreate.Flags().StringP("script", "s", "", "Startup script contents.") - scriptCreate.Flags().StringP("type", "t", "", "(Optional) Type of startup script. Possible values: 'boot', 'pxe'. Default is 'boot'.") - - scriptCreate.MarkFlagRequired("name") - scriptCreate.MarkFlagRequired("script") - - scriptUpdate.Flags().StringP("name", "n", "", "Name of the startup script.") - scriptUpdate.Flags().StringP("script", "s", "", "Startup script contents.") - scriptUpdate.Flags().StringP("type", "t", "", "Type of startup script. Possible values: 'boot', 'pxe'. Default is 'boot'.") - - scriptList.Flags().StringP("cursor", "c", "", "(optional) Cursor for paging.") - scriptList.Flags().IntP("per-page", "p", 100, "(optional) Number of items requested per page. Default is 100 and Max is 500.") - - return cmd -} - -// Create startup script command -var scriptCreate = &cobra.Command{ - Use: "create", - Short: "Create a startup script", - Long: ``, - Run: func(cmd *cobra.Command, args []string) { - name, _ := cmd.Flags().GetString("name") - script, _ := cmd.Flags().GetString("script") - scriptType, _ := cmd.Flags().GetString("type") - - options := &govultr.StartupScriptReq{ - Name: name, - Script: script, - Type: scriptType, - } - - startup, err := client.StartupScript.Create(context.Background(), options) - if err != nil { - fmt.Printf("%v\n", err) - os.Exit(1) - } - - printer.Script(startup) - }, -} - -// Delete startup script command -var scriptDelete = &cobra.Command{ - Use: "delete ", - Short: "Delete a startup script", - Aliases: []string{"destroy"}, - Long: ``, - Args: func(cmd *cobra.Command, args []string) error { - if len(args) < 1 { - return errors.New("please provide a scriptID") - } - return nil - }, - Run: func(cmd *cobra.Command, args []string) { - id := args[0] - if err := client.StartupScript.Delete(context.Background(), id); err != nil { - fmt.Printf("%v\n", err) - os.Exit(1) - } - - fmt.Println("Startup script has been deleted") - }, -} - -// List all startup scripts command -var scriptList = &cobra.Command{ - Use: "list", - Short: "List all startup scripts", - Long: ``, - Run: func(cmd *cobra.Command, args []string) { - options := getPaging(cmd) - list, meta, err := client.StartupScript.List(context.Background(), options) - if err != nil { - fmt.Printf("%v\n", err) - os.Exit(1) - } - - printer.ScriptList(list, meta) - }, -} - -// Displays the contents of a specified script -var scriptGet = &cobra.Command{ - Use: "get ", - Short: "Displays the contents of specified script", - Long: ``, - Args: func(cmd *cobra.Command, args []string) error { - if len(args) < 1 { - return errors.New("please provide a scriptID") - } - return nil - }, - Run: func(cmd *cobra.Command, args []string) { - id := args[0] - script, err := client.StartupScript.Get(context.Background(), id) - if err != nil { - fmt.Printf("%v\n", err) - os.Exit(1) - } - - printer.Script(script) - }, -} - -// Update startup script command -var scriptUpdate = &cobra.Command{ - Use: "update ", - Short: "Update startup script", - Args: func(cmd *cobra.Command, args []string) error { - if len(args) < 1 { - return errors.New("please provide a scriptID") - } - return nil - }, - Long: ``, - Run: func(cmd *cobra.Command, args []string) { - id := args[0] - name, _ := cmd.Flags().GetString("name") - script, _ := cmd.Flags().GetString("script") - scriptType, _ := cmd.Flags().GetString("type") - - s := &govultr.StartupScriptReq{} - - if name != "" { - s.Name = name - } - - if script != "" { - s.Script = script - } - - if scriptType != "" { - s.Type = scriptType - } - - if err := client.StartupScript.Update(context.Background(), id, s); err != nil { - fmt.Printf("%v\n", err) - os.Exit(1) - } - - fmt.Println("Startup script has been updated") - }, -} diff --git a/cmd/script/printer.go b/cmd/script/printer.go new file mode 100644 index 00000000..db2d57e9 --- /dev/null +++ b/cmd/script/printer.go @@ -0,0 +1,97 @@ +package script + +import ( + "github.com/vultr/govultr/v3" + "github.com/vultr/vultr-cli/v3/cmd/printer" +) + +// ScriptsPrinter ... +type ScriptsPrinter struct { + Scripts []govultr.StartupScript `json:"startup_scripts"` + Meta *govultr.Meta `json:"meta"` +} + +// JSON ... +func (s *ScriptsPrinter) JSON() []byte { + return printer.MarshalObject(s, "json") +} + +// YAML ... +func (s *ScriptsPrinter) YAML() []byte { + return printer.MarshalObject(s, "yaml") +} + +// Columns ... +func (s *ScriptsPrinter) Columns() [][]string { + return [][]string{0: { + "ID", + "DATE CREATED", + "DATE MODIFIED", + "TYPE", + "NAME", + }} +} + +// Data ... +func (s *ScriptsPrinter) Data() [][]string { + if len(s.Scripts) == 0 { + return [][]string{0: {"---", "---", "---", "---", "---"}} + } + + var data [][]string + for i := range s.Scripts { + data = append(data, []string{ + s.Scripts[i].ID, + s.Scripts[i].DateCreated, + s.Scripts[i].DateModified, + s.Scripts[i].Type, + s.Scripts[i].Name, + }) + } + + return data +} + +// Paging ... +func (s *ScriptsPrinter) Paging() [][]string { + return printer.NewPaging(s.Meta.Total, &s.Meta.Links.Next, &s.Meta.Links.Prev).Compose() +} + +// ====================================== + +// ScriptPrinter ... +type ScriptPrinter struct { + Script *govultr.StartupScript `json:"startup_script"` +} + +// JSON ... +func (s *ScriptPrinter) JSON() []byte { + return printer.MarshalObject(s, "json") +} + +// YAML ... +func (s *ScriptPrinter) YAML() []byte { + return printer.MarshalObject(s, "yaml") +} + +// Columns ... +func (s *ScriptPrinter) Columns() [][]string { + return nil +} + +// Data ... +func (s *ScriptPrinter) Data() [][]string { + return [][]string{ + 0: {"ID", s.Script.ID}, + 1: {"DATE CREATED", s.Script.DateCreated}, + 2: {"DATE MODIFIED", s.Script.DateModified}, + 3: {"TYPE", s.Script.Type}, + 4: {"NAME", s.Script.Name}, + 5: {"SCRIPT", s.Script.Script}, + } +} + +// Paging ... +func (s *ScriptPrinter) Paging() [][]string { + return nil +} diff --git a/cmd/script/script.go b/cmd/script/script.go new file mode 100644 index 00000000..08b4dc66 --- /dev/null +++ b/cmd/script/script.go @@ -0,0 +1,255 @@ +// Package script provides the script commands to the CLI +package script + +import ( + "errors" + "fmt" + "os" + + "github.com/spf13/cobra" + "github.com/vultr/govultr/v3" + "github.com/vultr/vultr-cli/v3/cmd/printer" + "github.com/vultr/vultr-cli/v3/cmd/utils" + "github.com/vultr/vultr-cli/v3/pkg/cli" +) + +// NewCmdScript provides the CLI command for startup script functions +func NewCmdScript(base *cli.Base) *cobra.Command { //nolint:gocyclo + o := &options{Base: base} + + cmd := &cobra.Command{ + Use: "script", + Short: "Commands to interact with startup scripts", + Aliases: []string{"ss", "startup-script"}, + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + utils.SetOptions(o.Base, cmd, args) + if !o.Base.HasAuth { + return errors.New(utils.APIKeyError) + } + return nil + }, + } + + // List + list := &cobra.Command{ + Use: "list", + Short: "List all startup scripts", + RunE: func(cmd *cobra.Command, args []string) error { + o.Base.Options = utils.GetPaging(cmd) + + scripts, meta, err := o.list() + if err != nil { + return fmt.Errorf("error retrieving startup script list : %v", err) + } + + data := &ScriptsPrinter{Scripts: scripts, Meta: meta} + o.Base.Printer.Display(data, nil) + + return nil + }, + } + + list.Flags().StringP("cursor", "c", "", "(optional) Cursor for paging.") + list.Flags().IntP( + "per-page", + "p", + utils.PerPageDefault, + fmt.Sprintf("(optional) Number of items requested per page. Default is %d and Max is 500.", utils.PerPageDefault), + ) + + // Get + get := &cobra.Command{ + Use: "get