[
  {
    "path": ".fastly/config.toml",
    "content": "config_version = 6\n\n[fastly]\naccount_endpoint = \"https://accounts.fastly.com\"\napi_endpoint = \"https://api.fastly.com\"\n\n[wasm-metadata]\nbuild_info = \"enable\"\nmachine_info = \"disable\" # users have to opt-in for this (everything else they'll have to opt-out)\npackage_info = \"enable\"\nscript_info = \"enable\"\n\n[language]\n[language.go]\ntinygo_constraint = \">= 0.28.1-0\"          # NOTE -0 indicates to the CLI's semver package that we accept pre-releases (TinyGo users commonly use pre-releases).\ntinygo_constraint_fallback = \">= 0.26.0-0\" # The Fastly Go SDK 0.2.0 requires `tinygo_constraint` but the 0.1.x SDK requires this constraint.\ntoolchain_constraint = \">= 1.21\"           # Go toolchain constraint for use with WASI support.\ntoolchain_constraint_tinygo = \">= 1.18\"    # Go toolchain constraint for use with TinyGo.\n\n[language.rust]\n# * Rust 1.91.0 has a bug where it produces broken wasm packages which crash\n#   when handling requests; the bug is fixed in Rust 1.91.1.\n# * Rust 1.93.0 has a bug on wasm32-wasip2 where it leaks file descriptors in\n#   `std::fs::File`; the bug is fixed in Rust 1.93.1.\ntoolchain_constraint = \">= 1.78 != 1.91.0 != 1.93.0\"\nwasm_wasi_target = \"wasm32-wasip1\"\n\n[language.cpp]\ntoolchain_constraint = \">= 14.0.0\"\nwasm_wasi_target = \"wasm32-wasip1\"\n\n[wasm-tools]\nttl = \"24h\"\n\n[viceroy]\nttl = \"24h\"\n"
  },
  {
    "path": ".fastly/help/README.md",
    "content": "# Developer Hub Help Pages\n\nThis directory contains troubleshooting pages for common issues in this project, which are ingested by the [Developer Hub](https://fastly.com/documentation/developers) and served on the `fastly.help` domain.\n\nTo update or create a help page, add or edit the Markdown files in this directory. Changes will be deployed on Developer Hub within 24 hours.\n\n## Example Page\n\n```md\n---\nid: ecp-feature\ntitle: Compute is not enabled on your account\ntemplate: help\n---\n\nOur edge compute platform is in limited availability and not yet available to all customers. Contact [Fastly Support](https://support.fastly.com/) or your account manager to have the feature enabled on your account.\n\n```\n"
  },
  {
    "path": ".fastly/help/cli-auth.mdx",
    "content": "---\nid: cli-auth\ntitle: Authenticate with the Fastly CLI\ntemplate: help\n---\n\n## Quick start\n\nPick whichever method suits your workflow:\n\n```\nfastly auth login                      # paste an API token interactively\nfastly auth login --sso --token <name> # authenticate via browser-based SSO\n```\n\nBoth store a default token so subsequent commands authenticate automatically.\n\n## Token sources and precedence\n\nThe CLI resolves a token in this order (first match wins):\n\n1. `--token` flag (a raw token string, or the name of a stored auth token). Not available when `FASTLY_DISABLE_AUTH_COMMAND` is set.\n2. `FASTLY_API_TOKEN` environment variable\n3. `profile` field in your project's `fastly.toml`\n4. Default auth token from the CLI config file\n\n## Stored tokens\n\nYou can store multiple named tokens and switch between them:\n\n```\nfastly auth add staging --api-token $STAGING_TOKEN\nfastly auth list\nfastly auth use staging\nfastly auth show staging\nfastly auth delete staging\n```\n\n## Non-interactive usage\n\nIn CI/CD or scripts where interactive prompts are not available, supply a token via the flag or environment variable:\n\n```\nfastly service list --token $MY_TOKEN\nFASTLY_API_TOKEN=... fastly service list\n```\n\n## Managed environments\n\nIf `FASTLY_DISABLE_AUTH_COMMAND` is set, both the `fastly auth` command tree and the `--token` global flag are disabled. Authentication is expected to be handled externally via `FASTLY_API_TOKEN` or pre-configured stored tokens. Background SSO refresh flows are unaffected.\n\n## Generating a token\n\nCreate an API token at: https://manage.fastly.com/account/personal/tokens\n"
  },
  {
    "path": ".fastly/help/ecp-feature.mdx",
    "content": "---\nid: ecp-feature\ntitle: Compute is not enabled on your account\ntemplate: help\n---\n\nOur edge compute platform is in limited availability and not yet available to all customers. Contact [Fastly Support](https://support.fastly.com/hc/en-us) or your account manager to have the feature enabled on your account.\n"
  },
  {
    "path": ".github/CODEOWNERS",
    "content": "* @fastly/developer-tools"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: Bug report\nabout: Tell us about a bug to help us improve\ntitle: ''\nlabels: bug\nassignees: ''\n\n---\n\n<!-- \n  **Note**:  Please keep in mind that if your issue requires troubleshooting that will require any details that you aren't comfortable disclosing in this public forum (such as service IDs), you will need to open a Fastly support ticket instead: https://support.fastly.com. \n  \n  Bug reports opened here:\n    - Do not have an SLA\n    - Should not be a duplicate of an existing support ticket that you have already created, or vice versa\n    - May take longer to solve compared to our dedicated support team\n\n  More details on submitting issues can be found here: https://github.com/fastly/cli/blob/main/ISSUES.md \n-->\n\n**Version**\n\nPlease paste the output of `fastly version` here.\n\n**What happened**\n\nPlease describe the command you ran, what you expected to happen, and what happened instead.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "content": "# Disable blank issues for non-maintainers. \nblank_issues_enabled: false\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "content": "---\nname: Feature request\nabout: Suggest an idea for this project\ntitle: \"[FEATURE REQUEST] ...\"\nlabels: feature request\nassignees: ''\n\n---\n\n<!-- \n  **Note**:  Please keep in mind that if your feature request discusses any details that you aren't comfortable disclosing in this public forum (such as service IDs), you will need to open a Fastly support ticket instead: https://support.fastly.com. \n  \n  Feature requests opened here:\n    - Do not have an SLA nor are guaranteed to be implemented\n    - Should not be a duplicate of an existing support ticket that you have already created, or vice versa\n\n  More details on submitting issues can be found here: https://github.com/fastly/cli/blob/main/ISSUES.md \n-->\n\n**Is your feature request related to a problem? Please describe.**\nA clear and concise description of what the problem is. Ex. I'm always frustrated when [...]\n\n**Describe the solution you'd like**\nA clear and concise description of what you want to happen.\n\n**Describe alternatives you've considered**\nA clear and concise description of any alternative solutions or features you've considered.\n\n**Additional context**\nAdd any other context or screenshots about the feature request here.\n"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "content": "### Change summary\n\n <!--\nBriefly describe the changes introduced in this pull request. Include context or\nreasoning behind the changes, even if they seem minor. If relevant, link to any\nrelated discussions (e.g. Slack threads, tickets, documents).\n-->\n\nAll Submissions:\n\n* [ ] Have you followed the guidelines in our Contributing document?\n* [ ] Have you checked to ensure there aren't other open [Pull Requests](https://github.com/fastly/cli/pulls) for the same update/change?\n\n<!-- You can erase any parts of this template not applicable to your Pull Request. -->\n\n### New Feature Submissions:\n\n* [ ] Does your submission pass tests?\n\n### Changes to Core Features:\n\n* [ ] Have you written new tests for your core changes, as applicable?\n* [ ] Have you successfully run tests with your changes locally?\n\n### User Impact\n\n<!-- What is the user impact of this change? -->\n\n### Are there any considerations that need to be addressed for release?\n\n<!-- Any breaking changes, etc -->\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\nupdates:\n  - package-ecosystem: \"gomod\"\n    directory: \"/\"\n    schedule:\n      interval: \"weekly\"\n    allow:\n      - dependency-type: \"all\"\n    groups:\n      go-dependencies:\n        patterns:\n          - \"*\"\n  - package-ecosystem: \"gomod\"\n    directory: \"/tools\"\n    schedule:\n      interval: \"weekly\"\n    allow:\n      - dependency-type: \"all\"\n    labels:\n      - \"tools\"\n    groups:\n      go-dependencies:\n        patterns:\n          - \"*\"\n  - package-ecosystem: \"github-actions\"\n    directory: \"/\"\n    schedule:\n      interval: \"weekly\"\n    labels:\n      - \"github_actions\"\n    groups:\n      gha-dependencies:\n        patterns:\n          - \"*\"\n"
  },
  {
    "path": ".github/workflows/dependabot_changelog_update.yml",
    "content": "name: Generate changelog entry for Dependabot\n\non:\n  pull_request:\n    types:\n      - opened\n      - synchronize\n      - reopened\npermissions:\n  contents: read\n  pull-requests: write\njobs:\n  dependabot-changelog-update:\n    if: github.actor == 'dependabot[bot]'\n    runs-on: ubuntu-latest\n    steps:\n      - name: Check labels\n        id: check-labels\n        uses: actions/github-script@v9\n        with:\n          script: |\n            const labels = context.payload.pull_request.labels.map(l => l.name);\n            const skip = labels.includes('tools') || labels.includes('github_actions');\n            if (skip) {\n              await github.rest.issues.addLabels({\n                owner: context.repo.owner,\n                repo: context.repo.repo,\n                issue_number: context.payload.pull_request.number,\n                labels: ['Skip-Changelog']\n              });\n            }\n            return !skip;\n          result-encoding: string\n      - name: Generate a GitHub token\n        if: steps.check-labels.outputs.result == 'true'\n        id: github-token\n        uses: actions/create-github-app-token@v3\n        with:\n          app-id: ${{ vars.GH_APP_ID }}\n          private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}\n          owner: ${{ github.repository_owner }}\n          repositories: \"cli\"\n      - name: Checkout code\n        if: steps.check-labels.outputs.result == 'true'\n        uses: actions/checkout@v6\n        with:\n          token: ${{ steps.github-token.outputs.token }}\n          repository: ${{ github.event.pull_request.head.repo.full_name }}\n          ref: ${{ github.event.pull_request.head.ref }}\n      - name: Generate changelog entry\n        if: steps.check-labels.outputs.result == 'true'\n        uses: dangoslen/dependabot-changelog-helper@v4\n        with:\n          activationLabels: dependencies\n          changelogPath: './CHANGELOG.md'\n          entryPrefix: 'build(deps): '\n      - name: Commit changelog entry\n        if: steps.check-labels.outputs.result == 'true'\n        uses: stefanzweifel/git-auto-commit-action@v7\n        with:\n          commit_message: \"docs(CHANGELOG.md): add dependency bump from dependabot\"\n"
  },
  {
    "path": ".github/workflows/merge_to_main.yml",
    "content": "name: Build CLI Binaries\non:\n  pull_request:\n    branches:\n      - \"main\"\n    types:\n      [closed]\npermissions:\n  contents: read\njobs:\n  build:\n    if: ${{ github.event.pull_request.merged }}\n    strategy:\n      matrix:\n        platform: [ubuntu-latest, macos-latest]\n    runs-on: ${{ matrix.platform }}\n    steps:\n      - name: \"Checkout code\"\n        uses: actions/checkout@v6\n      - name: \"Install Node\"\n        uses: actions/setup-node@v6\n        with:\n          node-version: 18\n      - name: \"Install Rust\"\n        uses: dtolnay/rust-toolchain@stable # to install tq via `make config`\n      - name: Install Go\n        uses: actions/setup-go@v6\n        with:\n          go-version: 1.25.x\n      - name: \"Install dependencies\"\n        run: make mod-download\n        shell: bash\n      - name: \"Create Build\"\n        run: make build\n        shell: bash\n      - name: \"Upload Build\"\n        uses: actions/upload-artifact@v7\n        with:\n          name: fastly-cli-build-${{ matrix.platform }}-${{ github.sha }}\n          path: fastly\n"
  },
  {
    "path": ".github/workflows/pr_test.yml",
    "content": "name: Test\n\non:\n  pull_request:\n    types:\n      - opened\n      - synchronize\n      - reopened\n      - labeled\n      - unlabeled\n    branches:\n      - main\npermissions:\n  contents: read\n# Stop any in-flight CI jobs when a new commit is pushed.\nconcurrency:\n  group: ${{ github.ref_name }}\n  cancel-in-progress: true\nenv:\n  GO_VERSION: 1.25.x\n  GOLANGCI_LINT_VERSION: v2.4\n  WASI_SDK_VERSION: 25\n  WASI_SDK_FULL_VERSION: \"25.0\"\njobs:\n  changelog:\n    if: github.actor != 'dependabot[bot]'\n    runs-on: ubuntu-latest\n    steps:\n      - uses: dangoslen/changelog-enforcer@v3\n  config:\n    runs-on: ubuntu-latest\n    steps:\n      - name: \"Checkout code\"\n        uses: actions/checkout@v6\n      - name: \"Install Rust\"\n        uses: dtolnay/rust-toolchain@stable\n      - name: \"Generate static app config\"\n        run: make config\n      - name: \"Config Artifact\"\n        uses: actions/upload-artifact@v7\n        with:\n          name: config-artifact-${{ github.sha }}\n          path: pkg/config/config.toml\n  lint:\n    needs: [config]\n    runs-on: ubuntu-latest\n    steps:\n      - name: \"Checkout code\"\n        uses: actions/checkout@v6\n      - name: \"Install Rust\"\n        uses: dtolnay/rust-toolchain@stable\n      - name: Install Go\n        uses: actions/setup-go@v6\n        with:\n          go-version: ${{ env.GO_VERSION }}\n      - name: \"Install dependencies\"\n        run: make mod-download\n        shell: bash\n      - name: \"Config Artifact\"\n        uses: actions/download-artifact@v8\n        with:\n          name: config-artifact-${{ github.sha }}\n      - name: \"Move Config\"\n        run: mv config.toml pkg/config/config.toml\n      - name: golangci-lint\n        uses: golangci/golangci-lint-action@v9\n        with:\n          version: ${{ env.GOLANGCI_LINT_VERSION }}\n          only-new-issues: true\n  test:\n    needs: [config]\n    strategy:\n      matrix:\n        tinygo-version: [0.31.2]\n        go-version: [1.25.x]\n        node-version: [18]\n        platform: [ubuntu-latest]\n    runs-on: ${{ matrix.platform }}\n    steps:\n      - name: \"Checkout code\"\n        uses: actions/checkout@v6\n      - name: \"Install Go\"\n        uses: actions/setup-go@v6\n        with:\n          go-version: ${{ matrix.go-version }}\n          # IMPORTANT: Disable caching to prevent cache restore errors later.\n          cache: false\n      - uses: acifani/setup-tinygo@v3\n        with:\n          tinygo-version: ${{ matrix.tinygo-version }}\n      - name: \"Install Rust\"\n        uses: dtolnay/rust-toolchain@stable\n      - name: \"Add wasm32-wasip1 Rust target\"\n        run: rustup target add wasm32-wasip1 --toolchain stable\n      - name: \"Validate Rust toolchain\"\n        run: rustup show && rustup target list --installed --toolchain stable\n        shell: bash\n      - name: \"Install Node\"\n        uses: actions/setup-node@v6\n        with:\n          node-version: ${{ matrix.node-version }}\n      - name: \"Install WASI SDK\"\n        run: |\n          wget -q https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-${{ env.WASI_SDK_VERSION }}/wasi-sdk-${{ env.WASI_SDK_FULL_VERSION }}-x86_64-linux.tar.gz\n          tar xzf wasi-sdk-${{ env.WASI_SDK_FULL_VERSION }}-x86_64-linux.tar.gz\n          echo \"$(pwd)/wasi-sdk-${{ env.WASI_SDK_FULL_VERSION }}-x86_64-linux/bin\" >> $GITHUB_PATH\n        shell: bash\n      - name: \"Config Artifact\"\n        uses: actions/download-artifact@v8\n        with:\n          name: config-artifact-${{ github.sha }}\n      - name: \"Move Config\"\n        run: mv config.toml pkg/config/config.toml\n      - name: \"Modify git cloned repo files 'modified' times\"\n        run: go run ./scripts/go-test-cache/main.go\n      # NOTE: Windows should fail quietly running pre-requisite target of `test`.\n      #\n      # On Windows, executing `make config` directly works fine.\n      # But when `config` is a pre-requisite to running `test`, it fails.\n      # But only when run via GitHub Actions.\n      # The ../../scripts/config.sh isn't run because you can't nest PowerShell instances.\n      # Each GitHub Action 'run' step is a PowerShell instance.\n      # And each instance is run as: powershell.exe -command \". '...'\"\n      - name: \"Test suite\"\n        run: make test\n        shell: bash\n        env:\n          # NOTE: The following lets us focus the test run while debugging.\n          # TEST_ARGS: \"-run TestBuild ./pkg/commands/compute/...\"\n          TEST_COMPUTE_INIT: true\n          TEST_COMPUTE_BUILD: true\n          TEST_COMPUTE_DEPLOY: true\n  test-release:\n    if: contains(github.head_ref, 'release')\n    needs: [config]\n    strategy:\n      matrix:\n        tinygo-version: [0.31.2]\n        go-version: [1.25.x]\n        node-version: [18]\n        platform: [macos-latest, windows-latest]\n    runs-on: ${{ matrix.platform }}\n    steps:\n      - name: \"Checkout code\"\n        uses: actions/checkout@v6\n      - name: \"Install Go\"\n        uses: actions/setup-go@v6\n        with:\n          go-version: ${{ matrix.go-version }}\n          # IMPORTANT: Disable caching to prevent cache restore errors later.\n          cache: false\n      - uses: acifani/setup-tinygo@v3\n        with:\n          tinygo-version: ${{ matrix.tinygo-version }}\n      - name: \"Install Rust\"\n        uses: dtolnay/rust-toolchain@stable\n      - name: \"Add wasm32-wasip1 Rust target\"\n        run: rustup target add wasm32-wasip1 --toolchain stable\n      - name: \"Validate Rust toolchain\"\n        run: rustup show && rustup target list --installed --toolchain stable\n        shell: bash\n      - name: \"Install Node\"\n        uses: actions/setup-node@v6\n        with:\n          node-version: ${{ matrix.node-version }}\n      - name: \"Install WASI SDK (Ubuntu)\"\n        if: matrix.platform == 'ubuntu-latest'\n        run: |\n          wget -q https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-${{ env.WASI_SDK_VERSION }}/wasi-sdk-${{ env.WASI_SDK_FULL_VERSION }}-x86_64-linux.tar.gz\n          tar xzf wasi-sdk-${{ env.WASI_SDK_FULL_VERSION }}-x86_64-linux.tar.gz\n          echo \"$(pwd)/wasi-sdk-${{ env.WASI_SDK_FULL_VERSION }}-x86_64-linux/bin\" >> $GITHUB_PATH\n        shell: bash\n      - name: \"Install WASI SDK (macOS)\"\n        if: matrix.platform == 'macos-latest'\n        run: |\n          wget -q https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-${{ env.WASI_SDK_VERSION }}/wasi-sdk-${{ env.WASI_SDK_FULL_VERSION }}-arm64-macos.tar.gz\n          tar xzf wasi-sdk-${{ env.WASI_SDK_FULL_VERSION }}-arm64-macos.tar.gz\n          echo \"$(pwd)/wasi-sdk-${{ env.WASI_SDK_FULL_VERSION }}-arm64-macos/bin\" >> $GITHUB_PATH\n        shell: bash\n      - name: \"Install WASI SDK (Windows)\"\n        if: matrix.platform == 'windows-latest'\n        run: |\n          Invoke-WebRequest -Uri \"https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-${{ env.WASI_SDK_VERSION }}/wasi-sdk-${{ env.WASI_SDK_FULL_VERSION }}-x86_64-windows.tar.gz\" -OutFile \"wasi-sdk.tar.gz\"\n          tar -xzf wasi-sdk.tar.gz\n          echo \"$PWD/wasi-sdk-${{ env.WASI_SDK_FULL_VERSION }}-x86_64-windows/bin\" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append\n        shell: pwsh\n      - name: \"Config Artifact\"\n        uses: actions/download-artifact@v8\n        with:\n          name: config-artifact-${{ github.sha }}\n      - name: \"Move Config\"\n        run: mv config.toml pkg/config/config.toml\n      - name: \"Modify git cloned repo files 'modified' times\"\n        run: go run ./scripts/go-test-cache/main.go\n      # NOTE: Windows should fail quietly running pre-requisite target of `test`.\n      #\n      # On Windows, executing `make config` directly works fine.\n      # But when `config` is a pre-requisite to running `test`, it fails.\n      # But only when run via GitHub Actions.\n      # The ../../scripts/config.sh isn't run because you can't nest PowerShell instances.\n      # Each GitHub Action 'run' step is a PowerShell instance.\n      # And each instance is run as: powershell.exe -command \". '...'\"\n      - name: \"Test suite\"\n        run: make test\n        shell: bash\n        env:\n          # NOTE: The following lets us focus the test run while debugging.\n          # TEST_ARGS: \"-run TestBuild ./pkg/commands/compute/...\"\n          TEST_COMPUTE_INIT: true\n          TEST_COMPUTE_BUILD: true\n          TEST_COMPUTE_DEPLOY: true\n  docker-builds:\n    runs-on: ubuntu-latest\n    steps:\n      - name: \"Checkout code\"\n        uses: actions/checkout@v6\n      - name: Build docker images\n        run: |\n          for dockerFile in Dockerfile*; do docker build -f $dockerFile . ; done\n  tools-build:\n    name: \"goreleaser tools build\"\n    if: contains(github.event.pull_request.labels.*.name, 'tools')\n    needs: [config]\n    runs-on: ubuntu-latest\n    steps:\n      - name: \"Checkout code\"\n        uses: actions/checkout@v6\n      - name: \"Install Go\"\n        uses: actions/setup-go@v6\n        with:\n          go-version-file: tools/go.mod\n      - name: \"Config Artifact\"\n        uses: actions/download-artifact@v8\n        with:\n          name: config-artifact-${{ github.sha }}\n      - name: \"Move Config\"\n        run: mv config.toml pkg/config/config.toml\n      - name: \"Test goreleaser tools build\"\n        run: go tool -modfile=tools/go.mod goreleaser build --single-target --snapshot --skip=post-hooks --skip=validate\n  golangci-latest:\n    name: lint-latest (informational)\n    needs: [config]\n    runs-on: ubuntu-latest\n    continue-on-error: true\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v6\n        with:\n          fetch-depth: 0\n      - name: Download config artifact\n        uses: actions/download-artifact@v8\n        with:\n          name: config-artifact-${{ github.sha }}\n          path: pkg/config\n      - name: Verify embedded config exists\n        run: |\n          test -f pkg/config/config.toml || { echo \"missing pkg/config/config.toml\"; ls -la pkg/config; exit 1; }\n      - name: Setup Go\n        uses: actions/setup-go@v6\n        with:\n          go-version: ${{ env.GO_VERSION }}\n      - name: Run golangci-lint@latest\n        id: lint\n        uses: golangci/golangci-lint-action@v9\n        with:\n          version: latest\n          only-new-issues: true\n        continue-on-error: true\n      - name: Report lint summary\n        run: |\n          if [ \"${{ steps.lint.outcome }}\" == \"success\" ]; then\n            echo \"✅ golangci-lint@latest passed.\" >> $GITHUB_STEP_SUMMARY\n          else\n            echo \"⚠️ golangci-lint@latest failed (informational only).\" >> $GITHUB_STEP_SUMMARY\n          fi\n"
  },
  {
    "path": ".github/workflows/publish_release.yml",
    "content": "name: NPM Release\non:\n  workflow_dispatch:\n  release:\n    types:\n      - published\npermissions:\n  id-token: write\n  contents: read\n  packages: write\njobs:\n  npm_release:\n    runs-on: ubuntu-latest\n    steps:\n      - name: \"Checkout code\"\n        uses: actions/checkout@v6\n      - name: \"Fetch unshallow repo\"\n        run: git fetch --prune --unshallow\n      - name: Set up Node.js\n        uses: actions/setup-node@v6\n        with:\n          node-version: 'lts/*'\n          registry-url: 'https://registry.npmjs.org'\n      - name: Set up auth for GitHub packages\n        run: |\n          npm config set \"//npm.pkg.github.com/:_authToken\" \"\\${NODE_AUTH_TOKEN}\"\n      - name: Update npm packages to latest version\n        working-directory: ./npm/@fastly/cli\n        run: npm install && npm version \"${{ github.ref_name }}\" --allow-same-version\n      - name: Publish packages to npmjs.org\n        working-directory: ./npm/@fastly\n        run: |\n          for dir in *; do\n            (\n              echo $dir\n              cd $dir\n              npm publish --access=public\n            )\n          done\n      - name: Publish packages to GitHub packages\n        working-directory: ./npm/@fastly\n        env:\n          NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        run: |\n          npm config set \"@fastly:registry\" \"https://npm.pkg.github.com/\"\n          for dir in *; do\n            (\n              echo $dir\n              cd $dir\n              npm publish --access=public\n            )\n          done\n"
  },
  {
    "path": ".github/workflows/tag_to_draft_release.yml",
    "content": "name: Draft Release from Tag\non:\n  workflow_dispatch:\n  push:\n    tags:\n      - 'v*'\npermissions:\n  contents: read\njobs:\n  goreleaser:\n    runs-on: ubuntu-latest\n    steps:\n      - name: \"Checkout code\"\n        uses: actions/checkout@v6\n      - name: \"Fetch unshallow repo\"\n        run: git fetch --prune --unshallow\n      - name: \"Install Go\"\n        uses: actions/setup-go@v6\n        with:\n          go-version: '1.26.x'\n      - name: \"Install Rust\"\n        uses: dtolnay/rust-toolchain@stable\n      - name: \"Generate static app config\"\n        run: make config\n      # Passing the raw SSH private key causes an error:\n      # Load key \"/tmp/id_*\": invalid format\n      #\n      # Testing locally we discovered that storing in a file and passing the file path works.\n      #\n      # NOTE:\n      # The file aur_key must be added to .gitignore otherwise a 'dirty state' error is triggered in goreleaser.\n      # https://github.com/goreleaser/goreleaser/blob/9505cf7054b05a6e9a4a36f806d525bc33660e9e/www/docs/errors/dirty.md\n      #\n      # You must also reduce the permissions from a default of 0644 to 600 to avoid a 'bad permissions' error.\n      - name: \"Store AUR_KEY in local file\"\n        run: echo '${{ secrets.AUR_KEY }}' > '${{ github.workspace }}/aur_key' && chmod 600 '${{ github.workspace }}/aur_key'\n      - name: \"Run GoReleaser\"\n        uses: goreleaser/goreleaser-action@v7\n        with:\n          # goreleaser version (NOT goreleaser-action version)\n          # update inline with the Makefile\n          version: '~> v2'\n          args: release --clean\n        env:\n          AUR_KEY: '${{ github.workspace }}/aur_key'\n          GITHUB_TOKEN: ${{ secrets.RELEASE_GITHUB_TOKEN }}\n"
  },
  {
    "path": ".gitignore",
    "content": "# Fastly binary\n**/fastly\n# But allow fastly main package\n!cmd/fastly\n\nRELEASE_CHANGELOG.md\n\n# Fastly package format files\n**/fastly.toml\n!pkg/commands/compute/testdata/build/rust/fastly.toml\n**/Cargo.toml\n!pkg/commands/compute/testdata/build/rust/Cargo.toml\n**/Cargo.lock\n!pkg/commands/compute/testdata/build/rust/Cargo.lock\n**/*.tar.gz\n!pkg/github/testdata/*.tar.gz\n!pkg/commands/compute/testdata/deploy/pkg/package.tar.gz\n**/bin\n**/src\n!pkg/commands/compute/testdata/build/rust/src\n!pkg/commands/compute/testdata/build/javascript/src\n**/target\nrust-toolchain\n.cargo\n**/node_modules\npkg/commands/compute/package-lock.json\n\n# Binaries for programs and plugins\n*.exe\n*.exe~*\n*.dll\n*.so\n*.dylib\n\n# Test binary, build with `go test -c`\n*.test\n\n# Output of the go coverage tool, specifically when used with LiteIDE\n*.out\n\n# Ignore IDEs\n.idea\n\n# Ignore Vim\n# https://github.com/github/gitignore/blob/41ec05833ae00be887bab36fceaee63611e86189/Global/Vim.gitignore\n[._]*.s[a-v][a-z]\n[._]*.sw[a-p]\n[._]s[a-rt-v][a-z]\n[._]ss[a-gi-z]\n[._]sw[a-p]\n\n# Ignore OS files\n.DS_Store\n\n# Ignore binaries\ndist/\nbuild/\n!pkg/commands/compute/testdata/build/\n\n# Ignore application configuration\nvendor/\n\n# Ignore generated file for AUR_KEY which is passed to goreleaser as an environment variable.\naur_key\n\n# Ignore static config that is embedded into the CLI\n# All Makefile targets use the 'config' as a prerequisite (which generates the config)\npkg/config/config.toml\n\n# Ignore commitlint tool\ncommitlint.config.js\ncallvis.svg\n\n# Ignore generated npm packages\nnpm/@fastly/cli-*/\n"
  },
  {
    "path": ".golangci.yml",
    "content": "version: \"2\"\nrun:\n  allow-parallel-runners: true\n  modules-download-mode: readonly\nlinters:\n  enable:\n    - durationcheck\n    - errcheck\n    - exhaustive\n    - forcetypeassert\n    - gocritic\n    - godot\n    - gosec\n    - govet\n    - ineffassign\n    - makezero\n    - misspell\n    - nilerr\n    - predeclared\n    - revive\n    - staticcheck\n    - unconvert\n    - unparam\n    - unused\n  settings:\n    govet:\n      enable:\n        - nilness\n    staticcheck:\n      checks:\n        - all\n        - '-QF1008' \n  exclusions:\n    generated: lax\n    presets:\n      - comments\n      - common-false-positives\n      - legacy\n      - std-error-handling\n    paths:\n      - third_party$\n      - builtin$\n      - examples$\nformatters:\n  enable:\n    - gofumpt\n    - goimports\n  settings:\n    goimports:\n      local-prefixes:\n        - github.com/fastly\n  exclusions:\n    generated: lax\n    paths:\n      - third_party$\n      - builtin$\n      - examples$"
  },
  {
    "path": ".goreleaser.yml",
    "content": "# https://goreleaser.com/customization/project/\nproject_name: fastly\nversion: 2\n\n# https://goreleaser.com/customization/release/\nrelease:\n  draft: true\n  prerelease: auto\n  extra_files:\n    - glob: \"dist/usage.json\"\n\n# https://goreleaser.com/customization/hooks/\nbefore:\n  hooks:\n    - go mod tidy\n    - go mod download\n\n# https://goreleaser.com/customization/builds/\nbuilds:\n  - <<: &build_defaults\n      main: ./cmd/fastly\n      ldflags:\n        - -s -w -X \"github.com/fastly/cli/pkg/revision.AppVersion=v{{ .Version }}\"\n        - -X \"github.com/fastly/cli/pkg/revision.GitCommit={{ .ShortCommit }}\"\n        - -X \"github.com/fastly/cli/pkg/revision.Environment=release\"\n    env:\n      - CGO_ENABLED=0\n    id: macos\n    goos: [darwin]\n    goarch: [amd64, arm64]\n  - <<: *build_defaults\n    env:\n      - CGO_ENABLED=0\n    id: linux\n    goos: [linux]\n    goarch: [\"386\", amd64, arm64]\n  - <<: *build_defaults\n    env:\n      - CGO_ENABLED=0\n    id: windows\n    goos: [windows]\n    goarch: [\"386\", amd64, arm64]\n  - <<: *build_defaults\n    env:\n      - CGO_ENABLED=0\n    id: generate-usage\n    goos: [linux]\n    goarch: [amd64]\n    binary: 'fastly-usage' # we rename the binary to prevent an error caused by the earlier 'linux/amd64' step\n                           # which already creates a 'fastly' binary in '/usr/local/bin'.\n    hooks:\n      post:\n        - cmd: \"scripts/documentation.sh {{ .Path }}\"\n\n# https://goreleaser.com/customization/archive/\narchives:\n  - id: nix\n    ids: [macos, linux]\n    <<: &archive_defaults\n      name_template: \"{{ .ProjectName }}_v{{ .Version }}_{{ .Os }}-{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}\"\n      files:\n        - none*\n    wrap_in_directory: false\n    formats: [tar.gz]\n  - id: windows-tar\n    ids: [windows]\n    <<: *archive_defaults\n    wrap_in_directory: false\n    formats: [tar.gz]\n  - id: windows-zip\n    ids: [windows]\n    <<: *archive_defaults\n    wrap_in_directory: false\n    formats: [zip]\n# https://goreleaser.com/customization/aur/\naurs:\n  - homepage: \"https://github.com/fastly/cli\"\n    description: \"A CLI for interacting with the Fastly platform\"\n    maintainers:\n      - 'oss@fastly.com'\n    license: \"Apache license 2.0\"\n    skip_upload: auto\n    provides:\n      - fastly\n    conflicts:\n      - fastly\n\n    # The SSH private key that should be used to commit to the Git repository.\n    # This can either be a path or the key contents.\n    #\n    # WARNING: do not expose your private key in the config file!\n    private_key: '{{ .Env.AUR_KEY }}'\n\n    # The AUR Git URL for this package.\n    # Defaults to empty.\n    git_url: 'ssh://aur@aur.archlinux.org/fastly-bin.git'\n\n    # List of packages that are not needed for the software to function,\n    # but provide additional features.\n    #\n    # Must be in the format `package: short description of the extra functionality`.\n    #\n    # Defaults to empty.\n    optdepends:\n      - 'viceroy: for running service locally'\n\n    # The value to be passed to `GIT_SSH_COMMAND`.\n    #\n    #\n    # Defaults to `ssh -i {{ .KeyPath }} -o StrictHostKeyChecking=accept-new -F /dev/null`.\n    git_ssh_command: 'ssh -i {{ .KeyPath }} -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -F /dev/null'\n\n# https://goreleaser.com/customization/homebrew/\nbrews:\n  - name: fastly\n    ids: [nix]\n    repository:\n      owner: fastly\n      name: homebrew-tap\n    skip_upload: auto\n    description: A CLI for interacting with the Fastly platform\n    homepage: https://github.com/fastly/cli\n    directory: Formula\n    custom_block: |\n      head do\n        url \"https://github.com/fastly/cli.git\"\n        depends_on \"go\"\n      end\n    install: |-\n      system \"make\" if build.head?\n      bin.install \"fastly\"\n      (bash_completion/\"fastly.sh\").write `#{bin}/fastly --completion-script-bash`\n      (zsh_completion/\"_fastly\").write `#{bin}/fastly --completion-script-zsh`\n    test: |-\n      help_text = shell_output(\"#{bin}/fastly --help\")\n      assert_includes help_text, \"Usage:\"\n\n# https://goreleaser.com/customization/nfpm/\nnfpms:\n  - license: Apache 2.0\n    maintainer: Fastly\n    homepage: https://github.com/fastly/cli\n    bindir: /usr/local/bin\n    description: CLI tool for interacting with the Fastly API.\n    formats:\n      - deb\n      - rpm\n    contents:\n      - src: deb-copyright\n        dst: /usr/share/doc/fastly/copyright\n        packager: deb\n\n# https://goreleaser.com/customization/checksum/\nchecksum:\n  name_template: \"{{ .ProjectName }}_v{{ .Version }}_SHA256SUMS\"\n\n# https://goreleaser.com/customization/snapshots/\nsnapshot:\n  version_template: \"{{ .Tag }}-next\"\n\n# https://goreleaser.com/customization/changelog/\nchangelog:\n  disable: true\n\n# https://goreleaser.com/customization/docker/\n# dockers:\n# - <<: &build_opts\n#     use: buildx\n#     goos: linux\n#     goarch: amd64\n#     image_templates:\n#       - \"ghcr.io/fastly/cli:{{ .Version }}\"\n#     build_flag_templates:\n#       - \"--platform=linux/amd64\"\n#       - --label=title={{ .ProjectName }}\n#       - --label=description={{ .ProjectName }}\n#       - --label=url=https://github.com/fastly/cli\n#       - --label=source=https://github.com/fastly/cli\n#       - --label=version={{ .Version }}\n#       - --label=created={{ time \"2006-01-02T15:04:05Z07:00\" }}\n#       - --label=revision={{ .FullCommit }}\n#       - --label=licenses=Apache-2.0\n#   dockerfile: Dockerfile-node\n# - <<: *build_opts\n#   dockerfile: Dockerfile-rust\n"
  },
  {
    "path": ".tmpl/create.go",
    "content": "package ${CLI_PACKAGE}\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/manifest\"\n\t\"github.com/fastly/cli/pkg/config\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/text\"\n\t\"github.com/fastly/go-fastly/v4/fastly\"\n)\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, globals *config.Data, data manifest.Data) *CreateCommand {\n\tvar c CreateCommand\n\tc.CmdClause = parent.Command(\"create\", \"<...>\").Alias(\"add\")\n\tc.Globals = globals\n\tc.manifest = data\n\n\t// Required flags\n\t// c.CmdClause.Flag(\"<...>\", \"<...>\").Required().StringVar(&c.<...>)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional flags\n\t// c.CmdClause.Flag(\"<...>\", \"<...>\").Action(c.<...>.Set).StringVar(&c.<...>.Value)\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.autoClone.Set,\n\t\tDst:    &c.autoClone.Value,\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &c.manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\n\treturn &c\n}\n\n// CreateCommand calls the Fastly API to create an appropriate resource.\ntype CreateCommand struct {\n\targparser.Base\n\n\tautoClone      argparser.OptionalAutoClone\n\tmanifest manifest.Data\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *CreateCommand) Exec(in io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(false),\n\t\tLocked:             optional.Of(false),\n\t\tAutoCloneFlag:      c.autoClone,\n\t\tClient:             c.Globals.Client,\n\t\tManifest:           c.manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flag.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]interface{}{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": errors.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tinput := c.constructInput(serviceID, serviceVersion.Number)\n\n\tr, err := c.Globals.Client.Create${CLI_API}(input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]interface{}{\n\t\t\t\"Service ID\": serviceID,\n\t\t\t\"Service Version\": serviceVersion.Number,\n\t\t})\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Created <...> '%s' (service: %s, version: %d)\", r.<...>, r.ServiceID, r.ServiceVersion)\n\treturn nil\n}\n\n// constructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *CreateCommand) constructInput(serviceID string, serviceVersion int) *fastly.Create${CLI_API}Input {\n\tvar input fastly.Create${CLI_API}Input\n\n\tinput.ServiceID = serviceID\n\tinput.ServiceVersion = serviceVersion\n\n\t// if c.<...>.WasSet {\n\t// \tinput.<...> = c.<...>.Value\n\t// }\n\n\treturn &input\n}\n"
  },
  {
    "path": ".tmpl/delete.go",
    "content": "package ${CLI_PACKAGE}\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/manifest\"\n\t\"github.com/fastly/cli/pkg/config\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/text\"\n\t\"github.com/fastly/go-fastly/v4/fastly\"\n)\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, globals *config.Data, data manifest.Data) *DeleteCommand {\n\tvar c DeleteCommand\n\tc.CmdClause = parent.Command(\"delete\", \"<...>\").Alias(\"remove\")\n\tc.Globals = globals\n\tc.manifest = data\n\n\t// Required flags\n\t// c.CmdClause.Flag(\"<...>\", \"<...>\").Required().StringVar(&c.<...>)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional flags\n\t// c.CmdClause.Flag(\"<...>\", \"<...>\").Action(c.<...>.Set).StringVar(&c.<...>.Value)\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.autoClone.Set,\n\t\tDst:    &c.autoClone.Value,\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &c.manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\n\treturn &c\n}\n\n// DeleteCommand calls the Fastly API to delete an appropriate resource.\ntype DeleteCommand struct {\n\targparser.Base\n\n\tautoClone      argparser.OptionalAutoClone\n\tmanifest manifest.Data\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DeleteCommand) Exec(in io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(false),\n\t\tLocked:             optional.Of(false),\n\t\tAutoCloneFlag:      c.autoClone,\n\t\tClient:             c.Globals.Client,\n\t\tManifest:           c.manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flag.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]interface{}{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": errors.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tinput := c.constructInput(serviceID, serviceVersion.Number)\n\n\terr := c.Globals.Client.Delete${CLI_API}(input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]interface{}{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": serviceVersion.Number,\n\t\t})\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Deleted <...> '%s' (service: %s, version: %d)\", c.<...>, serviceID, serviceVersion.Number)\n\treturn nil\n}\n\n// constructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *DeleteCommand) constructInput(serviceID string, serviceVersion int) *fastly.Delete${CLI_API}Input {\n\tvar input fastly.Delete${CLI_API}Input\n\n\tinput.ACLID = c.aclID\n\tinput.ID = c.id\n\tinput.ServiceID = serviceID\n\n\treturn &input\n}\n"
  },
  {
    "path": ".tmpl/describe.go",
    "content": "package ${CLI_PACKAGE}\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/manifest\"\n\t\"github.com/fastly/cli/pkg/config\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/go-fastly/v4/fastly\"\n)\n\n// NewDescribeCommand returns a usable command registered under the parent.\nfunc NewDescribeCommand(parent argparser.Registerer, globals *config.Data, data manifest.Data) *DescribeCommand {\n\tvar c DescribeCommand\n\tc.CmdClause = parent.Command(\"describe\", \"<...>\").Alias(\"get\")\n\tc.Globals = globals\n\tc.manifest = data\n\n\t// Required flags\n\t// c.CmdClause.Flag(\"<...>\", \"<...>\").Required().StringVar(&c.<...>)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional flags\n\t// c.CmdClause.Flag(\"<...>\", \"<...>\").Action(c.<...>.Set).StringVar(&c.<...>.Value)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &c.manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\n\treturn &c\n}\n\n// DescribeCommand calls the Fastly API to describe an appropriate resource.\ntype DescribeCommand struct {\n\targparser.Base\n\n\tmanifest manifest.Data\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DescribeCommand) Exec(in io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tClient:             c.Globals.Client,\n\t\tManifest:           c.manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flag.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]interface{}{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": errors.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tinput := c.constructInput(serviceID, serviceVersion.Number)\n\n\tr, err := c.Globals.Client.Get${CLI_API}(input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]interface{}{\n\t\t\t\"Service ID\": serviceID,\n\t\t\t\"Service Version\": serviceVersion.Number,\n\t\t})\n\t\treturn err\n\t}\n\n\tc.print(out, r)\n\treturn nil\n}\n\n// constructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *DescribeCommand) constructInput(serviceID string, serviceVersion int) *fastly.Get${CLI_API}Input {\n\tvar input fastly.Get${CLI_API}Input\n\n\tinput.ACLID = c.aclID\n\tinput.ID = c.id\n\tinput.ServiceID = serviceID\n\n\treturn &input\n}\n\n// print displays the information returned from the API.\nfunc (c *DescribeCommand) print(out io.Writer, r *fastly.${CLI_API}) {\n\tfmt.Fprintf(out, \"\\nService ID: %s\\n\", r.ServiceID)\n\tfmt.Fprintf(out, \"Service Version: %d\\n\\n\", r.ServiceVersion)\n\tfmt.Fprintf(out, \"<...>: %s\\n\\n\", r.<...>)\n\n\tif r.CreatedAt != nil {\n\t\tfmt.Fprintf(out, \"Created at: %s\\n\", r.CreatedAt)\n\t}\n\tif r.UpdatedAt != nil {\n\t\tfmt.Fprintf(out, \"Updated at: %s\\n\", r.UpdatedAt)\n\t}\n\tif r.DeletedAt != nil {\n\t\tfmt.Fprintf(out, \"Deleted at: %s\\n\", r.DeletedAt)\n\t}\n}\n"
  },
  {
    "path": ".tmpl/doc.go",
    "content": "// Package ${CLI_PACKAGE} contains commands to <...>.\npackage ${CLI_PACKAGE}\n"
  },
  {
    "path": ".tmpl/doc_parent.go",
    "content": "// Package ${CLI_CATEGORY} contains commands for <...>.\npackage ${CLI_CATEGORY}\n"
  },
  {
    "path": ".tmpl/list.go",
    "content": "package ${CLI_PACKAGE}\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/manifest\"\n\t\"github.com/fastly/cli/pkg/config\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/text\"\n\t\"github.com/fastly/go-fastly/v4/fastly\"\n)\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, globals *config.Data, data manifest.Data) *ListCommand {\n\tvar c ListCommand\n\tc.CmdClause = parent.Command(\"list\", \"<...>\")\n\tc.Globals = globals\n\tc.manifest = data\n\n\t// Required flags\n\t// c.CmdClause.Flag(\"<...>\", \"<...>\").Required().StringVar(&c.<...>)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional Flags\n\t// c.CmdClause.Flag(\"<...>\", \"<...>\").Action(c.<...>.Set).StringVar(&c.<...>.Value)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &c.manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\n\treturn &c\n}\n\n// ListCommand calls the Fastly API to list appropriate resources.\ntype ListCommand struct {\n\targparser.Base\n\n\tmanifest manifest.Data\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *ListCommand) Exec(in io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tClient:             c.Globals.Client,\n\t\tManifest:           c.manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flag.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]interface{}{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": errors.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tinput := c.constructInput(serviceID, serviceVersion.Number)\n\n\trs, err := c.Globals.Client.List${CLI_API}s(input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]interface{}{\n\t\t\t\"Service ID\": serviceID,\n\t\t\t\"Service Version\": serviceVersion.Number,\n\t\t})\n\t\treturn err\n\t}\n\n\tif c.Globals.Verbose() {\n\t\tc.printVerbose(out, serviceID, rs)\n\t} else {\n\t\tc.printSummary(out, rs)\n\t}\n\treturn nil\n}\n\n// constructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *ListCommand) constructInput(serviceID string) *fastly.List${CLI_API}sInput {\n\tvar input fastly.List${CLI_API}sInput\n\n\tinput.ServiceID = serviceID\n\tinput.ServiceVersion = serviceVersion\n\n\treturn &input\n}\n\n// printVerbose displays the information returned from the API in a verbose\n// format.\nfunc (c *ListCommand) printVerbose(out io.Writer, serviceID string, serviceVersion int, rs []*fastly.${CLI_API}) {\n\tfmt.Fprintf(out, \"\\nService ID: %s\\n\", serviceID)\n\tfmt.Fprintf(out, \"Service Version: %d\\n\", serviceVersion)\n\n\tfor _, r := range rs {\n\t\tfmt.Fprintf(out, \"\\n<...>: %s\\n\\n\", r.<...>)\n\n\t\tif r.CreatedAt != nil {\n\t\t\tfmt.Fprintf(out, \"Created at: %s\\n\", r.CreatedAt)\n\t\t}\n\t\tif r.UpdatedAt != nil {\n\t\t\tfmt.Fprintf(out, \"Updated at: %s\\n\", r.UpdatedAt)\n\t\t}\n\t\tif r.DeletedAt != nil {\n\t\t\tfmt.Fprintf(out, \"Deleted at: %s\\n\", r.DeletedAt)\n\t\t}\n\t}\n}\n\n// printSummary displays the information returned from the API in a summarised\n// format.\nfunc (c *ListCommand) printSummary(out io.Writer, rs []*fastly.${CLI_API}) {\n\tt := text.NewTable(out)\n\tt.AddHeader(\"SERVICE ID\", \"<...>\")\n\tfor _, r := range rs {\n\t\tt.AddLine(r.ServiceID, r.<...>)\n\t}\n\tt.Print()\n}\n"
  },
  {
    "path": ".tmpl/root.go",
    "content": "package ${CLI_PACKAGE}\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/config\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, globals *config.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = globals\n\tc.CmdClause = parent.Command(\"${CLI_COMMAND}\", \"<...>\")\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(in io.Reader, out io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": ".tmpl/root_parent.go",
    "content": "package ${CLI_CATEGORY}\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/config\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, globals *config.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = globals\n\tc.CmdClause = parent.Command(\"${CLI_CATEGORY_COMMAND}\", \"<...>\")\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(in io.Reader, out io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": ".tmpl/test.go",
    "content": "package ${CLI_PACKAGE}_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/fastly/go-fastly/v10/fastly\"\n\n\t\"github.com/fastly/cli/pkg/mock\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n)\n\nconst (\n\tbaseCommand = \"${CLI_COMMAND}\"\n)\n\nfunc TestCreate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --version flag\",\n\t\t\tWantError: \"error parsing arguments: required flag --version not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --service-id flag\",\n\t\t\tArgs:      \"--version 3\",\n\t\t\tWantError: \"error reading service: no service ID found\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate missing --autoclone flag with 'active' service\",\n\t\t\tAPI: mock.API{\n\t\t\t\tListVersionsFn: testutil.ListVersions,\n\t\t\t},\n\t\t\tArgs:      \"--service-id 123 --version 1\",\n\t\t\tWantError: \"service version 1 is active\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate missing --autoclone flag with 'locked' service\",\n\t\t\tAPI: mock.API{\n\t\t\t\tListVersionsFn: testutil.ListVersions,\n\t\t\t},\n\t\t\tArgs:      \"--service-id 123 --version 2\",\n\t\t\tWantError: \"service version 2 is locked\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate Create${CLI_API} API error\",\n\t\t\tAPI: mock.API{\n\t\t\t\tListVersionsFn: testutil.ListVersions,\n\t\t\t\tCreate${CLI_API}Fn: func(i *fastly.Create${CLI_API}Input) (*fastly.${CLI_API}, error) {\n\t\t\t\t\treturn nil, testutil.Err\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:      \"--service-id 123 --version 3\",\n\t\t\tWantError: testutil.Err.Error(),\n\t\t},\n\t\t{\n\t\t\tName: \"validate Create${CLI_API} API success\",\n\t\t\tAPI: mock.API{\n\t\t\t\tListVersionsFn: testutil.ListVersions,\n\t\t\t\tCreate${CLI_API}Fn: func(i *fastly.Create${CLI_API}Input) (*fastly.${CLI_API}, error) {\n\t\t\t\t\treturn &fastly.${CLI_API}{\n\t\t\t\t\t\tServiceID: i.ServiceID,\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:       \"--service-id 123 --version 3\",\n\t\t\tWantOutput: \"Created <...> '456' (service: 123)\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate --autoclone results in cloned service version\",\n\t\t\tAPI: mock.API{\n\t\t\t\tListVersionsFn: testutil.ListVersions,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tCreate${CLI_API}Fn: func(i *fastly.Create${CLI_API}Input) (*fastly.${CLI_API}, error) {\n\t\t\t\t\treturn &fastly.VCL{\n\t\t\t\t\t\tServiceID:      i.ServiceID,\n\t\t\t\t\t\tServiceVersion: i.ServiceVersion,\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:       \"--autoclone --service-id 123 --version 1\",\n\t\t\tWantOutput: \"Created <...> 'foo' (service: 123, version: 4)\",\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{baseCommand, \"create\"}, scenarios)\n}\n\nfunc TestDelete(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --version flag\",\n\t\t\tWantError: \"error parsing arguments: required flag --version not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --service-id flag\",\n\t\t\tArgs:      \"--version 1\",\n\t\t\tWantError: \"error reading service: no service ID found\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate missing --autoclone flag with 'active' service\",\n\t\t\tAPI: mock.API{\n\t\t\t\tListVersionsFn: testutil.ListVersions,\n\t\t\t},\n\t\t\tArgs:      \"--service-id 123 --version 1\",\n\t\t\tWantError: \"service version 1 is active\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate missing --autoclone flag with 'locked' service\",\n\t\t\tAPI: mock.API{\n\t\t\t\tListVersionsFn: testutil.ListVersions,\n\t\t\t},\n\t\t\tArgs:      \"--service-id 123 --version 2\",\n\t\t\tWantError: \"service version 2 is locked\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate Delete${CLI_API} API error\",\n\t\t\tAPI: mock.API{\n\t\t\t\tListVersionsFn: testutil.ListVersions,\n\t\t\t\tDelete${CLI_API}Fn: func(i *fastly.Delete${CLI_API}Input) error {\n\t\t\t\t\treturn testutil.Err\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:      \"--service-id 123 --version 3\",\n\t\t\tWantError: testutil.Err.Error(),\n\t\t},\n\t\t{\n\t\t\tName: \"validate Delete${CLI_API} API success\",\n\t\t\tAPI: mock.API{\n\t\t\t\tListVersionsFn: testutil.ListVersions,\n\t\t\t\tDelete${CLI_API}Fn: func(i *fastly.Delete${CLI_API}Input) error {\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:       \"--service-id 123 --version 3\",\n\t\t\tWantOutput: \"Deleted <...> '456' (service: 123)\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate --autoclone results in cloned service version\",\n\t\t\tAPI: mock.API{\n\t\t\t\tListVersionsFn: testutil.ListVersions,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tDelete${CLI_API}Fn: func(i *fastly.Delete${CLI_API}Input) error {\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:       \"--autoclone --service-id 123 --version 1\",\n\t\t\tWantOutput: \"Deleted <...> 'foo' (service: 123, version: 4)\",\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{baseCommand, \"delete\"}, scenarios)\n}\n\nfunc TestDescribe(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --version flag\",\n\t\t\tWantError: \"error parsing arguments: required flag --version not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --service-id flag\",\n\t\t\tArgs:      \"--version 3\",\n\t\t\tWantError: \"error reading service: no service ID found\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate Get${CLI_API} API error\",\n\t\t\tAPI: mock.API{\n\t\t\t\tListVersionsFn: testutil.ListVersions,\n\t\t\t\tGet${CLI_API}Fn: func(i *fastly.Get${CLI_API}Input) (*fastly.${CLI_API}, error) {\n\t\t\t\t\treturn nil, testutil.Err\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:      \"--service-id 123 --version 3\",\n\t\t\tWantError: testutil.Err.Error(),\n\t\t},\n\t\t{\n\t\t\tName: \"validate Get${CLI_API} API success\",\n\t\t\tAPI: mock.API{\n\t\t\t\tListVersionsFn: testutil.ListVersions,\n\t\t\t\tGet${CLI_API}Fn: get${CLI_API},\n\t\t\t},\n\t\t\tArgs:       \"--service-id 123 --version 3\",\n\t\t\tWantOutput: \"<...>\",\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{baseCommand, \"describe\"}, scenarios)\n}\n\nfunc TestList(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --version flag\",\n\t\t\tWantError: \"error parsing arguments: required flag --version not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --service-id flag\",\n\t\t\tArgs:      \"--version 3\",\n\t\t\tWantError: \"error reading service: no service ID found\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate List${CLI_API}s API error\",\n\t\t\tAPI: mock.API{\n\t\t\t\tListVersionsFn: testutil.ListVersions,\n\t\t\t\tList${CLI_API}sFn: func(i *fastly.List${CLI_API}sInput) ([]*fastly.${CLI_API}, error) {\n\t\t\t\t\treturn nil, testutil.Err\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:      \"--service-id 123 --version 3\",\n\t\t\tWantError: testutil.Err.Error(),\n\t\t},\n\t\t{\n\t\t\tName: \"validate List${CLI_API}s API success\",\n\t\t\tAPI: mock.API{\n\t\t\t\tListVersionsFn: testutil.ListVersions,\n\t\t\t\tList${CLI_API}sFn: list${CLI_API}s,\n\t\t\t},\n\t\t\tArgs:       \"--service-id 123 --version 3\",\n\t\t\tWantOutput: \"<...>\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate --verbose flag\",\n\t\t\tAPI: mock.API{\n\t\t\t\tListVersionsFn: testutil.ListVersions,\n\t\t\t\tList${CLI_API}sFn: list${CLI_API}s,\n\t\t\t},\n\t\t\tArgs:       \"--service-id 123 --version 3 --verbose\",\n\t\t\tWantOutput: \"<...>\",\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{baseCommand, \"list\"}, scenarios)\n}\n\nfunc TestUpdate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --name flag\",\n\t\t\tArgs:      \"--version 3\",\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --version flag\",\n\t\t\tArgs:      \"--name foobar\",\n\t\t\tWantError: \"error parsing arguments: required flag --version not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --service-id flag\",\n\t\t\tArgs:      \"--name foobar --version 3\",\n\t\t\tWantError: \"error reading service: no service ID found\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate missing --autoclone flag with 'active' service\",\n\t\t\tAPI: mock.API{\n\t\t\t\tListVersionsFn: testutil.ListVersions,\n\t\t\t},\n\t\t\tArgs:      \"--name foobar --service-id 123 --version 1\",\n\t\t\tWantError: \"service version 1 is active\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate missing --autoclone flag with 'locked' service\",\n\t\t\tAPI: mock.API{\n\t\t\t\tListVersionsFn: testutil.ListVersions,\n\t\t\t},\n\t\t\tArgs:      \"--name foobar --service-id 123 --version 2\",\n\t\t\tWantError: \"service version 2 is locked\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate Update${CLI_API} API error\",\n\t\t\tAPI: mock.API{\n\t\t\t\tListVersionsFn: testutil.ListVersions,\n\t\t\t\tUpdate${CLI_API}Fn: func(i *fastly.Update${CLI_API}Input) (*fastly.${CLI_API}, error) {\n\t\t\t\t\treturn nil, testutil.Err\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:      \"--name foobar --service-id 123 --version 3\",\n\t\t\tWantError: testutil.Err.Error(),\n\t\t},\n\t\t{\n\t\t\tName: \"validate Update${CLI_API} API success with --new-name\",\n\t\t\tAPI: mock.API{\n\t\t\t\tListVersionsFn: testutil.ListVersions,\n\t\t\t\tUpdate${CLI_API}Fn: func(i *fastly.Update${CLI_API}Input) (*fastly.${CLI_API}, error) {\n\t\t\t\t\treturn &fastly.${CLI_API}{\n\t\t\t\t\t\tName:           *i.NewName,\n\t\t\t\t\t\tServiceID:      i.ServiceID,\n\t\t\t\t\t\tServiceVersion: i.ServiceVersion,\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:       \"--name foobar --new-name beepboop --service-id 123 --version 3\",\n\t\t\tWantOutput: \"Updated <...> 'beepboop' (previously: 'foobar', service: 123, version: 3)\",\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{baseCommand, \"update\"}, scenarios)\n}\n\nfunc get${CLI_API}(i *fastly.Get${CLI_API}Input) (*fastly.${CLI_API}, error) {\n\tt := testutil.Date\n\n\treturn &fastly.${CLI_API}{\n\t\tServiceID: i.ServiceID,\n\n\t\tCreatedAt: &t,\n\t\tDeletedAt: &t,\n\t\tUpdatedAt: &t,\n\t}, nil\n}\n\nfunc list${CLI_API}s(i *fastly.List${CLI_API}sInput) ([]*fastly.${CLI_API}, error) {\n\tt := testutil.Date\n\tvs := []*fastly.${CLI_API}{\n\t\t{\n\t\t\tServiceID: i.ServiceID,\n\n\t\t\tCreatedAt: &t,\n\t\t\tDeletedAt: &t,\n\t\t\tUpdatedAt: &t,\n\t\t},\n\t\t{\n\t\t\tServiceID: i.ServiceID,\n\n\t\t\tCreatedAt: &t,\n\t\t\tDeletedAt: &t,\n\t\t\tUpdatedAt: &t,\n\t\t},\n\t}\n\treturn vs, nil\n}\n"
  },
  {
    "path": ".tmpl/update.go",
    "content": "package ${CLI_PACKAGE}\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/manifest\"\n\t\"github.com/fastly/cli/pkg/config\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/text\"\n\t\"github.com/fastly/go-fastly/v4/fastly\"\n)\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, globals *config.Data, data manifest.Data) *UpdateCommand {\n\tvar c UpdateCommand\n\tc.CmdClause = parent.Command(\"update\", \"<...>\")\n\tc.Globals = globals\n\tc.manifest = data\n\n\t// Required flags\n\t// c.CmdClause.Flag(\"name\", \"<...>\").Required().StringVar(&c.name)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional flags\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.autoClone.Set,\n\t\tDst:    &c.autoClone.Value,\n\t})\n\tc.CmdClause.Flag(\"new-name\", \"<...>\").Action(c.newName.Set).StringVar(&c.newName.Value)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &c.manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\n\treturn &c\n}\n\n// UpdateCommand calls the Fastly API to update an appropriate resource.\ntype UpdateCommand struct {\n\targparser.Base\n\n\tautoClone      argparser.OptionalAutoClone\n\tmanifest manifest.Data\n\tname           string\n\tnewName        argparser.OptionalString\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *UpdateCommand) Exec(in io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(false),\n\t\tLocked:             optional.Of(false),\n\t\tAutoCloneFlag:      c.autoClone,\n\t\tClient:             c.Globals.Client,\n\t\tManifest:           c.manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flag.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]interface{}{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": errors.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tinput, err := c.constructInput(serviceID, serviceVersion.Number)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tr, err := c.Globals.Client.Update${CLI_API}(input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]interface{}{\n\t\t\t\"Service ID\": serviceID,\n\t\t\t\"Service Version\": serviceVersion.Number,\n\t\t})\n\t\treturn err\n\t}\n\n\tif input.NewName != nil && *input.NewName != \"\" {\n\t\ttext.Success(out, \"Updated <...> '%s' (previously: '%s', service: %s, version: %d)\", r.Name, input.Name, r.ServiceID, r.ServiceVersion)\n\t} else {\n\t\ttext.Success(out, \"Updated <...> '%s' (service: %s, version: %d)\", r.Name, r.ServiceID, r.ServiceVersion)\n\t}\n\treturn nil\n}\n\n// constructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *UpdateCommand) constructInput(serviceID string, serviceVersion int) (*fastly.Update${CLI_API}Input, error) {\n\tvar input fastly.Update${CLI_API}Input\n\n\tinput.Name = c.name\n\tinput.ServiceID = serviceID\n\tinput.ServiceVersion = serviceVersion\n\n\tif !c.newName.WasSet && !c.content.WasSet {\n\t\treturn nil, fmt.Errorf(\"error parsing arguments: must provide either --new-name or --content to update the <...>\")\n\t}\n\tif c.newName.WasSet {\n\t\tinput.NewName = fastly.String(c.newName.Value)\n\t}\n\n\treturn &input, nil\n\n}\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# CHANGELOG\n\n## [Unreleased]\n\n### Breaking:\n\n### Bug Fixes:\n\n### Enhancements:\n\n### Dependencies:\n- build(deps): `github.com/bodgit/sevenzip` from 1.6.1 to 1.6.2 ([#1795](https://github.com/fastly/cli/pull/1795))\n- build(deps): `github.com/minio/minlz` from 1.0.1 to 1.1.1 ([#1795](https://github.com/fastly/cli/pull/1795))\n- build(deps): `github.com/nwaples/rardecode/v2` from 2.2.0 to 2.2.2 ([#1795](https://github.com/fastly/cli/pull/1795))\n- build(deps): `go4.org` from 0.0.0-20230225012048-214862532bf5 to 0.0.0-20260112195520-a5071408f32f ([#1795](https://github.com/fastly/cli/pull/1795))\n- build(deps): `golang.org/x/net` from 0.53.0 to 0.54.0 ([#1795](https://github.com/fastly/cli/pull/1795))\n\n## [v15.1.0](https://github.com/fastly/cli/releases/tag/v15.1.0) (2026-05-13)\n\n### Bug Fixes:\n\n- fix(auth): honor deprecated `--profile`/`-o` when resolving the API token; an unknown profile name is now a hard error instead of a silent fallback to the default token ([#1792](https://github.com/fastly/cli/pull/1792))\n- fix(text): send deprecation warnings to stderr instead of stdout ([#1782](https://github.com/fastly/cli/pull/1782))\n\n### Enhancements:\n\n- feat(compute): add file field support for setup.kv_stores bulk import ([#1784](https://github.com/fastly/cli/pull/1784))\n- feat(compute): add support for cpp for compute ([#1773](https://github.com/fastly/cli/pull/1773))\n\n### Dependencies:\n\n- refactor(deps): migrate from `mholt/archiver/v3` to `mholt/archives` v0.1.5 ([#1787](https://github.com/fastly/cli/pull/1787))\n- build(deps): `golang.org/x/sys` from 0.43.0 to 0.44.0 ([#1785](https://github.com/fastly/cli/pull/1785))\n- build(deps): `golang.org/x/term` from 0.42.0 to 0.43.0 ([#1785](https://github.com/fastly/cli/pull/1785))\n- build(deps): `golang.org/x/crypto` from 0.50.0 to 0.51.0 ([#1785](https://github.com/fastly/cli/pull/1785))\n- build(deps): `golang.org/x/mod` from 0.35.0 to 0.36.0 ([#1785](https://github.com/fastly/cli/pull/1785))\n- build(deps): `golang.org/x/text` from 0.36.0 to 0.37.0 ([#1785](https://github.com/fastly/cli/pull/1785))\n\n## [v15.0.0](https://github.com/fastly/cli/releases/tag/v15.0.0) (2026-05-08)\n\n### Breaking:\n\n- breaking(ngwaf/workspace): change flag name to match API spec ([#1768](https://github.com/fastly/cli/pull/1768]))\n\n### Bug Fixes:\n\n- fix(compute/deploy): remove compute trial activation code because trials no longer exist ([#1730](https://github.com/fastly/cli/pull/1730))\n- fix(auth): SSO token expiration status now reflects the actual API token lifetime (~12 hours) instead of the internal JWT refresh token (~30 minutes), preventing spurious warnings and premature re-authentication [#1728](https://github.com/fastly/cli/pull/1728)\n- fix(argparser): skip ListVersions API call for numeric versions [#1774](https://github.com/fastly/cli/pull/1774)\n\n### Enhancements:\n\n- feat(service/backend): add support for the `max_use` and `max_lifetime` parameters ([#1779](https://github.com/fastly/cli/pull/1779))\n\n### Dependencies:\n\n- build(deps): `golang.org/x/term` from 0.41.0 to 0.42.0 ([#1726](https://github.com/fastly/cli/pull/1726))\n- build(deps): `golang.org/x/crypto` from 0.49.0 to 0.50.0 ([#1726](https://github.com/fastly/cli/pull/1726))\n- build(deps): `golang.org/x/mod` from 0.34.0 to 0.35.0 ([#1726](https://github.com/fastly/cli/pull/1726))\n- build(deps): `golang.org/x/net` from 0.52.0 to 0.53.0 ([#1726](https://github.com/fastly/cli/pull/1726))\n- build(deps): `golang.org/x/text` from 0.35.0 to 0.36.0 ([#1726](https://github.com/fastly/cli/pull/1726))\n- build(deps): `acifani/setup-tinygo` from 2 to 3 ([#1729](https://github.com/fastly/cli/pull/1729))\n- build(deps): `github.com/mattn/go-isatty` from 0.0.21 to 0.0.22 ([#1735](https://github.com/fastly/cli/pull/1735))\n- build(deps): `github.com/hashicorp/cap` from 0.12.0 to 0.13.0 ([#1771](https://github.com/fastly/cli/pull/1771))\n- build(deps): `github.com/Masterminds/semver/v3` from 3.4.0 to 3.5.0 ([#1775](https://github.com/fastly/cli/pull/1775))\n- build(deps): `github.com/fsnotify/fsnotify` from 1.9.0 to 1.10.1 ([#1775](https://github.com/fastly/cli/pull/1775))\n- build(deps): `github.com/klauspost/compress` from 1.18.5 to 1.18.6 ([#1775](https://github.com/fastly/cli/pull/1775))\n- build(deps): `github.com/fastly/go-fastly/v15` from 14.2.0 to 15.0.1([#1778](https://github.com/fastly/terraform-provider-fastly/pull/1778))\n\n## [v14.3.1](https://github.com/fastly/cli/releases/tag/v14.3.1) (2026-04-13)\n\n### Bug Fixes:\n\n- fix(publish_release): add back perms for publishing to npm [#1724](https://github.com/fastly/cli/pull/1724)\n\n## [v14.3.0](https://github.com/fastly/cli/releases/tag/v14.3.0) (2026-04-10)\n\n### Bug Fixes:\n\n- fix(vcl/condition): `--comment` flag in `condition update` now correctly sets the comment instead of overwriting the statement [#1714](https://github.com/fastly/cli/pull/1714)\n- fix(manifest): `env_file` parsing no longer rejects values containing `=` characters (e.g. `KEY=val=ue`) [#1715](https://github.com/fastly/cli/pull/1715)\n\n### Enhancements:\n\n- feat(auth): add `auth revoke` subcommand for revoking API tokens via `--current`, `--name`, `--token-value`, `--id`, or `--file` (bulk) [#1717](https://github.com/fastly/cli/pull/1717)\n- feat(service/logging/debug): add support for logging endpoint error streaming via the `service logging debug` subcommand [#1721](https://github.com/fastly/cli/pull/1721)\n- feat(stats): accept `--json` / `-j` as an alias for `--format=json` on all stats and help subcommands, matching the flag style used by the rest of the CLI [#1719](https://github.com/fastly/cli/pull/1719)\n\n### Dependencies:\n\n- build(deps): `github.com/andybalholm/brotli` from 1.2.0 to 1.2.1 ([#1716](https://github.com/fastly/cli/pull/1716))\n- build(deps): `github.com/go-jose/go-jose/v3` from 3.0.4 to 3.0.5 ([#1716](https://github.com/fastly/cli/pull/1716))\n- build(deps): `github.com/mattn/go-runewidth` from 0.0.21 to 0.0.22 ([#1716](https://github.com/fastly/cli/pull/1716))\n- build(deps): `github.com/mattn/go-isatty` from 0.0.20 to 0.0.21 ([#1720](https://github.com/fastly/cli/pull/1720))\n- build(deps): `golang.org/x/sys` from 0.42.0 to 0.43.0 ([#1720](https://github.com/fastly/cli/pull/1720))\n- build(deps): `github.com/coreos/go-oidc/v3` from 3.17.0 to 3.18.0 ([#1720](https://github.com/fastly/cli/pull/1720))\n- build(deps): `github.com/mattn/go-runewidth` from 0.0.22 to 0.0.23 ([#1720](https://github.com/fastly/cli/pull/1720))\n- build(deps): `github.com/fastly/go-fastly/v14` from 13.1.2 to 14.2.0 ([#1722](https://github.com/fastly/cli/pull/1722))\n\n## [v14.2.0](https://github.com/fastly/cli/releases/tag/v14.2.0) (2026-03-24)\n\n### Bug Fixes:\n\n- fix(auth): `fastly profile`, `fastly sso` and `fastly auth-token` commands now correctly respect the `--quiet` flag [#1710](https://github.com/fastly/cli/pull/1710)\n\n### Enhancements:\n\n- feat(vcl/snippet): add support for the '--content' flag, allowing for the raw output of VCL.  [#1706](https://github.com/fastly/cli/pull/1706)\n\n### Dependencies:\n\n- build(deps): `github.com/fatih/color` from 1.18.0 to 1.19.0 ([#1707](https://github.com/fastly/cli/pull/1707))\n- build(deps): `github.com/klauspost/compress` from 1.18.4 to 1.18.5 ([#1707](https://github.com/fastly/cli/pull/1707))\n\n## [v14.1.1](https://github.com/fastly/cli/releases/tag/v14.1.1) (2026-03-18)\n\n### Bug Fixes:\n\n- fix(compute): `compute pack`, `compute validate`, and `install` no longer require authentication. [#1701](https://github.com/fastly/cli/pull/1701)\n\n## [v14.1.0](https://github.com/fastly/cli/releases/tag/v14.1.0) (2026-03-17)\n\n### Bug Fixes:\n\n- fix(stats): `stats historical` now returns write errors instead of silently swallowing them [#1678](https://github.com/fastly/cli/pull/1678)\n\n### Deprecations:\n\n- deprecated(auth): `fastly profile`, `fastly sso`, and `fastly auth-token` command trees are deprecated and will be removed in a future release. Use `fastly auth` subcommands instead. [#1676](https://github.com/fastly/cli/pull/1676)\n- deprecated(auth): `--profile` and `--enable-sso` global flags are deprecated. Use `--token <name>` to select a stored auth token by name, or `fastly auth login --sso --token <name>` for SSO. [#1676](https://github.com/fastly/cli/pull/1676)\n\n### Enhancements:\n\n- feat(auth): add `auth token` subcommand to output the active API token for use in shell substitutions (e.g. `$(fastly auth token)`).\n- feat(auth): `auth login --sso` now requires `--token <name>` to explicitly name the stored token. This prevents accidentally overwriting tokens in multi-user SSO workflows. [#1676](https://github.com/fastly/cli/pull/1676)\n- feat(auth): add `FASTLY_DISABLE_AUTH_COMMAND` env var to hide the `fastly auth` command tree from help, completions, and invocation. [#1676](https://github.com/fastly/cli/pull/1676)\n- feat(auth): when `FASTLY_DISABLE_AUTH_COMMAND` is set, the `--token`/`-t` global flag is also disabled. Use `FASTLY_API_TOKEN` or stored config tokens instead. [#1676](https://github.com/fastly/cli/pull/1676)\n- feat(stats): add `--field` flag to `stats historical` to filter to a single stats field. [#1678](https://github.com/fastly/cli/pull/1678)\n- feat(stats): add `stats aggregate` subcommand for cross-service aggregated stats. [#1678](https://github.com/fastly/cli/pull/1678)\n- feat(stats): add `stats usage` subcommand for bandwidth/request usage, with `--by-service` breakdown. [#1678](https://github.com/fastly/cli/pull/1678)\n- feat(stats): add `stats domain-inspector` subcommand for domain-level metrics. [#1678](https://github.com/fastly/cli/pull/1678)\n- feat(stats): add `stats origin-inspector` subcommand for origin-level metrics. [#1678](https://github.com/fastly/cli/pull/1678)\n- feat(apisecurity/discoveredoperations): add support for 'list' and 'update' support for 'API discovery'. [#1689](https://github.com/fastly/cli/pull/1689)\n- feat(apisecurity/operations): add CRUD support for 'API Inventory' operations. [#1689](https://github.com/fastly/cli/pull/1689)\n- feat(apisecurity/tags): add API Security Operations tag support ([#1688](https://github.com/fastly/cli/pull/1688))\n- feat(service/version): add support for service validation. [#1695](https://github.com/fastly/cli/pull/1695)\n- feat(compute/build): Block version 1.93.0 of Rust to avoid a wasm32-wasip2 bug. ([#1653](https://github.com/fastly/cli/pull/1653))\n- feat(service/vcl): escape control characters when displaying VCL content for cleaner terminal output ([#1637](https://github.com/fastly/cli/pull/1637))\n\n### Dependencies:\n\n- build(deps): `golang.org/x/net` from 0.50.0 to 0.51.0 ([#1674](https://github.com/fastly/cli/pull/1674))\n- build(deps): `actions/upload-artifact` from 6 to 7 ([#1675](https://github.com/fastly/cli/pull/1675))\n- build(deps): `actions/download-artifact` from 7 to 8 ([#1675](https://github.com/fastly/cli/pull/1675))\n- build(deps): `golang.org/x/sys` from 0.41.0 to 0.42.0 ([#1679](https://github.com/fastly/cli/pull/1679))\n- build(deps): `github.com/mattn/go-runewidth` from 0.0.20 to 0.0.21 ([#1679](https://github.com/fastly/cli/pull/1679))\n- build(deps): `github.com/pierrec/lz4/v4` from 4.1.25 to 4.1.26 ([#1679](https://github.com/fastly/cli/pull/1679))\n- build(deps): `golang.org/x/oauth2` from 0.35.0 to 0.36.0 ([#1679](https://github.com/fastly/cli/pull/1679))\n- build(deps): `golang.org/x/sync` from 0.19.0 to 0.20.0 ([#1679](https://github.com/fastly/cli/pull/1679))\n- build(deps): `github.com/fastly/go-fastly/v13` from 13.0.0 to 13.0.1 ([#1679](https://github.com/fastly/cli/pull/1679))\n- build(deps): `golang.org/x/term` from 0.40.0 to 0.41.0 ([#1687](https://github.com/fastly/cli/pull/1687))\n- build(deps): `golang.org/x/mod` from 0.33.0 to 0.34.0 ([#1687](https://github.com/fastly/cli/pull/1687))\n- build(deps): `golang.org/x/text` from 0.34.0 to 0.35.0 ([#1687](https://github.com/fastly/cli/pull/1687))\n- build(deps): `github.com/fastly/go-fastly/v13` from 13.0.1 to 13.1.0 ([#1687](https://github.com/fastly/cli/pull/1687))\n- build(deps): `golang.org/x/crypto` from 0.48.0 to 0.49.0 ([#1693](https://github.com/fastly/cli/pull/1693))\n- build(deps): `golang.org/x/net` from 0.51.0 to 0.52.0 ([#1693](https://github.com/fastly/cli/pull/1693))\n- build(deps): `github.com/fastly/go-fastly/v13` from 13.1.0 to 13.1.1 ([#1693](https://github.com/fastly/cli/pull/1693))\n- build(deps): `github.com/fastly/go-fastly/v13` from 13.1.1 to 13.1.2 ([#1696](https://github.com/fastly/cli/pull/1696))\n- build(deps): `actions/create-github-app-token` from 2 to 3 ([#1692](https://github.com/fastly/cli/pull/1692))\n\n## [v14.0.4](https://github.com/fastly/cli/releases/tag/v14.0.4) (2026-02-26)\n\n### Documentation:\n\n- fix(changelog): change code blocks to be all on one line [#1670](https://github.com/fastly/cli/pull/1670)\n\n## [v14.0.3](https://github.com/fastly/cli/releases/tag/v14.0.3) (2026-02-25)\n\n### Bug Fixes:\n\n- fix(npm): add contents write perms [#1667](https://github.com/fastly/cli/pull/1667)\n\n## [v14.0.2](https://github.com/fastly/cli/releases/tag/v14.0.2) (2026-02-25)\n\n### Bug Fixes:\n\n- fix(npm): add write perms [#1665](https://github.com/fastly/cli/pull/1665)\n\n## [v14.0.1](https://github.com/fastly/cli/releases/tag/v14.0.1) (2026-02-25)\n\n### Bug Fixes:\n\n- fix(npm): Include repository info in package.json of subpackages required for trusted publishing [#1663](https://github.com/fastly/cli/pull/1663)\n\n## [v14.0.0](https://github.com/fastly/cli/releases/tag/v14.0.0) (2026-02-25)\n\n## BREAKING CHANGES\n\nThis release of the Fastly CLI includes a significant reorganization\nof the commands which are used to manage the configuration of Fastly\nservices (both Delivery and Compute services). Specifically, each of\nthe command families listed below have been changed from \n`fastly <family> create/delete/describe/list/update` to \n`fastly service <family> create/delete/describe/list/update`. For nearly \nall of these command families, the previous commands are still available \nbut are not listed in the `fastly help` output. In addition, invocations \nof the previous commands will generate a deprecation message, which\nincludes the new command that should be used instead.\n\nThe `fastly domain` family of commands are the lone exception; those\ncommands exist in both the old and new forms, but the top-level\ncommands are used to manage 'versionless' domains (a new feature of\nthe Fastly platform, and those commands were previously named \n`fastly domain-v1 create/delete/describe/list/update`), while \nthe service-level commands are used to manage 'classic' domains. As a\nresult, you will need to update any scripts or workflows which used the\n`fastly domain create/delete/describe/list/update` commands to use the\n`fastly service domain create/delete/describe/list/update` commands\ninstead.\n\nThe command families which have been reorganized and are available in\nboth the old and new forms are:\n\n  * acl\n  * aclentry\n  * alert\n  * backend\n  * dictionary\n  * dictionary-entry\n  * healthcheck\n  * imageoptimizerdefaults\n  * logging\n  * purge\n  * rate-limit\n  * resource-link\n  * service-auth\n  * service-version\n  * vcl\n\n### Breaking:\n\n- breaking(domain) - service-version oriented `domain` commands have been moved under the `service domain` command. Versionless `domain-v1` commands have been moved to the `domain` command ([#1615](https://github.com/fastly/cli/pull/1615))\n### Deprecations:\n\n- deprecated(auth): `fastly profile`, `fastly sso`, and `fastly auth-token` command trees are deprecated and will be removed in a future release. Use `fastly auth` subcommands instead.\n- deprecated(auth): `--profile` and `--enable-sso` global flags are deprecated. Use `--token <name>` to select a stored auth token by name, or `fastly auth login --sso --token <name>` for SSO.\n\n### Enhancements:\n\n- feat(auth): `auth login --sso` now requires `--token <name>` to explicitly name the stored token. This prevents accidentally overwriting tokens in multi-user SSO workflows.\n- feat(auth): add `FASTLY_DISABLE_AUTH_COMMAND` env var to hide the `fastly auth` command tree from help, completions, and invocation.\n- feat(auth): when `FASTLY_DISABLE_AUTH_COMMAND` is set, the `--token`/`-t` global flag is also disabled. Use `FASTLY_API_TOKEN` or stored config tokens instead.\n- feat(ngwaf/rules): Upgrade go-fastly to v13.0.0 and allow ngwaf rules to accept multival conditions ([#1655](https://github.com/fastly/cli/pull/1655))\n- feat(rust): Allow testing with prerelease Rust versions ([#1604](https://github.com/fastly/cli/pull/1604))\n- feat(compute/hashfiles): remove hashsum subcommand ([#1608](https://github.com/fastly/cli/pull/1608))\n- feat(ngwaf/rules): add support for CRUD operations for NGWAF rules ([#1605](https://github.com/fastly/cli/pull/1605))\n- feat(compute/deploy): added the `--no-default-domain` flag to allow for the skipping of automatic domain creation when deploying a Compute service([#1610](https://github.com/fastly/cli/pull/1610))\n- refactor(argparser/flags.go): add flag conversion utilities for converting string flags to bools and checking ascending and descending flags ([#1611](https://github.com/fastly/cli/pull/1611))\n- feat(service/purge): Add 'service purge' command as replacement for 'purge', with an unlisted and deprecated alias of 'purge'. ([#1612](https://github.com/fastly/cli/pull/1612))\n- feat(service/version): Add 'service version ...' commands as replacements for 'service-version ...', with unlisted and deprecated aliases of 'service-version ...'. ([#1614](https://github.com/fastly/cli/pull/1614))\n- feat(service/vcl): moved the `vcl` command under the `service` command, with an unlisted and deprecated alias of `vcl` ([#1616](https://github.com/fastly/cli/pull/1616))\n- feat(service/healthcheck): moved the `healthcheck` command under the `service` command, with an unlisted and deprecated alias of `healthcheck` ([#1619](https://github.com/fastly/cli/pull/1619))\n- feat(service/backend): moved the `backend` command under the `service` command, with an unlisted and deprecated alias of `backend` ([#1621](https://github.com/fastly/cli/pull/1621))\n- feat(service/acl): moved the `acl` and `aclentry` commands under the `service` command, with unlisted and deprecated aliases of `acl` and `aclentry` ([#1621](https://github.com/fastly/cli/pull/1624))\n- feat(version): If the latest version is at least one major version higher than the current version, provide links to the release notes for the major version(s) so the user can review them before upgrading. ([#1623](https://github.com/fastly/cli/pull/1623))\n- feat(service/imageoptimizerdefaults): moved the `imageoptimizerdefaults` commands under the `service` command, with an unlisted and deprecated alias of `imageoptimizerdefaults` ([#1627](https://github.com/fastly/cli/pull/1627))\n- feat(service/alert): moved the `alerts` command to the `service alert` command, with an unlisted and deprecated alias of `alerts` ([#1616](https://github.com/fastly/cli/pull/1626))\n- feat(service/dictionary-entry): moved the `dictionary-entry` commands under the `service` command, with an unlisted and deprecated alias of `dictionary-entry` ([#1628](https://github.com/fastly/cli/pull/1628))\n- feat(service/dictionary): moved the `dictionary` command under the `service` command, with an unlisted and deprecated alias of `dictionary` ([#1621](https://github.com/fastly/cli/pull/1630))\n- feat(service/ratelimit): moved the `rate-limit` commands under the `service` command, with an unlisted and deprecated alias of `rate-limit` ([#1632](https://github.com/fastly/cli/pull/1632))\n- feat(compute/build): Remove Rust version restriction, allowing 1.93.0 and later versions to be used. ([#1633](https://github.com/fastly/cli/pull/1633))\n- feat(service/resourcelink): moved the `resource-link` commands under the `service` command, with an unlisted and deprecated alias of `resource-link` ([#1635](https://github.com/fastly/cli/pull/1635))\n- feat(service/logging): moved the `logging` commands under the `service` command, with an unlisted and deprecated alias of `logging` ([#1642](https://github.com/fastly/cli/pull/1642))\n- feat(service/auth): moved the `service-auth` commands under the `service` command and renamed to `auth`, with an unlisted and deprecated alias of `service-auth` ([#1643](https://github.com/fastly/cli/pull/1643))\n- feat(compute/build): improved error messaging for JavaScript builds with pre-flight toolchain verification including Bun runtime support ([#1640](https://github.com/fastly/cli/pull/1640))\n\n### Bug fixes:\n\n- fix(docker): Use base image toolchain instead of reinstalling stable, which could pull in an unvalidated Rust version.\n- fix(compute/serve): ensure hostname has a port number when building pushpin routes ([#1631](https://github.com/fastly/cli/pull/1631))\n- fix(manifest): Correct setup.Defined to include checks for ObjectStores and SecretStores ([#1639](https://github.com/fastly/cli/pull/1639))\n\n### Dependencies:\n\n- build(deps): `golang` from 1.24 to 1.25 ([#1651](https://github.com/fastly/cli/pull/1651))\n- build(deps): `actions/upload-artifact` from 5 to 6 ([#1603](https://github.com/fastly/cli/pull/1603))\n- build(deps): `actions/download-artifact` from 6 to 7 ([#1603](https://github.com/fastly/cli/pull/1603))\n- build(deps): `golang.org/x/term` from 0.37.0 to 0.38.0 ([#1602](https://github.com/fastly/cli/pull/1602))\n- build(deps): `golang.org/x/crypto` from 0.45.0 to 0.46.0 ([#1602](https://github.com/fastly/cli/pull/1602))\n- build(deps): `golang.org/x/mod` from 0.30.0 to 0.31.0 ([#1602](https://github.com/fastly/cli/pull/1602))\n- build(deps): `golang.org/x/net` from 0.47.0 to 0.48.0 ([#1602](https://github.com/fastly/cli/pull/1602))\n- build(deps): `golang.org/x/text` from 0.31.0 to 0.32.0 ([#1602](https://github.com/fastly/cli/pull/1602))\n- build(deps): `github.com/pierrec/lz4/v4` from 4.1.22 to 4.1.23 ([#1606](https://github.com/fastly/cli/pull/1606))\n- build(deps): `github.com/google/go-querystring` from 1.1.0 to 1.2.0 ([#1607](https://github.com/fastly/cli/pull/1607))\n- build(deps): `golang.org/x/sys` from 0.39.0 to 0.40.0 ([#1613](https://github.com/fastly/cli/pull/1613))\n- build(deps): `golang.org/x/term` from 0.38.0 to 0.39.0 ([#1613](https://github.com/fastly/cli/pull/1613))\n- build(deps): `golang.org/x/crypto` from 0.46.0 to 0.47.0 ([#1613](https://github.com/fastly/cli/pull/1613))\n- build(deps): `golang.org/x/mod` from 0.31.0 to 0.32.0 ([#1613](https://github.com/fastly/cli/pull/1613))\n- build(deps): `golang.org/x/net` from 0.48.0 to 0.49.0 ([#1613](https://github.com/fastly/cli/pull/1613))\n- build(deps): `golang.org/x/text` from 0.32.0 to 0.33.0 ([#1613](https://github.com/fastly/cli/pull/1613))\n- build(deps): `github.com/fastly/go-fastly/v13` from 12.1.0 to 12.1.1 ([#1613](https://github.com/fastly/cli/pull/1613))\n- build(deps): `github.com/clipperhouse/uax29/v2` from 2.3.0 to 2.3.1 ([#1625](https://github.com/fastly/cli/pull/1625))\n- build(deps): `github.com/klauspost/compress` from 1.18.2 to 1.18.3 ([#1625](https://github.com/fastly/cli/pull/1625))\n- build(deps): `github.com/pierrec/lz4/v4` from 4.1.23 to 4.1.25 ([#1625](https://github.com/fastly/cli/pull/1625))\n- build(deps): `github.com/clipperhouse/uax29/v2` from 2.3.1 to 2.4.0 ([#1634](https://github.com/fastly/cli/pull/1634))\n- build(deps): `github.com/clipperhouse/uax29/v2` from 2.4.0 to 2.5.0 ([#1647](https://github.com/fastly/cli/pull/1647))\n- build(deps): `golang.org/x/sys` from 0.40.0 to 0.41.0 ([#1652](https://github.com/fastly/cli/pull/1652))\n- build(deps): `golang.org/x/term` from 0.39.0 to 0.40.0 ([#1652](https://github.com/fastly/cli/pull/1652))\n- build(deps): `golang.org/x/crypto` from 0.47.0 to 0.48.0 ([#1652](https://github.com/fastly/cli/pull/1652))\n- build(deps): `golang.org/x/mod` from 0.32.0 to 0.33.0 ([#1652](https://github.com/fastly/cli/pull/1652))\n- build(deps): `github.com/clipperhouse/uax29/v2` from 2.5.0 to 2.6.0 ([#1652](https://github.com/fastly/cli/pull/1652))\n- build(deps): `github.com/klauspost/compress` from 1.18.3 to 1.18.4 ([#1652](https://github.com/fastly/cli/pull/1652))\n- build(deps): `golang.org/x/net` from 0.49.0 to 0.50.0 ([#1652](https://github.com/fastly/cli/pull/1652))\n- build(deps): `golang.org/x/oauth2` from 0.34.0 to 0.35.0 ([#1652](https://github.com/fastly/cli/pull/1652))\n- build(deps): `golang.org/x/text` from 0.33.0 to 0.34.0 ([#1652](https://github.com/fastly/cli/pull/1652))\n- build(deps): `github.com/clipperhouse/uax29/v2` from 2.6.0 to 2.7.0 ([#1657](https://github.com/fastly/cli/pull/1657))\n- build(deps): `golang.org/x/text` from 0.33.0 to 0.34.0 ([#1652](https://github.com/fastly/cli/pull/1652))\n- build(deps): `github.com/mattn/go-runewidth` from 0.0.19 to 0.0.20 ([#1659](https://github.com/fastly/cli/pull/1659))\n- build(deps): `goreleaser/goreleaser-action` from 6 to 7 ([#1660](https://github.com/fastly/cli/pull/1660))\n\n## [v13.3.0](https://github.com/fastly/cli/releases/tag/v13.3.0) (2025-12-11)\n\n### Enhancements:\n\n- feat(toml/rust): add support for Rust 1.9.2 ([#1599](https://github.com/fastly/cli/pull/1599))\n\n## [v13.2.0](https://github.com/fastly/cli/releases/tag/v13.2.0) (2025-12-10)\n\n### Enhancements:\n\n- feat(commands/ngwaf/workspaces): add support for update operation for NGWAF workspaces ([#1578](https://github.com/fastly/cli/pull/1578))\n- feat(commands/ngwaf/lists): add support for CRUD operations for NGWAF Lists at account and workspace levels ([#1582](https://github.com/fastly/cli/pull/1582))\n- feat(commands/ngwaf/workspaces/alerts): add support for operations for NGWAF alerts ([#1589](https://github.com/fastly/cli/pull/1589))\n- feat(commands/ngwaf/customsignals): add support for CRUD operations for NGWAF Custom Signals ([#1592](https://github.com/fastly/cli/pull/1592))\n- feat(commands/ngwaf/threshold): add support for CRUD operations for NGWAF Thresholds ([#1595](https://github.com/fastly/cli/pull/1595))\n\n### Bug fixes:\n\n- fix(commands/ngwaf/virtualpatch): ensured a check was in place for the 'update' command that disallowed the --json and --verbose flag to be ran at the same time. ([#1596](https://github.com/fastly/cli/pull/1596))\n- fix(commands/ngwaf/redaction): ensured a check was in place for the 'create' and 'update' commands that disallowed the --json and --verbose flag to be ran at the same time. ([#1596](https://github.com/fastly/cli/pull/1596))\n\n### Dependencies:\n\n- build(deps): `golang.org/x/crypto` from 0.43.0 to 0.45.0 ([#1584](https://github.com/fastly/cli/pull/1584))\n- build(deps): `actions/checkout` from 5 to 6 ([#1587](https://github.com/fastly/cli/pull/1587))\n- build(deps): `golang.org/x/mod` from 0.29.0 to 0.30.0 ([#1588](https://github.com/fastly/cli/pull/1588))\n- build(deps): `github.com/coreos/go-oidc/v3` from 3.16.0 to 3.17.0 ([#1588](https://github.com/fastly/cli/pull/1588))\n- build(deps): `github.com/klauspost/compress` from 1.18.1 to 1.18.2 ([#1593](https://github.com/fastly/cli/pull/1593))\n- build(deps): `golang.org/x/sys` from 0.38.0 to 0.39.0 ([#1594](https://github.com/fastly/cli/pull/1594))\n- build(deps): `github.com/hashicorp/cap` from 0.11.0 to 0.12.0 ([#1594](https://github.com/fastly/cli/pull/1594))\n- build(deps): `golang.org/x/oauth2` from 0.33.0 to 0.34.0 ([#1594](https://github.com/fastly/cli/pull/1594))\n- build(deps): `golang.org/x/sync` from 0.18.0 to 0.19.0 ([#1594](https://github.com/fastly/cli/pull/1594))\n\n## [v13.1.0](https://github.com/fastly/cli/releases/tag/v13.1.0) (2025-11-12)\n\n### Enhancements:\n\n- feat(service-version): Add JSON support to service-version clone command ([#1550](https://github.com/fastly/cli/pull/1550))\n- feat(compute/build): Allow usage of Rust 1.91.1 and later patch releases ([#1576](https://github.com/fastly/cli/pull/1576))\n- feat(commands/ngwaf/workspaces): add support for CRUD operations for NGWAF workspaces ([#1570](https://github.com/fastly/cli/pull/1570))\n- feat(commands/ngwaf/virtualpatch): add support for CRUD operations for NGWAF virtual patches ([#1579](https://github.com/fastly/cli/pull/1579))\n- feat(commands/ngwaf/redaction): add support for CRUD operations for NGWAF redactions ([#1581](https://github.com/fastly/cli/pull/1581))\n\n### Dependencies:\n\n- build(deps): `golangci/golangci-lint-action` from 8 to 9 ([#1575](https://github.com/fastly/cli/pull/1575))\n- build(deps): `golang.org/x/sys` from 0.37.0 to 0.38.0 ([#1574](https://github.com/fastly/cli/pull/1574))\n- build(deps): `golang.org/x/oauth2` from 0.32.0 to 0.33.0 ([#1574](https://github.com/fastly/cli/pull/1574))\n- build(deps): `golang.org/x/sync` from 0.17.0 to 0.18.0 ([#1574](https://github.com/fastly/cli/pull/1574))\n\n## [v13.0.0](https://github.com/fastly/cli/releases/tag/v13.0.0) (2025-10-30)\n\n### Breaking:\n\n- breaking(tls-custom): correct 'tls-custom activation enable' command parameters to reflect expected input from API ([#1562](https://github.com/fastly/cli/pull/1562))\n- breaking(compute/build): Block version 1.91.0 of Rust as it produces broken WASM packages. ([#1571](https://github.com/fastly/cli/pull/1571))\n\n### Enhancements:\n\n- feat(compute/serve): set sig_iss and sig_key to allow client code to test Grip-Sig signing ([#1569](https://github.com/fastly/cli/pull/1569))\n- build(dockerfile-rust): add wasm tools to the rust docker container ([#1552](https://github.com/fastly/cli/pull/1552))\n- feat(env): add detection for workspace ID ([#1560](https://github.com/fastly/cli/pull/1560))\n\n### Bug fixes:\n\n- fix(compute): clarify fastly.toml error message when file not found ([#1556](https://github.com/fastly/cli/pull/1556))\n- fix(purge/key): ensures that single-key purges will work even if the key contains URL-unsafe characters ([#1566](https://github.com/fastly/cli/pull/1566))\n\n### Dependencies:\n\n- build(deps): `github.com/hashicorp/cap` from 0.10.0 to 0.11.0 ([#1546](https://github.com/fastly/cli/pull/1546))\n- build(deps): `github.com/coreos/go-oidc/v3` from 3.15.0 to 3.16.0 ([#1546](https://github.com/fastly/cli/pull/1546))\n- build(deps): `stefanzweifel/git-auto-commit-action` from 6 to 7 ([#1549](https://github.com/fastly/cli/pull/1549))\n- build(deps): `golang.org/x/sys` from 0.36.0 to 0.37.0 ([#1548](https://github.com/fastly/cli/pull/1548))\n- build(deps): `golang.org/x/term` from 0.35.0 to 0.36.0 ([#1548](https://github.com/fastly/cli/pull/1548))\n- build(deps): `golang.org/x/crypto` from 0.42.0 to 0.43.0 ([#1548](https://github.com/fastly/cli/pull/1548))\n- build(deps): `golang.org/x/mod` from 0.28.0 to 0.29.0 ([#1548](https://github.com/fastly/cli/pull/1548))\n- build(deps): `golang.org/x/net` from 0.44.0 to 0.45.0 ([#1548](https://github.com/fastly/cli/pull/1548))\n- build(deps): `golang.org/x/oauth2` from 0.31.0 to 0.32.0 ([#1548](https://github.com/fastly/cli/pull/1548))\n- build(deps): `golang.org/x/text` from 0.29.0 to 0.30.0 ([#1548](https://github.com/fastly/cli/pull/1548))\n- build(deps): `actions/setup-node` from 5 to 6 ([#1559](https://github.com/fastly/cli/pull/1559))\n- build(deps): `actions/download-artifact` from 4 to 5 ([#1559](https://github.com/fastly/cli/pull/1559))\n- build(deps): `github.com/klauspost/compress` from 1.18.0 to 1.18.1 ([#1558](https://github.com/fastly/cli/pull/1558))\n- build(deps): `golang.org/x/net` from 0.45.0 to 0.46.0 ([#1558](https://github.com/fastly/cli/pull/1558))\n- build(deps): `github.com/clipperhouse/uax29/v2` from 2.2.0 to 2.3.0 ([#1564](https://github.com/fastly/cli/pull/1564))\n- build(deps): `actions/upload-artifact` from 4 to 5 ([#1565](https://github.com/fastly/cli/pull/1565))\n- build(deps): `actions/download-artifact` from 5 to 6 ([#1565](https://github.com/fastly/cli/pull/1565))\n\n## [v12.1.0](https://github.com/fastly/cli/releases/tag/v12.1.0) (2025-09-30)\n\n### Breaking:\n\n### Enhancements:\n\n- feat(manifest): Enable loading Secret Store configuration through environment variables ([#1540](https://github.com/fastly/cli/pull/1540))\n- feat(products): Add enable/disable support for API Discovery ([#1543](https://github.com/fastly/cli/pull/1543))\n\n### Bug fixes:\n\n### Dependencies:\n\n- build(deps): `golang.org/x/net` from 0.43.0 to 0.44.0 ([#1538](https://github.com/fastly/cli/pull/1538))\n- build(deps): `github.com/fastly/go-fastly/v11` from 11.3.1 to 12.0.0 ([#1541](https://github.com/fastly/cli/pull/1541))\n- build(deps): `github.com/mattn/go-runewidth` from 0.0.16 to 0.0.19 ([#1542](https://github.com/fastly/cli/pull/1542))\n- build(deps): `github.com/fastly/go-fastly/v11` from 11.3.1 to 12.0.0 ([#1541](https://github.com/fastly/cli/pull/1541))\n\n## [v12.0.0](https://github.com/fastly/cli/releases/tag/v12.0.0) (2025-09-10)\n\n### Breaking:\n\n- breaking(kvstoreentry): The 'describe' command now returns only key attributes (ie: generation, metadata) instead of a given key's value ([#1529](https://github.com/fastly/cli/pull/1529))\n\n### Enhancements:\n\n- feat(logging): Add support for 'CompressionCodec' and 'GzipLevel' attribute to the HTTPS endpoint.\n- feat(kvstoreentry): Add support for the 'prefix' parameter for List operations ([#1526](https://github.com/fastly/cli/pull/1526))\n- feat(kvstoreentry): Add support for the add, append, prepend, metadata, if_generation_match, and background_fetch 'create' command operations ([#1529](https://github.com/fastly/cli/pull/1529))\n- feat(kvstoreentry): Add support for the if_generation_match and metadata 'describe' command operations ([#1529](https://github.com/fastly/cli/pull/1529))\n- feat(kvstoreentry): Add support for the if_generation_match and force 'delete' command operations ([#1529](https://github.com/fastly/cli/pull/1529))\n- feat(kvstoreentry): Add the 'get' command operation which obtains the value of the item ([#1529](https://github.com/fastly/cli/pull/1529))\n- feat(logging/https): Add support for 'Period' attribute. ([#1537](https://github.com/fastly/cli/pull/1537))\n\n### Bug fixes:\n\n- fix(manifest): Ensure pushpin section is persisted during manifest file update ([#1535](https://github.com/fastly/cli/pull/1535))\n\n### Dependencies:\n\n- build(deps): `github.com/ulikunitz/xz` from 0.5.12 to 0.5.13 ([#1524](https://github.com/fastly/cli/pull/1524))\n- build(deps): `github.com/stretchr/testify` from 1.10.0 to 1.11.0 ([#1527](https://github.com/fastly/cli/pull/1527))\n- build(deps): `github.com/ulikunitz/xz` from 0.5.13 to 0.5.14 ([#1528](https://github.com/fastly/cli/pull/1528))\n- build(deps): `github.com/stretchr/testify` from 1.11.0 to 1.11.1 ([#1530](https://github.com/fastly/cli/pull/1530))\n- build(deps): `github.com/ulikunitz/xz` from 0.5.14 to 0.5.15 ([#1530](https://github.com/fastly/cli/pull/1530))\n- build(deps): `actions/setup-node` from 4 to 5 ([#1534](https://github.com/fastly/cli/pull/1534))\n- build(deps): `actions/setup-go` from 5 to 6 ([#1534](https://github.com/fastly/cli/pull/1534))\n- build(deps): `golang.org/x/sys` from 0.35.0 to 0.36.0 ([#1533](https://github.com/fastly/cli/pull/1533))\n- build(deps): `golang.org/x/term` from 0.34.0 to 0.35.0 ([#1533](https://github.com/fastly/cli/pull/1533))\n- build(deps): `github.com/fastly/go-fastly/v11` from 11.3.0 to 11.3.1 ([#1533](https://github.com/fastly/cli/pull/1533))\n- build(deps): `golang.org/x/crypto` from 0.41.0 to 0.42.0 ([#1533](https://github.com/fastly/cli/pull/1533))\n- build(deps): `golang.org/x/mod` from 0.27.0 to 0.28.0 ([#1533](https://github.com/fastly/cli/pull/1533))\n- build(deps): `golang.org/x/oauth2` from 0.30.0 to 0.31.0 ([#1533](https://github.com/fastly/cli/pull/1533))\n- build(deps): `golang.org/x/sync` from 0.16.0 to 0.17.0 ([#1533](https://github.com/fastly/cli/pull/1533))\n- build(deps): `golang.org/x/text` from 0.28.0 to 0.29.0 ([#1533](https://github.com/fastly/cli/pull/1533))\n\n## [v11.5.0](https://github.com/fastly/cli/releases/tag/v11.5.0) (2025-08-20)\n\n### Enhancements:\n\n- feat(vcl): Allow showing of generated VCL for a service version [#1498](https://github.com/fastly/cli/pull/1498)\n- feat(compute/serve): Add experimental \"enable Pushpin\" mode ([#1509](https://github.com/fastly/cli/pull/1509), [#1520](https://github.com/fastly/cli/pull/1520))\n- feat(object-storage): improve access-keys list output ([#1513](https://github.com/fastly/cli/pull/1513))\n- refactor(domainv1,tools): use updated go-fastly domainmanagement imports and types ([#1517](https://github.com/fastly/cli/pull/1517))\n- feat(imageoptimizerdefaults): Support for retrieving and updating Image Optimizer defaults for a given VCL service ([#1518](https://github.com/fastly/cli/pull/1518))\n\n### Dependencies:\n\n- build(deps): `github.com/fastly/go-fastly/v11` from 10 to 11 ([#1514](https://github.com/fastly/cli/pull/1514))\n- build(deps): `golang.org/x/sys` from 0.33.0 to 0.34.0 ([#1508](https://github.com/fastly/cli/pull/1508))\n- build(deps): `golang.org/x/term` from 0.32.0 to 0.33.0 ([#1508](https://github.com/fastly/cli/pull/1508))\n- build(deps): `golang.org/x/crypto` from 0.39.0 to 0.40.0 ([#1508](https://github.com/fastly/cli/pull/1508))\n- build(deps): `golang.org/x/mod` from 0.25.0 to 0.26.0 ([#1508](https://github.com/fastly/cli/pull/1508))\n- build(deps): `golang.org/x/net` from 0.41.0 to 0.42.0 ([#1508](https://github.com/fastly/cli/pull/1508))\n- build(deps): `golang.org/x/sync` from 0.15.0 to 0.16.0 ([#1508](https://github.com/fastly/cli/pull/1508))\n- build(deps): `golang.org/x/text` from 0.26.0 to 0.27.0 ([#1508](https://github.com/fastly/cli/pull/1508))\n- build(deps): `github.com/coreos/go-oidc/v3` from 3.14.1 to 3.15.0 ([#1510](https://github.com/fastly/cli/pull/1510))\n- build(deps): `golang.org/x/sys` from 0.34.0 to 0.35.0 ([#1516](https://github.com/fastly/cli/pull/1516))\n- build(deps): `golang.org/x/term` from 0.33.0 to 0.34.0 ([#1516](https://github.com/fastly/cli/pull/1516))\n- build(deps): `github.com/hashicorp/cap` from 0.9.0 to 0.10.0 ([#1516](https://github.com/fastly/cli/pull/1516))\n- build(deps): `golang.org/x/crypto` from 0.40.0 to 0.41.0 ([#1516](https://github.com/fastly/cli/pull/1516))\n- build(deps): `golang.org/x/mod` from 0.26.0 to 0.27.0 ([#1516](https://github.com/fastly/cli/pull/1516))\n- build(deps): `golang.org/x/net` from 0.42.0 to 0.43.0 ([#1516](https://github.com/fastly/cli/pull/1516))\n- build(deps): `golang.org/x/text` from 0.27.0 to 0.28.0 ([#1516](https://github.com/fastly/cli/pull/1516))\n- build(deps): `actions/checkout` from 4 to 5 ([#1515](https://github.com/fastly/cli/pull/1515))\n- build(deps): `actions/download-artifact` from 4 to 5 ([#1515](https://github.com/fastly/cli/pull/1515))\n\n## [v11.4.0](https://github.com/fastly/cli/releases/tag/v11.4.0) (2025-07-09)\n\n### Enhancements:\n\n- feat(env): Add environment variable for extending the UserAgent string. ([#1502](https://github.com/fastly/cli/pull/1502))\n\n### Bug fixes:\n\n- fix(sso): Ensure that OPTIONS requests sent by browsers do not break SSO authentication. ([#1496](https://github.com/fastly/cli/pull/1496))\n\n### Dependencies:\n\n- build(deps): `github.com/fastly/go-fastly/v10` from 10.3.0 to 10.4.0 ([#1499](https://github.com/fastly/cli/pull/1499))\n- build(deps): `stefanzweifel/git-auto-commit-action` from 5 to 6 ([#1497](https://github.com/fastly/cli/pull/1497))\n- build(deps): `github.com/fastly/go-fastly/v10` from 10.4.0 to 10.5.0 ([#1501](https://github.com/fastly/cli/pull/1501))\n- build(deps): `github.com/andybalholm/brotli` from 1.1.1 to 1.2.0 ([#1501](https://github.com/fastly/cli/pull/1501))\n- build(deps): `github.com/Masterminds/semver/v3` from 3.3.1 to 3.4.0 ([#1503](https://github.com/fastly/cli/pull/1503))\n- build(deps): `github.com/fastly/go-fastly/v10` from 10.5.0 to 10.5.1 ([#1504](https://github.com/fastly/cli/pull/1504))\n\n## [v11.3.0](https://github.com/fastly/cli/releases/tag/v11.3.0) (2025-06-11)\n\n### Enhancements:\n\n- feat(config-store): Allow for dynamic limits on Config Store entry lengths ([#1485](https://github.com/fastly/cli/pull/1485))\n- feat(backend): Add support for 'prefer IPv6' attribute. ([#1487](https://github.com/fastly/cli/pull/1487))\n- feat(tools/domain): add `suggest` and `status` domain tools endpoints ([#1482](https://github.com/fastly/cli/pull/1482))\n- feat(logging): Add support for 'processing region' attribute. ([#1491](https://github.com/fastly/cli/pull/1491))\n- feat(domains): add `description` to `domainv1` endpoints ([#1483](https://github.com/fastly/cli/pull/1483))\n\n### Bug fixes:\n\n- fix(sso): Don't display the token after authentication. ([#1490](https://github.com/fastly/cli/pull/1490))\n- fix(service-version): Stop hiding the 'stage' and 'unstage' commands. ([#1492](https://github.com/fastly/cli/pull/1492))\n\n### Dependencies:\n\n- build(deps): `github.com/fastly/go-fastly/v10` from 10.0.1 to 10.1.0 ([#1476](https://github.com/fastly/cli/pull/1476))\n- build(deps): `github.com/fastly/go-fastly/v10` from 10.0.0 to 10.0.1 ([#1467](https://github.com/fastly/cli/pull/1467))\n- build(deps): `golang.org/x/net` from 0.37.0 to 0.39.0 ([#1467](https://github.com/fastly/cli/pull/1467))\n- build(go.mod): upgrade to go 1.24.0 in order to take advantage of the new tooling mechanism ([#1469](https://github.com/fastly/cli/pull/1469))\n- build(deps): `golang.org/x/image` from 0.15.0 to 0.18.0 ([#1470](https://github.com/fastly/cli/pull/1470))\n- build(deps): `github.com/magiconair/properties` from 1.8.7 to 1.8.10 ([#1474](https://github.com/fastly/cli/pull/1474))\n- build(deps): `golang.org/x/sys` from 0.32.0 to 0.33.0 ([#1472](https://github.com/fastly/cli/pull/1472))\n- build(deps): `cel.dev/expr` from 0.22.1 to 0.23.1 ([#1472](https://github.com/fastly/cli/pull/1472))\n- build(deps): `cloud.google.com/go` from 0.120.0 to 0.121.0 ([#1472](https://github.com/fastly/cli/pull/1472))\n- build(deps): `cloud.google.com/go/ai` from 0.8.0 to 0.11.0 ([#1472](https://github.com/fastly/cli/pull/1472))\n- build(deps): `cloud.google.com/go/auth` from 0.15.0 to 0.16.0 ([#1472](https://github.com/fastly/cli/pull/1472))\n- build(deps): `cloud.google.com/go/iam` from 1.4.2 to 1.5.0 ([#1472](https://github.com/fastly/cli/pull/1472))\n- build(deps): `cloud.google.com/go/kms` from 1.21.1 to 1.21.2 ([#1472](https://github.com/fastly/cli/pull/1472))\n- build(deps): `cloud.google.com/go/longrunning` from 0.6.6 to 0.6.7 ([#1472](https://github.com/fastly/cli/pull/1472))\n- build(deps): `cloud.google.com/go/monitoring` from 1.24.1 to 1.24.2 ([#1472](https://github.com/fastly/cli/pull/1472))\n- build(deps): `cloud.google.com/go/storage` from 1.51.0 to 1.52.0 ([#1472](https://github.com/fastly/cli/pull/1472))\n- build(deps): `github.com/42wim/httpsig` from 1.2.2 to 1.2.3 ([#1472](https://github.com/fastly/cli/pull/1472))\n- build(deps): `github.com/Azure/azure-sdk-for-go/sdk/azcore` from 1.17.1 to 1.18.0 ([#1472](https://github.com/fastly/cli/pull/1472))\n- build(deps): `github.com/Azure/azure-sdk-for-go/sdk/azidentity` from 1.8.2 to 1.9.0 ([#1472](https://github.com/fastly/cli/pull/1472))\n- build(deps): `github.com/fastly/go-fastly/v10` from 10.1.0 to 10.2.0 ([#1481](https://github.com/fastly/cli/pull/1481))\n- build(deps): `github.com/fastly/go-fastly/v10` from 10.2.0 to 10.3.0 ([#1488](https://github.com/fastly/cli/pull/1488))\n- build(deps): `golang.org/x/mod` from 0.24.0 to 0.25.0 ([#1488](https://github.com/fastly/cli/pull/1488))\n- build(deps): `golang.org/x/sync` from 0.14.0 to 0.15.0 ([#1488](https://github.com/fastly/cli/pull/1488))\n- build(deps): `golang.org/x/text` from 0.25.0 to 0.26.0 ([#1488](https://github.com/fastly/cli/pull/1488))\n- build(deps): `golang.org/x/crypto` from 0.38.0 to 0.39.0 ([#1489](https://github.com/fastly/cli/pull/1489))\n- build(deps): `golang.org/x/net` from 0.40.0 to 0.41.0 ([#1489](https://github.com/fastly/cli/pull/1489))\n\n## [v11.2.0](https://github.com/fastly/cli/releases/tag/v11.2.0) (2025-04-10)\n\n### Enhancements:\n\n- feat(config): Support file/format for kv_store and secret_store in fastly.toml\n- feat(config): Support metadata for kv_store in fastly.toml\n- feat(logging): add support for passing a filepath as an argument for format in logging commands\n\n### Bug fixes:\n\n- fix(language/rust): Check for wasm32-wasi output from build process and inform user how to reconfigure their project. [#1458](https://github.com/fastly/cli/pull/1458)\n\n### Dependencies:\n\n- dep(go.mod): upgrade go-fastly from v9 to v10 [#1448](https://github.com/fastly/cli/pull/1448)\n- build(deps): `golang.org/x/oauth2` from 0.28.0 to 0.29.0 ([#1451](https://github.com/fastly/cli/pull/1451))\n- build(deps): `golang.org/x/sys` from 0.31.0 to 0.32.0 ([#1454](https://github.com/fastly/cli/pull/1454))\n- build(deps): `github.com/fsnotify/fsnotify` from 1.8.0 to 1.9.0 ([#1450](https://github.com/fastly/cli/pull/1450))\n- build(deps): `golang.org/x/term` from 0.30.0 to 0.31.0 ([#1455](https://github.com/fastly/cli/pull/1455))\n- build(deps): `golang.org/x/crypto` from 0.36.0 to 0.37.0 ([#1453](https://github.com/fastly/cli/pull/1453))\n- build(deps): `github.com/coreos/go-oidc/v3` from 3.13.0 to 3.14.1 ([#1456](https://github.com/fastly/cli/pull/1456))\n- build(deps): `actions/create-github-app-token` from 1 to 2 ([#1449](https://github.com/fastly/cli/pull/1449))\n\n## [v11.1.0](https://github.com/fastly/cli/releases/tag/v11.1.0) (2025-03-27)\n\n### Bug fixes:\n\n- fix(logging): revert removal of placement param [#1444](https://github.com/fastly/cli/pull/1444)\n\n## [v11.0.0](https://github.com/fastly/cli/releases/tag/v11.0.0) (2025-03-25)\n\n### Breaking:\n\n- breaking(logging): The 'placement' parameter to the logging commands\n  has been removed; it was only used in combination with the Fastly\n  WAF, which is no longer supported.\n  [#1419](https://github.com/fastly/cli/pull/1419)\n- breaking(language.rust): Switch Rust builds to wasm32-wasip1 instead of wasm32-wasi [#1382](https://github.com/fastly/cli/pull/1382)\n- breaking(language.assemblyscript): Remove support for AssemblyScript [#1001](https://github.com/fastly/cli/pull/1001)\n- breaking(compute/pack): use package name from manifest [#1025](https://github.com/fastly/cli/pull/1025)\n\n### Enhancements:\n\n- fix(compute/init): Updates for renamed TypeScript default starter kit [#1405](https://github.com/fastly/cli/pull/1405)\n- feat(objectstorage/accesskeys): add support for access keys [#1428](https://github.com/fastly/cli/pull/1428)\n\n### Dependencies\n\n- build(deps): upgrade Go from 1.22 to 1.23 ([#624](https://github.com/fastly/cli/pull/1414))\n- build(deps): `github.com/rogpeppe/go-internal` from 1.13.1 to 1.14.1 ([#1416](https://github.com/fastly/cli/pull/1416))\n- build(deps): `golang.org/x/crypto` from 0.33.0 to 0.35.0 ([#1417](https://github.com/fastly/cli/pull/1417))\n- build(deps): `github.com/go-jose/go-jose/v4` from 4.0.4 to 4.0.5 ([#1412](https://github.com/fastly/cli/pull/1412))\n- build(deps): `github.com/klauspost/compress` from 1.17.11 to 1.18.0 ([#1411](https://github.com/fastly/cli/pull/1411))\n- build(deps): `github.com/google/go-cmp` from 0.6.0 to 0.7.0 ([#1409](https://github.com/fastly/cli/pull/1409))\n- build(deps): `golang.org/x/oauth2` from 0.26.0 to 0.27.0 ([#1421](https://github.com/fastly/cli/pull/1421))\n- build(deps): `github.com/hashicorp/cap` from 0.8.0 to 0.9.0 ([#1422](https://github.com/fastly/cli/pull/1422))\n- build(deps): `github.com/fastly/go-fastly/v9` from 9.13.1 to 9.14.0 ([#1433](https://github.com/fastly/cli/pull/1433))\n- build(deps): `golang.org/x/crypto` from 0.35.0 to 0.36.0 ([#1430](https://github.com/fastly/cli/pull/1430))\n- build(deps): `golang.org/x/oauth2` from 0.27.0 to 0.28.0 ([#1432](https://github.com/fastly/cli/pull/1432))\n- build(deps): `golang.org/x/net` from 0.35.0 to 0.37.0 ([#1439](https://github.com/fastly/cli/pull/1439))\n- build(deps): `github.com/golang/snappy` from 0.0.4 to 1.0.0 ([#1438](https://github.com/fastly/cli/pull/1438))\n- build(deps): `golang.org/x/mod` from 0.23.0 to 0.24.0 ([#1437](https://github.com/fastly/cli/pull/1437))\n- build(deps): `github.com/coreos/go-oidc/v3` from 3.12.0 to 3.13.0 ([#1442](https://github.com/fastly/cli/pull/1442))\n\n## [v10.19.0](https://github.com/fastly/cli/releases/tag/v10.19.0) (2025-02-18)\n\n**Enhancements:**\n\n- feat(computeacl): add support for compute platform ACLs [#1388](https://github.com/fastly/cli/pull/1388)\n\n**Dependencies:**\n\n- build(deps): bump golang.org/x/net from 0.34.0 to 0.35.0 [#1394](https://github.com/fastly/cli/pull/1394)\n- build(deps): bump golang.org/x/crypto from 0.32.0 to 0.33.0 [#1391](https://github.com/fastly/cli/pull/1391)\n- build(deps): bump golang.org/x/term from 0.28.0 to 0.29.0[#1393](https://github.com/fastly/cli/pull/1393)\n- build(deps): bump golang.org/x/oauth2 from 0.25.0 to 0.26.0 [#1390](https://github.com/fastly/cli/pull/1390)\n- build(deps): bump github.com/fastly/go-fastly/v9 from 9.13.0 to 9.13.1 [#1388](https://github.com/fastly/cli/pull/1388)\n\n## [v10.18.0](https://github.com/fastly/cli/releases/tag/v10.18.0) (2025-01-29)\n\n**Enhancements:**\n\n- feat(domains): add support for v1 functionality [#1374](https://github.com/fastly/cli/pull/1374)\n- feat(dashboard): add support for Observability custom dashboards [#1357](https://github.com/fastly/cli/pull/1357)\n\n**Bug fixes:**\n\n- fix(npm): fix build checks for JavaScript code [#1354](https://github.com/fastly/cli/pull/1354)\n- fix(build): pin Rust to 1.83.0 [#1368](https://github.com/fastly/cli/pull/1368)\n- fix(build): correct generation of static configuration file [#1378](https://github.com/fastly/cli/pull/1378)\n- fix(kvstoreentry/create): rework --dir flag [#1371](https://github.com/fastly/cli/pull/1371)\n\n**Dependencies:**\n\n- build(deps): bump golang.org/x/crypto from 0.31.0 to 0.32.0 [#1366](https://github.com/fastly/cli/pull/1366)\n- build(deps): bump golang.org/x/text from 0.20.0 to 0.21.0 [#1360](https://github.com/fastly/cli/pull/1360)\n- build(deps): bump github.com/otiai10/copy from 1.14.0 to 1.14.1 [#1364](https://github.com/fastly/cli/pull/1364)\n- build(deps): bump github.com/hashicorp/cap from 0.7.0 to 0.8.0 [#1365](https://github.com/fastly/cli/pull/1365)\n- build(deps): bump golang.org/x/net from 0.27.0 to 0.33.0 [#1370](https://github.com/fastly/cli/pull/1370)\n- build(deps): bump github.com/rogpeppe/go-internal from 1.11.0 to 1.13.1 [#1379](https://github.com/fastly/cli/pull/1379)\n- build(deps): bump github.com/dustinkirkland/golang-petname from 20240428194347 to eebcea082ee0 [#1377](https://github.com/fastly/cli/pull/1377)\n\n## [v10.17.1](https://github.com/fastly/cli/releases/tag/v10.17.1) (2024-12-04)\n\n**Bug fixes:**\n\n- fix(sso): Ensure that only one authentication cycle is started. [#1355](https://github.com/fastly/cli/pull/1355)\n\n**Dependencies:**\n\n- build(deps): bump github.com/Masterminds/semver/v3 from 3.3.0 to 3.3.1 [#1352](https://github.com/fastly/cli/pull/1352)\n\n## [v10.17.0](https://github.com/fastly/cli/releases/tag/v10.17.0) (2024-11-20)\n\n**Enhancements:**\n\n- feat(compute/build): Support 'upper bound' constraints on Rust versions. [#1350](https://github.com/fastly/cli/pull/1350)\n\n**Bug fixes:**\n\n- fix(compute/init): Init no longer fails if directory of same name as starter kit exists in current directory [#1349](https://github.com/fastly/cli/pull/1349)\n\n## [v10.16.0](https://github.com/fastly/cli/releases/tag/v10.16.0) (2024-11-12)\n\n**Enhancements:**\n\n- Publish to GitHub packages in addition to npmjs [#1330](https://github.com/fastly/cli/pull/1330)\n- feat(logging): Add support for Grafana Cloud Logs. [#1333](https://github.com/fastly/cli/pull/1333)\n- feat(profile/token): Allow user to specify how long token must be valid. [#1340](https://github.com/fastly/cli/pull/1340)\n- feat(products): Add support for Log Explorer & Insights. [#1341](https://github.com/fastly/cli/pull/1341)\n\n**Bug fixes:**\n\n- breaking(products): Remove support for NGWAF product. [#1338](https://github.com/fastly/cli/pull/1338)\n- fix(profile/token): 'profile token' command must check the validity of the stored token. [#1339](https://github.com/fastly/cli/pull/1339)\n- fix(lint): Update staticcheck and fix identified problems. [#1346](https://github.com/fastly/cli/pull/1346)\n\n**Dependencies:**\n\n- build(deps): bump golang.org/x/term from 0.24.0 to 0.25.0 [#1324](https://github.com/fastly/cli/pull/1324)\n- build(deps): bump golang.org/x/crypto from 0.27.0 to 0.28.0 [#1325](https://github.com/fastly/cli/pull/1325)\n- build(deps): bump github.com/fatih/color from 1.17.0 to 1.18.0 [#1331](https://github.com/fastly/cli/pull/1331)\n- build(deps): bump github.com/fsnotify/fsnotify from 1.7.0 to 1.8.0 [#1334](https://github.com/fastly/cli/pull/1334)\n- build(deps): Update to go-fastly v9.12.0. [#1337](https://github.com/fastly/cli/pull/1337)\n- build(deps): bump golang.org/x/term from 0.25.0 to 0.26.0 [#1342](https://github.com/fastly/cli/pull/1342)\n- build(deps): bump golang.org/x/crypto from 0.28.0 to 0.29.0 [#1343](https://github.com/fastly/cli/pull/1343)\n- build(deps): bump golang.org/x/text from 0.19.0 to 0.20.0 [#1344](https://github.com/fastly/cli/pull/1344)\n- build(deps): bump golang.org/x/mod from 0.21.0 to 0.22.0 [#1345](https://github.com/fastly/cli/pull/1345)\n\n## [v10.15.0](https://github.com/fastly/cli/releases/tag/v10.15.0) (2024-10-03)\n\n**Enhancements:**\n\n- feat(products): Add support for NGWAF product [#1322](https://github.com/fastly/cli/pull/1322)\n\n**Dependencies:**\n\n- build(deps): Upgrade to go-fastly 9.11.0. [#1322](https://github.com/fastly/cli/pull/1322)\n\n## [v10.14.1](https://github.com/fastly/cli/releases/tag/v10.14.1) (2024-09-16)\n\n**Bug fixes:**\n\n- fix(tls/subscription): Recognise Certainly CA as an option when creating TLS subscriptions. [#1315](https://github.com/fastly/cli/pull/1315)\n\n## [v10.14.0](https://github.com/fastly/cli/releases/tag/v10.14.0) (2024-09-10)\n\n**Enhancements:**\n\n- feat(npm): Add TypeScript types to @fastly/cli [#1296](https://github.com/fastly/cli/pull/1296)\n- feat(products): Add support for Fastly Bot Management product. [#1300](https://github.com/fastly/cli/pull/1300)\n\n**Bug fixes:**\n\n- fix(compute/publish): Don't change directory twice during execution. [#1295](https://github.com/fastly/cli/pull/1295)\n- feat(npm): Properly handle error from npm-invoked cli [#1302](https://github.com/fastly/cli/pull/1302)\n\n**Dependencies:**\n\n- build(deps): bump github.com/Masterminds/semver/v3 from 3.2.1 to 3.3.0 [#1306](https://github.com/fastly/cli/pull/1306)\n- build(deps): bump golang.org/x/text from 0.17.0 to 0.18.0 [#1309](https://github.com/fastly/cli/pull/1309)\n- build(deps): bump golang.org/x/term from 0.23.0 to 0.24.0 [#1310](https://github.com/fastly/cli/pull/1310)\n- build(deps): bump golang.org/x/crypto from 0.26.0 to 0.27.0 [#1311](https://github.com/fastly/cli/pull/1311)\n- build(deps): bump golang.org/x/mod from 0.20.0 to 0.21.0 [#1312](https://github.com/fastly/cli/pull/1312)\n\n## [v10.13.3](https://github.com/fastly/cli/releases/tag/v10.13.3) (2024-08-15)\n\nThis release does not contain any code changes, but was made in order\nto trigger the new 'NPM release' workflow after resolving some flaws\nin that workflow.\n\n## [v10.13.2](https://github.com/fastly/cli/releases/tag/v10.13.2) (2024-08-15)\n\nThis release does not contain any code changes, but was made in order\nto trigger the new 'NPM release' workflow after resolving an\nauthentication flaw in that workflow.\n\n## [v10.13.1](https://github.com/fastly/cli/releases/tag/v10.13.1) (2024-08-14)\n\nThis release does not contain any code changes, but was made in order\nto trigger the new 'NPM release' workflow.\n\n## [v10.13.0](https://github.com/fastly/cli/releases/tag/v10.13.0) (2024-08-14)\n\n**Enhancements:**\n\n- feat(tls): add optional `--key-path` parameter to `tls-custom private-key create` command [#1215](https://github.com/fastly/cli/pull/1215)\n- feat: add debug-mode around all network requests [#1239](https://github.com/fastly/cli/pull/1239)\n- logtail: add --timestamps flag [#1254](https://github.com/fastly/cli/pull/1254)\n- Distribute binaries via npm module [#1269](https://github.com/fastly/cli/pull/1269)\n- Enable quiet mode when `--json` flag is supplied [#1271](https://github.com/fastly/cli/pull/1271)\n- Support configuring connection keepalive parameters [#1275](https://github.com/fastly/cli/pull/1275)\n\n**Bug fixes:**\n\n- fix(update): Ensure that the CLI binary will be executable after an update [#1244](https://github.com/fastly/cli/pull/1244)\n- fix(service-version): Allow 'locked' services to be activated. [#1245](https://github.com/fastly/cli/pull/1245)\n- fix(compute/serve): don't fail the serve workflow if github errors [#1246](https://github.com/fastly/cli/pull/1246)\n- fix(all commands): --service-name flag should have priority. [#1264](https://github.com/fastly/cli/pull/1264)\n- fix(products): Display product names in API style [#1270](https://github.com/fastly/cli/pull/1270)\n\n**Dependencies:**\n\n- build(deps): bump goreleaser/goreleaser-action from 5 to 6 [#1220](https://github.com/fastly/cli/pull/1220)\n- build(deps): bump golang.org/x/text from 0.15.0 to 0.16.0 [#1222](https://github.com/fastly/cli/pull/1222)\n- build(deps): bump golang.org/x/mod from 0.17.0 to 0.18.0 [#1223](https://github.com/fastly/cli/pull/1223)\n- build(deps): bump golang.org/x/term from 0.20.0 to 0.21.0 [#1224](https://github.com/fastly/cli/pull/1224)\n- build(deps): bump golang.org/x/crypto from 0.23.0 to 0.24.0 [#1225](https://github.com/fastly/cli/pull/1225)\n- build(deps): bump github.com/fastly/go-fastly/v9 from 9.5.0 to 9.7.0 [#1235](https://github.com/fastly/cli/pull/1235)\n- build(deps): bump golang.org/x/term from 0.21.0 to 0.22.0 [#1240](https://github.com/fastly/cli/pull/1240)\n- build(deps): bump golang.org/x/crypto from 0.24.0 to 0.25.0 [#1241](https://github.com/fastly/cli/pull/1241)\n- build(deps): bump golang.org/x/mod from 0.18.0 to 0.19.0 [#1242](https://github.com/fastly/cli/pull/1242)\n- build(deps): 'tomlq' package now installs a 'tq' binary [#1243](https://github.com/fastly/cli/pull/1243)\n- build(deps): bump github.com/hashicorp/cap from 0.6.0 to 0.7.0 [#1272](https://github.com/fastly/cli/pull/1272)\n- build(deps): bump golang.org/x/mod from 0.19.0 to 0.20.0 [#1273](https://github.com/fastly/cli/pull/1273)\n- build(deps): bump golang.org/x/text from 0.16.0 to 0.17.0 [#1281](https://github.com/fastly/cli/pull/1281)\n- build(deps): bump golang.org/x/crypto from 0.25.0 to 0.26.0 [#1282](https://github.com/fastly/cli/pull/1282)\n- build(deps): bump golang.org/x/term from 0.22.0 to 0.23.0 [#1283](https://github.com/fastly/cli/pull/1283)\n\n## [v10.12.3](https://github.com/fastly/cli/releases/tag/v10.12.3) (2024-06-14)\n\n**Bug fixes:**\n\n- fix(sso): correct the behaviour for direct sso invocation [#1230](https://github.com/fastly/cli/pull/1230)\n- fix(compute/deploy): dereference service number pointer [#1231](https://github.com/fastly/cli/pull/1231)\n- fix(sso): update output to reflect default profile behaviour [#1232](https://github.com/fastly/cli/pull/1232)\n\n## [v10.12.2](https://github.com/fastly/cli/releases/tag/v10.12.2) (2024-06-13)\n\n**Bug fixes:**\n\n- fix(sso): re-auth on profile switch + support MAUA [#1226](https://github.com/fastly/cli/pull/1226)\n\n## [v10.12.1](https://github.com/fastly/cli/releases/tag/v10.12.1) (2024-06-10)\n\n**Enhancements:**\n\n- expose SSO commands and flags [#1218](https://github.com/fastly/cli/pull/1218)\n\n## [v10.12.0](https://github.com/fastly/cli/releases/tag/v10.12.0) (2024-06-10)\n\n**Enhancements:**\n\n- feat(sso): support active session account switching [#1207](https://github.com/fastly/cli/pull/1207)\n\n## [v10.11.0](https://github.com/fastly/cli/releases/tag/v10.11.0) (2024-06-06)\n\n**Enhancements:**\n\n- feat(app): improve error messaging when Fastly servers are unresponsive [#1212](https://github.com/fastly/cli/pull/1212)\n- feat(compute): clone starter kit source with init --from=serviceID [#1213](https://github.com/fastly/cli/pull/1213)\n- Adds --cert-path argument to `tls-custom certificate update` command to pass in a path to a certificate file [#1214](https://github.com/fastly/cli/pull/1214)\n\n## [v10.10.0](https://github.com/fastly/cli/releases/tag/v10.10.0) (2024-05-20)\n\n**Enhancements:**\n\n- Adds --cert-path argument to `tls-custom certificate create` command to pass in a path to a certificate file [#1189](https://github.com/fastly/cli/pull/1189)\n- feat(observability/alerts): Alerts support [#1192](https://github.com/fastly/cli/pull/1192)\n- feat(compute/rust) Handle Cargo config filename for Rust >=1.78.0 [#1199](https://github.com/fastly/cli/pull/1199)\n- add project-id to gcs logging setting [#1202](https://github.com/fastly/cli/pull/1202)\n\n**Dependencies:**\n\n- build(deps): bump github.com/fastly/go-fastly/v9 from 9.3.1 to 9.3.2 [#1204](https://github.com/fastly/cli/pull/1204)\n- build(deps): bump github.com/fatih/color from 1.16.0 to 1.17.0 [#1205](https://github.com/fastly/cli/pull/1205)\n\n## [v10.9.0](https://github.com/fastly/cli/releases/tag/v10.9.0) (2024-05-08)\n\n**Enhancements:**\n\n- chore: grammar and capitalization fixes for KV Store commands [#1178](https://github.com/fastly/cli/pull/1178)\n- feat(kvstores): add support for specifying location when creating KV stores [#1182](https://github.com/fastly/cli/pull/1182)\n- feat(compute/build): support wasm-tools installed into `$PATH` [#1183](https://github.com/fastly/cli/pull/1183)\n- feat(compute/serve): support arbitrary arguments to Viceroy [#1186](https://github.com/fastly/cli/pull/1186)\n- ci: update tinygo version used in tests [#1188](https://github.com/fastly/cli/pull/1188)\n- feat(compute/init): allow `--from` to take a Service ID [#1187](https://github.com/fastly/cli/pull/1187)\n\n**Bug fixes:**\n\n- fix(kvstore): delete all keys [#1181](https://github.com/fastly/cli/pull/1181)\n- fix(compute/rust) handling of 'cargo version' output [#1197](https://github.com/fastly/cli/pull/1197)\n- fix(compute/serve): skip build if `--file` set [#1200](https://github.com/fastly/cli/pull/1200)\n\n**Dependencies:**\n\n- build(deps): bump github.com/fastly/go-fastly/v9 from 9.2.1 to 9.2.2 [#1180](https://github.com/fastly/cli/pull/1180)\n- build(deps): bump golang.org/x/crypto from 0.22.0 to 0.23.0 [#1194](https://github.com/fastly/cli/pull/1194)\n\n## [v10.8.10](https://github.com/fastly/cli/releases/tag/v10.8.10) (2024-04-10)\n\n**Dependencies:**\n\n- build(deps): bump golang.org/x/crypto from 0.21.0 to 0.22.0 [#1173](https://github.com/fastly/cli/pull/1173)\n- build(deps): bump golang.org/x/mod from 0.16.0 to 0.17.0 [#1175](https://github.com/fastly/cli/pull/1175)\n\n## [v10.8.9](https://github.com/fastly/cli/releases/tag/v10.8.9) (2024-03-27)\n\n**Bug fixes:**\n\n- fix(stats/historical): avoid runtime SIGSEGV [#1169](https://github.com/fastly/cli/pull/1169)\n\n## [v10.8.8](https://github.com/fastly/cli/releases/tag/v10.8.8) (2024-03-15)\n\n**Enhancements:**\n\n- feat(logging/scalyr): add project-id [#1166](https://github.com/fastly/cli/pull/1166)\n- Update all URLs for developer.fastly.com to their new forms [#1164](https://github.com/fastly/cli/pull/1164)\n\n**Dependencies:**\n\n- build(deps): bump google.golang.org/protobuf from 1.28.1 to 1.33.0 [#1158](https://github.com/fastly/cli/pull/1158)\n\n## [v10.8.7](https://github.com/fastly/cli/releases/tag/v10.8.7) (2024-03-14)\n\n**Bug fixes:**\n\n- fix(text): deref pointers [#1161](https://github.com/fastly/cli/pull/1161)\n- fix(compute/serve): let wasm-tools fail more gracefully [#1160](https://github.com/fastly/cli/pull/1160)\n- fix(compute/serve): support Windows [#1159](https://github.com/fastly/cli/pull/1159)\n\n**Enhancements:**\n\n- refactor: avoid duplicate path strings [#1162](https://github.com/fastly/cli/pull/1162)\n\n## [v10.8.6](https://github.com/fastly/cli/releases/tag/v10.8.6) (2024-03-12)\n\n**Dependencies:**\n\n- build(deps): bump golang.org/x/crypto from 0.20.0 to 0.21.0 [#1153](https://github.com/fastly/cli/pull/1153)\n- build: bump go-fastly to v9.0.1 [#1155](https://github.com/fastly/cli/pull/1155)\n- build(deps): bump actions/setup-go from 4 to 5 [#1106](https://github.com/fastly/cli/pull/1106)\n- build(deps): bump github.com/go-jose/go-jose/v3 from 3.0.1 to 3.0.3 [#1149](https://github.com/fastly/cli/pull/1149)\n- build(deps): bump actions/download-artifact and actions/upload-artifact from 3 to 4 [#1156](https://github.com/fastly/cli/pull/1156)\n\n## [v10.8.5](https://github.com/fastly/cli/releases/tag/v10.8.5) (2024-03-11)\n\n**Bug fixes:**\n\n- fix(compute/serve): avoid wasm validation when --file is set [#1150](https://github.com/fastly/cli/pull/1150)\n\n**Enhancements:**\n\n- refactor(app): update list of commands that require a token [#1145](https://github.com/fastly/cli/pull/1145)\n\n**Dependencies:**\n\n- build(deps): bump golang.org/x/crypto from 0.19.0 to 0.20.0 [#1146](https://github.com/fastly/cli/pull/1146)\n- build(deps): bump golang.org/x/mod from 0.15.0 to 0.16.0 [#1147](https://github.com/fastly/cli/pull/1147)\n\n## [v10.8.4](https://github.com/fastly/cli/releases/tag/v10.8.4) (2024-03-01)\n\n**Bug fixes:**\n\n- fix(compute/build): avoid persisting old metadata [#1142](https://github.com/fastly/cli/pull/1142)\n\n## [v10.8.3](https://github.com/fastly/cli/releases/tag/v10.8.3) (2024-02-21)\n\n**Bug fixes:**\n\n- fix(github): update wasm-tools path [#1136](https://github.com/fastly/cli/pull/1136)\n- fix(compute/serve): avoid `text.Output` when dealing with large `bytes.Buffer` [#1138](https://github.com/fastly/cli/pull/1138)\n\n**Enhancements:**\n\n- resolve GitHub linter issues [#1137](https://github.com/fastly/cli/pull/1137)\n\n**Dependencies:**\n\n- build(deps): bump golang.org/x/mod from 0.14.0 to 0.15.0 [#1135](https://github.com/fastly/cli/pull/1135)\n\n## [v10.8.2](https://github.com/fastly/cli/releases/tag/v10.8.2) (2024-02-15)\n\n**Bug fixes:**\n\n- fix: directory switching logic [#1132](https://github.com/fastly/cli/pull/1132)\n\n## [v10.8.1](https://github.com/fastly/cli/releases/tag/v10.8.1) (2024-02-14)\n\n**Bug fixes:**\n\n- fix(compute/build): normalise and bucket heap allocations [#1130](https://github.com/fastly/cli/pull/1130)\n\n**Enhancements:**\n\n- refactor(all): support go-fastly v9 [#1124](https://github.com/fastly/cli/pull/1124)\n\n**Dependencies:**\n\n- build(deps): bump actions/cache from 3 to 4 [#1122](https://github.com/fastly/cli/pull/1122)\n- build(deps): bump github.com/hashicorp/cap from 0.3.4 to 0.5.0 [#1128](https://github.com/fastly/cli/pull/1128)\n- build(deps): bump golang.org/x/crypto from 0.18.0 to 0.19.0 [#1127](https://github.com/fastly/cli/pull/1127)\n\n## [v10.8.0](https://github.com/fastly/cli/releases/tag/v10.8.0) (2024-01-17)\n\n**Bug fixes:**\n\n- doc(tls/custom): correct flag descriptions [#1116](https://github.com/fastly/cli/pull/1116)\n- fix(profile/create): support sso [#1117](https://github.com/fastly/cli/pull/1117)\n- fix: update list of commands that require auth server [#1120](https://github.com/fastly/cli/pull/1120)\n\n**Enhancements:**\n\n- feat: install CLI version command [#1104](https://github.com/fastly/cli/pull/1104)\n- refactor(cmd): rename package to argparser [#1105](https://github.com/fastly/cli/pull/1105)\n- refactor: rename test function names [#1107](https://github.com/fastly/cli/pull/1107)\n\n**Dependencies:**\n\n- build(deps): bump golang.org/x/crypto from 0.15.0 to 0.18.0 [#1119](https://github.com/fastly/cli/pull/1119)\n\n## [v10.7.0](https://github.com/fastly/cli/releases/tag/v10.7.0) (2023-11-30)\n\nThe Fastly CLI internal configuration file has `config_version` bumped to version `6`. We've added a new `[wasm-metadata.script_info]` field so that users can omit script info (which comes from the fastly.toml) from the metadata annotated onto their compiled Wasm binaries.\n\nWhen upgrading to this version of the CLI, and running a command for the first time, the config file should automatically update, but this can also be manually triggered by executing:\n\n```shell\nfastly config --reset\n```\n\n**Bug fixes:**\n\n- fix: move auth setup so it doesn't run for non-token based commands [#1099](https://github.com/fastly/cli/pull/1099)\n\n**Enhancements:**\n\n- remove(profile/update): APIClientFactory [#1094](https://github.com/fastly/cli/pull/1094)\n- feat: switch on metadata collection [#1097](https://github.com/fastly/cli/pull/1097)\n\n## [v10.6.4](https://github.com/fastly/cli/releases/tag/v10.6.4) (2023-11-15)\n\n**Bug fixes:**\n\n- fix(errors): ensure help output is displayed [#1092](https://github.com/fastly/cli/pull/1092)\n\n## [v10.6.3](https://github.com/fastly/cli/releases/tag/v10.6.3) (2023-11-15)\n\nThe Fastly CLI internal configuration file has `config_version` bumped to version `5`. We've added a new account endpoint field (used as an override for Single-Sign On testing).\n\nWhen upgrading to this version of the CLI, and running a command for the first time, the config file should automatically update, but this can also be manually triggered by executing:\n\n```shell\nfastly config --reset\n```\n\n**Bug fixes:**\n\n- fix(text): prompt colour [#1089](https://github.com/fastly/cli/pull/1089)\n- fix(app): allow config override for account endpoint [#1090](https://github.com/fastly/cli/pull/1090)\n\n**Enhancements:**\n\n- feat: support SSO (Single Sign-On) [#1010](https://github.com/fastly/cli/pull/1010)\n\n**Dependencies:**\n\n- build(deps): bump golang.org/x/(crypto|term) [#1088](https://github.com/fastly/cli/pull/1088)\n\n## [v10.6.2](https://github.com/fastly/cli/releases/tag/v10.6.2) (2023-11-09)\n\n**Bug fixes:**\n\n- fix(github): corrections for Windows users downloading wasm-tools [#1083](https://github.com/fastly/cli/pull/1083)\n- fix(compute/build): don't block user if wasm-tool fails [#1084](https://github.com/fastly/cli/pull/1084)\n\n**Enhancements:**\n\n- refactor: apply linting fixes [#1080](https://github.com/fastly/cli/pull/1080)\n- refactor(compute/serve): replace log.Fatal usage with channel [#1081](https://github.com/fastly/cli/pull/1081)\n- refactor(logtail): replace log.Fatal usage with channel [#1081](https://github.com/fastly/cli/pull/1082)\n\n**Dependencies:**\n\n- build(deps): bump golang.org/x/mod from 0.13.0 to 0.14.0 [#1079](https://github.com/fastly/cli/pull/1079)\n- build(deps): bump golang.org/x/text from 0.13.0 to 0.14.0 [#1078](https://github.com/fastly/cli/pull/1078)\n- build(deps): bump github.com/fatih/color from 1.15.0 to 1.16.0 [#1077](https://github.com/fastly/cli/pull/1077)\n\n## [v10.6.1](https://github.com/fastly/cli/releases/tag/v10.6.1) (2023-11-03)\n\n**Bug fixes:**\n\n- fix(manifest): only reset EnvVars if EnvFile set [#1073](https://github.com/fastly/cli/pull/1073)\n- fix(github): check architecture when fetching wasm-tools [#1074](https://github.com/fastly/cli/pull/1074)\n\n## [v10.6.0](https://github.com/fastly/cli/releases/tag/v10.6.0) (2023-11-03)\n\n**Bug fixes:**\n\n- fix(backend): support disabling `ssl-check-cert` [#1055](https://github.com/fastly/cli/pull/1055)\n\n**Enhancements:**\n\n- feat(compute): add metadata subcommand [#1013](https://github.com/fastly/cli/pull/1013)\n- feat(telemetry): add wasm-tools wasm binary annotations [#1016](https://github.com/fastly/cli/pull/1016)\n- feat: add `--consistency` flag to `kv-store-entry list` command [#1058](https://github.com/fastly/cli/pull/1058)\n- feat: add `--debug-mode` [#1056](https://github.com/fastly/cli/pull/1056)\n- ci: replace setup-tinygo fork with original [#1057](https://github.com/fastly/cli/pull/1057)\n\n**Dependencies:**\n\n- build(deps): bump github.com/docker/docker [#1060](https://github.com/fastly/cli/pull/1060)\n- build(deps): bump google.golang.org/grpc from 1.56.2 to 1.56.3 [#1061](https://github.com/fastly/cli/pull/1061)\n- build(deps): bump all go.mod dependencies [#1062](https://github.com/fastly/cli/pull/1062)\n\n## [v10.5.1](https://github.com/fastly/cli/releases/tag/v10.5.1) (2023-10-25)\n\n**Bug fixes:**\n\n- fix(compute/deploy): ignore package comparison error [#1053](https://github.com/fastly/cli/pull/1053)\n- remove: trufflehog [#1064](https://github.com/fastly/cli/pull/1064)\n- fix(cmd/flags): handle zero length check separately [#1065](https://github.com/fastly/cli/pull/1065)\n- fix(compute/deploy): only cleanup service if there is an ID [#1066](https://github.com/fastly/cli/pull/1066)\n\n**Enhancements:**\n\n- refactor(compute/deploy): add setup message for existing service users [#1052](https://github.com/fastly/cli/pull/1052)\n- feat(manifest): support env_file [#1067](https://github.com/fastly/cli/pull/1067)\n- fix(compute/build): improve redaction logic [#1068](https://github.com/fastly/cli/pull/1068)\n- feat(compute/secrets): redact common org secrets [#1069](https://github.com/fastly/cli/pull/1069)\n\n**Dependencies:**\n\n- build(deps): bump github.com/fsnotify/fsnotify from 1.6.0 to 1.7.0 [#1050](https://github.com/fastly/cli/pull/1050)\n- build(deps): bump actions/setup-node from 3 to 4 [#1051](https://github.com/fastly/cli/pull/1051)\n\n## [v10.5.0](https://github.com/fastly/cli/releases/tag/v10.5.0) (2023-10-18)\n\nThe Fastly CLI internal configuration file has been updated to version `4`, with the only change being the addition of the Fastly [TinyGo Compute Starter Kit](https://github.com/fastly/compute-starter-kit-go-tinygo).\n\nWhen upgrading to this version of the CLI, and running a command for the first time, the config file should automatically update, but this can also be manually triggered by executing:\n\n```shell\nfastly config --reset\n```\n\nThe other change worth noting is to the parsing of the `fastly.toml` manifest file, which now supports a `file` field inside `[setup.kv_stores.<T>.items]` which can be used in place of the `value` field. Assigning a file path to the `file` field will use the content of the file as the value for the key. See: https://www.fastly.com/documentation/reference/compute/fastly-toml\n\n**Bug fixes:**\n\n- fix(compute/init): `post_init` to support `env_vars` [#1014](https://github.com/fastly/cli/pull/1014)\n- fix(app): return error when input is `--` only [#1022](https://github.com/fastly/cli/pull/1022)\n- fix(compute/deploy): check package before service clone [#1026](https://github.com/fastly/cli/pull/1026)\n- fix: spinner wraps original error [#1029](https://github.com/fastly/cli/pull/1029)\n- fix(compute/serve): ensure `--env` files are processed [#1039](https://github.com/fastly/cli/pull/1039)\n\n**Enhancements:**\n\n- add: vcl condition commands [#1008](https://github.com/fastly/cli/pull/1008)\n- feat(compute/build): support `env_vars` for JavaScript/Rust [#1012](https://github.com/fastly/cli/pull/1012)\n- feat(config): add tinygo starter kit [#1011](https://github.com/fastly/cli/pull/1011)\n- feat(compute/serve): support guest profiler under Viceroy [#1019](https://github.com/fastly/cli/pull/1019)\n- fix(packaging): Improve metadata in Linux packages [#1021](https://github.com/fastly/cli/pull/1021)\n- feat(compute/build): support Cargo Workspaces [#1023](https://github.com/fastly/cli/pull/1023)\n- feat(spinner): abstract common pattern [#1024](https://github.com/fastly/cli/pull/1024)\n- fix(text): consistent formatting and output alignment [#1030](https://github.com/fastly/cli/pull/1030)\n- feat(product_enablement): add `products` command [#1036](https://github.com/fastly/cli/pull/1036)\n- fix(compute/serve): update Viceroy guest profile flag [#1033](https://github.com/fastly/cli/pull/1033)\n- fix(compute/deploy): support file field for `kv_store` setup [#1040](https://github.com/fastly/cli/pull/1040)\n- refactor(compute/deploy): simplify logic flows [#1032](https://github.com/fastly/cli/pull/1032)\n- feat(compute/build): allow user to specify project directory to build [#1043](https://github.com/fastly/cli/pull/1043)\n- feat(compute/deploy): avoid store conflicts [#1041](https://github.com/fastly/cli/pull/1041)\n- feat: support `--env` flag [#1046](https://github.com/fastly/cli/pull/1046)\n\n**Dependencies:**\n\n- build(deps): bump goreleaser/goreleaser-action from 4 to 5 [#1015](https://github.com/fastly/cli/pull/1015)\n- build(deps): bump golang.org/x/crypto from 0.12.0 to 0.13.0 [#1009](https://github.com/fastly/cli/pull/1009)\n- build(deps): bump actions/checkout from 3 to 4 [#1006](https://github.com/fastly/cli/pull/1006)\n- build(deps): bump github.com/fastly/go-fastly/v8 from 8.6.1 to 8.6.2 [#1028](https://github.com/fastly/cli/pull/1028)\n- build(deps): bump github.com/otiai10/copy from 1.12.0 to 1.14.0 [#1027](https://github.com/fastly/cli/pull/1027)\n- build(deps): bump golang.org/x/crypto from 0.13.0 to 0.14.0 [#1034](https://github.com/fastly/cli/pull/1034)\n- build(deps): bump golang.org/x/net from 0.10.0 to 0.17.0 [#1042](https://github.com/fastly/cli/pull/1042)\n- build(deps): bump github.com/google/go-cmp from 0.5.9 to 0.6.0 [#1045](https://github.com/fastly/cli/pull/1045)\n\n**Documentation:**\n\n- fix(DEVELOP.MD): clarify Go version requirement and document Rust requirement [#1017](https://github.com/fastly/cli/pull/1017)\n- doc(compute/serve): update GetViceroy doc [#1038](https://github.com/fastly/cli/pull/1038)\n- branding: Replace all Compute@Edge/C@E references with Compute [#1044](https://github.com/fastly/cli/pull/1044)\n\n## [v10.4.0](https://github.com/fastly/cli/releases/tag/v10.4.0) (2023-08-31)\n\nThe Fastly CLI internal configuration file has been updated to version `3`, with the primary change being updates to the toolchain constraints within the `[language.go]` section ([diff](https://github.com/fastly/cli/pull/995/files#diff-8b30a64872c0f304cd83a24f92c57f62b12d6ba81c6a51428da7d1ed3ceb83fd)).\n\nWhen upgrading to this version of the CLI, and running a command for the first time, the config file should automatically update, but this can also be manually triggered by executing:\n\n```shell\nfastly config --reset\n```\n\nThe changes to the internal configuration correlate with another change in this release, which is adding support for standard Go alongside TinyGo.\n\nIf your fastly.toml has no custom `[scripts.build]` defined, then TinyGo will continue to be the default compiler used for building your Compute@Edge project. Otherwise, adding the following will enable you to use the Wasm support that Go 1.21+ provides:\n\n```toml\n[scripts]\nenv_vars = [\"GOARCH=wasm\", \"GOOS=wasip1\"]\nbuild = \"go build -o bin/main.wasm .\"\n```\n\n**Deprecations:**\n\n- remove(compute/init): assemblyscript [#1002](https://github.com/fastly/cli/pull/1002)\n\n**Enhancements:**\n\n- feat(compute/build): support native go [#995](https://github.com/fastly/cli/pull/995)\n- Add support for interacting with the New Relic OTLP logging endpoint [#990](https://github.com/fastly/cli/pull/990)\n\n**Dependencies:**\n\n- build: bump go-fastly to v8.6.1 [#1000](https://github.com/fastly/cli/pull/1000)\n- build(deps): bump golang.org/x/crypto from 0.11.0 to 0.12.0 [#994](https://github.com/fastly/cli/pull/994)\n- build(deps): bump github.com/fastly/go-fastly/v8 from 8.5.7 to 8.5.9 [#996](https://github.com/fastly/cli/pull/996)\n\n## [v10.3.0](https://github.com/fastly/cli/releases/tag/v10.3.0) (2023-08-16)\n\n**Enhancements:**\n\n- feat(compute/init): support post_init [#997](https://github.com/fastly/cli/pull/997)\n\n**Bug fixes:**\n\n- build(scripts): use /usr/bin/env bash to retrieve system bash path [#987](https://github.com/fastly/cli/pull/987)\n- fix(kvstores/list): support pagination [#988](https://github.com/fastly/cli/pull/988)\n- fix(secretstore): pagination + support for json [#991](https://github.com/fastly/cli/pull/991)\n\n## [v10.2.4](https://github.com/fastly/cli/releases/tag/v10.2.4) (2023-07-28)\n\n**Enhancements:**\n\n- fix(kvstoreentry): improve error handling for batch processing [#980](https://github.com/fastly/cli/pull/980)\n- feat(kvstore): support deleting all keys [#981](https://github.com/fastly/cli/pull/981)\n- feat(configstoreentry): support deleting all keys [#983](https://github.com/fastly/cli/pull/983)\n\n**Bug fixes:**\n\n- fix(compute/deploy): support --service-name for publishing to a non-manifest specific service [#979](https://github.com/fastly/cli/pull/979)\n- fix(compute/validate): remove broken decompression bomb check [#984](https://github.com/fastly/cli/pull/984)\n\n## [v10.2.3](https://github.com/fastly/cli/releases/tag/v10.2.3) (2023-07-20)\n\n**Enhancements:**\n\n- refactor(compute): clean-up logic surrounding filesHash generation [#969](https://github.com/fastly/cli/pull/969)\n- fix: increase text width [#970](https://github.com/fastly/cli/pull/970)\n\n**Bug fixes:**\n\n- Correctly check if the package is up to date [#967](https://github.com/fastly/cli/pull/967)\n- fix(flags): ensure ListServices call is paginated [#976](https://github.com/fastly/cli/pull/976)\n\n**Dependencies:**\n\n- build(deps): bump github.com/fastly/go-fastly/v8 from 8.5.1 to 8.5.2 [#966](https://github.com/fastly/cli/pull/966)\n- build(deps): bump github.com/fastly/go-fastly/v8 from 8.5.2 to 8.5.4 [#968](https://github.com/fastly/cli/pull/968)\n- build(deps): bump golang.org/x/crypto from 0.10.0 to 0.11.0 [#972](https://github.com/fastly/cli/pull/972)\n- build(deps): bump golang.org/x/term from 0.9.0 to 0.10.0 [#971](https://github.com/fastly/cli/pull/971)\n\n## [v10.2.2](https://github.com/fastly/cli/releases/tag/v10.2.2) (2023-06-22)\n\n**Enhancements:**\n\n- refactor(ci): disable setup-go caching to avoid later cache restoration errors [#960](https://github.com/fastly/cli/pull/960)\n\n**Bug fixes:**\n\n- fix(update): use consistent pattern for replacing binary [#961](https://github.com/fastly/cli/pull/961)\n- fix(kvstoreentry): avoid runtime panic for out of bound slice index [#964](https://github.com/fastly/cli/pull/964)\n\n**Dependencies:**\n\n- build(deps): bump golang.org/x/term from 0.8.0 to 0.9.0 [#959](https://github.com/fastly/cli/pull/959)\n- build(deps): bump github.com/otiai10/copy from 1.11.0 to 1.12.0 [#958](https://github.com/fastly/cli/pull/958)\n- build(deps): bump golang.org/x/crypto from 0.9.0 to 0.10.0 [#957](https://github.com/fastly/cli/pull/957)\n\n## [v10.2.1](https://github.com/fastly/cli/releases/tag/v10.2.1) (2023-06-19)\n\n**Enhancements:**\n\n- feat(logging/s3): add --file-max-bytes flag [#952](https://github.com/fastly/cli/pull/952)\n- ci: better caching support [#951](https://github.com/fastly/cli/pull/951)\n- fix: remove sentry [#954](https://github.com/fastly/cli/pull/954)\n- refactor: logic clean-up [#955](https://github.com/fastly/cli/pull/955)\n\n**Bug fixes:**\n\n- ci: fix cache restore bug [#953](https://github.com/fastly/cli/pull/953)\n\n**Dependencies:**\n\n- build(deps): bump github.com/fastly/go-fastly/v8 from 8.4.1 to 8.5.0 [#949](https://github.com/fastly/cli/pull/949)\n\n## [v10.2.0](https://github.com/fastly/cli/releases/tag/v10.2.0) (2023-06-12)\n\n**Enhancements:**\n\n- feat: support viceroy pinning [#947](https://github.com/fastly/cli/pull/947)\n- Enable environment variable hints for `--token` flag [#945](https://github.com/fastly/cli/pull/945)\n- secret store: add `--recreate` and `--recreate-must` options [#930](https://github.com/fastly/cli/pull/930)\n\n**Dependencies:**\n\n- build(deps): bump github.com/fastly/go-fastly/v8 from 8.3.0 to 8.4.1 [#946](https://github.com/fastly/cli/pull/946)\n\n## [v10.1.0](https://github.com/fastly/cli/releases/tag/v10.1.0) (2023-05-18)\n\nDeprecation notice: `fastly compute hashsum` is being phased out in favour of `fastly compute hash-files`.\n\n**Enhancements:**\n\n- feat(compute/hashfiles): add hash-files subcommand [#943](https://github.com/fastly/cli/pull/943)\n\n## [v10.0.1](https://github.com/fastly/cli/releases/tag/v10.0.1) (2023-05-17)\n\n**Bug fixes:**\n\n- fix(kvstoreentry): support JSON output for bulk processing [#940](https://github.com/fastly/cli/pull/940)\n\n## [v10.0.0](https://github.com/fastly/cli/releases/tag/v10.0.0) (2023-05-16)\n\n**Breaking:**\n\nThis release introduces a breaking interface change to the `kv-store-entry` command. The `--key-name` flag is renamed to `--key` to be consistent with the other 'stores' supported within the CLI.\n\n**Bug fixes:**\n\n- fastly backend create: override host cannot be an empty string [#936](https://github.com/fastly/cli/pull/936)\n- fix(profile): support automation tokens [#938](https://github.com/fastly/cli/pull/938)\n\n**Enhancements:**\n\n- feat(kvstore): Bulk Import [#927](https://github.com/fastly/cli/pull/927)\n- refactor: make config/kv/secret store output consistent [#937](https://github.com/fastly/cli/pull/937)\n\n**Dependencies:**\n\n- build(deps): bump github.com/fastly/go-fastly/v8 from 8.0.0 to 8.0.1 [#926](https://github.com/fastly/cli/pull/926)\n- build(deps): bump golang.org/x/term from 0.7.0 to 0.8.0 [#928](https://github.com/fastly/cli/pull/928)\n- build(deps): bump github.com/getsentry/sentry-go from 0.20.0 to 0.21.0 [#929](https://github.com/fastly/cli/pull/929)\n- build(deps): bump golang.org/x/crypto from 0.8.0 to 0.9.0 [#934](https://github.com/fastly/cli/pull/934)\n\n## [v9.0.3](https://github.com/fastly/cli/releases/tag/v9.0.3) (2023-04-26)\n\n**Bug fixes:**\n\n- Omit errors from Sentry reporting [#922](https://github.com/fastly/cli/pull/922)\n\n**Enhancements:**\n\n- fix(compute/serve): always set verbose mode for viceroy [#924](https://github.com/fastly/cli/pull/924)\n\n**Dependencies:**\n\n- build(deps): bump github.com/otiai10/copy from 1.10.0 to 1.11.0 [#923](https://github.com/fastly/cli/pull/923)\n\n## [v9.0.2](https://github.com/fastly/cli/releases/tag/v9.0.2) (2023-04-19)\n\n**Bug fixes:**\n\n- fix(kvstore): alias `object-store` [#920](https://github.com/fastly/cli/pull/920)\n\n## [v9.0.1](https://github.com/fastly/cli/releases/tag/v9.0.1) (2023-04-19)\n\n**Bug fixes:**\n\n- fix: reinstate support for `[setup.object_stores]` [#918](https://github.com/fastly/cli/pull/918)\n\n## [v9.0.0](https://github.com/fastly/cli/releases/tag/v9.0.0) (2023-04-19)\n\nThere are a couple of important 'breaking' changes in this release.\n\nThe `object-store` command has been renamed to `kv-store` and the `fastly.toml` manifest (used by the Fastly CLI) has updated its data model (see https://www.fastly.com/documentation/reference/compute/fastly-toml) by renaming `[setup.dictionaries]` and `[local_server.dictionaries]` to their `config_stores` equivalent, which has resulted in a new `manifest_version` number due to the change to the consumer interface.\n\n**Breaking:**\n\n- breaking(objectstore): rename object-store command to kv-store [#904](https://github.com/fastly/cli/pull/904)\n- breaking(manifest): support latest fastly.toml manifest data model [#907](https://github.com/fastly/cli/pull/907)\n\n**Bug fixes:**\n\n- fix(manifest): re-raise remediation error to avoid go-toml wrapping issue [#915](https://github.com/fastly/cli/pull/915)\n- fix(compute/deploy): shorten message to avoid spinner bug [#916](https://github.com/fastly/cli/pull/916)\n\n**Enhancements:**\n\n- feat(compute/deploy): add Secret Store to manifest `[setup]` [#769](https://github.com/fastly/cli/pull/769)\n- feat(config): add TypeScript Starter Kit [#908](https://github.com/fastly/cli/pull/908)\n- fix(errors/log): support filtering errors to Sentry [#909](https://github.com/fastly/cli/pull/909)\n- fix(manifest): improve output message for fastly.toml related errors [#910](https://github.com/fastly/cli/pull/910)\n- feat(service): support `--json` for service search subcommand [#911](https://github.com/fastly/cli/pull/911)\n- refactor: consistent `--json` implementation [#912](https://github.com/fastly/cli/pull/912)\n\n**Dependencies:**\n\n- build(deps): bump github.com/otiai10/copy from 1.9.0 to 1.10.0 [#900](https://github.com/fastly/cli/pull/900)\n- build(deps): bump golang.org/x/crypto from 0.7.0 to 0.8.0 [#901](https://github.com/fastly/cli/pull/901)\n- build(deps): bump golang.org/x/term from 0.6.0 to 0.7.0 [#902](https://github.com/fastly/cli/pull/902)\n- build(deps): bump github.com/Masterminds/semver/v3 from 3.2.0 to 3.2.1 [#903](https://github.com/fastly/cli/pull/903)\n\n## [v8.2.4](https://github.com/fastly/cli/releases/tag/v8.2.4) (2023-04-06)\n\n**Enhancements:**\n\n- feat(compute/serve): support forcing a viceroy check [#898](https://github.com/fastly/cli/pull/898)\n\n## [v8.2.3](https://github.com/fastly/cli/releases/tag/v8.2.3) (2023-04-05)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v8.2.2...v8.2.3)\n\n**Enhancements:**\n\n- fix(profile): always prompt for token [#896](https://github.com/fastly/cli/pull/896)\n\n**Dependencies:**\n\n- build(deps): bump github.com/getsentry/sentry-go from 0.19.0 to 0.20.0 [#895](https://github.com/fastly/cli/pull/895)\n- build(deps): bump actions/setup-go from 3 to 4 [#882](https://github.com/fastly/cli/pull/882)\n- build(deps): bump github.com/fatih/color from 1.14.1 to 1.15.0 [#865](https://github.com/fastly/cli/pull/865)\n\n## [v8.2.2](https://github.com/fastly/cli/releases/tag/v8.2.2) (2023-03-31)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v8.2.1...v8.2.2)\n\n**Enhancements:**\n\n- feat(ratelimit): add missing properties [#891](https://github.com/fastly/cli/pull/891)\n- feat(ratelimiter): add uri-dict-name flag [#892](https://github.com/fastly/cli/pull/892)\n\n## [v8.2.1](https://github.com/fastly/cli/releases/tag/v8.2.1) (2023-03-30)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v8.2.0...v8.2.1)\n\n**Dependencies:**\n\n- build(deps): bump tinygo baseline version [#888](https://github.com/fastly/cli/pull/888)\n\n**Bug fixes:**\n\n- fix(toolchain): bump go version to align with updated tinygo constraint [#889](https://github.com/fastly/cli/pull/889)\n\n## [v8.2.0](https://github.com/fastly/cli/releases/tag/v8.2.0) (2023-03-28)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v8.1.2...v8.2.0)\n\n**Enhancements:**\n\n- feat(ratelimit): implement rate-limiter API [#886](https://github.com/fastly/cli/pull/886)\n\n## [v8.1.2](https://github.com/fastly/cli/releases/tag/v8.1.2) (2023-03-21)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v8.1.1...v8.1.2)\n\n**Bug fixes:**\n\n- fix(service/create): input.Type assigned wrong value [#881](https://github.com/fastly/cli/pull/881)\n\n## [v8.1.1](https://github.com/fastly/cli/releases/tag/v8.1.1) (2023-03-20)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v8.1.0...v8.1.1)\n\n**Bug fixes:**\n\n- Pass verbosity flag along to viceroy binary [#878](https://github.com/fastly/cli/pull/878)\n- fix(compute/serve): always display local server address [#879](https://github.com/fastly/cli/pull/879)\n\n## [v8.1.0](https://github.com/fastly/cli/releases/tag/v8.1.0) (2023-03-17)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v8.0.1...v8.1.0)\n\n**Enhancements:**\n\n- fix various lint and CI issues [#873](https://github.com/fastly/cli/pull/873)\n- feat(config-store): Add Config Store commands [#829](https://github.com/fastly/cli/pull/829)\n- fix(compute/deploy): service availability [#875](https://github.com/fastly/cli/pull/875)\n- fix(compute/deploy): check status code range [#876](https://github.com/fastly/cli/pull/876)\n\n## [v8.0.1](https://github.com/fastly/cli/releases/tag/v8.0.1) (2023-03-15)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v8.0.0...v8.0.1)\n\n**Bug fixes:**\n\n- fix(compute/serve): stop spinner before starting another instance [#867](https://github.com/fastly/cli/pull/867)\n- fix(http/client): address confusion with timeout error [#869](https://github.com/fastly/cli/pull/869)\n- fix(http/client): bump timeout to account for poor network conditions [#870](https://github.com/fastly/cli/pull/870)\n\n**Enhancements:**\n\n- refactor(compute/deploy): change default port from 80 to 443 [#866](https://github.com/fastly/cli/pull/866)\n\n## [v8.0.0](https://github.com/fastly/cli/releases/tag/v8.0.0) (2023-03-08)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v7.0.1...v8.0.0)\n\n**Breaking:**\n\nThis release contains a small breaking interface change that has required us to bump to a new major version.\n\nWhen viewing a profile token using `fastly profile token` the `--name` flag is no longer supported. It has been moved to a positional argument to make it consistent with the other profile subcommands. The `profile token` command now respects the global `--profile` flag, which allows a user to switch profiles for the lifetime of a single command execution.\n\nExamples:\n\n- `fastly profile token --name=example` -> `fastly profile token example`\n- `fastly profile token --profile=example`\n\n* breaking(profiles): replace `--name` with positional arg + allow global override [#862](https://github.com/fastly/cli/pull/862)\n\n**Bug fixes:**\n\n- fix(build): show build output with `--verbose` flag [#853](https://github.com/fastly/cli/pull/853)\n\n**Enhancements:**\n\n- fix(profile/update): update active profile as default behaviour [#857](https://github.com/fastly/cli/pull/857)\n- fix(compute/serve): allow overriding of viceroy binary [#859](https://github.com/fastly/cli/pull/859)\n- feat(compute/deploy): check service availability [#860](https://github.com/fastly/cli/pull/860)\n\n**Dependencies:**\n\n- build(deps): bump github.com/getsentry/sentry-go from 0.18.0 to 0.19.0 [#856](https://github.com/fastly/cli/pull/856)\n- build(deps): bump golang.org/x/crypto [#855](https://github.com/fastly/cli/pull/855)\n\n## [v7.0.1](https://github.com/fastly/cli/releases/tag/v7.0.1) (2023-03-02)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v7.0.0...v7.0.1)\n\n**Bug fixes:**\n\n- fix(compute/build): move log calls before subprocess call [#847](https://github.com/fastly/cli/pull/847)\n- fix(compute/serve): ensure spinner is closed for all logic branches [#849](https://github.com/fastly/cli/pull/849)\n\n**Enhancements:**\n\n- feat(dict/create): display dictionary ID on creation [#848](https://github.com/fastly/cli/pull/848)\n- refactor: clean-up nil error checks [#851](https://github.com/fastly/cli/pull/851)\n\n## [v7.0.0](https://github.com/fastly/cli/releases/tag/v7.0.0) (2023-02-23)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v6.0.6...v7.0.0)\n\n**Breaking:**\n\nThere are a couple of small breaking changes to the CLI.\n\nPrior versions of the CLI would consult the following files to ignore specific files while running `compute serve --watch`:\n\n- `.ignore`\n- `.gitignore`\n- The user's global git ignore configuration\n\nWe are dropping support for these files and will instead consult `.fastlyignore`, which is already used by `compute build`.\n\nWe've removed support for the `logging logentries` subcommand as the third-party logging product has been deprecated.\n\n- fix(compute/serve): replace separate ignore files with `.fastlyignore` [#834](https://github.com/fastly/cli/pull/834)\n- breaking(logging): remove logentries [#835](https://github.com/fastly/cli/pull/835)\n\n**Bug fixes:**\n\n- fix(compute/build): ignore all files except manifest and wasm binary [#836](https://github.com/fastly/cli/pull/836)\n- fix(compute/serve): output rendering [#839](https://github.com/fastly/cli/pull/839)\n- Fix compute build rendered output [#842](https://github.com/fastly/cli/pull/842)\n\n**Enhancements:**\n\n- use secret store client keys when creating secret store entries [#805](https://github.com/fastly/cli/pull/805)\n- fix(compute/serve): check for missing override_host [#832](https://github.com/fastly/cli/pull/832)\n- feat(resource-link): Add Service Resource commands [#800](https://github.com/fastly/cli/pull/800)\n- Replace custom spinner with less buggy third-party package [#838](https://github.com/fastly/cli/pull/838)\n- refactor(spinner): hide `...` after spinner has stopped [#840](https://github.com/fastly/cli/pull/840)\n- fix(compute/serve): make override_host a default behaviour [#843](https://github.com/fastly/cli/pull/843)\n\n**Dependencies:**\n\n- build(deps): bump golang.org/x/net from 0.2.0 to 0.7.0 [#830](https://github.com/fastly/cli/pull/830)\n- build(deps): bump github.com/fastly/go-fastly/v7 from 7.2.0 to 7.3.0 [#831](https://github.com/fastly/cli/pull/831)\n\n**Clean-ups:**\n\n- refactor: linter issues resolved [#833](https://github.com/fastly/cli/pull/833)\n\n## [v6.0.6](https://github.com/fastly/cli/releases/tag/v6.0.6) (2023-02-15)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v6.0.5...v6.0.6)\n\n**Bug fixes:**\n\n- build(goreleaser): build with explicit `CGO_ENABLED=0` [#826](https://github.com/fastly/cli/pull/826)\n\n## [v6.0.5](https://github.com/fastly/cli/releases/tag/v6.0.5) (2023-02-15)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v6.0.4...v6.0.5)\n\n**Enhancements:**\n\n- fix(dns): migrate to go1.20 [#824](https://github.com/fastly/cli/pull/824)\n\n## [v6.0.4](https://github.com/fastly/cli/releases/tag/v6.0.4) (2023-02-13)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v6.0.3...v6.0.4)\n\n**Bug fixes:**\n\n- fix(compute/build): only use default build script if none defined [#814](https://github.com/fastly/cli/pull/814)\n- fix(compute/deploy): replace spinner implementation [#820](https://github.com/fastly/cli/pull/820)\n\n**Enhancements:**\n\n- fix(compute/build): ensure build output doesn't show unless --verbose is set [#815](https://github.com/fastly/cli/pull/815)\n\n**Documentation:**\n\n- docs: remove --skip-verification [#816](https://github.com/fastly/cli/pull/816)\n\n**Dependencies:**\n\n- build(deps): bump github.com/fastly/go-fastly/v7 from 7.1.0 to 7.2.0 [#819](https://github.com/fastly/cli/pull/819)\n- build(deps): bump github.com/getsentry/sentry-go from 0.17.0 to 0.18.0 [#818](https://github.com/fastly/cli/pull/818)\n- build(deps): bump golang.org/x/term from 0.4.0 to 0.5.0 [#817](https://github.com/fastly/cli/pull/817)\n\n## [v6.0.3](https://github.com/fastly/cli/releases/tag/v6.0.3) (2023-02-09)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v6.0.2...v6.0.3)\n\n**Bug fixes:**\n\n- fix(compute/setup): fix duplicated domains [#808](https://github.com/fastly/cli/pull/808)\n- fix(setup/domain): allow user to correct a domain already in use [#811](https://github.com/fastly/cli/pull/811)\n\n**Enhancements:**\n\n- build(goreleaser): replace deprecated flag [#807](https://github.com/fastly/cli/pull/807)\n- refactor: add type annotations [#809](https://github.com/fastly/cli/pull/809)\n- build(lint): implement semgrep for local validation [#810](https://github.com/fastly/cli/pull/810)\n\n## [v6.0.2](https://github.com/fastly/cli/releases/tag/v6.0.2) (2023-02-08)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v6.0.1...v6.0.2)\n\n**Bug fixes:**\n\n- fix(compute/build): ensure we only parse stdout from cargo command [#804](https://github.com/fastly/cli/pull/804)\n\n## [v6.0.1](https://github.com/fastly/cli/releases/tag/v6.0.1) (2023-02-08)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v6.0.0...v6.0.1)\n\n**Enhancements:**\n\n- refactor(compute): add command output when there is an error [#801](https://github.com/fastly/cli/pull/801)\n\n## [v6.0.0](https://github.com/fastly/cli/releases/tag/v6.0.0) (2023-02-07)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v5.1.1...v6.0.0)\n\n**Breaking:**\n\nThere are three breaking changes in this release.\n\nThe first comes from the removal of logic related to user environment\nvalidation. This logic existed as an attempt to reduce the number of possible\nerrors when trying to compile a Compute project. The reality was that this\nvalidation logic was tightly coupled to specific expectations of the CLI\n(and of its starter kits) and consequently resulted in errors that were often\ndifficult to understand and debug, as well as restricting users from using their\nown tools and scripts. By simplifying the logic flow we hope to reduce friction\nfor users who want to switch the tooling used, as well as reduce the general\nconfusion caused for users when there are environment validation errors, while\nalso reducing the maintenance overhead for contributors to the CLI code base.\nThis change has meant we no longer need to define a `--skip-validation` flag and\nthat resulted in a breaking interface change.\n\nThe second breaking change is to the `objectstore` command. This has now been\nrenamed to `object-store`. Additionally, there is no `keys`, `get` or `insert`\ncommands for manipulating the object-store entries. These operations have been\nmoved to a separate subcommand `object-store-entry` as this keeps the naming and\nstructural convention consistent with ACLs and Edge Dictionaries.\n\nThe third breaking change is to Edge Dictionaries, which sees the\n`dictionary-item` subcommand renamed to `dictionary-entry`, again for\nconsistency with other similar subcommands.\n\n- Remove user environment validation logic [#785](https://github.com/fastly/cli/pull/785)\n- Rework objectstore subcommand [#792](https://github.com/fastly/cli/pull/792)\n- Rename object store 'keys' to 'entry' for consistency [#795](https://github.com/fastly/cli/pull/795)\n- refactor(dictionaryitem): rename from item to entry [#798](https://github.com/fastly/cli/pull/798)\n\n**Bug fixes:**\n\n- Fix description in the manifest [#788](https://github.com/fastly/cli/pull/788)\n\n**Enhancements:**\n\n- Update `local_server` object and secret store formats [#789](https://github.com/fastly/cli/pull/789)\n\n**Clean-ups:**\n\n- refactor: move global struct and config.Source types into separate packages [#796](https://github.com/fastly/cli/pull/796)\n- refactor(secretstore): divide command files into separate packages [#797](https://github.com/fastly/cli/pull/797)\n\n## [v5.1.1](https://github.com/fastly/cli/releases/tag/v5.1.1) (2023-02-01)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v5.1.0...v5.1.1)\n\n**Bug fixes:**\n\n- fix(compute/build): AssemblyScript bugs [#786](https://github.com/fastly/cli/pull/786)\n\n**Dependencies:**\n\n- Bump github.com/fatih/color from 1.14.0 to 1.14.1 [#783](https://github.com/fastly/cli/pull/783)\n\n## [v5.1.0](https://github.com/fastly/cli/releases/tag/v5.1.0) (2023-01-27)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v5.0.0...v5.1.0)\n\n**Enhancements:**\n\n- Add Secret Store support [#717](https://github.com/fastly/cli/pull/717)\n- refactor(compute/deploy): reduce size of `Exec()` [#775](https://github.com/fastly/cli/pull/775)\n- refactor(compute/deploy): add messaging to explain `[setup]` [#779](https://github.com/fastly/cli/pull/779)\n\n**Bug fixes:**\n\n- fix(objectstore/get): output value unless verbose/json flag passed [#774](https://github.com/fastly/cli/pull/774)\n- fix(compute/init): add warning for paths with spaces [#778](https://github.com/fastly/cli/pull/778)\n- fix(compute/deploy): clean-up new service creation on-error [#776](https://github.com/fastly/cli/pull/776)\n\n**Dependencies:**\n\n- Bump github.com/fatih/color from 1.13.0 to 1.14.0 [#772](https://github.com/fastly/cli/pull/772)\n\n## [v5.0.0](https://github.com/fastly/cli/releases/tag/v5.0.0) (2023-01-19)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v4.6.2...v5.0.0)\n\n**Breaking:**\n\nThe `objectstore` command was incorrectly configured to have a long flag using\na single character (e.g. `--k` and `--v`). These were corrected to `--key` and\n`--value` (and a short flag variant for `-k` was added as well).\n\n- feat(objectstore): add --json support to keys/list subcommands [#762](https://github.com/fastly/cli/pull/762)\n- feat(objectstore/get): implement --json flag for getting key value [#763](https://github.com/fastly/cli/pull/763)\n\n**Enhancements:**\n\n- feat(compute/deploy): add Object Store to manifest \\[setup\\] [#764](https://github.com/fastly/cli/pull/764)\n- feat(compute/build): support locating language manifests outside project directory [#765](https://github.com/fastly/cli/pull/765)\n- feat(compute/serve): implement --watch-dir flag [#758](https://github.com/fastly/cli/pull/758)\n\n**Bug fixes:**\n\n- fix(setup): object_store needs to be linked to service [#767](https://github.com/fastly/cli/pull/767)\n\n**Dependencies:**\n\n- Bump github.com/getsentry/sentry-go from 0.16.0 to 0.17.0 [#759](https://github.com/fastly/cli/pull/759)\n\n## [v4.6.2](https://github.com/fastly/cli/releases/tag/v4.6.2) (2023-01-12)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v4.6.1...v4.6.2)\n\n**Bug fixes:**\n\n- fix(pkg/commands/compute/serve): prevent 386 arch users executing command [#753](https://github.com/fastly/cli/pull/753)\n- build(goreleaser): fix Windows archive generation to include zips [#756](https://github.com/fastly/cli/pull/756)\n\n**Dependencies:**\n\n- Bump golang.org/x/term from 0.3.0 to 0.4.0 [#754](https://github.com/fastly/cli/pull/754)\n\n## [v4.6.1](https://github.com/fastly/cli/releases/tag/v4.6.1) (2023-01-05)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v4.6.0...v4.6.1)\n\n**Bug fixes:**\n\n- fix(pkg/commands/vcl/snippet): set default dynamic value [#751](https://github.com/fastly/cli/pull/751)\n\n**Dependencies:**\n\n- Bump github.com/mattn/go-isatty from 0.0.16 to 0.0.17 [#748](https://github.com/fastly/cli/pull/748)\n\n**Enhancements:**\n\n- build(makefile): add goreleaser target for testing builds locally [#750](https://github.com/fastly/cli/pull/750)\n\n## [v4.6.0](https://github.com/fastly/cli/releases/tag/v4.6.0) (2023-01-03)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v4.5.0...v4.6.0)\n\n**Bug fixes:**\n\n- vcl/snippet: pass AllowActiveLocked if --dynamic was passed [#742](https://github.com/fastly/cli/pull/742)\n\n**Dependencies:**\n\n- Bump goreleaser/goreleaser-action from 3 to 4 [#746](https://github.com/fastly/cli/pull/746)\n\n**Enhancements:**\n\n- Use DevHub endpoint for acquiring CLI/Viceroy metadata [#739](https://github.com/fastly/cli/pull/739)\n\n## [v4.5.0](https://github.com/fastly/cli/releases/tag/v4.5.0) (2022-12-15)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v4.4.1...v4.5.0)\n\n**Documentation:**\n\n- docs(pkg/compute): remove PLC labels from supported languages [#740](https://github.com/fastly/cli/pull/740)\n\n**Enhancements:**\n\n- refactor(pkg/commands/update): move versioner logic to separate package [#735](https://github.com/fastly/cli/pull/735)\n- fix(compute): don't validate js-compute-runtime binary location [#731](https://github.com/fastly/cli/pull/731)\n- Link to Starter Kits during compute init [#730](https://github.com/fastly/cli/pull/730)\n- Update tinygo default build command [#727](https://github.com/fastly/cli/pull/727)\n- CI/Dockerfiles: minor dockerfiles improvements [#722](https://github.com/fastly/cli/pull/722)\n- Switch JavaScript build script based on webpack in package.json prebuild [#728](https://github.com/fastly/cli/pull/728)\n- Add support for TOML secret_store section [#726](https://github.com/fastly/cli/pull/726)\n\n**Dependencies:**\n\n- Bump golang.org/x/term from 0.2.0 to 0.3.0 [#733](https://github.com/fastly/cli/pull/733)\n- Bump github.com/getsentry/sentry-go from 0.15.0 to 0.16.0 [#734](https://github.com/fastly/cli/pull/734)\n- Bump github.com/Masterminds/semver/v3 from 3.1.1 to 3.2.0 [#724](https://github.com/fastly/cli/pull/724)\n\n## [v4.4.1](https://github.com/fastly/cli/releases/tag/v4.4.1) (2022-12-02)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v4.4.0...v4.4.1)\n\n**Bug fixes:**\n\n- Avoid sending empty string to Backend create API [#720](https://github.com/fastly/cli/pull/720)\n\n## [v4.4.0](https://github.com/fastly/cli/releases/tag/v4.4.0) (2022-11-29)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v4.3.0...v4.4.0)\n\n**Enhancements:**\n\n- Add `PrintLines` to `text` package and use in logging [#698](https://github.com/fastly/cli/pull/698)\n- Add dependabot workflow automation for updating dependency [#701](https://github.com/fastly/cli/pull/701)\n- Add account name to pubsub and bigquery [#699](https://github.com/fastly/cli/pull/699)\n\n**Bug fixes:**\n\n- Add missing `--help` flag to globals [#695](https://github.com/fastly/cli/pull/695)\n- Fix check for mutual exclusion flags [#696](https://github.com/fastly/cli/pull/696)\n- Fix object store TOML definitions, add test data [#715](https://github.com/fastly/cli/pull/715)\n\n**Dependencies:**\n\n- Bump github.com/otiai10/copy from 1.7.0 to 1.9.0 [#706](https://github.com/fastly/cli/pull/706)\n- Bump github.com/mholt/archiver/v3 from 3.5.0 to 3.5.1 [#703](https://github.com/fastly/cli/pull/703)\n- Bump github.com/fastly/go-fastly/v6 from 6.6.0 to 6.8.0 [#704](https://github.com/fastly/cli/pull/704)\n- Bump github.com/mattn/go-isatty from 0.0.14 to 0.0.16 [#702](https://github.com/fastly/cli/pull/702)\n- Bump github.com/google/go-cmp from 0.5.6 to 0.5.9 [#708](https://github.com/fastly/cli/pull/708)\n- Bump github.com/mitchellh/mapstructure from 1.4.3 to 1.5.0 [#709](https://github.com/fastly/cli/pull/709)\n- Bump github.com/bep/debounce from 1.2.0 to 1.2.1 [#711](https://github.com/fastly/cli/pull/711)\n- Bump github.com/getsentry/sentry-go from 0.12.0 to 0.15.0 [#712](https://github.com/fastly/cli/pull/712)\n- Bump github.com/pelletier/go-toml from 1.9.3 to 1.9.5 [#710](https://github.com/fastly/cli/pull/710)\n- Bump go-fastly to v7 [#707](https://github.com/fastly/cli/pull/707)\n- Bump github.com/fsnotify/fsnotify from 1.5.1 to 1.6.0 [#716](https://github.com/fastly/cli/pull/716)\n\n## [v4.3.0](https://github.com/fastly/cli/releases/tag/v4.3.0) (2022-10-26)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v4.2.0...v4.3.0)\n\n**Enhancements:**\n\n- Fix release process to not use external config [#688](https://github.com/fastly/cli/pull/688)\n- Skip exit code 1 for 'help' output [#689](https://github.com/fastly/cli/pull/689)\n- Implement dynamic package name [#686](https://github.com/fastly/cli/pull/686)\n- Replace fiddle.fastly.dev with fiddle.fastlydemo.net [#687](https://github.com/fastly/cli/pull/687)\n- Code clean-up [#685](https://github.com/fastly/cli/pull/685)\n- Implement --quiet flag [#690](https://github.com/fastly/cli/pull/690)\n- Make `compute build` respect `--quiet` [#694](https://github.com/fastly/cli/pull/694)\n\n**Bug fixes:**\n\n- Fix runtime panic [#683](https://github.com/fastly/cli/pull/683)\n- Fix runtime panic caused by outdated global flags [#693](https://github.com/fastly/cli/pull/693)\n- Fix runtime panic caused by missing `--help` global flag [#695](https://github.com/fastly/cli/pull/695)\n- Fix check for mutual exclusion flags[#696](https://github.com/fastly/cli/pull/696)\n- Correct installation instructions for Go [#682](https://github.com/fastly/cli/pull/682)\n\n## [v4.2.0](https://github.com/fastly/cli/releases/tag/v4.2.0) (2022-10-18)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v4.1.0...v4.2.0)\n\n**Enhancements:**\n\n- Service Authorization [#660](https://github.com/fastly/cli/pull/660)\n- Add Object Store API calls [#670](https://github.com/fastly/cli/pull/670)\n- Remove upper limit on Go toolchain [#678](https://github.com/fastly/cli/pull/678)\n\n**Bug fixes:**\n\n- Fix `compute pack` to produce expected `package.tar.gz` filename [#662](https://github.com/fastly/cli/pull/662)\n- Fix `--help` flag to not display an error [#672](https://github.com/fastly/cli/pull/672)\n- Fix command substitution issue for Windows OS [#677](https://github.com/fastly/cli/pull/677)\n- Fix Makefile for Windows [#679](https://github.com/fastly/cli/pull/679)\n\n## [v4.1.0](https://github.com/fastly/cli/releases/tag/v4.1.0) (2022-10-11)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v4.0.1...v4.1.0)\n\n**Bug fixes:**\n\n- Fix Rust validation step for fastly crate dependency [#661](https://github.com/fastly/cli/pull/661)\n- Fix `compute build --first-byte-timeout` [#667](https://github.com/fastly/cli/pull/667)\n- Ensure the ./bin directory is present even with `--skip-verification` [#665](https://github.com/fastly/cli/pull/665)\n\n**Enhancements:**\n\n- Reduce duplication of strings in logging package [#653](https://github.com/fastly/cli/pull/653)\n- Support `cert_host` and `use_sni` Viceroy properties [#663](https://github.com/fastly/cli/pull/663)\n\n## [v4.0.1](https://github.com/fastly/cli/releases/tag/v4.0.1) (2022-10-05)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v4.0.0...v4.0.1)\n\n**Bug fixes:**\n\n- Fix JS dependency lookup [#656](https://github.com/fastly/cli/pull/656)\n- Fix Rust 'existing project' bug [#657](https://github.com/fastly/cli/pull/657)\n- Fix Rust toolchain lookup regression [#658](https://github.com/fastly/cli/pull/658)\n\n## [v4.0.0](https://github.com/fastly/cli/releases/tag/v4.0.0) (2022-10-04)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v3.3.0...v4.0.0)\n\n**Enhancements:**\n\n- Bump go-fastly to v6.5.1 [#635](https://github.com/fastly/cli/pull/635)\n- Update `--ssl-ciphers` description [#636](https://github.com/fastly/cli/pull/636)\n- Improve JS error message when a dependency is missing [#637](https://github.com/fastly/cli/pull/637)\n- Change default service version selection behaviour [#638](https://github.com/fastly/cli/pull/638)\n- Support for additional S3 storage classes [#641](https://github.com/fastly/cli/pull/641)\n- Change `compute serve --watch` flag to default to the project root directory [#642](https://github.com/fastly/cli/pull/642)\n- Document the newly supported Datadog sites for logging [#576](https://github.com/fastly/cli/pull/576)\n- Move the internal build scripts to the fastly.toml manifest [#640](https://github.com/fastly/cli/pull/640)\n- Implement `compute hashsum` [#649](https://github.com/fastly/cli/pull/649)\n- Add support for TOML `object_store` section [#651](https://github.com/fastly/cli/pull/651)\n- Add `--account-name` to GCS logging endpoint [#549](https://github.com/fastly/cli/pull/549)\n\n**Bug fixes:**\n\n- errors/log: be defensive against nil pointer dereference [#650](https://github.com/fastly/cli/pull/650)\n\n**Documentation:**\n\n- Fix typos [#652](https://github.com/fastly/cli/pull/652)\n\n## [v3.3.0](https://github.com/fastly/cli/releases/tag/v3.3.0) (2022-09-05)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v3.2.5...v3.3.0)\n\n**Enhancements:**\n\n- TLS Support [#630](https://github.com/fastly/cli/pull/630)\n- CI to use community TinyGo action [#624](https://github.com/fastly/cli/pull/624)\n- Improve compute init remediation [#627](https://github.com/fastly/cli/pull/627)\n- Change default Makefile target [#629](https://github.com/fastly/cli/pull/629)\n- Refactor after remote config removal [#626](https://github.com/fastly/cli/pull/626)\n- Refactor for revive linter [#632](https://github.com/fastly/cli/pull/632)\n\n## [v3.2.5](https://github.com/fastly/cli/releases/tag/v3.2.5) (2022-08-10)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v3.2.4...v3.2.5)\n\n**Enhancements:**\n\n- Remove dynamic configuration [#620](https://github.com/fastly/cli/pull/620)\n- Static analysis updates [#621](https://github.com/fastly/cli/pull/621)\n- Semgrep updates [#619](https://github.com/fastly/cli/pull/619)\n\n**Bug fixes:**\n\n- Fix `fastly help` tests to work with Go 1.19 [#623](https://github.com/fastly/cli/pull/623)\n\n## [v3.2.4](https://github.com/fastly/cli/releases/tag/v3.2.4) (2022-07-28)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v3.2.3...v3.2.4)\n\n**Bug fixes:**\n\n- Fix `--completion-script-zsh` [#617](https://github.com/fastly/cli/pull/617)\n\n## [v3.2.3](https://github.com/fastly/cli/releases/tag/v3.2.3) (2022-07-28)\n\n[Full Changelog](https://github.com/fastly/cli/releases/tag/v3.2.2...v3.2.3)\n\n**Bug fixes:**\n\n- Block for config update if CLI version updated [#615](https://github.com/fastly/cli/pull/615)\n\n## [v3.2.2](https://github.com/fastly/cli/releases/tag/v3.2.2) (2022-07-28)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v3.2.1...v3.2.2)\n\n**Bug fixes:**\n\n- Ignore TTL & update config if app version doesn't match config version [#612](https://github.com/fastly/cli/pull/612)\n\n## [v3.2.1](https://github.com/fastly/cli/releases/tag/v3.2.1) (2022-07-27)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v3.2.0...v3.2.1)\n\n**Enhancements:**\n\n- Print subprocess commands in verbose mode [#608](https://github.com/fastly/cli/pull/608)\n\n**Bug fixes:**\n\n- Ensure application configuration is updated after `fastly update` [#610](https://github.com/fastly/cli/pull/610)\n- Don't include language manifest in packages [#609](https://github.com/fastly/cli/pull/609)\n\n## [v3.2.0](https://github.com/fastly/cli/releases/tag/v3.2.0) (2022-07-27)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v3.1.1...v3.2.0)\n\n**Enhancements:**\n\n- Compute@Edge TinyGo Support [#594](https://github.com/fastly/cli/pull/594)\n- `pkg/commands/profile`: add `--json` flag for `list` command [#602](https://github.com/fastly/cli/pull/602)\n\n**Bug fixes:**\n\n- `pkg/commands/compute/deploy.go` (`getHashSum`): sort key order [#596](https://github.com/fastly/cli/pull/596)\n- `pkg/errors/log.go`: prevent runtime panic due to a `nil` reference [#601](https://github.com/fastly/cli/pull/601)\n\n## [v3.1.1](https://github.com/fastly/cli/releases/tag/v3.1.1) (2022-07-04)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v3.1.0...v3.1.1)\n\n**Enhancements:**\n\n- Handle build info more accurately [#588](https://github.com/fastly/cli/pull/588)\n\n**Bug fixes:**\n\n- Fix version check [#589](https://github.com/fastly/cli/pull/589)\n- Address profile data loss [#590](https://github.com/fastly/cli/pull/590)\n\n## [v3.1.0](https://github.com/fastly/cli/releases/tag/v3.1.0) (2022-06-24)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v3.0.1...v3.1.0)\n\n**Enhancements:**\n\n- Implement `profile token` command [#578](https://github.com/fastly/cli/pull/578)\n\n**Bug fixes:**\n\n- Fix `compute publish --non-interactive` [#583](https://github.com/fastly/cli/pull/583)\n- Support `fastly --help <command>` format [#581](https://github.com/fastly/cli/pull/581)\n\n## [v3.0.1](https://github.com/fastly/cli/releases/tag/v3.0.1) (2022-06-23)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v3.0.0...v3.0.1)\n\n**Enhancements:**\n\n- Makefile: when building binary, depend on .go files [#579](https://github.com/fastly/cli/pull/579)\n- Include `fastly.toml` hashsum [#575](https://github.com/fastly/cli/pull/575)\n- Hash main.wasm and not the package [#574](https://github.com/fastly/cli/pull/574)\n\n## [v3.0.0](https://github.com/fastly/cli/releases/tag/v3.0.0) (2022-05-30)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v2.0.3...v3.0.0)\n\n**Breaking changes:**\n\n- Implement new global flags for handling interactive prompts [#568](https://github.com/fastly/cli/pull/568)\n\n**Bug fixes:**\n\n- The `backend create` command should set `--port` value if specified [#566](https://github.com/fastly/cli/pull/566)\n- Don't overwrite `file.Load` error with `nil` [#569](https://github.com/fastly/cli/pull/569)\n\n**Enhancements:**\n\n- Support `[scripts.post_build]` [#565](https://github.com/fastly/cli/pull/565)\n- Support Viceroy \"inline-toml\" `format` and new `contents` field [#567](https://github.com/fastly/cli/pull/567)\n- Add example inline-toml dictionary to tests [#570](https://github.com/fastly/cli/pull/570)\n- Avoid config update checks when handling 'completion' flags [#554](https://github.com/fastly/cli/pull/554)\n\n## [v2.0.3](https://github.com/fastly/cli/releases/tag/v2.0.3) (2022-05-20)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v2.0.2...v2.0.3)\n\n**Bug fixes:**\n\n- Update Sentry DSN [#563](https://github.com/fastly/cli/pull/563)\n\n## [v2.0.2](https://github.com/fastly/cli/releases/tag/v2.0.2) (2022-05-18)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v2.0.1...v2.0.2)\n\n**Enhancements:**\n\n- Remove user identifiable data [#561](https://github.com/fastly/cli/pull/561)\n\n## [v2.0.1](https://github.com/fastly/cli/releases/tag/v2.0.1) (2022-05-17)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v2.0.0...v2.0.1)\n\n**Security:**\n\n- Omit data from Sentry [#559](https://github.com/fastly/cli/pull/559)\n\n## [v2.0.0](https://github.com/fastly/cli/releases/tag/v2.0.0) (2022-04-05)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v1.7.1...v2.0.0)\n\n**Bug fixes:**\n\n- Update `--request-max-entries`/`--request-max-bytes` description defaults [#541](https://github.com/fastly/cli/pull/541)\n\n**Enhancements:**\n\n- Add `--debug` flag to `compute serve` [#545](https://github.com/fastly/cli/pull/545)\n- Support multiple profiles via `[profiles]` configuration [#546](https://github.com/fastly/cli/pull/546)\n- Reorder C@E languages and make JS 'Limited Availability' [#548](https://github.com/fastly/cli/pull/548)\n\n## [v1.7.1](https://github.com/fastly/cli/releases/tag/v1.7.1) (2022-03-14)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v1.7.0...v1.7.1)\n\n**Bug fixes:**\n\n- Fix runtime panic in arg parser [#542](https://github.com/fastly/cli/pull/542)\n\n## [v1.7.0](https://github.com/fastly/cli/releases/tag/v1.7.0) (2022-02-22)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v1.6.0...v1.7.0)\n\n**Enhancements:**\n\n- Add `fastly` user to Dockerfiles [#521](https://github.com/fastly/cli/pull/521)\n- Support Sentry 'suspect commit' feature [#508](https://github.com/fastly/cli/pull/508)\n- Populate language manifest `name` field with project name [#527](https://github.com/fastly/cli/pull/527)\n- Make `--name` flag for `service search` command a required flag [#529](https://github.com/fastly/cli/pull/529)\n- Update config `last_checked` field even on config load error [#528](https://github.com/fastly/cli/pull/528)\n- Implement Compute@Edge Free Trial Activation [#531](https://github.com/fastly/cli/pull/531)\n- Bump Rust toolchain constraint to `1.56.1` for 2021 edition [#533](https://github.com/fastly/cli/pull/533)\n- Support Arch User Repositories [#530](https://github.com/fastly/cli/pull/530)\n\n## [v1.6.0](https://github.com/fastly/cli/releases/tag/v1.6.0) (2022-01-20)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v1.5.0...v1.6.0)\n\n**Enhancements:**\n\n- Display the requested command in Sentry breadcrumb [#519](https://github.com/fastly/cli/pull/519)\n\n## [v1.5.0](https://github.com/fastly/cli/releases/tag/v1.5.0) (2022-01-20)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v1.4.0...v1.5.0)\n\n**Enhancements:**\n\n- Implement `--json` output for describe/list operations [#505](https://github.com/fastly/cli/pull/505)\n- Omit unix file permissions from error message [#507](https://github.com/fastly/cli/pull/507)\n- Implement new go-fastly pagination types [#511](https://github.com/fastly/cli/pull/511)\n\n## [v1.4.0](https://github.com/fastly/cli/releases/tag/v1.4.0) (2022-01-07)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v1.3.0...v1.4.0)\n\n**Enhancements:**\n\n- Add `viceroy.ttl` to CLI app config [#489](https://github.com/fastly/cli/pull/489)\n- Display `viceroy --version` if installed [#487](https://github.com/fastly/cli/pull/487)\n- Support `compute build` for 'other' language option using `[scripts.build]` [#484](https://github.com/fastly/cli/pull/484)\n- Pass parent environment to subprocess [#491](https://github.com/fastly/cli/pull/491)\n- Implement a yes/no user prompt abstraction [#500](https://github.com/fastly/cli/pull/500)\n- Ensure build compilation errors are displayed [#492](https://github.com/fastly/cli/pull/492)\n- Implement `--service-name` as swap-in replacement for `--service-id` [#495](https://github.com/fastly/cli/pull/495)\n- Support `FASTLY_CUSTOMER_ID` environment variable [#494](https://github.com/fastly/cli/pull/494)\n- Support `gotest` [#501](https://github.com/fastly/cli/pull/501)\n\n**Bug fixes:**\n\n- Fix the `--watch` flag for AssemblyScript [#493](https://github.com/fastly/cli/pull/493)\n- Fix `compute init --from` for Windows [#490](https://github.com/fastly/cli/pull/490)\n- Avoid triggering GitHub API rate limit when running Viceroy in CI [#488](https://github.com/fastly/cli/pull/488)\n- Fix Windows ANSI escape code rendering [#503](https://github.com/fastly/cli/pull/503)\n- Prevent runtime panic when global flag set with no command [#502](https://github.com/fastly/cli/pull/502)\n\n## [v1.3.0](https://github.com/fastly/cli/releases/tag/v1.3.0) (2021-12-01)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v1.2.0...v1.3.0)\n\n**Enhancements:**\n\n- Implement custom `[scripts.build]` operation [#480](https://github.com/fastly/cli/pull/480)\n- Move `manifest` package into top-level `pkg` directory [#478](https://github.com/fastly/cli/pull/478)\n- Refactor AssemblyScript logic to call out to the JavaScript implementation [#479](https://github.com/fastly/cli/pull/479)\n\n## [v1.2.0](https://github.com/fastly/cli/releases/tag/v1.2.0) (2021-11-25)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v1.1.1...v1.2.0)\n\n**Enhancements:**\n\n- Implement `SEE ALSO` section in help output [#472](https://github.com/fastly/cli/pull/472)\n- Add command 'API' metadata [#473](https://github.com/fastly/cli/pull/473)\n\n## [v1.1.1](https://github.com/fastly/cli/releases/tag/v1.1.1) (2021-11-11)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v1.1.0...v1.1.1)\n\n**Bug fixes:**\n\n- Avoid displaying a wildcard domain [#468](https://github.com/fastly/cli/pull/468)\n- Set sensible defaults for host related flags on `backend create` [#469](https://github.com/fastly/cli/pull/469)\n- Fix error extracting package files from `.tgz` archive [#470](https://github.com/fastly/cli/pull/470)\n\n## [v1.1.0](https://github.com/fastly/cli/releases/tag/v1.1.0) (2021-11-08)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v1.0.1...v1.1.0)\n\n**Enhancements:**\n\n- Implement `--watch` flag for `compute serve` [#464](https://github.com/fastly/cli/pull/464)\n\n## [v1.0.1](https://github.com/fastly/cli/releases/tag/v1.0.1) (2021-11-08)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v1.0.0...v1.0.1)\n\n**Bug fixes:**\n\n- Allow git repo to be used as a value at the starter kit prompt [#465](https://github.com/fastly/cli/pull/465)\n\n## [v1.0.0](https://github.com/fastly/cli/releases/tag/v1.0.0) (2021-11-02)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v0.43.0...v1.0.0)\n\n**Changed:**\n\n- Use `EnumsVar` for `auth-token --scope` [#447](https://github.com/fastly/cli/pull/447)\n- Rename `logs tail` to `log-tail` [#456](https://github.com/fastly/cli/pull/456)\n- Rename `dictionaryitem` to `dictionary-item` [#459](https://github.com/fastly/cli/pull/459)\n- Rename `fastly compute ... --path` flags [#460](https://github.com/fastly/cli/pull/460)\n- Rename `--force` to `--skip-verification` [#461](https://github.com/fastly/cli/pull/461)\n\n## [v0.43.0](https://github.com/fastly/cli/releases/tag/v0.43.0) (2021-11-01)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v0.42.0...v0.43.0)\n\n**Bug fixes:**\n\n- Ignore possible `rustup` 'sync' output when calling `rustc --version` [#453](https://github.com/fastly/cli/pull/453)\n- Bump goreleaser to avoid Homebrew warning [#455](https://github.com/fastly/cli/pull/455)\n- Prevent `.Hidden()` flags/commands from being documented via `--format json` [#454](https://github.com/fastly/cli/pull/454)\n\n## [v0.42.0](https://github.com/fastly/cli/releases/tag/v0.42.0) (2021-10-22)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v0.41.0...v0.42.0)\n\n**Enhancements:**\n\n- Fallback to existing viceroy binary in case of network error [#445](https://github.com/fastly/cli/pull/445)\n- Remove `text.ServiceType` abstraction [#449](https://github.com/fastly/cli/pull/449)\n\n**Bug fixes:**\n\n- Avoid fetching packages when manifest exists [#448](https://github.com/fastly/cli/pull/448)\n- Implement `--path` lookup fallback for manifest [#446](https://github.com/fastly/cli/pull/446)\n- Fix broken Windows support [#450](https://github.com/fastly/cli/pull/450)\n\n## [v0.41.0](https://github.com/fastly/cli/releases/tag/v0.41.0) (2021-10-19)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v0.40.2...v0.41.0)\n\n**Enhancements:**\n\n- Implement `[setup.log_endpoints.<name>]` support [#443](https://github.com/fastly/cli/pull/443)\n- The `compute init --from` flag should support archives [#428](https://github.com/fastly/cli/pull/428)\n- Add region support for logentries logging endpoint [#375](https://github.com/fastly/cli/pull/375)\n\n## [v0.40.2](https://github.com/fastly/cli/releases/tag/v0.40.2) (2021-10-14)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v0.40.1...v0.40.2)\n\n**Bug fixes:**\n\n- Fix shell autocomplete evaluation [#441](https://github.com/fastly/cli/pull/441)\n\n## [v0.40.1](https://github.com/fastly/cli/releases/tag/v0.40.1) (2021-10-14)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v0.40.0...v0.40.1)\n\n**Bug fixes:**\n\n- Fix shell completion (and Homebrew upgrade) [#439](https://github.com/fastly/cli/pull/439)\n\n## [v0.40.0](https://github.com/fastly/cli/releases/tag/v0.40.0) (2021-10-13)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v0.39.3...v0.40.0)\n\n**Bug fixes:**\n\n- Auto-migrate `manifest_version` from 1 to 2 when applicable [#434](https://github.com/fastly/cli/pull/434)\n- Better error handling for manifest parsing [#436](https://github.com/fastly/cli/pull/436)\n\n**Enhancements:**\n\n- Implement `[setup.dictionaries]` support [#431](https://github.com/fastly/cli/pull/431)\n- Tests for `[setup.dictionaries]` support [#438](https://github.com/fastly/cli/pull/438)\n- Refactor `app.Run()` [#429](https://github.com/fastly/cli/pull/429)\n- Ensure manifest is read only once for all missed references [#433](https://github.com/fastly/cli/pull/433)\n\n## [v0.39.3](https://github.com/fastly/cli/releases/tag/v0.39.3) (2021-10-06)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v0.39.2...v0.39.3)\n\n**Bug fixes:**\n\n- Add missing description for `user list --customer-id` [#425](https://github.com/fastly/cli/pull/425)\n- Trim the rust version to fix parse errors [#427](https://github.com/fastly/cli/pull/427)\n\n**Enhancements:**\n\n- Abstraction layer for `[setup.backends]` [#421](https://github.com/fastly/cli/pull/421)\n\n## [v0.39.2](https://github.com/fastly/cli/releases/tag/v0.39.2) (2021-09-29)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v0.39.1...v0.39.2)\n\n**Bug fixes:**\n\n- Provide better remediation for unrecognised `manifest_version` [#422](https://github.com/fastly/cli/pull/422)\n- Bump `go-fastly` to `v5.0.0` to fix ACL entries bug [#417](https://github.com/fastly/cli/pull/417)\n- Remove Rust debug flags [#416](https://github.com/fastly/cli/pull/416)\n\n**Enhancements:**\n\n- Clarify Starter Kit options in `compute init` flow [#418](https://github.com/fastly/cli/pull/418)\n- Avoid excessive manifest reads [#420](https://github.com/fastly/cli/pull/420)\n\n## [v0.39.1](https://github.com/fastly/cli/releases/tag/v0.39.1) (2021-09-21)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v0.39.0...v0.39.1)\n\n**Bug fixes:**\n\n- Bug fixes for `auth-token` [#413](https://github.com/fastly/cli/pull/413)\n\n## [v0.39.0](https://github.com/fastly/cli/releases/tag/v0.39.0) (2021-09-21)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v0.38.0...v0.39.0)\n\n**Enhancements:**\n\n- Implement `user` commands [#406](https://github.com/fastly/cli/pull/406)\n- Implement `auth-token` commands [#409](https://github.com/fastly/cli/pull/409)\n- Add region support for New Relic logging endpoint [#378](https://github.com/fastly/cli/pull/378)\n\n**Bug fixes:**\n\n- Add the `--name` flag to `compute deploy` [#410](https://github.com/fastly/cli/pull/410)\n\n## [v0.38.0](https://github.com/fastly/cli/releases/tag/v0.38.0) (2021-09-15)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v0.37.1...v0.38.0)\n\n**Enhancements:**\n\n- Add support for `override_host` to Local Server configuration [#394](https://github.com/fastly/cli/pull/394)\n- Add support for Dictionaries to Local Server configuration [#395](https://github.com/fastly/cli/pull/395)\n- Integrate domain validation [#402](https://github.com/fastly/cli/pull/402)\n- Refactor Versioner `GitHub.Download()` logic [#403](https://github.com/fastly/cli/pull/403)\n\n**Bug fixes:**\n\n- Pass down `compute publish --name` to `compute deploy` [#398](https://github.com/fastly/cli/pull/398)\n- Sanitise name when packing the wasm file [#401](https://github.com/fastly/cli/pull/401)\n- Use a non-interactive progress writer in non-TTY environments [#405](https://github.com/fastly/cli/pull/405)\n\n**Removed:**\n\n- Remove support for Scoop, the Window's command-line installer [#396](https://github.com/fastly/cli/pull/396)\n- Remove unused 'rename local binary' code [#399](https://github.com/fastly/cli/pull/399)\n\n## [v0.37.1](https://github.com/fastly/cli/releases/tag/v0.37.1) (2021-09-06)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v0.37.0...v0.37.1)\n\n**Enhancements:**\n\n- Bump `go-github` dependency to latest release [#388](https://github.com/fastly/cli/pull/388)\n- Add Service ID to `--verbose` output [#383](https://github.com/fastly/cli/pull/383)\n\n**Bug fixes:**\n\n- Download Viceroy to a _randomly_ generated directory [#386](https://github.com/fastly/cli/pull/386)\n- Bug fix for ensuring assets are downloaded into a randomly generated directory [#389](https://github.com/fastly/cli/pull/389)\n\n## [v0.37.0](https://github.com/fastly/cli/releases/tag/v0.37.0) (2021-09-03)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v0.36.0...v0.37.0)\n\n**Enhancements:**\n\n- Update CLI config using flag on `update` command [#382](https://github.com/fastly/cli/pull/382)\n- Validate package size doesn't exceed limit [#380](https://github.com/fastly/cli/pull/380)\n- Log tailing should respect the configured `--endpoint` [#374](https://github.com/fastly/cli/pull/374)\n- Support Windows arm64 [#372](https://github.com/fastly/cli/pull/372)\n- Refactor compute deploy logic to better support `[setup]` configuration [#370](https://github.com/fastly/cli/pull/370)\n- Omit messaging when using `--accept-defaults` [#366](https://github.com/fastly/cli/pull/366)\n- Implement `[setup]` configuration for backends [#355](https://github.com/fastly/cli/pull/355)\n- Refactor code to help CI performance [#360](https://github.com/fastly/cli/pull/360)\n\n**Bug fixes:**\n\n- Add executable permissions to Viceroy binary after renaming/moving it [#368](https://github.com/fastly/cli/pull/368)\n- Update rust toolchain validation steps [#371](https://github.com/fastly/cli/pull/371)\n\n**Security:**\n\n- Update dependency to avoid dependabot warning in GitHub UI [#381](https://github.com/fastly/cli/pull/381)\n\n## [v0.36.0](https://github.com/fastly/cli/releases/tag/v0.36.0) (2021-07-30)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v0.35.0...v0.36.0)\n\n**Enhancements:**\n\n- Implement `logging newrelic` command [#354](https://github.com/fastly/cli/pull/354)\n\n## [v0.35.0](https://github.com/fastly/cli/releases/tag/v0.35.0) (2021-07-29)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v0.34.0...v0.35.0)\n\n**Enhancements:**\n\n- Support for Compute@Edge JS SDK (Beta) [#347](https://github.com/fastly/cli/pull/347)\n- Implement `--override-host` and `--ssl-sni-hostname` [#352](https://github.com/fastly/cli/pull/352)\n- Implement `acl` command [#350](https://github.com/fastly/cli/pull/350)\n- Implement `acl-entry` command [#351](https://github.com/fastly/cli/pull/351)\n- Separate command files from other logic files [#349](https://github.com/fastly/cli/pull/349)\n- Log a record of errors to disk [#340](https://github.com/fastly/cli/pull/340)\n\n**Bug fixes:**\n\n- Fix nondeterministic flag parsing [#353](https://github.com/fastly/cli/pull/353)\n- Fix `compute serve --addr` description to reference port requirement [#348](https://github.com/fastly/cli/pull/348)\n\n## [v0.34.0](https://github.com/fastly/cli/releases/tag/v0.34.0) (2021-07-16)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v0.33.0...v0.34.0)\n\n**Enhancements:**\n\n- Implement `compute serve` subcommand [#252](https://github.com/fastly/cli/pull/252)\n- Simplify logic for prefixing fastly spec to file [#345](https://github.com/fastly/cli/pull/345)\n- `fastly compute publish` and `deploy` should accept a comment [#328](https://github.com/fastly/cli/pull/328)\n- Improve GitHub Actions intermittent test timeouts [#336](https://github.com/fastly/cli/pull/336)\n- New flags for displaying the CLI config, and its location [#338](https://github.com/fastly/cli/pull/338)\n- Don't allow stats short help to wrap [#331](https://github.com/fastly/cli/pull/331)\n\n**Bug fixes:**\n\n- Ensure incompatibility message only shown when config is invalid [#335](https://github.com/fastly/cli/pull/335)\n- Check-in static config for traditional golang workflows [#337](https://github.com/fastly/cli/pull/337)\n\n## [v0.33.0](https://github.com/fastly/cli/releases/tag/v0.33.0) (2021-07-06)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v0.32.0...v0.33.0)\n\n**Enhancements:**\n\n- Improve CI workflow [#333](https://github.com/fastly/cli/pull/333)\n- Support multiple versions of Rust [#330](https://github.com/fastly/cli/pull/330)\n- Replace `app.Run` positional signature with a struct [#329](https://github.com/fastly/cli/pull/329)\n- Test suite improvements [#327](https://github.com/fastly/cli/pull/327)\n\n## [v0.32.0](https://github.com/fastly/cli/releases/tag/v0.32.0) (2021-06-30)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v0.31.0...v0.32.0)\n\n**Enhancements:**\n\n- Embed app config into compiled CLI binary [#312](https://github.com/fastly/cli/pull/312)\n- Service ID lookup includes `$FASTLY_SERVICE_ID` environment variable [#320](https://github.com/fastly/cli/pull/320)\n- Implement `vcl custom` commands [#310](https://github.com/fastly/cli/pull/310)\n- Implement `vcl snippet` commands [#316](https://github.com/fastly/cli/pull/316)\n- Implement `purge` command [#323](https://github.com/fastly/cli/pull/323)\n\n**Bug fixes:**\n\n- Correctly set the port if `--use-ssl` is used [#317](https://github.com/fastly/cli/pull/317)\n- Fixed a regression in `compute publish` [#321](https://github.com/fastly/cli/pull/321)\n\n## [v0.31.0](https://github.com/fastly/cli/releases/tag/v0.31.0) (2021-06-17)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v0.30.0...v0.31.0)\n\n**Enhancements:**\n\n- Add new `pops` command [#309](https://github.com/fastly/cli/pull/309)\n- Add new `ip-list` command [#308](https://github.com/fastly/cli/pull/308)\n- Implement new `--version` and `--autoclone` flags [#302](https://github.com/fastly/cli/pull/302)\n- Reword `backend create --use-ssl` warning output [#303](https://github.com/fastly/cli/pull/303)\n- Define new `--version` and `--autoclone` flags [#300](https://github.com/fastly/cli/pull/300)\n- Implement remediation for dynamic config context deadline error [#298](https://github.com/fastly/cli/pull/298)\n- Capitalise 'n' for `[y/N]` prompt [#299](https://github.com/fastly/cli/pull/299)\n- Move exec behaviour from `common` package to its own package [#297](https://github.com/fastly/cli/pull/297)\n- Move command behaviour from `common` package to its own package [#296](https://github.com/fastly/cli/pull/296)\n- Move time behaviour from `common` package to its own package [#295](https://github.com/fastly/cli/pull/295)\n- Move sync behaviour from `common` package to its own package [#294](https://github.com/fastly/cli/pull/294)\n- Move undo behaviour from `common` package to its own package [#293](https://github.com/fastly/cli/pull/293)\n- Surface any cargo metadata errors [#286](https://github.com/fastly/cli/pull/286)\n\n**Bug fixes:**\n\n- Don't persist `--service-id` flag value to manifest [#307](https://github.com/fastly/cli/pull/307)\n- Fix broken `--service-id` flag in `compute publish` [#292](https://github.com/fastly/cli/pull/292)\n- Fix parsing backend port number [#291](https://github.com/fastly/cli/pull/291)\n\n**Documentation:**\n\n- Update broken link in `stats historical` [#285](https://github.com/fastly/cli/pull/285)\n\n## [v0.30.0](https://github.com/fastly/cli/releases/tag/v0.30.0) (2021-05-19)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v0.29.0...v0.30.0)\n\n**Enhancements:**\n\n- Update messaging for `rustup self update` [#281](https://github.com/fastly/cli/pull/281)\n- Replace archived go-git dependency [#283](https://github.com/fastly/cli/pull/283)\n- Implement `pack` subcommand [#282](https://github.com/fastly/cli/pull/282)\n\n## [v0.29.0](https://github.com/fastly/cli/releases/tag/v0.29.0) (2021-05-13)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v0.28.0...v0.29.0)\n\n**Enhancements:**\n\n- Add arm64 to macOS build [#277](https://github.com/fastly/cli/pull/277)\n\n**Bug fixes:**\n\n- Validate package before prompting inside `compute deploy` flow [#279](https://github.com/fastly/cli/pull/279)\n- Clear Service ID from manifest when service is deleted [#278](https://github.com/fastly/cli/pull/278)\n\n## [v0.28.0](https://github.com/fastly/cli/releases/tag/v0.28.0) (2021-05-11)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v0.27.2...v0.28.0)\n\n**Enhancements:**\n\n- Add `isBool` to command flags [#267](https://github.com/fastly/cli/pull/267)\n- Move service creation to `fastly compute deploy`. [#266](https://github.com/fastly/cli/pull/266)\n\n**Bug fixes:**\n\n- Fix runtime panic when dealing with empty manifest. [#274](https://github.com/fastly/cli/pull/274)\n- Fix `--force` flag not being respected. [#272](https://github.com/fastly/cli/pull/272)\n- Clean-out `service_id` from manifest when deleting a service. [#268](https://github.com/fastly/cli/pull/268)\n\n## [v0.27.2](https://github.com/fastly/cli/releases/tag/v0.27.2) (2021-04-21)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v0.27.1...v0.27.2)\n\n**Bug fixes:**\n\n- Fix bug where legacy creds are reset after call to configure subcommand. [#260](https://github.com/fastly/cli/pull/260)\n\n## [v0.27.1](https://github.com/fastly/cli/releases/tag/v0.27.1) (2021-04-16)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v0.27.0...v0.27.1)\n\n**Bug fixes:**\n\n- Track CLI version. [#257](https://github.com/fastly/cli/pull/257)\n\n## [v0.27.0](https://github.com/fastly/cli/releases/tag/v0.27.0) (2021-04-15)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v0.26.3...v0.27.0)\n\n**Enhancements:**\n\n- Support IAM role in Kinesis logging endpoint [#255](https://github.com/fastly/cli/pull/255)\n- Support IAM role in S3 and Kinesis logging endpoints [#253](https://github.com/fastly/cli/pull/253)\n- Add support for `file_max_bytes` configuration for Azure logging endpoint [#251](https://github.com/fastly/cli/pull/251)\n- Warn on empty directory [#247](https://github.com/fastly/cli/pull/247)\n- Add `compute publish` subcommand [#242](https://github.com/fastly/cli/pull/242)\n- Allow local binary to be renamed [#240](https://github.com/fastly/cli/pull/240)\n- Retain `RUSTFLAGS` values from the environment [#239](https://github.com/fastly/cli/pull/239)\n- Make GitHub Versioner configurable [#236](https://github.com/fastly/cli/pull/236)\n- Add support for `compression_codec` to logging file sink endpoints [#190](https://github.com/fastly/cli/pull/190)\n\n**Bug fixes:**\n\n- Remove flaky test logic. [#249](https://github.com/fastly/cli/pull/249)\n- Check the rustup version [#248](https://github.com/fastly/cli/pull/248)\n- Print all commands and subcommands in usage [#244](https://github.com/fastly/cli/pull/244)\n- pkg/logs: fix typo in error message [#238](https://github.com/fastly/cli/pull/238)\n\n## [v0.26.3](https://github.com/fastly/cli/releases/tag/v0.26.3) (2021-03-26)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v0.26.2...v0.26.3)\n\n**Enhancements:**\n\n- Default to port 443 if UseSSL set. [#234](https://github.com/fastly/cli/pull/234)\n\n**Bug fixes:**\n\n- Ensure all UPDATE operations don't set optional fields. [#235](https://github.com/fastly/cli/pull/235)\n- Avoid setting fields that cause API to fail when given zero value. [#233](https://github.com/fastly/cli/pull/233)\n\n## [v0.26.2](https://github.com/fastly/cli/releases/tag/v0.26.2) (2021-03-22)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v0.26.1...v0.26.2)\n\n**Enhancements:**\n\n- Extra error handling around loading remote configuration data. [#229](https://github.com/fastly/cli/pull/229)\n\n**Bug fixes:**\n\n- `fastly compute build` exits with error 1 [#227](https://github.com/fastly/cli/issues/227)\n- Set GOVERSION for goreleaser. [#228](https://github.com/fastly/cli/pull/228)\n\n## [v0.26.1](https://github.com/fastly/cli/releases/tag/v0.26.1) (2021-03-19)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v0.26.0...v0.26.1)\n\n**Bug fixes:**\n\n- Fix manifest_version as a section bug. [#225](https://github.com/fastly/cli/pull/225)\n\n## [v0.26.0](https://github.com/fastly/cli/releases/tag/v0.26.0) (2021-03-18)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v0.25.2...v0.26.0)\n\n**Enhancements:**\n\n- Remove version from fastly.toml manifest. [#222](https://github.com/fastly/cli/pull/222)\n- Don't run \"cargo update\" before building rust app. [#221](https://github.com/fastly/cli/pull/221)\n\n**Bug fixes:**\n\n- Loading remote config.toml should fail gracefully. [#223](https://github.com/fastly/cli/pull/223)\n- Update the fastly.toml manifest if missing manifest_version. [#220](https://github.com/fastly/cli/pull/220)\n- Refactor UserAgent. [#219](https://github.com/fastly/cli/pull/219)\n\n## [v0.25.2](https://github.com/fastly/cli/releases/tag/v0.25.2) (2021-03-16)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v0.25.1...v0.25.2)\n\n**Bug fixes:**\n\n- Fix duplicate warning messages and missing SetOutput(). [#216](https://github.com/fastly/cli/pull/216)\n\n## [v0.25.1](https://github.com/fastly/cli/releases/tag/v0.25.1) (2021-03-16)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v0.25.0...v0.25.1)\n\n**Bug fixes:**\n\n- The manifest_version should default to 1 if missing. [#214](https://github.com/fastly/cli/pull/214)\n\n## [v0.25.0](https://github.com/fastly/cli/releases/tag/v0.25.0) (2021-03-16)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v0.24.2...v0.25.0)\n\n**Enhancements:**\n\n- Replace deprecated ioutil functions with go 1.16. [#212](https://github.com/fastly/cli/pull/212)\n- Replace TOML parser [#211](https://github.com/fastly/cli/pull/211)\n- Implement manifest_version into the fastly.toml [#210](https://github.com/fastly/cli/pull/210)\n- Dynamic Configuration [#187](https://github.com/fastly/cli/pull/187)\n\n**Bug fixes:**\n\n- Log output should be simplified when running in CI [#175](https://github.com/fastly/cli/issues/175)\n- Override error message in compute init [#204](https://github.com/fastly/cli/pull/204)\n\n## [v0.24.2](https://github.com/fastly/cli/releases/tag/v0.24.2) (2021-02-15)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v0.24.1...v0.24.2)\n\n**Bug fixes:**\n\n- Fix CI binary overlap [#209](https://github.com/fastly/cli/pull/209)\n- Fix CI workflow by switching from old syntax to new [#208](https://github.com/fastly/cli/pull/208)\n- Fix goreleaser version lookup [#207](https://github.com/fastly/cli/pull/207)\n- LogTail: Properly close response body [#205](https://github.com/fastly/cli/pull/205)\n- Add port prompt for compute init [#203](https://github.com/fastly/cli/pull/203)\n- Update GitHub Action to not use commit hash [#200](https://github.com/fastly/cli/pull/200)\n\n## [v0.24.1](https://github.com/fastly/cli/releases/tag/v0.24.1) (2021-02-03)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v0.24.0...v0.24.1)\n\n**Bug fixes:**\n\n- Logs Tail: Give the user better feedback when --from flag errors [#201](https://github.com/fastly/cli/pull/201)\n\n## [v0.24.0](https://github.com/fastly/cli/releases/tag/v0.24.0) (2021-02-02)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v0.23.0...v0.24.0)\n\n**Enhancements:**\n\n- Add static content starter kit [#197](https://github.com/fastly/cli/pull/197)\n- 🦀 Update rust toolchain [#196](https://github.com/fastly/cli/pull/196)\n\n**Bug fixes:**\n\n- Fix go vet error related to missing docstring [#198](https://github.com/fastly/cli/pull/198)\n\n## [v0.23.0](https://github.com/fastly/cli/releases/tag/v0.23.0) (2021-01-22)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v0.22.0...v0.23.0)\n\n**Enhancements:**\n\n- Update Go-Fastly dependency to v3.0.0 [#193](https://github.com/fastly/cli/pull/193)\n- Support for Compute@Edge Log Tailing [#192](https://github.com/fastly/cli/pull/192)\n\n**Bug fixes:**\n\n- Resolve issues with Rust integration tests. [#194](https://github.com/fastly/cli/pull/194)\n- Update URL for default Rust starter [#191](https://github.com/fastly/cli/pull/191)\n\n## [v0.22.0](https://github.com/fastly/cli/releases/tag/v0.22.0) (2021-01-07)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v0.21.2...v0.22.0)\n\n**Enhancements:**\n\n- Add support for TLS client and batch size options for splunk [#183](https://github.com/fastly/cli/pull/183)\n- Add support for Kinesis logging endpoint [#177](https://github.com/fastly/cli/pull/177)\n\n## [v0.21.2](https://github.com/fastly/cli/releases/tag/v0.21.2) (2021-01-06)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v0.21.1...v0.21.2)\n\n**Bug fixes:**\n\n- Switch from third-party dependency to our own mirror [#184](https://github.com/fastly/cli/pull/184)\n\n## [v0.21.1](https://github.com/fastly/cli/releases/tag/v0.21.1) (2020-12-18)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v0.21.0...v0.21.1)\n\n**Bug fixes:**\n\n- CLI shouldn't recommend Rust crate prerelease versions [#168](https://github.com/fastly/cli/issues/168)\n- Run cargo update before attempting to build Rust compute packages [#179](https://github.com/fastly/cli/pull/179)\n\n## [v0.21.0](https://github.com/fastly/cli/releases/tag/v0.21.0) (2020-12-14)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v0.20.0...v0.21.0)\n\n**Enhancements:**\n\n- Adds support for managing edge dictionaries [#159](https://github.com/fastly/cli/pull/159)\n\n## [v0.20.0](https://github.com/fastly/cli/releases/tag/v0.20.0) (2020-11-24)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v0.19.0...v0.20.0)\n\n**Enhancements:**\n\n- Migrate to Go-Fastly 2.0.0 [#169](https://github.com/fastly/cli/pull/169)\n\n**Bug fixes:**\n\n- Build failure with Cargo workspaces [#171](https://github.com/fastly/cli/issues/171)\n- Support cargo workspaces [#172](https://github.com/fastly/cli/pull/172)\n\n## [v0.19.0](https://github.com/fastly/cli/releases/tag/v0.19.0) (2020-11-19)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v0.18.1...v0.19.0)\n\n**Enhancements:**\n\n- Support sasl kafka endpoint options in Fastly CLI [#161](https://github.com/fastly/cli/pull/161)\n\n## [v0.18.1](https://github.com/fastly/cli/releases/tag/v0.18.1) (2020-11-03)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v0.18.0...v0.18.1)\n\n**Enhancements:**\n\n- Update the default Rust template to fastly-0.5.0 [#163](https://github.com/fastly/cli/pull/163)\n\n**Bug fixes:**\n\n- Constrain Version Upgrade Suggestion [#165](https://github.com/fastly/cli/pull/165)\n- Fix AssemblyScript compilation messaging [#164](https://github.com/fastly/cli/pull/164)\n\n## [v0.18.0](https://github.com/fastly/cli/releases/tag/v0.18.0) (2020-10-27)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v0.17.0...v0.18.0)\n\n**Enhancements:**\n\n- Add AssemblyScript support to compute init and build commands [#160](https://github.com/fastly/cli/pull/160)\n\n## [v0.17.0](https://github.com/fastly/cli/releases/tag/v0.17.0) (2020-09-24)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v0.16.1...v0.17.0)\n\n**Enhancements:**\n\n- Bump supported Rust toolchain version to 1.46 [#156](https://github.com/fastly/cli/pull/156)\n- Add service search command [#152](https://github.com/fastly/cli/pull/152)\n\n**Bug fixes:**\n\n- Broken link in usage info [#148](https://github.com/fastly/cli/issues/148)\n\n## [v0.16.1](https://github.com/fastly/cli/releases/tag/v0.16.1) (2020-07-21)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v0.16.0...v0.16.1)\n\n**Bug fixes:**\n\n- Display the correct version number on error [#144](https://github.com/fastly/cli/pull/144)\n- Fix bug where name was not added to the manifest [#143](https://github.com/fastly/cli/pull/143)\n\n## [v0.16.0](https://github.com/fastly/cli/releases/tag/v0.16.0) (2020-07-09)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v0.15.0...v0.16.0)\n\n**Enhancements:**\n\n- Compare package hashsum during deployment [#139](https://github.com/fastly/cli/pull/139)\n- Allow compute init to be reinvoked within an existing package directory [#138](https://github.com/fastly/cli/pull/138)\n\n## [v0.15.0](https://github.com/fastly/cli/releases/tag/v0.15.0) (2020-06-29)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v0.14.0...v0.15.0)\n\n**Enhancements:**\n\n- Adds OpenStack logging support [#132](https://github.com/fastly/cli/pull/132)\n\n## [v0.14.0](https://github.com/fastly/cli/releases/tag/v0.14.0) (2020-06-25)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v0.13.0...v0.14.0)\n\n**Enhancements:**\n\n- Bump default Rust template version to v0.4.0 [#133](https://github.com/fastly/cli/pull/133)\n\n## [v0.13.0](https://github.com/fastly/cli/releases/tag/v0.13.0) (2020-06-15)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v0.12.0...v0.13.0)\n\n**Enhancements:**\n\n- Allow compute services to be initialised from an existing service ID [#125](https://github.com/fastly/cli/pull/125)\n\n**Bug fixes:**\n\n- Fix bash completion [#128](https://github.com/fastly/cli/pull/128)\n\n**Closed issues:**\n\n- Bash Autocomplete is broken [#127](https://github.com/fastly/cli/issues/127)\n\n## [v0.12.0](https://github.com/fastly/cli/releases/tag/v0.12.0) (2020-06-05)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v0.11.0...v0.12.0)\n\n**Enhancements:**\n\n- Adds MessageType field to SFTP [#118](https://github.com/fastly/cli/pull/118)\n- Adds User field to Cloudfiles Updates [#117](https://github.com/fastly/cli/pull/117)\n- Adds Region field to Scalyr [#116](https://github.com/fastly/cli/pull/116)\n- Adds PublicKey field to S3 [#114](https://github.com/fastly/cli/pull/114)\n- Adds MessageType field to GCS Updates [#113](https://github.com/fastly/cli/pull/113)\n- Adds ResponseCondition and Placement fields to BigQuery Creates [#111](https://github.com/fastly/cli/pull/111)\n\n**Bug fixes:**\n\n- Unable to login with API key [#94](https://github.com/fastly/cli/issues/94)\n\n## [v0.11.0](https://github.com/fastly/cli/releases/tag/v0.11.0) (2020-05-29)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v0.10.0...v0.11.0)\n\n**Enhancements:**\n\n- Add ability to exclude files from build package [#87](https://github.com/fastly/cli/pull/87)\n\n**Bug fixes:**\n\n- unintended files included in upload package [#24](https://github.com/fastly/cli/issues/24)\n\n## [v0.10.0](https://github.com/fastly/cli/releases/tag/v0.10.0) (2020-05-28)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v0.9.0...v0.10.0)\n\n**Enhancements:**\n\n- Adds Google Cloud Pub/Sub logging endpoint support [#96](https://github.com/fastly/cli/pull/96)\n- Adds Datadog logging endpoint support [#92](https://github.com/fastly/cli/pull/92)\n- Adds HTTPS logging endpoint support [#91](https://github.com/fastly/cli/pull/91)\n- Adds Elasticsearch logging endpoint support [#90](https://github.com/fastly/cli/pull/90)\n- Adds Azure Blob Storage logging endpoint support [#89](https://github.com/fastly/cli/pull/89)\n\n## [v0.9.0](https://github.com/fastly/cli/releases/tag/v0.9.0) (2020-05-21)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v0.8.0...v0.9.0)\n\n**Breaking changes:**\n\n- Describe subcommand consistent --name short flag -d -> -n [#85](https://github.com/fastly/cli/pull/85)\n\n**Enhancements:**\n\n- Adds Kafka logging endpoint support [#95](https://github.com/fastly/cli/pull/95)\n- Adds DigitalOcean Spaces logging endpoint support [#80](https://github.com/fastly/cli/pull/80)\n- Adds Rackspace Cloudfiles logging endpoint support [#79](https://github.com/fastly/cli/pull/79)\n- Adds Log Shuttle logging endpoint support [#78](https://github.com/fastly/cli/pull/78)\n- Adds SFTP logging endpoint support [#77](https://github.com/fastly/cli/pull/77)\n- Adds Heroku logging endpoint support [#76](https://github.com/fastly/cli/pull/76)\n- Adds Honeycomb logging endpoint support [#75](https://github.com/fastly/cli/pull/75)\n- Adds Loggly logging endpoint support [#74](https://github.com/fastly/cli/pull/74)\n- Adds Scalyr logging endpoint support [#73](https://github.com/fastly/cli/pull/73)\n- Verify fastly crate version during compute build. [#67](https://github.com/fastly/cli/pull/67)\n- Basic support for historical & realtime stats [#66](https://github.com/fastly/cli/pull/66)\n- Adds Splunk endpoint [#64](https://github.com/fastly/cli/pull/64)\n- Adds FTP logging endpoint support [#63](https://github.com/fastly/cli/pull/63)\n- Adds GCS logging endpoint support [#62](https://github.com/fastly/cli/pull/62)\n- Adds Sumo Logic logging endpoint support [#59](https://github.com/fastly/cli/pull/59)\n- Adds Papertrail logging endpoint support [#57](https://github.com/fastly/cli/pull/57)\n- Adds Logentries logging endpoint support [#56](https://github.com/fastly/cli/pull/56)\n\n**Bug fixes:**\n\n- Fallback to a file copy during update if the file rename fails [#72](https://github.com/fastly/cli/pull/72)\n\n## [v0.8.0](https://github.com/fastly/cli/releases/tag/v0.8.0) (2020-05-13)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v0.7.1...v0.8.0)\n\n**Enhancements:**\n\n- Add a --force flag to compute build to skip verification steps. [#68](https://github.com/fastly/cli/pull/68)\n- Improve `compute build` rust compilation error messaging [#60](https://github.com/fastly/cli/pull/60)\n- Adds Syslog logging endpoint support [#55](https://github.com/fastly/cli/pull/55)\n\n**Bug fixes:**\n\n- debian package doesn't install in default $PATH [#58](https://github.com/fastly/cli/issues/58)\n- deb and rpm packages install the binary in `/usr/local` instead of `/usr/local/bin` [#53](https://github.com/fastly/cli/issues/53)\n\n**Closed issues:**\n\n- ERROR: error during compilation process: exit status 101. [#52](https://github.com/fastly/cli/issues/52)\n\n## [v0.7.1](https://github.com/fastly/cli/releases/tag/v0.7.1) (2020-05-04)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v0.7.0...v0.7.1)\n\n**Bug fixes:**\n\n- Ensure compute deploy selects the most ideal version to clone/activate [#50](https://github.com/fastly/cli/pull/50)\n\n## [v0.7.0](https://github.com/fastly/cli/releases/tag/v0.7.0) (2020-04-28)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v0.6.0...v0.7.0)\n\n**Enhancements:**\n\n- Publish scoop package manifest during release process [#45](https://github.com/fastly/cli/pull/45)\n- Generate dep and rpm packages during release process [#44](https://github.com/fastly/cli/pull/44)\n- 🦀 🆙date to Rust 1.43.0 [#40](https://github.com/fastly/cli/pull/40)\n\n**Closed issues:**\n\n- README's build instructions do not work without additional dependencies met [#35](https://github.com/fastly/cli/issues/35)\n\n## [v0.6.0](https://github.com/fastly/cli/releases/tag/v0.6.0) (2020-04-24)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v0.5.0...v0.6.0)\n\n**Enhancements:**\n\n- Bump default Rust template to v0.3.0 [#32](https://github.com/fastly/cli/pull/32)\n- Publish to homebrew [#26](https://github.com/fastly/cli/pull/26)\n\n**Bug fixes:**\n\n- Don't display the fastly token in the terminal when doing `fastly configure` [#27](https://github.com/fastly/cli/issues/27)\n- Documentation typo in `fastly service-version update` [#22](https://github.com/fastly/cli/issues/22)\n- Fix typo in service-version update command [#31](https://github.com/fastly/cli/pull/31)\n- Tidy up `fastly configure` text output [#30](https://github.com/fastly/cli/pull/30)\n- compute/init: make space after Author prompt match other prompts [#25](https://github.com/fastly/cli/pull/25)\n\n## [v0.5.0](https://github.com/fastly/cli/releases/tag/v0.5.0) (2020-04-08)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v0.4.1...v0.5.0)\n\n**Enhancements:**\n\n- Add the ability to initialise a compute project from a specific branch [#14](https://github.com/fastly/cli/pull/14)\n\n## [v0.4.1](https://github.com/fastly/cli/releases/tag/v0.4.1) (2020-03-27)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v0.4.0...v0.4.1)\n\n**Bug fixes:**\n\n- Fix persistence of author string to fastly.toml [#12](https://github.com/fastly/cli/pull/12)\n- Fix up undoStack.RunIfError [#11](https://github.com/fastly/cli/pull/11)\n\n## [v0.4.0](https://github.com/fastly/cli/releases/tag/v0.4.0) (2020-03-20)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v0.3.0...v0.4.0)\n\n**Enhancements:**\n\n- Add commands for S3 logging endpoints [#9](https://github.com/fastly/cli/pull/9)\n- Add useful next step links to compute deploy [#8](https://github.com/fastly/cli/pull/8)\n- Persist version to manifest file when deploying compute services [#7](https://github.com/fastly/cli/pull/7)\n\n**Bug fixes:**\n\n- Fix comment for --use-ssl flag [#6](https://github.com/fastly/cli/pull/6)\n\n## [v0.3.0](https://github.com/fastly/cli/releases/tag/v0.3.0) (2020-03-11)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v0.2.0...v0.3.0)\n\n**Enhancements:**\n\n- Interactive init [#5](https://github.com/fastly/cli/pull/5)\n\n## [v0.2.0](https://github.com/fastly/cli/releases/tag/v0.2.0) (2020-02-24)\n\n[Full Changelog](https://github.com/fastly/cli/compare/v0.1.0...v0.2.0)\n\n**Enhancements:**\n\n- Improve toolchain installation help messaging [#3](https://github.com/fastly/cli/pull/3)\n\n**Bug fixes:**\n\n- Filter unwanted files from template repository whilst initialising [#1](https://github.com/fastly/cli/pull/1)\n\n## [v0.1.0](https://github.com/fastly/cli/releases/tag/v0.1.0) (2020-02-05)\n\n[Full Changelog](https://github.com/fastly/cli/compare/5a8d21b6b1973abe7a27f985856d910f4396ce95...v0.1.0)\n\nInitial release :tada:\n\n\\* _This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)_\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing\n\nWe're happy to receive feature requests and PRs. If your change is nontrivial,\nplease open an [issue](https://github.com/fastly/cli/issues/new) to discuss the\nidea and implementation strategy before submitting a PR.\n\n1. Fork the repository.\n2. Create an `upstream` remote.\n    ```bash\n    $ git remote add upstream git@github.com:fastly/cli.git\n    ```\n3. Create a feature branch.\n4. Write tests.\n5. Run the linter and formatter `make all`. \n    1. You may need to install [golangci-lint](https://golangci-lint.run/welcome/install/) if you don't have it installed\n6. Add your changes to `CHANGELOG.md` in [Commitizen](https://commitizen-tools.github.io/commitizen/) style message\n7. Open a pull request against `upstream main`.\n    1. Once you have marked your PR as `Ready for Review` please do not force push to the branch\n8. Celebrate :tada:!\n"
  },
  {
    "path": "DEVELOPMENT.md",
    "content": "## Development\n\nBuilding the Fastly CLI requires [Go](https://golang.org) (version\n1.18 or later), and [Rust](https://www.rust-lang.org/). Clone this\nrepo to any path and type `make` to run all of the tests and generate\na development build locally.\n\n```sh\ngit clone git@github.com:fastly/cli\ncd cli\nmake\n./fastly version\n```\n\nThe `make` task requires the following executables to exist in your `$PATH`:\n\n- [golint](https://github.com/golang/lint)\n- [gosec](https://github.com/securego/gosec)\n- [staticcheck](https://staticcheck.io/)\n\nIf you have none of them installed, or don't mind them being upgraded automatically, you can run `make mod-download` to install them.\n\n### New API Command Scaffolding\n\nThere are two ways to scaffold a new command, that is intended to be a non-composite command (i.e. a straight 1-1 mapping to an underlying Fastly API endpoint):\n\n1. `make scaffold`: e.g. `fastly foo <create|delete|describe|update>`\n2. `make scaffold-category`: e.g. `fastly foo bar <create|delete|describe|update>`\n\nThe latter `Makefile` target is for commands we want to group under a common category (in the above example `foo` is the category and `bar` is the command). A real example of a category command would be `fastly logging`, where `logging` is the category and within that category are multiple logging provider commands (e.g. `fastly logging splunk`, where `splunk` is the command).\n\nThe `logging` category is an otherwise non-functional command (i.e. if you execute `fastly logging`, then all you see is help output describing the available commands under the logging category).\n\n**Makefile target structure:**\n\n```bash\nCLI_PACKAGE=... CLI_COMMAND=... CLI_API=... make scaffold\nCLI_CATEGORY=... CLI_CATEGORY_COMMAND=... CLI_PACKAGE=... CLI_COMMAND=... CLI_API=... make scaffold-category\n```\n\n**Example usage:**\n\nImagine you want to add the following top-level command `fastly foo-bar` with CRUD commands beneath it (e.g. `fastly foo <create|delete|describe|list|update>`), then you would execute:\n\n```bash\nCLI_PACKAGE=foobar CLI_COMMAND=foo-bar CLI_API=Bar make scaffold\n```\n\n> **NOTE**: Go package names shouldn't have special characters, hence the difference between `foobar` and the command `foo-bar`. Also, the `CLI_API` value will be interpolated into CRUD verbs (`CreateBar`, `DeleteBar` etc), along with their inputs (`fastly.CreateBarInput`, `fastly.DeleteBarInput` etc).\n\nNow imagine you want to add a new subcommand to an existing category such as 'logging' `fastly logging foo-bar` with CRUD commands beneath it (e.g. `fastly logging foo-bar <create|delete|describe|list|update>`), then you would execute a similar command but you would change to the `scaffold-category` target and also prefix two additional inputs:\n\n```bash\nCLI_CATEGORY=logging CLI_CATEGORY_COMMAND=logging CLI_PACKAGE=foobar CLI_COMMAND=foo-bar CLI_API=Bar make scaffold-category\n```\n\n> **NOTE**: Within the generated files, keep an eye out for any `<...>` references that need to be manually updated.\n\n### `.fastly/config.toml`\n\nThe CLI dynamically generates the `./pkg/config/config.toml` within the CI release process so it can be embedded into the CLI binary.\n\nThe file is added to `.gitignore` to avoid it being added to the git repository.\n\nWhen compiling the CLI for a new release, it will execute [`./scripts/config.sh`](./scripts/config.sh). The script uses [`./.fastly/config.toml`](./.fastly/config.toml) as a template file to then dynamically inject a list of starter kits (pulling their data from their public repositories).\n\nThe resulting configuration is then saved to disk at `./pkg/config/config.toml` and embedded into the CLI when compiled.\n\nWhen a user installs the CLI for the first time, they'll have no existing config and so the embedded config will be used. In the future, when the user updates their CLI, the existing config they have will be used.\n\nIf the config has changed in any way, then you (the CLI developer) should ensure the `config_version` number is bumped before publishing a new CLI release. This is because when the user updates to that new CLI version and they invoke the CLI, the CLI will identify a mismatch between the user's local config version and the embedded config version. This will cause the embedded config to be merged with the local config and consequently the user's config will be updated to include the new fields.\n\n> **NOTE:** The CLI does provide a `fastly config --reset` option that resets the config to a version compatible with the user's current CLI version. This is fallback for users who run into issues for whatever reason.\n\n### Running Compute commands locally\n\nIf you need to test the Fastly CLI locally while developing a Compute feature, then use the `--dir` flag (exposed on `compute build`, `compute deploy`, `compute serve` and `compute publish`) to ensure the CLI doesn't attempt to treat the repository directory as your project directory.\n\n```shell\ngo run cmd/fastly/main.go compute deploy --verbose --dir ../../test-projects/testing-fastly-cli\n```\n"
  },
  {
    "path": "DOCUMENTATION.md",
    "content": "## Documentation\n\nThe help output from the Fastly CLI is consumed by the Fastly Developer Hub to produce online documentation: https://www.fastly.com/documentation/reference/cli\n\nPart of the documentation is to provide additional usage examples and links to APIs used by the CLI commands (example: https://www.fastly.com/documentation/reference/cli/backend/create/#examples).\n\nThese examples and API references are defined in [`pkg/app/metadata.json`](./pkg/app/metadata.json).\n"
  },
  {
    "path": "Dockerfile-node",
    "content": "FROM node:latest\nLABEL maintainer=\"Fastly OSS <oss@fastly.com>\"\n\nRUN apt-get update && apt-get install -y curl jq && apt-get -y clean && rm -rf /var/lib/apt/lists/* \\\n  && export FASTLY_CLI_VERSION=$(curl -s https://api.github.com/repos/fastly/cli/releases/latest | jq -r .tag_name | cut -d 'v' -f 2) \\\n            GOARCH=$(dpkg --print-architecture) \\\n  && curl -sL \"https://github.com/fastly/cli/releases/download/v${FASTLY_CLI_VERSION}/fastly_v${FASTLY_CLI_VERSION}_linux-$GOARCH.tar.gz\" -o fastly.tar.gz \\\n  && curl -sL \"https://github.com/fastly/cli/releases/download/v${FASTLY_CLI_VERSION}/fastly_v${FASTLY_CLI_VERSION}_SHA256SUMS\" -o sha256sums \\\n  && dlsha=$(shasum -a 256 fastly.tar.gz | cut -d \" \" -f 1) && expected=$(cat sha256sums | awk -v pat=\"$dlsha\" '$0~pat' | cut -d \" \" -f 1) \\\n  && if [ \"$dlsha\" != \"$expected\" ]; then echo \"shasums don't match\" && exit 1; fi \\\n  && tar -xzf fastly.tar.gz --directory /usr/bin && rm -f sha256sums fastly.tar.gz \\\n  && useradd -ms /bin/bash fastly\n\nUSER fastly\n\nWORKDIR /app\nENTRYPOINT [\"/usr/bin/fastly\"]\nCMD [\"--help\"]\n\n# docker build -t fastly/cli/node . -f ./Dockerfile-node\n# docker run -v $PWD:/app -it -p 7676:7676 fastly/cli/node compute serve --addr=\"0.0.0.0:7676\"\n"
  },
  {
    "path": "Dockerfile-rust",
    "content": "FROM rust:latest\nLABEL maintainer=\"Fastly OSS <oss@fastly.com>\"\n\nENV RUSTUP_TOOLCHAIN=$RUST_VERSION\nRUN rustup target add wasm32-wasip1 \\\n  && apt-get update && apt-get install -y curl jq && apt-get -y clean && rm -rf /var/lib/apt/lists/* \\\n  && cargo install wasm-tools --locked \\\n  && export FASTLY_CLI_VERSION=$(curl -s https://api.github.com/repos/fastly/cli/releases/latest | jq -r .tag_name | cut -d 'v' -f 2) \\\n            GOARCH=$(dpkg --print-architecture) \\\n  && curl -sL \"https://github.com/fastly/cli/releases/download/v${FASTLY_CLI_VERSION}/fastly_v${FASTLY_CLI_VERSION}_linux-$GOARCH.tar.gz\" -o fastly.tar.gz \\\n  && curl -sL \"https://github.com/fastly/cli/releases/download/v${FASTLY_CLI_VERSION}/fastly_v${FASTLY_CLI_VERSION}_SHA256SUMS\" -o sha256sums \\\n  && dlsha=$(shasum -a 256 fastly.tar.gz | cut -d \" \" -f 1) && expected=$(cat sha256sums | awk -v pat=\"$dlsha\" '$0~pat' | cut -d \" \" -f 1) \\\n  && if [ \"$dlsha\" != \"$expected\" ]; then echo \"shasums don't match\" && exit 1; fi \\\n  && tar -xzf fastly.tar.gz --directory /usr/bin && rm -f sha256sums fastly.tar.gz \\\n  && useradd -ms /bin/bash fastly\n\nUSER fastly\n\nWORKDIR /app\nENTRYPOINT [\"/usr/bin/fastly\"]\nCMD [\"--help\"]\n\n# docker build -t fastly/cli/rust . -f ./Dockerfile-rust\n# docker run -v $PWD:/app -it -p 7676:7676 fastly/cli/rust compute serve --addr=\"0.0.0.0:7676\"\n"
  },
  {
    "path": "ISSUES.md",
    "content": "<div align=\"center\">\n  <h3 align=\"center\">CLI Issues</h3>\n  <p align=\"center\">Best practices for submitting an issue to the Fastly CLI repository.</p>\n</div>\n\n## Issue Type: Bug\n\nIssues related to the CLI behavior not working as intended. \n\n- The CLI crashes or exits with an unexpected error\n- A command produces incorrect output or wrong results\n- Commands or flags don't work as documented\n\n**Example:** \"When I run `fastly service list --json`, malformed JSON is produced.\"\n\n## Issue Type: Feature Request\n\nIssues related to suggesting improvements to the CLI:\n\n- New commands or subcommands based on existing Fastly APIs\n- Improved error messages or user experience\n- Adding support for a third party integration\n\n**Example:** \"Add a `fastly service version validate` command, which already exists in the Fastly API.\"\n\n## Fastly Support\n\nCLI behavior specific to your environment or service / account should be routed to the Fastly support team @ support.fastly.com or support@fastly.com. \n\n- A feature is missing from your account / service\n- Partial content is returned that you may not have access to with your current Fastly account role\n- My site is not loading after a configuration change\n\n**Example:** When running `fastly service vcl snippet create`, an error is thrown that the provided VCL is not valid\n"
  },
  {
    "path": "LICENSE",
    "content": "Apache License\nVersion 2.0, January 2004\nhttp://www.apache.org/licenses/\n\nTERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n1. Definitions.\n\n\"License\" shall mean the terms and conditions for use, reproduction,\nand distribution as defined by Sections 1 through 9 of this document.\n\n\"Licensor\" shall mean the copyright owner or entity authorized by\nthe copyright owner that is granting the License.\n\n\"Legal Entity\" shall mean the union of the acting entity and all\nother entities that control, are controlled by, or are under common\ncontrol with that entity. For the purposes of this definition,\n\"control\" means (i) the power, direct or indirect, to cause the\ndirection or management of such entity, whether by contract or\notherwise, or (ii) ownership of fifty percent (50%) or more of the\noutstanding shares, or (iii) beneficial ownership of such entity.\n\n\"You\" (or \"Your\") shall mean an individual or Legal Entity\nexercising permissions granted by this License.\n\n\"Source\" form shall mean the preferred form for making modifications,\nincluding but not limited to software source code, documentation\nsource, and configuration files.\n\n\"Object\" form shall mean any form resulting from mechanical\ntransformation or translation of a Source form, including but\nnot limited to compiled object code, generated documentation,\nand conversions to other media types.\n\n\"Work\" shall mean the work of authorship, whether in Source or\nObject form, made available under the License, as indicated by a\ncopyright notice that is included in or attached to the work\n(an example is provided in the Appendix below).\n\n\"Derivative Works\" shall mean any work, whether in Source or Object\nform, that is based on (or derived from) the Work and for which the\neditorial revisions, annotations, elaborations, or other modifications\nrepresent, as a whole, an original work of authorship. For the purposes\nof this License, Derivative Works shall not include works that remain\nseparable from, or merely link (or bind by name) to the interfaces of,\nthe Work and Derivative Works thereof.\n\n\"Contribution\" shall mean any work of authorship, including\nthe original version of the Work and any modifications or additions\nto that Work or Derivative Works thereof, that is intentionally\nsubmitted to Licensor for inclusion in the Work by the copyright owner\nor by an individual or Legal Entity authorized to submit on behalf of\nthe copyright owner. For the purposes of this definition, \"submitted\"\nmeans any form of electronic, verbal, or written communication sent\nto the Licensor or its representatives, including but not limited to\ncommunication on electronic mailing lists, source code control systems,\nand issue tracking systems that are managed by, or on behalf of, the\nLicensor for the purpose of discussing and improving the Work, but\nexcluding communication that is conspicuously marked or otherwise\ndesignated in writing by the copyright owner as \"Not a Contribution.\"\n\n\"Contributor\" shall mean Licensor and any individual or Legal Entity\non behalf of whom a Contribution has been received by Licensor and\nsubsequently incorporated within the Work.\n\n2. Grant of Copyright License. Subject to the terms and conditions of\nthis License, each Contributor hereby grants to You a perpetual,\nworldwide, non-exclusive, no-charge, royalty-free, irrevocable\ncopyright license to reproduce, prepare Derivative Works of,\npublicly display, publicly perform, sublicense, and distribute the\nWork and such Derivative Works in Source or Object form.\n\n3. Grant of Patent License. Subject to the terms and conditions of\nthis License, each Contributor hereby grants to You a perpetual,\nworldwide, non-exclusive, no-charge, royalty-free, irrevocable\n(except as stated in this section) patent license to make, have made,\nuse, offer to sell, sell, import, and otherwise transfer the Work,\nwhere such license applies only to those patent claims licensable\nby such Contributor that are necessarily infringed by their\nContribution(s) alone or by combination of their Contribution(s)\nwith the Work to which such Contribution(s) was submitted. If You\ninstitute patent litigation against any entity (including a\ncross-claim or counterclaim in a lawsuit) alleging that the Work\nor a Contribution incorporated within the Work constitutes direct\nor contributory patent infringement, then any patent licenses\ngranted to You under this License for that Work shall terminate\nas of the date such litigation is filed.\n\n4. Redistribution. You may reproduce and distribute copies of the\nWork or Derivative Works thereof in any medium, with or without\nmodifications, and in Source or Object form, provided that You\nmeet the following conditions:\n\n(a) You must give any other recipients of the Work or\nDerivative Works a copy of this License; and\n\n(b) You must cause any modified files to carry prominent notices\nstating that You changed the files; and\n\n(c) You must retain, in the Source form of any Derivative Works\nthat You distribute, all copyright, patent, trademark, and\nattribution notices from the Source form of the Work,\nexcluding those notices that do not pertain to any part of\nthe Derivative Works; and\n\n(d) If the Work includes a \"NOTICE\" text file as part of its\ndistribution, then any Derivative Works that You distribute must\ninclude a readable copy of the attribution notices contained\nwithin such NOTICE file, excluding those notices that do not\npertain to any part of the Derivative Works, in at least one\nof the following places: within a NOTICE text file distributed\nas part of the Derivative Works; within the Source form or\ndocumentation, if provided along with the Derivative Works; or,\nwithin a display generated by the Derivative Works, if and\nwherever such third-party notices normally appear. The contents\nof the NOTICE file are for informational purposes only and\ndo not modify the License. You may add Your own attribution\nnotices within Derivative Works that You distribute, alongside\nor as an addendum to the NOTICE text from the Work, provided\nthat such additional attribution notices cannot be construed\nas modifying the License.\n\nYou may add Your own copyright statement to Your modifications and\nmay provide additional or different license terms and conditions\nfor use, reproduction, or distribution of Your modifications, or\nfor any such Derivative Works as a whole, provided Your use,\nreproduction, and distribution of the Work otherwise complies with\nthe conditions stated in this License.\n\n5. Submission of Contributions. Unless You explicitly state otherwise,\nany Contribution intentionally submitted for inclusion in the Work\nby You to the Licensor shall be under the terms and conditions of\nthis License, without any additional terms or conditions.\nNotwithstanding the above, nothing herein shall supersede or modify\nthe terms of any separate license agreement you may have executed\nwith Licensor regarding such Contributions.\n\n6. Trademarks. This License does not grant permission to use the trade\nnames, trademarks, service marks, or product names of the Licensor,\nexcept as required for reasonable and customary use in describing the\norigin of the Work and reproducing the content of the NOTICE file.\n\n7. Disclaimer of Warranty. Unless required by applicable law or\nagreed to in writing, Licensor provides the Work (and each\nContributor provides its Contributions) on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\nimplied, including, without limitation, any warranties or conditions\nof TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\nPARTICULAR PURPOSE. You are solely responsible for determining the\nappropriateness of using or redistributing the Work and assume any\nrisks associated with Your exercise of permissions under this License.\n\n8. Limitation of Liability. In no event and under no legal theory,\nwhether in tort (including negligence), contract, or otherwise,\nunless required by applicable law (such as deliberate and grossly\nnegligent acts) or agreed to in writing, shall any Contributor be\nliable to You for damages, including any direct, indirect, special,\nincidental, or consequential damages of any character arising as a\nresult of this License or out of the use or inability to use the\nWork (including but not limited to damages for loss of goodwill,\nwork stoppage, computer failure or malfunction, or any and all\nother commercial damages or losses), even if such Contributor\nhas been advised of the possibility of such damages.\n\n9. Accepting Warranty or Additional Liability. While redistributing\nthe Work or Derivative Works thereof, You may choose to offer,\nand charge a fee for, acceptance of support, warranty, indemnity,\nor other liability obligations and/or rights consistent with this\nLicense. However, in accepting such obligations, You may act only\non Your own behalf and on Your sole responsibility, not on behalf\nof any other Contributor, and only if You agree to indemnify,\ndefend, and hold each Contributor harmless for any liability\nincurred by, or claims asserted against, such Contributor by reason\nof your accepting any such warranty or additional liability.\n\nEND OF TERMS AND CONDITIONS\n\nAPPENDIX: How to apply the Apache License to your work.\n\nTo apply the Apache License to your work, attach the following\nboilerplate notice, with the fields enclosed by brackets \"[]\"\nreplaced with your own identifying information. (Don't include\nthe brackets!)  The text should be enclosed in the appropriate\ncomment syntax for the file format. We also recommend that a\nfile or class name and description of purpose be included on the\nsame \"printed page\" as the copyright notice for easier\nidentification within third-party archives.\n\nCopyright 2015 Seth Vargo\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\nhttp://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n"
  },
  {
    "path": "Makefile",
    "content": ".PHONY: clean\n\nSHELL := /usr/bin/env bash -o pipefail ## Set the shell to use for finding Go files (default: /bin/bash)\n\n\n# Compile program (implicit default target).\n#\n# GO_ARGS allows for passing additional arguments.\n# e.g. make build GO_ARGS='--ldflags \"-s -w\"'\n.PHONY: build\nbuild: config ## Compile program (CGO disabled)\n\tCGO_ENABLED=0 $(GO_BIN) build $(GO_ARGS) ./cmd/fastly\n\n## Allows overriding go executable.\nGO_BIN ?= go\n## Enables support for tools such as https://github.com/rakyll/gotest\nTEST_COMMAND ?= $(GO_BIN) test\n## The compute tests can sometimes exceed the default 10m limit\nTEST_ARGS ?= -v -timeout 15m ./...\n\nifeq ($(OS), Windows_NT)\n\tSHELL = cmd.exe\n\t.SHELLFLAGS = /c\n\tGO_FILES = $(shell where /r pkg *.go)\n\tGO_FILES += $(shell where /r cmd *.go)\n\tCONFIG_SCRIPT = scripts\\config.sh\n\tCONFIG_FILE = pkg\\config\\config.toml\nelse\n\tGO_FILES = $(shell find cmd pkg -type f -name '*.go')\n\tCONFIG_SCRIPT = ./scripts/config.sh\n\tCONFIG_FILE = pkg/config/config.toml\nendif\n\n# Tooling versions\nGOLANGCI_LINT_VERSION = v2.4.0\nBIN_DIR := $(CURDIR)/bin\nGOLANGCI_LINT := $(BIN_DIR)/golangci-lint\n\n# Build executables using goreleaser (useful for local testing purposes).\n#\n# You can pass flags to goreleaser via GORELEASER_ARGS\n# --clean will save you deleting the dist dir\n# --single-target will be quicker and only build for your os & architecture\n# --skip=post-hooks which prevents errors such as trying to execute the binary for each OS (e.g. we call scripts/documentation.sh and we can't run Windows exe on a Mac).\n# --skip=validate will skip the checks (e.g. git tag checks which result in a 'dirty git state' error)\n#\n# EXAMPLE:\n# make release GORELEASER_ARGS=\"--clean --skip=post-hooks --skip=validate\"\nrelease: $(GO_FILES) ## Build executables using goreleaser\n\t$(GO_BIN) tool -modfile=tools/go.mod goreleaser build ${GORELEASER_ARGS}\n\n# Useful for attaching a debugger such as https://github.com/go-delve/delve\ndebug:\n\t@$(GO_BIN) build -gcflags=\"all=-N -l\" $(GO_ARGS) -o \"fastly\" ./cmd/fastly\n\n.PHONY: config\nconfig:\n\t@$(CONFIG_SCRIPT)\n\n.PHONY: all\nall: config mod-download tidy fmt lint semgrep test build install ## Run EVERYTHING!\n\n## Downloads the Go modules\nmod-download: \n\t@echo \"==> Downloading Go module\"\n\t@$(GO_BIN) mod download\n.PHONY: mod-download\n\n# Clean up Go modules file.\n.PHONY: tidy\ntidy:\n\t$(GO_BIN) mod tidy\n\n# Run formatter.\n.PHONY: fmt\nfmt:\n\t$(GOLANGCI_LINT) fmt\n\n# Run semgrep checker.\n# NOTE: We can only exclude the import-text-template rule via a semgrep CLI flag\n.PHONY: semgrep\nsemgrep: ## Run semgrep\n\t@if [ \"$$(uname)\" = 'Darwin' ]; then \\\n\t\tif ! command -v semgrep &> /dev/null; then \\\n\t\t\t\tbrew install semgrep; \\\n\t\tfi \\\n\tfi\n\t@if [ '$(SEMGREP_SKIP)' != 'true' ]; then \\\n\t\tif command -v semgrep &> /dev/null; then semgrep ci --config auto --exclude-rule go.lang.security.audit.xss.import-text-template.import-text-template $(SEMGREP_ARGS); fi \\\n\tfi\n\n.PHONY: lint\nlint: install-linter check-linter-version ## Run golangci-lint\n\t@echo \"==> Running golangci-lint\"\n\t@$(GOLANGCI_LINT) run --verbose\n\n# Run tests\n.PHONY: test\ntest: config ## Run tests (with race detection)\n\t@$(TEST_COMMAND) -race $(TEST_ARGS)\n\n# Compile and install program.\n#\n# GO_ARGS allows for passing additional arguments.\n.PHONY: install\ninstall: config ## Compile and install program\n\tCGO_ENABLED=0 $(GO_BIN) install $(GO_ARGS) ./cmd/fastly\n\n# Scaffold a new CLI command from template files.\n.PHONY: scaffold\nscaffold:\n\t@$(shell pwd)/scripts/scaffold.sh $(CLI_PACKAGE) $(CLI_COMMAND) $(CLI_API)\n\n# Scaffold a new CLI 'category' command from template files.\n.PHONY: scaffold-category\nscaffold-category:\n\t@$(shell pwd)/scripts/scaffold-category.sh $(CLI_CATEGORY) $(CLI_CATEGORY_COMMAND) $(CLI_PACKAGE) $(CLI_COMMAND) $(CLI_API)\n\n# Graph generates a call graph that focuses on the specified package.\n# Output is callvis.svg\n# e.g. make graph PKG_IMPORT_PATH=github.com/fastly/cli/pkg/commands/kvstoreentry\n.PHONY: graph\ngraph: ## Graph generates a call graph that focuses on the specified package\n\t$(GO_BIN) tool -modfile=tools/go.mod go-callvis -file \"callvis\" -focus \"$(PKG_IMPORT_PATH)\" ./cmd/fastly/\n\t@rm callvis.gv\n\n.PHONY: deps-app-update\ndeps-app-update: ## Update all application dependencies\n\t$(GO_BIN) get -u -d -t ./...\n\t$(GO_BIN) mod tidy\n\tif [ -d \"vendor\" ]; then $(GO_BIN) mod vendor; fi\n\n.PHONY: help\nhelp:\n\t@printf \"Targets\\n\"\n\t@(grep -h -E '^[0-9a-zA-Z_.-]+:.*?## .*$$' $(MAKEFILE_LIST) || true) | sort | awk 'BEGIN {FS = \":.*?## \"}; {printf \"\\033[36m%-22s\\033[0m %s\\n\", $$1, $$2}'\n\t@printf \"\\nDefault target\\n\"\n\t@printf \"\\033[36m%s\\033[0m\" $(.DEFAULT_GOAL)\n\t@printf \"\\n\\nMake Variables\\n\"\n\t@(grep -h -E '^[0-9a-zA-Z_.-]+\\s[:?]?=.*? ## .*$$' $(MAKEFILE_LIST) || true) | sort | awk 'BEGIN {FS = \"[:?]?=.*?## \"}; {printf \"\\033[36m%-25s\\033[0m %s\\n\", $$1, $$2}'\n\n.PHONY: run\nrun: config\n\t$(GO_BIN) run cmd/fastly/main.go $(GO_ARGS)\n\n.PHONY: install-linter\ninstall-linter: ## Installs golangci-lint via go install\n\t@echo \"==> Installing golangci-lint $(GOLANGCI_LINT_VERSION)\"\n\t@mkdir -p $(BIN_DIR)\n\t@GOBIN=$(BIN_DIR) go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@$(GOLANGCI_LINT_VERSION)\n\n.PHONY: check-linter-version\ncheck-linter-version: ## Verifies installed golangci-lint version matches expected\n\t@echo \"==> Checking golangci-lint version\"\n\t@EXPECTED=\"$(GOLANGCI_LINT_VERSION)\"; \\\n\tEXPECTED=$${EXPECTED#v}; \\\n\tINSTALLED=$$($(GOLANGCI_LINT) version --short); \\\n\tif [ \"$$INSTALLED\" != \"$$EXPECTED\" ]; then \\\n\t\techo \"Expected golangci-lint v$$EXPECTED but found $$INSTALLED\"; \\\n\t\texit 1; \\\n\tfi\n\n.PHONY: clean-bin\nclean-bin: ## Removes locally installed binaries\n\t@echo \"==> Cleaning ./bin directory\"\n\t@rm -rf $(BIN_DIR)\n"
  },
  {
    "path": "README.md",
    "content": "<div align=\"center\">\n  <h3 align=\"center\">Fastly CLI</h3>\n  <p align=\"center\">A CLI for interacting with the Fastly platform.</p>\n  <p align=\"center\">\n      <a href=\"https://www.fastly.com/documentation/reference/cli\"><img alt=\"Documentation\" src=\"https://img.shields.io/badge/cli-reference-yellow\"></a>\n      <a href=\"https://github.com/fastly/cli/releases/latest\"><img alt=\"Latest release\" src=\"https://img.shields.io/github/v/release/fastly/cli\" /></a>\n      <a href=\"#License\"><img alt=\"Apache 2.0 License\" src=\"https://img.shields.io/github/license/fastly/cli\" /></a>\n      <a href=\"https://goreportcard.com/report/github.com/fastly/cli\"><img alt=\"Go Report Card\" src=\"https://goreportcard.com/badge/github.com/fastly/cli\" /></a>\n  </p>\n</div>\n\n## Quick links\n\n- [Installation](https://www.fastly.com/documentation/reference/tools/cli#installing)\n- [Shell auto-completion](https://www.fastly.com/documentation/reference/tools/cli#shell-auto-completion)\n- [Configuring](https://www.fastly.com/documentation/reference/tools/cli#configuring)\n- [Authentication](https://fastly.help/cli/cli-auth) (`fastly auth login`)\n- [Commands](https://www.fastly.com/documentation/reference/cli#command-groups)\n- [Development](https://github.com/fastly/cli/blob/main/DEVELOPMENT.md)\n- [Testing](https://github.com/fastly/cli/blob/main/TESTING.md)\n- [Issues](https://github.com/fastly/cli/blob/main/ISSUES.md)\n- [Documentation](https://github.com/fastly/cli/blob/main/DOCUMENTATION.md)\n\n\n## Environment Variables\n\n| Variable | Description |\n|----------|-------------|\n| `FASTLY_API_TOKEN` | Fastly API token used for authentication. |\n| `FASTLY_DISABLE_AUTH_COMMAND` | When set (any non-empty value), all authentication-related commands (`auth`, `auth-token`, `sso`, `profile`, `whoami`) and the `--token`/`-t` global flag are disabled. Authentication is handled via `FASTLY_API_TOKEN` or pre-configured stored tokens. Background SSO flows are unaffected. |\n\n## Versioning and Release Schedules\n\nThe maintainers of this module strive to maintain [semantic versioning\n(SemVer)](https://semver.org/). This means that breaking changes\n(removal of functionality, or incompatible changes to existing\nfunctionality) will be released in a version with the first version\ncomponent (`major`) incremented. Feature additions will increment the\nsecond version component (`minor`), and bug fixes which do not affect\ncompatibility will increment the third version component (`patch`).\n\nOn the second Wednesday of each month, a release will be published\nincluding all breaking, feature, and bug-fix changes that are ready\nfor release. If that Wednesday should happen to be a US holiday, the\nrelease will be delayed until the next available working day.\n\nIf critical or urgent bug fixes are ready for release in between those\nprimary releases, patch releases will be made as needed to make those\nfixes available.\n\n## Contributing\n\nRefer to [CONTRIBUTING.md](https://github.com/fastly/cli/blob/main/CONTRIBUTING.md)\n\n## Issues\n\nIf you encounter any non-security-related bug or unexpected behavior, please [file an issue][bug]\nusing the bug report template.\n\nPlease also check the [CHANGELOG](https://github.com/fastly/cli/blob/main/CHANGELOG.md) for any breaking-changes or migration guidance.\n\n### Security issues\n\nPlease see our [SECURITY.md](SECURITY.md) for guidance on reporting security-related issues.\n\n## Binaries with unreleased changes\n\nBinaries containing merged changes that are planned for the next release are available [here](https://github.com/fastly/cli/actions/workflows/merge_to_main.yml). \nUse at your own risk. \nUpdating will revert the binary to the latest released version.\n\n## License\n\n[Apache 2.0](LICENSE).\n\n[bug]: https://github.com/fastly/cli/issues/new?labels=bug&template=bug_report.md\n"
  },
  {
    "path": "RELEASE.md",
    "content": "# Release Process\n\n1. Merge all PRs intended for the release.\n1. Ensure any relevant `FIXME` notes in the code are addressed (e.g. `FIXME: remove this feature before next major release`).\n1. Rebase latest remote main branch locally (`git pull --rebase origin main`).\n1. Ensure all analysis checks and tests are passing (`time TEST_COMPUTE_INIT=1 TEST_COMPUTE_BUILD=1 TEST_COMPUTE_DEPLOY=1 make all`).\n1. Ensure goreleaser builds locally (`make release GORELEASER_ARGS=\"--snapshot --skip=validate --skip=post-hooks --clean\"`).\n1. Open a new PR to update CHANGELOG ([example](https://github.com/fastly/cli/pull/273)).\n    - We utilize [semantic versioning](https://semver.org/) and only include relevant/significant changes within the CHANGELOG (be sure to document changes to the app config if `config_version` has changed, and if any breaking interface changes are made to the fastly.toml manifest those should be documented on https://fastly.com/documentation/developers).\n1. Merge CHANGELOG.\n1. Rebase latest remote main branch locally (`git pull --rebase origin main`).\n1. Create a new signed tag (replace `{{remote}}` with the remote pointing to the official repository i.e. `origin` or `upstream` depending on your Git workflow): `tag=vX.Y.Z && git tag -s $tag -m $tag && git push {{remote}} $tag`\n    - Triggers a [github action](https://github.com/fastly/cli/blob/main/.github/workflows/tag_to_draft_release.yml) that produces a 'draft' release.\n1. Copy/paste CHANGELOG into the [draft release](https://github.com/fastly/cli/releases).\n1. Publish draft release.\n\n## Creation of npm packages\n\nEach release of the Fastly CLI triggers a workflow in `.github/workflows/publish_release.yml` that results in the creation of a new version of the `@fastly/cli` npm package, as well as multiple packages each representing a supported platform/arch combo (e.g. `@fastly/cli-darwin-arm64`). These packages are given the same version number as the Fastly CLI release. The workflow then publishes the `@fastly/cli` package and the per-platform/arch packages to npmjs.com using [Trusted Publishing](https://docs.npmjs.com/trusted-publishers). The per-platform/arch packages are generated on each release and not committed to source control.\n\n> [!NOTE]\n> The workflow step that performs `npm version` in the directory of the `@fastly/cli` package triggers the execution of the `version` script listed in its `package.json`. In turn, this script creates the per-platform/arch packages.\n\nThe `@fastly/cli` package is set up to declare the platform/arch-specific packages as `optionalDependencies`. When a package installs `@fastly/cli` as one of its `dependencies`, npm will additionally install just the platform/arch-specific package compatible with the environment.\n\n> [!NOTE]\n> The `optionalDependencies` list only restricts the packages that are actually installed into the `node_modules` directory in an environment, and does not affect what is saved to the lockfile (`package-lock.json`). All the platform/arch-specific packages will be listed in the lockfile, so a single lockfile is safe to use in environments that may represent a different platform/arch combo.\n\nTo see an example of the module layout, run:\n\n```sh\nnpm install @fastly/cli-darwin-arm64 --verbose\nls node_modules/@fastly/cli-darwin-arm64\n```\n\nYou should see a `fastly` executable binary as well as an `index.js` shim which allows the package to be imported as a module by other JavaScript projects.\n"
  },
  {
    "path": "SECURITY.md",
    "content": "## Report a security issue\n\nThe fastly/cli project team welcomes security reports and is committed to providing prompt attention to security issues. Security issues should be reported privately via [Fastly’s security issue reporting process](https://www.fastly.com/security/report-security-issue).\n\n## Security advisories\n\nRemediation of security vulnerabilities is prioritized by the project team. The project team endeavors to coordinate remediation with third-party stakeholders, and is committed to transparency in the disclosure process. The fastly/cli team announces security issues in release notes as well as Github Security Advisories on a best-effort basis.\n\nNote that communications related to security issues in Fastly-maintained OSS as described here are distinct from [Fastly Security Advisories](https://www.fastly.com/security-advisories).\n"
  },
  {
    "path": "TESTING.md",
    "content": "## Testing\n\nTo run the test suite:\n\n```sh\nmake test\n```\n\nNote that by default the tests are run using `go test` with the following configuration:\n\n```\n-race ./{cmd,pkg}/...\n```\n\nTo run a specific test use the `-run` flag (exposed by `go test`) and also provide the path to the directory where the test files reside (replace `...` and `<path>` with appropriate values):\n\n```sh\nmake test TEST_ARGS=\"-run <...> <path>\"\n```\n\n**Example**:\n\n```sh\nmake test TEST_ARGS=\"-run TestBackendCreate ./pkg/commands/backend\"\n```\n\nSome integration tests aren't run outside of the CI environment, to enable these tests locally you'll need to set a specific environment variable relevant to the test.\n\nThe available environment variables are:\n\n- `TEST_COMPUTE_INIT`: runs `TestInit`.\n- `TEST_COMPUTE_BUILD`: runs `TestBuildRust`, `TestBuildJavaScript`, `TestBuildGo`.\n- `TEST_COMPUTE_BUILD_RUST`: runs `TestBuildRust`.\n- `TEST_COMPUTE_BUILD_JAVASCRIPT`: runs `TestBuildJavaScript`.\n- `TEST_COMPUTE_DEPLOY`: runs `TestDeploy`.\n\n**Example**:\n\n```sh\nTEST_COMPUTE_BUILD_RUST=1 make test TEST_ARGS=\"-run TestBuildRust/fastly_crate_prerelease ./pkg/compute/...\" \n```\n\nWhen running the tests locally, if you don't have the relevant language ecosystems set-up properly then the tests will fail to run and you'll need to review the code to see what the remediation steps are, as that output doesn't get shown when running the test suite.\n\n> **NOTE**: you might notice a discrepancy between CI and your local environment which is caused by the difference in Rust toolchain versions as defined in .github/workflows/pr_test.yml which specifies the version required to be tested for in CI. Running `rustup toolchain install <version>` and `rustup target add wasm32-wasip1 --toolchain <version>` will resolve any failing integration tests you may be running locally.\n\nTo run the full test suite:\n\n```sh\nTEST_COMPUTE_INIT=1 TEST_COMPUTE_BUILD=1 TEST_COMPUTE_DEPLOY=1 TEST_COMMAND=gotest make all\n```\n\n> **NOTE**: `TEST_COMMAND` is optional and allows the use of https://github.com/rakyll/gotest to improve test output.\n\n### Debugging\n\nTo debug failing tests you can use [Delve](<>).\n\nEssentially, `cd` into a package directory (where the `_test.go` file is you want to run) and then execute...\n\n```\nTEST_COMPUTE_BUILD=1 dlv test -- -test.v -test.run TestNameGoesHere\n```\n\nOnce that is done, you can set breakpoints. For example:\n\n```\nbreak ../../app/run.go:152\n```\n\n> **NOTE:** The path is relative to the package directory you're running the test file.\n"
  },
  {
    "path": "cmd/fastly/main.go",
    "content": "// Package main is the entry point for the Fastly CLI.\npackage main\n\nimport (\n\t\"os\"\n\n\t\"github.com/fastly/cli/pkg/app\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n)\n\nfunc main() {\n\tif err := app.Run(os.Args, os.Stdin); err != nil {\n\t\tif skipExit := fsterr.Process(err, os.Args, os.Stdout); skipExit {\n\t\t\treturn\n\t\t}\n\t\tos.Exit(1)\n\t}\n}\n"
  },
  {
    "path": "deb-copyright",
    "content": "Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/\nSource: https://github.com/fastly/cli\nUpstream-Contact: https://github.com/fastly/cli/issues\nLicense: Apache\n On Debian systems, the complete text of the Apache version 2.0 license can be\n found in `/usr/share/common-licenses/Apache-2.0'.\n"
  },
  {
    "path": "go.mod",
    "content": "module github.com/fastly/cli\n\ngo 1.25.0\n\ntoolchain go1.25.7\n\nrequire (\n\tgithub.com/Masterminds/semver/v3 v3.5.0\n\tgithub.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b // indirect\n\tgithub.com/bep/debounce v1.2.1\n\tgithub.com/blang/semver v3.5.1+incompatible\n\tgithub.com/dustinkirkland/golang-petname v0.0.0-20240428194347-eebcea082ee0\n\tgithub.com/fastly/kingpin v2.1.12-0.20191105091915-95d230a53780+incompatible\n\tgithub.com/fatih/color v1.19.0\n\tgithub.com/fsnotify/fsnotify v1.10.1\n\tgithub.com/google/go-cmp v0.7.0\n\tgithub.com/mattn/go-isatty v0.0.22 // indirect\n\tgithub.com/mitchellh/go-wordwrap v1.0.1\n\tgithub.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c\n\tgithub.com/nicksnyder/go-i18n v1.10.3 // indirect\n\tgithub.com/pelletier/go-toml v1.9.5\n\tgithub.com/segmentio/textio v1.2.0\n\tgithub.com/tomnomnom/linkheader v0.0.0-20250811210735-e5fe3b51442e\n\tgolang.org/x/sys v0.44.0 // indirect\n\tgolang.org/x/term v0.43.0\n)\n\nrequire (\n\tgithub.com/hashicorp/cap v0.13.0\n\tgithub.com/kennygrant/sanitize v1.2.4\n\tgithub.com/otiai10/copy v1.14.1\n\tgithub.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06\n\tgithub.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966\n\tgithub.com/theckman/yacspin v0.13.12\n\tgolang.org/x/crypto v0.51.0\n\tgolang.org/x/exp v0.0.0-20260112195511-716be5621a96\n\tgolang.org/x/mod v0.36.0\n)\n\nrequire (\n\tgithub.com/STARRY-S/zip v0.2.3 // indirect\n\tgithub.com/bodgit/plumbing v1.3.0 // indirect\n\tgithub.com/bodgit/sevenzip v1.6.2 // indirect\n\tgithub.com/bodgit/windows v1.0.1 // indirect\n\tgithub.com/clipperhouse/uax29/v2 v2.7.0 // indirect\n\tgithub.com/dnaeon/go-vcr v1.2.0 // indirect\n\tgithub.com/go-jose/go-jose/v4 v4.1.4 // indirect\n\tgithub.com/hashicorp/golang-lru/v2 v2.0.7 // indirect\n\tgithub.com/kr/pretty v0.3.1 // indirect\n\tgithub.com/mikelolasagasti/xz v1.0.1 // indirect\n\tgithub.com/minio/minlz v1.1.1 // indirect\n\tgithub.com/nwaples/rardecode/v2 v2.2.2 // indirect\n\tgithub.com/otiai10/mint v1.6.3 // indirect\n\tgithub.com/rogpeppe/go-internal v1.14.1 // indirect\n\tgithub.com/sorairolake/lzip-go v0.3.8 // indirect\n\tgithub.com/spf13/afero v1.15.0 // indirect\n\tgithub.com/stretchr/testify v1.11.1 // indirect\n\tgo4.org v0.0.0-20260112195520-a5071408f32f // indirect\n\tgopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect\n)\n\nrequire (\n\tgithub.com/andybalholm/brotli v1.2.1 // indirect\n\tgithub.com/coreos/go-oidc/v3 v3.18.0 // indirect\n\tgithub.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect\n\tgithub.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 // indirect\n\tgithub.com/go-jose/go-jose/v3 v3.0.5 // indirect\n\tgithub.com/google/go-querystring v1.2.0 // indirect\n\tgithub.com/google/jsonapi v1.0.0 // indirect\n\tgithub.com/hashicorp/go-cleanhttp v0.5.2 // indirect\n\tgithub.com/hashicorp/go-hclog v1.6.3 // indirect\n\tgithub.com/hashicorp/go-uuid v1.0.3 // indirect\n\tgithub.com/klauspost/compress v1.18.6 // indirect\n\tgithub.com/klauspost/pgzip v1.2.6 // indirect\n\tgithub.com/mattn/go-colorable v0.1.14 // indirect\n\tgithub.com/mattn/go-runewidth v0.0.23 // indirect\n\tgithub.com/peterhellberg/link v1.2.0 // indirect\n\tgithub.com/pierrec/lz4/v4 v4.1.26 // indirect\n\tgithub.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect\n\tgithub.com/ulikunitz/xz v0.5.15 // indirect\n\tgolang.org/x/net v0.54.0 // indirect\n\tgolang.org/x/oauth2 v0.36.0 // indirect\n\tgolang.org/x/sync v0.20.0 // indirect\n\tgolang.org/x/text v0.37.0\n\tgopkg.in/yaml.v2 v2.4.0 // indirect\n\tgopkg.in/yaml.v3 v3.0.1 // indirect\n)\n\nrequire (\n\t4d63.com/optional v0.2.0\n\tgithub.com/creack/pty v1.1.24\n\tgithub.com/fastly/go-fastly/v15 v15.0.1\n\tgithub.com/mholt/archives v0.1.5\n\tgithub.com/mitchellh/go-ps v1.0.0\n)\n"
  },
  {
    "path": "go.sum",
    "content": "4d63.com/optional v0.2.0 h1:VtMa/Iy8Xn5JuIqJYwDScgBSBsZsKCwP7s35NiUB+8A=\n4d63.com/optional v0.2.0/go.mod h1:DBA8tAdkYkYbvRq1lK3FyDBBzioAJzZzQPC6Vj+a3jk=\ngithub.com/Masterminds/semver/v3 v3.5.0 h1:kQceYJfbupGfZOKZQg0kou0DgAKhzDg2NZPAwZ/2OOE=\ngithub.com/Masterminds/semver/v3 v3.5.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=\ngithub.com/STARRY-S/zip v0.2.3 h1:luE4dMvRPDOWQdeDdUxUoZkzUIpTccdKdhHHsQJ1fm4=\ngithub.com/STARRY-S/zip v0.2.3/go.mod h1:lqJ9JdeRipyOQJrYSOtpNAiaesFO6zVDsE8GIGFaoSk=\ngithub.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b h1:mimo19zliBX/vSQ6PWWSL9lK8qwHozUj03+zLoEB8O0=\ngithub.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b/go.mod h1:fvzegU4vN3H1qMT+8wDmzjAcDONcgo2/SZ/TyfdUOFs=\ngithub.com/andybalholm/brotli v1.2.1 h1:R+f5xP285VArJDRgowrfb9DqL18yVK0gKAW/F+eTWro=\ngithub.com/andybalholm/brotli v1.2.1/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=\ngithub.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY=\ngithub.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0=\ngithub.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ=\ngithub.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=\ngithub.com/bodgit/plumbing v1.3.0 h1:pf9Itz1JOQgn7vEOE7v7nlEfBykYqvUYioC61TwWCFU=\ngithub.com/bodgit/plumbing v1.3.0/go.mod h1:JOTb4XiRu5xfnmdnDJo6GmSbSbtSyufrsyZFByMtKEs=\ngithub.com/bodgit/sevenzip v1.6.2 h1:6/0mwj5KaRXpuf9iSiE+VpG7VpzFJ8D60P53VjxRv34=\ngithub.com/bodgit/sevenzip v1.6.2/go.mod h1:q8DktB7GbvNn0Q6u4Iq6zULE0vo3rWtRHQg5L1XmjuU=\ngithub.com/bodgit/windows v1.0.1 h1:tF7K6KOluPYygXa3Z2594zxlkbKPAOvqr97etrGNIz4=\ngithub.com/bodgit/windows v1.0.1/go.mod h1:a6JLwrB4KrTR5hBpp8FI9/9W9jJfeQ2h4XDXU74ZCdM=\ngithub.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk=\ngithub.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM=\ngithub.com/coreos/go-oidc/v3 v3.18.0 h1:V9orjXynvu5wiC9SemFTWnG4F45v403aIcjWo0d41+A=\ngithub.com/coreos/go-oidc/v3 v3.18.0/go.mod h1:DYCf24+ncYi+XkIH97GY1+dqoRlbaSI26KVTCI9SrY4=\ngithub.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=\ngithub.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s=\ngithub.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=\ngithub.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI=\ngithub.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ=\ngithub.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 h1:2tV76y6Q9BB+NEBasnqvs7e49aEBFI8ejC89PSnWH+4=\ngithub.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707/go.mod h1:qssHWj60/X5sZFNxpG4HBPDHVqxNm4DfnCKgrbZOT+s=\ngithub.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY=\ngithub.com/dustinkirkland/golang-petname v0.0.0-20240428194347-eebcea082ee0 h1:aYo8nnk3ojoQkP5iErif5Xxv0Mo0Ga/FR5+ffl/7+Nk=\ngithub.com/dustinkirkland/golang-petname v0.0.0-20240428194347-eebcea082ee0/go.mod h1:8AuBTZBRSFqEYBPYULd+NN474/zZBLP+6WeT5S9xlAc=\ngithub.com/fastly/go-fastly/v15 v15.0.1 h1:RSUttbnx6iiBHgeNF6suPSdno8eURtv/JfWI0QRHDQM=\ngithub.com/fastly/go-fastly/v15 v15.0.1/go.mod h1:hR7lXnPPI57fKIVtTTmrGrKnfE0GnW5mCT0drGYdjn0=\ngithub.com/fastly/kingpin v2.1.12-0.20191105091915-95d230a53780+incompatible h1:FhrXlfhgGCS+uc6YwyiFUt04alnjpoX7vgDKJxS6Qbk=\ngithub.com/fastly/kingpin v2.1.12-0.20191105091915-95d230a53780+incompatible/go.mod h1:U8UynVoU1SQaqD2I4ZqgYd5lx3A1ipQYn4aSt2Y5h6c=\ngithub.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=\ngithub.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w=\ngithub.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE=\ngithub.com/fsnotify/fsnotify v1.10.1 h1:b0/UzAf9yR5rhf3RPm9gf3ehBPpf0oZKIjtpKrx59Ho=\ngithub.com/fsnotify/fsnotify v1.10.1/go.mod h1:TLheqan6HD6GBK6PrDWyDPBaEV8LspOxvPSjC+bVfgo=\ngithub.com/go-jose/go-jose/v3 v3.0.5 h1:BLLJWbC4nMZOfuPVxoZIxeYsn6Nl2r1fITaJ78UQlVQ=\ngithub.com/go-jose/go-jose/v3 v3.0.5/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ=\ngithub.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA=\ngithub.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=\ngithub.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=\ngithub.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=\ngithub.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=\ngithub.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=\ngithub.com/google/go-querystring v1.2.0 h1:yhqkPbu2/OH+V9BfpCVPZkNmUXhb2gBxJArfhIxNtP0=\ngithub.com/google/go-querystring v1.2.0/go.mod h1:8IFJqpSRITyJ8QhQ13bmbeMBDfmeEJZD5A0egEOmkqU=\ngithub.com/google/jsonapi v1.0.0 h1:qIGgO5Smu3yJmSs+QlvhQnrscdZfFhiV6S8ryJAglqU=\ngithub.com/google/jsonapi v1.0.0/go.mod h1:YYHiRPJT8ARXGER8In9VuLv4qvLfDmA9ULQqptbLE4s=\ngithub.com/hashicorp/cap v0.13.0 h1:bzLS1er9am6hOiw//TEjmwZ3t975iFfRfvXY6VRLKEw=\ngithub.com/hashicorp/cap v0.13.0/go.mod h1:Kbu5owAOJzQ/HH4Ba/76wsIXN2tRiJgToJKBO2MAEFE=\ngithub.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=\ngithub.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=\ngithub.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k=\ngithub.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=\ngithub.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=\ngithub.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=\ngithub.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=\ngithub.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=\ngithub.com/kennygrant/sanitize v1.2.4 h1:gN25/otpP5vAsO2djbMhF/LQX6R7+O1TB4yv8NzpJ3o=\ngithub.com/kennygrant/sanitize v1.2.4/go.mod h1:LGsjYYtgxbetdg5owWB2mpgUL6e2nfw2eObZ0u0qvak=\ngithub.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=\ngithub.com/klauspost/compress v1.18.6 h1:2jupLlAwFm95+YDR+NwD2MEfFO9d4z4Prjl1XXDjuao=\ngithub.com/klauspost/compress v1.18.6/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=\ngithub.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=\ngithub.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=\ngithub.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=\ngithub.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=\ngithub.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=\ngithub.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=\ngithub.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=\ngithub.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=\ngithub.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=\ngithub.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=\ngithub.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=\ngithub.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=\ngithub.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=\ngithub.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=\ngithub.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=\ngithub.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=\ngithub.com/mattn/go-isatty v0.0.22 h1:j8l17JJ9i6VGPUFUYoTUKPSgKe/83EYU2zBC7YNKMw4=\ngithub.com/mattn/go-isatty v0.0.22/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4=\ngithub.com/mattn/go-runewidth v0.0.23 h1:7ykA0T0jkPpzSvMS5i9uoNn2Xy3R383f9HDx3RybWcw=\ngithub.com/mattn/go-runewidth v0.0.23/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=\ngithub.com/mholt/archives v0.1.5 h1:Fh2hl1j7VEhc6DZs2DLMgiBNChUux154a1G+2esNvzQ=\ngithub.com/mholt/archives v0.1.5/go.mod h1:3TPMmBLPsgszL+1As5zECTuKwKvIfj6YcwWPpeTAXF4=\ngithub.com/mikelolasagasti/xz v1.0.1 h1:Q2F2jX0RYJUG3+WsM+FJknv+6eVjsjXNDV0KJXZzkD0=\ngithub.com/mikelolasagasti/xz v1.0.1/go.mod h1:muAirjiOUxPRXwm9HdDtB3uoRPrGnL85XHtokL9Hcgc=\ngithub.com/minio/minlz v1.1.1 h1:OGmft1V6AnI/Wme332U6bhG54nxEan+VFgkD7lat4KM=\ngithub.com/minio/minlz v1.1.1/go.mod h1:qT0aEB35q79LLornSzeDH75LBf3aH1MV+jB5w9Wasec=\ngithub.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc=\ngithub.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg=\ngithub.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0=\ngithub.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0=\ngithub.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c h1:cqn374mizHuIWj+OSJCajGr/phAmuMug9qIX3l9CflE=\ngithub.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=\ngithub.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8=\ngithub.com/nicksnyder/go-i18n v1.10.3 h1:0U60fnLBNrLBVt8vb8Q67yKNs+gykbQuLsIkiesJL+w=\ngithub.com/nicksnyder/go-i18n v1.10.3/go.mod h1:hvLG5HTlZ4UfSuVLSRuX7JRUomIaoKQM19hm6f+no7o=\ngithub.com/nwaples/rardecode/v2 v2.2.2 h1:/5oL8dzYivRM/tqX9VcTSWfbpwcbwKG1QtSJr3b3KcU=\ngithub.com/nwaples/rardecode/v2 v2.2.2/go.mod h1:7uz379lSxPe6j9nvzxUZ+n7mnJNgjsRNb6IbvGVHRmw=\ngithub.com/otiai10/copy v1.14.1 h1:5/7E6qsUMBaH5AnQ0sSLzzTg1oTECmcCmT6lvF45Na8=\ngithub.com/otiai10/copy v1.14.1/go.mod h1:oQwrEDDOci3IM8dJF0d8+jnbfPDllW6vUjNc3DoZm9I=\ngithub.com/otiai10/mint v1.6.3 h1:87qsV/aw1F5as1eH1zS/yqHY85ANKVMgkDrf9rcxbQs=\ngithub.com/otiai10/mint v1.6.3/go.mod h1:MJm72SBthJjz8qhefc4z1PYEieWmy8Bku7CjcAqyUSM=\ngithub.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=\ngithub.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=\ngithub.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=\ngithub.com/peterhellberg/link v1.2.0 h1:UA5pg3Gp/E0F2WdX7GERiNrPQrM1K6CVJUUWfHa4t6c=\ngithub.com/peterhellberg/link v1.2.0/go.mod h1:gYfAh+oJgQu2SrZHg5hROVRQe1ICoK0/HHJTcE0edxc=\ngithub.com/pierrec/lz4/v4 v4.1.26 h1:GrpZw1gZttORinvzBdXPUXATeqlJjqUG/D87TKMnhjY=\ngithub.com/pierrec/lz4/v4 v4.1.26/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4=\ngithub.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=\ngithub.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=\ngithub.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=\ngithub.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=\ngithub.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06 h1:OkMGxebDjyw0ULyrTYWeN0UNCCkmCWfjPnIA2W6oviI=\ngithub.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06/go.mod h1:+ePHsJ1keEjQtpvf9HHw0f4ZeJ0TLRsxhunSI2hYJSs=\ngithub.com/segmentio/textio v1.2.0 h1:Ug4IkV3kh72juJbG8azoSBlgebIbUUxVNrfFcKHfTSQ=\ngithub.com/segmentio/textio v1.2.0/go.mod h1:+Rb7v0YVODP+tK5F7FD9TCkV7gOYx9IgLHWiqtvY8ag=\ngithub.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 h1:JIAuq3EEf9cgbU6AtGPK4CTG3Zf6CKMNqf0MHTggAUA=\ngithub.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog=\ngithub.com/sorairolake/lzip-go v0.3.8 h1:j5Q2313INdTA80ureWYRhX+1K78mUXfMoPZCw/ivWik=\ngithub.com/sorairolake/lzip-go v0.3.8/go.mod h1:JcBqGMV0frlxwrsE9sMWXDjqn3EeVf0/54YPsw66qkU=\ngithub.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=\ngithub.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=\ngithub.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=\ngithub.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=\ngithub.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=\ngithub.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=\ngithub.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=\ngithub.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=\ngithub.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=\ngithub.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=\ngithub.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=\ngithub.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=\ngithub.com/theckman/yacspin v0.13.12 h1:CdZ57+n0U6JMuh2xqjnjRq5Haj6v1ner2djtLQRzJr4=\ngithub.com/theckman/yacspin v0.13.12/go.mod h1:Rd2+oG2LmQi5f3zC3yeZAOl245z8QOvrH4OPOJNZxLg=\ngithub.com/tomnomnom/linkheader v0.0.0-20250811210735-e5fe3b51442e h1:tD38/4xg4nuQCASJ/JxcvCHNb46w0cdAaJfkzQOO1bA=\ngithub.com/tomnomnom/linkheader v0.0.0-20250811210735-e5fe3b51442e/go.mod h1:krvJ5AY/MjdPkTeRgMYbIDhbbbVvnPQPzsIsDJO8xrY=\ngithub.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=\ngithub.com/ulikunitz/xz v0.5.15 h1:9DNdB5s+SgV3bQ2ApL10xRc35ck0DuIX/isZvIk+ubY=\ngithub.com/ulikunitz/xz v0.5.15/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=\ngithub.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=\ngithub.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=\ngithub.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=\ngo4.org v0.0.0-20260112195520-a5071408f32f h1:ziUVAjmTPwQMBmYR1tbdRFJPtTcQUI12fH9QQjfb0Sw=\ngo4.org v0.0.0-20260112195520-a5071408f32f/go.mod h1:ZRJnO5ZI4zAwMFp+dS1+V6J6MSyAowhRqAE+DPa1Xp0=\ngolang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=\ngolang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=\ngolang.org/x/crypto v0.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI=\ngolang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8=\ngolang.org/x/exp v0.0.0-20260112195511-716be5621a96 h1:Z/6YuSHTLOHfNFdb8zVZomZr7cqNgTJvA8+Qz75D8gU=\ngolang.org/x/exp v0.0.0-20260112195511-716be5621a96/go.mod h1:nzimsREAkjBCIEFtHiYkrJyT+2uy9YZJB7H1k68CXZU=\ngolang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=\ngolang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=\ngolang.org/x/mod v0.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4=\ngolang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ=\ngolang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=\ngolang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=\ngolang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=\ngolang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=\ngolang.org/x/net v0.54.0 h1:2zJIZAxAHV/OHCDTCOHAYehQzLfSXuf/5SoL/Dv6w/w=\ngolang.org/x/net v0.54.0/go.mod h1:Sj4oj8jK6XmHpBZU/zWHw3BV3abl4Kvi+Ut7cQcY+cQ=\ngolang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=\ngolang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=\ngolang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=\ngolang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=\ngolang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=\ngolang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ=\ngolang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=\ngolang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=\ngolang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=\ngolang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=\ngolang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=\ngolang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=\ngolang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4=\ngolang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk=\ngolang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=\ngolang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=\ngolang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=\ngolang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=\ngolang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=\ngolang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=\ngolang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=\ngolang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=\ngolang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=\ngopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=\ngopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=\ngopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=\ngopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=\ngopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\n"
  },
  {
    "path": "npm/@fastly/cli/.gitignore",
    "content": "LICENSE\nREADME.md\nSECURITY.md\n"
  },
  {
    "path": "npm/@fastly/cli/fastly.js",
    "content": "#!/usr/bin/env node\nimport { execFileSync } from \"node:child_process\";\n\nimport { pkgForCurrentPlatform } from \"./package-helpers.js\";\n\nconst pkg = pkgForCurrentPlatform();\n\nlet location;\ntry {\n  // Check for the binary package from our \"optionalDependencies\". This\n  // package should have been installed alongside this package at install time.\n  location = (await import(pkg)).default;\n} catch (e) {\n  throw new Error(`The package \"${pkg}\" could not be found, and is needed by @fastly/cli.\n    Either the package is missing or the platform/architecture you are using is not supported.\n    If you are installing @fastly/cli with npm, make sure that you don't specify the\n    \"--no-optional\" flag. The \"optionalDependencies\" package.json feature is used\n    by @fastly/cli to install the correct binary executable for your current platform.\n    If your platform is not supported, you can open an issue at https://github.com/fastly/cli/issues`);\n}\n\ntry {\n  execFileSync(location, process.argv.slice(2), { stdio: \"inherit\" });\n} catch(err) {\n  if (err.code) {\n    // Spawning child process failed\n    throw err;\n  }\n  if (err.status != null) {\n    process.exitCode = err.status;\n  }\n}\n"
  },
  {
    "path": "npm/@fastly/cli/index.d.ts",
    "content": "declare module '@fastly/cli' {\n  const location: string;\n  export default location;\n}\n"
  },
  {
    "path": "npm/@fastly/cli/index.js",
    "content": "import { pkgForCurrentPlatform } from \"./package-helpers.js\";\n\nconst pkg = pkgForCurrentPlatform();\n\nlet location;\ntry {\n  // Check for the binary package from our \"optionalDependencies\". This\n  // package should have been installed alongside this package at install time.\n  location = (await import(pkg)).default;\n} catch (e) {\n  throw new Error(`The package \"${pkg}\" could not be found, and is needed by @fastly/cli.\n    Either the package is missing or the platform/architecture you are using is not supported.\n    If you are installing @fastly/cli with npm, make sure that you don't specify the\n    \"--no-optional\" flag. The \"optionalDependencies\" package.json feature is used\n    by @fastly/cli to install the correct binary executable for your current platform.\n    If your platform is not supported, you can open an issue at https://github.com/fastly/cli/issues`);\n}\n\nexport default location;\n"
  },
  {
    "path": "npm/@fastly/cli/package-helpers.js",
    "content": "import { platform, arch } from \"node:process\";\n\nexport function pkgForCurrentPlatform() {\n  return `@fastly/cli-${platform}-${arch}`;\n}\n"
  },
  {
    "path": "npm/@fastly/cli/package.json",
    "content": "{\n    \"name\": \"@fastly/cli\",\n    \"version\": \"10.12.3\",\n    \"description\": \"Build, deploy and configure Fastly services from your terminal\",\n    \"type\": \"module\",\n    \"scripts\": {\n        \"prepack\": \"cp ../../../README.md ../../../LICENSE ../../../SECURITY.md .\",\n        \"version\": \"node ./update.js $npm_package_version\"\n    },\n    \"devDependencies\": {\n        \"decompress\": \"^4.2.1\",\n        \"decompress-targz\": \"^4.1.1\"\n    },\n    \"main\": \"index.js\",\n    \"types\": \"index.d.ts\",\n    \"engines\": {\n        \"node\": \">=16\"\n    },\n    \"bin\": {\n        \"fastly\": \"fastly.js\"\n    },\n    \"optionalDependencies\": {},\n    \"files\": [\n        \"index.js\",\n        \"fastly.js\",\n        \"package-helpers.js\",\n        \"update.js\",\n        \"index.d.ts\",\n        \"README.md\",\n        \"LICENSE\",\n        \"SECURITY.md\"\n    ],\n    \"license\": \"Apache-2.0\",\n    \"repository\": {\n        \"type\": \"git\",\n        \"url\": \"git+https://github.com/fastly/cli.git\"\n    },\n    \"bugs\": {\n        \"url\": \"https://github.com/fastly/cli/issues\"\n    },\n    \"homepage\": \"https://github.com/fastly/cli#readme\"\n}\n"
  },
  {
    "path": "npm/@fastly/cli/update.js",
    "content": "#!/usr/bin/env node\n\nimport { fileURLToPath } from \"node:url\";\nimport { dirname, join, parse } from \"node:path\";\nimport { mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport decompress from \"decompress\";\nimport decompressTargz from \"decompress-targz\";\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\nconst input = process.argv.slice(2).at(0);\nconst tag = input ? `v${input}` : \"dev\";\n\nlet packages = [\n  {\n    releaseAsset: `fastly_${tag}_darwin-arm64.tar.gz`,\n    binaryAsset: \"fastly\",\n    description: \"The macOS (M-series) binary for the Fastly CLI\",\n    os: \"darwin\",\n    cpu: \"arm64\",\n  },\n  {\n    releaseAsset: `fastly_${tag}_darwin-amd64.tar.gz`,\n    binaryAsset: \"fastly\",\n    description: \"The macOS (Intel) binary for the Fastly CLI\",\n    os: \"darwin\",\n    cpu: \"x64\",\n  },\n  {\n    releaseAsset: `fastly_${tag}_linux-arm64.tar.gz`,\n    binaryAsset: \"fastly\",\n    description: \"The Linux (arm64) binary for the Fastly CLI\",\n    os: \"linux\",\n    cpu: \"arm64\",\n  },\n  {\n    releaseAsset: `fastly_${tag}_linux-amd64.tar.gz`,\n    binaryAsset: \"fastly\",\n    description: \"The Linux (64-bit) binary for the Fastly CLI\",\n    os: \"linux\",\n    cpu: \"x64\",\n  },\n  {\n    releaseAsset: `fastly_${tag}_linux-386.tar.gz`,\n    binaryAsset: \"fastly\",\n    description: \"The Linux (32-bit) binary for the Fastly CLI\",\n    os: \"linux\",\n    cpu: \"x32\",\n  },\n  {\n    releaseAsset: `fastly_${tag}_windows-arm64.tar.gz`,\n    binaryAsset: \"fastly.exe\",\n    description: \"The Windows (arm64) binary for the Fastly CLI\",\n    os: \"win32\",\n    cpu: \"arm64\",\n  },\n  {\n    releaseAsset: `fastly_${tag}_windows-amd64.tar.gz`,\n    binaryAsset: \"fastly.exe\",\n    description: \"The Windows (64-bit) binary for the Fastly CLI\",\n    os: \"win32\",\n    cpu: \"x64\",\n  },\n  {\n    releaseAsset: `fastly_${tag}_windows-386.tar.gz`,\n    binaryAsset: \"fastly.exe\",\n    description: \"The Windows (32-bit) binary for the Fastly CLI\",\n    os: \"win32\",\n    cpu: \"x32\",\n  },\n];\n\nlet response = await fetch(\n  `https://api.github.com/repos/fastly/cli/releases/tags/${tag}`\n);\nif (!response.ok) {\n  console.error(\n    '%s %o',\n    `Response from https://api.github.com/repos/fastly/cli/releases/tags/${tag} was not ok`,\n    response\n  );\n  console.error(await response.text());\n  process.exit(1);\n}\nresponse = await response.json();\nconst id = response.id;\nlet assets = await fetch(\n  `https://api.github.com/repos/fastly/cli/releases/${id}/assets`\n);\nif (!assets.ok) {\n  console.error(\n    '%s %o',\n    `Response from https://api.github.com/repos/fastly/cli/releases/${id}/assets was not ok`,\n    assets\n  );\n  console.error(await assets.text());\n  process.exit(1);\n}\nassets = await assets.json();\n\nlet generatedPackages = [];\n\nfor (const info of packages) {\n  const packageName = `cli-${info.os}-${info.cpu}`;\n  const asset = assets.find((asset) => asset.name === info.releaseAsset);\n  if (!asset) {\n    console.error(\n      `Can't find an asset named ${info.releaseAsset} for the release https://github.com/fastly/cli/releases/tag/${tag}`\n    );\n    process.exit(1);\n  }\n  const packageDirectory = join(__dirname, \"../\", packageName.split(\"/\").pop());\n  await mkdir(packageDirectory, { recursive: true });\n  await writeFile(\n    join(packageDirectory, \"package.json\"),\n    packageJson(packageName, tag, info.description, info.os, info.cpu, info.binaryAsset)\n  );\n  await writeFile(\n    join(packageDirectory, \"index.js\"),\n    indexJs(info.binaryAsset)\n  );\n  generatedPackages.push(packageName);\n  const browser_download_url = asset.browser_download_url;\n  const archive = await fetch(browser_download_url);\n  if (!archive.ok) {\n    console.error(\n      '%s %o',\n      `Response from ${browser_download_url} was not ok`,\n      archive\n    );\n    console.error(await response.text());\n    process.exit(1);\n  }\n  let buf = await archive.arrayBuffer();\n\n  await decompress(Buffer.from(buf), packageDirectory, {\n    // Remove the leading directory from the extracted file.\n    strip: 1,\n    plugins: [decompressTargz()],\n    // Only extract the binary file and nothing else\n    filter: (file) => parse(file.path).base === info.binaryAsset,\n  });\n}\n\n// Generate `optionalDependencies` section in the root package.json\nconst rootPackageJsonPath = join(__dirname, \"./package.json\");\nlet rootPackageJson = await readFile(rootPackageJsonPath, \"utf8\");\nrootPackageJson = JSON.parse(rootPackageJson);\nrootPackageJson[\"optionalDependencies\"] = generatedPackages.reduce(\n  (acc, packageName) => {\n    acc[`@fastly/${packageName}`] = `=${tag.substring(1)}`;\n    return acc;\n  },\n  {}\n);\nawait writeFile(rootPackageJsonPath, JSON.stringify(rootPackageJson, null, 4));\n\nfunction indexJs(binaryAsset) {\n  return `\nimport { fileURLToPath } from 'node:url'\nimport { dirname, join } from 'node:path'\nconst __dirname = dirname(fileURLToPath(import.meta.url))\nlet location = join(__dirname, '${binaryAsset}')\nexport default location\n`;\n}\nfunction packageJson(name, version, description, os, cpu, binaryAsset) {\n  version = version.startsWith(\"v\") ? version.replace(\"v\", \"\") : version;\n  return JSON.stringify(\n    {\n      name: `@fastly/${name}`,\n      bin: {\n        [name]: `${binaryAsset}`,\n      },\n      scripts: {\n      },\n      type: \"module\",\n      version,\n      main: \"index.js\",\n      description,\n      license: \"Apache-2.0\",\n      repository: {\n        type: \"git\",\n        url: \"git+https://github.com/fastly/cli.git\"\n      },\n      bugs: {\n        url: \"https://github.com/fastly/cli/issues\"\n      },\n      homepage: \"https://github.com/fastly/cli#readme\",\n      preferUnplugged: false,\n      os: [os],\n      cpu: [cpu],\n    },\n    null,\n    4\n  );\n}\n"
  },
  {
    "path": "pkg/api/doc.go",
    "content": "// Package api provides abstractions for talking to the Fastly API.\npackage api\n"
  },
  {
    "path": "pkg/api/interface.go",
    "content": "package api\n\nimport (\n\t\"context\"\n\t\"crypto/ed25519\"\n\t\"net/http\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n)\n\n// HTTPClient models a concrete http.Client. It's a consumer contract for some\n// commands which need to make direct HTTP requests to the API, because the\n// official Fastly client library lacks certain endpoints, so we call the API\n// directly.\ntype HTTPClient interface {\n\tDo(*http.Request) (*http.Response, error)\n}\n\n// Interface models the methods of the Fastly API client that we use.\n// It exists to allow for easier testing, in combination with Mock.\ntype Interface interface {\n\tAllIPs(context.Context) (v4, v6 fastly.IPAddrs, err error)\n\tAllDatacenters(context.Context) (datacenters []fastly.Datacenter, err error)\n\n\tCreateService(context.Context, *fastly.CreateServiceInput) (*fastly.Service, error)\n\tGetServices(context.Context, *fastly.GetServicesInput) *fastly.ListPaginator[fastly.Service]\n\tListServices(context.Context, *fastly.ListServicesInput) ([]*fastly.Service, error)\n\tGetService(context.Context, *fastly.GetServiceInput) (*fastly.Service, error)\n\tGetServiceDetails(context.Context, *fastly.GetServiceDetailsInput) (*fastly.ServiceDetail, error)\n\tUpdateService(context.Context, *fastly.UpdateServiceInput) (*fastly.Service, error)\n\tDeleteService(context.Context, *fastly.DeleteServiceInput) error\n\tSearchService(context.Context, *fastly.SearchServiceInput) (*fastly.Service, error)\n\n\tCloneVersion(context.Context, *fastly.CloneVersionInput) (*fastly.Version, error)\n\tListVersions(context.Context, *fastly.ListVersionsInput) ([]*fastly.Version, error)\n\tGetVersion(context.Context, *fastly.GetVersionInput) (*fastly.Version, error)\n\tUpdateVersion(context.Context, *fastly.UpdateVersionInput) (*fastly.Version, error)\n\tActivateVersion(context.Context, *fastly.ActivateVersionInput) (*fastly.Version, error)\n\tDeactivateVersion(context.Context, *fastly.DeactivateVersionInput) (*fastly.Version, error)\n\tLockVersion(context.Context, *fastly.LockVersionInput) (*fastly.Version, error)\n\tLatestVersion(context.Context, *fastly.LatestVersionInput) (*fastly.Version, error)\n\tValidateVersion(context.Context, *fastly.ValidateVersionInput) (bool, string, error)\n\n\tCreateDomain(context.Context, *fastly.CreateDomainInput) (*fastly.Domain, error)\n\tListDomains(context.Context, *fastly.ListDomainsInput) ([]*fastly.Domain, error)\n\tGetDomain(context.Context, *fastly.GetDomainInput) (*fastly.Domain, error)\n\tUpdateDomain(context.Context, *fastly.UpdateDomainInput) (*fastly.Domain, error)\n\tDeleteDomain(context.Context, *fastly.DeleteDomainInput) error\n\tValidateDomain(context.Context, *fastly.ValidateDomainInput) (*fastly.DomainValidationResult, error)\n\tValidateAllDomains(context.Context, *fastly.ValidateAllDomainsInput) ([]*fastly.DomainValidationResult, error)\n\n\tGetImageOptimizerDefaultSettings(context.Context, *fastly.GetImageOptimizerDefaultSettingsInput) (*fastly.ImageOptimizerDefaultSettings, error)\n\tUpdateImageOptimizerDefaultSettings(context.Context, *fastly.UpdateImageOptimizerDefaultSettingsInput) (*fastly.ImageOptimizerDefaultSettings, error)\n\n\tCreateBackend(context.Context, *fastly.CreateBackendInput) (*fastly.Backend, error)\n\tListBackends(context.Context, *fastly.ListBackendsInput) ([]*fastly.Backend, error)\n\tGetBackend(context.Context, *fastly.GetBackendInput) (*fastly.Backend, error)\n\tUpdateBackend(context.Context, *fastly.UpdateBackendInput) (*fastly.Backend, error)\n\tDeleteBackend(context.Context, *fastly.DeleteBackendInput) error\n\n\tCreateHealthCheck(context.Context, *fastly.CreateHealthCheckInput) (*fastly.HealthCheck, error)\n\tListHealthChecks(context.Context, *fastly.ListHealthChecksInput) ([]*fastly.HealthCheck, error)\n\tGetHealthCheck(context.Context, *fastly.GetHealthCheckInput) (*fastly.HealthCheck, error)\n\tUpdateHealthCheck(context.Context, *fastly.UpdateHealthCheckInput) (*fastly.HealthCheck, error)\n\tDeleteHealthCheck(context.Context, *fastly.DeleteHealthCheckInput) error\n\n\tGetPackage(context.Context, *fastly.GetPackageInput) (*fastly.Package, error)\n\tUpdatePackage(context.Context, *fastly.UpdatePackageInput) (*fastly.Package, error)\n\n\tCreateDictionary(context.Context, *fastly.CreateDictionaryInput) (*fastly.Dictionary, error)\n\tGetDictionary(context.Context, *fastly.GetDictionaryInput) (*fastly.Dictionary, error)\n\tDeleteDictionary(context.Context, *fastly.DeleteDictionaryInput) error\n\tListDictionaries(context.Context, *fastly.ListDictionariesInput) ([]*fastly.Dictionary, error)\n\tUpdateDictionary(context.Context, *fastly.UpdateDictionaryInput) (*fastly.Dictionary, error)\n\n\tGetDictionaryItems(context.Context, *fastly.GetDictionaryItemsInput) *fastly.ListPaginator[fastly.DictionaryItem]\n\tListDictionaryItems(context.Context, *fastly.ListDictionaryItemsInput) ([]*fastly.DictionaryItem, error)\n\tGetDictionaryItem(context.Context, *fastly.GetDictionaryItemInput) (*fastly.DictionaryItem, error)\n\tCreateDictionaryItem(context.Context, *fastly.CreateDictionaryItemInput) (*fastly.DictionaryItem, error)\n\tUpdateDictionaryItem(context.Context, *fastly.UpdateDictionaryItemInput) (*fastly.DictionaryItem, error)\n\tDeleteDictionaryItem(context.Context, *fastly.DeleteDictionaryItemInput) error\n\tBatchModifyDictionaryItems(context.Context, *fastly.BatchModifyDictionaryItemsInput) error\n\n\tGetDictionaryInfo(context.Context, *fastly.GetDictionaryInfoInput) (*fastly.DictionaryInfo, error)\n\n\tCreateBigQuery(context.Context, *fastly.CreateBigQueryInput) (*fastly.BigQuery, error)\n\tListBigQueries(context.Context, *fastly.ListBigQueriesInput) ([]*fastly.BigQuery, error)\n\tGetBigQuery(context.Context, *fastly.GetBigQueryInput) (*fastly.BigQuery, error)\n\tUpdateBigQuery(context.Context, *fastly.UpdateBigQueryInput) (*fastly.BigQuery, error)\n\tDeleteBigQuery(context.Context, *fastly.DeleteBigQueryInput) error\n\n\tCreateS3(context.Context, *fastly.CreateS3Input) (*fastly.S3, error)\n\tListS3s(context.Context, *fastly.ListS3sInput) ([]*fastly.S3, error)\n\tGetS3(context.Context, *fastly.GetS3Input) (*fastly.S3, error)\n\tUpdateS3(context.Context, *fastly.UpdateS3Input) (*fastly.S3, error)\n\tDeleteS3(context.Context, *fastly.DeleteS3Input) error\n\n\tCreateKinesis(context.Context, *fastly.CreateKinesisInput) (*fastly.Kinesis, error)\n\tListKinesis(context.Context, *fastly.ListKinesisInput) ([]*fastly.Kinesis, error)\n\tGetKinesis(context.Context, *fastly.GetKinesisInput) (*fastly.Kinesis, error)\n\tUpdateKinesis(context.Context, *fastly.UpdateKinesisInput) (*fastly.Kinesis, error)\n\tDeleteKinesis(context.Context, *fastly.DeleteKinesisInput) error\n\n\tCreateSyslog(context.Context, *fastly.CreateSyslogInput) (*fastly.Syslog, error)\n\tListSyslogs(context.Context, *fastly.ListSyslogsInput) ([]*fastly.Syslog, error)\n\tGetSyslog(context.Context, *fastly.GetSyslogInput) (*fastly.Syslog, error)\n\tUpdateSyslog(context.Context, *fastly.UpdateSyslogInput) (*fastly.Syslog, error)\n\tDeleteSyslog(context.Context, *fastly.DeleteSyslogInput) error\n\n\tCreateLogentries(context.Context, *fastly.CreateLogentriesInput) (*fastly.Logentries, error)\n\tListLogentries(context.Context, *fastly.ListLogentriesInput) ([]*fastly.Logentries, error)\n\tGetLogentries(context.Context, *fastly.GetLogentriesInput) (*fastly.Logentries, error)\n\tUpdateLogentries(context.Context, *fastly.UpdateLogentriesInput) (*fastly.Logentries, error)\n\tDeleteLogentries(context.Context, *fastly.DeleteLogentriesInput) error\n\n\tCreatePapertrail(context.Context, *fastly.CreatePapertrailInput) (*fastly.Papertrail, error)\n\tListPapertrails(context.Context, *fastly.ListPapertrailsInput) ([]*fastly.Papertrail, error)\n\tGetPapertrail(context.Context, *fastly.GetPapertrailInput) (*fastly.Papertrail, error)\n\tUpdatePapertrail(context.Context, *fastly.UpdatePapertrailInput) (*fastly.Papertrail, error)\n\tDeletePapertrail(context.Context, *fastly.DeletePapertrailInput) error\n\n\tCreateSumologic(context.Context, *fastly.CreateSumologicInput) (*fastly.Sumologic, error)\n\tListSumologics(context.Context, *fastly.ListSumologicsInput) ([]*fastly.Sumologic, error)\n\tGetSumologic(context.Context, *fastly.GetSumologicInput) (*fastly.Sumologic, error)\n\tUpdateSumologic(context.Context, *fastly.UpdateSumologicInput) (*fastly.Sumologic, error)\n\tDeleteSumologic(context.Context, *fastly.DeleteSumologicInput) error\n\n\tCreateGCS(context.Context, *fastly.CreateGCSInput) (*fastly.GCS, error)\n\tListGCSs(context.Context, *fastly.ListGCSsInput) ([]*fastly.GCS, error)\n\tGetGCS(context.Context, *fastly.GetGCSInput) (*fastly.GCS, error)\n\tUpdateGCS(context.Context, *fastly.UpdateGCSInput) (*fastly.GCS, error)\n\tDeleteGCS(context.Context, *fastly.DeleteGCSInput) error\n\n\tCreateGrafanaCloudLogs(context.Context, *fastly.CreateGrafanaCloudLogsInput) (*fastly.GrafanaCloudLogs, error)\n\tListGrafanaCloudLogs(context.Context, *fastly.ListGrafanaCloudLogsInput) ([]*fastly.GrafanaCloudLogs, error)\n\tGetGrafanaCloudLogs(context.Context, *fastly.GetGrafanaCloudLogsInput) (*fastly.GrafanaCloudLogs, error)\n\tUpdateGrafanaCloudLogs(context.Context, *fastly.UpdateGrafanaCloudLogsInput) (*fastly.GrafanaCloudLogs, error)\n\tDeleteGrafanaCloudLogs(context.Context, *fastly.DeleteGrafanaCloudLogsInput) error\n\n\tCreateFTP(context.Context, *fastly.CreateFTPInput) (*fastly.FTP, error)\n\tListFTPs(context.Context, *fastly.ListFTPsInput) ([]*fastly.FTP, error)\n\tGetFTP(context.Context, *fastly.GetFTPInput) (*fastly.FTP, error)\n\tUpdateFTP(context.Context, *fastly.UpdateFTPInput) (*fastly.FTP, error)\n\tDeleteFTP(context.Context, *fastly.DeleteFTPInput) error\n\n\tCreateSplunk(context.Context, *fastly.CreateSplunkInput) (*fastly.Splunk, error)\n\tListSplunks(context.Context, *fastly.ListSplunksInput) ([]*fastly.Splunk, error)\n\tGetSplunk(context.Context, *fastly.GetSplunkInput) (*fastly.Splunk, error)\n\tUpdateSplunk(context.Context, *fastly.UpdateSplunkInput) (*fastly.Splunk, error)\n\tDeleteSplunk(context.Context, *fastly.DeleteSplunkInput) error\n\n\tCreateScalyr(context.Context, *fastly.CreateScalyrInput) (*fastly.Scalyr, error)\n\tListScalyrs(context.Context, *fastly.ListScalyrsInput) ([]*fastly.Scalyr, error)\n\tGetScalyr(context.Context, *fastly.GetScalyrInput) (*fastly.Scalyr, error)\n\tUpdateScalyr(context.Context, *fastly.UpdateScalyrInput) (*fastly.Scalyr, error)\n\tDeleteScalyr(context.Context, *fastly.DeleteScalyrInput) error\n\n\tCreateLoggly(context.Context, *fastly.CreateLogglyInput) (*fastly.Loggly, error)\n\tListLoggly(context.Context, *fastly.ListLogglyInput) ([]*fastly.Loggly, error)\n\tGetLoggly(context.Context, *fastly.GetLogglyInput) (*fastly.Loggly, error)\n\tUpdateLoggly(context.Context, *fastly.UpdateLogglyInput) (*fastly.Loggly, error)\n\tDeleteLoggly(context.Context, *fastly.DeleteLogglyInput) error\n\n\tCreateHoneycomb(context.Context, *fastly.CreateHoneycombInput) (*fastly.Honeycomb, error)\n\tListHoneycombs(context.Context, *fastly.ListHoneycombsInput) ([]*fastly.Honeycomb, error)\n\tGetHoneycomb(context.Context, *fastly.GetHoneycombInput) (*fastly.Honeycomb, error)\n\tUpdateHoneycomb(context.Context, *fastly.UpdateHoneycombInput) (*fastly.Honeycomb, error)\n\tDeleteHoneycomb(context.Context, *fastly.DeleteHoneycombInput) error\n\n\tCreateHeroku(context.Context, *fastly.CreateHerokuInput) (*fastly.Heroku, error)\n\tListHerokus(context.Context, *fastly.ListHerokusInput) ([]*fastly.Heroku, error)\n\tGetHeroku(context.Context, *fastly.GetHerokuInput) (*fastly.Heroku, error)\n\tUpdateHeroku(context.Context, *fastly.UpdateHerokuInput) (*fastly.Heroku, error)\n\tDeleteHeroku(context.Context, *fastly.DeleteHerokuInput) error\n\n\tCreateSFTP(context.Context, *fastly.CreateSFTPInput) (*fastly.SFTP, error)\n\tListSFTPs(context.Context, *fastly.ListSFTPsInput) ([]*fastly.SFTP, error)\n\tGetSFTP(context.Context, *fastly.GetSFTPInput) (*fastly.SFTP, error)\n\tUpdateSFTP(context.Context, *fastly.UpdateSFTPInput) (*fastly.SFTP, error)\n\tDeleteSFTP(context.Context, *fastly.DeleteSFTPInput) error\n\n\tCreateLogshuttle(context.Context, *fastly.CreateLogshuttleInput) (*fastly.Logshuttle, error)\n\tListLogshuttles(context.Context, *fastly.ListLogshuttlesInput) ([]*fastly.Logshuttle, error)\n\tGetLogshuttle(context.Context, *fastly.GetLogshuttleInput) (*fastly.Logshuttle, error)\n\tUpdateLogshuttle(context.Context, *fastly.UpdateLogshuttleInput) (*fastly.Logshuttle, error)\n\tDeleteLogshuttle(context.Context, *fastly.DeleteLogshuttleInput) error\n\n\tCreateCloudfiles(context.Context, *fastly.CreateCloudfilesInput) (*fastly.Cloudfiles, error)\n\tListCloudfiles(context.Context, *fastly.ListCloudfilesInput) ([]*fastly.Cloudfiles, error)\n\tGetCloudfiles(context.Context, *fastly.GetCloudfilesInput) (*fastly.Cloudfiles, error)\n\tUpdateCloudfiles(context.Context, *fastly.UpdateCloudfilesInput) (*fastly.Cloudfiles, error)\n\tDeleteCloudfiles(context.Context, *fastly.DeleteCloudfilesInput) error\n\n\tCreateDigitalOcean(context.Context, *fastly.CreateDigitalOceanInput) (*fastly.DigitalOcean, error)\n\tListDigitalOceans(context.Context, *fastly.ListDigitalOceansInput) ([]*fastly.DigitalOcean, error)\n\tGetDigitalOcean(context.Context, *fastly.GetDigitalOceanInput) (*fastly.DigitalOcean, error)\n\tUpdateDigitalOcean(context.Context, *fastly.UpdateDigitalOceanInput) (*fastly.DigitalOcean, error)\n\tDeleteDigitalOcean(context.Context, *fastly.DeleteDigitalOceanInput) error\n\n\tCreateElasticsearch(context.Context, *fastly.CreateElasticsearchInput) (*fastly.Elasticsearch, error)\n\tListElasticsearch(context.Context, *fastly.ListElasticsearchInput) ([]*fastly.Elasticsearch, error)\n\tGetElasticsearch(context.Context, *fastly.GetElasticsearchInput) (*fastly.Elasticsearch, error)\n\tUpdateElasticsearch(context.Context, *fastly.UpdateElasticsearchInput) (*fastly.Elasticsearch, error)\n\tDeleteElasticsearch(context.Context, *fastly.DeleteElasticsearchInput) error\n\n\tCreateBlobStorage(context.Context, *fastly.CreateBlobStorageInput) (*fastly.BlobStorage, error)\n\tListBlobStorages(context.Context, *fastly.ListBlobStoragesInput) ([]*fastly.BlobStorage, error)\n\tGetBlobStorage(context.Context, *fastly.GetBlobStorageInput) (*fastly.BlobStorage, error)\n\tUpdateBlobStorage(context.Context, *fastly.UpdateBlobStorageInput) (*fastly.BlobStorage, error)\n\tDeleteBlobStorage(context.Context, *fastly.DeleteBlobStorageInput) error\n\n\tCreateDatadog(context.Context, *fastly.CreateDatadogInput) (*fastly.Datadog, error)\n\tListDatadog(context.Context, *fastly.ListDatadogInput) ([]*fastly.Datadog, error)\n\tGetDatadog(context.Context, *fastly.GetDatadogInput) (*fastly.Datadog, error)\n\tUpdateDatadog(context.Context, *fastly.UpdateDatadogInput) (*fastly.Datadog, error)\n\tDeleteDatadog(context.Context, *fastly.DeleteDatadogInput) error\n\n\tCreateHTTPS(context.Context, *fastly.CreateHTTPSInput) (*fastly.HTTPS, error)\n\tListHTTPS(context.Context, *fastly.ListHTTPSInput) ([]*fastly.HTTPS, error)\n\tGetHTTPS(context.Context, *fastly.GetHTTPSInput) (*fastly.HTTPS, error)\n\tUpdateHTTPS(context.Context, *fastly.UpdateHTTPSInput) (*fastly.HTTPS, error)\n\tDeleteHTTPS(context.Context, *fastly.DeleteHTTPSInput) error\n\n\tCreateKafka(context.Context, *fastly.CreateKafkaInput) (*fastly.Kafka, error)\n\tListKafkas(context.Context, *fastly.ListKafkasInput) ([]*fastly.Kafka, error)\n\tGetKafka(context.Context, *fastly.GetKafkaInput) (*fastly.Kafka, error)\n\tUpdateKafka(context.Context, *fastly.UpdateKafkaInput) (*fastly.Kafka, error)\n\tDeleteKafka(context.Context, *fastly.DeleteKafkaInput) error\n\n\tCreatePubsub(context.Context, *fastly.CreatePubsubInput) (*fastly.Pubsub, error)\n\tListPubsubs(context.Context, *fastly.ListPubsubsInput) ([]*fastly.Pubsub, error)\n\tGetPubsub(context.Context, *fastly.GetPubsubInput) (*fastly.Pubsub, error)\n\tUpdatePubsub(context.Context, *fastly.UpdatePubsubInput) (*fastly.Pubsub, error)\n\tDeletePubsub(context.Context, *fastly.DeletePubsubInput) error\n\n\tCreateOpenstack(context.Context, *fastly.CreateOpenstackInput) (*fastly.Openstack, error)\n\tListOpenstack(context.Context, *fastly.ListOpenstackInput) ([]*fastly.Openstack, error)\n\tGetOpenstack(context.Context, *fastly.GetOpenstackInput) (*fastly.Openstack, error)\n\tUpdateOpenstack(context.Context, *fastly.UpdateOpenstackInput) (*fastly.Openstack, error)\n\tDeleteOpenstack(context.Context, *fastly.DeleteOpenstackInput) error\n\n\tGetRegions(context.Context) (*fastly.RegionsResponse, error)\n\tGetStatsJSON(context.Context, *fastly.GetStatsInput, any) error\n\tGetAggregateJSON(context.Context, *fastly.GetAggregateInput, any) error\n\tGetUsage(context.Context, *fastly.GetUsageInput) (*fastly.UsageResponse, error)\n\tGetUsageByService(context.Context, *fastly.GetUsageInput) (*fastly.UsageByServiceResponse, error)\n\tGetDomainMetricsForService(context.Context, *fastly.GetDomainMetricsInput) (*fastly.DomainInspector, error)\n\tGetDomainMetricsForServiceJSON(context.Context, *fastly.GetDomainMetricsInput, any) error\n\tGetOriginMetricsForService(context.Context, *fastly.GetOriginMetricsInput) (*fastly.OriginInspector, error)\n\tGetOriginMetricsForServiceJSON(context.Context, *fastly.GetOriginMetricsInput, any) error\n\n\tCreateManagedLogging(context.Context, *fastly.CreateManagedLoggingInput) (*fastly.ManagedLogging, error)\n\tGetLoggingEndpointErrors(context.Context, *fastly.LoggingEndpointErrorsInput) (*fastly.LoggingEndpointErrorsResponse, error)\n\n\tGetGeneratedVCL(context.Context, *fastly.GetGeneratedVCLInput) (*fastly.VCL, error)\n\n\tCreateVCL(context.Context, *fastly.CreateVCLInput) (*fastly.VCL, error)\n\tListVCLs(context.Context, *fastly.ListVCLsInput) ([]*fastly.VCL, error)\n\tGetVCL(context.Context, *fastly.GetVCLInput) (*fastly.VCL, error)\n\tUpdateVCL(context.Context, *fastly.UpdateVCLInput) (*fastly.VCL, error)\n\tDeleteVCL(context.Context, *fastly.DeleteVCLInput) error\n\n\tCreateSnippet(context.Context, *fastly.CreateSnippetInput) (*fastly.Snippet, error)\n\tListSnippets(context.Context, *fastly.ListSnippetsInput) ([]*fastly.Snippet, error)\n\tGetSnippet(context.Context, *fastly.GetSnippetInput) (*fastly.Snippet, error)\n\tGetDynamicSnippet(context.Context, *fastly.GetDynamicSnippetInput) (*fastly.DynamicSnippet, error)\n\tUpdateSnippet(context.Context, *fastly.UpdateSnippetInput) (*fastly.Snippet, error)\n\tUpdateDynamicSnippet(context.Context, *fastly.UpdateDynamicSnippetInput) (*fastly.DynamicSnippet, error)\n\tDeleteSnippet(context.Context, *fastly.DeleteSnippetInput) error\n\n\tPurge(context.Context, *fastly.PurgeInput) (*fastly.Purge, error)\n\tPurgeKey(context.Context, *fastly.PurgeKeyInput) (*fastly.Purge, error)\n\tPurgeKeys(context.Context, *fastly.PurgeKeysInput) (map[string]string, error)\n\tPurgeAll(context.Context, *fastly.PurgeAllInput) (*fastly.Purge, error)\n\n\tCreateACL(context.Context, *fastly.CreateACLInput) (*fastly.ACL, error)\n\tDeleteACL(context.Context, *fastly.DeleteACLInput) error\n\tGetACL(context.Context, *fastly.GetACLInput) (*fastly.ACL, error)\n\tListACLs(context.Context, *fastly.ListACLsInput) ([]*fastly.ACL, error)\n\tUpdateACL(context.Context, *fastly.UpdateACLInput) (*fastly.ACL, error)\n\n\tCreateACLEntry(context.Context, *fastly.CreateACLEntryInput) (*fastly.ACLEntry, error)\n\tDeleteACLEntry(context.Context, *fastly.DeleteACLEntryInput) error\n\tGetACLEntry(context.Context, *fastly.GetACLEntryInput) (*fastly.ACLEntry, error)\n\tGetACLEntries(context.Context, *fastly.GetACLEntriesInput) *fastly.ListPaginator[fastly.ACLEntry]\n\tListACLEntries(context.Context, *fastly.ListACLEntriesInput) ([]*fastly.ACLEntry, error)\n\tUpdateACLEntry(context.Context, *fastly.UpdateACLEntryInput) (*fastly.ACLEntry, error)\n\tBatchModifyACLEntries(context.Context, *fastly.BatchModifyACLEntriesInput) error\n\n\tCreateNewRelic(context.Context, *fastly.CreateNewRelicInput) (*fastly.NewRelic, error)\n\tDeleteNewRelic(context.Context, *fastly.DeleteNewRelicInput) error\n\tGetNewRelic(context.Context, *fastly.GetNewRelicInput) (*fastly.NewRelic, error)\n\tListNewRelic(context.Context, *fastly.ListNewRelicInput) ([]*fastly.NewRelic, error)\n\tUpdateNewRelic(context.Context, *fastly.UpdateNewRelicInput) (*fastly.NewRelic, error)\n\n\tCreateNewRelicOTLP(context.Context, *fastly.CreateNewRelicOTLPInput) (*fastly.NewRelicOTLP, error)\n\tDeleteNewRelicOTLP(context.Context, *fastly.DeleteNewRelicOTLPInput) error\n\tGetNewRelicOTLP(context.Context, *fastly.GetNewRelicOTLPInput) (*fastly.NewRelicOTLP, error)\n\tListNewRelicOTLP(context.Context, *fastly.ListNewRelicOTLPInput) ([]*fastly.NewRelicOTLP, error)\n\tUpdateNewRelicOTLP(context.Context, *fastly.UpdateNewRelicOTLPInput) (*fastly.NewRelicOTLP, error)\n\n\tCreateUser(context.Context, *fastly.CreateUserInput) (*fastly.User, error)\n\tDeleteUser(context.Context, *fastly.DeleteUserInput) error\n\tGetCurrentUser(context.Context) (*fastly.User, error)\n\tGetUser(context.Context, *fastly.GetUserInput) (*fastly.User, error)\n\tListCustomerUsers(context.Context, *fastly.ListCustomerUsersInput) ([]*fastly.User, error)\n\tUpdateUser(context.Context, *fastly.UpdateUserInput) (*fastly.User, error)\n\tResetUserPassword(context.Context, *fastly.ResetUserPasswordInput) error\n\n\tBatchDeleteTokens(context.Context, *fastly.BatchDeleteTokensInput) error\n\tCreateToken(context.Context, *fastly.CreateTokenInput) (*fastly.Token, error)\n\tDeleteToken(context.Context, *fastly.DeleteTokenInput) error\n\tDeleteTokenSelf(context.Context) error\n\tGetTokenSelf(context.Context) (*fastly.Token, error)\n\tListCustomerTokens(context.Context, *fastly.ListCustomerTokensInput) ([]*fastly.Token, error)\n\tListTokens(context.Context, *fastly.ListTokensInput) ([]*fastly.Token, error)\n\n\tNewListKVStoreKeysPaginator(context.Context, *fastly.ListKVStoreKeysInput) fastly.PaginatorKVStoreEntries\n\n\tGetCustomTLSConfiguration(context.Context, *fastly.GetCustomTLSConfigurationInput) (*fastly.CustomTLSConfiguration, error)\n\tListCustomTLSConfigurations(context.Context, *fastly.ListCustomTLSConfigurationsInput) ([]*fastly.CustomTLSConfiguration, error)\n\tUpdateCustomTLSConfiguration(context.Context, *fastly.UpdateCustomTLSConfigurationInput) (*fastly.CustomTLSConfiguration, error)\n\tGetTLSActivation(context.Context, *fastly.GetTLSActivationInput) (*fastly.TLSActivation, error)\n\tListTLSActivations(context.Context, *fastly.ListTLSActivationsInput) ([]*fastly.TLSActivation, error)\n\tUpdateTLSActivation(context.Context, *fastly.UpdateTLSActivationInput) (*fastly.TLSActivation, error)\n\tCreateTLSActivation(context.Context, *fastly.CreateTLSActivationInput) (*fastly.TLSActivation, error)\n\tDeleteTLSActivation(context.Context, *fastly.DeleteTLSActivationInput) error\n\n\tCreateCustomTLSCertificate(context.Context, *fastly.CreateCustomTLSCertificateInput) (*fastly.CustomTLSCertificate, error)\n\tDeleteCustomTLSCertificate(context.Context, *fastly.DeleteCustomTLSCertificateInput) error\n\tGetCustomTLSCertificate(context.Context, *fastly.GetCustomTLSCertificateInput) (*fastly.CustomTLSCertificate, error)\n\tListCustomTLSCertificates(context.Context, *fastly.ListCustomTLSCertificatesInput) ([]*fastly.CustomTLSCertificate, error)\n\tUpdateCustomTLSCertificate(context.Context, *fastly.UpdateCustomTLSCertificateInput) (*fastly.CustomTLSCertificate, error)\n\n\tListTLSDomains(context.Context, *fastly.ListTLSDomainsInput) ([]*fastly.TLSDomain, error)\n\n\tCreatePrivateKey(context.Context, *fastly.CreatePrivateKeyInput) (*fastly.PrivateKey, error)\n\tDeletePrivateKey(context.Context, *fastly.DeletePrivateKeyInput) error\n\tGetPrivateKey(context.Context, *fastly.GetPrivateKeyInput) (*fastly.PrivateKey, error)\n\tListPrivateKeys(context.Context, *fastly.ListPrivateKeysInput) ([]*fastly.PrivateKey, error)\n\n\tCreateBulkCertificate(context.Context, *fastly.CreateBulkCertificateInput) (*fastly.BulkCertificate, error)\n\tDeleteBulkCertificate(context.Context, *fastly.DeleteBulkCertificateInput) error\n\tGetBulkCertificate(context.Context, *fastly.GetBulkCertificateInput) (*fastly.BulkCertificate, error)\n\tListBulkCertificates(context.Context, *fastly.ListBulkCertificatesInput) ([]*fastly.BulkCertificate, error)\n\tUpdateBulkCertificate(context.Context, *fastly.UpdateBulkCertificateInput) (*fastly.BulkCertificate, error)\n\n\tCreateTLSSubscription(context.Context, *fastly.CreateTLSSubscriptionInput) (*fastly.TLSSubscription, error)\n\tDeleteTLSSubscription(context.Context, *fastly.DeleteTLSSubscriptionInput) error\n\tGetTLSSubscription(context.Context, *fastly.GetTLSSubscriptionInput) (*fastly.TLSSubscription, error)\n\tListTLSSubscriptions(context.Context, *fastly.ListTLSSubscriptionsInput) ([]*fastly.TLSSubscription, error)\n\tUpdateTLSSubscription(context.Context, *fastly.UpdateTLSSubscriptionInput) (*fastly.TLSSubscription, error)\n\n\tListServiceAuthorizations(context.Context, *fastly.ListServiceAuthorizationsInput) (*fastly.ServiceAuthorizations, error)\n\tGetServiceAuthorization(context.Context, *fastly.GetServiceAuthorizationInput) (*fastly.ServiceAuthorization, error)\n\tCreateServiceAuthorization(context.Context, *fastly.CreateServiceAuthorizationInput) (*fastly.ServiceAuthorization, error)\n\tUpdateServiceAuthorization(context.Context, *fastly.UpdateServiceAuthorizationInput) (*fastly.ServiceAuthorization, error)\n\tDeleteServiceAuthorization(context.Context, *fastly.DeleteServiceAuthorizationInput) error\n\n\tCreateConfigStore(context.Context, *fastly.CreateConfigStoreInput) (*fastly.ConfigStore, error)\n\tDeleteConfigStore(context.Context, *fastly.DeleteConfigStoreInput) error\n\tGetConfigStore(context.Context, *fastly.GetConfigStoreInput) (*fastly.ConfigStore, error)\n\tGetConfigStoreMetadata(context.Context, *fastly.GetConfigStoreMetadataInput) (*fastly.ConfigStoreMetadata, error)\n\tListConfigStores(context.Context, *fastly.ListConfigStoresInput) ([]*fastly.ConfigStore, error)\n\tListConfigStoreServices(context.Context, *fastly.ListConfigStoreServicesInput) ([]*fastly.Service, error)\n\tUpdateConfigStore(context.Context, *fastly.UpdateConfigStoreInput) (*fastly.ConfigStore, error)\n\n\tCreateConfigStoreItem(context.Context, *fastly.CreateConfigStoreItemInput) (*fastly.ConfigStoreItem, error)\n\tDeleteConfigStoreItem(context.Context, *fastly.DeleteConfigStoreItemInput) error\n\tGetConfigStoreItem(context.Context, *fastly.GetConfigStoreItemInput) (*fastly.ConfigStoreItem, error)\n\tListConfigStoreItems(context.Context, *fastly.ListConfigStoreItemsInput) ([]*fastly.ConfigStoreItem, error)\n\tUpdateConfigStoreItem(context.Context, *fastly.UpdateConfigStoreItemInput) (*fastly.ConfigStoreItem, error)\n\n\tCreateKVStore(context.Context, *fastly.CreateKVStoreInput) (*fastly.KVStore, error)\n\tListKVStores(context.Context, *fastly.ListKVStoresInput) (*fastly.ListKVStoresResponse, error)\n\tDeleteKVStore(context.Context, *fastly.DeleteKVStoreInput) error\n\tGetKVStore(context.Context, *fastly.GetKVStoreInput) (*fastly.KVStore, error)\n\tListKVStoreKeys(context.Context, *fastly.ListKVStoreKeysInput) (*fastly.ListKVStoreKeysResponse, error)\n\tGetKVStoreKey(context.Context, *fastly.GetKVStoreKeyInput) (string, error)\n\tGetKVStoreItem(context.Context, *fastly.GetKVStoreItemInput) (fastly.GetKVStoreItemOutput, error)\n\tDeleteKVStoreKey(context.Context, *fastly.DeleteKVStoreKeyInput) error\n\tInsertKVStoreKey(context.Context, *fastly.InsertKVStoreKeyInput) error\n\tBatchModifyKVStoreKey(context.Context, *fastly.BatchModifyKVStoreKeyInput) error\n\n\tCreateSecretStore(context.Context, *fastly.CreateSecretStoreInput) (*fastly.SecretStore, error)\n\tGetSecretStore(context.Context, *fastly.GetSecretStoreInput) (*fastly.SecretStore, error)\n\tDeleteSecretStore(context.Context, *fastly.DeleteSecretStoreInput) error\n\tListSecretStores(context.Context, *fastly.ListSecretStoresInput) (*fastly.SecretStores, error)\n\tCreateSecret(context.Context, *fastly.CreateSecretInput) (*fastly.Secret, error)\n\tGetSecret(context.Context, *fastly.GetSecretInput) (*fastly.Secret, error)\n\tDeleteSecret(context.Context, *fastly.DeleteSecretInput) error\n\tListSecrets(context.Context, *fastly.ListSecretsInput) (*fastly.Secrets, error)\n\tCreateClientKey(context.Context) (*fastly.ClientKey, error)\n\tGetSigningKey(context.Context) (ed25519.PublicKey, error)\n\n\tCreateResource(context.Context, *fastly.CreateResourceInput) (*fastly.Resource, error)\n\tDeleteResource(context.Context, *fastly.DeleteResourceInput) error\n\tGetResource(context.Context, *fastly.GetResourceInput) (*fastly.Resource, error)\n\tListResources(context.Context, *fastly.ListResourcesInput) ([]*fastly.Resource, error)\n\tUpdateResource(context.Context, *fastly.UpdateResourceInput) (*fastly.Resource, error)\n\n\tCreateERL(context.Context, *fastly.CreateERLInput) (*fastly.ERL, error)\n\tDeleteERL(context.Context, *fastly.DeleteERLInput) error\n\tGetERL(context.Context, *fastly.GetERLInput) (*fastly.ERL, error)\n\tListERLs(context.Context, *fastly.ListERLsInput) ([]*fastly.ERL, error)\n\tUpdateERL(context.Context, *fastly.UpdateERLInput) (*fastly.ERL, error)\n\n\tCreateCondition(context.Context, *fastly.CreateConditionInput) (*fastly.Condition, error)\n\tDeleteCondition(context.Context, *fastly.DeleteConditionInput) error\n\tGetCondition(context.Context, *fastly.GetConditionInput) (*fastly.Condition, error)\n\tListConditions(context.Context, *fastly.ListConditionsInput) ([]*fastly.Condition, error)\n\tUpdateCondition(context.Context, *fastly.UpdateConditionInput) (*fastly.Condition, error)\n\n\tListAlertDefinitions(context.Context, *fastly.ListAlertDefinitionsInput) (*fastly.AlertDefinitionsResponse, error)\n\tCreateAlertDefinition(context.Context, *fastly.CreateAlertDefinitionInput) (*fastly.AlertDefinition, error)\n\tGetAlertDefinition(context.Context, *fastly.GetAlertDefinitionInput) (*fastly.AlertDefinition, error)\n\tUpdateAlertDefinition(context.Context, *fastly.UpdateAlertDefinitionInput) (*fastly.AlertDefinition, error)\n\tDeleteAlertDefinition(context.Context, *fastly.DeleteAlertDefinitionInput) error\n\tTestAlertDefinition(context.Context, *fastly.TestAlertDefinitionInput) error\n\tListAlertHistory(context.Context, *fastly.ListAlertHistoryInput) (*fastly.AlertHistoryResponse, error)\n\n\tListObservabilityCustomDashboards(context.Context, *fastly.ListObservabilityCustomDashboardsInput) (*fastly.ListDashboardsResponse, error)\n\tCreateObservabilityCustomDashboard(context.Context, *fastly.CreateObservabilityCustomDashboardInput) (*fastly.ObservabilityCustomDashboard, error)\n\tGetObservabilityCustomDashboard(context.Context, *fastly.GetObservabilityCustomDashboardInput) (*fastly.ObservabilityCustomDashboard, error)\n\tUpdateObservabilityCustomDashboard(context.Context, *fastly.UpdateObservabilityCustomDashboardInput) (*fastly.ObservabilityCustomDashboard, error)\n\tDeleteObservabilityCustomDashboard(context.Context, *fastly.DeleteObservabilityCustomDashboardInput) error\n}\n\n// RealtimeStatsInterface is the subset of go-fastly's realtime stats API used here.\ntype RealtimeStatsInterface interface {\n\tGetRealtimeStatsJSON(context.Context, *fastly.GetRealtimeStatsInput, any) error\n}\n\n// Ensure that fastly.Client satisfies Interface.\nvar _ Interface = (*fastly.Client)(nil)\n\n// Ensure that fastly.RTSClient satisfies RealtimeStatsInterface.\nvar _ RealtimeStatsInterface = (*fastly.RTSClient)(nil)\n"
  },
  {
    "path": "pkg/api/undocumented/undocumented.go",
    "content": "// Package undocumented provides abstractions for talking to undocumented Fastly\n// API endpoints.\npackage undocumented\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/fastly/cli/pkg/api\"\n\t\"github.com/fastly/cli/pkg/debug\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/useragent\"\n)\n\n// RequestTimeout is the timeout for the API network request.\nconst RequestTimeout = 5 * time.Second\n\n// APIError models a custom error for undocumented API calls.\ntype APIError struct {\n\tErr        error\n\tStatusCode int\n}\n\n// Error implements the error interface.\nfunc (e APIError) Error() string {\n\treturn e.Err.Error()\n}\n\nfunc (e APIError) Unwrap() error {\n\treturn e.Err\n}\n\nfunc (e APIError) HTTPStatusCode() int {\n\treturn e.StatusCode\n}\n\n// NewError returns an APIError.\nfunc NewError(err error, statusCode int) APIError {\n\treturn APIError{\n\t\tErr:        err,\n\t\tStatusCode: statusCode,\n\t}\n}\n\n// HTTPHeader represents a HTTP request header.\ntype HTTPHeader struct {\n\tKey   string\n\tValue string\n}\n\n// CallOptions is used as input to Call().\ntype CallOptions struct {\n\tAPIEndpoint string\n\tBody        io.Reader\n\tDebug       bool\n\tHTTPClient  api.HTTPClient\n\tHTTPHeaders []HTTPHeader\n\tMethod      string\n\tPath        string\n\tToken       string\n}\n\n// Call calls the given API endpoint and returns its response data.\n//\n// WARNING: Loads entire response body into memory.\nfunc Call(opts CallOptions) (data []byte, err error) {\n\thost := strings.TrimSuffix(opts.APIEndpoint, \"/\")\n\tendpoint := fmt.Sprintf(\"%s%s\", host, opts.Path)\n\n\treq, err := http.NewRequest(opts.Method, endpoint, opts.Body)\n\tif err != nil {\n\t\treturn data, NewError(err, 0)\n\t}\n\n\tif opts.Token != \"\" {\n\t\treq.Header.Set(\"Fastly-Key\", opts.Token)\n\t}\n\treq.Header.Set(\"User-Agent\", useragent.Name)\n\tfor _, header := range opts.HTTPHeaders {\n\t\treq.Header.Set(header.Key, header.Value)\n\t}\n\n\tif opts.Debug {\n\t\tdebug.DumpHTTPRequest(req)\n\t}\n\tres, err := opts.HTTPClient.Do(req)\n\tif opts.Debug {\n\t\tdebug.DumpHTTPResponse(res)\n\t}\n\n\tif err != nil {\n\t\tif urlErr, ok := err.(*url.Error); ok && urlErr.Timeout() {\n\t\t\treturn data, fsterr.RemediationError{\n\t\t\t\tInner:       err,\n\t\t\t\tRemediation: fsterr.NetworkRemediation,\n\t\t\t}\n\t\t}\n\t\treturn data, NewError(err, 0)\n\t}\n\tdefer res.Body.Close() // #nosec G307\n\n\tdata, err = io.ReadAll(res.Body)\n\tif err != nil {\n\t\treturn []byte{}, NewError(err, res.StatusCode)\n\t}\n\n\tif res.StatusCode >= http.StatusBadRequest {\n\t\treturn data, NewError(fmt.Errorf(\"error response: %q\", data), res.StatusCode)\n\t}\n\n\treturn data, nil\n}\n"
  },
  {
    "path": "pkg/app/disable_token_flag_test.go",
    "content": "package app_test\n\nimport (\n\t\"bytes\"\n\tstderrors \"errors\"\n\t\"io\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/fastly/cli/pkg/app\"\n\t\"github.com/fastly/cli/pkg/env\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n)\n\nfunc TestTokenFlagDisabledWhenAuthCommandDisabled(t *testing.T) {\n\tt.Setenv(env.DisableAuthCommand, \"1\")\n\n\tt.Run(\"--token flag rejected\", func(t *testing.T) {\n\t\tvar stdout bytes.Buffer\n\t\targs := testutil.SplitArgs(\"--token abc version\")\n\n\t\tapp.Init = func(_ []string, _ io.Reader) (*global.Data, error) {\n\t\t\treturn testutil.MockGlobalData(args, &stdout), nil\n\t\t}\n\n\t\terr := app.Run(args, nil)\n\t\tif err == nil {\n\t\t\tt.Fatal(\"expected error when using --token with FASTLY_DISABLE_AUTH_COMMAND set\")\n\t\t}\n\t\terrStr := err.Error()\n\t\tif !strings.Contains(errStr, \"unknown long flag\") {\n\t\t\tt.Errorf(\"expected unknown flag error, got: %s\", errStr)\n\t\t}\n\t})\n\n\tt.Run(\"-t flag rejected\", func(t *testing.T) {\n\t\tvar stdout bytes.Buffer\n\t\targs := testutil.SplitArgs(\"-t abc version\")\n\n\t\tapp.Init = func(_ []string, _ io.Reader) (*global.Data, error) {\n\t\t\treturn testutil.MockGlobalData(args, &stdout), nil\n\t\t}\n\n\t\terr := app.Run(args, nil)\n\t\tif err == nil {\n\t\t\tt.Fatal(\"expected error when using -t with FASTLY_DISABLE_AUTH_COMMAND set\")\n\t\t}\n\t\terrStr := err.Error()\n\t\tif !strings.Contains(errStr, \"unknown short flag\") {\n\t\t\tt.Errorf(\"expected unknown flag error, got: %s\", errStr)\n\t\t}\n\t})\n\n\tt.Run(\"help output omits --token\", func(t *testing.T) {\n\t\tvar stdout bytes.Buffer\n\t\targs := testutil.SplitArgs(\"--help\")\n\n\t\tapp.Init = func(_ []string, _ io.Reader) (*global.Data, error) {\n\t\t\treturn testutil.MockGlobalData(args, &stdout), nil\n\t\t}\n\n\t\terr := app.Run(args, nil)\n\n\t\tvar output string\n\t\tif err != nil {\n\t\t\tvar re errors.RemediationError\n\t\t\tif stderrors.As(err, &re) {\n\t\t\t\toutput = re.Prefix\n\t\t\t}\n\t\t}\n\t\toutput += stdout.String()\n\n\t\tif strings.Contains(output, \"--token\") {\n\t\t\tt.Errorf(\"expected --token to be absent from help output when FASTLY_DISABLE_AUTH_COMMAND is set, got:\\n%s\", output)\n\t\t}\n\t})\n}\n\nfunc TestTokenFlagAvailableByDefault(t *testing.T) {\n\tt.Setenv(env.DisableAuthCommand, \"\")\n\tt.Run(\"help output includes --token\", func(t *testing.T) {\n\t\tvar stdout bytes.Buffer\n\t\targs := testutil.SplitArgs(\"--help\")\n\n\t\tapp.Init = func(_ []string, _ io.Reader) (*global.Data, error) {\n\t\t\treturn testutil.MockGlobalData(args, &stdout), nil\n\t\t}\n\n\t\terr := app.Run(args, nil)\n\n\t\tvar output string\n\t\tif err != nil {\n\t\t\tvar re errors.RemediationError\n\t\t\tif stderrors.As(err, &re) {\n\t\t\t\toutput = re.Prefix\n\t\t\t}\n\t\t}\n\t\toutput += stdout.String()\n\n\t\tif !strings.Contains(output, \"--token\") {\n\t\t\tt.Errorf(\"expected --token in help output, got:\\n%s\", output)\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "pkg/app/doc.go",
    "content": "// Package app provides helpers for creating and running the CLI application.\n// Care has been taken to make the CLI testable through the use of dependency\n// injection and interfaces.\npackage app\n"
  },
  {
    "path": "pkg/app/expiry_warning_test.go",
    "content": "package app\n\nimport (\n\t\"bytes\"\n\t\"os\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/fastly/cli/pkg/config\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/manifest\"\n)\n\n// expiringTokenData returns a global.Data configured with a stored token that\n// expires soon. Callers can override Flags and commandName to test suppression.\nfunc expiringTokenData(out *bytes.Buffer) *global.Data {\n\tsoon := time.Now().Add(20 * time.Minute).Format(time.RFC3339)\n\treturn &global.Data{\n\t\tOutput:    out,\n\t\tErrOutput: out,\n\t\tErrLog:    fsterr.Log,\n\t\tManifest:  &manifest.Data{},\n\t\tConfig: config.File{\n\t\t\tAuth: config.Auth{\n\t\t\t\tDefault: \"mytoken\",\n\t\t\t\tTokens: config.AuthTokens{\n\t\t\t\t\t\"mytoken\": &config.AuthToken{\n\t\t\t\t\t\tType:              config.AuthTokenTypeStatic,\n\t\t\t\t\t\tToken:             \"tok_abc123\",\n\t\t\t\t\t\tAPITokenExpiresAt: soon,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n}\n\nfunc TestCheckTokenExpirationWarning(t *testing.T) {\n\tsoon := time.Now().Add(20 * time.Minute).Format(time.RFC3339)\n\tfarFuture := time.Now().Add(60 * 24 * time.Hour).Format(time.RFC3339)\n\n\ttests := []struct {\n\t\tname        string\n\t\tcommandName string\n\t\tdata        func(out *bytes.Buffer) *global.Data\n\t\twantWarn    bool\n\t\twantSubstr  string\n\t}{\n\t\t{\n\t\t\tname:        \"SourceAuth expiring soon shows warning\",\n\t\t\tcommandName: \"service list\",\n\t\t\tdata: func(out *bytes.Buffer) *global.Data {\n\t\t\t\treturn &global.Data{\n\t\t\t\t\tOutput:    out,\n\t\t\t\t\tErrOutput: out,\n\t\t\t\t\tErrLog:    fsterr.Log,\n\t\t\t\t\tConfig: config.File{\n\t\t\t\t\t\tAuth: config.Auth{\n\t\t\t\t\t\t\tDefault: \"mytoken\",\n\t\t\t\t\t\t\tTokens: config.AuthTokens{\n\t\t\t\t\t\t\t\t\"mytoken\": &config.AuthToken{\n\t\t\t\t\t\t\t\t\tType:              config.AuthTokenTypeStatic,\n\t\t\t\t\t\t\t\t\tToken:             \"tok_abc123\",\n\t\t\t\t\t\t\t\t\tAPITokenExpiresAt: soon,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t},\n\t\t\twantWarn:   true,\n\t\t\twantSubstr: \"expires in\",\n\t\t},\n\t\t{\n\t\t\tname:        \"SourceAuth not expiring soon no warning\",\n\t\t\tcommandName: \"service list\",\n\t\t\tdata: func(out *bytes.Buffer) *global.Data {\n\t\t\t\treturn &global.Data{\n\t\t\t\t\tOutput:    out,\n\t\t\t\t\tErrOutput: out,\n\t\t\t\t\tErrLog:    fsterr.Log,\n\t\t\t\t\tConfig: config.File{\n\t\t\t\t\t\tAuth: config.Auth{\n\t\t\t\t\t\t\tDefault: \"mytoken\",\n\t\t\t\t\t\t\tTokens: config.AuthTokens{\n\t\t\t\t\t\t\t\t\"mytoken\": &config.AuthToken{\n\t\t\t\t\t\t\t\t\tType:              config.AuthTokenTypeStatic,\n\t\t\t\t\t\t\t\t\tToken:             \"tok_abc123\",\n\t\t\t\t\t\t\t\t\tAPITokenExpiresAt: farFuture,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t},\n\t\t\twantWarn: false,\n\t\t},\n\t\t{\n\t\t\tname:        \"SourceEnvironment JWT-like token no warning\",\n\t\t\tcommandName: \"service list\",\n\t\t\tdata: func(out *bytes.Buffer) *global.Data {\n\t\t\t\treturn &global.Data{\n\t\t\t\t\tOutput:    out,\n\t\t\t\t\tErrOutput: out,\n\t\t\t\t\tErrLog:    fsterr.Log,\n\t\t\t\t\tEnv:       config.Environment{APIToken: \"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.fake\"},\n\t\t\t\t\tConfig: config.File{\n\t\t\t\t\t\tAuth: config.Auth{\n\t\t\t\t\t\t\tDefault: \"mytoken\",\n\t\t\t\t\t\t\tTokens: config.AuthTokens{\n\t\t\t\t\t\t\t\t\"mytoken\": &config.AuthToken{\n\t\t\t\t\t\t\t\t\tType:              config.AuthTokenTypeStatic,\n\t\t\t\t\t\t\t\t\tToken:             \"tok_abc123\",\n\t\t\t\t\t\t\t\t\tAPITokenExpiresAt: soon,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t},\n\t\t\twantWarn: false,\n\t\t},\n\t\t{\n\t\t\tname:        \"SourceFlag raw token no warning\",\n\t\t\tcommandName: \"service list\",\n\t\t\tdata: func(out *bytes.Buffer) *global.Data {\n\t\t\t\treturn &global.Data{\n\t\t\t\t\tOutput:    out,\n\t\t\t\t\tErrOutput: out,\n\t\t\t\t\tErrLog:    fsterr.Log,\n\t\t\t\t\tFlags:     global.Flags{Token: \"some-raw-token\"},\n\t\t\t\t\tConfig: config.File{\n\t\t\t\t\t\tAuth: config.Auth{\n\t\t\t\t\t\t\tDefault: \"mytoken\",\n\t\t\t\t\t\t\tTokens: config.AuthTokens{\n\t\t\t\t\t\t\t\t\"mytoken\": &config.AuthToken{\n\t\t\t\t\t\t\t\t\tType:              config.AuthTokenTypeStatic,\n\t\t\t\t\t\t\t\t\tToken:             \"tok_abc123\",\n\t\t\t\t\t\t\t\t\tAPITokenExpiresAt: soon,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t},\n\t\t\twantWarn: false,\n\t\t},\n\t\t{\n\t\t\tname:        \"stale default name nil token no panic\",\n\t\t\tcommandName: \"service list\",\n\t\t\tdata: func(out *bytes.Buffer) *global.Data {\n\t\t\t\treturn &global.Data{\n\t\t\t\t\tOutput:    out,\n\t\t\t\t\tErrOutput: out,\n\t\t\t\t\tErrLog:    fsterr.Log,\n\t\t\t\t\tConfig: config.File{\n\t\t\t\t\t\tAuth: config.Auth{\n\t\t\t\t\t\t\tDefault: \"deleted-token\",\n\t\t\t\t\t\t\tTokens:  config.AuthTokens{},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t},\n\t\t\twantWarn: false,\n\t\t},\n\t\t{\n\t\t\tname:        \"malformed expiry logs error no visible warning\",\n\t\t\tcommandName: \"service list\",\n\t\t\tdata: func(out *bytes.Buffer) *global.Data {\n\t\t\t\treturn &global.Data{\n\t\t\t\t\tOutput:    out,\n\t\t\t\t\tErrOutput: out,\n\t\t\t\t\tErrLog:    fsterr.Log,\n\t\t\t\t\tConfig: config.File{\n\t\t\t\t\t\tAuth: config.Auth{\n\t\t\t\t\t\t\tDefault: \"mytoken\",\n\t\t\t\t\t\t\tTokens: config.AuthTokens{\n\t\t\t\t\t\t\t\t\"mytoken\": &config.AuthToken{\n\t\t\t\t\t\t\t\t\tType:              config.AuthTokenTypeStatic,\n\t\t\t\t\t\t\t\t\tToken:             \"tok_abc123\",\n\t\t\t\t\t\t\t\t\tAPITokenExpiresAt: \"not-a-date\",\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t},\n\t\t\twantWarn: false,\n\t\t},\n\t\t{\n\t\t\tname:        \"nil ErrLog does not panic\",\n\t\t\tcommandName: \"service list\",\n\t\t\tdata: func(out *bytes.Buffer) *global.Data {\n\t\t\t\treturn &global.Data{\n\t\t\t\t\tOutput:    out,\n\t\t\t\t\tErrOutput: out,\n\t\t\t\t\tConfig: config.File{\n\t\t\t\t\t\tAuth: config.Auth{\n\t\t\t\t\t\t\tDefault: \"mytoken\",\n\t\t\t\t\t\t\tTokens: config.AuthTokens{\n\t\t\t\t\t\t\t\t\"mytoken\": &config.AuthToken{\n\t\t\t\t\t\t\t\t\tType:              config.AuthTokenTypeStatic,\n\t\t\t\t\t\t\t\t\tToken:             \"tok_abc123\",\n\t\t\t\t\t\t\t\t\tAPITokenExpiresAt: \"not-a-date\",\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t},\n\t\t\twantWarn: false,\n\t\t},\n\t\t{\n\t\t\tname:        \"SSO token expiring soon shows remediation\",\n\t\t\tcommandName: \"service list\",\n\t\t\tdata: func(out *bytes.Buffer) *global.Data {\n\t\t\t\treturn &global.Data{\n\t\t\t\t\tOutput:    out,\n\t\t\t\t\tErrOutput: out,\n\t\t\t\t\tErrLog:    fsterr.Log,\n\t\t\t\t\tConfig: config.File{\n\t\t\t\t\t\tAuth: config.Auth{\n\t\t\t\t\t\t\tDefault: \"sso-tok\",\n\t\t\t\t\t\t\tTokens: config.AuthTokens{\n\t\t\t\t\t\t\t\t\"sso-tok\": &config.AuthToken{\n\t\t\t\t\t\t\t\t\tType:             config.AuthTokenTypeSSO,\n\t\t\t\t\t\t\t\t\tToken:            \"tok_sso\",\n\t\t\t\t\t\t\t\t\tRefreshExpiresAt: soon,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t},\n\t\t\twantWarn:   true,\n\t\t\twantSubstr: \"fastly auth login --sso\",\n\t\t},\n\t}\n\n\t// Ensure FASTLY_DISABLE_AUTH_COMMAND is not set.\n\toriginalEnv := os.Getenv(\"FASTLY_DISABLE_AUTH_COMMAND\")\n\tos.Setenv(\"FASTLY_DISABLE_AUTH_COMMAND\", \"\")\n\tdefer os.Setenv(\"FASTLY_DISABLE_AUTH_COMMAND\", originalEnv)\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tvar buf bytes.Buffer\n\t\t\tdata := tt.data(&buf)\n\n\t\t\t// Ensure Manifest is initialized to avoid nil panics in Token().\n\t\t\tif data.Manifest == nil {\n\t\t\t\tdata.Manifest = &manifest.Data{}\n\t\t\t}\n\n\t\t\tcheckTokenExpirationWarning(data, tt.commandName)\n\n\t\t\toutput := buf.String()\n\t\t\tif tt.wantWarn && output == \"\" {\n\t\t\t\tt.Error(\"expected warning output but got none\")\n\t\t\t}\n\t\t\tif !tt.wantWarn && output != \"\" {\n\t\t\t\tt.Errorf(\"expected no warning but got: %s\", output)\n\t\t\t}\n\t\t\tif tt.wantSubstr != \"\" && !strings.Contains(output, tt.wantSubstr) {\n\t\t\t\tt.Errorf(\"expected output to contain %q, got: %s\", tt.wantSubstr, output)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestCheckTokenExpirationWarningDisabledAuth(t *testing.T) {\n\toriginalEnv := os.Getenv(\"FASTLY_DISABLE_AUTH_COMMAND\")\n\tos.Setenv(\"FASTLY_DISABLE_AUTH_COMMAND\", \"1\")\n\tdefer os.Setenv(\"FASTLY_DISABLE_AUTH_COMMAND\", originalEnv)\n\n\tvar buf bytes.Buffer\n\tdata := expiringTokenData(&buf)\n\n\tcheckTokenExpirationWarning(data, \"service list\")\n\n\toutput := buf.String()\n\tif !strings.Contains(output, \"FASTLY_API_TOKEN\") {\n\t\tt.Errorf(\"expected FASTLY_API_TOKEN remediation in disabled-auth mode, got: %s\", output)\n\t}\n\tif strings.Contains(output, \"fastly auth\") {\n\t\tt.Errorf(\"should not mention fastly auth in disabled mode, got: %s\", output)\n\t}\n}\n\n// TestCheckTokenExpirationWarningSuppression tests that the warning is\n// suppressed for all auth-related commands, --quiet, and --json (which sets\n// Quiet=true). The auth-related set matches FASTLY_DISABLE_AUTH_COMMAND\n// (pkg/env/env.go): auth, auth-token, sso, profile, whoami.\nfunc TestCheckTokenExpirationWarningSuppression(t *testing.T) {\n\ttests := []struct {\n\t\tname        string\n\t\tcommandName string\n\t\tflags       global.Flags\n\t}{\n\t\t// auth family.\n\t\t{\n\t\t\tname:        \"suppressed for auth list\",\n\t\t\tcommandName: \"auth list\",\n\t\t},\n\t\t{\n\t\t\tname:        \"suppressed for auth show\",\n\t\t\tcommandName: \"auth show\",\n\t\t},\n\t\t{\n\t\t\tname:        \"suppressed for auth login\",\n\t\t\tcommandName: \"auth login\",\n\t\t},\n\t\t{\n\t\t\tname:        \"suppressed for bare auth\",\n\t\t\tcommandName: \"auth\",\n\t\t},\n\t\t// Other auth-related families.\n\t\t{\n\t\t\tname:        \"suppressed for sso\",\n\t\t\tcommandName: \"sso\",\n\t\t},\n\t\t{\n\t\t\tname:        \"suppressed for auth-token create\",\n\t\t\tcommandName: \"auth-token create\",\n\t\t},\n\t\t{\n\t\t\tname:        \"suppressed for bare auth-token\",\n\t\t\tcommandName: \"auth-token\",\n\t\t},\n\t\t{\n\t\t\tname:        \"suppressed for profile switch\",\n\t\t\tcommandName: \"profile switch\",\n\t\t},\n\t\t{\n\t\t\tname:        \"suppressed for bare profile\",\n\t\t\tcommandName: \"profile\",\n\t\t},\n\t\t{\n\t\t\tname:        \"suppressed for whoami\",\n\t\t\tcommandName: \"whoami\",\n\t\t},\n\t\t// Flag-based suppression.\n\t\t{\n\t\t\tname:        \"suppressed with --quiet flag\",\n\t\t\tcommandName: \"service list\",\n\t\t\tflags:       global.Flags{Quiet: true},\n\t\t},\n\t}\n\n\toriginalEnv := os.Getenv(\"FASTLY_DISABLE_AUTH_COMMAND\")\n\tos.Setenv(\"FASTLY_DISABLE_AUTH_COMMAND\", \"\")\n\tdefer os.Setenv(\"FASTLY_DISABLE_AUTH_COMMAND\", originalEnv)\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tvar buf bytes.Buffer\n\t\t\tdata := expiringTokenData(&buf)\n\t\t\tdata.Flags = tt.flags\n\n\t\t\tcheckTokenExpirationWarning(data, tt.commandName)\n\n\t\t\toutput := buf.String()\n\t\t\tif output != \"\" {\n\t\t\t\tt.Errorf(\"expected no output for %q with flags %+v, got: %s\", tt.commandName, tt.flags, output)\n\t\t\t}\n\t\t})\n\t}\n}\n\n// TestCheckTokenExpirationWarningShownForJSON verifies that --json mode still\n// emits the warning (to stderr) rather than suppressing it entirely.\nfunc TestCheckTokenExpirationWarningShownForJSON(t *testing.T) {\n\tvar buf bytes.Buffer\n\tdata := expiringTokenData(&buf)\n\tdata.Flags = global.Flags{JSON: true}\n\n\tcheckTokenExpirationWarning(data, \"service list\")\n\n\toutput := buf.String()\n\tif !strings.Contains(output, \"expires in\") {\n\t\tt.Errorf(\"expected expiry warning in --json mode (written to stderr), got: %s\", output)\n\t}\n}\n\n// TestCheckTokenExpirationWarningNotSuppressedForNonAuth ensures that commands\n// starting with \"auth\" as a prefix of another word (e.g. \"authtoken\") are not\n// incorrectly suppressed.\nfunc TestCheckTokenExpirationWarningNotSuppressedForNonAuth(t *testing.T) {\n\toriginalEnv := os.Getenv(\"FASTLY_DISABLE_AUTH_COMMAND\")\n\tos.Setenv(\"FASTLY_DISABLE_AUTH_COMMAND\", \"\")\n\tdefer os.Setenv(\"FASTLY_DISABLE_AUTH_COMMAND\", originalEnv)\n\n\tvar buf bytes.Buffer\n\tdata := expiringTokenData(&buf)\n\n\t// \"authtoken\" is not \"auth\" or \"auth <sub>\", so warning should fire.\n\tcheckTokenExpirationWarning(data, \"authtoken list\")\n\n\toutput := buf.String()\n\tif output == \"\" {\n\t\tt.Error(\"expected warning for non-auth command 'authtoken list' but got none\")\n\t}\n}\n"
  },
  {
    "path": "pkg/app/metadata.json",
    "content": "{\n  \"acl\": {\n    \"create\": {\n      \"examples\": [\n        {\n          \"cmd\": \"fastly acl create --name robots --version active --autoclone\",\n          \"description\": \"Uses the `--version` flag to select the currently active service version and the `--autoclone` flag to enable automate cloning of the service version.\",\n          \"title\": \"Create a new ACL attached to the currently active service version\"\n        }\n      ],\n      \"apis\": [\n        \"https://www.fastly.com/documentation/reference/api/acls/acl#create-acl\"\n      ]\n    },\n    \"delete\": {\n      \"examples\": [\n        {\n          \"cmd\": \"fastly acl delete --name robots --version 1\",\n          \"title\": \"Delete an ACL from the specified service version\"\n        }\n      ],\n      \"apis\": [\n        \"https://www.fastly.com/documentation/reference/api/acls/acl#delete-acl\"\n      ]\n    },\n    \"describe\": {\n      \"examples\": [\n        {\n          \"cmd\": \"fastly acl describe --name robots --version active\",\n          \"title\": \"Retrieve a single ACL by name for the currently active service version\"\n        }\n      ],\n      \"apis\": [\n        \"https://www.fastly.com/documentation/reference/api/acls/acl#get-acl\"\n      ]\n    },\n    \"list\": {\n      \"examples\": [\n        {\n          \"cmd\": \"fastly acl list --version 1\",\n          \"title\": \"List ACLs for the specified service version\"\n        }\n      ],\n      \"apis\": [\n        \"https://www.fastly.com/documentation/reference/api/acls/acl#list-acls\"\n      ]\n    },\n    \"update\": {\n      \"examples\": [\n        {\n          \"cmd\": \"fastly acl update --name robots --new-name blocklist --version latest\",\n          \"title\": \"Update an ACL for the highest numbered existing service version\"\n        }\n      ],\n      \"apis\": [\n        \"https://www.fastly.com/documentation/reference/api/acls/acl#update-acl\"\n      ]\n    }\n  },\n  \"acl-entry\": {\n    \"create\": {\n      \"examples\": [\n        {\n          \"cmd\": \"fastly acl-entry create --acl-id SU1Z0isxPaozGVKXdv0eY --ip 192.0.2.0\",\n          \"title\": \"Add an ACL entry to the specified ACL\"\n        },\n        {\n          \"cmd\": \"fastly acl-entry create --acl-id SU1Z0isxPaozGVKXdv0eY --ip 192.0.2.0 --negated\",\n          \"title\": \"Add a negated ACL entry to the specified ACL\"\n        }\n      ],\n      \"apis\": [\n        \"https://www.fastly.com/documentation/reference/api/acls/acl-entry#create-acl-entry\"\n      ]\n    },\n    \"delete\": {\n      \"examples\": [\n        {\n          \"cmd\": \"fastly acl-entry delete --acl-id SU1Z0isxPaozGVKXdv0eY --id 4DiuYrv9nVoa4HFmQmujT1\",\n          \"title\": \"Delete an ACL entry from the specified ACL\"\n        }\n      ],\n      \"apis\": [\n        \"https://www.fastly.com/documentation/reference/api/acls/acl-entry#delete-acl-entry\"\n      ]\n    },\n    \"describe\": {\n      \"examples\": [\n        {\n          \"cmd\": \"fastly acl-entry describe --acl-id SU1Z0isxPaozGVKXdv0eY --id x9KzsrACXZv8tPwlEDsKb6\",\n          \"title\": \"Retrieve a single ACL entry from the specified ACL\"\n        }\n      ],\n      \"apis\": [\n        \"https://www.fastly.com/documentation/reference/api/acls/acl-entry#get-acl-entry\"\n      ]\n    },\n    \"list\": {\n      \"examples\": [\n        {\n          \"cmd\": \"fastly acl-entry list --acl-id SU1Z0isxPaozGVKXdv0eY\",\n          \"title\": \"List ACL entries from the specified ACL\"\n        }\n      ],\n      \"apis\": [\n        \"https://www.fastly.com/documentation/reference/api/acls/acl-entry#list-acl-entries\"\n      ]\n    },\n    \"update\": {\n      \"examples\": [\n        {\n          \"cmd\": \"fastly acl-entry update --acl-id SU1Z0isxPaozGVKXdv0eY --id x9KzsrACXZv8tPwlEDsKb6 --negated\",\n          \"title\": \"Update an ACL entry in the specified ACL\"\n        },\n        {\n          \"cmd\": \"fastly acl-entry update --acl-id SU1Z0isxPaozGVKXdv0eY --file ./batch.json\",\n          \"description\": \"Update multiple ACL entries using a [JSON batch file](https://www.fastly.com/documentation/reference/api/acls/acl-entry#bulk-update-acl-entries).\",\n          \"title\": \"Update multiple ACL entries in the specified ACL using a local file\"\n        },\n        {\n          \"cmd\": \"fastly acl-entry update --acl-id SU1Z0isxPaozGVKXdv0eY --file \\\"$(< batch.json)\\\"\",\n          \"description\": \"Update multiple ACL entries using a [JSON batch file](https://www.fastly.com/documentation/reference/api/acls/acl-entry#bulk-update-acl-entries)'s content passed in using shell command substitution.\",\n          \"title\": \"Update multiple ACL entries in the specified ACL using command substitution\"\n        }\n      ],\n      \"apis\": [\n        \"https://www.fastly.com/documentation/reference/api/acls/acl-entry#bulk-update-acl-entries\",\n        \"https://www.fastly.com/documentation/reference/api/acls/acl-entry#update-acl-entry\"\n      ]\n    }\n  },\n  \"auth\": {\n    \"login\": {\n      \"examples\": [\n        {\n          \"cmd\": \"fastly auth login\",\n          \"title\": \"Authenticate by pasting an API token\"\n        },\n        {\n          \"cmd\": \"fastly auth login --sso --token my-sso\",\n          \"title\": \"Authenticate using browser-based SSO\"\n        }\n      ]\n    },\n    \"add\": {\n      \"examples\": [\n        {\n          \"cmd\": \"fastly auth add staging --api-token $FASTLY_API_TOKEN\",\n          \"title\": \"Store a named token for a secondary environment\"\n        }\n      ]\n    },\n    \"use\": {\n      \"examples\": [\n        {\n          \"cmd\": \"fastly auth use staging\",\n          \"title\": \"Make a stored token the default\"\n        }\n      ]\n    },\n    \"list\": {\n      \"examples\": [\n        {\n          \"cmd\": \"fastly auth list\",\n          \"title\": \"List stored tokens\"\n        }\n      ]\n    },\n    \"show\": {\n      \"examples\": [\n        {\n          \"cmd\": \"fastly auth show staging\",\n          \"title\": \"Show details for a stored token\"\n        }\n      ]\n    },\n    \"delete\": {\n      \"examples\": [\n        {\n          \"cmd\": \"fastly auth delete staging\",\n          \"title\": \"Delete a stored token\"\n        }\n      ]\n    }\n  },\n  \"auth-token\": {\n    \"create\": {\n      \"apis\": [\n        \"https://www.fastly.com/documentation/reference/api/auth-tokens#create-token\"\n      ]\n    },\n    \"delete\": {\n      \"apis\": [\n        \"https://www.fastly.com/documentation/reference/api/auth-tokens#revoke-token-current\",\n        \"https://www.fastly.com/documentation/reference/api/auth-tokens#bulk-revoke-tokens\",\n        \"https://www.fastly.com/documentation/reference/api/auth-tokens#revoke-token\"\n      ]\n    },\n    \"describe\": {\n      \"apis\": [\n        \"https://www.fastly.com/documentation/reference/api/auth-tokens#get-token-current\"\n      ]\n    },\n    \"list\": {\n      \"apis\": [\n        \"https://www.fastly.com/documentation/reference/api/auth-tokens#list-tokens-customer\",\n        \"https://www.fastly.com/documentation/reference/api/auth-tokens#list-tokens-user\"\n      ]\n    }\n  },\n  \"backend\": {\n    \"create\": {\n      \"examples\": [\n        {\n          \"cmd\": \"fastly backend create --name example --address example.com --version active --autoclone\",\n          \"description\": \"Create a backend with a hostname assigned to the `--address` flag. The `--override-host`, `--ssl-cert-hostname` and `--ssl-sni-hostname` will default to the same hostname assigned to `--address`.\",\n          \"title\": \"Create a backend on the currently active service version\"\n        }\n      ],\n      \"apis\": [\n        \"https://www.fastly.com/documentation/reference/api/services/backend#create-backend\"\n      ]\n    },\n    \"delete\": {\n      \"examples\": [\n        {\n          \"cmd\": \"fastly backend delete --name example --version latest\",\n          \"title\": \"Delete a backend from the highest numbered existing service version\"\n        }\n      ],\n      \"apis\": [\n        \"https://www.fastly.com/documentation/reference/api/services/backend#delete-backend\"\n      ]\n    },\n    \"describe\": {\n      \"examples\": [\n        {\n          \"cmd\": \"fastly backend describe --name example --version 1\",\n          \"title\": \"Show detailed information about a backend on the specified service version\"\n        }\n      ],\n      \"apis\": [\n        \"https://www.fastly.com/documentation/reference/api/services/backend#get-backend\"\n      ]\n    },\n    \"list\": {\n      \"examples\": [\n        {\n          \"cmd\": \"fastly backend list --version active\",\n          \"title\": \"List backends on the currently active service version\"\n        }\n      ],\n      \"apis\": [\n        \"https://www.fastly.com/documentation/reference/api/services/backend#list-backends\"\n      ]\n    },\n    \"update\": {\n      \"examples\": [\n        {\n          \"cmd\": \"fastly backend update --name example --new-name testing --version latest\",\n          \"title\": \"Update a backend on the highest numbered existing service version\"\n        }\n      ],\n      \"apis\": [\n        \"https://www.fastly.com/documentation/reference/api/services/backend#update-backend\"\n      ]\n    }\n  },\n  \"compute\": {\n    \"build\": {\n      \"examples\": [\n        {\n          \"cmd\": \"fastly compute build\",\n          \"description\": \"In the `fastly.toml` manifest define a new `[scripts]` table and within it define a `build` key with your Yarn instructions. For example, `build = \\\"yarn install && yarn build\\\"`.\",\n          \"title\": \"Build a JavaScript Compute package using Yarn instead of NPM\"\n        }\n      ]\n    },\n    \"deploy\": {\n      \"examples\": [\n        {\n          \"cmd\": \"fastly compute deploy --accept-defaults\",\n          \"description\": \"The optional `--accept-defaults` flag accepts default values for all prompts if configured via the [fastly.toml](https://www.fastly.com/documentation/reference/compute/fastly-toml) `[setup]` section and performs a deploy non-interactively\",\n          \"title\": \"Deploy a package to a Fastly Compute service\"\n        },\n        {\n          \"cmd\": \"fastly compute deploy --package ./pkg/example.tar.gz\",\n          \"description\": \"Use the <kdb>fastly compute pack</kbd> command to package up a pre-compiled Wasm binary and then reference the generated archive file when deploying.\",\n          \"title\": \"Deploy a custom package to a Fastly Compute service\"\n        }\n      ],\n      \"apis\": [\n        \"https://www.fastly.com/documentation/reference/api/services/service#create-service\",\n        \"https://www.fastly.com/documentation/reference/api/services/service#get-service\",\n        \"https://www.fastly.com/documentation/reference/api/services/package#get-package\",\n        \"https://www.fastly.com/documentation/reference/api/services/package#put-package\"\n      ]\n    },\n    \"init\": {\n      \"examples\": [\n        {\n          \"cmd\": \"fastly compute init --name example --language rust\",\n          \"description\": \"To initialize a new Compute package you must select a supported language. The language can be provided using the optional `--language` flag, which supports tab completion hints, or the flag can be omitted and you'll be prompted interactively. The `--name` flag can also be omitted, which will result in the CLI prompting you interactively.\",\n          \"title\": \"Initialize a new Compute package locally\"\n        },\n        {\n          \"cmd\": \"fastly compute init --from=https://fiddle.fastly.dev/fiddle/0220c0d2\",\n          \"description\": \"Any [Compute examples](https://www.fastly.com/documentation/solutions/examples) can be used as a source template for your new package.\",\n          \"title\": \"Initialize a new Compute package locally using a remote package template\"\n        },\n        {\n          \"cmd\": \"fastly compute init --directory ./example\",\n          \"description\": \"We recommend that you change to the new project directory after running this command, before executing further CLI commands.\",\n          \"title\": \"Initialize a new Compute package locally in a different directory\"\n        }\n      ]\n    },\n    \"pack\": {\n      \"examples\": [\n        {\n          \"cmd\": \"fastly compute pack --wasm-binary ./bin/main.wasm\",\n          \"description\": \"Write Compute applications in [any WASI-supporting language](https://www.fastly.com/documentation/guides/compute/custom) and use <kbd>fastly compute pack</kbd> to package the pre-compiled Wasm binary into a supported format.\",\n          \"title\": \"Package a pre-compiled Wasm binary for a Fastly Compute service\"\n        }\n      ]\n    },\n    \"publish\": {\n      \"examples\": [\n        {\n          \"cmd\": \"fastly compute publish --accept-defaults\",\n          \"description\": \"The <kbd>fastly compute publish</kbd> command is a convenience wrapper around the existing build and deploy commands. All flags present on the <kbd>fastly compute build</kbd> and <kbd>fastly compute deploy</kbd> commands are available to use here.\",\n          \"title\": \"Build and deploy a Compute package to a Fastly service\"\n        }\n      ],\n      \"apis\": [\n        \"https://www.fastly.com/documentation/reference/api/services/service#create-service\",\n        \"https://www.fastly.com/documentation/reference/api/services/service#get-service\",\n        \"https://www.fastly.com/documentation/reference/api/services/package#get-package\",\n        \"https://www.fastly.com/documentation/reference/api/services/package#put-package\"\n      ]\n    },\n    \"serve\": {\n      \"examples\": [\n        {\n          \"cmd\": \"fastly compute serve --watch\",\n          \"description\": \"The `compute serve` command wraps the existing build command. All flags present on the <kbd>fastly compute build</kbd> command are available to use here. Additionally, the `--watch` command enables 'hot reloading' of your project code whenever changes are made to the source code.\",\n          \"title\": \"Build and run a Compute package locally\"\n        }\n      ]\n    },\n    \"update\": {\n      \"examples\": [\n        {\n          \"cmd\": \"fastly compute update --package ./pkg/example.tar.gz --version active --autoclone\",\n          \"description\": \"Uses the `--version` flag to select the currently active service version and the `--autoclone` flag to enable automate cloning of the service version.\",\n          \"title\": \"Update a package on the currently active service version\"\n        }\n      ],\n      \"apis\": [\n        \"https://www.fastly.com/documentation/reference/api/services/package#put-package\"\n      ]\n    },\n    \"validate\": {\n      \"examples\": [\n        {\n          \"cmd\": \"fastly compute validate --package ./pkg/example.tar.gz\",\n          \"title\": \"Validate a Compute package\"\n        }\n      ]\n    }\n  },\n  \"config-store-entry\": {\n    \"delete\": {\n      \"examples\": [\n        {\n          \"cmd\": \"fastly config-store-entry delete --all --store-id <STORE_ID> --batch-size 30 --auto-yes\",\n          \"description\": \"Delete all entries from the Config Store in batches of 30 and ignoring the warning prompt.\",\n          \"title\": \"Delete all entries from the Config Store\"\n        }\n      ],\n      \"apis\": [\n        \"https://www.fastly.com/documentation/reference/api/services/resources/config-store-item#list-config-store-items\",\n        \"https://www.fastly.com/documentation/reference/api/services/resources/config-store-item#delete-config-store-item\"\n      ]\n    }\n  },\n  \"dictionary\": {\n    \"create\": {\n      \"apis\": [\n        \"https://www.fastly.com/documentation/reference/api/dictionaries/dictionary#get-dictionary\"\n      ]\n    },\n    \"delete\": {\n      \"apis\": [\n        \"https://www.fastly.com/documentation/reference/api/dictionaries/dictionary#delete-dictionary\"\n      ]\n    },\n    \"describe\": {\n      \"apis\": [\n        \"https://www.fastly.com/documentation/reference/api/dictionaries/dictionary#get-dictionary\",\n        \"https://www.fastly.com/documentation/reference/api/dictionaries/dictionary-info#get-dictionary-info\",\n        \"https://www.fastly.com/documentation/reference/api/dictionaries/dictionary-item#list-dictionary-items\"\n      ]\n    },\n    \"list\": {\n      \"apis\": [\n        \"https://www.fastly.com/documentation/reference/api/dictionaries/dictionary#list-dictionaries\"\n      ]\n    },\n    \"update\": {\n      \"apis\": [\n        \"https://www.fastly.com/documentation/reference/api/dictionaries/dictionary#update-dictionary\"\n      ]\n    }\n  },\n  \"dictionary-item\": {\n    \"create\": {\n      \"apis\": [\n        \"https://www.fastly.com/documentation/reference/api/dictionaries/dictionary-item#create-dictionary-item\"\n      ]\n    },\n    \"delete\": {\n      \"apis\": [\n        \"https://www.fastly.com/documentation/reference/api/dictionaries/dictionary-item#delete-dictionary-item\"\n      ]\n    },\n    \"describe\": {\n      \"apis\": [\n        \"https://www.fastly.com/documentation/reference/api/dictionaries/dictionary-item#get-dictionary-item\"\n      ]\n    },\n    \"list\": {\n      \"apis\": [\n        \"https://www.fastly.com/documentation/reference/api/dictionaries/dictionary-item#list-dictionary-items\"\n      ]\n    },\n    \"update\": {\n      \"apis\": [\n        \"https://www.fastly.com/documentation/reference/api/dictionaries/dictionary-item#bulk-update-dictionary-item\",\n        \"https://www.fastly.com/documentation/reference/api/dictionaries/dictionary-item#upsert-dictionary-item\"\n      ]\n    }\n  },\n  \"domain\": {\n    \"create\": {\n      \"examples\": [\n        {\n          \"cmd\": \"fastly domain create --name example.com --version latest --autoclone\",\n          \"description\": \"Uses the `--version` flag to dynamically determine the highest numbered existing service version, and the `--autoclone` flag if the latest version is currently 'active'\",\n          \"title\": \"Create a domain on the highest numbered existing service version\"\n        }\n      ],\n      \"apis\": [\n        \"https://www.fastly.com/documentation/reference/api/services/domain#create-domain\"\n      ]\n    },\n    \"delete\": {\n      \"examples\": [\n        {\n          \"cmd\": \"fastly domain delete --name example.com --version latest --autoclone\",\n          \"description\": \"Uses the `--version` flag to dynamically determine the highest numbered existing service version, and the `--autoclone` flag if the latest version is currently 'active'\",\n          \"title\": \"Delete a domain from the highest numbered existing service version\"\n        }\n      ],\n      \"apis\": [\n        \"https://www.fastly.com/documentation/reference/api/services/domain#delete-domain\"\n      ]\n    },\n    \"describe\": {\n      \"examples\": [\n        {\n          \"cmd\": \"fastly domain describe --name example.com --version 1\",\n          \"title\": \"Show detailed information about a domain on the specified service version\"\n        }\n      ],\n      \"apis\": [\n        \"https://www.fastly.com/documentation/reference/api/services/domain#get-domain\"\n      ]\n    },\n    \"list\": {\n      \"examples\": [\n        {\n          \"cmd\": \"fastly domain list --version active\",\n          \"title\": \"List domains on the currently active service version\"\n        }\n      ],\n      \"apis\": [\n        \"https://www.fastly.com/documentation/reference/api/services/domain#list-domains\"\n      ]\n    },\n    \"update\": {\n      \"examples\": [\n        {\n          \"cmd\": \"fastly domain update --name example.com --new-name example.net --version active --autoclone\",\n          \"description\": \"Uses the `--version` flag to select the currently active service version and the `--autoclone` flag to enable automate cloning of the service version.\",\n          \"title\": \"Update a domain on the currently active service version\"\n        }\n      ],\n      \"apis\": [\n        \"https://www.fastly.com/documentation/reference/api/services/domain#update-domain\"\n      ]\n    },\n    \"validate\": {\n      \"examples\": [\n        {\n          \"cmd\": \"fastly domain validate --name example.com --version 2\",\n          \"description\": \"To validate all domains at once replace the `--name` flag with `--all`.\",\n          \"title\": \"Check the status of a specific domain's DNS record for the specified service version\"\n        }\n      ],\n      \"apis\": [\n        \"https://www.fastly.com/documentation/reference/api/services/domain#check-domains\",\n        \"https://www.fastly.com/documentation/reference/api/services/domain#check-domain\"\n      ]\n    }\n  },\n  \"healthcheck\": {\n    \"create\": {\n      \"apis\": [\n        \"https://www.fastly.com/documentation/reference/api/services/healthcheck#create-healthcheck\"\n      ]\n    },\n    \"delete\": {\n      \"apis\": [\n        \"https://www.fastly.com/documentation/reference/api/services/healthcheck#delete-healthcheck\"\n      ]\n    },\n    \"describe\": {\n      \"apis\": [\n        \"https://www.fastly.com/documentation/reference/api/services/healthcheck#get-healthcheck\"\n      ]\n    },\n    \"list\": {\n      \"apis\": [\n        \"https://www.fastly.com/documentation/reference/api/services/healthcheck#list-healthchecks\"\n      ]\n    },\n    \"update\": {\n      \"apis\": [\n        \"https://www.fastly.com/documentation/reference/api/services/healthcheck#update-healthcheck\"\n      ]\n    }\n  },\n  \"ip-list\": {\n    \"apis\": [\n      \"https://www.fastly.com/documentation/reference/api/utils/public-ip-list\"\n    ]\n  },\n  \"kv-store-entry\": {\n    \"create\": {\n      \"examples\": [\n        {\n          \"cmd\": \"echo '{\\\"key\\\":\\\"example\\\",\\\"value\\\":\\\"VkFMVUU=\\\"}' | fastly kv-store-entry create --stdin\",\n          \"description\": \"Each JSON entry should be separated by a newline (\\n) delimiter, and the value to be inserted should be base64 encoded.\",\n          \"title\": \"Stream data into a KV Store using STDIN\"\n        },\n        {\n          \"cmd\": \"fastly kv-store-entry create --file data.json\",\n          \"description\": \"Each entry in the JSON file should be its own JSON object separated by a newline, and the value to be inserted should be base64 encoded.\",\n          \"title\": \"Stream data into a KV Store using a JSON file\"\n        },\n        {\n          \"cmd\": \"fastly kv-store-entry create --dir ./data/\",\n          \"description\": \"The filename will be used as the key, and the file contents will be used as the value (unlike other options, the file content doesn't need to be base64 encoded).\",\n          \"title\": \"Concurrently insert data into a KV Store using a file directory structure\"\n        }\n      ],\n      \"apis\": [\n        \"https://www.fastly.com/documentation/reference/api/services/resources/kv-store-item#set-value-for-key\"\n      ]\n    }\n  },\n  \"logging\": {\n    \"azureblob\": {\n      \"create\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/azureblob#create-log-azure\"\n        ]\n      },\n      \"delete\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/azureblob#delete-log-azure\"\n        ]\n      },\n      \"describe\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/azureblob#get-log-azure\"\n        ]\n      },\n      \"list\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/azureblob#list-log-azure\"\n        ]\n      },\n      \"update\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/azureblob#update-log-azure\"\n        ]\n      }\n    },\n    \"bigquery\": {\n      \"create\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/bigquery#create-log-bigquery\"\n        ]\n      },\n      \"delete\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/bigquery#delete-log-bigquery\"\n        ]\n      },\n      \"describe\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/bigquery#get-log-bigquery\"\n        ]\n      },\n      \"list\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/bigquery#list-log-bigquery\"\n        ]\n      },\n      \"update\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/bigquery#update-log-bigquery\"\n        ]\n      }\n    },\n    \"cloudfiles\": {\n      \"create\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/cloudfiles#create-log-cloudfiles\"\n        ]\n      },\n      \"delete\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/cloudfiles#delete-log-cloudfiles\"\n        ]\n      },\n      \"describe\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/cloudfiles#get-log-cloudfiles\"\n        ]\n      },\n      \"list\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/cloudfiles#list-log-cloudfiles\"\n        ]\n      },\n      \"update\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/cloudfiles#update-log-cloudfiles\"\n        ]\n      }\n    },\n    \"datadog\": {\n      \"create\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/datadog#create-log-datadog\"\n        ]\n      },\n      \"delete\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/datadog#delete-log-datadog\"\n        ]\n      },\n      \"describe\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/datadog#get-log-datadog\"\n        ]\n      },\n      \"list\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/datadog#list-log-datadog\"\n        ]\n      },\n      \"update\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/datadog#update-log-datadog\"\n        ]\n      }\n    },\n    \"digitalocean\": {\n      \"create\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/digitalocean#create-log-digocean\"\n        ]\n      },\n      \"delete\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/digitalocean#delete-log-digocean\"\n        ]\n      },\n      \"describe\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/digitalocean#get-log-digocean\"\n        ]\n      },\n      \"list\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/digitalocean#list-log-digocean\"\n        ]\n      },\n      \"update\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/digitalocean#update-log-digocean\"\n        ]\n      }\n    },\n    \"elasticsearch\": {\n      \"create\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/elasticsearch#create-log-elasticsearch\"\n        ]\n      },\n      \"delete\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/elasticsearch#delete-log-elasticsearch\"\n        ]\n      },\n      \"describe\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/elasticsearch#get-log-elasticsearch\"\n        ]\n      },\n      \"list\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/elasticsearch#list-log-elasticsearch\"\n        ]\n      },\n      \"update\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/elasticsearch#update-log-elasticsearch\"\n        ]\n      }\n    },\n    \"ftp\": {\n      \"create\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/ftp#create-log-ftp\"\n        ]\n      },\n      \"delete\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/ftp#delete-log-ftp\"\n        ]\n      },\n      \"describe\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/ftp#get-log-ftp\"\n        ]\n      },\n      \"list\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/ftp#list-log-ftp\"\n        ]\n      },\n      \"update\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/ftp#update-log-ftp\"\n        ]\n      }\n    },\n    \"gcs\": {\n      \"create\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/gcs#create-log-gcs\"\n        ]\n      },\n      \"delete\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/gcs#delete-log-gcs\"\n        ]\n      },\n      \"describe\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/gcs#get-log-gcs\"\n        ]\n      },\n      \"list\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/gcs#list-log-gcs\"\n        ]\n      },\n      \"update\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/gcs#update-log-gcs\"\n        ]\n      }\n    },\n    \"googlepubsub\": {\n      \"create\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/google-pubsub#create-log-gcp-pubsub\"\n        ]\n      },\n      \"delete\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/google-pubsub#delete-log-gcp-pubsub\"\n        ]\n      },\n      \"describe\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/google-pubsub#get-log-gcp-pubsub\"\n        ]\n      },\n      \"list\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/google-pubsub#list-log-gcp-pubsub\"\n        ]\n      },\n      \"update\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/google-pubsub#update-log-gcp-pubsub\"\n        ]\n      }\n    },\n    \"heroku\": {\n      \"create\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/heroku#create-log-heroku\"\n        ]\n      },\n      \"delete\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/heroku#delete-log-heroku\"\n        ]\n      },\n      \"describe\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/heroku#get-log-heroku\"\n        ]\n      },\n      \"list\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/heroku#list-log-heroku\"\n        ]\n      },\n      \"update\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/heroku#update-log-heroku\"\n        ]\n      }\n    },\n    \"honeycomb\": {\n      \"create\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/honeycomb#create-log-honeycomb\"\n        ]\n      },\n      \"delete\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/honeycomb#delete-log-honeycomb\"\n        ]\n      },\n      \"describe\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/honeycomb#get-log-honeycomb\"\n        ]\n      },\n      \"list\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/honeycomb#list-log-honeycomb\"\n        ]\n      },\n      \"update\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/honeycomb#update-log-honeycomb\"\n        ]\n      }\n    },\n    \"https\": {\n      \"create\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/https#create-log-https\"\n        ]\n      },\n      \"delete\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/https#delete-log-https\"\n        ]\n      },\n      \"describe\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/https#get-log-https\"\n        ]\n      },\n      \"list\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/https#list-log-https\"\n        ]\n      },\n      \"update\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/https#update-log-https\"\n        ]\n      }\n    },\n    \"kafka\": {\n      \"create\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/kafka#create-log-kafka\"\n        ]\n      },\n      \"delete\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/kafka#delete-log-kafka\"\n        ]\n      },\n      \"describe\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/kafka#get-log-kafka\"\n        ]\n      },\n      \"list\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/kafka#list-log-kafka\"\n        ]\n      },\n      \"update\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/kafka#update-log-kafka\"\n        ]\n      }\n    },\n    \"kinesis\": {\n      \"create\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/kinesis#create-log-kinesis\"\n        ]\n      },\n      \"delete\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/kinesis#delete-log-kinesis\"\n        ]\n      },\n      \"describe\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/kinesis#get-log-kinesis\"\n        ]\n      },\n      \"list\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/kinesis#list-log-kinesis\"\n        ]\n      },\n      \"update\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/kinesis#update-log-kinesis\"\n        ]\n      }\n    },\n    \"logentries\": {\n      \"create\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/logentries#create-log-logentries\"\n        ]\n      },\n      \"delete\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/logentries#delete-log-logentries\"\n        ]\n      },\n      \"describe\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/logentries#get-log-logentries\"\n        ]\n      },\n      \"list\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/logentries#list-log-logentries\"\n        ]\n      },\n      \"update\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/logentries#update-log-logentries\"\n        ]\n      }\n    },\n    \"loggly\": {\n      \"create\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/loggly#create-log-loggly\"\n        ]\n      },\n      \"delete\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/loggly#delete-log-loggly\"\n        ]\n      },\n      \"describe\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/loggly#get-log-loggly\"\n        ]\n      },\n      \"list\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/loggly#list-log-loggly\"\n        ]\n      },\n      \"update\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/loggly#update-log-loggly\"\n        ]\n      }\n    },\n    \"logshuttle\": {\n      \"create\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/logshuttle#create-log-logshuttle\"\n        ]\n      },\n      \"delete\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/logshuttle#delete-log-logshuttle\"\n        ]\n      },\n      \"describe\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/logshuttle#get-log-logshuttle\"\n        ]\n      },\n      \"list\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/logshuttle#list-log-logshuttle\"\n        ]\n      },\n      \"update\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/logshuttle#update-log-logshuttle\"\n        ]\n      }\n    },\n    \"newrelic\": {\n      \"create\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/new-relic#create-log-newrelic\"\n        ]\n      },\n      \"delete\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/new-relic#delete-log-newrelic\"\n        ]\n      },\n      \"describe\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/new-relic#get-log-newrelic\"\n        ]\n      },\n      \"list\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/new-relic#list-log-newrelic\"\n        ]\n      },\n      \"update\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/new-relic#update-log-newrelic\"\n        ]\n      }\n    },\n    \"openstack\": {\n      \"create\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/openstack#get-log-openstack\"\n        ]\n      },\n      \"delete\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/openstack#delete-log-openstack\"\n        ]\n      },\n      \"describe\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/openstack#get-log-openstack\"\n        ]\n      },\n      \"list\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/openstack#list-log-openstack\"\n        ]\n      },\n      \"update\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/openstack#update-log-openstack\"\n        ]\n      }\n    },\n    \"papertrail\": {\n      \"create\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/papertrail#create-log-papertrail\"\n        ]\n      },\n      \"delete\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/papertrail#delete-log-papertrail\"\n        ]\n      },\n      \"describe\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/papertrail#get-log-papertrail\"\n        ]\n      },\n      \"list\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/papertrail#list-log-papertrail\"\n        ]\n      },\n      \"update\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/papertrail#update-log-papertrail\"\n        ]\n      }\n    },\n    \"s3\": {\n      \"create\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/s3#create-log-aws-s3\"\n        ]\n      },\n      \"delete\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/s3#delete-log-aws-s3\"\n        ]\n      },\n      \"describe\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/s3#get-log-aws-s3\"\n        ]\n      },\n      \"list\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/s3#list-log-aws-s3\"\n        ]\n      },\n      \"update\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/s3#update-log-aws-s3\"\n        ]\n      }\n    },\n    \"scalyr\": {\n      \"create\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/scalyr#create-log-scalyr\"\n        ]\n      },\n      \"delete\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/scalyr#delete-log-scalyr\"\n        ]\n      },\n      \"describe\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/scalyr#get-log-scalyr\"\n        ]\n      },\n      \"list\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/scalyr#list-log-scalyr\"\n        ]\n      },\n      \"update\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/scalyr#update-log-scalyr\"\n        ]\n      }\n    },\n    \"sftp\": {\n      \"create\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/sftp#create-log-sftp\"\n        ]\n      },\n      \"delete\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/sftp#delete-log-sftp\"\n        ]\n      },\n      \"describe\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/sftp#get-log-sftp\"\n        ]\n      },\n      \"list\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/sftp#list-log-sftp\"\n        ]\n      },\n      \"update\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/sftp#update-log-sftp\"\n        ]\n      }\n    },\n    \"splunk\": {\n      \"create\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/splunk#create-log-splunk\"\n        ]\n      },\n      \"delete\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/splunk#delete-log-splunk\"\n        ]\n      },\n      \"describe\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/splunk#get-log-splunk\"\n        ]\n      },\n      \"list\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/splunk#list-log-splunk\"\n        ]\n      },\n      \"update\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/splunk#update-log-splunk\"\n        ]\n      }\n    },\n    \"sumologic\": {\n      \"create\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/sumologic#create-log-sumologic\"\n        ]\n      },\n      \"delete\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/sumologic#delete-log-sumologic\"\n        ]\n      },\n      \"describe\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/sumologic#list-log-sumologic\"\n        ]\n      },\n      \"list\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/sumologic#list-log-sumologic\"\n        ]\n      },\n      \"update\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/sumologic#update-log-sumologic\"\n        ]\n      }\n    },\n    \"syslog\": {\n      \"create\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/syslog#create-log-syslog\"\n        ]\n      },\n      \"delete\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/syslog#delete-log-syslog\"\n        ]\n      },\n      \"describe\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/syslog#get-log-syslog\"\n        ]\n      },\n      \"list\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/syslog#list-log-syslog\"\n        ]\n      },\n      \"update\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/logging/syslog#update-log-syslog\"\n        ]\n      }\n    }\n  },\n  \"metadata\": {\n    \"examples\": [\n      {\n        \"cmd\": \"fastly compute metadata --enable\",\n        \"title\": \"Enable all metadata collection information\"\n      },\n      {\n        \"cmd\": \"fastly compute metadata --disable\",\n        \"title\": \"Disable all metadata collection information\"\n      },\n      {\n        \"cmd\": \"fastly compute metadata --enable-build --enable-machine --enable-package\",\n        \"title\": \"Enable specific metadata collection information\"\n      },\n      {\n        \"cmd\": \"fastly compute metadata --disable-build --disable-machine --disable-package\",\n        \"title\": \"Disable specific metadata collection information\"\n      }\n    ]\n  },\n  \"pops\": {\n    \"apis\": [\n      \"https://www.fastly.com/documentation/reference/api/utils/pops#list-pops\"\n    ]\n  },\n  \"profile\": {\n    \"apis\": [\n      \"https://www.fastly.com/documentation/reference/api/auth-tokens#get-token-current\",\n      \"https://www.fastly.com/documentation/reference/api/account/user#get-user\"\n    ]\n  },\n  \"purge\": {\n    \"apis\": [\n      \"https://www.fastly.com/documentation/reference/api/purging#purge-all\",\n      \"https://www.fastly.com/documentation/reference/api/purging#bulk-purge-tag\",\n      \"https://www.fastly.com/documentation/reference/api/purging#purge-tag\",\n      \"https://www.fastly.com/documentation/reference/api/purging#purge-single-url\"\n    ]\n  },\n  \"search\": {\n    \"create\": {\n      \"apis\": [\n        \"https://www.fastly.com/documentation/reference/api/services/service#create-service\"\n      ]\n    },\n    \"delete\": {\n      \"apis\": [\n        \"https://www.fastly.com/documentation/reference/api/services/service#get-service-detail\",\n        \"https://www.fastly.com/documentation/reference/api/services/version#deactivate-service-version\",\n        \"https://www.fastly.com/documentation/reference/api/services/service#delete-service\"\n      ]\n    },\n    \"describe\": {\n      \"apis\": [\n        \"https://www.fastly.com/documentation/reference/api/services/service#get-service-detail\"\n      ]\n    },\n    \"list\": {\n      \"apis\": [\n        \"https://www.fastly.com/documentation/reference/api/services/service#list-services\"\n      ]\n    },\n    \"search\": {\n      \"apis\": [\n        \"https://www.fastly.com/documentation/reference/api/services/service#search-service\"\n      ]\n    },\n    \"update\": {\n      \"apis\": [\n        \"https://www.fastly.com/documentation/reference/api/services/service#update-service\"\n      ]\n    }\n  },\n  \"service-version\": {\n    \"activate\": {\n      \"apis\": [\n        \"https://www.fastly.com/documentation/reference/api/services/version#activate-service-version\"\n      ]\n    },\n    \"clone\": {\n      \"apis\": [\n        \"https://www.fastly.com/documentation/reference/api/services/version#clone-service-version\"\n      ]\n    },\n    \"deactivate\": {\n      \"apis\": [\n        \"https://www.fastly.com/documentation/reference/api/services/version#deactivate-service-version\"\n      ]\n    },\n    \"list\": {\n      \"apis\": [\n        \"https://www.fastly.com/documentation/reference/api/services/version#list-service-versions\"\n      ]\n    },\n    \"lock\": {\n      \"apis\": [\n        \"https://www.fastly.com/documentation/reference/api/services/version#lock-service-version\"\n      ]\n    },\n    \"update\": {\n      \"apis\": [\n        \"https://www.fastly.com/documentation/reference/api/services/version#update-service-version\"\n      ]\n    }\n  },\n  \"stats\": {\n    \"historical\": {\n      \"apis\": [\n        \"https://www.fastly.com/documentation/reference/api/metrics-stats/historical-stats#get-hist-stats-service\"\n      ]\n    },\n    \"realtime\": {\n      \"apis\": [\n        \"https://www.fastly.com/documentation/reference/api/metrics-stats/realtime#get-stats-last-second\"\n      ]\n    },\n    \"regional\": {\n      \"apis\": [\n        \"https://www.fastly.com/documentation/reference/api/metrics-stats/historical-stats#get-regions\"\n      ]\n    }\n  },\n  \"user\": {\n    \"create\": {\n      \"apis\": [\n        \"https://www.fastly.com/documentation/reference/api/account/user#create-user\"\n      ]\n    },\n    \"delete\": {\n      \"apis\": [\n        \"https://www.fastly.com/documentation/reference/api/account/user#delete-user\"\n      ]\n    },\n    \"describe\": {\n      \"apis\": [\n        \"https://www.fastly.com/documentation/reference/api/account/user#get-current-user\",\n        \"https://www.fastly.com/documentation/reference/api/account/user#get-user\"\n      ]\n    },\n    \"list\": {\n      \"apis\": [\n        \"https://www.fastly.com/documentation/reference/api/account/customer#list-users\"\n      ]\n    },\n    \"update\": {\n      \"apis\": [\n        \"https://www.fastly.com/documentation/reference/api/account/user#request-password-reset\",\n        \"https://www.fastly.com/documentation/reference/api/account/user#update-user\"\n      ]\n    }\n  },\n  \"vcl\": {\n    \"condition\": {\n      \"create\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/vcl-services/condition#create-condition\"\n        ]\n      },\n      \"delete\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/vcl-services/condition#delete-condition\"\n        ]\n      },\n      \"describe\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/vcl-services/condition#get-condition\"\n        ]\n      },\n      \"list\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/vcl-services/condition#list-conditions\"\n        ]\n      },\n      \"update\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/vcl-services/condition#update-condition\"\n        ]\n      }\n    },\n    \"custom\": {\n      \"create\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/vcl-services/vcl#create-custom-vcl\"\n        ]\n      },\n      \"delete\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/vcl-services/vcl#delete-custom-vcl\"\n        ]\n      },\n      \"describe\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/vcl-services/vcl#get-custom-vcl\"\n        ]\n      },\n      \"list\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/vcl-services/vcl#list-custom-vcl\"\n        ]\n      },\n      \"update\": {\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/vcl-services/vcl#update-custom-vcl\"\n        ]\n      }\n    },\n    \"snippet\": {\n      \"create\": {\n        \"examples\": [\n          {\n            \"cmd\": \"fastly vcl snippet create --name example --content ./example.vcl --type recv --version latest\",\n            \"description\": \"The `--type` flag additionally supports tab completion hints for valid location values.\",\n            \"title\": \"Create a snippet on the highest numbered existing service version, using a local file\"\n          },\n          {\n            \"cmd\": \"fastly vcl snippet create --name example --content \\\"$(< example.vcl)\\\" --type recv --version latest\",\n            \"description\": \"The `--type` flag additionally supports tab completion hints for valid location values.\",\n            \"title\": \"Create a snippet on the highest numbered existing service version, using command substitution\"\n          }\n        ],\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/vcl-services/snippet#create-snippet\"\n        ]\n      },\n      \"delete\": {\n        \"examples\": [\n          {\n            \"cmd\": \"fastly vcl snippet delete --name example --version 1\",\n            \"title\": \"Delete a specific snippet from the specified service version\"\n          }\n        ],\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/vcl-services/snippet#delete-snippet\"\n        ]\n      },\n      \"describe\": {\n        \"examples\": [\n          {\n            \"cmd\": \"fastly vcl snippet describe --snippet-id 3p8fPcMVB6OqbMxGT83hb9 --dynamic --version active\",\n            \"description\": \"To describe a 'versioned' snippet replace the `--snippet-id` and `--dynamic` flags with `--name`.\",\n            \"title\": \"Get the uploaded VCL snippet for the currently active service version\"\n          }\n        ],\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/vcl-services/snippet#get-snippet-dynamic\",\n          \"https://www.fastly.com/documentation/reference/api/vcl-services/snippet#get-snippet\"\n        ]\n      },\n      \"list\": {\n        \"examples\": [\n          {\n            \"cmd\": \"fastly vcl snippet list --version active\",\n            \"title\": \"List the uploaded VCL snippets for the currently active service version\"\n          }\n        ],\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/vcl-services/snippet#list-snippets\"\n        ]\n      },\n      \"update\": {\n        \"examples\": [\n          {\n            \"cmd\": \"fastly vcl snippet update --snippet-id 2k5KYQCSJERvR8aB3cbOdA --dynamic --type deliver --version latest\",\n            \"description\": \"To update a 'versioned' snippet replace the `--snippet-id` and `--dynamic` flags with `--name`.\",\n            \"title\": \"Update a VCL snippet for the highest numbered existing service version\"\n          }\n        ],\n        \"apis\": [\n          \"https://www.fastly.com/documentation/reference/api/vcl-services/snippet#update-snippet-dynamic\",\n          \"https://www.fastly.com/documentation/reference/api/vcl-services/snippet#update-snippet\"\n        ]\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "pkg/app/run.go",
    "content": "package app\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"os\"\n\t\"slices\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/fatih/color\"\n\t\"github.com/hashicorp/cap/oidc\"\n\t\"github.com/skratchdot/open-golang/open\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/kingpin\"\n\n\t\"github.com/fastly/cli/pkg/api\"\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/auth\"\n\t\"github.com/fastly/cli/pkg/commands\"\n\tauthcmd \"github.com/fastly/cli/pkg/commands/auth\"\n\t\"github.com/fastly/cli/pkg/commands/compute\"\n\t\"github.com/fastly/cli/pkg/commands/update\"\n\t\"github.com/fastly/cli/pkg/commands/version\"\n\t\"github.com/fastly/cli/pkg/config\"\n\t\"github.com/fastly/cli/pkg/env\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/github\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/lookup\"\n\t\"github.com/fastly/cli/pkg/manifest\"\n\t\"github.com/fastly/cli/pkg/revision\"\n\t\"github.com/fastly/cli/pkg/sync\"\n\t\"github.com/fastly/cli/pkg/text\"\n\t\"github.com/fastly/cli/pkg/useragent\"\n)\n\n// Run kick starts the CLI application.\nfunc Run(args []string, stdin io.Reader) error {\n\tdata, err := Init(args, stdin)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to initialise application: %w\", err)\n\t}\n\treturn Exec(data)\n}\n\n// Init constructs all the required objects and data for Exec().\n//\n// NOTE: We define as a package level variable so we can mock output for tests.\nvar Init = func(args []string, stdin io.Reader) (*global.Data, error) {\n\t// Parse the arguments provided by the user via the command-line interface.\n\targs = args[1:]\n\n\t// Define a HTTP client that will be used for making arbitrary HTTP requests.\n\thttpClient := &http.Client{Timeout: time.Minute * 2}\n\n\t// Define the standard input/output streams.\n\tvar (\n\t\tin            = stdin\n\t\tout io.Writer = sync.NewWriter(color.Output)\n\t)\n\n\t// Read relevant configuration options from the user's environment.\n\tvar e config.Environment\n\te.Read(env.Parse(os.Environ()))\n\n\t// Identify verbose flag early (before Kingpin parser has executed) so we can\n\t// print additional output related to the CLI configuration.\n\tvar verboseOutput bool\n\tfor _, seg := range args {\n\t\tif seg == \"-v\" || seg == \"--verbose\" {\n\t\t\tverboseOutput = true\n\t\t}\n\t}\n\n\t// Identify auto-yes/non-interactive flag early (before Kingpin parser has\n\t// executed) so we can handle the interactive prompts appropriately with\n\t// regards to processing the CLI configuration.\n\tvar autoYes, nonInteractive bool\n\tfor _, seg := range args {\n\t\tif seg == \"-y\" || seg == \"--auto-yes\" {\n\t\t\tautoYes = true\n\t\t}\n\t\tif seg == \"-i\" || seg == \"--non-interactive\" {\n\t\t\tnonInteractive = true\n\t\t}\n\t}\n\n\t// Extract a subset of configuration options from the local app directory.\n\tvar cfg config.File\n\tcfg.SetAutoYes(autoYes)\n\tcfg.SetNonInteractive(nonInteractive)\n\tif err := cfg.Read(config.FilePath, in, out, fsterr.Log, verboseOutput); err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Extract user's project configuration from the fastly.toml manifest.\n\tvar md manifest.Data\n\tmd.File.Args = args\n\tmd.File.SetErrLog(fsterr.Log)\n\tmd.File.SetOutput(out)\n\n\t// NOTE: We skip handling the error because not all commands relate to Compute.\n\t_ = md.File.Read(manifest.Filename)\n\n\tfactory := func(token, endpoint string, debugMode bool) (api.Interface, error) {\n\t\tclient, err := fastly.NewClientForEndpoint(token, endpoint)\n\t\tif debugMode {\n\t\t\tclient.DebugMode = true\n\t\t}\n\t\treturn client, err\n\t}\n\n\t// Identify debug-mode flag early (before Kingpin parser has executed) so we\n\t// can inform the github versioners that we're in debug mode.\n\tvar debugMode bool\n\tfor _, seg := range args {\n\t\tif seg == \"--debug-mode\" {\n\t\t\tdebugMode = true\n\t\t}\n\t}\n\n\tversioners := global.Versioners{\n\t\tCLI: github.New(github.Opts{\n\t\t\tDebugMode:  debugMode,\n\t\t\tHTTPClient: httpClient,\n\t\t\tOrg:        \"fastly\",\n\t\t\tRepo:       \"cli\",\n\t\t\tBinary:     \"fastly\",\n\t\t}),\n\t\tViceroy: github.New(github.Opts{\n\t\t\tDebugMode:  debugMode,\n\t\t\tHTTPClient: httpClient,\n\t\t\tOrg:        \"fastly\",\n\t\t\tRepo:       \"viceroy\",\n\t\t\tBinary:     \"viceroy\",\n\t\t\tVersion:    md.File.LocalServer.ViceroyVersion,\n\t\t}),\n\t\tWasmTools: github.New(github.Opts{\n\t\t\tDebugMode:  debugMode,\n\t\t\tHTTPClient: httpClient,\n\t\t\tOrg:        \"bytecodealliance\",\n\t\t\tRepo:       \"wasm-tools\",\n\t\t\tBinary:     \"wasm-tools\",\n\t\t\tExternal:   true,\n\t\t\tNested:     true,\n\t\t}),\n\t}\n\n\t// If a UserAgent extension has been set in the environment,\n\t// apply it\n\tif e.UserAgentExtension != \"\" {\n\t\tuseragent.SetExtension(e.UserAgentExtension)\n\t}\n\t// Override the go-fastly UserAgent value by prepending the CLI version.\n\t//\n\t// Results in a header similar to:\n\t// User-Agent: FastlyCLI/v11.3.0, FastlyGo/10.5.0 (+github.com/fastly/go-fastly; go1.24.3)\n\t// (with any extension supplied above between the FastlyCLI and FastlyGo values)\n\tfastly.UserAgent = fmt.Sprintf(\"%s, %s\", useragent.Name, fastly.UserAgent)\n\n\treturn &global.Data{\n\t\tAPIClientFactory: factory,\n\t\tArgs:             args,\n\t\tConfig:           cfg,\n\t\tConfigPath:       config.FilePath,\n\t\tEnv:              e,\n\t\tErrLog:           fsterr.Log,\n\t\tErrOutput:        os.Stderr,\n\t\tExecuteWasmTools: compute.ExecuteWasmTools,\n\t\tHTTPClient:       httpClient,\n\t\tManifest:         &md,\n\t\tOpener:           open.Run,\n\t\tOutput:           out,\n\t\tVersioners:       versioners,\n\t\tInput:            in,\n\t}, nil\n}\n\n// Exec constructs the application including all of the subcommands, parses the\n// args, invokes the client factory with the token to create a Fastly API\n// client, and executes the chosen command, using the provided io.Reader and\n// io.Writer for input and output, respectively. In the real CLI, func main is\n// just a simple shim to this function; it exists to make end-to-end testing of\n// commands easier/possible.\n//\n// The Exec helper should NOT output any error-related information to the out\n// io.Writer. All error-related information should be encoded into an error type\n// and returned to the caller. This includes usage text.\nfunc Exec(data *global.Data) error {\n\tapp := configureKingpin(data)\n\tcmds := commands.Define(app, data)\n\tcommand, commandName, err := processCommandInput(data, app, cmds)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Check for --json flag early. JSON mode suppresses stdout-bound noise\n\t// (metadata notices, update checks) but still allows stderr warnings\n\t// (token expiry, profile mismatch) so they don't corrupt JSON output.\n\tif slices.Contains(data.Args, \"--json\") {\n\t\tdata.Flags.JSON = true\n\t}\n\n\t// We short-circuit the execution for specific cases:\n\t//\n\t// - argparser.ArgsIsHelpJSON() == true\n\t// - shell autocompletion flag provided\n\tswitch commandName {\n\tcase \"help--json\":\n\t\tfallthrough\n\tcase \"help--format=json\":\n\t\tfallthrough\n\tcase \"help--formatjson\":\n\t\tfallthrough\n\tcase \"shell-autocomplete\":\n\t\treturn nil\n\t}\n\n\tmetadataDisable, _ := strconv.ParseBool(data.Env.WasmMetadataDisable)\n\tif !slices.Contains(data.Args, \"--metadata-disable\") && !metadataDisable && !data.Config.CLI.MetadataNoticeDisplayed && commandCollectsData(commandName) && !data.Flags.Quiet && !data.Flags.JSON {\n\t\ttext.Important(data.Output, \"The Fastly CLI is configured to collect data related to Wasm builds (e.g. compilation times, resource usage, and other non-identifying data). To learn more about what data is being collected, why, and how to disable it: https://www.fastly.com/documentation/reference/cli\")\n\t\ttext.Break(data.Output)\n\t\tdata.Config.CLI.MetadataNoticeDisplayed = true\n\t\terr := data.Config.Write(data.ConfigPath)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to persist change to metadata notice: %w\", err)\n\t\t}\n\t\ttime.Sleep(5 * time.Second) // this message is only displayed once so give the user a chance to see it before it possibly scrolls off screen\n\t}\n\n\tif data.Flags.Quiet || data.Flags.JSON {\n\t\tdata.Manifest.File.SetQuiet(true)\n\t}\n\n\t// Migrate legacy profiles to [auth] section.\n\t// MigrateProfilesToAuth merges without overwriting existing auth entries.\n\tif len(data.Config.Profiles) > 0 {\n\t\tdata.Config.MigrateProfilesToAuth()\n\t\tdata.Config.Profiles = nil\n\t\tif err := data.Config.Write(data.ConfigPath); err != nil {\n\t\t\tdata.ErrLog.Add(err)\n\t\t}\n\t}\n\n\tapiEndpoint, endpointSource := data.APIEndpoint()\n\tif data.Verbose() && !commandSuppressesVerbose(command) {\n\t\tdisplayAPIEndpoint(apiEndpoint, endpointSource, data.Output)\n\t}\n\n\t// User can set env.DebugMode env var or the --debug-mode boolean flag.\n\t// This will prioritize the flag over the env var.\n\tif data.Flags.Debug {\n\t\tdata.Env.DebugMode = \"true\"\n\t}\n\n\t// NOTE: Some commands need just the auth server to be running\n\t// but not necessarily need to process an existing token.\n\tneedsAuthServer := commandRequiresAuthServer(commandName, data.Args)\n\tif !commandRequiresToken(command) && needsAuthServer {\n\t\t// NOTE: Checking for nil allows our test suite to mock the server.\n\t\t// i.e. it'll be nil whenever the CLI is run by a user but not `go test`.\n\t\tif data.AuthServer == nil {\n\t\t\tauthServer, err := configureAuth(apiEndpoint, data.Args, data.Config, data.HTTPClient, data.Env)\n\t\t\tif err != nil {\n\t\t\t\t// Non-fatal: SSO flows will detect the nil auth server and\n\t\t\t\t// report a clear error.\n\t\t\t\tdata.ErrLog.Add(err)\n\t\t\t} else {\n\t\t\t\tdata.AuthServer = authServer\n\t\t\t}\n\t\t}\n\t}\n\n\tif commandRequiresToken(command) {\n\t\t// NOTE: Checking for nil allows our test suite to mock the server.\n\t\t// i.e. it'll be nil whenever the CLI is run by a user but not `go test`.\n\t\tif data.AuthServer == nil {\n\t\t\tauthServer, err := configureAuth(apiEndpoint, data.Args, data.Config, data.HTTPClient, data.Env)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"failed to configure authentication processes: %w\", err)\n\t\t\t}\n\t\t\tdata.AuthServer = authServer\n\t\t}\n\n\t\tif !data.Flags.Quiet && data.Flags.Token == \"\" && data.Flags.Profile == \"\" && data.Env.APIToken == \"\" && data.Manifest != nil && data.Manifest.File.Profile != \"\" {\n\t\t\tif data.Config.GetAuthToken(data.Manifest.File.Profile) == nil {\n\t\t\t\tif defaultName, _ := data.Config.GetDefaultAuthToken(); defaultName != \"\" {\n\t\t\t\t\ttext.Warning(data.ErrOutput, \"fastly.toml profile %q not found in auth config, using default token %q.\\n\", data.Manifest.File.Profile, defaultName)\n\t\t\t\t} else {\n\t\t\t\t\ttext.Warning(data.ErrOutput, \"fastly.toml profile %q not found in auth config and no default token is configured.\\n\", data.Manifest.File.Profile)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\ttoken, tokenSource, err := processToken(data)\n\t\tif err != nil {\n\t\t\tif errors.Is(err, fsterr.ErrDontContinue) {\n\t\t\t\treturn nil // we shouldn't exit 1 if user chooses to stop\n\t\t\t}\n\t\t\treturn fmt.Errorf(\"failed to process token: %w\", err)\n\t\t}\n\n\t\tif token == \"\" && tokenSource == lookup.SourceUndefined {\n\t\t\ttoken, tokenSource, err = promptForAuth(data)\n\t\t\tif err != nil {\n\t\t\t\tif errors.Is(err, fsterr.ErrDontContinue) {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\t\t\t\treturn fmt.Errorf(\"failed to process token: %w\", err)\n\t\t\t}\n\t\t}\n\n\t\tif data.Verbose() && !commandSuppressesVerbose(command) {\n\t\t\tdisplayToken(tokenSource, data)\n\t\t}\n\t\tif !data.Flags.Quiet {\n\t\t\tcheckConfigPermissions(tokenSource, data.ErrOutput)\n\t\t}\n\n\t\tdata.APIClient, data.RTSClient, err = configureClients(token, apiEndpoint, data.APIClientFactory, data.Flags.Debug)\n\t\tif err != nil {\n\t\t\tdata.ErrLog.Add(err)\n\t\t\treturn fmt.Errorf(\"error constructing client: %w\", err)\n\t\t}\n\t}\n\n\tcheckTokenExpirationWarning(data, commandName)\n\n\tf := checkForUpdates(data.Versioners.CLI, commandName)\n\tdefer func() {\n\t\tif !data.Flags.Quiet && !data.Flags.JSON {\n\t\t\tf(data.Output)\n\t\t}\n\t}()\n\n\treturn command.Exec(data.Input, data.Output)\n}\n\nfunc configureKingpin(data *global.Data) *kingpin.Application {\n\t// Set up the main application root, including global flags, and then each\n\t// of the subcommands. Note that we deliberately don't use some of the more\n\t// advanced features of the kingpin.Application flags, like env var\n\t// bindings, because we need to do things like track where a config\n\t// parameter came from.\n\tapp := kingpin.New(\"fastly\", \"A tool to interact with the Fastly API\")\n\tapp.Writers(data.Output, io.Discard) // don't let kingpin write error output\n\tapp.UsageContext(&kingpin.UsageContext{\n\t\tTemplate: VerboseUsageTemplate,\n\t\tFuncs:    UsageTemplateFuncs,\n\t})\n\n\t// Prevent kingpin from calling os.Exit, this gives us greater control over\n\t// error states and output control flow.\n\tapp.Terminate(nil)\n\n\t// IMPORTANT: Kingpin doesn't support global flags.\n\t// Any flags defined below must also be added to two other places:\n\t// 1. ./usage.go (`globalFlags` map).\n\t// 2. ../cmd/argparser.go (`IsGlobalFlagsOnly` function).\n\t//\n\t// NOTE: Global flags (long and short) MUST be unique.\n\t// A subcommand can't define a flag that is already global.\n\t// Kingpin will otherwise trigger a runtime panic 🎉\n\t// Interestingly, short flags can be reused but only across subcommands.\n\tapp.Flag(\"accept-defaults\", \"Accept default options for all interactive prompts apart from Yes/No confirmations\").Short('d').BoolVar(&data.Flags.AcceptDefaults)\n\tapp.Flag(\"account\", \"Fastly Accounts endpoint\").Hidden().StringVar(&data.Flags.AccountEndpoint)\n\tapp.Flag(\"api\", \"Fastly API endpoint\").Hidden().StringVar(&data.Flags.APIEndpoint)\n\tapp.Flag(\"auto-yes\", \"Answer yes automatically to all Yes/No confirmations. This may suppress security warnings\").Short('y').BoolVar(&data.Flags.AutoYes)\n\t// IMPORTANT: `--debug` is a built-in Kingpin flag so we must use `debug-mode`.\n\tapp.Flag(\"debug-mode\", \"Print API request and response details (NOTE: can disrupt the normal CLI flow output formatting)\").BoolVar(&data.Flags.Debug)\n\t// IMPORTANT: `--sso` causes a Kingpin runtime panic 🤦 so we use `enable-sso`.\n\tapp.Flag(\"enable-sso\", \"[DEPRECATED: use 'fastly auth login --sso --token <name>'] Enable SSO for current profile\").Hidden().BoolVar(&data.Flags.SSO)\n\tapp.Flag(\"non-interactive\", \"Do not prompt for user input - suitable for CI processes. Equivalent to --accept-defaults and --auto-yes\").Short('i').BoolVar(&data.Flags.NonInteractive)\n\tapp.Flag(\"profile\", \"[DEPRECATED: use 'fastly auth use'] Switch account profile for single command execution\").Hidden().Short('o').StringVar(&data.Flags.Profile)\n\tapp.Flag(\"quiet\", \"Silence all output except direct command output. This won't prevent interactive prompts (see: --accept-defaults, --auto-yes, --non-interactive)\").Short('q').BoolVar(&data.Flags.Quiet)\n\tif !env.AuthCommandDisabled() {\n\t\ttokenHelp := fmt.Sprintf(\"Fastly API token, or name of a stored auth token (use 'default' for the default token). Falls back to %s env var\", env.APIToken)\n\t\tapp.Flag(\"token\", tokenHelp).HintAction(env.Vars).Short('t').StringVar(&data.Flags.Token)\n\t}\n\tapp.Flag(\"verbose\", \"Verbose logging\").Short('v').BoolVar(&data.Flags.Verbose)\n\n\treturn app\n}\n\n// processToken handles all aspects related to the required API token.\n//\n// For [auth] SSO tokens, we check freshness and attempt to refresh if expired.\n// If both access and refresh tokens are expired, we trigger the SSO flow.\n//\n// Tokens from --token (raw, unavailable when FASTLY_DISABLE_AUTH_COMMAND is\n// set) or FASTLY_API_TOKEN are assumed to be valid.\nfunc processToken(data *global.Data) (token string, tokenSource lookup.Source, err error) {\n\tif err := data.ValidateProfileFlag(); err != nil {\n\t\treturn \"\", lookup.SourceUndefined, err\n\t}\n\n\ttoken, tokenSource = data.Token()\n\n\tswitch tokenSource {\n\tcase lookup.SourceUndefined:\n\t\tif data.Flags.NonInteractive || data.Flags.AutoYes || data.Flags.AcceptDefaults {\n\t\t\treturn \"\", tokenSource, fsterr.ErrNonInteractiveNoToken()\n\t\t}\n\t\tif data.Env.UseSSO == \"1\" {\n\t\t\treturn ssoAuthentication(\"No API token could be found\", data, false)\n\t\t}\n\t\treturn \"\", tokenSource, nil\n\tcase lookup.SourceAuth:\n\t\tname := data.AuthTokenName()\n\t\tif name == \"\" {\n\t\t\tbreak\n\t\t}\n\t\tat := data.Config.GetAuthToken(name)\n\t\tif at != nil && at.Type == config.AuthTokenTypeSSO && at.RefreshToken != \"\" {\n\t\t\treauth, err := checkAndRefreshAuthSSOToken(name, at, data)\n\t\t\tif err != nil {\n\t\t\t\tif errors.Is(err, auth.ErrInvalidGrant) {\n\t\t\t\t\treturn ssoAuthentication(\"We can't refresh your token\", data, true)\n\t\t\t\t}\n\t\t\t\treturn token, tokenSource, fmt.Errorf(\"failed to refresh auth token %q: %w\", name, err)\n\t\t\t}\n\t\t\tif reauth {\n\t\t\t\treturn ssoAuthentication(\"Your auth token has expired and needs re-authentication\", data, false)\n\t\t\t}\n\t\t\ttoken = at.Token\n\t\t}\n\tcase lookup.SourceEnvironment, lookup.SourceFlag, lookup.SourceDefault, lookup.SourceFile:\n\t\t// no-op\n\t}\n\n\treturn token, tokenSource, nil\n}\n\n// checkAndRefreshAuthSSOToken refreshes an SSO-type [auth] token if expired.\nfunc checkAndRefreshAuthSSOToken(name string, at *config.AuthToken, data *global.Data) (reauth bool, err error) {\n\tif at.AccessExpiresAt == \"\" {\n\t\treturn false, nil // no expiry info, assume still valid\n\t}\n\n\taccessExpires, err := time.Parse(time.RFC3339, at.AccessExpiresAt)\n\tif err != nil {\n\t\treturn false, fmt.Errorf(\"invalid access_expires_at %q: %w\", at.AccessExpiresAt, err)\n\t}\n\n\t// Access token still valid.\n\tif time.Now().Before(accessExpires) {\n\t\treturn false, nil\n\t}\n\n\t// Access token expired; check if refresh token is also expired.\n\tif at.RefreshExpiresAt != \"\" {\n\t\trefreshExpires, err := time.Parse(time.RFC3339, at.RefreshExpiresAt)\n\t\tif err != nil {\n\t\t\treturn false, fmt.Errorf(\"invalid refresh_expires_at %q: %w\", at.RefreshExpiresAt, err)\n\t\t}\n\t\tif time.Now().After(refreshExpires) {\n\t\t\tif at.APITokenExpiresAt != \"\" {\n\t\t\t\tapiExpires, err := time.Parse(time.RFC3339, at.APITokenExpiresAt)\n\t\t\t\tif err == nil && time.Now().Before(apiExpires) {\n\t\t\t\t\treturn false, nil\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn true, nil\n\t\t}\n\t}\n\n\tif data.AuthServer == nil {\n\t\treturn true, nil // can't refresh without auth server\n\t}\n\n\tif data.Flags.Verbose {\n\t\ttext.Info(data.Output, \"\\nYour access token has now expired. We will attempt to refresh it\")\n\t}\n\n\tupdatedJWT, err := data.AuthServer.RefreshAccessToken(at.RefreshToken)\n\tif err != nil {\n\t\tif errors.Is(err, auth.ErrInvalidGrant) {\n\t\t\treturn true, nil // refresh token rejected, needs full re-auth\n\t\t}\n\t\treturn false, fmt.Errorf(\"failed to refresh access token: %w\", err)\n\t}\n\n\temail, apiToken, err := data.AuthServer.ValidateAndRetrieveAPIToken(updatedJWT.AccessToken)\n\tif err != nil {\n\t\treturn false, fmt.Errorf(\"failed to validate JWT and retrieve API token: %w\", err)\n\t}\n\n\tnow := time.Now()\n\tat.Token = apiToken.AccessToken\n\tat.Email = email\n\tat.AccessToken = updatedJWT.AccessToken\n\tat.AccessExpiresAt = now.Add(time.Duration(updatedJWT.ExpiresIn) * time.Second).Format(time.RFC3339)\n\n\t// Refresh token may also be rotated.\n\tif at.RefreshToken != updatedJWT.RefreshToken {\n\t\tif data.Flags.Verbose {\n\t\t\ttext.Info(data.Output, \"Your refresh token was also updated\")\n\t\t\ttext.Break(data.Output)\n\t\t}\n\t\tat.RefreshToken = updatedJWT.RefreshToken\n\t\tat.RefreshExpiresAt = now.Add(time.Duration(updatedJWT.RefreshExpiresIn) * time.Second).Format(time.RFC3339)\n\t}\n\n\tauthcmd.EnrichWithTokenSelf(data, at)\n\n\tdata.Config.SetAuthToken(name, at)\n\tif err := data.Config.Write(data.ConfigPath); err != nil {\n\t\tdata.ErrLog.Add(err)\n\t\treturn false, fmt.Errorf(\"error saving config file: %w\", err)\n\t}\n\n\treturn false, nil\n}\n\n// authRelatedCommands lists the top-level command families related to\n// authentication. Expiry warnings are suppressed for these commands to avoid\n// noise during login, token management, and identity flows.\n// This matches the set hidden by FASTLY_DISABLE_AUTH_COMMAND (pkg/env/env.go).\nvar authRelatedCommands = []string{\"auth\", \"auth-token\", \"sso\", \"profile\", \"whoami\"}\n\n// checkTokenExpirationWarning prints a warning to stderr if the active stored\n// token is about to expire. Only fires for SourceAuth tokens; env/flag tokens\n// are opaque. Suppressed for auth-related commands and when --quiet is active.\n// In --json mode the warning still fires (written to stderr via data.ErrOutput).\nfunc checkTokenExpirationWarning(data *global.Data, commandName string) {\n\tif data.Flags.Quiet {\n\t\treturn\n\t}\n\tif isAuthRelatedCommand(commandName) {\n\t\treturn\n\t}\n\n\t_, src := data.Token()\n\tif src != lookup.SourceAuth {\n\t\treturn\n\t}\n\n\tname := data.AuthTokenName()\n\tif name == \"\" {\n\t\tname = data.Config.Auth.Default\n\t}\n\tat := data.Config.GetAuthToken(name)\n\tif at == nil {\n\t\treturn\n\t}\n\n\tstatus, expires, err := authcmd.GetExpirationStatus(at, time.Now())\n\tif err != nil && data.ErrLog != nil {\n\t\tdata.ErrLog.Add(err)\n\t}\n\tif status != authcmd.StatusExpiringSoon {\n\t\treturn\n\t}\n\n\tsummary := authcmd.ExpirationSummary(status, expires, time.Now())\n\tremediation := authcmd.ExpirationRemediation(at.Type)\n\tlabel := \"\"\n\tif at.RefreshExpiresAt != \"\" {\n\t\tlabel = \"session \"\n\t}\n\ttext.Warning(data.ErrOutput, \"Your active token %s%s. %s\\n\", label, summary, remediation)\n}\n\n// isAuthRelatedCommand reports whether commandName belongs to an auth-related\n// command family. Matches both bare commands (\"auth\") and subcommands (\"auth list\").\nfunc isAuthRelatedCommand(commandName string) bool {\n\tfor _, prefix := range authRelatedCommands {\n\t\tif commandName == prefix || strings.HasPrefix(commandName, prefix+\" \") {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\n// ssoAuthentication invokes the SSO runner to handle authentication.\nfunc ssoAuthentication(outputMessage string, data *global.Data, forceReAuth bool) (token string, tokenSource lookup.Source, err error) {\n\tif data.SSORunner == nil {\n\t\treturn \"\", lookup.SourceUndefined, fmt.Errorf(\"SSO runner is not configured\")\n\t}\n\n\tskipPrompt := false\n\tif !data.Flags.AutoYes && !data.Flags.NonInteractive {\n\t\tif data.Verbose() {\n\t\t\ttext.Break(data.Output)\n\t\t}\n\t\ttext.Important(data.Output, \"%s. We need to open your browser to authenticate you.\", outputMessage)\n\t\ttext.Break(data.Output)\n\t\tcont, err := text.AskYesNo(data.Output, text.BoldYellow(\"Do you want to continue? [y/N]: \"), data.Input)\n\t\ttext.Break(data.Output)\n\t\tif err != nil {\n\t\t\treturn token, tokenSource, err\n\t\t}\n\t\tif !cont {\n\t\t\treturn token, tokenSource, fsterr.ErrDontContinue\n\t\t}\n\t\tskipPrompt = true\n\t}\n\n\tif err := data.SSORunner(data.Input, data.Output, forceReAuth, skipPrompt); err != nil {\n\t\treturn token, tokenSource, fmt.Errorf(\"failed to authenticate: %w\", err)\n\t}\n\ttext.Break(data.Output)\n\n\ttoken, tokenSource = data.Token()\n\tif tokenSource == lookup.SourceUndefined {\n\t\treturn token, tokenSource, fsterr.ErrNoToken()\n\t}\n\treturn token, tokenSource, nil\n}\n\nfunc promptForAuth(data *global.Data) (string, lookup.Source, error) {\n\ttext.Important(data.Output, \"This command requires authentication to access your Fastly account.\")\n\ttext.Break(data.Output)\n\tif !env.AuthCommandDisabled() {\n\t\ttext.Output(data.Output, \"If you prefer SSO, run: fastly auth login --sso --token <name>\\n\")\n\t}\n\ttext.Output(data.Output, \"Otherwise, paste an API token now. It will be stored as your default auth token.\\n\")\n\tif env.AuthCommandDisabled() {\n\t\ttext.Output(data.Output, \"You can also set %s.\\n\", env.APIToken)\n\t} else {\n\t\ttext.Output(data.Output, \"You can also pass --token or set %s.\\n\", env.APIToken)\n\t}\n\ttext.Output(data.Output, \"An API token can be generated at: https://manage.fastly.com/account/personal/tokens\\n\")\n\ttext.Output(data.Output, \"Learn more: fastly.help/cli/cli-auth\\n\\n\")\n\n\ttoken, err := text.InputSecure(data.Output, \"Paste your API token: \", data.Input)\n\tif err != nil {\n\t\treturn \"\", lookup.SourceUndefined, fmt.Errorf(\"error reading token input: %w\", err)\n\t}\n\tif token == \"\" {\n\t\treturn \"\", lookup.SourceUndefined, fsterr.ErrNoToken()\n\t}\n\n\tname, md, err := authcmd.StoreStaticToken(data, token)\n\tif err != nil {\n\t\treturn \"\", lookup.SourceUndefined, err\n\t}\n\n\ttext.Success(data.Output, \"Authenticated as %s (token stored as %q)\", md.Email, name)\n\ttext.Info(data.Output, \"Token saved to %s\", data.ConfigPath)\n\treturn token, lookup.SourceAuth, nil\n}\n\nfunc displayToken(tokenSource lookup.Source, data *global.Data) {\n\tswitch tokenSource {\n\tcase lookup.SourceFlag:\n\t\tfmt.Fprintf(data.Output, \"Fastly API token provided via --token\\n\\n\")\n\tcase lookup.SourceEnvironment:\n\t\tfmt.Fprintf(data.Output, \"Fastly API token provided via %s\\n\\n\", env.APIToken)\n\tcase lookup.SourceAuth:\n\t\tname := data.AuthTokenName()\n\t\tif name != \"\" {\n\t\t\tfmt.Fprintf(data.Output, \"Fastly API token provided via config file (auth: %s)\\n\\n\", name)\n\t\t} else {\n\t\t\tfmt.Fprintf(data.Output, \"Fastly API token provided via config file (auth)\\n\\n\")\n\t\t}\n\tcase lookup.SourceUndefined, lookup.SourceDefault, lookup.SourceFile:\n\t\tfallthrough\n\tdefault:\n\t\tfmt.Fprintf(data.Output, \"Fastly API token not provided\\n\\n\")\n\t}\n}\n\n// If we are using the token from config file, check the file's permissions\n// to assert if they are not too open or have been altered outside of the\n// application and warn if so.\nfunc checkConfigPermissions(tokenSource lookup.Source, out io.Writer) {\n\tif tokenSource == lookup.SourceAuth {\n\t\tif fi, err := os.Stat(config.FilePath); err == nil {\n\t\t\tif mode := fi.Mode().Perm(); mode > config.FilePermissions {\n\t\t\t\ttext.Warning(out, \"Unprotected configuration file.\\n\\n\")\n\t\t\t\ttext.Output(out, \"Permissions for '%s' are too open\\n\\n\", config.FilePath)\n\t\t\t\ttext.Output(out, \"It is recommended that your configuration file is NOT accessible by others.\\n\\n\")\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc displayAPIEndpoint(endpoint string, endpointSource lookup.Source, out io.Writer) {\n\tswitch endpointSource {\n\tcase lookup.SourceFlag:\n\t\tfmt.Fprintf(out, \"Fastly API endpoint (via --api): %s\\n\", endpoint)\n\tcase lookup.SourceEnvironment:\n\t\tfmt.Fprintf(out, \"Fastly API endpoint (via %s): %s\\n\", env.APIEndpoint, endpoint)\n\tcase lookup.SourceFile:\n\t\tfmt.Fprintf(out, \"Fastly API endpoint (via config file): %s\\n\", endpoint)\n\tcase lookup.SourceDefault, lookup.SourceUndefined, lookup.SourceAuth:\n\t\tfallthrough\n\tdefault:\n\t\tfmt.Fprintf(out, \"Fastly API endpoint: %s\\n\", endpoint)\n\t}\n}\n\nfunc configureClients(token, apiEndpoint string, acf global.APIClientFactory, debugMode bool) (apiClient api.Interface, rtsClient api.RealtimeStatsInterface, err error) {\n\tapiClient, err = acf(token, apiEndpoint, debugMode)\n\tif err != nil {\n\t\treturn nil, nil, fmt.Errorf(\"error constructing Fastly API client: %w\", err)\n\t}\n\n\trtsClient, err = fastly.NewRealtimeStatsClientForEndpoint(token, fastly.DefaultRealtimeStatsEndpoint)\n\tif err != nil {\n\t\treturn nil, nil, fmt.Errorf(\"error constructing Fastly realtime stats client: %w\", err)\n\t}\n\n\treturn apiClient, rtsClient, nil\n}\n\nfunc checkForUpdates(av github.AssetVersioner, commandName string) func(io.Writer) {\n\tif av != nil && commandName != \"update\" && !version.IsPreRelease(revision.AppVersion) {\n\t\treturn update.CheckAsync(revision.AppVersion, av)\n\t}\n\treturn func(_ io.Writer) {\n\t\t// no-op\n\t}\n}\n\n// commandCollectsData determines if the command to be executed is one that\n// collects data related to a Wasm binary.\nfunc commandCollectsData(command string) bool {\n\tswitch command {\n\tcase \"compute build\", \"compute hash-files\", \"compute publish\", \"compute serve\":\n\t\treturn true\n\t}\n\treturn false\n}\n\n// commandRequiresAuthServer determines if the command to be executed is one that\n// requires just the authentication server to be running.\nfunc commandRequiresAuthServer(command string, args []string) bool {\n\tswitch command {\n\tcase \"auth login\":\n\t\treturn slices.Contains(args, \"--sso\")\n\tcase \"profile create\", \"profile switch\", \"profile update\", \"sso\":\n\t\treturn true\n\t}\n\treturn false\n}\n\n// commandRequiresToken determines if the command to be executed is one that\n// requires an API token.\nfunc commandRequiresToken(command argparser.Command) bool {\n\tcommandName := command.Name()\n\tswitch commandName {\n\tcase \"compute init\":\n\t\tif initCmd, ok := command.(*compute.InitCommand); ok {\n\t\t\treturn text.IsFastlyID(initCmd.CloneFrom)\n\t\t}\n\t\treturn false\n\tcase \"compute build\", \"compute hash-files\", \"compute metadata\", \"compute pack\", \"compute serve\", \"compute validate\":\n\t\treturn false\n\t}\n\tcommandName = strings.Split(commandName, \" \")[0]\n\tswitch commandName {\n\tcase \"auth\", \"config\", \"install\", \"profile\", \"sso\", \"update\", \"version\":\n\t\treturn false\n\t}\n\treturn true\n}\n\n// configureAuth processes authentication tasks.\n//\n// 1. Acquire .well-known configuration data.\n// 2. Instantiate authentication server.\n// 3. Start up request multiplexer.\nfunc configureAuth(apiEndpoint string, args []string, f config.File, c api.HTTPClient, e config.Environment) (*auth.Server, error) {\n\tmetadataEndpoint := fmt.Sprintf(auth.OIDCMetadata, accountEndpoint(args, e, f))\n\treq, err := http.NewRequest(http.MethodGet, metadataEndpoint, nil)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to construct request object for OpenID Connect .well-known metadata: %w\", err)\n\t}\n\n\tresp, err := c.Do(req)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to request OpenID Connect .well-known metadata (%s): %w\", metadataEndpoint, err)\n\t}\n\t// Set a more meaningful error message when Fastly servers are unresponsive\n\t// check if the response code is a 500 or above\n\tif resp.StatusCode >= http.StatusInternalServerError {\n\t\tvar body []byte\n\t\tbody, _ = io.ReadAll(resp.Body) // default to empty string if we fail to read the body\n\t\treturn nil, fmt.Errorf(\"the Fastly servers are unresponsive, please check the Fastly Status page (https://fastlystatus.com) and reach out to support if the error persists (HTTP Status Code: %d, Error Message: %s)\", resp.StatusCode, body)\n\t}\n\n\topenIDConfig, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to read OpenID Connect .well-known metadata: %w\", err)\n\t}\n\t_ = resp.Body.Close()\n\n\tvar wellknown auth.WellKnownEndpoints\n\terr = json.Unmarshal(openIDConfig, &wellknown)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to unmarshal OpenID Connect .well-known metadata: %w\", err)\n\t}\n\n\tresult := make(chan auth.AuthorizationResult)\n\trouter := http.NewServeMux()\n\tverifier, err := oidc.NewCodeVerifier()\n\tif err != nil {\n\t\treturn nil, fsterr.RemediationError{\n\t\t\tInner:       fmt.Errorf(\"failed to generate a code verifier for SSO authentication server: %w\", err),\n\t\t\tRemediation: auth.Remediation,\n\t\t}\n\t}\n\n\tauthServer := &auth.Server{\n\t\tAPIEndpoint:        apiEndpoint,\n\t\tDebugMode:          e.DebugMode,\n\t\tHTTPClient:         c,\n\t\tResult:             result,\n\t\tRouter:             router,\n\t\tVerifier:           verifier,\n\t\tWellKnownEndpoints: wellknown,\n\t}\n\n\trouter.HandleFunc(\"/callback\", authServer.HandleCallback())\n\n\treturn authServer, nil\n}\n\n// accountEndpoint parses the account endpoint from multiple locations.\nfunc accountEndpoint(args []string, e config.Environment, cfg config.File) string {\n\t// Check for flag override.\n\tfor i, a := range args {\n\t\tif a == \"--account\" && i+1 < len(args) {\n\t\t\treturn args[i+1]\n\t\t}\n\t}\n\t// Check for environment override.\n\tif e.AccountEndpoint != \"\" {\n\t\treturn e.AccountEndpoint\n\t}\n\t// Check for internal config override.\n\tif cfg.Fastly.AccountEndpoint != global.DefaultAccountEndpoint && cfg.Fastly.AccountEndpoint != \"\" {\n\t\treturn cfg.Fastly.AccountEndpoint\n\t}\n\t// Otherwise return the default account endpoint.\n\treturn global.DefaultAccountEndpoint\n}\n\n// commandSuppressesVerbose checks if the given command suppresses verbose output.\n// This uses type assertion to check if the command has an embedded Base struct with SuppressVerbose set.\nfunc commandSuppressesVerbose(command argparser.Command) bool {\n\t// Try to access the SuppressesVerbose method which is available on commands that embed argparser.Base\n\ttype verboseSuppressor interface {\n\t\tSuppressesVerbose() bool\n\t}\n\tif vs, ok := command.(verboseSuppressor); ok {\n\t\treturn vs.SuppressesVerbose()\n\t}\n\n\treturn false\n}\n"
  },
  {
    "path": "pkg/app/run_test.go",
    "content": "package app_test\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"io\"\n\t\"os\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/app\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/mock\"\n\t\"github.com/fastly/cli/pkg/revision\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n)\n\n// If you add a Short flag and this test starts failing, it could be due to the same short flag existing at the global level.\nfunc TestShellCompletion(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName: \"bash shell complete\",\n\t\t\tArgs: \"--completion-script-bash\",\n\t\t\tWantOutput: `\n_fastly_bash_autocomplete() {\n    local cur prev opts base\n    COMPREPLY=()\n    cur=\"${COMP_WORDS[COMP_CWORD]}\"\n    opts=$( ${COMP_WORDS[0]} --completion-bash ${COMP_WORDS[@]:1:$COMP_CWORD} )\n    COMPREPLY=( $(compgen -W \"${opts}\" -- ${cur}) )\n    return 0\n}\ncomplete -F _fastly_bash_autocomplete fastly\n\n`,\n\t\t},\n\t\t{\n\t\t\tName: \"zsh shell complete\",\n\t\t\tArgs: \"--completion-script-zsh\",\n\t\t\tWantOutput: `\n#compdef fastly\nautoload -U compinit && compinit\nautoload -U bashcompinit && bashcompinit\n\n_fastly_bash_autocomplete() {\n    local cur prev opts base\n    COMPREPLY=()\n    cur=\"${COMP_WORDS[COMP_CWORD]}\"\n    opts=$( ${COMP_WORDS[0]} --completion-bash ${COMP_WORDS[@]:1:$COMP_CWORD} )\n    COMPREPLY=( $(compgen -W \"${opts}\" -- ${cur}) )\n    [[ $COMPREPLY ]] && return\n    compgen -f\n    return 0\n}\ncomplete -F _fastly_bash_autocomplete fastly\n`,\n\t\t},\n\t\t{\n\t\t\tName: \"shell evaluate completion options\",\n\t\t\tArgs: \"--completion-bash\",\n\t\t\tWantOutput: `help\nauth\napisecurity\ncompute\nconfig\nconfig-store\nconfig-store-entry\ndashboard\ndomain\ninstall\nip-list\nkv-store\nkv-store-entry\nlog-tail\nngwaf\nobject-storage\npops\nproducts\nsecret-store\nsecret-store-entry\nservice\nstats\ntls-config\ntls-custom\ntls-platform\ntls-subscription\ntools\nupdate\nuser\nversion\nwhoami\n`,\n\t\t},\n\t}\n\tfor testcaseIdx := range scenarios {\n\t\ttestcase := &scenarios[testcaseIdx]\n\t\tt.Run(testcase.Name, func(t *testing.T) {\n\t\t\tvar (\n\t\t\t\tstdout bytes.Buffer\n\t\t\t\tstderr bytes.Buffer\n\t\t\t)\n\n\t\t\t// NOTE: The Kingpin dependency internally overrides our stdout\n\t\t\t// variable when doing shell completion to the os.Stdout variable and so\n\t\t\t// in order for us to verify it contains the shell completion output, we\n\t\t\t// need an os.Pipe so we can copy off anything written to os.Stdout.\n\t\t\told := os.Stdout\n\t\t\tr, w, _ := os.Pipe()\n\t\t\tos.Stdout = w\n\t\t\toutC := make(chan string)\n\n\t\t\tgo func() {\n\t\t\t\tvar buf bytes.Buffer\n\t\t\t\t_, _ = io.Copy(&buf, r)\n\t\t\t\toutC <- buf.String()\n\t\t\t}()\n\n\t\t\tapp.Init = func(_ []string, _ io.Reader) (*global.Data, error) {\n\t\t\t\treturn testutil.MockGlobalData(testutil.SplitArgs(testcase.Args), &stdout), nil\n\t\t\t}\n\t\t\terr := app.Run(testutil.SplitArgs(testcase.Args), nil)\n\t\t\tif err != nil {\n\t\t\t\terrors.Deduce(err).Print(&stderr)\n\t\t\t}\n\n\t\t\tw.Close()\n\t\t\tos.Stdout = old\n\t\t\tout := <-outC\n\n\t\t\ttestutil.AssertString(t, testcase.WantOutput, stripTrailingSpace(out))\n\t\t})\n\t}\n}\n\n// TestExecQuietSuppressesExpiryWarning exercises the full Exec path to verify\n// that --quiet suppresses the expiration warning end-to-end.\nfunc TestExecQuietSuppressesExpiryWarning(t *testing.T) {\n\tvar stdout bytes.Buffer\n\n\targs := testutil.SplitArgs(\"config -l --quiet\")\n\tapp.Init = func(_ []string, _ io.Reader) (*global.Data, error) {\n\t\tdata := testutil.MockGlobalData(args, &stdout)\n\t\t// Set the default token to expire soon so a warning would fire without --quiet.\n\t\tdata.Config.Auth.Tokens[\"user\"].APITokenExpiresAt = time.Now().Add(20 * time.Minute).Format(time.RFC3339)\n\t\treturn data, nil\n\t}\n\terr := app.Run(args, nil)\n\tif err != nil {\n\t\tt.Fatalf(\"app.Run returned unexpected error: %v\", err)\n\t}\n\n\toutput := stdout.String()\n\tif strings.Contains(output, \"expires in\") {\n\t\tt.Errorf(\"--quiet should suppress expiry warning, but got: %s\", output)\n\t}\n}\n\n// TestExecConfigShowsExpiryWarning is a companion test verifying the warning\n// does appear for a non-quiet, non-auth command when the token is expiring.\nfunc TestExecConfigShowsExpiryWarning(t *testing.T) {\n\tvar stdout bytes.Buffer\n\n\targs := testutil.SplitArgs(\"config -l\")\n\tapp.Init = func(_ []string, _ io.Reader) (*global.Data, error) {\n\t\tdata := testutil.MockGlobalData(args, &stdout)\n\t\tdata.Config.Auth.Tokens[\"user\"].APITokenExpiresAt = time.Now().Add(20 * time.Minute).Format(time.RFC3339)\n\t\treturn data, nil\n\t}\n\terr := app.Run(args, nil)\n\tif err != nil {\n\t\tt.Fatalf(\"app.Run returned unexpected error: %v\", err)\n\t}\n\n\toutput := stdout.String()\n\tif !strings.Contains(output, \"expires in\") {\n\t\tt.Errorf(\"expected expiry warning for config command, got: %s\", output)\n\t}\n}\n\n// TestExecJSONLeavesStdoutCleanAndWritesWarningToStderr verifies that in\n// --json mode, the expiry warning is written to stderr (not stdout) so it\n// does not corrupt JSON output. Because the config command does not register\n// --json as a flag, we simulate the effect by pre-setting Flags.JSON (which\n// is what Exec does when it sees --json in the args).\nfunc TestExecJSONLeavesStdoutCleanAndWritesWarningToStderr(t *testing.T) {\n\tvar (\n\t\tstdout bytes.Buffer\n\t\tstderr bytes.Buffer\n\t)\n\n\targs := testutil.SplitArgs(\"config -l\")\n\tapp.Init = func(_ []string, _ io.Reader) (*global.Data, error) {\n\t\tdata := testutil.MockGlobalData(args, &stdout)\n\t\tdata.ErrOutput = &stderr\n\t\tdata.Flags.JSON = true\n\t\tdata.Config.Auth.Tokens[\"user\"].APITokenExpiresAt = time.Now().Add(20 * time.Minute).Format(time.RFC3339)\n\t\treturn data, nil\n\t}\n\terr := app.Run(args, nil)\n\tif err != nil {\n\t\tt.Fatalf(\"app.Run returned unexpected error: %v\", err)\n\t}\n\n\tif strings.Contains(stdout.String(), \"expires in\") {\n\t\tt.Errorf(\"expected stdout free of expiry warning, got: %s\", stdout.String())\n\t}\n\tif !strings.Contains(stderr.String(), \"expires in\") {\n\t\tt.Errorf(\"expected expiry warning on stderr, got: %s\", stderr.String())\n\t}\n}\n\n// TestStatsJSONSuppressesUpdateNotice verifies that --json and --format=json on\n// stats commands suppress the deferred update-check notice, keeping stdout as\n// clean JSON. This is the regression test for the timing bug where\n// data.Flags.JSON was set inside Exec but the update check captured the flag\n// value before Exec ran.\nfunc TestStatsJSONSuppressesUpdateNotice(t *testing.T) {\n\torigVersion := revision.AppVersion\n\trevision.AppVersion = \"0.0.1\"\n\tt.Cleanup(func() { revision.AppVersion = origVersion })\n\n\taggregateOK := func(_ context.Context, _ *fastly.GetAggregateInput, o any) error {\n\t\tmsg := []byte(`{\"status\":\"success\",\"meta\":{},\"msg\":null,\"data\":[{\"start_time\":0}]}`)\n\t\treturn json.Unmarshal(msg, o)\n\t}\n\n\tfor _, flag := range []string{\"--json\", \"--format=json\"} {\n\t\tt.Run(flag, func(t *testing.T) {\n\t\t\tvar stdout bytes.Buffer\n\t\t\targs := testutil.SplitArgs(\"stats aggregate \" + flag)\n\t\t\tapp.Init = func(_ []string, _ io.Reader) (*global.Data, error) {\n\t\t\t\tdata := testutil.MockGlobalData(args, &stdout)\n\t\t\t\tdata.APIClientFactory = mock.APIClient(mock.API{\n\t\t\t\t\tGetAggregateJSONFn: aggregateOK,\n\t\t\t\t})\n\t\t\t\tdata.Versioners.CLI = mock.AssetVersioner{AssetVersion: \"99.0.0\"}\n\t\t\t\treturn data, nil\n\t\t\t}\n\t\t\terr := app.Run(args, nil)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"app.Run returned unexpected error: %v\", err)\n\t\t\t}\n\t\t\tif strings.Contains(stdout.String(), \"new version\") {\n\t\t\t\tt.Errorf(\"update notice should be suppressed in JSON mode, got: %s\", stdout.String())\n\t\t\t}\n\t\t})\n\t}\n}\n\n// TestHelpJSON verifies that `help --json` takes the same early-exit path as\n// `help --format=json`.\nfunc TestHelpJSON(t *testing.T) {\n\tfor _, flag := range []string{\"--json\", \"--format=json\", \"--format json\"} {\n\t\tt.Run(flag, func(t *testing.T) {\n\t\t\tvar stdout bytes.Buffer\n\t\t\targs := testutil.SplitArgs(\"help \" + flag)\n\t\t\tapp.Init = func(_ []string, _ io.Reader) (*global.Data, error) {\n\t\t\t\treturn testutil.MockGlobalData(args, &stdout), nil\n\t\t\t}\n\t\t\terr := app.Run(args, nil)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"app.Run returned unexpected error: %v\", err)\n\t\t\t}\n\t\t\tif !strings.Contains(stdout.String(), `\"commands\"`) {\n\t\t\t\tt.Errorf(\"expected JSON usage output containing \\\"commands\\\", got: %s\", stdout.String())\n\t\t\t}\n\t\t})\n\t}\n}\n\n// stripTrailingSpace removes any trailing spaces from the multiline str.\nfunc stripTrailingSpace(str string) string {\n\tbuf := bytes.NewBuffer(nil)\n\n\tscan := bufio.NewScanner(strings.NewReader(str))\n\tfor scan.Scan() {\n\t\t_, _ = buf.WriteString(strings.TrimRight(scan.Text(), \" \\t\\r\\n\"))\n\t\t_, _ = buf.WriteString(\"\\n\")\n\t}\n\treturn buf.String()\n}\n"
  },
  {
    "path": "pkg/app/usage.go",
    "content": "package app\n\nimport (\n\t\"bytes\"\n\t_ \"embed\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"strings\"\n\t\"text/template\"\n\n\t\"github.com/fastly/kingpin\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/env\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// Usage returns a contextual usage string for the application. In order to deal\n// with Kingpin's annoying love of side effects, we have to swap the app.Writers\n// to capture output; the out and err parameters, therefore, are the io.Writers\n// re-assigned to the app via app.Writers after calling Usage.\nfunc Usage(args []string, app *kingpin.Application, out, err io.Writer, vars map[string]any) string {\n\tvar buf bytes.Buffer\n\tapp.Writers(&buf, io.Discard)\n\tapp.UsageContext(&kingpin.UsageContext{\n\t\tTemplate: CompactUsageTemplate,\n\t\tFuncs:    UsageTemplateFuncs,\n\t\tVars:     vars,\n\t})\n\tapp.Usage(args)\n\tapp.Writers(out, err)\n\treturn buf.String()\n}\n\n// authGuideTemplate is the template fragment for the auth guide section,\n// shared between CompactUsageTemplate and VerboseUsageTemplate.\nconst authGuideTemplate = `{{if .Context.SelectedCommand -}}\n{{if eq .Context.SelectedCommand.Name \"auth\" -}}\n{{if .Context.SelectedCommand.Commands -}}\n{{T \"AUTH GUIDE\"|Bold}}\n  Quick start:\n    fastly auth login\n    fastly auth login --sso --token <name>\n  Token precedence:\n    --token (raw or stored name) > FASTLY_API_TOKEN > fastly.toml profile > default auth token\n  Stored tokens:\n    fastly auth list\n    fastly auth use <name>\n  Non-interactive usage:\n    fastly service list --token $TOKEN\n\n{{end -}}\n{{end -}}\n{{end -}}\n`\n\n// CompactUsageTemplate is the default usage template, rendered when users type\n// e.g. just `fastly`, or use the `-h, --help` flag.\nvar CompactUsageTemplate = `{{define \"FormatCommand\" -}}\n{{if .FlagSummary}} {{.FlagSummary}}{{end -}}\n{{range .Args}} {{if not .Required}}[{{end}}<{{.Name}}>{{if .Value|IsCumulative}} ...{{end}}{{if not .Required}}]{{end}}{{end -}}\n{{end -}}\n{{define \"FormatCommandList\" -}}\n{{range . -}}\n{{if not .Hidden -}}\n{{.Depth|Indent}}{{.Name}}{{if .Default}}*{{end}}{{template \"FormatCommand\" .}}\n{{end -}}\n{{template \"FormatCommandList\" .Commands -}}\n{{end -}}\n{{end -}}\n{{define \"FormatUsage\" -}}\n{{template \"FormatCommand\" .}}{{if .Commands}} <command> [<args> ...]{{end}}\n{{if .Help}}\n{{.Help|Wrap 0 -}}\n{{end -}}\n{{end -}}\n{{define \"FormatCommandName\" -}}\n{{if .Parent}}{{if .Parent.Parent}}{{.Parent.Parent.Name}} {{end -}}{{.Parent.Name}} {{end -}}{{.Name -}}\n{{end -}}\n{{if .Context.SelectedCommand -}}\n{{T \"USAGE\"|Bold}}\n  {{.App.Name}} {{template \"FormatCommandName\" .Context.SelectedCommand}}{{ template \"FormatUsage\" .Context.SelectedCommand}}\n{{else -}}\n{{T \"USAGE\"|Bold}}\n  {{.App.Name}}{{template \"FormatUsage\" .App}}\n{{end -}}\n{{if .Context.Flags|RequiredFlags -}}\n{{T \"REQUIRED FLAGS\"|Bold}}\n{{.Context.Flags|RequiredFlags|FlagsToTwoColumns|FormatTwoColumns}}\n{{end -}}\n{{if .Context.Flags|OptionalFlags -}}\n{{T \"OPTIONAL FLAGS\"|Bold}}\n{{.Context.Flags|OptionalFlags|FlagsToTwoColumns|FormatTwoColumns}}\n{{end -}}\n{{if .Context.Flags|GlobalFlags -}}\n{{T \"GLOBAL FLAGS\"|Bold}}\n{{.Context.Flags|GlobalFlags|FlagsToTwoColumns|FormatTwoColumns}}\n{{end -}}\n{{if .Context.Args -}}\n{{T \"ARGS\"|Bold}}\n{{.Context.Args|ArgsToTwoColumns|FormatTwoColumns}}\n{{end -}}\n{{if .Context.SelectedCommand -}}\n{{if .Context.SelectedCommand.Commands -}}\n{{T \"COMMANDS\"|Bold}}\n  {{.Context.SelectedCommand}}\n{{.Context.SelectedCommand.Commands|CommandsToTwoColumns|FormatTwoColumns}}\n{{end -}}\n{{else if .App.Commands -}}\n{{T \"COMMANDS\"|Bold}}\n{{.App.Commands|CommandsToTwoColumns|FormatTwoColumns}}\n{{end -}}\n` + authGuideTemplate + `\n{{T \"SEE ALSO\"|Bold}}\n{{.Context.SelectedCommand|SeeAlso}}\n`\n\n// UsageTemplateFuncs is a map of template functions which get passed to the\n// usage template renderer.\nvar UsageTemplateFuncs = template.FuncMap{\n\t\"CommandsToTwoColumns\": func(c []*kingpin.CmdModel) [][2]string {\n\t\trows := [][2]string{}\n\t\tfor _, cmd := range c {\n\t\t\tif !cmd.Hidden {\n\t\t\t\trows = append(rows, [2]string{cmd.Name, cmd.Help})\n\t\t\t}\n\t\t}\n\t\treturn rows\n\t},\n\t\"GlobalFlags\": func(f []*kingpin.ClauseModel) []*kingpin.ClauseModel {\n\t\tgf := globalFlags()\n\t\tflags := []*kingpin.ClauseModel{}\n\t\tfor _, flag := range f {\n\t\t\tif gf[flag.Name] {\n\t\t\t\tflags = append(flags, flag)\n\t\t\t}\n\t\t}\n\t\treturn flags\n\t},\n\t\"OptionalFlags\": func(f []*kingpin.ClauseModel) []*kingpin.ClauseModel {\n\t\tgf := globalFlags()\n\t\toptionalFlags := []*kingpin.ClauseModel{}\n\t\tfor _, flag := range f {\n\t\t\tif !flag.Required && !flag.Hidden && !gf[flag.Name] {\n\t\t\t\toptionalFlags = append(optionalFlags, flag)\n\t\t\t}\n\t\t}\n\t\treturn optionalFlags\n\t},\n\t\"Bold\": func(s string) string {\n\t\treturn text.Bold(s)\n\t},\n\t\"SeeAlso\": func(cm *kingpin.CmdModel) string {\n\t\tcmd := cm.FullCommand()\n\t\turl := \"https://www.fastly.com/documentation/reference/cli/\"\n\t\tvar trail string\n\t\tif len(cmd) > 0 {\n\t\t\ttrail = \"/\"\n\t\t}\n\t\treturn fmt.Sprintf(\"  %s%s%s\", url, strings.ReplaceAll(cmd, \" \", \"/\"), trail)\n\t},\n}\n\n// IMPORTANT: Kingpin doesn't support global flags.\n// We hack a solution in ./run.go (`configureKingpin` function).\n//\n// NOTE: This map is used to help populate the CLI 'usage' template renderer.\nfunc globalFlags() map[string]bool {\n\tm := map[string]bool{\n\t\t\"accept-defaults\": true,\n\t\t\"account\":         true,\n\t\t\"auto-yes\":        true,\n\t\t\"debug-mode\":      true,\n\t\t\"endpoint\":        true,\n\t\t\"help\":            true,\n\t\t\"non-interactive\": true,\n\t\t\"quiet\":           true,\n\t\t\"verbose\":         true,\n\t}\n\tif !env.AuthCommandDisabled() {\n\t\tm[\"token\"] = true\n\t}\n\treturn m\n}\n\n// VerboseUsageTemplate is the full-fat usage template, rendered when users type\n// the long-form e.g. `fastly help service`.\nconst VerboseUsageTemplate = `{{define \"FormatCommands\" -}}\n{{range .FlattenedCommands -}}\n{{ if not .Hidden }}\n  {{.CmdSummary|Bold }}\n{{.Help|Wrap 4 }}\n{{if .Flags -}}\n{{with .Flags|FlagsToTwoColumns}}{{FormatTwoColumnsWithIndent . 4 2}}{{end -}}\n{{end -}}\n{{end -}}\n{{end -}}\n{{end -}}\n{{define \"FormatUsage\" -}}\n{{.AppSummary}}\n{{if .Help}}\n{{.Help|Wrap 0 -}}\n{{end -}}\n{{end -}}\n{{if .Context.SelectedCommand -}}\n{{T \"USAGE\"|Bold}}\n  {{.App.Name}} {{.App.FlagSummary}} {{.Context.SelectedCommand.CmdSummary}}\n{{else}}\n{{- T \"USAGE\"|Bold}}\n  {{template \"FormatUsage\" .App -}}\n{{end -}}\n{{if .Context.Flags|GlobalFlags }}\n{{T \"GLOBAL FLAGS\"|Bold}}\n{{.Context.Flags|GlobalFlags|FlagsToTwoColumns|FormatTwoColumns}}\n{{end -}}\n{{if .Context.Args -}}\n{{T \"ARGS\"|Bold}}\n{{.Context.Args|ArgsToTwoColumns|FormatTwoColumns}}\n{{end -}}\n{{if .Context.SelectedCommand -}}\n{{if len .Context.SelectedCommand.Commands -}}\n{{T \"SUBCOMMANDS\\n\"|Bold -}}\n  {{ template \"FormatCommands\" .Context.SelectedCommand}}\n{{end -}}\n{{else if .App.Commands -}}\n{{T \"COMMANDS\"|Bold -}}\n  {{template \"FormatCommands\" .App}}\n{{end -}}\n` + authGuideTemplate + `\n{{T \"SEE ALSO\"|Bold}}\n{{.Context.SelectedCommand|SeeAlso}}\n`\n\n// processCommandInput groups together all the logic related to parsing and\n// processing the incoming command request from the user, as well as handling\n// the various places where help output can be displayed.\nfunc processCommandInput(\n\tdata *global.Data,\n\tapp *kingpin.Application,\n\tcommands []argparser.Command,\n) (command argparser.Command, cmdName string, err error) {\n\t// As the `help` command model gets privately added as a side-effect of\n\t// kingpin.Parse, we cannot add the `--format json` flag to the model.\n\t// Therefore, we have to manually parse the args slice here to check for the\n\t// existence of `help --format json`, if present we print usage JSON and\n\t// exit early.\n\tif argparser.ArgsIsHelpJSON(data.Args) {\n\t\tj, err := UsageJSON(app)\n\t\tif err != nil {\n\t\t\tdata.ErrLog.Add(err)\n\t\t\treturn command, cmdName, err\n\t\t}\n\t\tfmt.Fprintf(data.Output, \"%s\", j)\n\t\treturn command, strings.Join(data.Args, \"\"), nil\n\t}\n\n\t// Use partial application to generate help output function.\n\thelp := displayHelp(data.ErrLog, data.Args, app, data.Output, io.Discard)\n\n\t// Handle parse errors and display contextual usage if possible. Due to bugs\n\t// and an obsession for lots of output side-effects in the kingpin.Parse\n\t// logic, we suppress it from writing any usage or errors to the writer by\n\t// swapping the writer with a no-op and then restoring the real writer\n\t// afterwards. This ensures usage text is only written once to the writer\n\t// and gives us greater control over our error formatting.\n\tapp.Writers(io.Discard, io.Discard)\n\n\t// The `vars` variable is passed into our CLI's Usage() function and exposes\n\t// variables to the template used to generate help output.\n\t//\n\t// NOTE: The zero value of a map is nil.\n\t// A nil map has no keys, nor can keys be added until initialised.\n\t//\n\t// TODO: In the future expose some variables for the template to utilise.\n\t// We don't initialise the map currently as there are no variables to expose.\n\t// But it's useful to have it implemented so it's ready to roll when we do.\n\tvar vars map[string]any\n\n\tif argparser.IsVerboseAndQuiet(data.Args) {\n\t\treturn command, cmdName, fsterr.RemediationError{\n\t\t\tInner:       errors.New(\"--verbose and --quiet flag provided\"),\n\t\t\tRemediation: \"Either remove both --verbose and --quiet flags, or one of them.\",\n\t\t}\n\t}\n\n\tif argparser.IsHelpFlagOnly(data.Args) && len(data.Args) == 1 {\n\t\treturn command, cmdName, fsterr.SkipExitError{\n\t\t\tSkip: true,\n\t\t\tErr:  help(vars, nil),\n\t\t}\n\t}\n\n\t// NOTE: We call two similar methods below: ParseContext() and Parse().\n\t//\n\t// We call Parse() because we want the high-level side effect of processing\n\t// the command information, but we call ParseContext() because we require a\n\t// context object separately to identify if the --help flag was passed (this\n\t// isn't possible to do with the Parse() method).\n\t//\n\t// Internally Parse() calls ParseContext(), to help it handle specific\n\t// behaviours such as configuring pre and post conditional behaviours, as well\n\t// as other related settings.\n\t//\n\t// Normally this would mean Parse() could fail because ParseContext() failed,\n\t// which happens if the given command or one of its sub commands are\n\t// unrecognised or if an unrecognised flag is provided, while Parse() can also\n\t// fail if a 'required' flag is missing. But in reality, because we call\n\t// ParseContext() first, it means the Parse() function should only really\n\t// error on things not already caught by ParseContext().\n\t//\n\t// ctx.SelectedCommand will be nil if only a flag like --verbose or -v is\n\t// provided but with no actual command set so we check with IsGlobalFlagsOnly.\n\tnoargs := len(data.Args) == 0\n\tglobalFlagsOnly := argparser.IsGlobalFlagsOnly(data.Args)\n\tctx, err := app.ParseContext(data.Args)\n\tif err != nil && !argparser.IsCompletion(data.Args) || noargs || globalFlagsOnly {\n\t\tif noargs || globalFlagsOnly {\n\t\t\terr = fmt.Errorf(\"command not specified\")\n\t\t}\n\t\treturn command, cmdName, help(vars, err)\n\t}\n\n\tif len(data.Args) == 1 && data.Args[0] == \"--\" {\n\t\treturn command, cmdName, fsterr.RemediationError{\n\t\t\tInner:       errors.New(\"-- is invalid input when not followed by a positional argument\"),\n\t\t\tRemediation: \"If looking for help output try: `fastly help` for full command list or `fastly --help` for command summary.\",\n\t\t}\n\t}\n\n\t// NOTE: `fastly help`, no flags, or only globals, should skip conditional.\n\t//\n\t// This is because the `ctx` variable will be assigned a\n\t// `kingpin.ParseContext` whose `SelectedCommand` will be nil.\n\t//\n\t// Additionally we don't want to use the ctx if dealing with a shell\n\t// completion flag, as that depends on kingpin.Parse() being called, and so\n\t// the `ctx` is otherwise empty.\n\tvar found bool\n\tif !noargs && !globalFlagsOnly && !argparser.IsHelpOnly(data.Args) && !argparser.IsHelpFlagOnly(data.Args) && !argparser.IsCompletion(data.Args) && !argparser.IsCompletionScript(data.Args) {\n\t\tcommand, found = argparser.Select(ctx.SelectedCommand.FullCommand(), commands)\n\t\tif !found {\n\t\t\treturn command, cmdName, help(vars, err)\n\t\t}\n\t}\n\n\tif argparser.ContextHasHelpFlag(ctx) && !argparser.IsHelpFlagOnly(data.Args) {\n\t\treturn command, cmdName, fsterr.SkipExitError{\n\t\t\tSkip: true,\n\t\t\tErr:  help(vars, nil),\n\t\t}\n\t}\n\n\t// NOTE: app.Parse() resets the default values for app.Writers() from\n\t// io.Discard to os.Stdout and os.Stderr, meaning when using a shell\n\t// autocomplete flag we'll not only see the expected output but also a help\n\t// message because the parser has no matching command and so it thinks there\n\t// is an error and prints the help output for us.\n\t//\n\t// The only way I've found to prevent this is by ensuring the arguments\n\t// provided have a valid command along with the flag, for example:\n\t//\n\t// fastly --completion-script-bash acl\n\t//\n\t// But rather than rely on a feature command, we have defined a hidden\n\t// command that we can safely append to the arguments and not have to worry\n\t// about it getting removed accidentally in the future as we now have a test\n\t// to validate the shell autocomplete behaviours.\n\t//\n\t// Lastly, we don't want to append our hidden shellcomplete command if the\n\t// caller passes --completion-bash because adding a command to the arguments\n\t// list in that scenario would cause Kingpin logic to fail (as it expects the\n\t// flag to be used on its own).\n\tif argparser.IsCompletionScript(data.Args) {\n\t\tdata.Args = append(data.Args, \"shellcomplete\")\n\t}\n\n\tcmdName, err = app.Parse(data.Args)\n\tif err != nil {\n\t\treturn command, \"\", help(vars, err)\n\t}\n\n\t// Restore output writers\n\tapp.Writers(data.Output, io.Discard)\n\n\t// Kingpin generates shell completion as a side-effect of kingpin.Parse() so\n\t// we allow it to call os.Exit, only if a completion flag is present.\n\tif argparser.IsCompletion(data.Args) || argparser.IsCompletionScript(data.Args) {\n\t\tapp.Terminate(os.Exit)\n\t\treturn command, \"shell-autocomplete\", nil\n\t}\n\n\t// A side-effect of suppressing app.Parse from writing output is the usage\n\t// isn't printed for the default `help` command. Therefore we capture it\n\t// here by calling Parse, again swapping the Writers. This also ensures the\n\t// larger and more verbose help formatting is used.\n\tif cmdName == \"help\" {\n\t\treturn command, cmdName, fsterr.SkipExitError{\n\t\t\tSkip: true,\n\t\t\tErr: fsterr.RemediationError{\n\t\t\t\tPrefix: useFullHelpOutput(app, data.Args, data.Output).String(),\n\t\t\t},\n\t\t}\n\t}\n\n\t// Catch scenario where user wants to view help with the following format:\n\t// fastly --help <command>\n\tif argparser.IsHelpFlagOnly(data.Args) {\n\t\treturn command, cmdName, fsterr.SkipExitError{\n\t\t\tSkip: true,\n\t\t\tErr:  help(vars, nil),\n\t\t}\n\t}\n\n\treturn command, cmdName, nil\n}\n\nfunc useFullHelpOutput(app *kingpin.Application, args []string, out io.Writer) *bytes.Buffer {\n\tvar buf bytes.Buffer\n\tapp.Writers(&buf, io.Discard)\n\t_, _ = app.Parse(args)\n\tapp.Writers(out, io.Discard)\n\n\t// The full-fat output of `fastly help` should have a hint at the bottom\n\t// for more specific help. Unfortunately I don't know of a better way to\n\t// distinguish `fastly help` from e.g. `fastly help pops` than this check.\n\tif len(args) > 0 && args[len(args)-1] == \"help\" {\n\t\tfmt.Fprintln(&buf, \"\\nFor help on a specific command, try e.g.\")\n\t\tfmt.Fprintln(&buf, \"\")\n\t\tfmt.Fprintln(&buf, \"\\tfastly help compute\")\n\t\tfmt.Fprintln(&buf, \"\\tfastly compute --help\")\n\t\tfmt.Fprintln(&buf, \"\")\n\t}\n\treturn &buf\n}\n\n// metadata is combined into the usage output so the Developer Hub can display\n// additional information about how to use the commands and what APIs they call.\n// e.g. https://www.fastly.com/documentation/reference/cli/vcl/snippet/create/\n//\n//go:embed metadata.json\nvar metadata []byte\n\n// commandsMetadata represents the metadata.json content that will provide extra\n// contextual information.\ntype commandsMetadata map[string]any\n\n// UsageJSON returns a structured representation of the application usage\n// documentation in JSON format. This is useful for machine consumption.\nfunc UsageJSON(app *kingpin.Application) (string, error) {\n\tvar data commandsMetadata\n\terr := json.Unmarshal(metadata, &data)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tusage := &usageJSON{\n\t\tGlobalFlags: getGlobalFlagJSON(app.Model().Flags),\n\t\tCommands:    getCommandJSON(app.Model().Commands, data),\n\t}\n\n\tj, err := json.Marshal(usage)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\treturn string(j), nil\n}\n\ntype usageJSON struct {\n\tGlobalFlags []flagJSON    `json:\"globalFlags\"`\n\tCommands    []commandJSON `json:\"commands\"`\n}\n\ntype flagJSON struct {\n\tName        string `json:\"name\"`\n\tDescription string `json:\"description\"`\n\tPlaceholder string `json:\"placeholder\"`\n\tRequired    bool   `json:\"required\"`\n\tDefault     string `json:\"default\"`\n\tIsBool      bool   `json:\"isBool\"`\n}\n\n// Example represents a metadata.json command example.\ntype Example struct {\n\tCmd         string `json:\"cmd\"`\n\tDescription string `json:\"description,omitempty\"`\n\tTitle       string `json:\"title\"`\n}\n\ntype commandJSON struct {\n\tName        string        `json:\"name\"`\n\tDescription string        `json:\"description\"`\n\tFlags       []flagJSON    `json:\"flags\"`\n\tChildren    []commandJSON `json:\"children\"`\n\tAPIs        []string      `json:\"apis,omitempty\"`\n\tExamples    []Example     `json:\"examples,omitempty\"`\n}\n\nfunc getGlobalFlagJSON(models []*kingpin.ClauseModel) []flagJSON {\n\tvar globalFlags []*kingpin.ClauseModel\n\tfor _, f := range models {\n\t\tif !f.Hidden {\n\t\t\tglobalFlags = append(globalFlags, f)\n\t\t}\n\t}\n\treturn getFlagJSON(globalFlags)\n}\n\nfunc getCommandJSON(models []*kingpin.CmdModel, data commandsMetadata) []commandJSON {\n\tvar cmds []commandJSON\n\tfor _, m := range models {\n\t\tif m.Hidden {\n\t\t\tcontinue\n\t\t}\n\t\tvar cj commandJSON\n\t\tcj.Name = m.Name\n\t\tcj.Description = m.Help\n\t\tcj.Flags = getFlagJSON(m.Flags)\n\t\tcj.Children = getCommandJSON(m.Commands, data)\n\t\tcj.APIs = []string{}\n\t\tcj.Examples = []Example{}\n\n\t\tsegs := strings.Split(m.FullCommand(), \" \")\n\t\tdata := recurse(m.Depth, segs, data)\n\t\tapis, ok := data[\"apis\"]\n\t\tif ok {\n\t\t\tapis, ok := apis.([]any)\n\t\t\tif ok {\n\t\t\t\tfor _, api := range apis {\n\t\t\t\t\ta, ok := api.(string)\n\t\t\t\t\tif ok {\n\t\t\t\t\t\tcj.APIs = append(cj.APIs, a)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\texamples, ok := data[\"examples\"]\n\t\tif ok {\n\t\t\texamples, ok := examples.([]any)\n\t\t\tif ok {\n\t\t\t\tfor _, example := range examples {\n\t\t\t\t\tc := resolveToString(example, \"cmd\")\n\t\t\t\t\td := resolveToString(example, \"description\")\n\t\t\t\t\tt := resolveToString(example, \"title\")\n\t\t\t\t\tif c != \"\" && t != \"\" {\n\t\t\t\t\t\tcj.Examples = append(cj.Examples, Example{\n\t\t\t\t\t\t\tCmd:         c,\n\t\t\t\t\t\t\tDescription: d,\n\t\t\t\t\t\t\tTitle:       t,\n\t\t\t\t\t\t})\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tcmds = append(cmds, cj)\n\t}\n\treturn cmds\n}\n\n// recurse simplifies the tree style traversal of a complex map.\n//\n// NOTE: The `n` arg represents the number of CLI arguments. For example,\n// with `logging kafka create`, the initial function call would be passed n=3.\n// The `segs` arg represents the CLI arguments. While `data` is the map data\n// structure populated from the metadata.json file.\n//\n// Each recursive call not only decrements the `n` counter but also removes the\n// previous CLI arg, so `segs` becomes shorter on each iteration.\nfunc recurse(n int, segs []string, data commandsMetadata) commandsMetadata {\n\tif n == 0 {\n\t\treturn data\n\t}\n\tvalue, ok := data[segs[0]]\n\tif ok {\n\t\tvalue, ok := value.(map[string]any)\n\t\tif ok {\n\t\t\treturn recurse(n-1, segs[1:], value)\n\t\t}\n\t}\n\treturn nil\n}\n\n// resolveToString extracts a value from a map as a string.\nfunc resolveToString(i any, key string) string {\n\tm, ok := i.(map[string]any)\n\tif ok {\n\t\tv, ok := m[key]\n\t\tif ok {\n\t\t\tv, ok := v.(string)\n\t\t\tif ok {\n\t\t\t\treturn v\n\t\t\t}\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc getFlagJSON(models []*kingpin.ClauseModel) []flagJSON {\n\tvar flags []flagJSON\n\tfor _, m := range models {\n\t\tif m.Hidden {\n\t\t\tcontinue\n\t\t}\n\t\tvar flag flagJSON\n\t\tflag.Name = m.Name\n\t\tflag.Description = m.Help\n\t\tflag.Placeholder = m.PlaceHolder\n\t\tflag.Required = m.Required\n\t\tflag.Default = strings.Join(m.Default, \",\")\n\t\tflag.IsBool = m.IsBoolFlag()\n\t\tflags = append(flags, flag)\n\t}\n\treturn flags\n}\n\n// displayHelp returns a function that prints the help output for a command or\n// command set.\n//\n// NOTE: This function is called multiple times within app.Run() and so we use\n// a closure to prevent having to pass the same unchanging arguments each time.\nfunc displayHelp(\n\terrLog fsterr.LogInterface,\n\targs []string,\n\tapp *kingpin.Application,\n\tstdout, stderr io.Writer,\n) func(vars map[string]any, err error) error {\n\treturn func(vars map[string]any, err error) error {\n\t\tusage := Usage(args, app, stdout, stderr, vars)\n\t\tremediation := fsterr.RemediationError{Prefix: usage}\n\t\tif err != nil {\n\t\t\terrLog.Add(err)\n\t\t\tremediation.Inner = fmt.Errorf(\"error parsing arguments: %w\", err)\n\t\t}\n\t\treturn remediation\n\t}\n}\n"
  },
  {
    "path": "pkg/app/usage_auth_help_test.go",
    "content": "package app_test\n\nimport (\n\t\"bytes\"\n\tstderrors \"errors\"\n\t\"io\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/fastly/cli/pkg/app\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n)\n\nfunc TestAuthGuideBlock(t *testing.T) {\n\tcases := []struct {\n\t\tname      string\n\t\targs      string\n\t\twantGuide bool\n\t}{\n\t\t{\n\t\t\tname:      \"auth --help includes AUTH GUIDE\",\n\t\t\targs:      \"auth --help\",\n\t\t\twantGuide: true,\n\t\t},\n\t\t{\n\t\t\tname:      \"auth login --help excludes AUTH GUIDE\",\n\t\t\targs:      \"auth login --help\",\n\t\t\twantGuide: false,\n\t\t},\n\t\t{\n\t\t\tname:      \"help auth includes AUTH GUIDE\",\n\t\t\targs:      \"help auth\",\n\t\t\twantGuide: true,\n\t\t},\n\t\t{\n\t\t\tname:      \"service --help excludes AUTH GUIDE\",\n\t\t\targs:      \"service --help\",\n\t\t\twantGuide: false,\n\t\t},\n\t}\n\n\tfor _, tc := range cases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tvar stdout bytes.Buffer\n\t\t\targs := testutil.SplitArgs(tc.args)\n\n\t\t\tapp.Init = func(_ []string, _ io.Reader) (*global.Data, error) {\n\t\t\t\treturn testutil.MockGlobalData(args, &stdout), nil\n\t\t\t}\n\n\t\t\terr := app.Run(args, nil)\n\n\t\t\tvar output string\n\t\t\tif err != nil {\n\t\t\t\tvar re errors.RemediationError\n\t\t\t\tif stderrors.As(err, &re) {\n\t\t\t\t\toutput = re.Prefix\n\t\t\t\t}\n\t\t\t}\n\t\t\toutput += stdout.String()\n\n\t\t\tif tc.wantGuide && !strings.Contains(output, \"AUTH GUIDE\") {\n\t\t\t\tt.Errorf(\"expected AUTH GUIDE in output, got:\\n%s\", output)\n\t\t\t}\n\t\t\tif tc.wantGuide && !strings.Contains(output, \"--sso --token\") {\n\t\t\t\tt.Errorf(\"expected AUTH GUIDE to contain '--sso --token' quick-start example, got:\\n%s\", output)\n\t\t\t}\n\t\t\tif !tc.wantGuide && strings.Contains(output, \"AUTH GUIDE\") {\n\t\t\t\tt.Errorf(\"did not expect AUTH GUIDE in output, got:\\n%s\", output)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/argparser/cmd.go",
    "content": "package argparser\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/kingpin\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/api\"\n\t\"github.com/fastly/cli/pkg/env\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/manifest\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// Command is an interface that abstracts over all of the concrete command\n// structs. The Name method lets us select which command should be run, and the\n// Exec method invokes whatever business logic the command should do.\ntype Command interface {\n\tName() string\n\tExec(in io.Reader, out io.Writer) error\n}\n\n// Select chooses the command matching name, if it exists.\nfunc Select(name string, commands []Command) (Command, bool) {\n\tfor _, command := range commands {\n\t\tif command.Name() == name {\n\t\t\treturn command, true\n\t\t}\n\t}\n\treturn nil, false\n}\n\n// Registerer abstracts over a kingpin.App and kingpin.CmdClause. We pass it to\n// each concrete command struct's constructor as the \"parent\" into which the\n// command should install itself.\ntype Registerer interface {\n\tCommand(name, help string) *kingpin.CmdClause\n}\n\n// Globals are flags and other stuff that's useful to every command. Globals are\n// passed to each concrete command's constructor as a pointer, and are populated\n// after a call to Parse. A concrete command's Exec method can use any of the\n// information in the globals.\ntype Globals struct {\n\tToken   string\n\tVerbose bool\n\tClient  api.Interface\n}\n\n// Base is stuff that should be included in every concrete command.\ntype Base struct {\n\tCmdClause       *kingpin.CmdClause\n\tGlobals         *global.Data\n\tSuppressVerbose bool\n}\n\n// Name implements the Command interface, and returns the FullCommand from the\n// kingpin.Command that's used to select which command to actually run.\nfunc (b Base) Name() string {\n\treturn b.CmdClause.FullCommand()\n}\n\n// SuppressesVerbose returns true if this command should suppress verbose output.\nfunc (b Base) SuppressesVerbose() bool {\n\treturn b.SuppressVerbose\n}\n\n// Optional models an optional type that consumers can use to assert whether the\n// inner value has been set and is therefore valid for use.\ntype Optional struct {\n\tWasSet bool\n}\n\n// Set implements kingpin.Action and is used as callback to set that the optional\n// inner value is valid.\nfunc (o *Optional) Set(_ *kingpin.ParseElement, _ *kingpin.ParseContext) error {\n\to.WasSet = true\n\treturn nil\n}\n\n// OptionalString models an optional string flag value.\ntype OptionalString struct {\n\tOptional\n\tValue string\n}\n\n// OptionalStringSlice models an optional string slice flag value.\ntype OptionalStringSlice struct {\n\tOptional\n\tValue []string\n}\n\n// OptionalBool models an optional boolean flag value.\ntype OptionalBool struct {\n\tOptional\n\tValue bool\n}\n\n// OptionalInt models an optional int flag value.\ntype OptionalInt struct {\n\tOptional\n\tValue int\n}\n\n// OptionalFloat64 models an optional int flag value.\ntype OptionalFloat64 struct {\n\tOptional\n\tValue float64\n}\n\n// ServiceDetailsOpts provides data and behaviours required by the\n// ServiceDetails function.\ntype ServiceDetailsOpts struct {\n\t// Active controls whether active service-versions will be included in the result;\n\t// if this is Empty, then the 'active' state of the version is ignored;\n\t// otherwise, the 'active' state must match the value\n\tActive optional.Optional[bool]\n\t// Locked controls whether locked service-versions will be included in the result;\n\t// if this is Empty, then the 'locked' state of the version is ignored;\n\t// otherwise, the 'locked' state must match the value\n\tLocked optional.Optional[bool]\n\t// Staging controls whether staging service-versions will be included in the result;\n\t// if this is Empty, then the 'staging' state of the version is ignored;\n\t// otherwise, the 'staging' state must match the value\n\tStaging            optional.Optional[bool]\n\tAutoCloneFlag      OptionalAutoClone\n\tAPIClient          api.Interface\n\tManifest           manifest.Data\n\tOut                io.Writer\n\tServiceNameFlag    OptionalServiceNameID\n\tServiceVersionFlag OptionalServiceVersion\n\tVerboseMode        bool\n\tErrLog             fsterr.LogInterface\n}\n\n// ServiceDetails returns the Service ID and Service Version.\nfunc ServiceDetails(opts ServiceDetailsOpts) (serviceID string, serviceVersion *fastly.Version, err error) {\n\tserviceID, source, flag, err := ServiceID(opts.ServiceNameFlag, opts.Manifest, opts.APIClient, opts.ErrLog)\n\tif err != nil {\n\t\treturn serviceID, serviceVersion, err\n\t}\n\tif opts.VerboseMode {\n\t\tDisplayServiceID(serviceID, flag, source, opts.Out)\n\t}\n\n\tv, err := opts.ServiceVersionFlag.Parse(serviceID, opts.APIClient)\n\tif err != nil {\n\t\treturn serviceID, serviceVersion, err\n\t}\n\n\tif opts.AutoCloneFlag.WasSet {\n\t\tcurrentVersion := v\n\t\tv, err = opts.AutoCloneFlag.Parse(currentVersion, serviceID, opts.VerboseMode, opts.Out, opts.APIClient)\n\t\tif err != nil {\n\t\t\treturn serviceID, currentVersion, err\n\t\t}\n\t\treturn serviceID, v, nil\n\t}\n\n\tfailure := false\n\tvar failureState string\n\n\tif active, present := opts.Active.Get(); present {\n\t\tif active && !fastly.ToValue(v.Active) {\n\t\t\tfailure = true\n\t\t\tfailureState = \"not active\"\n\t\t}\n\t\tif !active && fastly.ToValue(v.Active) {\n\t\t\tfailure = true\n\t\t\tfailureState = \"active\"\n\t\t}\n\t}\n\n\tif locked, present := opts.Locked.Get(); present {\n\t\tif locked && !fastly.ToValue(v.Locked) {\n\t\t\tfailure = true\n\t\t\tfailureState = \"not locked\"\n\t\t}\n\t\tif !locked && fastly.ToValue(v.Locked) {\n\t\t\tfailure = true\n\t\t\tfailureState = \"locked\"\n\t\t}\n\t}\n\n\tif staging, present := opts.Staging.Get(); present {\n\t\tif staging && !fastly.ToValue(v.Staging) {\n\t\t\tfailure = true\n\t\t\tfailureState = \"not staged\"\n\t\t}\n\t\tif !staging && fastly.ToValue(v.Staging) {\n\t\t\tfailure = true\n\t\t\tfailureState = \"staged\"\n\t\t}\n\t}\n\n\tif failure {\n\t\terr = fsterr.RemediationError{\n\t\t\tInner:       fmt.Errorf(\"service version %d is %s\", fastly.ToValue(v.Number), failureState),\n\t\t\tRemediation: fsterr.AutoCloneRemediation,\n\t\t}\n\t\treturn serviceID, v, err\n\t}\n\treturn serviceID, v, nil\n}\n\n// ServiceID returns the Service ID and the source of that information.\n//\n// NOTE: If Service Name is provided it overrides all other methods of\n// obtaining the Service ID.\nfunc ServiceID(serviceName OptionalServiceNameID, data manifest.Data, client api.Interface, li fsterr.LogInterface) (serviceID string, source manifest.Source, flag string, err error) {\n\tflag = \"--\" + FlagServiceIDName\n\tserviceID, source = data.ServiceID()\n\n\tif serviceName.WasSet {\n\t\tif source == manifest.SourceFlag {\n\t\t\terr = fmt.Errorf(\"cannot specify both %s and %s\", FlagServiceIDName, FlagServiceName)\n\t\t\tif li != nil {\n\t\t\t\tli.Add(err)\n\t\t\t}\n\t\t\treturn serviceID, source, flag, err\n\t\t}\n\n\t\tflag = \"--\" + FlagServiceName\n\t\tserviceID, err = serviceName.Parse(client)\n\t\tif err != nil {\n\t\t\tif li != nil {\n\t\t\t\tli.Add(err)\n\t\t\t}\n\t\t\treturn serviceID, source, flag, err\n\t\t}\n\t\tsource = manifest.SourceFlag\n\t}\n\n\tif source == manifest.SourceUndefined {\n\t\terr = fsterr.ErrNoServiceID\n\t}\n\n\treturn serviceID, source, flag, err\n}\n\n// DisplayServiceID acquires the Service ID (if provided) and displays both it\n// and its source location.\nfunc DisplayServiceID(sid, flag string, s manifest.Source, out io.Writer) {\n\tvar via string\n\tswitch s {\n\tcase manifest.SourceFlag:\n\t\tvia = fmt.Sprintf(\" (via %s)\", flag)\n\tcase manifest.SourceFile:\n\t\tvia = fmt.Sprintf(\" (via %s)\", manifest.Filename)\n\tcase manifest.SourceEnv:\n\t\tvia = fmt.Sprintf(\" (via %s)\", env.ServiceID)\n\tcase manifest.SourceUndefined:\n\t\tvia = \" (not provided)\"\n\t}\n\ttext.Output(out, \"Service ID%s: %s\", via, sid)\n\ttext.Break(out)\n}\n\n// ArgsIsHelpJSON determines whether the supplied command arguments are exactly\n// `help --format=json`, `help --format json`, or `help --json`.\nfunc ArgsIsHelpJSON(args []string) bool {\n\tswitch len(args) {\n\tcase 2:\n\t\tif args[0] == \"help\" && (args[1] == \"--format=json\" || args[1] == \"--json\") {\n\t\t\treturn true\n\t\t}\n\tcase 3:\n\t\tif args[0] == \"help\" && args[1] == \"--format\" && args[2] == \"json\" {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\n// IsHelpOnly indicates if the user called `fastly help [...]`.\nfunc IsHelpOnly(args []string) bool {\n\treturn len(args) > 0 && args[0] == \"help\"\n}\n\n// IsHelpFlagOnly indicates if the user called `fastly --help [...]`.\nfunc IsHelpFlagOnly(args []string) bool {\n\treturn len(args) > 0 && args[0] == \"--help\"\n}\n\n// IsVerboseAndQuiet indicates if the user called `fastly --verbose --quiet`.\n// These flags are mutually exclusive.\nfunc IsVerboseAndQuiet(args []string) bool {\n\tmatches := map[string]bool{}\n\tfor _, a := range args {\n\t\tif a == \"--verbose\" || a == \"-v\" {\n\t\t\tmatches[\"--verbose\"] = true\n\t\t}\n\t\tif a == \"--quiet\" || a == \"-q\" {\n\t\t\tmatches[\"--quiet\"] = true\n\t\t}\n\t}\n\treturn len(matches) > 1\n}\n\n// IsGlobalFlagsOnly indicates if the user called the binary with any\n// permutation order of the globally defined flags.\n//\n// NOTE: Some global flags accept a value while others do not. The following\n// algorithm takes this into account by mapping the flag to an expected value.\n// For example, --verbose doesn't accept a value so is set to zero.\n//\n// EXAMPLES:\n//\n// The following would return false as a command was specified:\n//\n// args: [--verbose -v --endpoint ... --token ... -t ... --endpoint ...  version] 11\n// total: 10\n//\n// The following would return true as only global flags were specified:\n//\n// args: [--verbose -v --endpoint ... --token ... -t ... --endpoint ...] 10\n// total: 10\n//\n// IMPORTANT: Kingpin doesn't support global flags.\n// We hack a solution in ../app/run.go (`configureKingpin` function).\nfunc IsGlobalFlagsOnly(args []string) bool {\n\t// Global flags are defined in ../app/run.go\n\t// False positive https://github.com/semgrep/semgrep/issues/8593\n\t// nosemgrep: trailofbits.go.iterate-over-empty-map.iterate-over-empty-map\n\tglobals := map[string]int{\n\t\t\"--accept-defaults\": 0,\n\t\t\"-d\":                0,\n\t\t\"--account\":         1,\n\t\t\"--api\":             1,\n\t\t\"--auto-yes\":        0,\n\t\t\"-y\":                0,\n\t\t\"--debug-mode\":      0,\n\t\t\"--enable-sso\":      0,\n\t\t\"--help\":            0,\n\t\t\"--non-interactive\": 0,\n\t\t\"-i\":                0,\n\t\t\"--profile\":         1,\n\t\t\"-o\":                1,\n\t\t\"--quiet\":           0,\n\t\t\"-q\":                0,\n\t\t\"--verbose\":         0,\n\t\t\"-v\":                0,\n\t}\n\tif !env.AuthCommandDisabled() {\n\t\tglobals[\"--token\"] = 1\n\t\tglobals[\"-t\"] = 1\n\t}\n\tvar total int\n\tfor _, a := range args {\n\t\tfor k := range globals {\n\t\t\tif a == k {\n\t\t\t\ttotal++\n\t\t\t\ttotal += globals[k]\n\t\t\t}\n\t\t}\n\t}\n\treturn len(args) == total\n}\n"
  },
  {
    "path": "pkg/argparser/cmd_test.go",
    "content": "package argparser_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/env\"\n)\n\nfunc TestIsGlobalFlagsOnly(t *testing.T) {\n\tt.Setenv(env.DisableAuthCommand, \"\")\n\ttests := []struct {\n\t\tname string\n\t\targs []string\n\t\twant bool\n\t}{\n\t\t{\n\t\t\tname: \"verbose only\",\n\t\t\targs: []string{\"--verbose\"},\n\t\t\twant: true,\n\t\t},\n\t\t{\n\t\t\tname: \"token with value\",\n\t\t\targs: []string{\"--token\", \"abc\"},\n\t\t\twant: true,\n\t\t},\n\t\t{\n\t\t\tname: \"short token with value\",\n\t\t\targs: []string{\"-t\", \"abc\"},\n\t\t\twant: true,\n\t\t},\n\t\t{\n\t\t\tname: \"subcommand present\",\n\t\t\targs: []string{\"--verbose\", \"version\"},\n\t\t\twant: false,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif got := argparser.IsGlobalFlagsOnly(tt.args); got != tt.want {\n\t\t\t\tt.Errorf(\"IsGlobalFlagsOnly(%v) = %v, want %v\", tt.args, got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestArgsIsHelpJSON(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\targs []string\n\t\twant bool\n\t}{\n\t\t{\n\t\t\tname: \"help --format=json\",\n\t\t\targs: []string{\"help\", \"--format=json\"},\n\t\t\twant: true,\n\t\t},\n\t\t{\n\t\t\tname: \"help --format json\",\n\t\t\targs: []string{\"help\", \"--format\", \"json\"},\n\t\t\twant: true,\n\t\t},\n\t\t{\n\t\t\tname: \"help --json\",\n\t\t\targs: []string{\"help\", \"--json\"},\n\t\t\twant: true,\n\t\t},\n\t\t{\n\t\t\tname: \"help only\",\n\t\t\targs: []string{\"help\"},\n\t\t\twant: false,\n\t\t},\n\t\t{\n\t\t\tname: \"help --format=yaml\",\n\t\t\targs: []string{\"help\", \"--format=yaml\"},\n\t\t\twant: false,\n\t\t},\n\t\t{\n\t\t\tname: \"help --json extra\",\n\t\t\targs: []string{\"help\", \"--json\", \"extra\"},\n\t\t\twant: false,\n\t\t},\n\t\t{\n\t\t\tname: \"empty\",\n\t\t\targs: []string{},\n\t\t\twant: false,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif got := argparser.ArgsIsHelpJSON(tt.args); got != tt.want {\n\t\t\t\tt.Errorf(\"ArgsIsHelpJSON(%v) = %v, want %v\", tt.args, got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestIsGlobalFlagsOnlyDisabledAuth(t *testing.T) {\n\tt.Setenv(env.DisableAuthCommand, \"1\")\n\n\ttests := []struct {\n\t\tname string\n\t\targs []string\n\t\twant bool\n\t}{\n\t\t{\n\t\t\tname: \"token is not global when auth disabled\",\n\t\t\targs: []string{\"--token\", \"abc\"},\n\t\t\twant: false,\n\t\t},\n\t\t{\n\t\t\tname: \"short token is not global when auth disabled\",\n\t\t\targs: []string{\"-t\", \"abc\"},\n\t\t\twant: false,\n\t\t},\n\t\t{\n\t\t\tname: \"other globals still work\",\n\t\t\targs: []string{\"--verbose\"},\n\t\t\twant: true,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif got := argparser.IsGlobalFlagsOnly(tt.args); got != tt.want {\n\t\t\t\tt.Errorf(\"IsGlobalFlagsOnly(%v) = %v, want %v\", tt.args, got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/argparser/common.go",
    "content": "package argparser\n\nvar (\n\t// FlagCustomerIDName is the flag name.\n\tFlagCustomerIDName = \"customer-id\"\n\t// FlagCustomerIDDesc is the flag description.\n\tFlagCustomerIDDesc = \"Alphanumeric string identifying the customer (falls back to FASTLY_CUSTOMER_ID)\"\n\t// FlagJSONName is the flag name.\n\tFlagJSONName = \"json\"\n\t// FlagJSONDesc is the flag description.\n\tFlagJSONDesc = \"Render output as JSON\"\n\t// FlagNGWAFAlertID is the alert ID.\n\tFlagNGWAFAlertID = \"alert-id\"\n\t// FlagNGWAFAlertIDDesc is the alert ID flag description.\n\tFlagNGWAFAlertIDDesc = \"Alphanumeric string identifying the alert\"\n\t// FlagNGWAFWorkspaceID is the workspace ID.\n\tFlagNGWAFWorkspaceID = \"workspace-id\"\n\t// FlagNGWAFWorkspaceIDDesc is the workspace ID flag description.\n\tFlagNGWAFWorkspaceIDDesc = \"Alphanumeric string identifying the NGWAF Workspace (falls back to FASTLY_WORKSPACE_ID)\"\n\t// FlagServiceIDName is the flag name.\n\tFlagServiceIDName = \"service-id\"\n\t// FlagServiceIDDesc is the flag description.\n\tFlagServiceIDDesc = \"Service ID (falls back to FASTLY_SERVICE_ID, then fastly.toml)\"\n\t// FlagServiceName is the flag name.\n\tFlagServiceName = \"service-name\"\n\t// FlagServiceNameDesc is the flag description.\n\tFlagServiceNameDesc = \"The name of the service\"\n\t// FlagVersionName is the flag name.\n\tFlagVersionName = \"version\"\n\t// FlagVersionDesc is the flag description.\n\tFlagVersionDesc = \"'latest', 'active', 'staged', or the number of a specific Fastly service version\"\n)\n\n// PaginationDirection is a list of directions the page results can be displayed.\nvar PaginationDirection = []string{\"ascend\", \"descend\"}\n\n// CursorFlag returns a cursor flag definition.\nfunc CursorFlag(dst *string) StringFlagOpts {\n\treturn StringFlagOpts{\n\t\tName:        \"cursor\",\n\t\tShort:       'c',\n\t\tDescription: \"Pagination cursor (Use 'next_cursor' value from list output)\",\n\t\tDst:         dst,\n\t}\n}\n\n// LimitFlag returns a limit flag definition.\nfunc LimitFlag(dst *int) IntFlagOpts {\n\treturn IntFlagOpts{\n\t\tName:        \"limit\",\n\t\tShort:       'l',\n\t\tDescription: \"Maximum number of items to list\",\n\t\tDefault:     50,\n\t\tDst:         dst,\n\t}\n}\n\n// StoreIDFlag returns a store-id flag definition.\nfunc StoreIDFlag(dst *string) StringFlagOpts {\n\treturn StringFlagOpts{\n\t\tName:        \"store-id\",\n\t\tShort:       's',\n\t\tDescription: \"Store ID\",\n\t\tDst:         dst,\n\t\tRequired:    true,\n\t}\n}\n"
  },
  {
    "path": "pkg/argparser/doc.go",
    "content": "// Package argparser contains helper abstractions for working with the CLI parser.\npackage argparser\n"
  },
  {
    "path": "pkg/argparser/fixtures/content_test.txt",
    "content": "This is a test"
  },
  {
    "path": "pkg/argparser/flags.go",
    "content": "package argparser\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"regexp\"\n\t\"sort\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/kingpin\"\n\n\t\"github.com/fastly/cli/pkg/api\"\n\t\"github.com/fastly/cli/pkg/env\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\nvar (\n\tcompletionRegExp       = regexp.MustCompile(\"completion-bash$\")\n\tcompletionScriptRegExp = regexp.MustCompile(\"completion-script-(?:bash|zsh)$\")\n)\n\n// StringFlagOpts enables easy configuration of a flag.\ntype StringFlagOpts struct {\n\tAction      kingpin.Action\n\tDescription string\n\tDst         *string\n\tName        string\n\tRequired    bool\n\tShort       rune\n}\n\n// RegisterFlag defines a flag.\nfunc (b Base) RegisterFlag(opts StringFlagOpts) {\n\tclause := b.CmdClause.Flag(opts.Name, opts.Description)\n\tif opts.Short > 0 {\n\t\tclause = clause.Short(opts.Short)\n\t}\n\tif opts.Required {\n\t\tclause = clause.Required()\n\t}\n\tif opts.Action != nil {\n\t\tclause = clause.Action(opts.Action)\n\t}\n\tclause.StringVar(opts.Dst)\n}\n\n// BoolFlagOpts enables easy configuration of a flag.\ntype BoolFlagOpts struct {\n\tAction      kingpin.Action\n\tDescription string\n\tDst         *bool\n\tName        string\n\tRequired    bool\n\tShort       rune\n}\n\n// RegisterFlagBool defines a boolean flag.\n//\n// TODO: Use generics support in go 1.18 to remove the need for multiple functions.\nfunc (b Base) RegisterFlagBool(opts BoolFlagOpts) {\n\tclause := b.CmdClause.Flag(opts.Name, opts.Description)\n\tif opts.Short > 0 {\n\t\tclause = clause.Short(opts.Short)\n\t}\n\tif opts.Required {\n\t\tclause = clause.Required()\n\t}\n\tif opts.Action != nil {\n\t\tclause = clause.Action(opts.Action)\n\t}\n\tclause.BoolVar(opts.Dst)\n}\n\n// IntFlagOpts enables easy configuration of a flag.\ntype IntFlagOpts struct {\n\tAction      kingpin.Action\n\tDefault     int\n\tDescription string\n\tDst         *int\n\tName        string\n\tRequired    bool\n\tShort       rune\n}\n\n// RegisterFlagInt defines an integer flag.\nfunc (b Base) RegisterFlagInt(opts IntFlagOpts) {\n\tclause := b.CmdClause.Flag(opts.Name, opts.Description)\n\tif opts.Short > 0 {\n\t\tclause = clause.Short(opts.Short)\n\t}\n\tif opts.Required {\n\t\tclause = clause.Required()\n\t}\n\tif opts.Action != nil {\n\t\tclause = clause.Action(opts.Action)\n\t}\n\tif opts.Default != 0 {\n\t\tclause = clause.Default(strconv.Itoa(opts.Default))\n\t}\n\tclause.IntVar(opts.Dst)\n}\n\n// OptionalServiceVersion represents a Fastly service version.\ntype OptionalServiceVersion struct {\n\tOptionalString\n}\n\n// Parse returns a service version based on the given user input.\n//\n// Supported values:\n//   - Numeric version (e.g., \"1\", \"2\", \"42\"): Returns the specified version\n//   - \"active\": Returns the currently active version\n//   - \"staged\": Returns the currently staged version\n//   - \"latest\": Returns the highest version number (latest version)\n//   - Omitted (no flag provided): Returns active version, falls back to latest if no active version exists\nfunc (sv *OptionalServiceVersion) Parse(sid string, client api.Interface) (*fastly.Version, error) {\n\t// When no --version flag is provided (WasSet=false), default to \"active\" to preserve\n\t// the original behavior of trying active version first, with fallback to latest.\n\tif sv.Value == \"\" && !sv.WasSet {\n\t\tsv.Value = \"active\"\n\t}\n\n\t// When a specific numeric version is provided, use it directly.\n\tif n, err := strconv.Atoi(sv.Value); err == nil {\n\t\treturn client.GetVersion(context.TODO(), &fastly.GetVersionInput{\n\t\t\tServiceID:      sid,\n\t\t\tServiceVersion: n,\n\t\t})\n\t}\n\n\tswitch strings.ToLower(sv.Value) {\n\tcase \"active\":\n\t\tserviceDetails, err := client.GetServiceDetails(context.TODO(), &fastly.GetServiceDetailsInput{\n\t\t\tServiceID: sid,\n\t\t\tFilters: []fastly.ServiceDetailsFilter{\n\t\t\t\t{Key: \"versions.active\", Value: true},\n\t\t\t},\n\t\t})\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"error getting service details: %w\", err)\n\t\t}\n\t\t// If active version exists, return it\n\t\tif serviceDetails.ActiveVersion != nil {\n\t\t\treturn serviceDetails.ActiveVersion, nil\n\t\t}\n\t\t// If flag was explicitly set to \"active\" but no active version exists, return error\n\t\tif sv.WasSet {\n\t\t\treturn nil, fmt.Errorf(\"no active service version found\")\n\t\t}\n\t\t// If flag was not explicitly set and there's no active version, fall through to latest\n\t\tfallthrough\n\tcase \"latest\":\n\t\tvs, err := client.ListVersions(context.TODO(), &fastly.ListVersionsInput{\n\t\t\tServiceID: sid,\n\t\t})\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"error listing service versions: %w\", err)\n\t\t}\n\t\tif len(vs) == 0 {\n\t\t\treturn nil, errors.New(\"no service versions available\")\n\t\t}\n\t\t// Sort versions into descending order to ensure we get the latest\n\t\tsort.Slice(vs, func(i, j int) bool {\n\t\t\treturn fastly.ToValue(vs[i].Number) > fastly.ToValue(vs[j].Number)\n\t\t})\n\t\treturn vs[0], nil\n\tcase \"staged\":\n\t\tserviceDetails, err := client.GetServiceDetails(context.TODO(), &fastly.GetServiceDetailsInput{\n\t\t\tServiceID: sid,\n\t\t\tFilters: []fastly.ServiceDetailsFilter{\n\t\t\t\t{Key: \"versions.staged\", Value: true},\n\t\t\t},\n\t\t})\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"error getting service details: %w\", err)\n\t\t}\n\t\tif serviceDetails.Version == nil {\n\t\t\treturn nil, fmt.Errorf(\"no staged service version found\")\n\t\t}\n\t\treturn serviceDetails.Version, nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"invalid version value %q: must be a version number, \\\"latest\\\", \\\"active\\\", or \\\"staged\\\"\", sv.Value)\n\t}\n}\n\n// OptionalServiceNameID represents a mapping between a Fastly service name and\n// its ID.\ntype OptionalServiceNameID struct {\n\tOptionalString\n}\n\n// Parse returns a service ID based off the given service name.\nfunc (sv *OptionalServiceNameID) Parse(client api.Interface) (serviceID string, err error) {\n\tpaginator := client.GetServices(context.TODO(), &fastly.GetServicesInput{})\n\tvar services []*fastly.Service\n\tfor paginator.HasNext() {\n\t\tdata, err := paginator.GetNext()\n\t\tif err != nil {\n\t\t\treturn serviceID, fmt.Errorf(\"error listing services: %w\", err)\n\t\t}\n\t\tservices = append(services, data...)\n\t}\n\tfor _, s := range services {\n\t\tif fastly.ToValue(s.Name) == sv.Value {\n\t\t\treturn fastly.ToValue(s.ServiceID), nil\n\t\t}\n\t}\n\treturn serviceID, errors.New(\"error matching service name with available services\")\n}\n\n// OptionalCustomerID represents a Fastly customer ID.\ntype OptionalCustomerID struct {\n\tOptionalString\n}\n\n// Parse returns a customer ID either from a flag or from a user defined\n// environment variable (see pkg/env/env.go).\n//\n// NOTE: Will fallback to FASTLY_CUSTOMER_ID environment variable if no flag value set.\nfunc (sv *OptionalCustomerID) Parse() error {\n\tif sv.Value == \"\" {\n\t\tif e := os.Getenv(env.CustomerID); e != \"\" {\n\t\t\tsv.Value = e\n\t\t\treturn nil\n\t\t}\n\t\treturn fsterr.ErrNoCustomerID\n\t}\n\treturn nil\n}\n\n// OptionalWorkspaceID represents a Fastly NGWAF Workspace ID.\ntype OptionalWorkspaceID struct {\n\tOptionalString\n}\n\n// Parse returns a workspace ID either from a flag or from a user defined\n// environment variable (see pkg/env/env.go).\n//\n// NOTE: Will fallback to FASTLY_WORKSPACE_ID environment variable if no flag value set.\nfunc (sv *OptionalWorkspaceID) Parse() error {\n\tif sv.Value == \"\" {\n\t\tif e := os.Getenv(env.WorkspaceID); e != \"\" {\n\t\t\tsv.Value = e\n\t\t\treturn nil\n\t\t}\n\t\treturn fsterr.ErrNoWorkspaceID\n\t}\n\treturn nil\n}\n\n// AutoCloneFlagOpts enables easy configuration of the --autoclone flag defined\n// via the RegisterAutoCloneFlag constructor.\ntype AutoCloneFlagOpts struct {\n\tAction kingpin.Action\n\tDst    *bool\n}\n\n// RegisterAutoCloneFlag defines a --autoclone flag that will cause a clone of the\n// identified service version if it's found to be active or locked.\nfunc (b Base) RegisterAutoCloneFlag(opts AutoCloneFlagOpts) {\n\tb.CmdClause.Flag(\"autoclone\", \"If the selected service version is not editable, clone it and use the clone.\").Action(opts.Action).BoolVar(opts.Dst)\n}\n\n// OptionalAutoClone defines a method set for abstracting the logic required to\n// identify if a given service version needs to be cloned.\ntype OptionalAutoClone struct {\n\tOptionalBool\n}\n\n// Parse returns a service version.\n//\n// The returned version is either the same as the input argument `v` or it's a\n// cloned version if the input argument was either active or locked.\nfunc (ac *OptionalAutoClone) Parse(v *fastly.Version, sid string, verbose bool, out io.Writer, client api.Interface) (*fastly.Version, error) {\n\t// if user didn't provide --autoclone flag\n\tif !ac.Value && (fastly.ToValue(v.Active) || fastly.ToValue(v.Locked)) {\n\t\treturn nil, fsterr.RemediationError{\n\t\t\tInner:       fmt.Errorf(\"service version %d is not editable\", fastly.ToValue(v.Number)),\n\t\t\tRemediation: fsterr.AutoCloneRemediation,\n\t\t}\n\t}\n\tstateUnknown := v.Active == nil && v.Locked == nil\n\tif ac.Value && (stateUnknown || v.Active != nil && *v.Active || v.Locked != nil && *v.Locked) {\n\t\tversion, err := client.CloneVersion(context.TODO(), &fastly.CloneVersionInput{\n\t\t\tServiceID:      sid,\n\t\t\tServiceVersion: fastly.ToValue(v.Number),\n\t\t})\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"error cloning service version: %w\", err)\n\t\t}\n\t\tif verbose {\n\t\t\tmsg := \"Service version %d is not editable, so it was automatically cloned because --autoclone is enabled. Now operating on version %d.\\n\\n\"\n\t\t\tformat := fmt.Sprintf(msg, fastly.ToValue(v.Number), fastly.ToValue(version.Number))\n\t\t\ttext.Info(out, format)\n\t\t}\n\t\treturn version, nil\n\t}\n\n\t// Treat the function as a no-op if the version is editable.\n\treturn v, nil\n}\n\n// Content determines if the given flag value is a file path, and if so read\n// the contents from disk, otherwise presume the given value is the content.\nfunc Content(flagval string) string {\n\tcontent := flagval\n\tif path, err := filepath.Abs(flagval); err == nil {\n\t\tif _, err := os.Stat(path); err == nil {\n\t\t\tif data, err := os.ReadFile(path); err == nil /* #nosec */ {\n\t\t\t\tcontent = string(data)\n\t\t\t}\n\t\t}\n\t}\n\treturn content\n}\n\n// IntToBool converts a binary 0|1 to a boolean.\nfunc IntToBool(i int) bool {\n\treturn i > 0\n}\n\n// ContextHasHelpFlag asserts whether a given kingpin.ParseContext contains a\n// `help` flag.\nfunc ContextHasHelpFlag(ctx *kingpin.ParseContext) bool {\n\t_, ok := ctx.Elements.FlagMap()[\"help\"]\n\treturn ok\n}\n\n// IsCompletionScript determines whether the supplied command arguments are for\n// shell completion output that is then eval()'ed by the user's shell.\nfunc IsCompletionScript(args []string) bool {\n\tvar found bool\n\tfor _, arg := range args {\n\t\tif completionScriptRegExp.MatchString(arg) {\n\t\t\tfound = true\n\t\t}\n\t}\n\treturn found\n}\n\n// IsCompletion determines whether the supplied command arguments are for\n// shell completion (i.e. --completion-bash) that should produce output that\n// the user's shell can utilise for handling autocomplete behaviour.\nfunc IsCompletion(args []string) bool {\n\tvar found bool\n\tfor _, arg := range args {\n\t\tif completionRegExp.MatchString(arg) {\n\t\t\tfound = true\n\t\t}\n\t}\n\treturn found\n}\n\n// JSONOutput is a helper for adding a `--json` flag and encoding\n// values to JSON. It can be embedded into command structs.\ntype JSONOutput struct {\n\tEnabled bool // Set via flag.\n}\n\n// JSONFlag creates a flag for enabling JSON output.\nfunc (j *JSONOutput) JSONFlag() BoolFlagOpts {\n\treturn BoolFlagOpts{\n\t\tName:        FlagJSONName,\n\t\tDescription: FlagJSONDesc,\n\t\tDst:         &j.Enabled,\n\t\tShort:       'j',\n\t}\n}\n\n// WriteJSON checks whether the enabled flag is set or not. If set,\n// then the given value is written as JSON to out. Otherwise, false is returned.\nfunc (j *JSONOutput) WriteJSON(out io.Writer, value any) (bool, error) {\n\tif !j.Enabled {\n\t\treturn false, nil\n\t}\n\n\tenc := json.NewEncoder(out)\n\tenc.SetIndent(\"\", \"  \")\n\treturn true, enc.Encode(value)\n}\n\nfunc ConvertBoolFromStringFlag(value string, argName string) (*bool, error) {\n\tswitch value {\n\tcase \"true\":\n\t\treturn fastly.ToPointer(true), nil\n\tcase \"false\":\n\t\treturn fastly.ToPointer(false), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"'%s' flag must be one of the following [true, false]\", argName)\n\t}\n}\n\nfunc ConvertOrderFromStringFlag(value string, argName string) (string, error) {\n\tswitch value {\n\tcase \"asc\":\n\t\treturn \"\", nil\n\tcase \"desc\":\n\t\treturn \"-\", nil\n\tdefault:\n\t\treturn \"\", fmt.Errorf(\"'%s' flag must be one of the following [asc, desc]\", argName)\n\t}\n}\n"
  },
  {
    "path": "pkg/argparser/flags_test.go",
    "content": "package argparser_test\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/manifest\"\n\t\"github.com/fastly/cli/pkg/mock\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n)\n\nfunc TestOptionalServiceVersionParse(t *testing.T) {\n\tcases := map[string]struct {\n\t\tflagValue   string\n\t\tflagOmitted bool\n\t\twantVersion int\n\t\terrExpected bool\n\t}{\n\t\t\"latest\": {\n\t\t\tflagValue:   \"latest\",\n\t\t\twantVersion: 4,\n\t\t},\n\t\t\"active\": {\n\t\t\tflagValue:   \"active\",\n\t\t\twantVersion: 1,\n\t\t},\n\t\t\"staged\": {\n\t\t\tflagValue:   \"staged\",\n\t\t\twantVersion: 4,\n\t\t},\n\t\t\"empty with WasSet\": {\n\t\t\tflagValue:   \"\",\n\t\t\terrExpected: true,\n\t\t},\n\t\t\"omitted\": {\n\t\t\tflagOmitted: true,\n\t\t\twantVersion: 1, // Returns active version when flag not provided (falls back to latest if no active)\n\t\t},\n\t\t\"specific version\": {\n\t\t\tflagValue:   \"2\",\n\t\t\twantVersion: 2,\n\t\t},\n\t\t\"specific version not found\": {\n\t\t\tflagValue:   \"999\",\n\t\t\terrExpected: true,\n\t\t},\n\t}\n\n\tfor name, c := range cases {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tsv := &argparser.OptionalServiceVersion{}\n\n\t\t\tif !c.flagOmitted {\n\t\t\t\tsv.OptionalString = argparser.OptionalString{\n\t\t\t\t\tOptional: argparser.Optional{WasSet: true},\n\t\t\t\t\tValue:    c.flagValue,\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tv, err := sv.Parse(\"123\", mock.API{\n\t\t\t\tGetVersionFn:        testutil.GetVersion,\n\t\t\t\tListVersionsFn:      listVersions,\n\t\t\t\tGetServiceDetailsFn: getServiceDetails,\n\t\t\t})\n\t\t\tif err != nil {\n\t\t\t\tif c.errExpected {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t\t}\n\t\t\tif c.errExpected {\n\t\t\t\tt.Fatalf(\"expected error, have %v\", v)\n\t\t\t}\n\n\t\t\twant := c.wantVersion\n\t\t\thave := fastly.ToValue(v.Number)\n\t\t\tif have != want {\n\t\t\t\tt.Errorf(\"wanted %d, have %d\", want, have)\n\t\t\t}\n\t\t})\n\t}\n}\n\n// listVersions returns a list of service versions in different states.\n//\n// Versions are returned in descending order by version number (highest first),\n// matching the real Fastly API behavior.\n// Version 4 (staged), Version 3 (editable), Version 2 (locked), Version 1 (active).\nfunc listVersions(_ context.Context, i *fastly.ListVersionsInput) ([]*fastly.Version, error) {\n\treturn []*fastly.Version{\n\t\t{\n\t\t\tServiceID: fastly.ToPointer(i.ServiceID),\n\t\t\tNumber:    fastly.ToPointer(4),\n\t\t\tStaging:   fastly.ToPointer(true),\n\t\t\tUpdatedAt: testutil.MustParseTimeRFC3339(\"2000-01-04T01:00:00Z\"),\n\t\t},\n\t\t{\n\t\t\tServiceID: fastly.ToPointer(i.ServiceID),\n\t\t\tNumber:    fastly.ToPointer(3),\n\t\t\tUpdatedAt: testutil.MustParseTimeRFC3339(\"2000-01-03T01:00:00Z\"),\n\t\t},\n\t\t{\n\t\t\tServiceID: fastly.ToPointer(i.ServiceID),\n\t\t\tNumber:    fastly.ToPointer(2),\n\t\t\tLocked:    fastly.ToPointer(true),\n\t\t\tUpdatedAt: testutil.MustParseTimeRFC3339(\"2000-01-02T01:00:00Z\"),\n\t\t},\n\t\t{\n\t\t\tServiceID: fastly.ToPointer(i.ServiceID),\n\t\t\tNumber:    fastly.ToPointer(1),\n\t\t\tActive:    fastly.ToPointer(true),\n\t\t\tUpdatedAt: testutil.MustParseTimeRFC3339(\"2000-01-01T01:00:00Z\"),\n\t\t},\n\t}, nil\n}\n\n// getServiceDetails returns service details with active and latest version info.\nfunc getServiceDetails(_ context.Context, i *fastly.GetServiceDetailsInput) (*fastly.ServiceDetail, error) {\n\tresult := &fastly.ServiceDetail{\n\t\tServiceID: fastly.ToPointer(i.ServiceID),\n\t}\n\n\t// Check if specific version is requested\n\tif i.Version != nil {\n\t\tresult.Version = &fastly.Version{\n\t\t\tServiceID: fastly.ToPointer(i.ServiceID),\n\t\t\tNumber:    i.Version,\n\t\t\tUpdatedAt: testutil.MustParseTimeRFC3339(\"2000-01-01T01:00:00Z\"),\n\t\t}\n\t\treturn result, nil\n\t}\n\n\t// Check filters\n\tfor _, filter := range i.Filters {\n\t\tif filter.Key == \"versions.active\" && filter.Value {\n\t\t\tresult.ActiveVersion = &fastly.Version{\n\t\t\t\tServiceID: fastly.ToPointer(i.ServiceID),\n\t\t\t\tNumber:    fastly.ToPointer(1),\n\t\t\t\tActive:    fastly.ToPointer(true),\n\t\t\t\tUpdatedAt: testutil.MustParseTimeRFC3339(\"2000-01-01T01:00:00Z\"),\n\t\t\t}\n\t\t\treturn result, nil\n\t\t}\n\t\tif filter.Key == \"versions.staged\" && filter.Value {\n\t\t\tresult.Version = &fastly.Version{\n\t\t\t\tServiceID: fastly.ToPointer(i.ServiceID),\n\t\t\t\tNumber:    fastly.ToPointer(4),\n\t\t\t\tStaging:   fastly.ToPointer(true),\n\t\t\t\tUpdatedAt: testutil.MustParseTimeRFC3339(\"2000-01-04T01:00:00Z\"),\n\t\t\t}\n\t\t\treturn result, nil\n\t\t}\n\t}\n\n\t// Default: return both active and latest\n\tresult.ActiveVersion = &fastly.Version{\n\t\tServiceID: fastly.ToPointer(i.ServiceID),\n\t\tNumber:    fastly.ToPointer(1),\n\t\tActive:    fastly.ToPointer(true),\n\t\tUpdatedAt: testutil.MustParseTimeRFC3339(\"2000-01-01T01:00:00Z\"),\n\t}\n\tresult.Version = &fastly.Version{\n\t\tServiceID: fastly.ToPointer(i.ServiceID),\n\t\tNumber:    fastly.ToPointer(4),\n\t\tStaging:   fastly.ToPointer(true),\n\t\tUpdatedAt: testutil.MustParseTimeRFC3339(\"2000-01-04T01:00:00Z\"),\n\t}\n\treturn result, nil\n}\n\nfunc TestOptionalAutoCloneParse(t *testing.T) {\n\tcases := map[string]struct {\n\t\tversion        *fastly.Version\n\t\tflagOmitted    bool\n\t\twantVersion    int\n\t\terrExpected    bool\n\t\texpectEditable bool\n\t}{\n\t\t\"version is editable\": {\n\t\t\tversion: &fastly.Version{\n\t\t\t\tNumber: fastly.ToPointer(1),\n\t\t\t\tActive: fastly.ToPointer(false),\n\t\t\t\tLocked: fastly.ToPointer(false),\n\t\t\t},\n\t\t\twantVersion:    1,\n\t\t\texpectEditable: true,\n\t\t},\n\t\t\"version is locked\": {\n\t\t\tversion: &fastly.Version{\n\t\t\t\tNumber: fastly.ToPointer(1),\n\t\t\t\tLocked: fastly.ToPointer(true),\n\t\t\t},\n\t\t\twantVersion: 2,\n\t\t},\n\t\t\"version is active\": {\n\t\t\tversion: &fastly.Version{\n\t\t\t\tNumber: fastly.ToPointer(1),\n\t\t\t\tActive: fastly.ToPointer(true),\n\t\t\t},\n\t\t\twantVersion: 2,\n\t\t},\n\t\t\"version is locked but flag omitted\": {\n\t\t\tversion: &fastly.Version{\n\t\t\t\tNumber: fastly.ToPointer(1),\n\t\t\t\tLocked: fastly.ToPointer(true),\n\t\t\t},\n\t\t\tflagOmitted: true,\n\t\t\terrExpected: true,\n\t\t},\n\t\t\"version is active but flag omitted\": {\n\t\t\tversion: &fastly.Version{\n\t\t\t\tNumber: fastly.ToPointer(1),\n\t\t\t\tActive: fastly.ToPointer(true),\n\t\t\t},\n\t\t\tflagOmitted: true,\n\t\t\terrExpected: true,\n\t\t},\n\t\t\"version state unknown with autoclone\": {\n\t\t\tversion: &fastly.Version{\n\t\t\t\tNumber: fastly.ToPointer(1),\n\t\t\t\tActive: nil,\n\t\t\t\tLocked: nil,\n\t\t\t},\n\t\t\twantVersion: 2,\n\t\t},\n\t}\n\n\tfor name, c := range cases {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tvar (\n\t\t\t\tacv *argparser.OptionalAutoClone\n\t\t\t\tbs  []byte\n\t\t\t)\n\t\t\tbuf := bytes.NewBuffer(bs)\n\n\t\t\tif c.flagOmitted {\n\t\t\t\tacv = &argparser.OptionalAutoClone{}\n\t\t\t} else {\n\t\t\t\tacv = &argparser.OptionalAutoClone{\n\t\t\t\t\tOptionalBool: argparser.OptionalBool{\n\t\t\t\t\t\tValue: true,\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tverboseMode := true\n\t\t\tv, err := acv.Parse(c.version, \"123\", verboseMode, buf, mock.API{\n\t\t\t\tCloneVersionFn: cloneVersionResult(fastly.ToValue(c.version.Number) + 1),\n\t\t\t})\n\t\t\tif err != nil {\n\t\t\t\tif c.errExpected && errMatches(fastly.ToValue(c.version.Number), err) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t\t}\n\t\t\tif c.errExpected {\n\t\t\t\tt.Fatalf(\"expected error, have %v\", v)\n\t\t\t}\n\n\t\t\twant := c.wantVersion\n\t\t\thave := fastly.ToValue(v.Number)\n\t\t\tif have != want {\n\t\t\t\tt.Errorf(\"wanted %d, have %d\", want, have)\n\t\t\t}\n\n\t\t\tif !c.expectEditable {\n\t\t\t\twant := fmt.Sprintf(\"Service version %d is not editable, so it was automatically cloned because --autoclone is enabled. Now operating on version %d.\", fastly.ToValue(c.version.Number), fastly.ToValue(v.Number))\n\t\t\t\thave := strings.Trim(strings.ReplaceAll(buf.String(), \"\\n\", \" \"), \" \")\n\t\t\t\tif !strings.Contains(have, want) {\n\t\t\t\t\tt.Errorf(\"wanted %s, have %s\", want, have)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestServiceID(t *testing.T) {\n\tcases := map[string]struct {\n\t\tServiceName   argparser.OptionalServiceNameID\n\t\tData          manifest.Data\n\t\tAPI           mock.API\n\t\tWantServiceID string\n\t\tWantError     string\n\t\tWantSource    manifest.Source\n\t\tWantFlag      string\n\t\tEnvVars       map[string]string\n\t}{\n\t\t\"service-id flag\": {\n\t\t\tData: manifest.Data{\n\t\t\t\tFlag: manifest.Flag{ServiceID: \"456\"},\n\t\t\t},\n\t\t\tWantServiceID: \"456\",\n\t\t\tWantSource:    manifest.SourceFlag,\n\t\t\tWantFlag:      argparser.FlagServiceIDName,\n\t\t},\n\t\t\"service ID in manifest\": {\n\t\t\tData: manifest.Data{\n\t\t\t\tFile: manifest.File{ServiceID: \"456\"},\n\t\t\t},\n\t\t\tWantServiceID: \"456\",\n\t\t\tWantSource:    manifest.SourceFile,\n\t\t\tEnvVars:       map[string]string{\"FASTLY_SERVICE_ID\": \"\"},\n\t\t},\n\t\t\"service-name flag with service-id flag\": {\n\t\t\tServiceName: argparser.OptionalServiceNameID{argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"bar\"}},\n\t\t\tData: manifest.Data{\n\t\t\t\tFlag: manifest.Flag{ServiceID: \"123\"},\n\t\t\t},\n\t\t\tWantError: \"cannot specify both service-id and service-name\",\n\t\t},\n\t\t\"service-name flag with service-id in file\": {\n\t\t\tServiceName: argparser.OptionalServiceNameID{argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"bar\"}},\n\t\t\tData: manifest.Data{\n\t\t\t\tFile: manifest.File{ServiceID: \"123\"},\n\t\t\t},\n\t\t\tAPI: mock.API{\n\t\t\t\tGetServicesFn: func(ctx context.Context, _ *fastly.GetServicesInput) *fastly.ListPaginator[fastly.Service] {\n\t\t\t\t\treturn fastly.NewPaginator[fastly.Service](ctx, &mock.HTTPClient{\n\t\t\t\t\t\tErrors: []error{nil},\n\t\t\t\t\t\tResponses: []*http.Response{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tBody: io.NopCloser(strings.NewReader(`[{\"id\": \"456\", \"name\": \"bar\"}]`)),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t}, fastly.ListOpts{}, \"/example\")\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantServiceID: \"456\",\n\t\t\tWantSource:    manifest.SourceFlag,\n\t\t\tWantFlag:      argparser.FlagServiceName,\n\t\t},\n\t\t\"unknown service-name flag value\": {\n\t\t\tServiceName: argparser.OptionalServiceNameID{argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"bar\"}},\n\t\t\tData:        manifest.Data{},\n\t\t\tAPI: mock.API{\n\t\t\t\tGetServicesFn: func(ctx context.Context, _ *fastly.GetServicesInput) *fastly.ListPaginator[fastly.Service] {\n\t\t\t\t\treturn fastly.NewPaginator[fastly.Service](ctx, &mock.HTTPClient{\n\t\t\t\t\t\tErrors: []error{nil},\n\t\t\t\t\t\tResponses: []*http.Response{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tBody: io.NopCloser(strings.NewReader(`[{\"id\": \"456\", \"name\": \"beepboop\"}]`)),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t}, fastly.ListOpts{}, \"/example\")\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"error matching service name with available services\",\n\t\t},\n\t\t\"no information provided\": {\n\t\t\tData:      manifest.Data{},\n\t\t\tWantError: \"error reading service: no service ID found\",\n\t\t\tEnvVars:   map[string]string{\"FASTLY_SERVICE_ID\": \"\"},\n\t\t},\n\t}\n\n\tfor name, c := range cases {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\t// Set environment variables for this test case\n\t\t\tfor k, v := range c.EnvVars {\n\t\t\t\tt.Setenv(k, v)\n\t\t\t}\n\t\t\tserviceID, source, flag, err := argparser.ServiceID(c.ServiceName, c.Data, c.API, nil)\n\t\t\ttestutil.AssertErrorContains(t, err, c.WantError)\n\t\t\tif err == nil {\n\t\t\t\ttestutil.AssertString(t, serviceID, c.WantServiceID)\n\t\t\t\ttestutil.AssertStringContains(t, flag, c.WantFlag)\n\t\t\t\ttestutil.AssertEqual(t, source, c.WantSource)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestContent(t *testing.T) {\n\tconst expectedContent = \"This is a test\"\n\tconst expectedPath = \"fixtures/content_test.txt\"\n\tfor _, testcase := range []struct {\n\t\tname    string\n\t\tcontent string\n\t}{\n\t\t{\n\t\t\tname:    \"regular string\",\n\t\t\tcontent: expectedContent,\n\t\t},\n\t\t{\n\t\t\tname:    \"path\",\n\t\t\tcontent: expectedPath,\n\t\t},\n\t} {\n\t\tt.Run(testcase.name, func(t *testing.T) {\n\t\t\tcontent := argparser.Content(testcase.content)\n\t\t\tif content != expectedContent {\n\t\t\t\tt.Errorf(\"for test %s, wanted content %s, got %s\", testcase.name, expectedContent, content)\n\t\t\t}\n\t\t})\n\t}\n}\n\n// cloneVersionResult returns a function which returns a specific cloned version.\nfunc cloneVersionResult(version int) func(_ context.Context, i *fastly.CloneVersionInput) (*fastly.Version, error) {\n\treturn func(_ context.Context, i *fastly.CloneVersionInput) (*fastly.Version, error) {\n\t\treturn &fastly.Version{\n\t\t\tServiceID: fastly.ToPointer(i.ServiceID),\n\t\t\tNumber:    fastly.ToPointer(version),\n\t\t}, nil\n\t}\n}\n\n// errMatches validates that the error message is what we expect when given a\n// service version that is either locked or active, while also not providing\n// the --autoclone flag.\nfunc errMatches(version int, err error) bool {\n\treturn err.Error() == fmt.Sprintf(\"service version %d is not editable\", version)\n}\n"
  },
  {
    "path": "pkg/auth/auth.go",
    "content": "package auth\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/hashicorp/cap/jwt\"\n\t\"github.com/hashicorp/cap/oidc\"\n\n\t\"github.com/fastly/cli/pkg/api\"\n\t\"github.com/fastly/cli/pkg/api/undocumented\"\n\t\"github.com/fastly/cli/pkg/debug\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n)\n\n// Remediation is a generic remediation message for an error authorizing.\nconst Remediation = \"Please re-run the command. If the problem persists, please file an issue: https://github.com/fastly/cli/issues/new?labels=bug&template=bug_report.md\"\n\n// ClientID is the auth provider's Client ID.\nconst ClientID = \"fastly-cli\"\n\n// redirectPath is the path in the internal webserver which will receive the authorization code.\nconst redirectPath = \"/callback\"\n\n// redirectURL is the endpoint the auth provider will pass an authorization code to.\nconst redirectURL = \"http://localhost:8080\" + redirectPath\n\n// OIDCMetadata is OpenID Connect's metadata discovery mechanism.\n// https://swagger.io/docs/specification/authentication/openid-connect-discovery/\nconst OIDCMetadata = \"%s/realms/fastly/.well-known/openid-configuration\"\n\n// ErrInvalidGrant represents an error refreshing the user's token.\nvar ErrInvalidGrant = errors.New(\"failed to refresh token: invalid grant\")\n\n// WellKnownEndpoints represents the OpenID Connect metadata.\ntype WellKnownEndpoints struct {\n\t// Auth is the authorization_endpoint.\n\tAuth string `json:\"authorization_endpoint\"`\n\t// Certs is the jwks_uri.\n\tCerts string `json:\"jwks_uri\"`\n\t// Token is the token_endpoint.\n\tToken string `json:\"token_endpoint\"`\n}\n\n// Runner defines the behaviour for the authentication server.\ntype Runner interface {\n\t// AuthURL returns a fully qualified authorization_endpoint.\n\t// i.e. path + audience + scope + code_challenge etc.\n\tAuthURL() (string, error)\n\t// GetResult returns the results channel\n\tGetResult() chan AuthorizationResult\n\t// RefreshAccessToken constructs and calls the token_endpoint with the\n\t// refresh token so we can refresh and return the access token.\n\tRefreshAccessToken(refreshToken string) (JWT, error)\n\t// SetParam sets the specified parameter for the authorization_endpoint.\n\t// https://openid.net/specs/openid-connect-basic-1_0.html#rfc.section.2.1.1.1\n\tSetParam(field, value string)\n\t// Start starts a local server for handling authentication processing.\n\tStart() error\n\t// ValidateAndRetrieveAPIToken verifies the signature and the claims and\n\t// exchanges the access token for an API token.\n\tValidateAndRetrieveAPIToken(accessToken string) (string, *APIToken, error)\n}\n\n// Server is a local server responsible for authentication processing.\ntype Server struct {\n\t// APIEndpoint is the API endpoint.\n\tAPIEndpoint string\n\t// AccountEndpoint is the accounts endpoint.\n\tAccountEndpoint string\n\t// DebugMode indicates to the CLI it can display debug information.\n\tDebugMode string\n\t// HTTPClient is a HTTP client used to call the API to exchange the access token for a session token.\n\tHTTPClient api.HTTPClient\n\t// Params are additional parameters for the authorization_endpoint.\n\tParams []Param\n\t// Result is a channel that reports the result of authorization.\n\tResult chan AuthorizationResult\n\t// Router is an HTTP request multiplexer.\n\tRouter *http.ServeMux\n\t// Verifier represents an OAuth PKCE code verifier that uses the S256 challenge method.\n\tVerifier *oidc.S256Verifier\n\t// WellKnownEndpoints is the .well-known metadata.\n\tWellKnownEndpoints WellKnownEndpoints\n}\n\n// Param is an individual parameter set on the authorization_endpoint.\ntype Param struct {\n\tField string\n\tValue string\n}\n\n// AuthURL returns a fully qualified authorization_endpoint.\n// i.e. path + audience + scope + code_challenge etc.\nfunc (s Server) AuthURL() (string, error) {\n\tchallenge, err := oidc.CreateCodeChallenge(s.Verifier)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tparams := url.Values{}\n\tparams.Add(\"audience\", s.APIEndpoint)\n\tparams.Add(\"scope\", \"openid\")\n\tparams.Add(\"response_type\", \"code\")\n\tparams.Add(\"client_id\", ClientID)\n\tparams.Add(\"code_challenge\", challenge)\n\tparams.Add(\"code_challenge_method\", \"S256\")\n\tparams.Add(\"redirect_uri\", redirectURL)\n\tfor _, p := range s.Params {\n\t\tparams.Add(p.Field, p.Value)\n\t}\n\treturn fmt.Sprintf(\"%s?%s\", s.WellKnownEndpoints.Auth, params.Encode()), nil\n}\n\n// SetParam sets the specified parameter for the authorization_endpoint.\nfunc (s *Server) SetParam(field, value string) {\n\ts.Params = append(s.Params, Param{field, value})\n}\n\n// GetResult returns the result channel.\nfunc (s Server) GetResult() chan AuthorizationResult {\n\treturn s.Result\n}\n\n// GetJWT constructs and calls the token_endpoint path, returning a JWT\n// containing the access and refresh tokens and associated TTLs.\nfunc (s Server) GetJWT(authorizationCode string) (JWT, error) {\n\tpayload := fmt.Sprintf(\n\t\t\"grant_type=authorization_code&client_id=%s&code_verifier=%s&code=%s&redirect_uri=%s\",\n\t\tClientID,\n\t\ts.Verifier.Verifier(),\n\t\tauthorizationCode,\n\t\tredirectURL, // NOTE: not redirected to, just a security check.\n\t)\n\n\treq, err := http.NewRequest(http.MethodPost, s.WellKnownEndpoints.Token, strings.NewReader(payload))\n\tif err != nil {\n\t\treturn JWT{}, err\n\t}\n\treq.Header.Add(\"content-type\", \"application/x-www-form-urlencoded\")\n\n\tdebugMode, _ := strconv.ParseBool(s.DebugMode)\n\n\tif debugMode {\n\t\tdebug.DumpHTTPRequest(req)\n\t}\n\tres, err := http.DefaultClient.Do(req)\n\tif debugMode {\n\t\tdebug.DumpHTTPResponse(res)\n\t}\n\n\tif err != nil {\n\t\treturn JWT{}, err\n\t}\n\tdefer res.Body.Close()\n\n\tif res.StatusCode != http.StatusOK {\n\t\treturn JWT{}, fmt.Errorf(\"failed to exchange code for jwt (status: %s)\", res.Status)\n\t}\n\n\tbody, err := io.ReadAll(res.Body)\n\tif err != nil {\n\t\treturn JWT{}, err\n\t}\n\n\tvar j JWT\n\terr = json.Unmarshal(body, &j)\n\tif err != nil {\n\t\treturn JWT{}, err\n\t}\n\n\treturn j, nil\n}\n\n// SetVerifier sets the code verifier endpoint.\nfunc (s *Server) SetVerifier(verifier *oidc.S256Verifier) {\n\ts.Verifier = verifier\n}\n\n// Start starts a local server for handling authentication processing.\nfunc (s *Server) Start() error {\n\tserver := &http.Server{\n\t\tAddr:         \":8080\",\n\t\tHandler:      s.Router,\n\t\tReadTimeout:  10 * time.Second,\n\t\tWriteTimeout: 10 * time.Second,\n\t}\n\n\terr := server.ListenAndServe()\n\tif err != nil {\n\t\treturn fsterr.RemediationError{\n\t\t\tInner:       fmt.Errorf(\"failed to start local server: %w\", err),\n\t\t\tRemediation: Remediation,\n\t\t}\n\t}\n\treturn nil\n}\n\n// HandleCallback processes the callback from the authentication service.\nfunc (s *Server) HandleCallback() http.HandlerFunc {\n\treturn func(w http.ResponseWriter, r *http.Request) {\n\t\tif r.URL.Path != redirectPath {\n\t\t\tw.WriteHeader(http.StatusBadRequest)\n\t\t\treturn\n\t\t}\n\n\t\tswitch r.Method {\n\t\tcase http.MethodOptions:\n\t\t\tw.Header().Add(\"Access-Control-Allow-Origin\", \"accounts.fastly.com\")\n\t\t\tw.WriteHeader(http.StatusOK)\n\t\t\treturn\n\t\tcase http.MethodGet:\n\t\t\t// handled below\n\t\tdefault:\n\t\t\tw.WriteHeader(http.StatusBadRequest)\n\t\t\treturn\n\t\t}\n\n\t\tauthorizationCode := r.URL.Query().Get(\"code\")\n\t\tif authorizationCode == \"\" {\n\t\t\tfmt.Fprint(w, \"ERROR: no authorization code returned\\n\")\n\t\t\ts.Result <- AuthorizationResult{\n\t\t\t\tErr: fmt.Errorf(\"no authorization code returned\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\t// Exchange the authorization code and the code verifier for a JWT.\n\t\t// NOTE: I use the identifier `j` to avoid overlap with the `jwt` package.\n\t\tj, err := s.GetJWT(authorizationCode)\n\t\tif err != nil || j.AccessToken == \"\" || j.IDToken == \"\" {\n\t\t\tfmt.Fprint(w, \"ERROR: failed to exchange code for JWT\\n\")\n\t\t\ts.Result <- AuthorizationResult{\n\t\t\t\tErr: fmt.Errorf(\"failed to exchange code for JWT\"),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\temail, at, err := s.ValidateAndRetrieveAPIToken(j.AccessToken)\n\t\tif err != nil {\n\t\t\ts.Result <- AuthorizationResult{\n\t\t\t\tErr: err,\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tfmt.Fprint(w, \"Authenticated successfully. Please close this page and return to the Fastly CLI in your terminal.\")\n\t\ts.Result <- AuthorizationResult{\n\t\t\tEmail:        email,\n\t\t\tJwt:          j,\n\t\t\tSessionToken: at.AccessToken,\n\t\t}\n\t}\n}\n\n// ValidateAndRetrieveAPIToken verifies the signature and the claims and\n// exchanges the access token for an API token.\n//\n// NOTE: This function exists as it's called by this package + app.Run().\nfunc (s *Server) ValidateAndRetrieveAPIToken(accessToken string) (string, *APIToken, error) {\n\tclaims, err := s.VerifyJWTSignature(accessToken)\n\tif err != nil {\n\t\treturn \"\", nil, err\n\t}\n\n\tazp, ok := claims[\"azp\"]\n\tif !ok {\n\t\treturn \"\", nil, errors.New(\"failed to extract azp from JWT claims\")\n\t}\n\tif azp != ClientID {\n\t\tif !ok {\n\t\t\treturn \"\", nil, fmt.Errorf(\"failed to match expected azp: %s\", azp)\n\t\t}\n\t}\n\n\taud, ok := claims[\"aud\"]\n\tif !ok {\n\t\treturn \"\", nil, errors.New(\"failed to extract aud from JWT claims\")\n\t}\n\n\tif aud != s.APIEndpoint {\n\t\tif !ok {\n\t\t\treturn \"\", nil, fmt.Errorf(\"failed to match expected aud: %s\", s.APIEndpoint)\n\t\t}\n\t}\n\n\temail, ok := claims[\"email\"]\n\tif !ok {\n\t\treturn \"\", nil, errors.New(\"failed to extract email from JWT claims\")\n\t}\n\n\t// Exchange the access token for a Fastly API token.\n\tat, err := s.ExchangeAccessToken(accessToken)\n\tif err != nil {\n\t\treturn \"\", nil, fmt.Errorf(\"failed to exchange access token for an API token: %w\", err)\n\t}\n\n\te, ok := email.(string)\n\tif !ok {\n\t\treturn \"\", nil, fmt.Errorf(\"failed to type assert 'email' (%#v) to a string\", email)\n\t}\n\treturn e, at, nil\n}\n\n// VerifyJWTSignature calls the jwks_uri endpoint and extracts its claims.\nfunc (s *Server) VerifyJWTSignature(accessToken string) (claims map[string]any, err error) {\n\tctx := context.Background()\n\n\t// NOTE: The last argument is optional and is for validating the JWKs endpoint\n\t// (which we don't need to do, so we pass an empty string)\n\tkeySet, err := jwt.NewJSONWebKeySet(ctx, s.WellKnownEndpoints.Certs, \"\")\n\tif err != nil {\n\t\treturn claims, fmt.Errorf(\"failed to verify signature of access token: %w\", err)\n\t}\n\n\tclaims, err = keySet.VerifySignature(ctx, accessToken)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to verify signature of access token: %w\", err)\n\t}\n\n\treturn claims, nil\n}\n\n// ExchangeAccessToken exchanges `accessToken` for a Fastly API token.\nfunc (s *Server) ExchangeAccessToken(accessToken string) (*APIToken, error) {\n\tdebug, _ := strconv.ParseBool(s.DebugMode)\n\tresp, err := undocumented.Call(undocumented.CallOptions{\n\t\tAPIEndpoint: s.APIEndpoint,\n\t\tHTTPClient:  s.HTTPClient,\n\t\tHTTPHeaders: []undocumented.HTTPHeader{\n\t\t\t{\n\t\t\t\tKey:   \"Authorization\",\n\t\t\t\tValue: fmt.Sprintf(\"Bearer %s\", accessToken),\n\t\t\t},\n\t\t},\n\t\tMethod: http.MethodPost,\n\t\tPath:   \"/login-enhanced\",\n\t\tDebug:  debug,\n\t})\n\tif err != nil {\n\t\tif apiErr, ok := err.(undocumented.APIError); ok {\n\t\t\tif apiErr.StatusCode != http.StatusConflict {\n\t\t\t\terr = fmt.Errorf(\"%w: %d %s\", err, apiErr.StatusCode, http.StatusText(apiErr.StatusCode))\n\t\t\t}\n\t\t}\n\t\treturn nil, err\n\t}\n\n\tat := &APIToken{}\n\terr = json.Unmarshal(resp, at)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to unmarshal json containing API token: %w\", err)\n\t}\n\n\treturn at, nil\n}\n\n// RefreshAccessToken constructs and calls the token_endpoint with the\n// refresh token so we can refresh and return the access token.\nfunc (s *Server) RefreshAccessToken(refreshToken string) (JWT, error) {\n\tpayload := fmt.Sprintf(\n\t\t\"grant_type=refresh_token&client_id=%s&refresh_token=%s\",\n\t\tClientID,\n\t\trefreshToken,\n\t)\n\n\treq, err := http.NewRequest(http.MethodPost, s.WellKnownEndpoints.Token, strings.NewReader(payload))\n\tif err != nil {\n\t\treturn JWT{}, err\n\t}\n\treq.Header.Add(\"content-type\", \"application/x-www-form-urlencoded\")\n\n\tdebugMode, _ := strconv.ParseBool(s.DebugMode)\n\tif debugMode {\n\t\tdebug.DumpHTTPRequest(req)\n\t}\n\tres, err := http.DefaultClient.Do(req)\n\tif debugMode {\n\t\tdebug.DumpHTTPResponse(res)\n\t}\n\n\tif err != nil {\n\t\treturn JWT{}, err\n\t}\n\tdefer res.Body.Close()\n\n\tbody, err := io.ReadAll(res.Body)\n\tif err != nil {\n\t\treturn JWT{}, err\n\t}\n\n\tif res.StatusCode != http.StatusOK {\n\t\tvar re RefreshError\n\t\terr = json.Unmarshal(body, &re)\n\t\tif err != nil {\n\t\t\treturn JWT{}, err\n\t\t}\n\n\t\tif re.Error == \"invalid_grant\" {\n\t\t\treturn JWT{}, ErrInvalidGrant\n\t\t}\n\t\treturn JWT{}, fmt.Errorf(\"non-2xx status: %s\", res.Status)\n\t}\n\n\tvar j JWT\n\terr = json.Unmarshal(body, &j)\n\tif err != nil {\n\t\treturn JWT{}, err\n\t}\n\n\treturn j, nil\n}\n\n// RefreshError represents an error when refreshing the user's token.\ntype RefreshError struct {\n\tError       string `json:\"error\"`\n\tDescription string `json:\"error_description\"`\n}\n\n// APIToken is returned from the /login-enhanced endpoint.\ntype APIToken struct {\n\t// AccessToken is used to access the Fastly API.\n\tAccessToken string `json:\"access_token\"`\n\t// CustomerID is the customer ID.\n\tCustomerID string `json:\"customer_id\"`\n\t// ExpiresAt is when the access token will expire.\n\tExpiresAt string `json:\"expires_at\"`\n\t// ID is a unique ID.\n\tID string `json:\"id\"`\n\t// Name is a description of the token.\n\tName string `json:\"name\"`\n\t// UserID is the user's ID.\n\tUserID string `json:\"user_id\"`\n}\n\n// AuthorizationResult represents the result of the authorization process.\ntype AuthorizationResult struct {\n\t// Email address extracted from JWT claims.\n\tEmail string\n\t// Err is any error received during authentication.\n\tErr error\n\t// Jwt is the JWT token returned by the authorization server.\n\tJwt JWT\n\t// SessionToken is a temporary API token.\n\tSessionToken string\n}\n\n// JWT is the API response for a Token request.\n//\n// Access Token typically has a TTL of 5mins.\n// Refresh Token typically has a TTL of 30mins.\ntype JWT struct {\n\t// AccessToken can be exchanged for a Fastly API token.\n\tAccessToken string `json:\"access_token\"`\n\t// ExpiresIn indicates the lifetime (in seconds) of the access token.\n\tExpiresIn int `json:\"expires_in\"`\n\t// IDToken contains user information that must be decoded and extracted.\n\tIDToken string `json:\"id_token\"`\n\t// RefreshExpiresIn indicates the lifetime (in seconds) of the refresh token.\n\tRefreshExpiresIn int `json:\"refresh_expires_in\"`\n\t// RefreshToken contains a token used to refresh the issued access token.\n\tRefreshToken string `json:\"refresh_token\"`\n\t// TokenType indicates which HTTP authentication scheme is used (e.g. Bearer).\n\tTokenType string `json:\"token_type\"`\n}\n\n// TokenExpired indicates if the specified TTL has past.\nfunc TokenExpired(ttl int, timestamp int64) bool {\n\td := time.Duration(ttl) * time.Second\n\tttlAgo := time.Now().Add(-d).Unix()\n\treturn timestamp < ttlAgo\n}\n"
  },
  {
    "path": "pkg/auth/doc.go",
    "content": "// Package auth contains types to authenticate with Fastly.\npackage auth\n"
  },
  {
    "path": "pkg/check/check.go",
    "content": "// Package check provides functions for validating installed binaries.\npackage check\n\nimport (\n\t\"time\"\n)\n\n// Stale validates if the given time is older than the given duration.\n//\n// EXAMPLE:\n// dur is a string like \"24h\", \"10m\" or \"5s\".\nfunc Stale(lastVersionCheck string, dur string) bool {\n\tttl, err := time.ParseDuration(dur)\n\tif err != nil {\n\t\t// If there is no duration provided, then we should presume the loading of\n\t\t// remote configuration failed and that we should retry that operation.\n\t\treturn true\n\t}\n\n\tlastChecked, _ := time.Parse(time.RFC3339, lastVersionCheck)\n\treturn lastChecked.Add(ttl).Before(time.Now())\n}\n"
  },
  {
    "path": "pkg/commands/alias/acl/create.go",
    "content": "package acl\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/acl\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// CreateCommand wraps the CreateCommand from the newcmd package.\ntype CreateCommand struct {\n\t*newcmd.CreateCommand\n}\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{newcmd.NewCreateCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *CreateCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service acl create' command instead.\")\n\treturn c.CreateCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/acl/delete.go",
    "content": "package acl\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/acl\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DeleteCommand wraps the DeleteCommand from the newcmd package.\ntype DeleteCommand struct {\n\t*newcmd.DeleteCommand\n}\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{newcmd.NewDeleteCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *DeleteCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service acl delete' command instead.\")\n\treturn c.DeleteCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/acl/describe.go",
    "content": "package acl\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/acl\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DescribeCommand wraps the DescribeCommand from the newcmd package.\ntype DescribeCommand struct {\n\t*newcmd.DescribeCommand\n}\n\n// NewDescribeCommand returns a usable command registered under the parent.\nfunc NewDescribeCommand(parent argparser.Registerer, g *global.Data) *DescribeCommand {\n\tc := DescribeCommand{newcmd.NewDescribeCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *DescribeCommand) Exec(in io.Reader, out io.Writer) error {\n\tif !c.JSONOutput.Enabled {\n\t\ttext.Deprecated(\"Use the 'service acl describe' command instead.\")\n\t}\n\treturn c.DescribeCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/acl/doc.go",
    "content": "// Package acl contains deprecated aliases for the 'service acl' commands.\npackage acl\n"
  },
  {
    "path": "pkg/commands/alias/acl/list.go",
    "content": "package acl\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/acl\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// ListCommand wraps the ListCommand from the newcmd package.\ntype ListCommand struct {\n\t*newcmd.ListCommand\n}\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{newcmd.NewListCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *ListCommand) Exec(in io.Reader, out io.Writer) error {\n\tif !c.JSONOutput.Enabled {\n\t\ttext.Deprecated(\"Use the 'service acl list' command instead.\")\n\t}\n\treturn c.ListCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/acl/root.go",
    "content": "package acl\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"acl\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manipulate Fastly ACLs\").Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/alias/acl/update.go",
    "content": "package acl\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/acl\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// UpdateCommand wraps the UpdateCommand from the newcmd package.\ntype UpdateCommand struct {\n\t*newcmd.UpdateCommand\n}\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{newcmd.NewUpdateCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *UpdateCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service acl update' command instead.\")\n\treturn c.UpdateCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/aclentry/create.go",
    "content": "package aclentry\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/aclentry\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// CreateCommand wraps the CreateCommand from the newcmd package.\ntype CreateCommand struct {\n\t*newcmd.CreateCommand\n}\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{newcmd.NewCreateCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *CreateCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service aclentry create' command instead.\")\n\treturn c.CreateCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/aclentry/delete.go",
    "content": "package aclentry\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/aclentry\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DeleteCommand wraps the DeleteCommand from the newcmd package.\ntype DeleteCommand struct {\n\t*newcmd.DeleteCommand\n}\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{newcmd.NewDeleteCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *DeleteCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service aclentry delete' command instead.\")\n\treturn c.DeleteCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/aclentry/describe.go",
    "content": "package aclentry\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/aclentry\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DescribeCommand wraps the DescribeCommand from the newcmd package.\ntype DescribeCommand struct {\n\t*newcmd.DescribeCommand\n}\n\n// NewDescribeCommand returns a usable command registered under the parent.\nfunc NewDescribeCommand(parent argparser.Registerer, g *global.Data) *DescribeCommand {\n\tc := DescribeCommand{newcmd.NewDescribeCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *DescribeCommand) Exec(in io.Reader, out io.Writer) error {\n\tif !c.JSONOutput.Enabled {\n\t\ttext.Deprecated(\"Use the 'service aclentry describe' command instead.\")\n\t}\n\treturn c.DescribeCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/aclentry/doc.go",
    "content": "// Package aclentry contains deprecated aliases for the 'service aclentry' commands.\npackage aclentry\n"
  },
  {
    "path": "pkg/commands/alias/aclentry/list.go",
    "content": "package aclentry\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/aclentry\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// ListCommand wraps the ListCommand from the newcmd package.\ntype ListCommand struct {\n\t*newcmd.ListCommand\n}\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{newcmd.NewListCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *ListCommand) Exec(in io.Reader, out io.Writer) error {\n\tif !c.JSONOutput.Enabled {\n\t\ttext.Deprecated(\"Use the 'service aclentry list' command instead.\")\n\t}\n\treturn c.ListCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/aclentry/root.go",
    "content": "package aclentry\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"aclentry\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manipulate Fastly ACLs\").Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/alias/aclentry/update.go",
    "content": "package aclentry\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/aclentry\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// UpdateCommand wraps the UpdateCommand from the newcmd package.\ntype UpdateCommand struct {\n\t*newcmd.UpdateCommand\n}\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{newcmd.NewUpdateCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *UpdateCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service aclentry update' command instead.\")\n\treturn c.UpdateCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/alerts/create.go",
    "content": "package alerts\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/alert\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// CreateCommand wraps the CreateCommand from the newcmd package.\ntype CreateCommand struct {\n\t*newcmd.CreateCommand\n}\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{newcmd.NewCreateCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *CreateCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service alert create' command instead.\")\n\treturn c.CreateCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/alerts/delete.go",
    "content": "package alerts\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/alert\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DeleteCommand wraps the DeleteCommand from the newcmd package.\ntype DeleteCommand struct {\n\t*newcmd.DeleteCommand\n}\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{newcmd.NewDeleteCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *DeleteCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service alert delete' command instead.\")\n\treturn c.DeleteCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/alerts/describe.go",
    "content": "package alerts\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/alert\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DescribeCommand wraps the DescribeCommand from the newcmd package.\ntype DescribeCommand struct {\n\t*newcmd.DescribeCommand\n}\n\n// NewDescribeCommand returns a usable command registered under the parent.\nfunc NewDescribeCommand(parent argparser.Registerer, g *global.Data) *DescribeCommand {\n\tc := DescribeCommand{newcmd.NewDescribeCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *DescribeCommand) Exec(in io.Reader, out io.Writer) error {\n\tif !c.JSONOutput.Enabled {\n\t\ttext.Deprecated(\"Use the 'service alert describe' command instead.\")\n\t}\n\treturn c.DescribeCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/alerts/doc.go",
    "content": "// Package alerts contains deprecated aliases for the 'service alert' commands.\npackage alerts\n"
  },
  {
    "path": "pkg/commands/alias/alerts/history.go",
    "content": "package alerts\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/alert\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// ListHistoryCommand wraps the ListHistoryCommand from the newcmd package.\ntype ListHistoryCommand struct {\n\t*newcmd.ListHistoryCommand\n}\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListHistoryCommand(parent argparser.Registerer, g *global.Data) *ListHistoryCommand {\n\tc := ListHistoryCommand{newcmd.NewListHistoryCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *ListHistoryCommand) Exec(in io.Reader, out io.Writer) error {\n\tif !c.JSONOutput.Enabled {\n\t\ttext.Deprecated(\"Use the 'service alert list history' command instead.\")\n\t}\n\treturn c.ListHistoryCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/alerts/list.go",
    "content": "package alerts\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/alert\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// ListCommand wraps the ListCommand from the newcmd package.\ntype ListCommand struct {\n\t*newcmd.ListCommand\n}\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{newcmd.NewListCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *ListCommand) Exec(in io.Reader, out io.Writer) error {\n\tif !c.JSONOutput.Enabled {\n\t\ttext.Deprecated(\"Use the 'service alert list' command instead.\")\n\t}\n\treturn c.ListCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/alerts/root.go",
    "content": "package alerts\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"alerts\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manipulate Fastly Alerts\").Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/alias/alerts/update.go",
    "content": "package alerts\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/alert\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// UpdateCommand wraps the UpdateCommand from the newcmd package.\ntype UpdateCommand struct {\n\t*newcmd.UpdateCommand\n}\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{newcmd.NewUpdateCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *UpdateCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service alert update' command instead.\")\n\treturn c.UpdateCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/backend/create.go",
    "content": "package backend\n\nimport (\n\t\"io\"\n\n\tservicebackend \"github.com/fastly/cli/pkg/commands/service/backend\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// CreateCommand wraps the CreateCommand from the servicebackend package.\ntype CreateCommand struct {\n\t*servicebackend.CreateCommand\n}\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{servicebackend.NewCreateCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *CreateCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service backend create' command instead.\")\n\treturn c.CreateCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/backend/delete.go",
    "content": "package backend\n\nimport (\n\t\"io\"\n\n\tservicebackend \"github.com/fastly/cli/pkg/commands/service/backend\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DeleteCommand wraps the DeleteCommand from the servicebackend package.\ntype DeleteCommand struct {\n\t*servicebackend.DeleteCommand\n}\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{servicebackend.NewDeleteCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *DeleteCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service backend delete' command instead.\")\n\treturn c.DeleteCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/backend/describe.go",
    "content": "package backend\n\nimport (\n\t\"io\"\n\n\tservicebackend \"github.com/fastly/cli/pkg/commands/service/backend\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DescribeCommand wraps the DescribeCommand from the servicebackend package.\ntype DescribeCommand struct {\n\t*servicebackend.DescribeCommand\n}\n\n// NewDescribeCommand returns a usable command registered under the parent.\nfunc NewDescribeCommand(parent argparser.Registerer, g *global.Data) *DescribeCommand {\n\tc := DescribeCommand{servicebackend.NewDescribeCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *DescribeCommand) Exec(in io.Reader, out io.Writer) error {\n\tif !c.JSONOutput.Enabled {\n\t\ttext.Deprecated(\"Use the 'service backend describe' command instead.\")\n\t}\n\treturn c.DescribeCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/backend/doc.go",
    "content": "// Package backend contains the 'backend' alias for the 'service backend' command.\npackage backend\n"
  },
  {
    "path": "pkg/commands/alias/backend/list.go",
    "content": "package backend\n\nimport (\n\t\"io\"\n\n\tservicebackend \"github.com/fastly/cli/pkg/commands/service/backend\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// ListCommand wraps the ListCommand from the servicebackend package.\ntype ListCommand struct {\n\t*servicebackend.ListCommand\n}\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{servicebackend.NewListCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *ListCommand) Exec(in io.Reader, out io.Writer) error {\n\tif !c.JSONOutput.Enabled {\n\t\ttext.Deprecated(\"Use the 'service backend list' command instead.\")\n\t}\n\treturn c.ListCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/backend/root.go",
    "content": "package backend\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"backend\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manipulate Fastly service version backends\").Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/alias/backend/update.go",
    "content": "package backend\n\nimport (\n\t\"io\"\n\n\tservicebackend \"github.com/fastly/cli/pkg/commands/service/backend\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// UpdateCommand wraps the UpdateCommand from the servicebackend package.\ntype UpdateCommand struct {\n\t*servicebackend.UpdateCommand\n}\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{servicebackend.NewUpdateCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *UpdateCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service backend update' command instead.\")\n\treturn c.UpdateCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/dictionary/create.go",
    "content": "package dictionary\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/dictionary\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// CreateCommand wraps the CreateCommand from the newcmd package.\ntype CreateCommand struct {\n\t*newcmd.CreateCommand\n}\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{newcmd.NewCreateCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *CreateCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service dictionary create' command instead.\")\n\treturn c.CreateCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/dictionary/delete.go",
    "content": "package dictionary\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/dictionary\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DeleteCommand wraps the DeleteCommand from the newcmd package.\ntype DeleteCommand struct {\n\t*newcmd.DeleteCommand\n}\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{newcmd.NewDeleteCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *DeleteCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service dictionary delete' command instead.\")\n\treturn c.DeleteCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/dictionary/describe.go",
    "content": "package dictionary\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/dictionary\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DescribeCommand wraps the DescribeCommand from the newcmd package.\ntype DescribeCommand struct {\n\t*newcmd.DescribeCommand\n}\n\n// NewDescribeCommand returns a usable command registered under the parent.\nfunc NewDescribeCommand(parent argparser.Registerer, g *global.Data) *DescribeCommand {\n\tc := DescribeCommand{newcmd.NewDescribeCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *DescribeCommand) Exec(in io.Reader, out io.Writer) error {\n\tif !c.JSONOutput.Enabled {\n\t\ttext.Deprecated(\"Use the 'service dictionary describe' command instead.\")\n\t}\n\treturn c.DescribeCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/dictionary/doc.go",
    "content": "// Package dictionary contains deprecated aliases for the 'service dictionary' commands.\npackage dictionary\n"
  },
  {
    "path": "pkg/commands/alias/dictionary/list.go",
    "content": "package dictionary\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/dictionary\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// ListCommand wraps the ListCommand from the newcmd package.\ntype ListCommand struct {\n\t*newcmd.ListCommand\n}\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{newcmd.NewListCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *ListCommand) Exec(in io.Reader, out io.Writer) error {\n\tif !c.JSONOutput.Enabled {\n\t\ttext.Deprecated(\"Use the 'service dictionary list' command instead.\")\n\t}\n\treturn c.ListCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/dictionary/root.go",
    "content": "package dictionary\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"dictionary\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manipulate FastlyService Dictionaries\").Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/alias/dictionary/update.go",
    "content": "package dictionary\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/dictionary\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// UpdateCommand wraps the UpdateCommand from the newcmd package.\ntype UpdateCommand struct {\n\t*newcmd.UpdateCommand\n}\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{newcmd.NewUpdateCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *UpdateCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service dictionary update' command instead.\")\n\treturn c.UpdateCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/dictionaryentry/create.go",
    "content": "package dictionaryentry\n\nimport (\n\t\"io\"\n\n\tservicedictionaryentry \"github.com/fastly/cli/pkg/commands/service/dictionaryentry\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// CreateCommand wraps the CreateCommand from the servicedictionaryentry package.\ntype CreateCommand struct {\n\t*servicedictionaryentry.CreateCommand\n}\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{servicedictionaryentry.NewCreateCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *CreateCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service dictionary-entry create' command instead.\")\n\treturn c.CreateCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/dictionaryentry/delete.go",
    "content": "package dictionaryentry\n\nimport (\n\t\"io\"\n\n\tservicedictionaryentry \"github.com/fastly/cli/pkg/commands/service/dictionaryentry\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DeleteCommand wraps the DeleteCommand from the servicedictionaryentry package.\ntype DeleteCommand struct {\n\t*servicedictionaryentry.DeleteCommand\n}\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{servicedictionaryentry.NewDeleteCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *DeleteCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service dictionary-entry delete' command instead.\")\n\treturn c.DeleteCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/dictionaryentry/describe.go",
    "content": "package dictionaryentry\n\nimport (\n\t\"io\"\n\n\tservicedictionaryentry \"github.com/fastly/cli/pkg/commands/service/dictionaryentry\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DescribeCommand wraps the DescribeCommand from the servicedictionaryentry package.\ntype DescribeCommand struct {\n\t*servicedictionaryentry.DescribeCommand\n}\n\n// NewDescribeCommand returns a usable command registered under the parent.\nfunc NewDescribeCommand(parent argparser.Registerer, g *global.Data) *DescribeCommand {\n\tc := DescribeCommand{servicedictionaryentry.NewDescribeCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *DescribeCommand) Exec(in io.Reader, out io.Writer) error {\n\tif !c.JSONOutput.Enabled {\n\t\ttext.Deprecated(\"Use the 'service dictionary-entry describe' command instead.\")\n\t}\n\treturn c.DescribeCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/dictionaryentry/doc.go",
    "content": "// Package dictionaryentry contains the 'dictionary-entry' alias for the 'service dictionary-entry' command.\npackage dictionaryentry\n"
  },
  {
    "path": "pkg/commands/alias/dictionaryentry/list.go",
    "content": "package dictionaryentry\n\nimport (\n\t\"io\"\n\n\tservicedictionaryentry \"github.com/fastly/cli/pkg/commands/service/dictionaryentry\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// ListCommand wraps the ListCommand from the servicedictionaryentry package.\ntype ListCommand struct {\n\t*servicedictionaryentry.ListCommand\n}\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{servicedictionaryentry.NewListCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *ListCommand) Exec(in io.Reader, out io.Writer) error {\n\tif !c.JSONOutput.Enabled {\n\t\ttext.Deprecated(\"Use the 'service dictionary-entry list' command instead.\")\n\t}\n\treturn c.ListCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/dictionaryentry/root.go",
    "content": "package dictionaryentry\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"dictionary-entry\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manipulate Fastly edge dictionary items\").Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/alias/dictionaryentry/update.go",
    "content": "package dictionaryentry\n\nimport (\n\t\"io\"\n\n\tservicedictionaryentry \"github.com/fastly/cli/pkg/commands/service/dictionaryentry\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// UpdateCommand wraps the UpdateCommand from the servicedictionaryentry package.\ntype UpdateCommand struct {\n\t*servicedictionaryentry.UpdateCommand\n}\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{servicedictionaryentry.NewUpdateCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *UpdateCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service dictionary-entry update' command instead.\")\n\treturn c.UpdateCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/doc.go",
    "content": "// Package alias contains aliases for commands which have been renamed/relocated and are deprecated.\npackage alias\n"
  },
  {
    "path": "pkg/commands/alias/healthcheck/create.go",
    "content": "package healthcheck\n\nimport (\n\t\"io\"\n\n\tservicehealthcheck \"github.com/fastly/cli/pkg/commands/service/healthcheck\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// CreateCommand wraps the CreateCommand from the servicehealthcheck package.\ntype CreateCommand struct {\n\t*servicehealthcheck.CreateCommand\n}\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{servicehealthcheck.NewCreateCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *CreateCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service healthcheck create' command instead.\")\n\treturn c.CreateCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/healthcheck/delete.go",
    "content": "package healthcheck\n\nimport (\n\t\"io\"\n\n\tservicehealthcheck \"github.com/fastly/cli/pkg/commands/service/healthcheck\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DeleteCommand wraps the DeleteCommand from the servicehealthcheck package.\ntype DeleteCommand struct {\n\t*servicehealthcheck.DeleteCommand\n}\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{servicehealthcheck.NewDeleteCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *DeleteCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service healthcheck delete' command instead.\")\n\treturn c.DeleteCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/healthcheck/describe.go",
    "content": "package healthcheck\n\nimport (\n\t\"io\"\n\n\tservicehealthcheck \"github.com/fastly/cli/pkg/commands/service/healthcheck\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DescribeCommand wraps the DescribeCommand from the servicehealthcheck package.\ntype DescribeCommand struct {\n\t*servicehealthcheck.DescribeCommand\n}\n\n// NewDescribeCommand returns a usable command registered under the parent.\nfunc NewDescribeCommand(parent argparser.Registerer, g *global.Data) *DescribeCommand {\n\tc := DescribeCommand{servicehealthcheck.NewDescribeCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *DescribeCommand) Exec(in io.Reader, out io.Writer) error {\n\tif !c.JSONOutput.Enabled {\n\t\ttext.Deprecated(\"Use the 'service healthcheck describe' command instead.\")\n\t}\n\treturn c.DescribeCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/healthcheck/doc.go",
    "content": "// Package healthcheck contains the 'healthcheck' alias for the 'service healthcheck' command.\npackage healthcheck\n"
  },
  {
    "path": "pkg/commands/alias/healthcheck/list.go",
    "content": "package healthcheck\n\nimport (\n\t\"io\"\n\n\tservicehealthcheck \"github.com/fastly/cli/pkg/commands/service/healthcheck\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// ListCommand wraps the ListCommand from the servicehealthcheck package.\ntype ListCommand struct {\n\t*servicehealthcheck.ListCommand\n}\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{servicehealthcheck.NewListCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *ListCommand) Exec(in io.Reader, out io.Writer) error {\n\tif !c.JSONOutput.Enabled {\n\t\ttext.Deprecated(\"Use the 'service healthcheck list' command instead.\")\n\t}\n\treturn c.ListCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/healthcheck/root.go",
    "content": "package healthcheck\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"healthcheck\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manipulate Fastly service version healthchecks\").Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/alias/healthcheck/update.go",
    "content": "package healthcheck\n\nimport (\n\t\"io\"\n\n\tservicehealthcheck \"github.com/fastly/cli/pkg/commands/service/healthcheck\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// UpdateCommand wraps the UpdateCommand from the servicehealthcheck package.\ntype UpdateCommand struct {\n\t*servicehealthcheck.UpdateCommand\n}\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{servicehealthcheck.NewUpdateCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *UpdateCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service healthcheck update' command instead.\")\n\treturn c.UpdateCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/imageoptimizerdefaults/doc.go",
    "content": "// Package imageoptimizerdefaults contains deprecated aliases for the 'service imageoptimizerdefaults' commands.\npackage imageoptimizerdefaults\n"
  },
  {
    "path": "pkg/commands/alias/imageoptimizerdefaults/get.go",
    "content": "package imageoptimizerdefaults\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/imageoptimizerdefaults\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// GetCommand wraps the GetCommand from the newcmd package.\ntype GetCommand struct {\n\t*newcmd.GetCommand\n}\n\n// NewGetCommand returns a usable command registered under the parent.\nfunc NewGetCommand(parent argparser.Registerer, g *global.Data) *GetCommand {\n\tc := GetCommand{newcmd.NewGetCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *GetCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service imageoptimizerdefaults get' command instead.\")\n\treturn c.GetCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/imageoptimizerdefaults/root.go",
    "content": "package imageoptimizerdefaults\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"imageoptimizerdefaults\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manipulate Fastly Image Optimizer Defaults\").Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/alias/imageoptimizerdefaults/update.go",
    "content": "package imageoptimizerdefaults\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/imageoptimizerdefaults\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// UpdateCommand wraps the UpdateCommand from the newcmd package.\ntype UpdateCommand struct {\n\t*newcmd.UpdateCommand\n}\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{newcmd.NewUpdateCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *UpdateCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service imageoptimizerdefaults update' command instead.\")\n\treturn c.UpdateCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/azureblob/create.go",
    "content": "package azureblob\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/azureblob\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// CreateCommand wraps the CreateCommand from the newcmd package.\ntype CreateCommand struct {\n\t*newcmd.CreateCommand\n}\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{newcmd.NewCreateCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *CreateCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service logging azureblob create' command instead.\")\n\treturn c.CreateCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/azureblob/delete.go",
    "content": "package azureblob\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/azureblob\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DeleteCommand wraps the DeleteCommand from the newcmd package.\ntype DeleteCommand struct {\n\t*newcmd.DeleteCommand\n}\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{newcmd.NewDeleteCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *DeleteCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service logging azureblob delete' command instead.\")\n\treturn c.DeleteCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/azureblob/describe.go",
    "content": "package azureblob\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/azureblob\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DescribeCommand wraps the DescribeCommand from the newcmd package.\ntype DescribeCommand struct {\n\t*newcmd.DescribeCommand\n}\n\n// NewDescribeCommand returns a usable command registered under the parent.\nfunc NewDescribeCommand(parent argparser.Registerer, g *global.Data) *DescribeCommand {\n\tc := DescribeCommand{newcmd.NewDescribeCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *DescribeCommand) Exec(in io.Reader, out io.Writer) error {\n\tif !c.JSONOutput.Enabled {\n\t\ttext.Deprecated(\"Use the 'service logging azureblob describe' command instead.\")\n\t}\n\treturn c.DescribeCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/azureblob/doc.go",
    "content": "// Package azureblob contains deprecated aliases for the 'service logging azureblob' commands.\npackage azureblob\n"
  },
  {
    "path": "pkg/commands/alias/logging/azureblob/list.go",
    "content": "package azureblob\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/azureblob\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// ListCommand wraps the ListCommand from the newcmd package.\ntype ListCommand struct {\n\t*newcmd.ListCommand\n}\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{newcmd.NewListCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *ListCommand) Exec(in io.Reader, out io.Writer) error {\n\tif !c.JSONOutput.Enabled {\n\t\ttext.Deprecated(\"Use the 'service logging azureblob list' command instead.\")\n\t}\n\treturn c.ListCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/azureblob/root.go",
    "content": "package azureblob\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"azureblob\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manipulate Fastly service version Azure Blob Storage logging endpoints\").Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/azureblob/update.go",
    "content": "package azureblob\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/azureblob\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// UpdateCommand wraps the UpdateCommand from the newcmd package.\ntype UpdateCommand struct {\n\t*newcmd.UpdateCommand\n}\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{newcmd.NewUpdateCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *UpdateCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service logging azureblob update' command instead.\")\n\treturn c.UpdateCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/bigquery/create.go",
    "content": "package bigquery\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/bigquery\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// CreateCommand wraps the CreateCommand from the newcmd package.\ntype CreateCommand struct {\n\t*newcmd.CreateCommand\n}\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{newcmd.NewCreateCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *CreateCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service logging bigquery create' command instead.\")\n\treturn c.CreateCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/bigquery/delete.go",
    "content": "package bigquery\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/bigquery\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DeleteCommand wraps the DeleteCommand from the newcmd package.\ntype DeleteCommand struct {\n\t*newcmd.DeleteCommand\n}\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{newcmd.NewDeleteCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *DeleteCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service logging bigquery delete' command instead.\")\n\treturn c.DeleteCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/bigquery/describe.go",
    "content": "package bigquery\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/bigquery\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DescribeCommand wraps the DescribeCommand from the newcmd package.\ntype DescribeCommand struct {\n\t*newcmd.DescribeCommand\n}\n\n// NewDescribeCommand returns a usable command registered under the parent.\nfunc NewDescribeCommand(parent argparser.Registerer, g *global.Data) *DescribeCommand {\n\tc := DescribeCommand{newcmd.NewDescribeCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *DescribeCommand) Exec(in io.Reader, out io.Writer) error {\n\tif !c.JSONOutput.Enabled {\n\t\ttext.Deprecated(\"Use the 'service logging bigquery describe' command instead.\")\n\t}\n\treturn c.DescribeCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/bigquery/doc.go",
    "content": "// Package bigquery contains deprecated aliases for the 'service logging bigquery' commands.\npackage bigquery\n"
  },
  {
    "path": "pkg/commands/alias/logging/bigquery/list.go",
    "content": "package bigquery\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/bigquery\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// ListCommand wraps the ListCommand from the newcmd package.\ntype ListCommand struct {\n\t*newcmd.ListCommand\n}\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{newcmd.NewListCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *ListCommand) Exec(in io.Reader, out io.Writer) error {\n\tif !c.JSONOutput.Enabled {\n\t\ttext.Deprecated(\"Use the 'service logging bigquery list' command instead.\")\n\t}\n\treturn c.ListCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/bigquery/root.go",
    "content": "package bigquery\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"bigquery\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manipulate Fastly service version Google BigQuery logging endpoints\").Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/bigquery/update.go",
    "content": "package bigquery\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/bigquery\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// UpdateCommand wraps the UpdateCommand from the newcmd package.\ntype UpdateCommand struct {\n\t*newcmd.UpdateCommand\n}\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{newcmd.NewUpdateCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *UpdateCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service logging bigquery update' command instead.\")\n\treturn c.UpdateCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/cloudfiles/create.go",
    "content": "package cloudfiles\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/cloudfiles\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// CreateCommand wraps the CreateCommand from the newcmd package.\ntype CreateCommand struct {\n\t*newcmd.CreateCommand\n}\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{newcmd.NewCreateCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *CreateCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service logging cloudfiles create' command instead.\")\n\treturn c.CreateCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/cloudfiles/delete.go",
    "content": "package cloudfiles\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/cloudfiles\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DeleteCommand wraps the DeleteCommand from the newcmd package.\ntype DeleteCommand struct {\n\t*newcmd.DeleteCommand\n}\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{newcmd.NewDeleteCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *DeleteCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service logging cloudfiles delete' command instead.\")\n\treturn c.DeleteCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/cloudfiles/describe.go",
    "content": "package cloudfiles\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/cloudfiles\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DescribeCommand wraps the DescribeCommand from the newcmd package.\ntype DescribeCommand struct {\n\t*newcmd.DescribeCommand\n}\n\n// NewDescribeCommand returns a usable command registered under the parent.\nfunc NewDescribeCommand(parent argparser.Registerer, g *global.Data) *DescribeCommand {\n\tc := DescribeCommand{newcmd.NewDescribeCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *DescribeCommand) Exec(in io.Reader, out io.Writer) error {\n\tif !c.JSONOutput.Enabled {\n\t\ttext.Deprecated(\"Use the 'service logging cloudfiles describe' command instead.\")\n\t}\n\treturn c.DescribeCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/cloudfiles/doc.go",
    "content": "// Package cloudfiles contains deprecated aliases for the 'service logging cloudfiles' commands.\npackage cloudfiles\n"
  },
  {
    "path": "pkg/commands/alias/logging/cloudfiles/list.go",
    "content": "package cloudfiles\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/cloudfiles\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// ListCommand wraps the ListCommand from the newcmd package.\ntype ListCommand struct {\n\t*newcmd.ListCommand\n}\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{newcmd.NewListCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *ListCommand) Exec(in io.Reader, out io.Writer) error {\n\tif !c.JSONOutput.Enabled {\n\t\ttext.Deprecated(\"Use the 'service logging cloudfiles list' command instead.\")\n\t}\n\treturn c.ListCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/cloudfiles/root.go",
    "content": "package cloudfiles\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"cloudfiles\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manipulate Fastly service version Rackspace Cloud Files logging endpoints\").Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/cloudfiles/update.go",
    "content": "package cloudfiles\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/cloudfiles\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// UpdateCommand wraps the UpdateCommand from the newcmd package.\ntype UpdateCommand struct {\n\t*newcmd.UpdateCommand\n}\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{newcmd.NewUpdateCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *UpdateCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service logging cloudfiles update' command instead.\")\n\treturn c.UpdateCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/datadog/create.go",
    "content": "package datadog\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/datadog\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// CreateCommand wraps the CreateCommand from the newcmd package.\ntype CreateCommand struct {\n\t*newcmd.CreateCommand\n}\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{newcmd.NewCreateCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *CreateCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service logging datadog create' command instead.\")\n\treturn c.CreateCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/datadog/delete.go",
    "content": "package datadog\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/datadog\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DeleteCommand wraps the DeleteCommand from the newcmd package.\ntype DeleteCommand struct {\n\t*newcmd.DeleteCommand\n}\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{newcmd.NewDeleteCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *DeleteCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service logging datadog delete' command instead.\")\n\treturn c.DeleteCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/datadog/describe.go",
    "content": "package datadog\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/datadog\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DescribeCommand wraps the DescribeCommand from the newcmd package.\ntype DescribeCommand struct {\n\t*newcmd.DescribeCommand\n}\n\n// NewDescribeCommand returns a usable command registered under the parent.\nfunc NewDescribeCommand(parent argparser.Registerer, g *global.Data) *DescribeCommand {\n\tc := DescribeCommand{newcmd.NewDescribeCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *DescribeCommand) Exec(in io.Reader, out io.Writer) error {\n\tif !c.JSONOutput.Enabled {\n\t\ttext.Deprecated(\"Use the 'service logging datadog describe' command instead.\")\n\t}\n\treturn c.DescribeCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/datadog/doc.go",
    "content": "// Package datadog contains deprecated aliases for the 'service logging datadog' commands.\npackage datadog\n"
  },
  {
    "path": "pkg/commands/alias/logging/datadog/list.go",
    "content": "package datadog\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/datadog\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// ListCommand wraps the ListCommand from the newcmd package.\ntype ListCommand struct {\n\t*newcmd.ListCommand\n}\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{newcmd.NewListCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *ListCommand) Exec(in io.Reader, out io.Writer) error {\n\tif !c.JSONOutput.Enabled {\n\t\ttext.Deprecated(\"Use the 'service logging datadog list' command instead.\")\n\t}\n\treturn c.ListCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/datadog/root.go",
    "content": "package datadog\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"datadog\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manipulate Fastly service version Datadog logging endpoints\").Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/datadog/update.go",
    "content": "package datadog\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/datadog\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// UpdateCommand wraps the UpdateCommand from the newcmd package.\ntype UpdateCommand struct {\n\t*newcmd.UpdateCommand\n}\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{newcmd.NewUpdateCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *UpdateCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service logging datadog update' command instead.\")\n\treturn c.UpdateCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/digitalocean/create.go",
    "content": "package digitalocean\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/digitalocean\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// CreateCommand wraps the CreateCommand from the newcmd package.\ntype CreateCommand struct {\n\t*newcmd.CreateCommand\n}\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{newcmd.NewCreateCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *CreateCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service logging digitalocean create' command instead.\")\n\treturn c.CreateCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/digitalocean/delete.go",
    "content": "package digitalocean\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/digitalocean\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DeleteCommand wraps the DeleteCommand from the newcmd package.\ntype DeleteCommand struct {\n\t*newcmd.DeleteCommand\n}\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{newcmd.NewDeleteCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *DeleteCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service logging digitalocean delete' command instead.\")\n\treturn c.DeleteCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/digitalocean/describe.go",
    "content": "package digitalocean\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/digitalocean\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DescribeCommand wraps the DescribeCommand from the newcmd package.\ntype DescribeCommand struct {\n\t*newcmd.DescribeCommand\n}\n\n// NewDescribeCommand returns a usable command registered under the parent.\nfunc NewDescribeCommand(parent argparser.Registerer, g *global.Data) *DescribeCommand {\n\tc := DescribeCommand{newcmd.NewDescribeCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *DescribeCommand) Exec(in io.Reader, out io.Writer) error {\n\tif !c.JSONOutput.Enabled {\n\t\ttext.Deprecated(\"Use the 'service logging digitalocean describe' command instead.\")\n\t}\n\treturn c.DescribeCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/digitalocean/doc.go",
    "content": "// Package digitalocean contains deprecated aliases for the 'service logging digitalocean' commands.\npackage digitalocean\n"
  },
  {
    "path": "pkg/commands/alias/logging/digitalocean/list.go",
    "content": "package digitalocean\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/digitalocean\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// ListCommand wraps the ListCommand from the newcmd package.\ntype ListCommand struct {\n\t*newcmd.ListCommand\n}\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{newcmd.NewListCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *ListCommand) Exec(in io.Reader, out io.Writer) error {\n\tif !c.JSONOutput.Enabled {\n\t\ttext.Deprecated(\"Use the 'service logging digitalocean list' command instead.\")\n\t}\n\treturn c.ListCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/digitalocean/root.go",
    "content": "package digitalocean\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"digitalocean\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manipulate Fastly service version DigitalOcean Spaces logging endpoints\").Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/digitalocean/update.go",
    "content": "package digitalocean\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/digitalocean\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// UpdateCommand wraps the UpdateCommand from the newcmd package.\ntype UpdateCommand struct {\n\t*newcmd.UpdateCommand\n}\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{newcmd.NewUpdateCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *UpdateCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service logging digitalocean update' command instead.\")\n\treturn c.UpdateCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/doc.go",
    "content": "// Package logging contains deprecated aliases for the 'service logging' commands.\npackage logging\n"
  },
  {
    "path": "pkg/commands/alias/logging/elasticsearch/create.go",
    "content": "package elasticsearch\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/elasticsearch\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// CreateCommand wraps the CreateCommand from the newcmd package.\ntype CreateCommand struct {\n\t*newcmd.CreateCommand\n}\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{newcmd.NewCreateCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *CreateCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service logging elasticsearch create' command instead.\")\n\treturn c.CreateCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/elasticsearch/delete.go",
    "content": "package elasticsearch\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/elasticsearch\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DeleteCommand wraps the DeleteCommand from the newcmd package.\ntype DeleteCommand struct {\n\t*newcmd.DeleteCommand\n}\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{newcmd.NewDeleteCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *DeleteCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service logging elasticsearch delete' command instead.\")\n\treturn c.DeleteCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/elasticsearch/describe.go",
    "content": "package elasticsearch\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/elasticsearch\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DescribeCommand wraps the DescribeCommand from the newcmd package.\ntype DescribeCommand struct {\n\t*newcmd.DescribeCommand\n}\n\n// NewDescribeCommand returns a usable command registered under the parent.\nfunc NewDescribeCommand(parent argparser.Registerer, g *global.Data) *DescribeCommand {\n\tc := DescribeCommand{newcmd.NewDescribeCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *DescribeCommand) Exec(in io.Reader, out io.Writer) error {\n\tif !c.JSONOutput.Enabled {\n\t\ttext.Deprecated(\"Use the 'service logging elasticsearch describe' command instead.\")\n\t}\n\treturn c.DescribeCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/elasticsearch/doc.go",
    "content": "// Package elasticsearch contains deprecated aliases for the 'service logging elasticsearch' commands.\npackage elasticsearch\n"
  },
  {
    "path": "pkg/commands/alias/logging/elasticsearch/list.go",
    "content": "package elasticsearch\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/elasticsearch\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// ListCommand wraps the ListCommand from the newcmd package.\ntype ListCommand struct {\n\t*newcmd.ListCommand\n}\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{newcmd.NewListCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *ListCommand) Exec(in io.Reader, out io.Writer) error {\n\tif !c.JSONOutput.Enabled {\n\t\ttext.Deprecated(\"Use the 'service logging elasticsearch list' command instead.\")\n\t}\n\treturn c.ListCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/elasticsearch/root.go",
    "content": "package elasticsearch\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"elasticsearch\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manipulate Fastly service version Elasticsearch logging endpoints\").Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/elasticsearch/update.go",
    "content": "package elasticsearch\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/elasticsearch\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// UpdateCommand wraps the UpdateCommand from the newcmd package.\ntype UpdateCommand struct {\n\t*newcmd.UpdateCommand\n}\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{newcmd.NewUpdateCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *UpdateCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service logging elasticsearch update' command instead.\")\n\treturn c.UpdateCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/ftp/create.go",
    "content": "package ftp\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/ftp\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// CreateCommand wraps the CreateCommand from the newcmd package.\ntype CreateCommand struct {\n\t*newcmd.CreateCommand\n}\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{newcmd.NewCreateCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *CreateCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service logging ftp create' command instead.\")\n\treturn c.CreateCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/ftp/delete.go",
    "content": "package ftp\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/ftp\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DeleteCommand wraps the DeleteCommand from the newcmd package.\ntype DeleteCommand struct {\n\t*newcmd.DeleteCommand\n}\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{newcmd.NewDeleteCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *DeleteCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service logging ftp delete' command instead.\")\n\treturn c.DeleteCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/ftp/describe.go",
    "content": "package ftp\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/ftp\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DescribeCommand wraps the DescribeCommand from the newcmd package.\ntype DescribeCommand struct {\n\t*newcmd.DescribeCommand\n}\n\n// NewDescribeCommand returns a usable command registered under the parent.\nfunc NewDescribeCommand(parent argparser.Registerer, g *global.Data) *DescribeCommand {\n\tc := DescribeCommand{newcmd.NewDescribeCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *DescribeCommand) Exec(in io.Reader, out io.Writer) error {\n\tif !c.JSONOutput.Enabled {\n\t\ttext.Deprecated(\"Use the 'service logging ftp describe' command instead.\")\n\t}\n\treturn c.DescribeCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/ftp/doc.go",
    "content": "// Package ftp contains deprecated aliases for the 'service logging ftp' commands.\npackage ftp\n"
  },
  {
    "path": "pkg/commands/alias/logging/ftp/list.go",
    "content": "package ftp\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/ftp\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// ListCommand wraps the ListCommand from the newcmd package.\ntype ListCommand struct {\n\t*newcmd.ListCommand\n}\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{newcmd.NewListCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *ListCommand) Exec(in io.Reader, out io.Writer) error {\n\tif !c.JSONOutput.Enabled {\n\t\ttext.Deprecated(\"Use the 'service logging ftp list' command instead.\")\n\t}\n\treturn c.ListCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/ftp/root.go",
    "content": "package ftp\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"ftp\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manipulate Fastly service version FTP logging endpoints\").Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/ftp/update.go",
    "content": "package ftp\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/ftp\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// UpdateCommand wraps the UpdateCommand from the newcmd package.\ntype UpdateCommand struct {\n\t*newcmd.UpdateCommand\n}\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{newcmd.NewUpdateCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *UpdateCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service logging ftp update' command instead.\")\n\treturn c.UpdateCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/gcs/create.go",
    "content": "package gcs\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/gcs\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// CreateCommand wraps the CreateCommand from the newcmd package.\ntype CreateCommand struct {\n\t*newcmd.CreateCommand\n}\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{newcmd.NewCreateCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *CreateCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service logging gcs create' command instead.\")\n\treturn c.CreateCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/gcs/delete.go",
    "content": "package gcs\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/gcs\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DeleteCommand wraps the DeleteCommand from the newcmd package.\ntype DeleteCommand struct {\n\t*newcmd.DeleteCommand\n}\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{newcmd.NewDeleteCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *DeleteCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service logging gcs delete' command instead.\")\n\treturn c.DeleteCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/gcs/describe.go",
    "content": "package gcs\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/gcs\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DescribeCommand wraps the DescribeCommand from the newcmd package.\ntype DescribeCommand struct {\n\t*newcmd.DescribeCommand\n}\n\n// NewDescribeCommand returns a usable command registered under the parent.\nfunc NewDescribeCommand(parent argparser.Registerer, g *global.Data) *DescribeCommand {\n\tc := DescribeCommand{newcmd.NewDescribeCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *DescribeCommand) Exec(in io.Reader, out io.Writer) error {\n\tif !c.JSONOutput.Enabled {\n\t\ttext.Deprecated(\"Use the 'service logging gcs describe' command instead.\")\n\t}\n\treturn c.DescribeCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/gcs/doc.go",
    "content": "// Package gcs contains deprecated aliases for the 'service logging gcs' commands.\npackage gcs\n"
  },
  {
    "path": "pkg/commands/alias/logging/gcs/list.go",
    "content": "package gcs\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/gcs\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// ListCommand wraps the ListCommand from the newcmd package.\ntype ListCommand struct {\n\t*newcmd.ListCommand\n}\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{newcmd.NewListCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *ListCommand) Exec(in io.Reader, out io.Writer) error {\n\tif !c.JSONOutput.Enabled {\n\t\ttext.Deprecated(\"Use the 'service logging gcs list' command instead.\")\n\t}\n\treturn c.ListCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/gcs/root.go",
    "content": "package gcs\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"gcs\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manipulate Fastly service version Google Cloud Storage logging endpoints\").Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/gcs/update.go",
    "content": "package gcs\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/gcs\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// UpdateCommand wraps the UpdateCommand from the newcmd package.\ntype UpdateCommand struct {\n\t*newcmd.UpdateCommand\n}\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{newcmd.NewUpdateCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *UpdateCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service logging gcs update' command instead.\")\n\treturn c.UpdateCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/googlepubsub/create.go",
    "content": "package googlepubsub\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/googlepubsub\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// CreateCommand wraps the CreateCommand from the newcmd package.\ntype CreateCommand struct {\n\t*newcmd.CreateCommand\n}\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{newcmd.NewCreateCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *CreateCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service logging googlepubsub create' command instead.\")\n\treturn c.CreateCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/googlepubsub/delete.go",
    "content": "package googlepubsub\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/googlepubsub\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DeleteCommand wraps the DeleteCommand from the newcmd package.\ntype DeleteCommand struct {\n\t*newcmd.DeleteCommand\n}\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{newcmd.NewDeleteCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *DeleteCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service logging googlepubsub delete' command instead.\")\n\treturn c.DeleteCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/googlepubsub/describe.go",
    "content": "package googlepubsub\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/googlepubsub\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DescribeCommand wraps the DescribeCommand from the newcmd package.\ntype DescribeCommand struct {\n\t*newcmd.DescribeCommand\n}\n\n// NewDescribeCommand returns a usable command registered under the parent.\nfunc NewDescribeCommand(parent argparser.Registerer, g *global.Data) *DescribeCommand {\n\tc := DescribeCommand{newcmd.NewDescribeCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *DescribeCommand) Exec(in io.Reader, out io.Writer) error {\n\tif !c.JSONOutput.Enabled {\n\t\ttext.Deprecated(\"Use the 'service logging googlepubsub describe' command instead.\")\n\t}\n\treturn c.DescribeCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/googlepubsub/doc.go",
    "content": "// Package googlepubsub contains deprecated aliases for the 'service logging googlepubsub' commands.\npackage googlepubsub\n"
  },
  {
    "path": "pkg/commands/alias/logging/googlepubsub/list.go",
    "content": "package googlepubsub\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/googlepubsub\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// ListCommand wraps the ListCommand from the newcmd package.\ntype ListCommand struct {\n\t*newcmd.ListCommand\n}\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{newcmd.NewListCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *ListCommand) Exec(in io.Reader, out io.Writer) error {\n\tif !c.JSONOutput.Enabled {\n\t\ttext.Deprecated(\"Use the 'service logging googlepubsub list' command instead.\")\n\t}\n\treturn c.ListCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/googlepubsub/root.go",
    "content": "package googlepubsub\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"googlepubsub\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manipulate Fastly service version Google Cloud Pub/Sub logging endpoints\").Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/googlepubsub/update.go",
    "content": "package googlepubsub\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/googlepubsub\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// UpdateCommand wraps the UpdateCommand from the newcmd package.\ntype UpdateCommand struct {\n\t*newcmd.UpdateCommand\n}\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{newcmd.NewUpdateCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *UpdateCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service logging googlepubsub update' command instead.\")\n\treturn c.UpdateCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/grafanacloudlogs/create.go",
    "content": "package grafanacloudlogs\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/grafanacloudlogs\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// CreateCommand wraps the CreateCommand from the newcmd package.\ntype CreateCommand struct {\n\t*newcmd.CreateCommand\n}\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{newcmd.NewCreateCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *CreateCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service logging grafanacloudlogs create' command instead.\")\n\treturn c.CreateCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/grafanacloudlogs/delete.go",
    "content": "package grafanacloudlogs\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/grafanacloudlogs\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DeleteCommand wraps the DeleteCommand from the newcmd package.\ntype DeleteCommand struct {\n\t*newcmd.DeleteCommand\n}\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{newcmd.NewDeleteCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *DeleteCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service logging grafanacloudlogs delete' command instead.\")\n\treturn c.DeleteCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/grafanacloudlogs/describe.go",
    "content": "package grafanacloudlogs\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/grafanacloudlogs\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DescribeCommand wraps the DescribeCommand from the newcmd package.\ntype DescribeCommand struct {\n\t*newcmd.DescribeCommand\n}\n\n// NewDescribeCommand returns a usable command registered under the parent.\nfunc NewDescribeCommand(parent argparser.Registerer, g *global.Data) *DescribeCommand {\n\tc := DescribeCommand{newcmd.NewDescribeCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *DescribeCommand) Exec(in io.Reader, out io.Writer) error {\n\tif !c.JSONOutput.Enabled {\n\t\ttext.Deprecated(\"Use the 'service logging grafanacloudlogs describe' command instead.\")\n\t}\n\treturn c.DescribeCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/grafanacloudlogs/doc.go",
    "content": "// Package grafanacloudlogs contains deprecated aliases for the 'service logging grafanacloudlogs' commands.\npackage grafanacloudlogs\n"
  },
  {
    "path": "pkg/commands/alias/logging/grafanacloudlogs/list.go",
    "content": "package grafanacloudlogs\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/grafanacloudlogs\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// ListCommand wraps the ListCommand from the newcmd package.\ntype ListCommand struct {\n\t*newcmd.ListCommand\n}\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{newcmd.NewListCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *ListCommand) Exec(in io.Reader, out io.Writer) error {\n\tif !c.JSONOutput.Enabled {\n\t\ttext.Deprecated(\"Use the 'service logging grafanacloudlogs list' command instead.\")\n\t}\n\treturn c.ListCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/grafanacloudlogs/root.go",
    "content": "package grafanacloudlogs\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"grafanacloudlogs\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manipulate Fastly service version Grafana Cloud Logs logging endpoints\").Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/grafanacloudlogs/update.go",
    "content": "package grafanacloudlogs\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/grafanacloudlogs\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// UpdateCommand wraps the UpdateCommand from the newcmd package.\ntype UpdateCommand struct {\n\t*newcmd.UpdateCommand\n}\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{newcmd.NewUpdateCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *UpdateCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service logging grafanacloudlogs update' command instead.\")\n\treturn c.UpdateCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/heroku/create.go",
    "content": "package heroku\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/heroku\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// CreateCommand wraps the CreateCommand from the newcmd package.\ntype CreateCommand struct {\n\t*newcmd.CreateCommand\n}\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{newcmd.NewCreateCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *CreateCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service logging heroku create' command instead.\")\n\treturn c.CreateCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/heroku/delete.go",
    "content": "package heroku\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/heroku\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DeleteCommand wraps the DeleteCommand from the newcmd package.\ntype DeleteCommand struct {\n\t*newcmd.DeleteCommand\n}\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{newcmd.NewDeleteCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *DeleteCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service logging heroku delete' command instead.\")\n\treturn c.DeleteCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/heroku/describe.go",
    "content": "package heroku\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/heroku\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DescribeCommand wraps the DescribeCommand from the newcmd package.\ntype DescribeCommand struct {\n\t*newcmd.DescribeCommand\n}\n\n// NewDescribeCommand returns a usable command registered under the parent.\nfunc NewDescribeCommand(parent argparser.Registerer, g *global.Data) *DescribeCommand {\n\tc := DescribeCommand{newcmd.NewDescribeCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *DescribeCommand) Exec(in io.Reader, out io.Writer) error {\n\tif !c.JSONOutput.Enabled {\n\t\ttext.Deprecated(\"Use the 'service logging heroku describe' command instead.\")\n\t}\n\treturn c.DescribeCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/heroku/doc.go",
    "content": "// Package heroku contains deprecated aliases for the 'service logging heroku' commands.\npackage heroku\n"
  },
  {
    "path": "pkg/commands/alias/logging/heroku/list.go",
    "content": "package heroku\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/heroku\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// ListCommand wraps the ListCommand from the newcmd package.\ntype ListCommand struct {\n\t*newcmd.ListCommand\n}\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{newcmd.NewListCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *ListCommand) Exec(in io.Reader, out io.Writer) error {\n\tif !c.JSONOutput.Enabled {\n\t\ttext.Deprecated(\"Use the 'service logging heroku list' command instead.\")\n\t}\n\treturn c.ListCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/heroku/root.go",
    "content": "package heroku\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"heroku\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manipulate Fastly service version Heroku logging endpoints\").Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/heroku/update.go",
    "content": "package heroku\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/heroku\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// UpdateCommand wraps the UpdateCommand from the newcmd package.\ntype UpdateCommand struct {\n\t*newcmd.UpdateCommand\n}\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{newcmd.NewUpdateCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *UpdateCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service logging heroku update' command instead.\")\n\treturn c.UpdateCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/honeycomb/create.go",
    "content": "package honeycomb\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/honeycomb\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// CreateCommand wraps the CreateCommand from the newcmd package.\ntype CreateCommand struct {\n\t*newcmd.CreateCommand\n}\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{newcmd.NewCreateCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *CreateCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service logging honeycomb create' command instead.\")\n\treturn c.CreateCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/honeycomb/delete.go",
    "content": "package honeycomb\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/honeycomb\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DeleteCommand wraps the DeleteCommand from the newcmd package.\ntype DeleteCommand struct {\n\t*newcmd.DeleteCommand\n}\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{newcmd.NewDeleteCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *DeleteCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service logging honeycomb delete' command instead.\")\n\treturn c.DeleteCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/honeycomb/describe.go",
    "content": "package honeycomb\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/honeycomb\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DescribeCommand wraps the DescribeCommand from the newcmd package.\ntype DescribeCommand struct {\n\t*newcmd.DescribeCommand\n}\n\n// NewDescribeCommand returns a usable command registered under the parent.\nfunc NewDescribeCommand(parent argparser.Registerer, g *global.Data) *DescribeCommand {\n\tc := DescribeCommand{newcmd.NewDescribeCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *DescribeCommand) Exec(in io.Reader, out io.Writer) error {\n\tif !c.JSONOutput.Enabled {\n\t\ttext.Deprecated(\"Use the 'service logging honeycomb describe' command instead.\")\n\t}\n\treturn c.DescribeCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/honeycomb/doc.go",
    "content": "// Package honeycomb contains deprecated aliases for the 'service logging honeycomb' commands.\npackage honeycomb\n"
  },
  {
    "path": "pkg/commands/alias/logging/honeycomb/list.go",
    "content": "package honeycomb\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/honeycomb\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// ListCommand wraps the ListCommand from the newcmd package.\ntype ListCommand struct {\n\t*newcmd.ListCommand\n}\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{newcmd.NewListCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *ListCommand) Exec(in io.Reader, out io.Writer) error {\n\tif !c.JSONOutput.Enabled {\n\t\ttext.Deprecated(\"Use the 'service logging honeycomb list' command instead.\")\n\t}\n\treturn c.ListCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/honeycomb/root.go",
    "content": "package honeycomb\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"honeycomb\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manipulate Fastly service version Honeycomb logging endpoints\").Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/honeycomb/update.go",
    "content": "package honeycomb\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/honeycomb\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// UpdateCommand wraps the UpdateCommand from the newcmd package.\ntype UpdateCommand struct {\n\t*newcmd.UpdateCommand\n}\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{newcmd.NewUpdateCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *UpdateCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service logging honeycomb update' command instead.\")\n\treturn c.UpdateCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/https/create.go",
    "content": "package https\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/https\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// CreateCommand wraps the CreateCommand from the newcmd package.\ntype CreateCommand struct {\n\t*newcmd.CreateCommand\n}\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{newcmd.NewCreateCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *CreateCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service logging https create' command instead.\")\n\treturn c.CreateCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/https/delete.go",
    "content": "package https\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/https\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DeleteCommand wraps the DeleteCommand from the newcmd package.\ntype DeleteCommand struct {\n\t*newcmd.DeleteCommand\n}\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{newcmd.NewDeleteCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *DeleteCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service logging https delete' command instead.\")\n\treturn c.DeleteCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/https/describe.go",
    "content": "package https\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/https\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DescribeCommand wraps the DescribeCommand from the newcmd package.\ntype DescribeCommand struct {\n\t*newcmd.DescribeCommand\n}\n\n// NewDescribeCommand returns a usable command registered under the parent.\nfunc NewDescribeCommand(parent argparser.Registerer, g *global.Data) *DescribeCommand {\n\tc := DescribeCommand{newcmd.NewDescribeCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *DescribeCommand) Exec(in io.Reader, out io.Writer) error {\n\tif !c.JSONOutput.Enabled {\n\t\ttext.Deprecated(\"Use the 'service logging https describe' command instead.\")\n\t}\n\treturn c.DescribeCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/https/doc.go",
    "content": "// Package https contains deprecated aliases for the 'service logging https' commands.\npackage https\n"
  },
  {
    "path": "pkg/commands/alias/logging/https/list.go",
    "content": "package https\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/https\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// ListCommand wraps the ListCommand from the newcmd package.\ntype ListCommand struct {\n\t*newcmd.ListCommand\n}\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{newcmd.NewListCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *ListCommand) Exec(in io.Reader, out io.Writer) error {\n\tif !c.JSONOutput.Enabled {\n\t\ttext.Deprecated(\"Use the 'service logging https list' command instead.\")\n\t}\n\treturn c.ListCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/https/root.go",
    "content": "package https\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"https\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manipulate Fastly service version HTTPS logging endpoints\").Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/https/update.go",
    "content": "package https\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/https\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// UpdateCommand wraps the UpdateCommand from the newcmd package.\ntype UpdateCommand struct {\n\t*newcmd.UpdateCommand\n}\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{newcmd.NewUpdateCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *UpdateCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service logging https update' command instead.\")\n\treturn c.UpdateCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/kafka/create.go",
    "content": "package kafka\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/kafka\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// CreateCommand wraps the CreateCommand from the newcmd package.\ntype CreateCommand struct {\n\t*newcmd.CreateCommand\n}\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{newcmd.NewCreateCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *CreateCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service logging kafka create' command instead.\")\n\treturn c.CreateCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/kafka/delete.go",
    "content": "package kafka\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/kafka\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DeleteCommand wraps the DeleteCommand from the newcmd package.\ntype DeleteCommand struct {\n\t*newcmd.DeleteCommand\n}\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{newcmd.NewDeleteCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *DeleteCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service logging kafka delete' command instead.\")\n\treturn c.DeleteCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/kafka/describe.go",
    "content": "package kafka\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/kafka\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DescribeCommand wraps the DescribeCommand from the newcmd package.\ntype DescribeCommand struct {\n\t*newcmd.DescribeCommand\n}\n\n// NewDescribeCommand returns a usable command registered under the parent.\nfunc NewDescribeCommand(parent argparser.Registerer, g *global.Data) *DescribeCommand {\n\tc := DescribeCommand{newcmd.NewDescribeCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *DescribeCommand) Exec(in io.Reader, out io.Writer) error {\n\tif !c.JSONOutput.Enabled {\n\t\ttext.Deprecated(\"Use the 'service logging kafka describe' command instead.\")\n\t}\n\treturn c.DescribeCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/kafka/doc.go",
    "content": "// Package kafka contains deprecated aliases for the 'service logging kafka' commands.\npackage kafka\n"
  },
  {
    "path": "pkg/commands/alias/logging/kafka/list.go",
    "content": "package kafka\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/kafka\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// ListCommand wraps the ListCommand from the newcmd package.\ntype ListCommand struct {\n\t*newcmd.ListCommand\n}\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{newcmd.NewListCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *ListCommand) Exec(in io.Reader, out io.Writer) error {\n\tif !c.JSONOutput.Enabled {\n\t\ttext.Deprecated(\"Use the 'service logging kafka list' command instead.\")\n\t}\n\treturn c.ListCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/kafka/root.go",
    "content": "package kafka\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"kafka\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manipulate Fastly service version Kafka logging endpoints\").Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/kafka/update.go",
    "content": "package kafka\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/kafka\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// UpdateCommand wraps the UpdateCommand from the newcmd package.\ntype UpdateCommand struct {\n\t*newcmd.UpdateCommand\n}\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{newcmd.NewUpdateCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *UpdateCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service logging kafka update' command instead.\")\n\treturn c.UpdateCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/kinesis/create.go",
    "content": "package kinesis\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/kinesis\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// CreateCommand wraps the CreateCommand from the newcmd package.\ntype CreateCommand struct {\n\t*newcmd.CreateCommand\n}\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{newcmd.NewCreateCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *CreateCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service logging kinesis create' command instead.\")\n\treturn c.CreateCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/kinesis/delete.go",
    "content": "package kinesis\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/kinesis\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DeleteCommand wraps the DeleteCommand from the newcmd package.\ntype DeleteCommand struct {\n\t*newcmd.DeleteCommand\n}\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{newcmd.NewDeleteCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *DeleteCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service logging kinesis delete' command instead.\")\n\treturn c.DeleteCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/kinesis/describe.go",
    "content": "package kinesis\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/kinesis\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DescribeCommand wraps the DescribeCommand from the newcmd package.\ntype DescribeCommand struct {\n\t*newcmd.DescribeCommand\n}\n\n// NewDescribeCommand returns a usable command registered under the parent.\nfunc NewDescribeCommand(parent argparser.Registerer, g *global.Data) *DescribeCommand {\n\tc := DescribeCommand{newcmd.NewDescribeCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *DescribeCommand) Exec(in io.Reader, out io.Writer) error {\n\tif !c.JSONOutput.Enabled {\n\t\ttext.Deprecated(\"Use the 'service logging kinesis describe' command instead.\")\n\t}\n\treturn c.DescribeCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/kinesis/doc.go",
    "content": "// Package kinesis contains deprecated aliases for the 'service logging kinesis' commands.\npackage kinesis\n"
  },
  {
    "path": "pkg/commands/alias/logging/kinesis/list.go",
    "content": "package kinesis\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/kinesis\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// ListCommand wraps the ListCommand from the newcmd package.\ntype ListCommand struct {\n\t*newcmd.ListCommand\n}\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{newcmd.NewListCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *ListCommand) Exec(in io.Reader, out io.Writer) error {\n\tif !c.JSONOutput.Enabled {\n\t\ttext.Deprecated(\"Use the 'service logging kinesis list' command instead.\")\n\t}\n\treturn c.ListCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/kinesis/root.go",
    "content": "package kinesis\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"kinesis\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manipulate Fastly service version Amazon Kinesis logging endpoints\").Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/kinesis/update.go",
    "content": "package kinesis\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/kinesis\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// UpdateCommand wraps the UpdateCommand from the newcmd package.\ntype UpdateCommand struct {\n\t*newcmd.UpdateCommand\n}\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{newcmd.NewUpdateCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *UpdateCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service logging kinesis update' command instead.\")\n\treturn c.UpdateCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/loggly/create.go",
    "content": "package loggly\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/loggly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// CreateCommand wraps the CreateCommand from the newcmd package.\ntype CreateCommand struct {\n\t*newcmd.CreateCommand\n}\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{newcmd.NewCreateCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *CreateCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service logging loggly create' command instead.\")\n\treturn c.CreateCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/loggly/delete.go",
    "content": "package loggly\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/loggly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DeleteCommand wraps the DeleteCommand from the newcmd package.\ntype DeleteCommand struct {\n\t*newcmd.DeleteCommand\n}\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{newcmd.NewDeleteCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *DeleteCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service logging loggly delete' command instead.\")\n\treturn c.DeleteCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/loggly/describe.go",
    "content": "package loggly\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/loggly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DescribeCommand wraps the DescribeCommand from the newcmd package.\ntype DescribeCommand struct {\n\t*newcmd.DescribeCommand\n}\n\n// NewDescribeCommand returns a usable command registered under the parent.\nfunc NewDescribeCommand(parent argparser.Registerer, g *global.Data) *DescribeCommand {\n\tc := DescribeCommand{newcmd.NewDescribeCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *DescribeCommand) Exec(in io.Reader, out io.Writer) error {\n\tif !c.JSONOutput.Enabled {\n\t\ttext.Deprecated(\"Use the 'service logging loggly describe' command instead.\")\n\t}\n\treturn c.DescribeCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/loggly/doc.go",
    "content": "// Package loggly contains deprecated aliases for the 'service logging loggly' commands.\npackage loggly\n"
  },
  {
    "path": "pkg/commands/alias/logging/loggly/list.go",
    "content": "package loggly\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/loggly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// ListCommand wraps the ListCommand from the newcmd package.\ntype ListCommand struct {\n\t*newcmd.ListCommand\n}\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{newcmd.NewListCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *ListCommand) Exec(in io.Reader, out io.Writer) error {\n\tif !c.JSONOutput.Enabled {\n\t\ttext.Deprecated(\"Use the 'service logging loggly list' command instead.\")\n\t}\n\treturn c.ListCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/loggly/root.go",
    "content": "package loggly\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"loggly\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manipulate Fastly service version Loggly logging endpoints\").Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/loggly/update.go",
    "content": "package loggly\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/loggly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// UpdateCommand wraps the UpdateCommand from the newcmd package.\ntype UpdateCommand struct {\n\t*newcmd.UpdateCommand\n}\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{newcmd.NewUpdateCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *UpdateCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service logging loggly update' command instead.\")\n\treturn c.UpdateCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/logshuttle/create.go",
    "content": "package logshuttle\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/logshuttle\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// CreateCommand wraps the CreateCommand from the newcmd package.\ntype CreateCommand struct {\n\t*newcmd.CreateCommand\n}\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{newcmd.NewCreateCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *CreateCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service logging logshuttle create' command instead.\")\n\treturn c.CreateCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/logshuttle/delete.go",
    "content": "package logshuttle\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/logshuttle\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DeleteCommand wraps the DeleteCommand from the newcmd package.\ntype DeleteCommand struct {\n\t*newcmd.DeleteCommand\n}\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{newcmd.NewDeleteCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *DeleteCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service logging logshuttle delete' command instead.\")\n\treturn c.DeleteCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/logshuttle/describe.go",
    "content": "package logshuttle\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/logshuttle\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DescribeCommand wraps the DescribeCommand from the newcmd package.\ntype DescribeCommand struct {\n\t*newcmd.DescribeCommand\n}\n\n// NewDescribeCommand returns a usable command registered under the parent.\nfunc NewDescribeCommand(parent argparser.Registerer, g *global.Data) *DescribeCommand {\n\tc := DescribeCommand{newcmd.NewDescribeCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *DescribeCommand) Exec(in io.Reader, out io.Writer) error {\n\tif !c.JSONOutput.Enabled {\n\t\ttext.Deprecated(\"Use the 'service logging logshuttle describe' command instead.\")\n\t}\n\treturn c.DescribeCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/logshuttle/doc.go",
    "content": "// Package logshuttle contains deprecated aliases for the 'service logging logshuttle' commands.\npackage logshuttle\n"
  },
  {
    "path": "pkg/commands/alias/logging/logshuttle/list.go",
    "content": "package logshuttle\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/logshuttle\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// ListCommand wraps the ListCommand from the newcmd package.\ntype ListCommand struct {\n\t*newcmd.ListCommand\n}\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{newcmd.NewListCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *ListCommand) Exec(in io.Reader, out io.Writer) error {\n\tif !c.JSONOutput.Enabled {\n\t\ttext.Deprecated(\"Use the 'service logging logshuttle list' command instead.\")\n\t}\n\treturn c.ListCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/logshuttle/root.go",
    "content": "package logshuttle\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"logshuttle\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manipulate Fastly service version Log Shuttle logging endpoints\").Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/logshuttle/update.go",
    "content": "package logshuttle\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/logshuttle\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// UpdateCommand wraps the UpdateCommand from the newcmd package.\ntype UpdateCommand struct {\n\t*newcmd.UpdateCommand\n}\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{newcmd.NewUpdateCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *UpdateCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service logging logshuttle update' command instead.\")\n\treturn c.UpdateCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/newrelic/create.go",
    "content": "package newrelic\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/newrelic\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// CreateCommand wraps the CreateCommand from the newcmd package.\ntype CreateCommand struct {\n\t*newcmd.CreateCommand\n}\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{newcmd.NewCreateCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *CreateCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service logging newrelic create' command instead.\")\n\treturn c.CreateCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/newrelic/delete.go",
    "content": "package newrelic\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/newrelic\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DeleteCommand wraps the DeleteCommand from the newcmd package.\ntype DeleteCommand struct {\n\t*newcmd.DeleteCommand\n}\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{newcmd.NewDeleteCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *DeleteCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service logging newrelic delete' command instead.\")\n\treturn c.DeleteCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/newrelic/describe.go",
    "content": "package newrelic\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/newrelic\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DescribeCommand wraps the DescribeCommand from the newcmd package.\ntype DescribeCommand struct {\n\t*newcmd.DescribeCommand\n}\n\n// NewDescribeCommand returns a usable command registered under the parent.\nfunc NewDescribeCommand(parent argparser.Registerer, g *global.Data) *DescribeCommand {\n\tc := DescribeCommand{newcmd.NewDescribeCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *DescribeCommand) Exec(in io.Reader, out io.Writer) error {\n\tif !c.JSONOutput.Enabled {\n\t\ttext.Deprecated(\"Use the 'service logging newrelic describe' command instead.\")\n\t}\n\treturn c.DescribeCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/newrelic/doc.go",
    "content": "// Package newrelic contains deprecated aliases for the 'service logging newrelic' commands.\npackage newrelic\n"
  },
  {
    "path": "pkg/commands/alias/logging/newrelic/list.go",
    "content": "package newrelic\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/newrelic\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// ListCommand wraps the ListCommand from the newcmd package.\ntype ListCommand struct {\n\t*newcmd.ListCommand\n}\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{newcmd.NewListCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *ListCommand) Exec(in io.Reader, out io.Writer) error {\n\tif !c.JSONOutput.Enabled {\n\t\ttext.Deprecated(\"Use the 'service logging newrelic list' command instead.\")\n\t}\n\treturn c.ListCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/newrelic/root.go",
    "content": "package newrelic\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"newrelic\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manipulate Fastly service version New Relic logging endpoints\").Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/newrelic/update.go",
    "content": "package newrelic\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/newrelic\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// UpdateCommand wraps the UpdateCommand from the newcmd package.\ntype UpdateCommand struct {\n\t*newcmd.UpdateCommand\n}\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{newcmd.NewUpdateCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *UpdateCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service logging newrelic update' command instead.\")\n\treturn c.UpdateCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/newrelicotlp/create.go",
    "content": "package newrelicotlp\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/newrelicotlp\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// CreateCommand wraps the CreateCommand from the newcmd package.\ntype CreateCommand struct {\n\t*newcmd.CreateCommand\n}\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{newcmd.NewCreateCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *CreateCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service logging newrelicotlp create' command instead.\")\n\treturn c.CreateCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/newrelicotlp/delete.go",
    "content": "package newrelicotlp\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/newrelicotlp\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DeleteCommand wraps the DeleteCommand from the newcmd package.\ntype DeleteCommand struct {\n\t*newcmd.DeleteCommand\n}\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{newcmd.NewDeleteCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *DeleteCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service logging newrelicotlp delete' command instead.\")\n\treturn c.DeleteCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/newrelicotlp/describe.go",
    "content": "package newrelicotlp\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/newrelicotlp\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DescribeCommand wraps the DescribeCommand from the newcmd package.\ntype DescribeCommand struct {\n\t*newcmd.DescribeCommand\n}\n\n// NewDescribeCommand returns a usable command registered under the parent.\nfunc NewDescribeCommand(parent argparser.Registerer, g *global.Data) *DescribeCommand {\n\tc := DescribeCommand{newcmd.NewDescribeCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *DescribeCommand) Exec(in io.Reader, out io.Writer) error {\n\tif !c.JSONOutput.Enabled {\n\t\ttext.Deprecated(\"Use the 'service logging newrelicotlp describe' command instead.\")\n\t}\n\treturn c.DescribeCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/newrelicotlp/doc.go",
    "content": "// Package newrelicotlp contains deprecated aliases for the 'service logging newrelicotlp' commands.\npackage newrelicotlp\n"
  },
  {
    "path": "pkg/commands/alias/logging/newrelicotlp/list.go",
    "content": "package newrelicotlp\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/newrelicotlp\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// ListCommand wraps the ListCommand from the newcmd package.\ntype ListCommand struct {\n\t*newcmd.ListCommand\n}\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{newcmd.NewListCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *ListCommand) Exec(in io.Reader, out io.Writer) error {\n\tif !c.JSONOutput.Enabled {\n\t\ttext.Deprecated(\"Use the 'service logging newrelicotlp list' command instead.\")\n\t}\n\treturn c.ListCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/newrelicotlp/root.go",
    "content": "package newrelicotlp\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"newrelicotlp\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manipulate Fastly service version New Relic OTLP logging endpoints\").Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/newrelicotlp/update.go",
    "content": "package newrelicotlp\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/newrelicotlp\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// UpdateCommand wraps the UpdateCommand from the newcmd package.\ntype UpdateCommand struct {\n\t*newcmd.UpdateCommand\n}\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{newcmd.NewUpdateCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *UpdateCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service logging newrelicotlp update' command instead.\")\n\treturn c.UpdateCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/openstack/create.go",
    "content": "package openstack\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/openstack\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// CreateCommand wraps the CreateCommand from the newcmd package.\ntype CreateCommand struct {\n\t*newcmd.CreateCommand\n}\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{newcmd.NewCreateCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *CreateCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service logging openstack create' command instead.\")\n\treturn c.CreateCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/openstack/delete.go",
    "content": "package openstack\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/openstack\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DeleteCommand wraps the DeleteCommand from the newcmd package.\ntype DeleteCommand struct {\n\t*newcmd.DeleteCommand\n}\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{newcmd.NewDeleteCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *DeleteCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service logging openstack delete' command instead.\")\n\treturn c.DeleteCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/openstack/describe.go",
    "content": "package openstack\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/openstack\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DescribeCommand wraps the DescribeCommand from the newcmd package.\ntype DescribeCommand struct {\n\t*newcmd.DescribeCommand\n}\n\n// NewDescribeCommand returns a usable command registered under the parent.\nfunc NewDescribeCommand(parent argparser.Registerer, g *global.Data) *DescribeCommand {\n\tc := DescribeCommand{newcmd.NewDescribeCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *DescribeCommand) Exec(in io.Reader, out io.Writer) error {\n\tif !c.JSONOutput.Enabled {\n\t\ttext.Deprecated(\"Use the 'service logging openstack describe' command instead.\")\n\t}\n\treturn c.DescribeCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/openstack/doc.go",
    "content": "// Package openstack contains deprecated aliases for the 'service logging openstack' commands.\npackage openstack\n"
  },
  {
    "path": "pkg/commands/alias/logging/openstack/list.go",
    "content": "package openstack\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/openstack\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// ListCommand wraps the ListCommand from the newcmd package.\ntype ListCommand struct {\n\t*newcmd.ListCommand\n}\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{newcmd.NewListCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *ListCommand) Exec(in io.Reader, out io.Writer) error {\n\tif !c.JSONOutput.Enabled {\n\t\ttext.Deprecated(\"Use the 'service logging openstack list' command instead.\")\n\t}\n\treturn c.ListCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/openstack/root.go",
    "content": "package openstack\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"openstack\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manipulate Fastly service version OpenStack Swift logging endpoints\").Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/openstack/update.go",
    "content": "package openstack\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/openstack\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// UpdateCommand wraps the UpdateCommand from the newcmd package.\ntype UpdateCommand struct {\n\t*newcmd.UpdateCommand\n}\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{newcmd.NewUpdateCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *UpdateCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service logging openstack update' command instead.\")\n\treturn c.UpdateCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/papertrail/create.go",
    "content": "package papertrail\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/papertrail\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// CreateCommand wraps the CreateCommand from the newcmd package.\ntype CreateCommand struct {\n\t*newcmd.CreateCommand\n}\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{newcmd.NewCreateCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *CreateCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service logging papertrail create' command instead.\")\n\treturn c.CreateCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/papertrail/delete.go",
    "content": "package papertrail\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/papertrail\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DeleteCommand wraps the DeleteCommand from the newcmd package.\ntype DeleteCommand struct {\n\t*newcmd.DeleteCommand\n}\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{newcmd.NewDeleteCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *DeleteCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service logging papertrail delete' command instead.\")\n\treturn c.DeleteCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/papertrail/describe.go",
    "content": "package papertrail\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/papertrail\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DescribeCommand wraps the DescribeCommand from the newcmd package.\ntype DescribeCommand struct {\n\t*newcmd.DescribeCommand\n}\n\n// NewDescribeCommand returns a usable command registered under the parent.\nfunc NewDescribeCommand(parent argparser.Registerer, g *global.Data) *DescribeCommand {\n\tc := DescribeCommand{newcmd.NewDescribeCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *DescribeCommand) Exec(in io.Reader, out io.Writer) error {\n\tif !c.JSONOutput.Enabled {\n\t\ttext.Deprecated(\"Use the 'service logging papertrail describe' command instead.\")\n\t}\n\treturn c.DescribeCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/papertrail/doc.go",
    "content": "// Package papertrail contains deprecated aliases for the 'service logging papertrail' commands.\npackage papertrail\n"
  },
  {
    "path": "pkg/commands/alias/logging/papertrail/list.go",
    "content": "package papertrail\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/papertrail\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// ListCommand wraps the ListCommand from the newcmd package.\ntype ListCommand struct {\n\t*newcmd.ListCommand\n}\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{newcmd.NewListCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *ListCommand) Exec(in io.Reader, out io.Writer) error {\n\tif !c.JSONOutput.Enabled {\n\t\ttext.Deprecated(\"Use the 'service logging papertrail list' command instead.\")\n\t}\n\treturn c.ListCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/papertrail/root.go",
    "content": "package papertrail\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"papertrail\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manipulate Fastly service version Papertrail logging endpoints\").Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/papertrail/update.go",
    "content": "package papertrail\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/papertrail\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// UpdateCommand wraps the UpdateCommand from the newcmd package.\ntype UpdateCommand struct {\n\t*newcmd.UpdateCommand\n}\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{newcmd.NewUpdateCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *UpdateCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service logging papertrail update' command instead.\")\n\treturn c.UpdateCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/root.go",
    "content": "package logging\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"logging\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manipulate Fastly service version logging endpoints\").Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/s3/create.go",
    "content": "package s3\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/s3\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// CreateCommand wraps the CreateCommand from the newcmd package.\ntype CreateCommand struct {\n\t*newcmd.CreateCommand\n}\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{newcmd.NewCreateCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *CreateCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service logging s3 create' command instead.\")\n\treturn c.CreateCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/s3/delete.go",
    "content": "package s3\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/s3\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DeleteCommand wraps the DeleteCommand from the newcmd package.\ntype DeleteCommand struct {\n\t*newcmd.DeleteCommand\n}\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{newcmd.NewDeleteCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *DeleteCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service logging s3 delete' command instead.\")\n\treturn c.DeleteCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/s3/describe.go",
    "content": "package s3\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/s3\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DescribeCommand wraps the DescribeCommand from the newcmd package.\ntype DescribeCommand struct {\n\t*newcmd.DescribeCommand\n}\n\n// NewDescribeCommand returns a usable command registered under the parent.\nfunc NewDescribeCommand(parent argparser.Registerer, g *global.Data) *DescribeCommand {\n\tc := DescribeCommand{newcmd.NewDescribeCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *DescribeCommand) Exec(in io.Reader, out io.Writer) error {\n\tif !c.JSONOutput.Enabled {\n\t\ttext.Deprecated(\"Use the 'service logging s3 describe' command instead.\")\n\t}\n\treturn c.DescribeCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/s3/doc.go",
    "content": "// Package s3 contains deprecated aliases for the 'service logging s3' commands.\npackage s3\n"
  },
  {
    "path": "pkg/commands/alias/logging/s3/list.go",
    "content": "package s3\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/s3\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// ListCommand wraps the ListCommand from the newcmd package.\ntype ListCommand struct {\n\t*newcmd.ListCommand\n}\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{newcmd.NewListCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *ListCommand) Exec(in io.Reader, out io.Writer) error {\n\tif !c.JSONOutput.Enabled {\n\t\ttext.Deprecated(\"Use the 'service logging s3 list' command instead.\")\n\t}\n\treturn c.ListCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/s3/root.go",
    "content": "package s3\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"s3\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manipulate Fastly service version Amazon S3 logging endpoints\").Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/s3/update.go",
    "content": "package s3\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/s3\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// UpdateCommand wraps the UpdateCommand from the newcmd package.\ntype UpdateCommand struct {\n\t*newcmd.UpdateCommand\n}\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{newcmd.NewUpdateCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *UpdateCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service logging s3 update' command instead.\")\n\treturn c.UpdateCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/scalyr/create.go",
    "content": "package scalyr\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/scalyr\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// CreateCommand wraps the CreateCommand from the newcmd package.\ntype CreateCommand struct {\n\t*newcmd.CreateCommand\n}\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{newcmd.NewCreateCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *CreateCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service logging scalyr create' command instead.\")\n\treturn c.CreateCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/scalyr/delete.go",
    "content": "package scalyr\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/scalyr\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DeleteCommand wraps the DeleteCommand from the newcmd package.\ntype DeleteCommand struct {\n\t*newcmd.DeleteCommand\n}\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{newcmd.NewDeleteCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *DeleteCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service logging scalyr delete' command instead.\")\n\treturn c.DeleteCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/scalyr/describe.go",
    "content": "package scalyr\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/scalyr\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DescribeCommand wraps the DescribeCommand from the newcmd package.\ntype DescribeCommand struct {\n\t*newcmd.DescribeCommand\n}\n\n// NewDescribeCommand returns a usable command registered under the parent.\nfunc NewDescribeCommand(parent argparser.Registerer, g *global.Data) *DescribeCommand {\n\tc := DescribeCommand{newcmd.NewDescribeCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *DescribeCommand) Exec(in io.Reader, out io.Writer) error {\n\tif !c.JSONOutput.Enabled {\n\t\ttext.Deprecated(\"Use the 'service logging scalyr describe' command instead.\")\n\t}\n\treturn c.DescribeCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/scalyr/doc.go",
    "content": "// Package scalyr contains deprecated aliases for the 'service logging scalyr' commands.\npackage scalyr\n"
  },
  {
    "path": "pkg/commands/alias/logging/scalyr/list.go",
    "content": "package scalyr\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/scalyr\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// ListCommand wraps the ListCommand from the newcmd package.\ntype ListCommand struct {\n\t*newcmd.ListCommand\n}\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{newcmd.NewListCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *ListCommand) Exec(in io.Reader, out io.Writer) error {\n\tif !c.JSONOutput.Enabled {\n\t\ttext.Deprecated(\"Use the 'service logging scalyr list' command instead.\")\n\t}\n\treturn c.ListCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/scalyr/root.go",
    "content": "package scalyr\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"scalyr\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manipulate Fastly service version Scalyr logging endpoints\").Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/scalyr/update.go",
    "content": "package scalyr\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/scalyr\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// UpdateCommand wraps the UpdateCommand from the newcmd package.\ntype UpdateCommand struct {\n\t*newcmd.UpdateCommand\n}\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{newcmd.NewUpdateCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *UpdateCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service logging scalyr update' command instead.\")\n\treturn c.UpdateCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/sftp/create.go",
    "content": "package sftp\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/sftp\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// CreateCommand wraps the CreateCommand from the newcmd package.\ntype CreateCommand struct {\n\t*newcmd.CreateCommand\n}\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{newcmd.NewCreateCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *CreateCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service logging sftp create' command instead.\")\n\treturn c.CreateCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/sftp/delete.go",
    "content": "package sftp\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/sftp\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DeleteCommand wraps the DeleteCommand from the newcmd package.\ntype DeleteCommand struct {\n\t*newcmd.DeleteCommand\n}\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{newcmd.NewDeleteCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *DeleteCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service logging sftp delete' command instead.\")\n\treturn c.DeleteCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/sftp/describe.go",
    "content": "package sftp\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/sftp\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DescribeCommand wraps the DescribeCommand from the newcmd package.\ntype DescribeCommand struct {\n\t*newcmd.DescribeCommand\n}\n\n// NewDescribeCommand returns a usable command registered under the parent.\nfunc NewDescribeCommand(parent argparser.Registerer, g *global.Data) *DescribeCommand {\n\tc := DescribeCommand{newcmd.NewDescribeCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *DescribeCommand) Exec(in io.Reader, out io.Writer) error {\n\tif !c.JSONOutput.Enabled {\n\t\ttext.Deprecated(\"Use the 'service logging sftp describe' command instead.\")\n\t}\n\treturn c.DescribeCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/sftp/doc.go",
    "content": "// Package sftp contains deprecated aliases for the 'service logging sftp' commands.\npackage sftp\n"
  },
  {
    "path": "pkg/commands/alias/logging/sftp/list.go",
    "content": "package sftp\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/sftp\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// ListCommand wraps the ListCommand from the newcmd package.\ntype ListCommand struct {\n\t*newcmd.ListCommand\n}\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{newcmd.NewListCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *ListCommand) Exec(in io.Reader, out io.Writer) error {\n\tif !c.JSONOutput.Enabled {\n\t\ttext.Deprecated(\"Use the 'service logging sftp list' command instead.\")\n\t}\n\treturn c.ListCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/sftp/root.go",
    "content": "package sftp\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"sftp\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manipulate Fastly service version SFTP logging endpoints\").Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/sftp/update.go",
    "content": "package sftp\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/sftp\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// UpdateCommand wraps the UpdateCommand from the newcmd package.\ntype UpdateCommand struct {\n\t*newcmd.UpdateCommand\n}\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{newcmd.NewUpdateCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *UpdateCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service logging sftp update' command instead.\")\n\treturn c.UpdateCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/splunk/create.go",
    "content": "package splunk\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/splunk\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// CreateCommand wraps the CreateCommand from the newcmd package.\ntype CreateCommand struct {\n\t*newcmd.CreateCommand\n}\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{newcmd.NewCreateCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *CreateCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service logging splunk create' command instead.\")\n\treturn c.CreateCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/splunk/delete.go",
    "content": "package splunk\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/splunk\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DeleteCommand wraps the DeleteCommand from the newcmd package.\ntype DeleteCommand struct {\n\t*newcmd.DeleteCommand\n}\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{newcmd.NewDeleteCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *DeleteCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service logging splunk delete' command instead.\")\n\treturn c.DeleteCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/splunk/describe.go",
    "content": "package splunk\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/splunk\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DescribeCommand wraps the DescribeCommand from the newcmd package.\ntype DescribeCommand struct {\n\t*newcmd.DescribeCommand\n}\n\n// NewDescribeCommand returns a usable command registered under the parent.\nfunc NewDescribeCommand(parent argparser.Registerer, g *global.Data) *DescribeCommand {\n\tc := DescribeCommand{newcmd.NewDescribeCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *DescribeCommand) Exec(in io.Reader, out io.Writer) error {\n\tif !c.JSONOutput.Enabled {\n\t\ttext.Deprecated(\"Use the 'service logging splunk describe' command instead.\")\n\t}\n\treturn c.DescribeCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/splunk/doc.go",
    "content": "// Package splunk contains deprecated aliases for the 'service logging splunk' commands.\npackage splunk\n"
  },
  {
    "path": "pkg/commands/alias/logging/splunk/list.go",
    "content": "package splunk\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/splunk\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// ListCommand wraps the ListCommand from the newcmd package.\ntype ListCommand struct {\n\t*newcmd.ListCommand\n}\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{newcmd.NewListCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *ListCommand) Exec(in io.Reader, out io.Writer) error {\n\tif !c.JSONOutput.Enabled {\n\t\ttext.Deprecated(\"Use the 'service logging splunk list' command instead.\")\n\t}\n\treturn c.ListCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/splunk/root.go",
    "content": "package splunk\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"splunk\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manipulate Fastly service version Splunk logging endpoints\").Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/splunk/update.go",
    "content": "package splunk\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/splunk\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// UpdateCommand wraps the UpdateCommand from the newcmd package.\ntype UpdateCommand struct {\n\t*newcmd.UpdateCommand\n}\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{newcmd.NewUpdateCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *UpdateCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service logging splunk update' command instead.\")\n\treturn c.UpdateCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/sumologic/create.go",
    "content": "package sumologic\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/sumologic\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// CreateCommand wraps the CreateCommand from the newcmd package.\ntype CreateCommand struct {\n\t*newcmd.CreateCommand\n}\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{newcmd.NewCreateCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *CreateCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service logging sumologic create' command instead.\")\n\treturn c.CreateCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/sumologic/delete.go",
    "content": "package sumologic\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/sumologic\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DeleteCommand wraps the DeleteCommand from the newcmd package.\ntype DeleteCommand struct {\n\t*newcmd.DeleteCommand\n}\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{newcmd.NewDeleteCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *DeleteCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service logging sumologic delete' command instead.\")\n\treturn c.DeleteCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/sumologic/describe.go",
    "content": "package sumologic\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/sumologic\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DescribeCommand wraps the DescribeCommand from the newcmd package.\ntype DescribeCommand struct {\n\t*newcmd.DescribeCommand\n}\n\n// NewDescribeCommand returns a usable command registered under the parent.\nfunc NewDescribeCommand(parent argparser.Registerer, g *global.Data) *DescribeCommand {\n\tc := DescribeCommand{newcmd.NewDescribeCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *DescribeCommand) Exec(in io.Reader, out io.Writer) error {\n\tif !c.JSONOutput.Enabled {\n\t\ttext.Deprecated(\"Use the 'service logging sumologic describe' command instead.\")\n\t}\n\treturn c.DescribeCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/sumologic/doc.go",
    "content": "// Package sumologic contains deprecated aliases for the 'service logging sumologic' commands.\npackage sumologic\n"
  },
  {
    "path": "pkg/commands/alias/logging/sumologic/list.go",
    "content": "package sumologic\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/sumologic\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// ListCommand wraps the ListCommand from the newcmd package.\ntype ListCommand struct {\n\t*newcmd.ListCommand\n}\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{newcmd.NewListCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *ListCommand) Exec(in io.Reader, out io.Writer) error {\n\tif !c.JSONOutput.Enabled {\n\t\ttext.Deprecated(\"Use the 'service logging sumologic list' command instead.\")\n\t}\n\treturn c.ListCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/sumologic/root.go",
    "content": "package sumologic\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"sumologic\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manipulate Fastly service version Sumo Logic logging endpoints\").Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/sumologic/update.go",
    "content": "package sumologic\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/sumologic\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// UpdateCommand wraps the UpdateCommand from the newcmd package.\ntype UpdateCommand struct {\n\t*newcmd.UpdateCommand\n}\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{newcmd.NewUpdateCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *UpdateCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service logging sumologic update' command instead.\")\n\treturn c.UpdateCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/syslog/create.go",
    "content": "package syslog\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/syslog\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// CreateCommand wraps the CreateCommand from the newcmd package.\ntype CreateCommand struct {\n\t*newcmd.CreateCommand\n}\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{newcmd.NewCreateCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *CreateCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service logging syslog create' command instead.\")\n\treturn c.CreateCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/syslog/delete.go",
    "content": "package syslog\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/syslog\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DeleteCommand wraps the DeleteCommand from the newcmd package.\ntype DeleteCommand struct {\n\t*newcmd.DeleteCommand\n}\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{newcmd.NewDeleteCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *DeleteCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service logging syslog delete' command instead.\")\n\treturn c.DeleteCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/syslog/describe.go",
    "content": "package syslog\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/syslog\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DescribeCommand wraps the DescribeCommand from the newcmd package.\ntype DescribeCommand struct {\n\t*newcmd.DescribeCommand\n}\n\n// NewDescribeCommand returns a usable command registered under the parent.\nfunc NewDescribeCommand(parent argparser.Registerer, g *global.Data) *DescribeCommand {\n\tc := DescribeCommand{newcmd.NewDescribeCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *DescribeCommand) Exec(in io.Reader, out io.Writer) error {\n\tif !c.JSONOutput.Enabled {\n\t\ttext.Deprecated(\"Use the 'service logging syslog describe' command instead.\")\n\t}\n\treturn c.DescribeCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/syslog/doc.go",
    "content": "// Package syslog contains deprecated aliases for the 'service logging syslog' commands.\npackage syslog\n"
  },
  {
    "path": "pkg/commands/alias/logging/syslog/list.go",
    "content": "package syslog\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/syslog\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// ListCommand wraps the ListCommand from the newcmd package.\ntype ListCommand struct {\n\t*newcmd.ListCommand\n}\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{newcmd.NewListCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *ListCommand) Exec(in io.Reader, out io.Writer) error {\n\tif !c.JSONOutput.Enabled {\n\t\ttext.Deprecated(\"Use the 'service logging syslog list' command instead.\")\n\t}\n\treturn c.ListCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/syslog/root.go",
    "content": "package syslog\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"syslog\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manipulate Fastly service version Syslog logging endpoints\").Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/alias/logging/syslog/update.go",
    "content": "package syslog\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/logging/syslog\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// UpdateCommand wraps the UpdateCommand from the newcmd package.\ntype UpdateCommand struct {\n\t*newcmd.UpdateCommand\n}\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{newcmd.NewUpdateCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *UpdateCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service logging syslog update' command instead.\")\n\treturn c.UpdateCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/purge/doc.go",
    "content": "// Package purge contains the 'purge' alias for the 'service purge' command.\npackage purge\n"
  },
  {
    "path": "pkg/commands/alias/purge/purge.go",
    "content": "package purge\n\nimport (\n\t\"io\"\n\n\tservicepurge \"github.com/fastly/cli/pkg/commands/service/purge\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// PurgeCommand wraps the PurgeCommand from the servicepurge package.\ntype Command struct {\n\t*servicepurge.PurgeCommand\n}\n\n// NewCommand returns a usable command registered under the parent.\nfunc NewCommand(parent argparser.Registerer, g *global.Data) *Command {\n\tc := Command{servicepurge.NewPurgeCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *Command) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service purge' command instead.\")\n\treturn c.PurgeCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/ratelimit/create.go",
    "content": "package ratelimit\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/ratelimit\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// CreateCommand wraps the CreateCommand from the newcmd package.\ntype CreateCommand struct {\n\t*newcmd.CreateCommand\n}\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{newcmd.NewCreateCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *CreateCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service rate-limit create' command instead.\")\n\treturn c.CreateCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/ratelimit/delete.go",
    "content": "package ratelimit\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/ratelimit\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DeleteCommand wraps the DeleteCommand from the newcmd package.\ntype DeleteCommand struct {\n\t*newcmd.DeleteCommand\n}\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{newcmd.NewDeleteCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *DeleteCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service rate-limit delete' command instead.\")\n\treturn c.DeleteCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/ratelimit/describe.go",
    "content": "package ratelimit\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/ratelimit\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DescribeCommand wraps the DescribeCommand from the newcmd package.\ntype DescribeCommand struct {\n\t*newcmd.DescribeCommand\n}\n\n// NewDescribeCommand returns a usable command registered under the parent.\nfunc NewDescribeCommand(parent argparser.Registerer, g *global.Data) *DescribeCommand {\n\tc := DescribeCommand{newcmd.NewDescribeCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *DescribeCommand) Exec(in io.Reader, out io.Writer) error {\n\tif !c.JSONOutput.Enabled {\n\t\ttext.Deprecated(\"Use the 'service rate-limit describe' command instead.\")\n\t}\n\treturn c.DescribeCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/ratelimit/doc.go",
    "content": "// Package ratelimit contains deprecated aliases for the 'service ratelimit' commands.\npackage ratelimit\n"
  },
  {
    "path": "pkg/commands/alias/ratelimit/list.go",
    "content": "package ratelimit\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/ratelimit\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// ListCommand wraps the ListCommand from the newcmd package.\ntype ListCommand struct {\n\t*newcmd.ListCommand\n}\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{newcmd.NewListCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *ListCommand) Exec(in io.Reader, out io.Writer) error {\n\tif !c.JSONOutput.Enabled {\n\t\ttext.Deprecated(\"Use the 'service rate-limit list' command instead.\")\n\t}\n\treturn c.ListCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/ratelimit/root.go",
    "content": "package ratelimit\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"rate-limit\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manipulate rate-limiters of the Fastly API and web interface\").Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/alias/ratelimit/update.go",
    "content": "package ratelimit\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/ratelimit\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// UpdateCommand wraps the UpdateCommand from the newcmd package.\ntype UpdateCommand struct {\n\t*newcmd.UpdateCommand\n}\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{newcmd.NewUpdateCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *UpdateCommand) Exec(in io.Reader, out io.Writer) error {\n\tif !c.JSONOutput.Enabled {\n\t\ttext.Deprecated(\"Use the 'service rate-limit update' command instead.\")\n\t}\n\treturn c.UpdateCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/resourcelink/create.go",
    "content": "package resourcelink\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/resourcelink\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// CreateCommand wraps the CreateCommand from the newcmd package.\ntype CreateCommand struct {\n\t*newcmd.CreateCommand\n}\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{newcmd.NewCreateCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *CreateCommand) Exec(in io.Reader, out io.Writer) error {\n\tif !c.JSONOutput.Enabled {\n\t\ttext.Deprecated(\"Use the 'service resource-link create' command instead.\")\n\t}\n\treturn c.CreateCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/resourcelink/delete.go",
    "content": "package resourcelink\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/resourcelink\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DeleteCommand wraps the DeleteCommand from the newcmd package.\ntype DeleteCommand struct {\n\t*newcmd.DeleteCommand\n}\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{newcmd.NewDeleteCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *DeleteCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service resource-link delete' command instead.\")\n\treturn c.DeleteCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/resourcelink/describe.go",
    "content": "package resourcelink\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/resourcelink\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DescribeCommand wraps the DescribeCommand from the newcmd package.\ntype DescribeCommand struct {\n\t*newcmd.DescribeCommand\n}\n\n// NewDescribeCommand returns a usable command registered under the parent.\nfunc NewDescribeCommand(parent argparser.Registerer, g *global.Data) *DescribeCommand {\n\tc := DescribeCommand{newcmd.NewDescribeCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *DescribeCommand) Exec(in io.Reader, out io.Writer) error {\n\tif !c.JSONOutput.Enabled {\n\t\ttext.Deprecated(\"Use the 'service resource-link describe' command instead.\")\n\t}\n\treturn c.DescribeCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/resourcelink/list.go",
    "content": "package resourcelink\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/resourcelink\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// ListCommand wraps the ListCommand from the newcmd package.\ntype ListCommand struct {\n\t*newcmd.ListCommand\n}\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{newcmd.NewListCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *ListCommand) Exec(in io.Reader, out io.Writer) error {\n\tif !c.JSONOutput.Enabled {\n\t\ttext.Deprecated(\"Use the 'service resource-link list' command instead.\")\n\t}\n\treturn c.ListCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/resourcelink/root.go",
    "content": "package resourcelink\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"resource-link\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manipulate Fastly service resource links\").Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/alias/resourcelink/update.go",
    "content": "package resourcelink\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/resourcelink\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// UpdateCommand wraps the UpdateCommand from the newcmd package.\ntype UpdateCommand struct {\n\t*newcmd.UpdateCommand\n}\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{newcmd.NewUpdateCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *UpdateCommand) Exec(in io.Reader, out io.Writer) error {\n\tif !c.JSONOutput.Enabled {\n\t\ttext.Deprecated(\"Use the 'service resource-link update' command instead.\")\n\t}\n\treturn c.UpdateCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/serviceauth/create.go",
    "content": "package serviceauth\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/auth\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// CreateCommand wraps the CreateCommand from the newcmd package.\ntype CreateCommand struct {\n\t*newcmd.CreateCommand\n}\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{newcmd.NewCreateCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *CreateCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service auth create' command instead.\")\n\treturn c.CreateCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/serviceauth/delete.go",
    "content": "package serviceauth\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/auth\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DeleteCommand wraps the DeleteCommand from the newcmd package.\ntype DeleteCommand struct {\n\t*newcmd.DeleteCommand\n}\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{newcmd.NewDeleteCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *DeleteCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service auth delete' command instead.\")\n\treturn c.DeleteCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/serviceauth/describe.go",
    "content": "package serviceauth\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/auth\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DescribeCommand wraps the DescribeCommand from the newcmd package.\ntype DescribeCommand struct {\n\t*newcmd.DescribeCommand\n}\n\n// NewDescribeCommand returns a usable command registered under the parent.\nfunc NewDescribeCommand(parent argparser.Registerer, g *global.Data) *DescribeCommand {\n\tc := DescribeCommand{newcmd.NewDescribeCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *DescribeCommand) Exec(in io.Reader, out io.Writer) error {\n\tif !c.JSONOutput.Enabled {\n\t\ttext.Deprecated(\"Use the 'service auth describe' command instead.\")\n\t}\n\treturn c.DescribeCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/serviceauth/doc.go",
    "content": "// Package serviceauth contains deprecated aliases for the 'service auth' commands.\npackage serviceauth\n"
  },
  {
    "path": "pkg/commands/alias/serviceauth/list.go",
    "content": "package serviceauth\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/auth\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// ListCommand wraps the ListCommand from the newcmd package.\ntype ListCommand struct {\n\t*newcmd.ListCommand\n}\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{newcmd.NewListCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *ListCommand) Exec(in io.Reader, out io.Writer) error {\n\tif !c.JSONOutput.Enabled {\n\t\ttext.Deprecated(\"Use the 'service auth list' command instead.\")\n\t}\n\treturn c.ListCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/serviceauth/root.go",
    "content": "package serviceauth\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"service-auth\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manipulate Fastly Service Authentication\").Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/alias/serviceauth/update.go",
    "content": "package serviceauth\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/auth\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// UpdateCommand wraps the UpdateCommand from the newcmd package.\ntype UpdateCommand struct {\n\t*newcmd.UpdateCommand\n}\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{newcmd.NewUpdateCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *UpdateCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service auth update' command instead.\")\n\treturn c.UpdateCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/serviceversion/activate.go",
    "content": "package serviceversion\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/version\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// ActivateCommand wraps the ActivateCommand from the newcmd package.\ntype ActivateCommand struct {\n\t*newcmd.ActivateCommand\n}\n\n// NewActivateCommand returns a usable command registered under the parent.\nfunc NewActivateCommand(parent argparser.Registerer, g *global.Data) *ActivateCommand {\n\tc := ActivateCommand{newcmd.NewActivateCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *ActivateCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service version activate' command instead.\")\n\treturn c.ActivateCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/serviceversion/clone.go",
    "content": "package serviceversion\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/version\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// CloneCommand wraps the CloneCommand from the newcmd package.\ntype CloneCommand struct {\n\t*newcmd.CloneCommand\n}\n\n// NewCloneCommand returns a usable command registered under the parent.\nfunc NewCloneCommand(parent argparser.Registerer, g *global.Data) *CloneCommand {\n\tc := CloneCommand{newcmd.NewCloneCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *CloneCommand) Exec(in io.Reader, out io.Writer) error {\n\tif !c.JSONOutput.Enabled {\n\t\ttext.Deprecated(\"Use the 'service version clone' command instead.\")\n\t}\n\treturn c.CloneCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/serviceversion/deactivate.go",
    "content": "package serviceversion\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/version\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DeactivateCommand wraps the DeactivateCommand from the newcmd package.\ntype DeactivateCommand struct {\n\t*newcmd.DeactivateCommand\n}\n\n// NewDeactivateCommand returns a usable command registered under the parent.\nfunc NewDeactivateCommand(parent argparser.Registerer, g *global.Data) *DeactivateCommand {\n\tc := DeactivateCommand{newcmd.NewDeactivateCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *DeactivateCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service version deactivate' command instead.\")\n\treturn c.DeactivateCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/serviceversion/doc.go",
    "content": "// Package serviceversion contains the 'service-version' aliases for the 'service version' commands.\npackage serviceversion\n"
  },
  {
    "path": "pkg/commands/alias/serviceversion/list.go",
    "content": "package serviceversion\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/version\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// ListCommand wraps the ListCommand from the newcmd package.\ntype ListCommand struct {\n\t*newcmd.ListCommand\n}\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{newcmd.NewListCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *ListCommand) Exec(in io.Reader, out io.Writer) error {\n\tif !c.JSONOutput.Enabled {\n\t\ttext.Deprecated(\"Use the 'service version list' command instead.\")\n\t}\n\treturn c.ListCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/serviceversion/lock.go",
    "content": "package serviceversion\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/version\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// LockCommand wraps the LockCommand from the newcmd package.\ntype LockCommand struct {\n\t*newcmd.LockCommand\n}\n\n// NewLockCommand returns a usable command registered under the parent.\nfunc NewLockCommand(parent argparser.Registerer, g *global.Data) *LockCommand {\n\tc := LockCommand{newcmd.NewLockCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *LockCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service version lock' command instead.\")\n\treturn c.LockCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/serviceversion/root.go",
    "content": "package serviceversion\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"service-version\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manipulate Fastly service versions\").Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/alias/serviceversion/stage.go",
    "content": "package serviceversion\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/version\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// StageCommand wraps the StageCommand from the newcmd package.\ntype StageCommand struct {\n\t*newcmd.StageCommand\n}\n\n// NewStageCommand returns a usable command registered under the parent.\nfunc NewStageCommand(parent argparser.Registerer, g *global.Data) *StageCommand {\n\tc := StageCommand{newcmd.NewStageCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *StageCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service version stage' command instead.\")\n\treturn c.StageCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/serviceversion/unstage.go",
    "content": "package serviceversion\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/version\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// UnstageCommand wraps the UnstageCommand from the newcmd package.\ntype UnstageCommand struct {\n\t*newcmd.UnstageCommand\n}\n\n// NewUnstageCommand returns a usable command registered under the parent.\nfunc NewUnstageCommand(parent argparser.Registerer, g *global.Data) *UnstageCommand {\n\tc := UnstageCommand{newcmd.NewUnstageCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *UnstageCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service version unstage' command instead.\")\n\treturn c.UnstageCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/serviceversion/update.go",
    "content": "package serviceversion\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/version\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// UpdateCommand wraps the UpdateCommand from the newcmd package.\ntype UpdateCommand struct {\n\t*newcmd.UpdateCommand\n}\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{newcmd.NewUpdateCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *UpdateCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service version update' command instead.\")\n\treturn c.UpdateCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/vcl/condition/create.go",
    "content": "package condition\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/vcl/condition\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// CreateCommand wraps the CreateCommand from the newcmd package.\ntype CreateCommand struct {\n\t*newcmd.CreateCommand\n}\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{newcmd.NewCreateCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *CreateCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service vcl condition create' command instead.\")\n\treturn c.CreateCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/vcl/condition/delete.go",
    "content": "package condition\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/vcl/condition\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DeleteCommand wraps the DeleteCommand from the newcmd package.\ntype DeleteCommand struct {\n\t*newcmd.DeleteCommand\n}\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{newcmd.NewDeleteCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *DeleteCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service vcl condition delete' command instead.\")\n\treturn c.DeleteCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/vcl/condition/describe.go",
    "content": "package condition\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/vcl/condition\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DescribeCommand wraps the DescribeCommand from the newcmd package.\ntype DescribeCommand struct {\n\t*newcmd.DescribeCommand\n}\n\n// NewDescribeCommand returns a usable command registered under the parent.\nfunc NewDescribeCommand(parent argparser.Registerer, g *global.Data) *DescribeCommand {\n\tc := DescribeCommand{newcmd.NewDescribeCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *DescribeCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service vcl condition describe' command instead.\")\n\treturn c.DescribeCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/vcl/condition/doc.go",
    "content": "// Package condition contains deprecated aliases for the 'service vcl' condition commands.\npackage condition\n"
  },
  {
    "path": "pkg/commands/alias/vcl/condition/list.go",
    "content": "package condition\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/vcl/condition\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// ListCommand wraps the ListCommand from the newcmd package.\ntype ListCommand struct {\n\t*newcmd.ListCommand\n}\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{newcmd.NewListCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *ListCommand) Exec(in io.Reader, out io.Writer) error {\n\tif !c.JSONOutput.Enabled {\n\t\ttext.Deprecated(\"Use the 'service vcl condition list' command instead.\")\n\t}\n\treturn c.ListCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/vcl/condition/root.go",
    "content": "package condition\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"condition\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manipulate Fastly VCL conditions\").Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/alias/vcl/condition/update.go",
    "content": "package condition\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/vcl/condition\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// UpdateCommand wraps the UpdateCommand from the newcmd package.\ntype UpdateCommand struct {\n\t*newcmd.UpdateCommand\n}\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{newcmd.NewUpdateCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *UpdateCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service vcl condition update' command instead.\")\n\treturn c.UpdateCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/vcl/custom/create.go",
    "content": "package custom\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/vcl/custom\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// CreateCommand wraps the CreateCommand from the newcmd package.\ntype CreateCommand struct {\n\t*newcmd.CreateCommand\n}\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{newcmd.NewCreateCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *CreateCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service vcl custom create' command instead.\")\n\treturn c.CreateCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/vcl/custom/delete.go",
    "content": "package custom\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/vcl/custom\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DeleteCommand wraps the DeleteCommand from the newcmd package.\ntype DeleteCommand struct {\n\t*newcmd.DeleteCommand\n}\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{newcmd.NewDeleteCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *DeleteCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service vcl custom delete' command instead.\")\n\treturn c.DeleteCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/vcl/custom/describe.go",
    "content": "package custom\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/vcl/custom\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DescribeCommand wraps the DescribeCommand from the newcmd package.\ntype DescribeCommand struct {\n\t*newcmd.DescribeCommand\n}\n\n// NewDescribeCommand returns a usable command registered under the parent.\nfunc NewDescribeCommand(parent argparser.Registerer, g *global.Data) *DescribeCommand {\n\tc := DescribeCommand{newcmd.NewDescribeCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *DescribeCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service vcl custom describe' command instead.\")\n\treturn c.DescribeCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/vcl/custom/doc.go",
    "content": "// Package custom contains deprecated aliases for the 'service vcl' custom commands.\npackage custom\n"
  },
  {
    "path": "pkg/commands/alias/vcl/custom/list.go",
    "content": "package custom\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/vcl/custom\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// ListCommand wraps the ListCommand from the newcmd package.\ntype ListCommand struct {\n\t*newcmd.ListCommand\n}\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{newcmd.NewListCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *ListCommand) Exec(in io.Reader, out io.Writer) error {\n\tif !c.JSONOutput.Enabled {\n\t\ttext.Deprecated(\"Use the 'service vcl custom list' command instead.\")\n\t}\n\treturn c.ListCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/vcl/custom/root.go",
    "content": "package custom\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"custom\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manipulate Fastly custom VCL files\").Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/alias/vcl/custom/update.go",
    "content": "package custom\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/vcl/custom\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// UpdateCommand wraps the UpdateCommand from the newcmd package.\ntype UpdateCommand struct {\n\t*newcmd.UpdateCommand\n}\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{newcmd.NewUpdateCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *UpdateCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service vcl custom update' command instead.\")\n\treturn c.UpdateCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/vcl/describe.go",
    "content": "package vcl\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/vcl\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DescribeCommand wraps the DescribeCommand from the newcmd package.\ntype DescribeCommand struct {\n\t*newcmd.DescribeCommand\n}\n\n// NewDescribeCommand returns a usable command registered under the parent.\nfunc NewDescribeCommand(parent argparser.Registerer, g *global.Data) *DescribeCommand {\n\tc := DescribeCommand{newcmd.NewDescribeCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *DescribeCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service vcl describe' command instead.\")\n\treturn c.DescribeCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/vcl/doc.go",
    "content": "// Package vcl contains deprecated aliases for the 'service vcl' commands.\npackage vcl\n"
  },
  {
    "path": "pkg/commands/alias/vcl/root.go",
    "content": "package vcl\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"vcl\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manipulate Fastly service version VCL\").Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/alias/vcl/snippet/create.go",
    "content": "package snippet\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/vcl/snippet\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// CreateCommand wraps the CreateCommand from the newcmd package.\ntype CreateCommand struct {\n\t*newcmd.CreateCommand\n}\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{newcmd.NewCreateCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *CreateCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service vcl snippet create' command instead.\")\n\treturn c.CreateCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/vcl/snippet/delete.go",
    "content": "package snippet\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/vcl/snippet\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DeleteCommand wraps the DeleteCommand from the newcmd package.\ntype DeleteCommand struct {\n\t*newcmd.DeleteCommand\n}\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{newcmd.NewDeleteCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *DeleteCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service vcl snippet delete' command instead.\")\n\treturn c.DeleteCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/vcl/snippet/describe.go",
    "content": "package snippet\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/vcl/snippet\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DescribeCommand wraps the DescribeCommand from the newcmd package.\ntype DescribeCommand struct {\n\t*newcmd.DescribeCommand\n}\n\n// NewDescribeCommand returns a usable command registered under the parent.\nfunc NewDescribeCommand(parent argparser.Registerer, g *global.Data) *DescribeCommand {\n\tc := DescribeCommand{newcmd.NewDescribeCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *DescribeCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service vcl snippet describe' command instead.\")\n\treturn c.DescribeCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/vcl/snippet/doc.go",
    "content": "// Package snippet contains deprecated aliases for the 'service vcl' snippet commands.\npackage snippet\n"
  },
  {
    "path": "pkg/commands/alias/vcl/snippet/list.go",
    "content": "package snippet\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/vcl/snippet\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// ListCommand wraps the ListCommand from the newcmd package.\ntype ListCommand struct {\n\t*newcmd.ListCommand\n}\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{newcmd.NewListCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *ListCommand) Exec(in io.Reader, out io.Writer) error {\n\tif !c.JSONOutput.Enabled {\n\t\ttext.Deprecated(\"Use the 'service vcl snippet list' command instead.\")\n\t}\n\treturn c.ListCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/alias/vcl/snippet/root.go",
    "content": "package snippet\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"snippet\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manipulate Fastly VCL snippets\").Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/alias/vcl/snippet/update.go",
    "content": "package snippet\n\nimport (\n\t\"io\"\n\n\tnewcmd \"github.com/fastly/cli/pkg/commands/service/vcl/snippet\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// UpdateCommand wraps the UpdateCommand from the newcmd package.\ntype UpdateCommand struct {\n\t*newcmd.UpdateCommand\n}\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{newcmd.NewUpdateCommand(parent, g)}\n\tc.CmdClause.Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *UpdateCommand) Exec(in io.Reader, out io.Writer) error {\n\ttext.Deprecated(\"Use the 'service vcl snippet update' command instead.\")\n\treturn c.UpdateCommand.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/apisecurity/discoveredoperations/discoveredoperations_test.go",
    "content": "package discoveredoperations_test\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"testing\"\n\n\tapisecurity \"github.com/fastly/cli/pkg/commands/apisecurity\"\n\troot \"github.com/fastly/cli/pkg/commands/apisecurity/discoveredoperations\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n\t\"github.com/fastly/go-fastly/v15/fastly/apisecurity/operations\"\n)\n\nconst (\n\tserviceID   = \"test-service-id\"\n\toperationID = \"test-operation-id\"\n)\n\nvar (\n\tlistResponse = operations.DiscoveredOperations{\n\t\tData: []operations.DiscoveredOperation{\n\t\t\t{\n\t\t\t\tID:         \"test-operation-id\",\n\t\t\t\tMethod:     \"GET\",\n\t\t\t\tDomain:     \"example.com\",\n\t\t\t\tPath:       \"/api/users\",\n\t\t\t\tStatus:     \"DISCOVERED\",\n\t\t\t\tRPS:        10.5,\n\t\t\t\tLastSeenAt: \"2026-03-10T12:00:00Z\",\n\t\t\t\tUpdatedAt:  \"2026-03-10T12:00:00Z\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tID:         \"test-operation-id-2\",\n\t\t\t\tMethod:     \"POST\",\n\t\t\t\tDomain:     \"example.com\",\n\t\t\t\tPath:       \"/api/users\",\n\t\t\t\tStatus:     \"SAVED\",\n\t\t\t\tRPS:        5.2,\n\t\t\t\tLastSeenAt: \"2026-03-10T12:00:00Z\",\n\t\t\t\tUpdatedAt:  \"2026-03-10T12:00:00Z\",\n\t\t\t},\n\t\t},\n\t\tMeta: operations.Meta{\n\t\t\tLimit: 2,\n\t\t\tTotal: 2,\n\t\t},\n\t}\n\n\tupdateResponse = operations.DiscoveredOperation{\n\t\tID:         \"test-operation-id\",\n\t\tMethod:     \"GET\",\n\t\tDomain:     \"example.com\",\n\t\tPath:       \"/api/users\",\n\t\tStatus:     \"IGNORED\",\n\t\tRPS:        10.5,\n\t\tLastSeenAt: \"2026-03-10T12:00:00Z\",\n\t\tUpdatedAt:  \"2026-03-10T13:00:00Z\",\n\t}\n\n\tupdateResponseJSON = testutil.GenJSON(updateResponse)\n\tlistResponseJSON   = testutil.GenJSON(listResponse)\n\n\tbulkResponse = operations.BulkOperationResultsResponse{\n\t\tData: []operations.BulkOperationResult{\n\t\t\t{\n\t\t\t\tID:         \"op-id-1\",\n\t\t\t\tStatusCode: 200,\n\t\t\t},\n\t\t\t{\n\t\t\t\tID:         \"op-id-2\",\n\t\t\t\tStatusCode: 200,\n\t\t\t},\n\t\t},\n\t}\n\tbulkResponseJSON = testutil.GenJSON(bulkResponse)\n\n\tlistDiscoveredOperationsOutput = strings.TrimSpace(`\nMETHOD  DOMAIN       PATH        STATUS      RPS    LAST SEEN\nGET     example.com  /api/users  DISCOVERED  10.50  2026-03-10T12:00:00Z\nPOST    example.com  /api/users  SAVED       5.20   2026-03-10T12:00:00Z\n`) + \"\\n\"\n\n\tlistDiscoveredOperationsVerboseOutput = strings.TrimSpace(`\nFastly API endpoint: https://api.fastly.com\nFastly API token provided via config file (auth: user)\n\nService ID (via --service-id): test-service-id\n\n\nDiscovered Operation 1/2\n\tID: test-operation-id\n\tMethod: GET\n\tDomain: example.com\n\tPath: /api/users\n\tStatus: DISCOVERED\n\tRPS: 10.50\n\tLast Seen: 2026-03-10T12:00:00Z\n\tUpdated At: 2026-03-10T12:00:00Z\n\nDiscovered Operation 2/2\n\tID: test-operation-id-2\n\tMethod: POST\n\tDomain: example.com\n\tPath: /api/users\n\tStatus: SAVED\n\tRPS: 5.20\n\tLast Seen: 2026-03-10T12:00:00Z\n\tUpdated At: 2026-03-10T12:00:00Z\n`) + \"\\n\\n\"\n)\n\nfunc TestListCommand(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --service-id flag\",\n\t\t\tArgs:      \"--status discovered\",\n\t\t\tWantError: \"error parsing arguments: required flag --service-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate list without status filter\",\n\t\t\tArgs: fmt.Sprintf(\"--service-id %s\", serviceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(listResponse))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: listDiscoveredOperationsOutput,\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--service-id %s\", serviceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(listResponse))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: listDiscoveredOperationsOutput,\n\t\t},\n\t\t{\n\t\t\tName: \"validate --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--service-id %s --status saved --json\", serviceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(listResponseJSON)),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: string(testutil.GenJSON(listResponse.Data)),\n\t\t},\n\t\t{\n\t\t\tName: \"validate invalid status\",\n\t\t\tArgs: fmt.Sprintf(\"--service-id %s --status invalid\", serviceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(listResponse))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"invalid status: invalid. Valid options: 'discovered', 'saved', 'ignored'\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API error\",\n\t\t\tArgs: fmt.Sprintf(\"--service-id %s --status discovered\", serviceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusInternalServerError,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusInternalServerError),\n\t\t\t\t\t\tBody:       io.NopCloser(strings.NewReader(`{\"detail\":\"Internal Server Error\"}`)),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"500\",\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{apisecurity.CommandName, root.CommandName, \"list\"}, scenarios)\n}\n\nfunc TestListCommandWithFilters(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName: \"validate --domain filter\",\n\t\t\tArgs: fmt.Sprintf(\"--service-id %s --status discovered --domain example.com\", serviceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(listResponse))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: listDiscoveredOperationsOutput,\n\t\t},\n\t\t{\n\t\t\tName: \"validate --method filter\",\n\t\t\tArgs: fmt.Sprintf(\"--service-id %s --status discovered --method GET\", serviceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(listResponse))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: listDiscoveredOperationsOutput,\n\t\t},\n\t\t{\n\t\t\tName: \"validate --path filter\",\n\t\t\tArgs: fmt.Sprintf(\"--service-id %s --status discovered --path /api/users\", serviceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(listResponse))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: listDiscoveredOperationsOutput,\n\t\t},\n\t\t{\n\t\t\tName: \"validate --verbose output\",\n\t\t\tArgs: fmt.Sprintf(\"--service-id %s --status discovered --verbose\", serviceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(listResponse))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: listDiscoveredOperationsVerboseOutput,\n\t\t},\n\t\t{\n\t\t\tName: \"validate empty results\",\n\t\t\tArgs: fmt.Sprintf(\"--service-id %s --status discovered\", serviceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(operations.DiscoveredOperations{\n\t\t\t\t\t\t\tData: []operations.DiscoveredOperation{},\n\t\t\t\t\t\t\tMeta: operations.Meta{\n\t\t\t\t\t\t\t\tLimit: 0,\n\t\t\t\t\t\t\t\tTotal: 0,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t}))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{apisecurity.CommandName, root.CommandName, \"list\"}, scenarios)\n}\n\nfunc TestUpdateCommand(t *testing.T) {\n\t// Create temp file for bulk update test\n\ttmpDir := t.TempDir()\n\ttestFile := filepath.Join(tmpDir, \"test-ops.json\")\n\tcontent := `{\"operation_ids\": [\"op-id-1\", \"op-id-2\"], \"status\": \"ignored\"}`\n\terr := os.WriteFile(testFile, []byte(content), 0o600)\n\tif err != nil {\n\t\tt.Fatalf(\"failed to create test file: %v\", err)\n\t}\n\n\tdiscoveredResponse := operations.DiscoveredOperation{\n\t\tID:         \"test-operation-id\",\n\t\tMethod:     \"GET\",\n\t\tDomain:     \"example.com\",\n\t\tPath:       \"/api/users\",\n\t\tStatus:     \"DISCOVERED\",\n\t\tRPS:        10.5,\n\t\tLastSeenAt: \"2026-03-10T12:00:00Z\",\n\t\tUpdatedAt:  \"2026-03-10T13:00:00Z\",\n\t}\n\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --service-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--operation-id %s --status ignored\", operationID),\n\t\t\tWantError: \"error parsing arguments: required flag --service-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --operation-id and --file flags\",\n\t\t\tArgs:      fmt.Sprintf(\"--service-id %s --status ignored\", serviceID),\n\t\t\tWantError: \"error parsing arguments: must provide either --operation-id or --file\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --status flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--service-id %s --operation-id %s\", serviceID, operationID),\n\t\t\tWantError: \"error parsing arguments: --status is required when using --operation-id\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate invalid status\",\n\t\t\tArgs: fmt.Sprintf(\"--service-id %s --operation-id %s --status invalid\", serviceID, operationID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(updateResponse))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"invalid status: invalid. Valid options: 'discovered', 'ignored'\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success with status ignored\",\n\t\t\tArgs: fmt.Sprintf(\"--service-id %s --operation-id %s --status ignored\", serviceID, operationID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(updateResponse))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutputs: []string{\n\t\t\t\t\"Updated discovered operation:\",\n\t\t\t\t\"ID: test-operation-id\",\n\t\t\t\t\"Method: GET\",\n\t\t\t\t\"Domain: example.com\",\n\t\t\t\t\"Path: /api/users\",\n\t\t\t\t\"Status: IGNORED\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success with status discovered\",\n\t\t\tArgs: fmt.Sprintf(\"--service-id %s --operation-id %s --status discovered\", serviceID, operationID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(discoveredResponse))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutputs: []string{\n\t\t\t\t\"Updated discovered operation:\",\n\t\t\t\t\"Status: DISCOVERED\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tName: \"validate --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--service-id %s --operation-id %s --status ignored --json\", serviceID, operationID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(updateResponseJSON)),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: string(updateResponseJSON),\n\t\t},\n\t\t{\n\t\t\tName: \"validate API error\",\n\t\t\tArgs: fmt.Sprintf(\"--service-id %s --operation-id %s --status ignored\", serviceID, operationID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusNotFound,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusNotFound),\n\t\t\t\t\t\tBody:       io.NopCloser(strings.NewReader(`{\"detail\":\"Not Found\"}`)),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"404\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate bulk mode with --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--service-id %s --file %s --json\", serviceID, testFile),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusMultiStatus,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusMultiStatus),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(bulkResponseJSON)),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: string(bulkResponseJSON),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{apisecurity.CommandName, root.CommandName, \"update\"}, scenarios)\n}\n\nfunc TestUpdateCommandEdgeCases(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate cannot use both --operation-id and --file\",\n\t\t\tArgs:      fmt.Sprintf(\"--service-id %s --operation-id %s --file /tmp/test.json --status ignored\", serviceID, operationID),\n\t\t\tWantError: \"error parsing arguments: cannot use both --operation-id and --file\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate cannot use --file with --status flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--service-id %s --file /tmp/test.json --status ignored\", serviceID),\n\t\t\tWantError: \"error parsing arguments: cannot use both --file and --status\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate comma-separated operation IDs\",\n\t\t\tArgs: fmt.Sprintf(\"--service-id %s --operation-id op-id-1,op-id-2 --status ignored\", serviceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusMultiStatus,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusMultiStatus),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(bulkResponse))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutputs: []string{\n\t\t\t\t\"Updated 2 discovered operation(s)\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tName: \"validate --verbose with bulk update\",\n\t\t\tArgs: fmt.Sprintf(\"--service-id %s --operation-id op-id-1,op-id-2 --status ignored --verbose\", serviceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusMultiStatus,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusMultiStatus),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(bulkResponse))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutputs: []string{\n\t\t\t\t\"Updated 2 discovered operation(s)\",\n\t\t\t\t\"Updating 2 operation(s) with status: IGNORED\",\n\t\t\t\t\"OPERATION ID\",\n\t\t\t\t\"STATUS CODE\",\n\t\t\t\t\"RESULT\",\n\t\t\t\t\"op-id-1\",\n\t\t\t\t\"200\",\n\t\t\t\t\"Success\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tName: \"validate bulk update with mixed results\",\n\t\t\tArgs: fmt.Sprintf(\"--service-id %s --operation-id op-id-1,op-id-2,op-id-3 --status ignored\", serviceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusMultiStatus,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusMultiStatus),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(operations.BulkOperationResultsResponse{\n\t\t\t\t\t\t\tData: []operations.BulkOperationResult{\n\t\t\t\t\t\t\t\t{ID: \"op-id-1\", StatusCode: 200},\n\t\t\t\t\t\t\t\t{ID: \"op-id-2\", StatusCode: 404, Reason: \"Not Found\"},\n\t\t\t\t\t\t\t\t{ID: \"op-id-3\", StatusCode: 200},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t}))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutputs: []string{\n\t\t\t\t\"Updated 2 discovered operation(s)\",\n\t\t\t\t\"1 operation(s) failed to update\",\n\t\t\t},\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{apisecurity.CommandName, root.CommandName, \"update\"}, scenarios)\n}\n"
  },
  {
    "path": "pkg/commands/apisecurity/discoveredoperations/doc.go",
    "content": "// Package discoveredoperations contains commands to list and update discovered operations.\npackage discoveredoperations\n"
  },
  {
    "path": "pkg/commands/apisecurity/discoveredoperations/list.go",
    "content": "package discoveredoperations\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"strings\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/apisecurity/operations\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// ListCommand calls the Fastly API to list discovered API operations.\ntype ListCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tinput       operations.ListDiscoveredInput\n\tserviceName argparser.OptionalServiceNameID\n\n\t// Optional.\n\tdomain argparser.OptionalString\n\tmethod argparser.OptionalString\n\tpath   argparser.OptionalString\n\tstatus argparser.OptionalString\n}\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"list\", \"List discovered operations\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t\tRequired:    true,\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\n\t// Optional.\n\tc.CmdClause.Flag(\"status\", \"Filters operations by status. Valid values are: discovered, saved, ignored\").Action(c.status.Set).StringVar(&c.status.Value)\n\tc.CmdClause.Flag(\"domain\", \"The domain for the operation\").Action(c.domain.Set).StringVar(&c.domain.Value)\n\tc.CmdClause.Flag(\"method\", \"Filters operations by HTTP method (e.g., GET, POST, PUT)\").Action(c.method.Set).StringVar(&c.method.Value)\n\tc.CmdClause.Flag(\"path\", \"Filters operations by path (exact match)\").Action(c.path.Set).StringVar(&c.path.Value)\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *ListCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tserviceID, source, flag, err := argparser.ServiceID(c.serviceName, *c.Globals.Manifest, c.Globals.APIClient, c.Globals.ErrLog)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif c.Globals.Verbose() {\n\t\targparser.DisplayServiceID(serviceID, flag, source, out)\n\t}\n\n\tc.input.ServiceID = &serviceID\n\n\t// The API only accepts uppercase values for 'status',\n\t// so we are handling accordingly here and allowing\n\t// end users to still use the normal lowercase pattern\n\t// for input in the CLI.\n\tif c.status.WasSet {\n\t\tswitch c.status.Value {\n\t\tcase \"discovered\":\n\t\t\tstatus := \"DISCOVERED\"\n\t\t\tc.input.Status = &status\n\t\tcase \"saved\":\n\t\t\tstatus := \"SAVED\"\n\t\t\tc.input.Status = &status\n\t\tcase \"ignored\":\n\t\t\tstatus := \"IGNORED\"\n\t\t\tc.input.Status = &status\n\t\tdefault:\n\t\t\terr := fmt.Errorf(\"invalid status: %s. Valid options: 'discovered', 'saved', 'ignored'\", c.status.Value)\n\t\t\tc.Globals.ErrLog.Add(err)\n\t\t\treturn err\n\t\t}\n\t}\n\tif c.domain.WasSet {\n\t\tc.input.Domain = []string{c.domain.Value}\n\t}\n\tif c.method.WasSet {\n\t\tc.input.Method = []string{c.method.Value}\n\t}\n\tif c.path.WasSet {\n\t\tc.input.Path = &c.path.Value\n\t}\n\n\tfc, ok := c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\t// Auto-paginate through all results\n\tvar allOperations []operations.DiscoveredOperation\n\tpage := 0\n\tlimit := 100\n\n\tfor {\n\t\tc.input.Page = &page\n\t\tc.input.Limit = &limit\n\n\t\to, err := operations.ListDiscovered(context.TODO(), fc, &c.input)\n\t\tif err != nil {\n\t\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\t\"Service ID\": serviceID,\n\t\t\t\t\"Domain\":     c.domain.Value,\n\t\t\t\t\"Method\":     c.method.Value,\n\t\t\t\t\"Status\":     c.status.Value,\n\t\t\t\t\"Path\":       c.path.Value,\n\t\t\t\t\"Page\":       page,\n\t\t\t})\n\t\t\treturn err\n\t\t}\n\n\t\tif o == nil || len(o.Data) == 0 {\n\t\t\tbreak\n\t\t}\n\n\t\tallOperations = append(allOperations, o.Data...)\n\n\t\t// Check if we've fetched all results\n\t\tif len(allOperations) >= o.Meta.Total {\n\t\t\tbreak\n\t\t}\n\n\t\tpage++\n\t}\n\n\tif ok, err := c.WriteJSON(out, allOperations); ok {\n\t\treturn err\n\t}\n\n\tif !c.Globals.Verbose() {\n\t\treturn c.printSummary(out, allOperations)\n\t}\n\n\treturn c.printVerbose(out, allOperations)\n}\n\n// printSummary displays the discovered operations in a table format.\nfunc (c *ListCommand) printSummary(out io.Writer, o []operations.DiscoveredOperation) error {\n\ttw := text.NewTable(out)\n\ttw.AddHeader(\"METHOD\", \"DOMAIN\", \"PATH\", \"STATUS\", \"RPS\", \"LAST SEEN\")\n\tfor _, op := range o {\n\t\ttw.AddLine(\n\t\t\tstrings.ToUpper(op.Method),\n\t\t\top.Domain,\n\t\t\top.Path,\n\t\t\top.Status,\n\t\t\tfmt.Sprintf(\"%.2f\", op.RPS),\n\t\t\top.LastSeenAt,\n\t\t)\n\t}\n\ttw.Print()\n\n\treturn nil\n}\n\n// printVerbose displays detailed information for each discovered operation.\nfunc (c *ListCommand) printVerbose(out io.Writer, o []operations.DiscoveredOperation) error {\n\tfor i, op := range o {\n\t\tfmt.Fprintf(out, \"\\nDiscovered Operation %d/%d\\n\", i+1, len(o))\n\t\tfmt.Fprintf(out, \"\\tID: %s\\n\", op.ID)\n\t\tfmt.Fprintf(out, \"\\tMethod: %s\\n\", strings.ToUpper(op.Method))\n\t\tfmt.Fprintf(out, \"\\tDomain: %s\\n\", op.Domain)\n\t\tfmt.Fprintf(out, \"\\tPath: %s\\n\", op.Path)\n\t\tfmt.Fprintf(out, \"\\tStatus: %s\\n\", op.Status)\n\t\tfmt.Fprintf(out, \"\\tRPS: %.2f\\n\", op.RPS)\n\t\tif op.LastSeenAt != \"\" {\n\t\t\tfmt.Fprintf(out, \"\\tLast Seen: %s\\n\", op.LastSeenAt)\n\t\t}\n\t\tif op.UpdatedAt != \"\" {\n\t\t\tfmt.Fprintf(out, \"\\tUpdated At: %s\\n\", op.UpdatedAt)\n\t\t}\n\t}\n\tfmt.Fprintln(out)\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/apisecurity/discoveredoperations/root.go",
    "content": "package discoveredoperations\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"discovered-operations\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, globals *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = globals\n\tc.CmdClause = parent.Command(CommandName, \"Retrieve and update discovered API operations\")\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/apisecurity/discoveredoperations/update.go",
    "content": "package discoveredoperations\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/apisecurity/operations\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// UpdateCommand calls the Fastly API to update a discovered API operation's status.\ntype UpdateCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required .\n\tserviceName argparser.OptionalServiceNameID\n\tfile        string\n\toperationID argparser.OptionalString\n\tstatus      argparser.OptionalString\n}\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"update\", \"Update the status of discovered operation(s)\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t\tRequired:    true,\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\tc.CmdClause.Flag(\"operation-id\", \"The ID of the discovered operation (comma-separated for multiple)\").Action(c.operationID.Set).StringVar(&c.operationID.Value)\n\tc.CmdClause.Flag(\"file\", \"Update operations in bulk from a JSON file\").StringVar(&c.file)\n\n\t// Optional.\n\tc.CmdClause.Flag(\"status\", \"The new status to apply. Valid values are: 'discovered', 'ignored'\").Action(c.status.Set).StringVar(&c.status.Value)\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *UpdateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tif !c.operationID.WasSet && c.file == \"\" {\n\t\treturn fmt.Errorf(\"error parsing arguments: must provide either --operation-id or --file\")\n\t}\n\n\tif c.operationID.WasSet && c.file != \"\" {\n\t\treturn fmt.Errorf(\"error parsing arguments: cannot use both --operation-id and --file\")\n\t}\n\n\t// When using --file, status should not be provided via flag.\n\tif c.file != \"\" && c.status.WasSet {\n\t\treturn fmt.Errorf(\"error parsing arguments: cannot use both --file and --status (status should be specified in the JSON file)\")\n\t}\n\n\t// When using --operation-id, status is required.\n\tif c.operationID.WasSet && !c.status.WasSet {\n\t\treturn fmt.Errorf(\"error parsing arguments: --status is required when using --operation-id\")\n\t}\n\n\tserviceID, source, flag, err := argparser.ServiceID(c.serviceName, *c.Globals.Manifest, c.Globals.APIClient, c.Globals.ErrLog)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif c.Globals.Verbose() {\n\t\targparser.DisplayServiceID(serviceID, flag, source, out)\n\t}\n\n\t// Handle bulk mode from file.\n\tif c.file != \"\" {\n\t\tfileInput, err := c.readFromFile()\n\t\tif err != nil {\n\t\t\tc.Globals.ErrLog.Add(err)\n\t\t\treturn err\n\t\t}\n\n\t\t// Convert status from file to uppercase to map to API.\n\t\tstatus, err := c.validateStatus(fileInput.Status)\n\t\tif err != nil {\n\t\t\tc.Globals.ErrLog.Add(err)\n\t\t\treturn err\n\t\t}\n\n\t\treturn c.executeBulkUpdate(out, serviceID, status, fileInput.OperationIDs)\n\t}\n\n\t// Convert status to uppercase for API.\n\tstatus, err := c.validateStatus(c.status.Value)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\t// Handle comma-separated operation IDs.\n\toperationIDs := strings.Split(c.operationID.Value, \",\")\n\t// Trim whitespace from each ID\n\tfor i, id := range operationIDs {\n\t\toperationIDs[i] = strings.TrimSpace(id)\n\t}\n\n\t// If multiple operation IDs, use bulk update.\n\tif len(operationIDs) > 1 {\n\t\treturn c.executeBulkUpdate(out, serviceID, status, operationIDs)\n\t}\n\n\t// Handle single operation mode.\n\tinput := operations.UpdateDiscoveredStatusInput{\n\t\tServiceID:   &serviceID,\n\t\tOperationID: &c.operationID.Value,\n\t\tStatus:      &status,\n\t}\n\n\tfc, ok := c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\to, err := operations.UpdateDiscoveredStatus(context.TODO(), fc, &input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":   serviceID,\n\t\t\t\"Operation ID\": c.operationID.Value,\n\t\t\t\"Status\":       c.status.Value,\n\t\t})\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\tif !c.Globals.Verbose() {\n\t\treturn c.printSummary(out, o)\n\t}\n\n\treturn c.printVerbose(out, o)\n}\n\n// validateStatus converts and validates the status value.\nfunc (c *UpdateCommand) validateStatus(statusValue string) (string, error) {\n\tswitch statusValue {\n\tcase \"discovered\", \"DISCOVERED\":\n\t\treturn \"DISCOVERED\", nil\n\tcase \"ignored\", \"IGNORED\":\n\t\treturn \"IGNORED\", nil\n\tdefault:\n\t\treturn \"\", fmt.Errorf(\"invalid status: %s. Valid options: 'discovered', 'ignored'\", statusValue)\n\t}\n}\n\n// executeBulkUpdate performs a bulk update operation for multiple operation IDs.\nfunc (c *UpdateCommand) executeBulkUpdate(out io.Writer, serviceID string, status string, operationIDs []string) error {\n\tif c.Globals.Verbose() {\n\t\tfmt.Fprintf(out, \"Updating %d operation(s) with status: %s\\n\", len(operationIDs), status)\n\t\tfmt.Fprintf(out, \"Operation IDs: %v\\n\", operationIDs)\n\t}\n\n\tinput := operations.BulkUpdateDiscoveredStatusInput{\n\t\tServiceID:    &serviceID,\n\t\tOperationIDs: operationIDs,\n\t\tStatus:       &status,\n\t}\n\n\tfc, ok := c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tresults, err := operations.BulkUpdateDiscoveredStatus(context.TODO(), fc, &input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\": serviceID,\n\t\t\t\"Status\":     c.status.Value,\n\t\t\t\"Count\":      len(operationIDs),\n\t\t})\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, results); ok {\n\t\treturn err\n\t}\n\n\treturn c.printBulkResults(out, results)\n}\n\n// UpdateFileInput represents the JSON file format for bulk update operation.\ntype UpdateFileInput struct {\n\tOperationIDs []string `json:\"operation_ids\"`\n\tStatus       string   `json:\"status\"`\n}\n\n// readFromFile reads operation IDs and status from a JSON file.\nfunc (c *UpdateCommand) readFromFile() (*UpdateFileInput, error) {\n\tpath, err := filepath.Abs(c.file)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif _, err := os.Stat(path); err != nil {\n\t\treturn nil, err\n\t}\n\n\tfile, err := os.Open(path) /* #nosec */\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer file.Close()\n\n\tbyteValue, err := io.ReadAll(file)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to read file: %w\", err)\n\t}\n\n\tvar input UpdateFileInput\n\tif err := json.Unmarshal(byteValue, &input); err != nil {\n\t\treturn nil, fmt.Errorf(\"invalid JSON format: %w\", err)\n\t}\n\n\tif len(input.OperationIDs) == 0 {\n\t\treturn nil, fmt.Errorf(\"no operation IDs found in file: %s\", c.file)\n\t}\n\n\tif input.Status == \"\" {\n\t\treturn nil, fmt.Errorf(\"status not specified in file: %s\", c.file)\n\t}\n\n\treturn &input, nil\n}\n\n// printBulkResults displays the results of a bulk update operation.\nfunc (c *UpdateCommand) printBulkResults(out io.Writer, results *operations.BulkOperationResultsResponse) error {\n\tvar succeeded, failed int\n\tfor _, result := range results.Data {\n\t\tif result.StatusCode >= 200 && result.StatusCode < 300 {\n\t\t\tsucceeded++\n\t\t} else {\n\t\t\tfailed++\n\t\t}\n\t}\n\n\ttext.Success(out, \"Updated %d discovered operation(s)\", succeeded)\n\n\tif failed > 0 {\n\t\ttext.Warning(out, \"%d operation(s) failed to update\", failed)\n\t}\n\n\tif c.Globals.Verbose() {\n\t\ttext.Break(out)\n\t\ttw := text.NewTable(out)\n\t\ttw.AddHeader(\"OPERATION ID\", \"STATUS CODE\", \"RESULT\")\n\t\tfor _, result := range results.Data {\n\t\t\tstatus := \"Success\"\n\t\t\tif result.StatusCode < 200 || result.StatusCode >= 300 {\n\t\t\t\tstatus = fmt.Sprintf(\"Failed: %s\", result.Reason)\n\t\t\t}\n\t\t\ttw.AddLine(result.ID, fmt.Sprintf(\"%d\", result.StatusCode), status)\n\t\t}\n\t\ttw.Print()\n\t}\n\n\treturn nil\n}\n\n// printSummary displays the discovered operation in a simple format.\nfunc (c *UpdateCommand) printSummary(out io.Writer, op *operations.DiscoveredOperation) error {\n\tfmt.Fprintf(out, \"Updated discovered operation:\\n\")\n\tfmt.Fprintf(out, \"  ID: %s\\n\", op.ID)\n\tfmt.Fprintf(out, \"  Method: %s\\n\", op.Method)\n\tfmt.Fprintf(out, \"  Domain: %s\\n\", op.Domain)\n\tfmt.Fprintf(out, \"  Path: %s\\n\", op.Path)\n\tfmt.Fprintf(out, \"  Status: %s\\n\", op.Status)\n\n\treturn nil\n}\n\n// printVerbose displays detailed information for the discovered operation.\nfunc (c *UpdateCommand) printVerbose(out io.Writer, op *operations.DiscoveredOperation) error {\n\tfmt.Fprintf(out, \"\\nUpdated Discovered Operation\\n\")\n\tfmt.Fprintf(out, \"\\tID: %s\\n\", op.ID)\n\tfmt.Fprintf(out, \"\\tMethod: %s\\n\", op.Method)\n\tfmt.Fprintf(out, \"\\tDomain: %s\\n\", op.Domain)\n\tfmt.Fprintf(out, \"\\tPath: %s\\n\", op.Path)\n\tfmt.Fprintf(out, \"\\tStatus: %s\\n\", op.Status)\n\tfmt.Fprintf(out, \"\\tRPS: %.2f\\n\", op.RPS)\n\tif op.LastSeenAt != \"\" {\n\t\tfmt.Fprintf(out, \"\\tLast Seen: %s\\n\", op.LastSeenAt)\n\t}\n\tif op.UpdatedAt != \"\" {\n\t\tfmt.Fprintf(out, \"\\tUpdated At: %s\\n\", op.UpdatedAt)\n\t}\n\tfmt.Fprintln(out)\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/apisecurity/doc.go",
    "content": "// Package apisecurity contains commands to manage API operations for services.\npackage apisecurity\n"
  },
  {
    "path": "pkg/commands/apisecurity/operations/addtags.go",
    "content": "package operations\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"path/filepath\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/apisecurity/operations\"\n\n\t\"github.com/fastly/kingpin\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// AddTagsCommand calls the Fastly API to add tags to operations.\ntype AddTagsCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tserviceName argparser.OptionalServiceNameID\n\ttagIDs      []string\n\n\t// Optional.\n\toperationIDs []string\n\tfile         string\n}\n\n// NewAddTagsCommand returns a usable command registered under the parent.\nfunc NewAddTagsCommand(parent argparser.Registerer, g *global.Data) *AddTagsCommand {\n\tc := AddTagsCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"add-tags\", \"Add tags to operation(s)\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t\tRequired:    true,\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\tc.CmdClause.Flag(\"tag-ids\", \"Comma-separated list of tag IDs to add\").Required().StringsVar(&c.tagIDs, kingpin.Separator(\",\"))\n\n\t// Optional.\n\tc.CmdClause.Flag(\"operation-ids\", \"Comma-separated list of operation IDs to add tags to\").StringsVar(&c.operationIDs, kingpin.Separator(\",\"))\n\tc.CmdClause.Flag(\"file\", \"Add tags to operations in bulk from a JSON file\").StringVar(&c.file)\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *AddTagsCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tif len(c.operationIDs) == 0 && c.file == \"\" {\n\t\treturn fmt.Errorf(\"error parsing arguments: must provide either --operation-ids or --file\")\n\t}\n\n\tif len(c.operationIDs) > 0 && c.file != \"\" {\n\t\treturn fmt.Errorf(\"error parsing arguments: cannot use both --operation-ids and --file\")\n\t}\n\n\tserviceID, source, flag, err := argparser.ServiceID(c.serviceName, *c.Globals.Manifest, c.Globals.APIClient, c.Globals.ErrLog)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif c.Globals.Verbose() {\n\t\targparser.DisplayServiceID(serviceID, flag, source, out)\n\t}\n\n\t// Get operation IDs and tag IDs from file or flags\n\tvar operationIDs []string\n\tvar tagIDs []string\n\tif c.file != \"\" {\n\t\tfileInput, err := c.readFromFile()\n\t\tif err != nil {\n\t\t\tc.Globals.ErrLog.Add(err)\n\t\t\treturn err\n\t\t}\n\t\toperationIDs = fileInput.OperationIDs\n\t\ttagIDs = fileInput.TagIDs\n\t} else {\n\t\toperationIDs = c.operationIDs\n\t\ttagIDs = c.tagIDs\n\t}\n\n\tif c.Globals.Verbose() {\n\t\tfmt.Fprintf(out, \"Adding %d tag(s) to %d operation(s)\\n\", len(tagIDs), len(operationIDs))\n\t}\n\n\tinput := &operations.BulkAddTagsInput{\n\t\tServiceID:    &serviceID,\n\t\tOperationIDs: operationIDs,\n\t\tTagIDs:       tagIDs,\n\t}\n\n\tfc, ok := c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tresults, err := operations.BulkAddTags(context.TODO(), fc, input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Operation Count\": len(operationIDs),\n\t\t\t\"Tag Count\":       len(c.tagIDs),\n\t\t})\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, results); ok {\n\t\treturn err\n\t}\n\n\treturn c.printResults(out, results)\n}\n\n// AddTagsFileInput represents the JSON file format for bulk add-tags operation.\ntype AddTagsFileInput struct {\n\tOperationIDs []string `json:\"operation_ids\"`\n\tTagIDs       []string `json:\"tag_ids\"`\n}\n\n// readFromFile reads operation IDs and tag IDs from a JSON file.\nfunc (c *AddTagsCommand) readFromFile() (*AddTagsFileInput, error) {\n\tpath, err := filepath.Abs(c.file)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif _, err := os.Stat(path); err != nil {\n\t\treturn nil, err\n\t}\n\n\tfile, err := os.Open(path) /* #nosec */\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer file.Close()\n\n\tbyteValue, err := io.ReadAll(file)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to read file: %w\", err)\n\t}\n\n\tvar input AddTagsFileInput\n\tif err := json.Unmarshal(byteValue, &input); err != nil {\n\t\treturn nil, fmt.Errorf(\"invalid JSON format: %w\", err)\n\t}\n\n\tif len(input.OperationIDs) == 0 {\n\t\treturn nil, fmt.Errorf(\"no operation IDs found in file: %s\", c.file)\n\t}\n\n\tif len(input.TagIDs) == 0 {\n\t\treturn nil, fmt.Errorf(\"no tag IDs found in file: %s\", c.file)\n\t}\n\n\treturn &input, nil\n}\n\n// printResults displays the results of the bulk add tags operation.\nfunc (c *AddTagsCommand) printResults(out io.Writer, results *operations.BulkOperationResultsResponse) error {\n\tvar succeeded, failed int\n\tfor _, result := range results.Data {\n\t\tif result.StatusCode >= 200 && result.StatusCode < 300 {\n\t\t\tsucceeded++\n\t\t} else {\n\t\t\tfailed++\n\t\t}\n\t}\n\n\ttext.Success(out, \"Added tags to %d operation(s)\", succeeded)\n\n\tif failed > 0 {\n\t\ttext.Warning(out, \"%d operation(s) failed\", failed)\n\t}\n\n\tif c.Globals.Verbose() {\n\t\ttext.Break(out)\n\t\ttw := text.NewTable(out)\n\t\ttw.AddHeader(\"OPERATION ID\", \"STATUS CODE\", \"RESULT\")\n\t\tfor _, result := range results.Data {\n\t\t\tstatus := \"Success\"\n\t\t\tif result.StatusCode < 200 || result.StatusCode >= 300 {\n\t\t\t\tstatus = fmt.Sprintf(\"Failed: %s\", result.Reason)\n\t\t\t}\n\t\t\ttw.AddLine(result.ID, fmt.Sprintf(\"%d\", result.StatusCode), status)\n\t\t}\n\t\ttw.Print()\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/apisecurity/operations/create.go",
    "content": "package operations\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/apisecurity/operations\"\n\n\t\"github.com/fastly/kingpin\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// CreateCommand calls the Fastly API to create an operation.\ntype CreateCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tserviceName argparser.OptionalServiceNameID\n\tmethod      string\n\tdomain      string\n\tpath        string\n\n\t// Optional.\n\tdescription string\n\ttagIDs      []string\n\tfile        string\n}\n\n// OperationInput represents a single operation to be created from JSON.\ntype OperationInput struct {\n\tMethod      string   `json:\"method\"`\n\tDomain      string   `json:\"domain\"`\n\tPath        string   `json:\"path\"`\n\tDescription string   `json:\"description,omitempty\"`\n\tTagIDs      []string `json:\"tag_ids,omitempty\"`\n}\n\n// CreateFileInput represents the JSON file format for bulk create operations.\ntype CreateFileInput struct {\n\tOperations []OperationInput `json:\"operations\"`\n}\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"create\", \"Create an operation\").Alias(\"add\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t\tRequired:    true,\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\tc.CmdClause.Flag(\"method\", \"The HTTP method for the operation (e.g., GET, POST, PUT)\").StringVar(&c.method)\n\tc.CmdClause.Flag(\"domain\", \"Domain for the operation\").StringVar(&c.domain)\n\tc.CmdClause.Flag(\"path\", \"The path for the operation, which may include path parameters.(e.g., /api/users)\").StringVar(&c.path)\n\n\t// Optional.\n\tc.CmdClause.Flag(\"description\", \"Description of what the operation does\").StringVar(&c.description)\n\tc.CmdClause.Flag(\"tag-ids\", \"A comma-separated array of operation tag IDs associated with this operation\").StringsVar(&c.tagIDs, kingpin.Separator(\",\"))\n\tc.CmdClause.Flag(\"file\", \"Create operations in bulk from a JSON file\").StringVar(&c.file)\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *CreateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\t// Validate flags\n\tif c.file != \"\" && (c.method != \"\" || c.domain != \"\" || c.path != \"\") {\n\t\treturn fmt.Errorf(\"error parsing arguments: cannot use both --file and individual operation flags (--method, --domain, --path)\")\n\t}\n\n\tif c.file == \"\" && (c.method == \"\" || c.domain == \"\" || c.path == \"\") {\n\t\treturn fmt.Errorf(\"error parsing arguments: must provide either --file or all of --method, --domain, and --path\")\n\t}\n\n\tserviceID, source, flag, err := argparser.ServiceID(c.serviceName, *c.Globals.Manifest, c.Globals.APIClient, c.Globals.ErrLog)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif c.Globals.Verbose() {\n\t\targparser.DisplayServiceID(serviceID, flag, source, out)\n\t}\n\n\t// Handle bulk mode from file\n\tif c.file != \"\" {\n\t\treturn c.createFromFile(serviceID, out)\n\t}\n\n\t// Handle single operation mode\n\tinput := c.constructInput(serviceID)\n\n\tfc, ok := c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\to, err := operations.Create(context.TODO(), fc, input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\": serviceID,\n\t\t\t\"Method\":     c.method,\n\t\t\t\"Domain\":     c.domain,\n\t\t\t\"Path\":       c.path,\n\t\t})\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Created operation %s %s%s (ID: %s)\", strings.ToUpper(o.Method), o.Domain, o.Path, o.ID)\n\tif c.description != \"\" {\n\t\tfmt.Fprintf(out, \"\\nDescription: %s\\n\", o.Description)\n\t}\n\tif len(o.TagIDs) > 0 {\n\t\tfmt.Fprintf(out, \"Tags: %d associated\\n\", len(o.TagIDs))\n\t}\n\n\treturn nil\n}\n\n// constructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *CreateCommand) constructInput(serviceID string) *operations.CreateInput {\n\tinput := &operations.CreateInput{\n\t\tServiceID: &serviceID,\n\t\tMethod:    &c.method,\n\t\tDomain:    &c.domain,\n\t\tPath:      &c.path,\n\t}\n\n\tif c.description != \"\" {\n\t\tinput.Description = &c.description\n\t}\n\n\tif len(c.tagIDs) > 0 {\n\t\tinput.TagIDs = c.tagIDs\n\t}\n\n\treturn input\n}\n\n// createFromFile creates operations in bulk from a newline-delimited JSON file.\nfunc (c *CreateCommand) createFromFile(serviceID string, out io.Writer) error {\n\tops, err := c.readOperationsFromFile()\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif c.Globals.Verbose() {\n\t\tfmt.Fprintf(out, \"Creating %d operation(s) from file\\n\", len(ops))\n\t}\n\n\ttype result struct {\n\t\tOperation *operations.Operation\n\t\tError     error\n\t}\n\n\tresults := make([]result, 0, len(ops))\n\n\tfc, ok := c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tfor _, op := range ops {\n\t\tinput := &operations.CreateInput{\n\t\t\tServiceID:   &serviceID,\n\t\t\tMethod:      &op.Method,\n\t\t\tDomain:      &op.Domain,\n\t\t\tPath:        &op.Path,\n\t\t\tDescription: &op.Description,\n\t\t\tTagIDs:      op.TagIDs,\n\t\t}\n\n\t\to, err := operations.Create(context.TODO(), fc, input)\n\t\tresults = append(results, result{\n\t\t\tOperation: o,\n\t\t\tError:     err,\n\t\t})\n\t}\n\n\t// Count successes and failures\n\tvar succeeded, failed int\n\tfor _, r := range results {\n\t\tif r.Error == nil {\n\t\t\tsucceeded++\n\t\t} else {\n\t\t\tfailed++\n\t\t}\n\t}\n\n\tif c.JSONOutput.Enabled {\n\t\ttype jsonResult struct {\n\t\t\tSuccess    int                     `json:\"success\"`\n\t\t\tFailed     int                     `json:\"failed\"`\n\t\t\tOperations []*operations.Operation `json:\"operations,omitempty\"`\n\t\t\tErrors     []string                `json:\"errors,omitempty\"`\n\t\t}\n\n\t\tjr := jsonResult{\n\t\t\tSuccess: succeeded,\n\t\t\tFailed:  failed,\n\t\t}\n\n\t\tfor _, r := range results {\n\t\t\tif r.Error == nil {\n\t\t\t\tjr.Operations = append(jr.Operations, r.Operation)\n\t\t\t} else {\n\t\t\t\tjr.Errors = append(jr.Errors, r.Error.Error())\n\t\t\t}\n\t\t}\n\n\t\t_, err := c.WriteJSON(out, jr)\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Created %d operation(s)\", succeeded)\n\n\tif failed > 0 {\n\t\ttext.Warning(out, \"%d operation(s) failed to create\", failed)\n\t}\n\n\tif c.Globals.Verbose() {\n\t\ttext.Break(out)\n\t\ttw := text.NewTable(out)\n\t\ttw.AddHeader(\"METHOD\", \"DOMAIN\", \"PATH\", \"RESULT\")\n\t\tfor i, r := range results {\n\t\t\tstatus := \"Success\"\n\t\t\tif r.Error != nil {\n\t\t\t\tstatus = fmt.Sprintf(\"Failed: %s\", r.Error.Error())\n\t\t\t}\n\t\t\top := ops[i]\n\t\t\ttw.AddLine(strings.ToUpper(op.Method), op.Domain, op.Path, status)\n\t\t}\n\t\ttw.Print()\n\t}\n\n\tif failed > 0 {\n\t\treturn fmt.Errorf(\"%d operation(s) failed to create\", failed)\n\t}\n\n\treturn nil\n}\n\n// readOperationsFromFile reads operations from a JSON file.\nfunc (c *CreateCommand) readOperationsFromFile() ([]OperationInput, error) {\n\tpath, err := filepath.Abs(c.file)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif _, err := os.Stat(path); err != nil {\n\t\treturn nil, err\n\t}\n\n\tfile, err := os.Open(path) /* #nosec */\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer file.Close()\n\n\tbyteValue, err := io.ReadAll(file)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to read file: %w\", err)\n\t}\n\n\tvar input CreateFileInput\n\tif err := json.Unmarshal(byteValue, &input); err != nil {\n\t\treturn nil, fmt.Errorf(\"invalid JSON format: %w\", err)\n\t}\n\n\tif len(input.Operations) == 0 {\n\t\treturn nil, fmt.Errorf(\"no operations found in file: %s\", c.file)\n\t}\n\n\t// Validate required fields\n\tfor i, op := range input.Operations {\n\t\tif op.Method == \"\" || op.Domain == \"\" || op.Path == \"\" {\n\t\t\treturn nil, fmt.Errorf(\"operation %d: missing required fields (method, domain, path)\", i+1)\n\t\t}\n\t}\n\n\treturn input.Operations, nil\n}\n"
  },
  {
    "path": "pkg/commands/apisecurity/operations/delete.go",
    "content": "package operations\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/apisecurity/operations\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DeleteCommand calls the Fastly API to delete an operation.\ntype DeleteCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tserviceName argparser.OptionalServiceNameID\n\toperationID string\n}\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"delete\", \"Delete an operation\").Alias(\"remove\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t\tRequired:    true,\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\tc.CmdClause.Flag(\"operation-id\", \"The unique identifier of the operation\").Required().StringVar(&c.operationID)\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DeleteCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tserviceID, source, flag, err := argparser.ServiceID(c.serviceName, *c.Globals.Manifest, c.Globals.APIClient, c.Globals.ErrLog)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif c.Globals.Verbose() {\n\t\targparser.DisplayServiceID(serviceID, flag, source, out)\n\t}\n\n\tinput := &operations.DeleteInput{\n\t\tServiceID:   &serviceID,\n\t\tOperationID: &c.operationID,\n\t}\n\n\tfc, ok := c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\terr = operations.Delete(context.TODO(), fc, input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":   serviceID,\n\t\t\t\"Operation ID\": c.operationID,\n\t\t})\n\t\treturn err\n\t}\n\n\tif c.JSONOutput.Enabled {\n\t\to := struct {\n\t\t\tServiceID   string `json:\"service_id\"`\n\t\t\tOperationID string `json:\"operation_id\"`\n\t\t\tDeleted     bool   `json:\"deleted\"`\n\t\t}{\n\t\t\tserviceID,\n\t\t\tc.operationID,\n\t\t\ttrue,\n\t\t}\n\t\t_, err := c.WriteJSON(out, o)\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Deleted operation '%s'\", c.operationID)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/apisecurity/operations/describe.go",
    "content": "package operations\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"strings\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/apisecurity/operations\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// DescribeCommand calls the Fastly API to describe an operation.\ntype DescribeCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tserviceName argparser.OptionalServiceNameID\n\toperationID string\n}\n\n// NewDescribeCommand returns a usable command registered under the parent.\nfunc NewDescribeCommand(parent argparser.Registerer, g *global.Data) *DescribeCommand {\n\tc := DescribeCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"describe\", \"Retrieve a single operation\").Alias(\"get\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t\tRequired:    true,\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\tc.CmdClause.Flag(\"operation-id\", \"The unique identifier of the operation\").Required().StringVar(&c.operationID)\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DescribeCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tserviceID, source, flag, err := argparser.ServiceID(c.serviceName, *c.Globals.Manifest, c.Globals.APIClient, c.Globals.ErrLog)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif c.Globals.Verbose() {\n\t\targparser.DisplayServiceID(serviceID, flag, source, out)\n\t}\n\n\tinput := &operations.DescribeInput{\n\t\tServiceID:   &serviceID,\n\t\tOperationID: &c.operationID,\n\t}\n\n\tfc, ok := c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\to, err := operations.Describe(context.TODO(), fc, input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":   serviceID,\n\t\t\t\"Operation ID\": c.operationID,\n\t\t})\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\treturn c.print(out, o)\n}\n\n// print displays the information returned from the API.\nfunc (c *DescribeCommand) print(out io.Writer, o *operations.Operation) error {\n\tfmt.Fprintf(out, \"\\nOperation ID: %s\\n\", o.ID)\n\tfmt.Fprintf(out, \"Method: %s\\n\", strings.ToUpper(o.Method))\n\tfmt.Fprintf(out, \"Domain: %s\\n\", o.Domain)\n\tfmt.Fprintf(out, \"Path: %s\\n\", o.Path)\n\tfmt.Fprintf(out, \"Description: %s\\n\", o.Description)\n\tfmt.Fprintf(out, \"Status: %s\\n\", o.Status)\n\tfmt.Fprintf(out, \"Tag IDs: %s\\n\", strings.Join(o.TagIDs, \", \"))\n\tfmt.Fprintf(out, \"RPS: %.2f\\n\\n\", o.RPS)\n\n\tif o.CreatedAt != \"\" {\n\t\tfmt.Fprintf(out, \"Created At: %s\\n\", o.CreatedAt)\n\t}\n\n\tif o.UpdatedAt != \"\" {\n\t\tfmt.Fprintf(out, \"Updated At: %s\\n\", o.UpdatedAt)\n\t}\n\n\tif o.LastSeenAt != \"\" {\n\t\tfmt.Fprintf(out, \"Last Seen At: %s\\n\", o.LastSeenAt)\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/apisecurity/operations/doc.go",
    "content": "// Package operations contains commands to manage operations associated with services.\npackage operations\n"
  },
  {
    "path": "pkg/commands/apisecurity/operations/list.go",
    "content": "package operations\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"strings\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/apisecurity/operations\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// ListCommand calls the Fastly API to list operations.\ntype ListCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tinput       operations.ListOperationsInput\n\tserviceName argparser.OptionalServiceNameID\n\n\t// Optional.\n\tdomain argparser.OptionalString\n\tmethod argparser.OptionalString\n\tpath   argparser.OptionalString\n\ttagID  argparser.OptionalString\n}\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"list\", \"List operations\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t\tRequired:    true,\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\n\t// Optional.\n\tc.CmdClause.Flag(\"domain\", \"Filters operations by domain (exact match)\").Action(c.domain.Set).StringVar(&c.domain.Value)\n\tc.CmdClause.Flag(\"method\", \"Filters operations by HTTP method (e.g., GET, POST, PUT)\").Action(c.method.Set).StringVar(&c.method.Value)\n\tc.CmdClause.Flag(\"path\", \"Filters operations by path (exact match)\").Action(c.path.Set).StringVar(&c.path.Value)\n\tc.CmdClause.Flag(\"tag-id\", \"Filters operations by tag ID\").Action(c.tagID.Set).StringVar(&c.tagID.Value)\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *ListCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tserviceID, source, flag, err := argparser.ServiceID(c.serviceName, *c.Globals.Manifest, c.Globals.APIClient, c.Globals.ErrLog)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif c.Globals.Verbose() {\n\t\targparser.DisplayServiceID(serviceID, flag, source, out)\n\t}\n\n\tc.input.ServiceID = &serviceID\n\n\tif c.domain.WasSet {\n\t\tc.input.Domain = []string{c.domain.Value}\n\t}\n\tif c.method.WasSet {\n\t\tc.input.Method = []string{c.method.Value}\n\t}\n\tif c.path.WasSet {\n\t\tc.input.Path = &c.path.Value\n\t}\n\tif c.tagID.WasSet {\n\t\tc.input.TagID = &c.tagID.Value\n\t}\n\n\tfc, ok := c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\t// Auto-paginate through all results\n\tvar allOperations []operations.Operation\n\tpage := 0\n\tlimit := 100\n\n\tfor {\n\t\tc.input.Page = &page\n\t\tc.input.Limit = &limit\n\n\t\to, err := operations.ListOperations(context.TODO(), fc, &c.input)\n\t\tif err != nil {\n\t\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\t\"Service ID\": serviceID,\n\t\t\t\t\"Domain\":     c.domain.Value,\n\t\t\t\t\"Method\":     c.method.Value,\n\t\t\t\t\"Path\":       c.path.Value,\n\t\t\t\t\"Tag ID\":     c.tagID.Value,\n\t\t\t\t\"Page\":       page,\n\t\t\t})\n\t\t\treturn err\n\t\t}\n\n\t\tif o == nil || len(o.Data) == 0 {\n\t\t\tbreak\n\t\t}\n\n\t\tallOperations = append(allOperations, o.Data...)\n\n\t\t// Check if we've fetched all results\n\t\tif len(allOperations) >= o.Meta.Total {\n\t\t\tbreak\n\t\t}\n\n\t\tpage++\n\t}\n\n\tif ok, err := c.WriteJSON(out, allOperations); ok {\n\t\treturn err\n\t}\n\n\tif !c.Globals.Verbose() {\n\t\treturn c.printSummary(out, allOperations)\n\t}\n\n\treturn c.printVerbose(out, allOperations)\n}\n\n// printSummary displays the operations in a table format.\nfunc (c *ListCommand) printSummary(out io.Writer, o []operations.Operation) error {\n\ttw := text.NewTable(out)\n\ttw.AddHeader(\"ID\", \"METHOD\", \"DOMAIN\", \"PATH\", \"DESCRIPTION\", \"TAGS\")\n\tfor _, op := range o {\n\t\tdescription := op.Description\n\t\tif len(description) > 50 {\n\t\t\tdescription = description[:47] + \"...\"\n\t\t}\n\t\ttags := fmt.Sprintf(\"%d\", len(op.TagIDs))\n\t\ttw.AddLine(\n\t\t\top.ID,\n\t\t\tstrings.ToUpper(op.Method),\n\t\t\top.Domain,\n\t\t\top.Path,\n\t\t\tdescription,\n\t\t\ttags,\n\t\t)\n\t}\n\ttw.Print()\n\n\treturn nil\n}\n\n// printVerbose displays detailed information for each operation.\nfunc (c *ListCommand) printVerbose(out io.Writer, o []operations.Operation) error {\n\tfor i, op := range o {\n\t\tfmt.Fprintf(out, \"\\nOperation %d/%d\\n\", i+1, len(o))\n\t\tfmt.Fprintf(out, \"\\tID: %s\\n\", op.ID)\n\t\tfmt.Fprintf(out, \"\\tMethod: %s\\n\", strings.ToUpper(op.Method))\n\t\tfmt.Fprintf(out, \"\\tDomain: %s\\n\", op.Domain)\n\t\tfmt.Fprintf(out, \"\\tPath: %s\\n\", op.Path)\n\t\tif op.Description != \"\" {\n\t\t\tfmt.Fprintf(out, \"\\tDescription: %s\\n\", op.Description)\n\t\t}\n\t\tif op.Status != \"\" {\n\t\t\tfmt.Fprintf(out, \"\\tStatus: %s\\n\", op.Status)\n\t\t}\n\t\tif len(op.TagIDs) > 0 {\n\t\t\tfmt.Fprintf(out, \"\\tTag IDs: %s\\n\", strings.Join(op.TagIDs, \", \"))\n\t\t}\n\t\tif op.RPS > 0 {\n\t\t\tfmt.Fprintf(out, \"\\tRPS: %.2f\\n\", op.RPS)\n\t\t}\n\t\tif op.CreatedAt != \"\" {\n\t\t\tfmt.Fprintf(out, \"\\tCreated At: %s\\n\", op.CreatedAt)\n\t\t}\n\t\tif op.UpdatedAt != \"\" {\n\t\t\tfmt.Fprintf(out, \"\\tUpdated At: %s\\n\", op.UpdatedAt)\n\t\t}\n\t\tif op.LastSeenAt != \"\" {\n\t\t\tfmt.Fprintf(out, \"\\tLast Seen: %s\\n\", op.LastSeenAt)\n\t\t}\n\t}\n\tfmt.Fprintln(out)\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/apisecurity/operations/operations_test.go",
    "content": "package operations_test\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly/apisecurity/operations\"\n\n\tapisecurity \"github.com/fastly/cli/pkg/commands/apisecurity\"\n\troot \"github.com/fastly/cli/pkg/commands/apisecurity/operations\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n)\n\nconst (\n\tserviceID = \"test-service-id\"\n)\n\nvar (\n\tlistResponse = operations.Operations{\n\t\tData: []operations.Operation{\n\t\t\t{\n\t\t\t\tID:          \"test-operation-id\",\n\t\t\t\tMethod:      \"DELETE\",\n\t\t\t\tDomain:      \"www.foo.com\",\n\t\t\t\tPath:        \"/api/v1/users/{var1}\",\n\t\t\t\tDescription: \"Retrieve user information\",\n\t\t\t\tStatus:      \"SAVED\",\n\t\t\t\tRPS:         10.5,\n\t\t\t\tCreatedAt:   \"2026-02-02T14:27:16Z\",\n\t\t\t\tUpdatedAt:   \"2026-02-02T14:33:19Z\",\n\t\t\t\tTagIDs:      []string{},\n\t\t\t},\n\t\t\t{\n\t\t\t\tID:          \"test-operation-id-2\",\n\t\t\t\tMethod:      \"POST\",\n\t\t\t\tDomain:      \"www.foo.com\",\n\t\t\t\tPath:        \"/api/v1/users\",\n\t\t\t\tDescription: \"Create a new user\",\n\t\t\t\tStatus:      \"SAVED\",\n\t\t\t\tRPS:         5.2,\n\t\t\t\tCreatedAt:   \"2026-02-01T10:00:00Z\",\n\t\t\t\tUpdatedAt:   \"2026-02-01T10:30:00Z\",\n\t\t\t\tTagIDs:      []string{\"tag-1\", \"tag-2\"},\n\t\t\t},\n\t\t},\n\t\tMeta: operations.Meta{\n\t\t\tLimit: 2,\n\t\t\tTotal: 2,\n\t\t},\n\t}\n\n\tlistResponseJSON = testutil.GenJSON(listResponse)\n\n\tlistOperationsOutput = strings.TrimSpace(`\nID                   METHOD  DOMAIN       PATH                  DESCRIPTION                TAGS\ntest-operation-id    DELETE  www.foo.com  /api/v1/users/{var1}  Retrieve user information  0\ntest-operation-id-2  POST    www.foo.com  /api/v1/users         Create a new user          2\n`) + \"\\n\"\n\n\tlistOperationsVerboseOutput = strings.TrimSpace(`\nOperation 1/2\n\tID: test-operation-id\n\tMethod: DELETE\n\tDomain: www.foo.com\n\tPath: /api/v1/users/{var1}\n\tDescription: Retrieve user information\n\tStatus: SAVED\n\tRPS: 10.50\n\tCreated At: 2026-02-02T14:27:16Z\n\tUpdated At: 2026-02-02T14:33:19Z\n\nOperation 2/2\n\tID: test-operation-id-2\n\tMethod: POST\n\tDomain: www.foo.com\n\tPath: /api/v1/users\n\tDescription: Create a new user\n\tStatus: SAVED\n\tTag IDs: tag-1, tag-2\n\tRPS: 5.20\n\tCreated At: 2026-02-01T10:00:00Z\n\tUpdated At: 2026-02-01T10:30:00Z\n`) + \"\\n\\n\"\n)\n\nfunc TestListCommand(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --service-id flag\",\n\t\t\tArgs:      \"\",\n\t\t\tWantError: \"error parsing arguments: required flag --service-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--service-id %s\", serviceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(listResponse))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: listOperationsOutput,\n\t\t},\n\t\t{\n\t\t\tName: \"validate --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--service-id %s --json\", serviceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(listResponseJSON)),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: string(testutil.GenJSON(listResponse.Data)),\n\t\t},\n\t\t{\n\t\t\tName: \"validate --verbose output\",\n\t\t\tArgs: fmt.Sprintf(\"--service-id %s --verbose\", serviceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(listResponse))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: listOperationsVerboseOutput,\n\t\t},\n\t\t{\n\t\t\tName: \"validate API error\",\n\t\t\tArgs: fmt.Sprintf(\"--service-id %s\", serviceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusInternalServerError,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusInternalServerError),\n\t\t\t\t\t\tBody:       io.NopCloser(strings.NewReader(`{\"detail\":\"Internal Server Error\"}`)),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"500\",\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{apisecurity.CommandName, root.CommandName, \"list\"}, scenarios)\n}\n\nfunc TestListCommandWithFilters(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName: \"validate --domain filter\",\n\t\t\tArgs: fmt.Sprintf(\"--service-id %s --domain www.foo.com\", serviceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(listResponse))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: listOperationsOutput,\n\t\t},\n\t\t{\n\t\t\tName: \"validate --method filter\",\n\t\t\tArgs: fmt.Sprintf(\"--service-id %s --method DELETE\", serviceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(listResponse))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: listOperationsOutput,\n\t\t},\n\t\t{\n\t\t\tName: \"validate --path filter\",\n\t\t\tArgs: fmt.Sprintf(\"--service-id %s --path /api/v1/users\", serviceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(listResponse))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: listOperationsOutput,\n\t\t},\n\t\t{\n\t\t\tName: \"validate --tag-id filter\",\n\t\t\tArgs: fmt.Sprintf(\"--service-id %s --tag-id tag-1\", serviceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(listResponse))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: listOperationsOutput,\n\t\t},\n\t\t{\n\t\t\tName: \"validate empty results\",\n\t\t\tArgs: fmt.Sprintf(\"--service-id %s\", serviceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(operations.Operations{\n\t\t\t\t\t\t\tData: []operations.Operation{},\n\t\t\t\t\t\t\tMeta: operations.Meta{\n\t\t\t\t\t\t\t\tLimit: 0,\n\t\t\t\t\t\t\t\tTotal: 0,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t}))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{apisecurity.CommandName, root.CommandName, \"list\"}, scenarios)\n}\n"
  },
  {
    "path": "pkg/commands/apisecurity/operations/root.go",
    "content": "package operations\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"operations\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, globals *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = globals\n\tc.CmdClause = parent.Command(CommandName, \"Manage operations associated with services\")\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/apisecurity/operations/update.go",
    "content": "package operations\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"strings\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/apisecurity/operations\"\n\n\t\"github.com/fastly/kingpin\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// UpdateCommand calls the Fastly API to update an operation.\ntype UpdateCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tserviceName argparser.OptionalServiceNameID\n\toperationID string\n\n\t// Optional.\n\tdescription argparser.OptionalString\n\ttagIDs      argparser.OptionalStringSlice\n}\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"update\", \"Update an operation\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t\tRequired:    true,\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\tc.CmdClause.Flag(\"operation-id\", \"The unique identifier of the operation\").Required().StringVar(&c.operationID)\n\n\t// Optional.\n\tc.CmdClause.Flag(\"description\", \"Updated description of what the operation does\").Action(c.description.Set).StringVar(&c.description.Value)\n\tc.CmdClause.Flag(\"tag-ids\", \"Comma-separated list of tag IDs to associate with the operation\").Action(c.tagIDs.Set).StringsVar(&c.tagIDs.Value, kingpin.Separator(\",\"))\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *UpdateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tif !c.description.WasSet && !c.tagIDs.WasSet {\n\t\treturn fmt.Errorf(\"error parsing arguments: must provide at least one field to update (--description or --tag-ids)\")\n\t}\n\n\tserviceID, source, flag, err := argparser.ServiceID(c.serviceName, *c.Globals.Manifest, c.Globals.APIClient, c.Globals.ErrLog)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif c.Globals.Verbose() {\n\t\targparser.DisplayServiceID(serviceID, flag, source, out)\n\t}\n\n\tinput := c.constructInput(serviceID)\n\n\tfc, ok := c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\to, err := operations.Update(context.TODO(), fc, input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":   serviceID,\n\t\t\t\"Operation ID\": c.operationID,\n\t\t})\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Updated operation %s %s%s (ID: %s)\", strings.ToUpper(o.Method), o.Domain, o.Path, o.ID)\n\n\tif c.Globals.Verbose() {\n\t\tfmt.Fprintln(out)\n\t\tif o.Description != \"\" {\n\t\t\tfmt.Fprintf(out, \"Description: %s\\n\", o.Description)\n\t\t}\n\t\tif len(o.TagIDs) > 0 {\n\t\t\tfmt.Fprintf(out, \"Tags: %d associated\\n\", len(o.TagIDs))\n\t\t}\n\t\tif o.UpdatedAt != \"\" {\n\t\t\tfmt.Fprintf(out, \"Updated At: %s\\n\", o.UpdatedAt)\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// constructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *UpdateCommand) constructInput(serviceID string) *operations.UpdateInput {\n\tinput := &operations.UpdateInput{\n\t\tServiceID:   &serviceID,\n\t\tOperationID: &c.operationID,\n\t}\n\n\tif c.description.WasSet {\n\t\tinput.Description = &c.description.Value\n\t}\n\n\tif c.tagIDs.WasSet {\n\t\tinput.TagIDs = c.tagIDs.Value\n\t}\n\n\treturn input\n}\n"
  },
  {
    "path": "pkg/commands/apisecurity/root.go",
    "content": "package apisecurity\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"apisecurity\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, globals *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = globals\n\tc.CmdClause = parent.Command(CommandName, \"Manipulate Fastly API security operations\")\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/apisecurity/tags/create.go",
    "content": "package tags\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/apisecurity/operations\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// CreateCommand calls the Fastly API to create an operation tag.\ntype CreateCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tname        string\n\tserviceName argparser.OptionalServiceNameID\n\n\t// Optional.\n\tdescription argparser.OptionalString\n}\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\n\tc.CmdClause = parent.Command(\"create\", \"Create an operation tag\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"Name of the operation tag\").Required().StringVar(&c.name)\n\n\t// Optional.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\tc.CmdClause.Flag(\"description\", \"Description of the operation tag\").Action(c.description.Set).StringVar(&c.description.Value)\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *CreateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tserviceID, source, flag, err := argparser.ServiceID(c.serviceName, *c.Globals.Manifest, c.Globals.APIClient, c.Globals.ErrLog)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif c.Globals.Verbose() {\n\t\targparser.DisplayServiceID(serviceID, flag, source, out)\n\t}\n\n\tif serviceID == \"\" {\n\t\treturn errors.New(\"service-id is required\")\n\t}\n\n\tfc, ok := c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tinput := &operations.CreateTagInput{\n\t\tServiceID: &serviceID,\n\t\tName:      &c.name,\n\t}\n\n\tif c.description.WasSet {\n\t\tinput.Description = &c.description.Value\n\t}\n\n\ttag, err := operations.CreateTag(context.TODO(), fc, input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, tag); ok {\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Created operation tag '%s' (id: %s)\", tag.Name, tag.ID)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/apisecurity/tags/delete.go",
    "content": "package tags\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/apisecurity/operations\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DeleteCommand calls the Fastly API to delete an operation tag.\ntype DeleteCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\ttagID       string\n\tserviceName argparser.OptionalServiceNameID\n}\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\n\tc.CmdClause = parent.Command(\"delete\", \"Delete an operation tag\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"tag-id\", \"Tag ID\").Required().StringVar(&c.tagID)\n\n\t// Optional.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DeleteCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tserviceID, source, flag, err := argparser.ServiceID(c.serviceName, *c.Globals.Manifest, c.Globals.APIClient, c.Globals.ErrLog)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif c.Globals.Verbose() {\n\t\targparser.DisplayServiceID(serviceID, flag, source, out)\n\t}\n\n\tif serviceID == \"\" {\n\t\treturn errors.New(\"service-id is required\")\n\t}\n\n\tfc, ok := c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\terr = operations.DeleteTag(context.TODO(), fc, &operations.DeleteTagInput{\n\t\tServiceID: &serviceID,\n\t\tTagID:     &c.tagID,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif c.JSONOutput.Enabled {\n\t\to := struct {\n\t\t\tServiceID string `json:\"service_id\"`\n\t\t\tTagID     string `json:\"tag_id\"`\n\t\t\tDeleted   bool   `json:\"deleted\"`\n\t\t}{\n\t\t\tserviceID,\n\t\t\tc.tagID,\n\t\t\ttrue,\n\t\t}\n\t\t_, err := c.WriteJSON(out, o)\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Deleted operation tag (id: %s)\", c.tagID)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/apisecurity/tags/doc.go",
    "content": "// Package tags contains commands to manipulate Fastly API Security operation tags.\npackage tags\n"
  },
  {
    "path": "pkg/commands/apisecurity/tags/get.go",
    "content": "package tags\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/apisecurity/operations\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// GetCommand calls the Fastly API to get an operation tag.\ntype GetCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\ttagID       string\n\tserviceName argparser.OptionalServiceNameID\n}\n\n// NewGetCommand returns a usable command registered under the parent.\nfunc NewGetCommand(parent argparser.Registerer, g *global.Data) *GetCommand {\n\tc := GetCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\n\tc.CmdClause = parent.Command(\"get\", \"Get an operation tag\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"tag-id\", \"Tag ID\").Required().StringVar(&c.tagID)\n\n\t// Optional.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *GetCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tserviceID, source, flag, err := argparser.ServiceID(c.serviceName, *c.Globals.Manifest, c.Globals.APIClient, c.Globals.ErrLog)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif c.Globals.Verbose() {\n\t\targparser.DisplayServiceID(serviceID, flag, source, out)\n\t}\n\n\tif serviceID == \"\" {\n\t\treturn errors.New(\"service-id is required\")\n\t}\n\n\tfc, ok := c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\ttag, err := operations.DescribeTag(context.TODO(), fc, &operations.DescribeTagInput{\n\t\tServiceID: &serviceID,\n\t\tTagID:     &c.tagID,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, tag); ok {\n\t\treturn err\n\t}\n\n\ttext.PrintOperationTag(out, tag)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/apisecurity/tags/list.go",
    "content": "package tags\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/apisecurity/operations\"\n)\n\n// ListCommand calls the Fastly API to list all operation tags.\ntype ListCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tserviceName argparser.OptionalServiceNameID\n}\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\n\tc.CmdClause = parent.Command(\"list\", \"List all operation tags\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *ListCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tserviceID, source, flag, err := argparser.ServiceID(c.serviceName, *c.Globals.Manifest, c.Globals.APIClient, c.Globals.ErrLog)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif c.Globals.Verbose() {\n\t\targparser.DisplayServiceID(serviceID, flag, source, out)\n\t}\n\n\tif serviceID == \"\" {\n\t\treturn errors.New(\"service-id is required\")\n\t}\n\n\tfc, ok := c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\t// Auto-paginate through all results\n\tvar allTags []operations.OperationTag\n\tpage := 0\n\tlimit := 100\n\n\tinput := &operations.ListTagsInput{\n\t\tServiceID: &serviceID,\n\t}\n\n\tfor {\n\t\tinput.Page = &page\n\t\tinput.Limit = &limit\n\n\t\ttags, err := operations.ListTags(context.TODO(), fc, input)\n\t\tif err != nil {\n\t\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\t\"Service ID\": serviceID,\n\t\t\t\t\"Page\":       page,\n\t\t\t})\n\t\t\treturn err\n\t\t}\n\n\t\tif tags == nil || len(tags.Data) == 0 {\n\t\t\tbreak\n\t\t}\n\n\t\tallTags = append(allTags, tags.Data...)\n\n\t\t// Check if we've fetched all results\n\t\tif len(allTags) >= tags.Meta.Total {\n\t\t\tbreak\n\t\t}\n\n\t\tpage++\n\t}\n\n\tif ok, err := c.WriteJSON(out, allTags); ok {\n\t\treturn err\n\t}\n\n\ttext.PrintOperationTagsTbl(out, allTags)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/apisecurity/tags/root.go",
    "content": "package tags\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"tags\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manipulate Fastly API Security operation tags\")\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/apisecurity/tags/tags_test.go",
    "content": "package tags_test\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\t\"testing\"\n\n\troot \"github.com/fastly/cli/pkg/commands/apisecurity\"\n\tsub \"github.com/fastly/cli/pkg/commands/apisecurity/tags\"\n\tfstfmt \"github.com/fastly/cli/pkg/fmt\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n\t\"github.com/fastly/go-fastly/v15/fastly/apisecurity/operations\"\n)\n\nconst (\n\tserviceID      = \"test-service-id\"\n\ttagID          = \"tag-123\"\n\ttagName        = \"APIv1\"\n\ttagDescription = \"All-APIv1-endpoints\"\n\tupdatedTagName = \"APIv1.1\"\n\tupdatedTagDesc = \"Updated-APIv1-endpoints\"\n)\n\nvar tag = operations.OperationTag{\n\tID:          tagID,\n\tName:        tagName,\n\tDescription: tagDescription,\n\tCount:       5,\n\tCreatedAt:   \"2021-06-15T23:00:00Z\",\n\tUpdatedAt:   \"2021-06-15T23:00:00Z\",\n}\n\nfunc TestTagsCreate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --service-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--name %s\", tagName),\n\t\t\tEnvVars:   map[string]string{\"FASTLY_SERVICE_ID\": \"\"},\n\t\t\tWantError: \"error reading service: no service ID found\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --name flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--service-id %s\", serviceID),\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate internal server error\",\n\t\t\tArgs: fmt.Sprintf(\"--service-id %s --name %s\", serviceID, tagName),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusInternalServerError,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusInternalServerError),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"500 - Internal Server Error\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--service-id %s --name %s --description %s\", serviceID, tagName, tagDescription),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(tag))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Created operation tag '%s' (id: %s)\", tagName, tagID),\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success without description\",\n\t\t\tArgs: fmt.Sprintf(\"--service-id %s --name %s\", serviceID, tagName),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(tag))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Created operation tag '%s' (id: %s)\", tagName, tagID),\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--service-id %s --name %s --description %s --json\", serviceID, tagName, tagDescription),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(tag))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(tag),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"create\"}, scenarios)\n}\n\nfunc TestTagsDelete(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --service-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--tag-id %s\", tagID),\n\t\t\tEnvVars:   map[string]string{\"FASTLY_SERVICE_ID\": \"\"},\n\t\t\tWantError: \"error reading service: no service ID found\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --tag-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--service-id %s\", serviceID),\n\t\t\tWantError: \"error parsing arguments: required flag --tag-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate bad request\",\n\t\t\tArgs: fmt.Sprintf(\"--service-id %s --tag-id %s\", serviceID, tagID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusBadRequest,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusBadRequest),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(`\n\t\t\t\t\t\t\t{\n    \t\t\t\t\t\t\t\"title\": \"invalid tag ID\",\n    \t\t\t\t\t\t\t\"status\": 400\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t`))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"400 - Bad Request\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--service-id %s --tag-id %s\", serviceID, tagID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusNoContent,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusNoContent),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Deleted operation tag (id: %s)\", tagID),\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--service-id %s --tag-id %s --json\", serviceID, tagID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusNoContent,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusNoContent),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.JSON(`{\"service_id\": %q, \"tag_id\": %q, \"deleted\": true}`, serviceID, tagID),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"delete\"}, scenarios)\n}\n\nfunc TestTagsGet(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --service-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--tag-id %s\", tagID),\n\t\t\tEnvVars:   map[string]string{\"FASTLY_SERVICE_ID\": \"\"},\n\t\t\tWantError: \"error reading service: no service ID found\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --tag-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--service-id %s\", serviceID),\n\t\t\tWantError: \"error parsing arguments: required flag --tag-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate bad request\",\n\t\t\tArgs: fmt.Sprintf(\"--service-id %s --tag-id invalid\", serviceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusBadRequest,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusBadRequest),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(`\n\t\t\t\t\t\t\t{\n    \t\t\t\t\t\t\t\"title\": \"invalid tag ID\",\n    \t\t\t\t\t\t\t\"status\": 400\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t`))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"400 - Bad Request\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--service-id %s --tag-id %s\", serviceID, tagID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(tag))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: tagString,\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--service-id %s --tag-id %s --json\", serviceID, tagID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(tag))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(tag),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"get\"}, scenarios)\n}\n\nfunc TestTagsList(t *testing.T) {\n\ttagsObject := operations.OperationTags{\n\t\tData: []operations.OperationTag{\n\t\t\t{\n\t\t\t\tID:          \"tag-001\",\n\t\t\t\tName:        \"API v1\",\n\t\t\t\tDescription: \"All v1 endpoints\",\n\t\t\t\tCount:       10,\n\t\t\t\tCreatedAt:   \"2021-06-15T23:00:00Z\",\n\t\t\t\tUpdatedAt:   \"2021-06-15T23:00:00Z\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tID:          \"tag-002\",\n\t\t\t\tName:        \"API v2\",\n\t\t\t\tDescription: \"All v2 endpoints\",\n\t\t\t\tCount:       25,\n\t\t\t\tCreatedAt:   \"2021-07-01T12:00:00Z\",\n\t\t\t\tUpdatedAt:   \"2021-07-01T12:00:00Z\",\n\t\t\t},\n\t\t},\n\t\tMeta: operations.Meta{\n\t\t\tLimit: 50,\n\t\t\tTotal: 2,\n\t\t},\n\t}\n\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --service-id flag\",\n\t\t\tArgs:      \"\",\n\t\t\tEnvVars:   map[string]string{\"FASTLY_SERVICE_ID\": \"\"},\n\t\t\tWantError: \"error reading service: no service ID found\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate internal server error\",\n\t\t\tArgs: fmt.Sprintf(\"--service-id %s\", serviceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusInternalServerError,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusInternalServerError),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"500 - Internal Server Error\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success (zero tags)\",\n\t\t\tArgs: fmt.Sprintf(\"--service-id %s\", serviceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(operations.OperationTags{\n\t\t\t\t\t\t\tData: []operations.OperationTag{},\n\t\t\t\t\t\t\tMeta: operations.Meta{\n\t\t\t\t\t\t\t\tLimit: 50,\n\t\t\t\t\t\t\t\tTotal: 0,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t}))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: zeroListTagsString,\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--service-id %s\", serviceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(tagsObject))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: listTagsString,\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success with pagination\",\n\t\t\tArgs: fmt.Sprintf(\"--service-id %s\", serviceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(tagsObject))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: listTagsString,\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--service-id %s --json\", serviceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(tagsObject))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(tagsObject.Data),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"list\"}, scenarios)\n}\n\nfunc TestTagsUpdate(t *testing.T) {\n\tupdatedTag := operations.OperationTag{\n\t\tID:          tagID,\n\t\tName:        updatedTagName,\n\t\tDescription: updatedTagDesc,\n\t\tCount:       5,\n\t\tCreatedAt:   \"2021-06-15T23:00:00Z\",\n\t\tUpdatedAt:   \"2021-06-16T10:00:00Z\",\n\t}\n\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --service-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--tag-id %s --name %s --description %s\", tagID, updatedTagName, updatedTagDesc),\n\t\t\tEnvVars:   map[string]string{\"FASTLY_SERVICE_ID\": \"\"},\n\t\t\tWantError: \"error reading service: no service ID found\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --tag-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--service-id %s --name %s --description %s\", serviceID, updatedTagName, updatedTagDesc),\n\t\t\tWantError: \"error parsing arguments: required flag --tag-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --name flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--service-id %s --tag-id %s --description %s\", serviceID, tagID, updatedTagDesc),\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --description flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--service-id %s --tag-id %s --name %s\", serviceID, tagID, updatedTagName),\n\t\t\tWantError: \"error parsing arguments: required flag --description not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate bad request\",\n\t\t\tArgs: fmt.Sprintf(\"--service-id %s --tag-id %s --name %s --description %s\", serviceID, tagID, updatedTagName, updatedTagDesc),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusBadRequest,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusBadRequest),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(`\n\t\t\t\t\t\t\t{\n    \t\t\t\t\t\t\t\"title\": \"invalid tag\",\n    \t\t\t\t\t\t\t\"status\": 400\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t`))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"400 - Bad Request\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--service-id %s --tag-id %s --name %s --description %s\", serviceID, tagID, updatedTagName, updatedTagDesc),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(updatedTag))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Updated operation tag '%s' (id: %s)\", updatedTagName, tagID),\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--service-id %s --tag-id %s --name %s --description %s --json\", serviceID, tagID, updatedTagName, updatedTagDesc),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(updatedTag))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(updatedTag),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"update\"}, scenarios)\n}\n\nvar tagString = strings.TrimSpace(`\nID: tag-123\nName: APIv1\nDescription: All-APIv1-endpoints\nOperation Count: 5\nCreated At: 2021-06-15T23:00:00Z\nUpdated At: 2021-06-15T23:00:00Z\n`) + \"\\n\"\n\nvar listTagsString = strings.TrimSpace(`\nID       Name    Description       Operations  Created At            Updated At\ntag-001  API v1  All v1 endpoints  10          2021-06-15T23:00:00Z  2021-06-15T23:00:00Z\ntag-002  API v2  All v2 endpoints  25          2021-07-01T12:00:00Z  2021-07-01T12:00:00Z\n`) + \"\\n\"\n\nvar zeroListTagsString = strings.TrimSpace(`\nID  Name  Description  Operations  Created At  Updated At\n`) + \"\\n\"\n"
  },
  {
    "path": "pkg/commands/apisecurity/tags/update.go",
    "content": "package tags\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/apisecurity/operations\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// UpdateCommand calls the Fastly API to update an operation tag.\ntype UpdateCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\ttagID       string\n\tname        string\n\tdescription string\n\tserviceName argparser.OptionalServiceNameID\n}\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\n\tc.CmdClause = parent.Command(\"update\", \"Update an operation tag\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"tag-id\", \"Tag ID\").Required().StringVar(&c.tagID)\n\tc.CmdClause.Flag(\"name\", \"Updated name of the operation tag\").Required().StringVar(&c.name)\n\tc.CmdClause.Flag(\"description\", \"Updated description of the operation tag\").Required().StringVar(&c.description)\n\n\t// Optional.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *UpdateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tserviceID, source, flag, err := argparser.ServiceID(c.serviceName, *c.Globals.Manifest, c.Globals.APIClient, c.Globals.ErrLog)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif c.Globals.Verbose() {\n\t\targparser.DisplayServiceID(serviceID, flag, source, out)\n\t}\n\n\tif serviceID == \"\" {\n\t\treturn errors.New(\"service-id is required\")\n\t}\n\n\tif c.name == \"\" {\n\t\treturn errors.New(\"--name is required\")\n\t}\n\n\tif c.description == \"\" {\n\t\treturn errors.New(\"--description is required\")\n\t}\n\n\tfc, ok := c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tinput := &operations.UpdateTagInput{\n\t\tDescription: &c.description,\n\t\tName:        &c.name,\n\t\tServiceID:   &serviceID,\n\t\tTagID:       &c.tagID,\n\t}\n\n\ttag, err := operations.UpdateTag(context.TODO(), fc, input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, tag); ok {\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Updated operation tag '%s' (id: %s)\", tag.Name, tag.ID)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/auth/add.go",
    "content": "package auth\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/config\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// AddCommand adds a named token entry.\ntype AddCommand struct {\n\targparser.Base\n\tname  string\n\ttoken string\n}\n\nfunc NewAddCommand(parent argparser.Registerer, g *global.Data) *AddCommand {\n\tvar c AddCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(\"add\", \"Store a named token\")\n\t// Optional.\n\tc.CmdClause.Arg(\"name\", \"Name for this token (pass to --token to use it later); if omitted, uses the API token's name\").StringVar(&c.name)\n\t// Required.\n\tc.CmdClause.Flag(\"api-token\", \"Fastly API token to store\").Required().StringVar(&c.token)\n\treturn &c\n}\n\nfunc (c *AddCommand) Exec(_ io.Reader, out io.Writer) error {\n\t// Short-circuit: if an explicit name was provided and already exists,\n\t// fail before making any network calls.\n\tif c.name != \"\" && c.Globals.Config.GetAuthToken(c.name) != nil {\n\t\treturn fmt.Errorf(\"token %q already exists; use 'fastly auth delete %s' first\", c.name, c.name)\n\t}\n\n\tmd, err := FetchTokenMetadataLenient(c.Globals, c.token)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tname := c.name\n\tif name == \"\" {\n\t\tif md.APITokenName == \"\" {\n\t\t\treturn fsterr.RemediationError{\n\t\t\t\tInner:       fmt.Errorf(\"could not determine a name for this token\"),\n\t\t\t\tRemediation: \"Provide a name as the first argument, e.g.: fastly auth add my-token --api-token <token>\",\n\t\t\t}\n\t\t}\n\t\tname = md.APITokenName\n\t\t// Check collision for the derived name too.\n\t\tif c.Globals.Config.GetAuthToken(name) != nil {\n\t\t\treturn fmt.Errorf(\"token %q already exists; use 'fastly auth delete %s' first\", name, name)\n\t\t}\n\t}\n\n\tentry := &config.AuthToken{\n\t\tType:              config.AuthTokenTypeStatic,\n\t\tToken:             c.token,\n\t\tEmail:             md.Email,\n\t\tAccountID:         md.AccountID,\n\t\tAPITokenName:      md.APITokenName,\n\t\tAPITokenScope:     md.APITokenScope,\n\t\tAPITokenExpiresAt: md.APITokenExpiresAt,\n\t\tAPITokenID:        md.APITokenID,\n\t}\n\n\tc.Globals.Config.SetAuthToken(name, entry)\n\n\t// When no default token is configured, automatically promote this token\n\t// so CLI commands work without an explicit --token flag.\n\tsetDefault := c.Globals.Config.Auth.Default == \"\"\n\tif setDefault {\n\t\tc.Globals.Config.Auth.Default = name\n\t}\n\n\tif err := c.Globals.Config.Write(c.Globals.ConfigPath); err != nil {\n\t\treturn fmt.Errorf(\"error saving config: %w\", err)\n\t}\n\n\ttext.Success(out, \"Token %q added\", name)\n\tif setDefault {\n\t\ttext.Info(out, \"Token %q set as default (no previous default was configured)\", name)\n\t}\n\ttext.Info(out, \"Token saved to %s\", c.Globals.ConfigPath)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/auth/delete.go",
    "content": "package auth\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DeleteCommand removes a stored token.\ntype DeleteCommand struct {\n\targparser.Base\n\tname string\n}\n\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tvar c DeleteCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(\"delete\", \"Delete a stored token\")\n\t// Required.\n\tc.CmdClause.Arg(\"name\", \"Name of the token to remove\").Required().StringVar(&c.name)\n\treturn &c\n}\n\nfunc (c *DeleteCommand) Exec(in io.Reader, out io.Writer) error {\n\tif c.Globals.Config.GetAuthToken(c.name) == nil {\n\t\treturn fmt.Errorf(\"token %q not found\", c.name)\n\t}\n\n\twasDefault := c.Globals.Config.Auth.Default == c.name\n\n\tif wasDefault && !c.Globals.Flags.AutoYes && !c.Globals.Flags.NonInteractive {\n\t\ttext.Warning(out, \"%q is your current default token. Deleting it will affect commands that don't use --token or FASTLY_API_TOKEN.\", c.name)\n\t\tcont, err := text.AskYesNo(out, \"Are you sure? [y/N]: \", in)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif !cont {\n\t\t\treturn nil\n\t\t}\n\t}\n\n\tc.Globals.Config.DeleteAuthToken(c.name)\n\n\tif err := c.Globals.Config.Write(c.Globals.ConfigPath); err != nil {\n\t\treturn fmt.Errorf(\"error saving config: %w\", err)\n\t}\n\n\ttext.Success(out, \"Token %q removed\", c.name)\n\tif wasDefault {\n\t\tif c.Globals.Config.Auth.Default != \"\" {\n\t\t\ttext.Info(out, \"Default token reassigned to %q\", c.Globals.Config.Auth.Default)\n\t\t} else {\n\t\t\ttext.Warning(out, \"No default token configured; use 'fastly auth use <name>' to set one\")\n\t\t}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/auth/expiry.go",
    "content": "package auth\n\nimport (\n\t\"fmt\"\n\t\"math\"\n\t\"time\"\n\n\t\"github.com/fastly/cli/pkg/config\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n)\n\n// ExpirationStatus represents the expiration state of a token.\ntype ExpirationStatus int\n\nconst (\n\t// StatusNoExpiry means no expiration information is available.\n\tStatusNoExpiry ExpirationStatus = iota\n\t// StatusOK means the token is valid and not close to expiring.\n\tStatusOK\n\t// StatusExpiringSoon means the token will expire within the warning threshold.\n\tStatusExpiringSoon\n\t// StatusExpired means the token has already expired.\n\tStatusExpired\n\t// StatusNeedsReauth means the token requires re-authentication (NeedsReauth flag set).\n\tStatusNeedsReauth\n)\n\nconst (\n\t// expiryWarningThreshold is the warning window for API tokens and SSO refresh tokens.\n\texpiryWarningThreshold = 30 * time.Minute\n\t// accessOnlyWarningThreshold is the warning window when only an access token\n\t// expiry is available (no refresh token). This is shorter because access\n\t// tokens are short-lived and auto-refresh.\n\taccessOnlyWarningThreshold = 1 * time.Hour\n)\n\n// GetExpirationStatus computes the expiration status for a token.\n// It returns the status, the effective expiry time (zero if no expiry), and a\n// parse error if timestamp fields are present but malformed.\nfunc GetExpirationStatus(at *config.AuthToken, now time.Time) (ExpirationStatus, time.Time, error) {\n\tif at == nil {\n\t\treturn StatusNoExpiry, time.Time{}, nil\n\t}\n\n\tif at.NeedsReauth {\n\t\treturn StatusNeedsReauth, time.Time{}, nil\n\t}\n\n\tswitch at.Type {\n\tcase config.AuthTokenTypeStatic:\n\t\treturn staticExpirationStatus(at, now)\n\tcase config.AuthTokenTypeSSO:\n\t\treturn ssoExpirationStatus(at, now)\n\tdefault:\n\t\t// Unknown type; try static-style check on APITokenExpiresAt.\n\t\treturn staticExpirationStatus(at, now)\n\t}\n}\n\nfunc staticExpirationStatus(at *config.AuthToken, now time.Time) (ExpirationStatus, time.Time, error) {\n\tif at.APITokenExpiresAt == \"\" {\n\t\treturn StatusNoExpiry, time.Time{}, nil\n\t}\n\n\texpires, err := time.Parse(time.RFC3339, at.APITokenExpiresAt)\n\tif err != nil {\n\t\treturn StatusNoExpiry, time.Time{}, fmt.Errorf(\"invalid api_token_expires_at %q: %w\", at.APITokenExpiresAt, err)\n\t}\n\n\treturn classifyExpiry(expires, now, expiryWarningThreshold), expires, nil\n}\n\n// ssoExpirationStatus handles expiration for SSO tokens.\nfunc ssoExpirationStatus(at *config.AuthToken, now time.Time) (ExpirationStatus, time.Time, error) {\n\tif at.APITokenExpiresAt != \"\" {\n\t\texpires, err := time.Parse(time.RFC3339, at.APITokenExpiresAt)\n\t\tif err == nil {\n\t\t\treturn classifyExpiry(expires, now, expiryWarningThreshold), expires, nil\n\t\t}\n\t}\n\n\tif at.RefreshToken != \"\" && at.RefreshExpiresAt == \"\" {\n\t\treturn StatusNoExpiry, time.Time{}, nil\n\t}\n\n\tif at.RefreshExpiresAt != \"\" {\n\t\texpires, err := time.Parse(time.RFC3339, at.RefreshExpiresAt)\n\t\tif err == nil {\n\t\t\treturn classifyExpiry(expires, now, expiryWarningThreshold), expires, nil\n\t\t}\n\t}\n\n\tif at.AccessExpiresAt != \"\" {\n\t\texpires, err := time.Parse(time.RFC3339, at.AccessExpiresAt)\n\t\tif err == nil {\n\t\t\treturn classifyExpiry(expires, now, accessOnlyWarningThreshold), expires, nil\n\t\t}\n\t\treturn StatusNoExpiry, time.Time{}, fmt.Errorf(\"invalid access_expires_at %q: %w\", at.AccessExpiresAt, err)\n\t}\n\n\treturn StatusNoExpiry, time.Time{}, nil\n}\n\nfunc classifyExpiry(expires, now time.Time, threshold time.Duration) ExpirationStatus {\n\tif now.After(expires) {\n\t\treturn StatusExpired\n\t}\n\tremaining := expires.Sub(now)\n\tif remaining <= threshold {\n\t\treturn StatusExpiringSoon\n\t}\n\treturn StatusOK\n}\n\n// ExpirationSummary returns a human-readable string describing the time until\n// or since expiry. Returns \"\" for StatusNoExpiry and StatusNeedsReauth.\nfunc ExpirationSummary(status ExpirationStatus, expires time.Time, now time.Time) string {\n\tswitch status {\n\tcase StatusOK, StatusExpiringSoon:\n\t\treturn \"expires in \" + humanDuration(expires.Sub(now))\n\tcase StatusExpired:\n\t\treturn \"expired \" + humanDuration(now.Sub(expires)) + \" ago\"\n\tcase StatusNoExpiry, StatusNeedsReauth:\n\t\treturn \"\"\n\t}\n\treturn \"\"\n}\n\n// ExpirationRemediation returns actionable remediation text for the given token type.\nfunc ExpirationRemediation(tokenType string) string {\n\treturn fsterr.TokenExpirationRemediationForType(tokenType)\n}\n\n// humanDuration formats a duration into a short human-readable string like\n// \"3 days\", \"2 hours\", \"45 minutes\". Always returns a positive representation.\nfunc humanDuration(d time.Duration) string {\n\tif d < 0 {\n\t\td = -d\n\t}\n\n\tswitch {\n\tcase d < time.Minute:\n\t\ts := int(d.Seconds())\n\t\tif s <= 1 {\n\t\t\treturn \"1 second\"\n\t\t}\n\t\treturn fmt.Sprintf(\"%d seconds\", s)\n\tcase d < time.Hour:\n\t\tm := int(d.Minutes())\n\t\tif m == 1 {\n\t\t\treturn \"1 minute\"\n\t\t}\n\t\treturn fmt.Sprintf(\"%d minutes\", m)\n\tcase d < 24*time.Hour:\n\t\th := int(math.Round(d.Hours()))\n\t\tif h == 1 {\n\t\t\treturn \"1 hour\"\n\t\t}\n\t\treturn fmt.Sprintf(\"%d hours\", h)\n\tdefault:\n\t\tdays := int(math.Round(d.Hours() / 24))\n\t\tif days == 1 {\n\t\t\treturn \"1 day\"\n\t\t}\n\t\treturn fmt.Sprintf(\"%d days\", days)\n\t}\n}\n"
  },
  {
    "path": "pkg/commands/auth/expiry_test.go",
    "content": "package auth_test\n\nimport (\n\t\"os\"\n\t\"testing\"\n\t\"time\"\n\n\tauthcmd \"github.com/fastly/cli/pkg/commands/auth\"\n\t\"github.com/fastly/cli/pkg/config\"\n)\n\nfunc TestGetExpirationStatus(t *testing.T) {\n\tnow := time.Date(2025, 6, 15, 12, 0, 0, 0, time.UTC)\n\n\ttests := []struct {\n\t\tname       string\n\t\ttoken      *config.AuthToken\n\t\twantStatus authcmd.ExpirationStatus\n\t\twantErr    bool\n\t}{\n\t\t// Nil token.\n\t\t{\n\t\t\tname:       \"nil token\",\n\t\t\ttoken:      nil,\n\t\t\twantStatus: authcmd.StatusNoExpiry,\n\t\t},\n\n\t\t// NeedsReauth precedence.\n\t\t{\n\t\t\tname: \"needs reauth takes precedence over valid expiry\",\n\t\t\ttoken: &config.AuthToken{\n\t\t\t\tType:             config.AuthTokenTypeSSO,\n\t\t\t\tNeedsReauth:      true,\n\t\t\t\tRefreshExpiresAt: now.Add(30 * 24 * time.Hour).Format(time.RFC3339),\n\t\t\t},\n\t\t\twantStatus: authcmd.StatusNeedsReauth,\n\t\t},\n\t\t{\n\t\t\tname: \"needs reauth takes precedence over expired\",\n\t\t\ttoken: &config.AuthToken{\n\t\t\t\tType:             config.AuthTokenTypeSSO,\n\t\t\t\tNeedsReauth:      true,\n\t\t\t\tRefreshExpiresAt: now.Add(-1 * time.Hour).Format(time.RFC3339),\n\t\t\t},\n\t\t\twantStatus: authcmd.StatusNeedsReauth,\n\t\t},\n\n\t\t// Static tokens.\n\t\t{\n\t\t\tname: \"static no expiry\",\n\t\t\ttoken: &config.AuthToken{\n\t\t\t\tType: config.AuthTokenTypeStatic,\n\t\t\t},\n\t\t\twantStatus: authcmd.StatusNoExpiry,\n\t\t},\n\t\t{\n\t\t\tname: \"static future expiry OK\",\n\t\t\ttoken: &config.AuthToken{\n\t\t\t\tType:              config.AuthTokenTypeStatic,\n\t\t\t\tAPITokenExpiresAt: now.Add(30 * 24 * time.Hour).Format(time.RFC3339),\n\t\t\t},\n\t\t\twantStatus: authcmd.StatusOK,\n\t\t},\n\t\t{\n\t\t\tname: \"static not expiring soon (3 days out)\",\n\t\t\ttoken: &config.AuthToken{\n\t\t\t\tType:              config.AuthTokenTypeStatic,\n\t\t\t\tAPITokenExpiresAt: now.Add(3 * 24 * time.Hour).Format(time.RFC3339),\n\t\t\t},\n\t\t\twantStatus: authcmd.StatusOK,\n\t\t},\n\t\t{\n\t\t\tname: \"static expiring soon (within 30 minutes)\",\n\t\t\ttoken: &config.AuthToken{\n\t\t\t\tType:              config.AuthTokenTypeStatic,\n\t\t\t\tAPITokenExpiresAt: now.Add(20 * time.Minute).Format(time.RFC3339),\n\t\t\t},\n\t\t\twantStatus: authcmd.StatusExpiringSoon,\n\t\t},\n\t\t{\n\t\t\tname: \"static expiring soon (exactly 30 minutes)\",\n\t\t\ttoken: &config.AuthToken{\n\t\t\t\tType:              config.AuthTokenTypeStatic,\n\t\t\t\tAPITokenExpiresAt: now.Add(30 * time.Minute).Format(time.RFC3339),\n\t\t\t},\n\t\t\twantStatus: authcmd.StatusExpiringSoon,\n\t\t},\n\t\t{\n\t\t\tname: \"static expired\",\n\t\t\ttoken: &config.AuthToken{\n\t\t\t\tType:              config.AuthTokenTypeStatic,\n\t\t\t\tAPITokenExpiresAt: now.Add(-2 * time.Hour).Format(time.RFC3339),\n\t\t\t},\n\t\t\twantStatus: authcmd.StatusExpired,\n\t\t},\n\t\t{\n\t\t\tname: \"static malformed expiry\",\n\t\t\ttoken: &config.AuthToken{\n\t\t\t\tType:              config.AuthTokenTypeStatic,\n\t\t\t\tAPITokenExpiresAt: \"not-a-date\",\n\t\t\t},\n\t\t\twantStatus: authcmd.StatusNoExpiry,\n\t\t\twantErr:    true,\n\t\t},\n\n\t\t// SSO tokens: RefreshExpiresAt primary.\n\t\t{\n\t\t\tname: \"sso api_token_expires_at preferred over refresh\",\n\t\t\ttoken: &config.AuthToken{\n\t\t\t\tType:              config.AuthTokenTypeSSO,\n\t\t\t\tAPITokenExpiresAt: now.Add(30 * 24 * time.Hour).Format(time.RFC3339),\n\t\t\t\tRefreshExpiresAt:  now.Add(25 * time.Minute).Format(time.RFC3339),\n\t\t\t},\n\t\t\twantStatus: authcmd.StatusOK,\n\t\t},\n\t\t{\n\t\t\tname: \"sso api_token_expires_at not expiring soon (3 days out)\",\n\t\t\ttoken: &config.AuthToken{\n\t\t\t\tType:              config.AuthTokenTypeSSO,\n\t\t\t\tAPITokenExpiresAt: now.Add(3 * 24 * time.Hour).Format(time.RFC3339),\n\t\t\t\tRefreshExpiresAt:  now.Add(25 * time.Minute).Format(time.RFC3339),\n\t\t\t},\n\t\t\twantStatus: authcmd.StatusOK,\n\t\t},\n\t\t{\n\t\t\tname: \"sso api_token_expires_at expiring soon (within 30 minutes)\",\n\t\t\ttoken: &config.AuthToken{\n\t\t\t\tType:              config.AuthTokenTypeSSO,\n\t\t\t\tAPITokenExpiresAt: now.Add(15 * time.Minute).Format(time.RFC3339),\n\t\t\t\tRefreshExpiresAt:  now.Add(25 * time.Minute).Format(time.RFC3339),\n\t\t\t},\n\t\t\twantStatus: authcmd.StatusExpiringSoon,\n\t\t},\n\t\t{\n\t\t\tname: \"sso api_token_expires_at expired\",\n\t\t\ttoken: &config.AuthToken{\n\t\t\t\tType:              config.AuthTokenTypeSSO,\n\t\t\t\tAPITokenExpiresAt: now.Add(-1 * time.Hour).Format(time.RFC3339),\n\t\t\t\tRefreshExpiresAt:  now.Add(-2 * time.Hour).Format(time.RFC3339),\n\t\t\t},\n\t\t\twantStatus: authcmd.StatusExpired,\n\t\t},\n\t\t{\n\t\t\tname: \"sso refresh OK\",\n\t\t\ttoken: &config.AuthToken{\n\t\t\t\tType:             config.AuthTokenTypeSSO,\n\t\t\t\tRefreshExpiresAt: now.Add(30 * 24 * time.Hour).Format(time.RFC3339),\n\t\t\t},\n\t\t\twantStatus: authcmd.StatusOK,\n\t\t},\n\t\t{\n\t\t\tname: \"sso refresh not expiring soon (3 days out)\",\n\t\t\ttoken: &config.AuthToken{\n\t\t\t\tType:             config.AuthTokenTypeSSO,\n\t\t\t\tRefreshExpiresAt: now.Add(3 * 24 * time.Hour).Format(time.RFC3339),\n\t\t\t},\n\t\t\twantStatus: authcmd.StatusOK,\n\t\t},\n\t\t{\n\t\t\tname: \"sso refresh expiring soon (within 30 minutes)\",\n\t\t\ttoken: &config.AuthToken{\n\t\t\t\tType:             config.AuthTokenTypeSSO,\n\t\t\t\tRefreshExpiresAt: now.Add(20 * time.Minute).Format(time.RFC3339),\n\t\t\t},\n\t\t\twantStatus: authcmd.StatusExpiringSoon,\n\t\t},\n\t\t{\n\t\t\tname: \"sso refresh expired\",\n\t\t\ttoken: &config.AuthToken{\n\t\t\t\tType:             config.AuthTokenTypeSSO,\n\t\t\t\tRefreshExpiresAt: now.Add(-1 * time.Hour).Format(time.RFC3339),\n\t\t\t},\n\t\t\twantStatus: authcmd.StatusExpired,\n\t\t},\n\n\t\t// SSO tokens: AccessExpiresAt fallback.\n\t\t{\n\t\t\tname: \"sso no refresh, access OK (beyond 1h threshold)\",\n\t\t\ttoken: &config.AuthToken{\n\t\t\t\tType:            config.AuthTokenTypeSSO,\n\t\t\t\tAccessExpiresAt: now.Add(2 * time.Hour).Format(time.RFC3339),\n\t\t\t},\n\t\t\twantStatus: authcmd.StatusOK,\n\t\t},\n\t\t{\n\t\t\tname: \"sso no refresh, access expiring soon (within 1h)\",\n\t\t\ttoken: &config.AuthToken{\n\t\t\t\tType:            config.AuthTokenTypeSSO,\n\t\t\t\tAccessExpiresAt: now.Add(30 * time.Minute).Format(time.RFC3339),\n\t\t\t},\n\t\t\twantStatus: authcmd.StatusExpiringSoon,\n\t\t},\n\t\t{\n\t\t\tname: \"sso no refresh, access expired\",\n\t\t\ttoken: &config.AuthToken{\n\t\t\t\tType:            config.AuthTokenTypeSSO,\n\t\t\t\tAccessExpiresAt: now.Add(-10 * time.Minute).Format(time.RFC3339),\n\t\t\t},\n\t\t\twantStatus: authcmd.StatusExpired,\n\t\t},\n\n\t\t// SSO tokens: malformed timestamps.\n\t\t{\n\t\t\tname: \"sso malformed refresh, valid access fallback\",\n\t\t\ttoken: &config.AuthToken{\n\t\t\t\tType:             config.AuthTokenTypeSSO,\n\t\t\t\tRefreshExpiresAt: \"garbage\",\n\t\t\t\tAccessExpiresAt:  now.Add(2 * time.Hour).Format(time.RFC3339),\n\t\t\t},\n\t\t\twantStatus: authcmd.StatusOK,\n\t\t},\n\t\t{\n\t\t\tname: \"sso malformed refresh, no access\",\n\t\t\ttoken: &config.AuthToken{\n\t\t\t\tType:             config.AuthTokenTypeSSO,\n\t\t\t\tRefreshExpiresAt: \"garbage\",\n\t\t\t},\n\t\t\twantStatus: authcmd.StatusNoExpiry,\n\t\t\twantErr:    false,\n\t\t},\n\t\t{\n\t\t\tname: \"sso no refresh, malformed access\",\n\t\t\ttoken: &config.AuthToken{\n\t\t\t\tType:            config.AuthTokenTypeSSO,\n\t\t\t\tAccessExpiresAt: \"garbage\",\n\t\t\t},\n\t\t\twantStatus: authcmd.StatusNoExpiry,\n\t\t\twantErr:    true,\n\t\t},\n\t\t{\n\t\t\tname: \"sso both malformed\",\n\t\t\ttoken: &config.AuthToken{\n\t\t\t\tType:             config.AuthTokenTypeSSO,\n\t\t\t\tRefreshExpiresAt: \"bad1\",\n\t\t\t\tAccessExpiresAt:  \"bad2\",\n\t\t\t},\n\t\t\twantStatus: authcmd.StatusNoExpiry,\n\t\t\twantErr:    true,\n\t\t},\n\t\t{\n\t\t\tname: \"sso no expiry fields at all\",\n\t\t\ttoken: &config.AuthToken{\n\t\t\t\tType: config.AuthTokenTypeSSO,\n\t\t\t},\n\t\t\twantStatus: authcmd.StatusNoExpiry,\n\t\t},\n\n\t\t// Unknown type falls through to static-style check.\n\t\t{\n\t\t\tname: \"unknown type with expiry (not soon)\",\n\t\t\ttoken: &config.AuthToken{\n\t\t\t\tType:              \"unknown\",\n\t\t\t\tAPITokenExpiresAt: now.Add(3 * 24 * time.Hour).Format(time.RFC3339),\n\t\t\t},\n\t\t\twantStatus: authcmd.StatusOK,\n\t\t},\n\t\t{\n\t\t\tname: \"unknown type with expiry (within 30 minutes)\",\n\t\t\ttoken: &config.AuthToken{\n\t\t\t\tType:              \"unknown\",\n\t\t\t\tAPITokenExpiresAt: now.Add(10 * time.Minute).Format(time.RFC3339),\n\t\t\t},\n\t\t\twantStatus: authcmd.StatusExpiringSoon,\n\t\t},\n\n\t\t// Consistency with checkAndRefreshAuthSSOToken: when both access and\n\t\t// refresh are expired, the refresh function returns reauth=true.\n\t\t// ExpirationStatus must not return StatusOK.\n\t\t{\n\t\t\tname: \"consistency: both expired yields StatusExpired not StatusOK\",\n\t\t\ttoken: &config.AuthToken{\n\t\t\t\tType:             config.AuthTokenTypeSSO,\n\t\t\t\tAccessExpiresAt:  now.Add(-2 * time.Hour).Format(time.RFC3339),\n\t\t\t\tRefreshExpiresAt: now.Add(-1 * time.Hour).Format(time.RFC3339),\n\t\t\t},\n\t\t\twantStatus: authcmd.StatusExpired,\n\t\t},\n\t\t{\n\t\t\tname: \"consistency: access expired, no refresh yields StatusExpired\",\n\t\t\ttoken: &config.AuthToken{\n\t\t\t\tType:            config.AuthTokenTypeSSO,\n\t\t\t\tAccessExpiresAt: now.Add(-2 * time.Hour).Format(time.RFC3339),\n\t\t\t},\n\t\t\twantStatus: authcmd.StatusExpired,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tstatus, _, err := authcmd.GetExpirationStatus(tt.token, now)\n\t\t\tif status != tt.wantStatus {\n\t\t\t\tt.Errorf(\"GetExpirationStatus() status = %v, want %v\", status, tt.wantStatus)\n\t\t\t}\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"GetExpirationStatus() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestExpirationSummary(t *testing.T) {\n\tnow := time.Date(2025, 6, 15, 12, 0, 0, 0, time.UTC)\n\n\ttests := []struct {\n\t\tname    string\n\t\tstatus  authcmd.ExpirationStatus\n\t\texpires time.Time\n\t\twant    string\n\t}{\n\t\t{\n\t\t\tname:    \"expires in 3 days\",\n\t\t\tstatus:  authcmd.StatusExpiringSoon,\n\t\t\texpires: now.Add(3 * 24 * time.Hour),\n\t\t\twant:    \"expires in 3 days\",\n\t\t},\n\t\t{\n\t\t\tname:    \"expires in 2 hours\",\n\t\t\tstatus:  authcmd.StatusExpiringSoon,\n\t\t\texpires: now.Add(2 * time.Hour),\n\t\t\twant:    \"expires in 2 hours\",\n\t\t},\n\t\t{\n\t\t\tname:    \"expired 2 hours ago\",\n\t\t\tstatus:  authcmd.StatusExpired,\n\t\t\texpires: now.Add(-2 * time.Hour),\n\t\t\twant:    \"expired 2 hours ago\",\n\t\t},\n\t\t{\n\t\t\tname:    \"expired 1 day ago\",\n\t\t\tstatus:  authcmd.StatusExpired,\n\t\t\texpires: now.Add(-24 * time.Hour),\n\t\t\twant:    \"expired 1 day ago\",\n\t\t},\n\t\t{\n\t\t\tname:    \"OK returns summary too\",\n\t\t\tstatus:  authcmd.StatusOK,\n\t\t\texpires: now.Add(30 * 24 * time.Hour),\n\t\t\twant:    \"expires in 30 days\",\n\t\t},\n\t\t{\n\t\t\tname:   \"no expiry returns empty\",\n\t\t\tstatus: authcmd.StatusNoExpiry,\n\t\t\twant:   \"\",\n\t\t},\n\t\t{\n\t\t\tname:   \"needs reauth returns empty\",\n\t\t\tstatus: authcmd.StatusNeedsReauth,\n\t\t\twant:   \"\",\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot := authcmd.ExpirationSummary(tt.status, tt.expires, now)\n\t\t\tif got != tt.want {\n\t\t\t\tt.Errorf(\"ExpirationSummary() = %q, want %q\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestExpirationRemediation(t *testing.T) {\n\t// Ensure we test in a clean env state.\n\toriginalEnv := os.Getenv(\"FASTLY_DISABLE_AUTH_COMMAND\")\n\tdefer os.Setenv(\"FASTLY_DISABLE_AUTH_COMMAND\", originalEnv)\n\n\ttests := []struct {\n\t\tname       string\n\t\ttokenType  string\n\t\tdisableEnv string\n\t\twantSubstr string\n\t}{\n\t\t{\n\t\t\tname:       \"sso with auth enabled\",\n\t\t\ttokenType:  \"sso\",\n\t\t\twantSubstr: \"fastly auth login --sso\",\n\t\t},\n\t\t{\n\t\t\tname:       \"static with auth enabled\",\n\t\t\ttokenType:  \"static\",\n\t\t\twantSubstr: \"fastly auth add\",\n\t\t},\n\t\t{\n\t\t\tname:       \"unknown type with auth enabled defaults to sso\",\n\t\t\ttokenType:  \"\",\n\t\t\twantSubstr: \"fastly auth login --sso\",\n\t\t},\n\t\t{\n\t\t\tname:       \"sso with auth disabled\",\n\t\t\ttokenType:  \"sso\",\n\t\t\tdisableEnv: \"1\",\n\t\t\twantSubstr: \"FASTLY_API_TOKEN\",\n\t\t},\n\t\t{\n\t\t\tname:       \"static with auth disabled\",\n\t\t\ttokenType:  \"static\",\n\t\t\tdisableEnv: \"1\",\n\t\t\twantSubstr: \"FASTLY_API_TOKEN\",\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tos.Setenv(\"FASTLY_DISABLE_AUTH_COMMAND\", tt.disableEnv)\n\t\t\tgot := authcmd.ExpirationRemediation(tt.tokenType)\n\t\t\tif got == \"\" {\n\t\t\t\tt.Fatal(\"ExpirationRemediation() returned empty string\")\n\t\t\t}\n\t\t\tfound := false\n\t\t\tif len(tt.wantSubstr) > 0 {\n\t\t\t\tfor i := 0; i <= len(got)-len(tt.wantSubstr); i++ {\n\t\t\t\t\tif got[i:i+len(tt.wantSubstr)] == tt.wantSubstr {\n\t\t\t\t\t\tfound = true\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tif !found {\n\t\t\t\tt.Errorf(\"ExpirationRemediation() = %q, want substring %q\", got, tt.wantSubstr)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/commands/auth/list.go",
    "content": "package auth\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"time\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// ListCommand lists stored tokens.\ntype ListCommand struct {\n\targparser.Base\n}\n\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tvar c ListCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(\"list\", \"List stored tokens and show the default\")\n\treturn &c\n}\n\nfunc (c *ListCommand) Exec(_ io.Reader, out io.Writer) error {\n\ttokens := c.Globals.Config.Auth.Tokens\n\tif len(tokens) == 0 {\n\t\ttext.Output(out, \"No tokens stored. Run `fastly auth login` to add one.\\n\")\n\t\treturn nil\n\t}\n\n\tnow := time.Now()\n\n\tfor name, entry := range tokens {\n\t\tmarker := \"  \"\n\t\tif name == c.Globals.Config.Auth.Default {\n\t\t\tmarker = \"* \"\n\t\t}\n\n\t\tinfo := entry.Type\n\t\tif entry.Email != \"\" {\n\t\t\tinfo = entry.Email\n\t\t}\n\n\t\treauthStr := \"\"\n\t\tif entry.NeedsReauth {\n\t\t\treauthStr = \" (needs re-authentication)\"\n\t\t}\n\n\t\texpiryStr := \"\"\n\t\tif !entry.NeedsReauth {\n\t\t\tstatus, expires, err := GetExpirationStatus(entry, now)\n\t\t\tif err != nil && c.Globals.ErrLog != nil {\n\t\t\t\tc.Globals.ErrLog.Add(err)\n\t\t\t}\n\n\t\t\tlabel := \"\"\n\t\t\tif entry.RefreshExpiresAt != \"\" {\n\t\t\t\tlabel = \"session \"\n\t\t\t}\n\n\t\t\tswitch status {\n\t\t\tcase StatusExpiringSoon:\n\t\t\t\tsummary := ExpirationSummary(status, expires, now)\n\t\t\t\texpiryStr = \" \" + text.BoldYellow(fmt.Sprintf(\"[%s%s]\", label, summary))\n\t\t\tcase StatusExpired:\n\t\t\t\tsummary := ExpirationSummary(status, expires, now)\n\t\t\t\texpiryStr = \" \" + text.BoldRed(fmt.Sprintf(\"[%s%s]\", label, summary))\n\t\t\tcase StatusOK, StatusNoExpiry, StatusNeedsReauth:\n\t\t\t}\n\t\t}\n\n\t\ttext.Output(out, \"%s%s (%s)%s%s\\n\", marker, name, info, reauthStr, expiryStr)\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/auth/login.go",
    "content": "package auth\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// LoginCommand stores a token as the default credential.\ntype LoginCommand struct {\n\targparser.Base\n\tsso bool\n}\n\nfunc NewLoginCommand(parent argparser.Registerer, g *global.Data) *LoginCommand {\n\tvar c LoginCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(\"login\", \"Authenticate and store a default token (paste token or use --sso)\")\n\t// Optional.\n\tc.CmdClause.Flag(\"sso\", \"Authenticate via browser-based SSO (requires --token <name> to specify the stored token name)\").BoolVar(&c.sso)\n\treturn &c\n}\n\nfunc (c *LoginCommand) Exec(in io.Reader, out io.Writer) error {\n\tif c.sso {\n\t\treturn c.execSSO(in, out)\n\t}\n\n\ttext.Output(out, \"An API token can be generated at: https://manage.fastly.com/account/personal/tokens\\n\\n\")\n\n\ttoken, err := text.InputSecure(out, \"Paste your API token: \", in)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error reading token input: %w\", err)\n\t}\n\tif token == \"\" {\n\t\treturn fmt.Errorf(\"no token provided\")\n\t}\n\n\tname, md, err := StoreStaticToken(c.Globals, token)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Authenticated as %s (token stored as %q)\", md.Email, name)\n\ttext.Info(out, \"Token saved to %s\", c.Globals.ConfigPath)\n\treturn nil\n}\n\nfunc (c *LoginCommand) execSSO(in io.Reader, out io.Writer) error {\n\tif c.Globals.Flags.Token == \"\" {\n\t\treturn fsterr.RemediationError{\n\t\t\tInner:       fmt.Errorf(\"SSO login requires a token name via --token\"),\n\t\t\tRemediation: \"Provide a name for the stored token, e.g.: fastly auth login --sso --token work-sso\",\n\t\t}\n\t}\n\ttokenName := c.Globals.Flags.Token\n\n\tif c.Globals.AuthServer == nil {\n\t\treturn fmt.Errorf(\"SSO authentication requires network access to the Fastly OIDC provider, but the auth server could not be configured; use 'fastly auth login' (without --sso) to paste a static API token instead\")\n\t}\n\n\tif err := RunSSOWithTokenName(in, out, c.Globals, false, false, tokenName); err != nil {\n\t\treturn fmt.Errorf(\"SSO authentication failed: %w\", err)\n\t}\n\n\tc.Globals.Config.Auth.Default = tokenName\n\tif err := c.Globals.Config.Write(c.Globals.ConfigPath); err != nil {\n\t\treturn fmt.Errorf(\"error saving config: %w\", err)\n\t}\n\n\ttext.Break(out)\n\ttext.Success(out, \"Authenticated via SSO.\")\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/auth/metadata.go",
    "content": "package auth\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"time\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/api\"\n\t\"github.com/fastly/cli/pkg/config\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// TokenMetadata holds validated token information from the Fastly API.\ntype TokenMetadata struct {\n\tEmail     string\n\tAccountID string\n\n\tAPITokenName      string\n\tAPITokenScope     string\n\tAPITokenExpiresAt string\n\tAPITokenID        string\n}\n\n// FetchTokenMetadata validates a token by calling GetCurrentUser (required)\n// and GetTokenSelf (best-effort), returning metadata for storage.\n// It constructs its own API client from the provided token string.\nfunc FetchTokenMetadata(g *global.Data, token string) (*TokenMetadata, error) {\n\tendpoint, _ := g.APIEndpoint()\n\tapiClient, err := g.APIClientFactory(token, endpoint, g.Flags.Debug)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error creating API client: %w\", err)\n\t}\n\n\tuser, err := apiClient.GetCurrentUser(context.TODO())\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"token validation failed (could not look up current user): %w\", err)\n\t}\n\n\tmd := &TokenMetadata{\n\t\tEmail:     fastly.ToValue(user.Login),\n\t\tAccountID: fastly.ToValue(user.CustomerID),\n\t}\n\n\tfetchTokenSelf(g, apiClient, md)\n\n\treturn md, nil\n}\n\n// FetchTokenMetadataLenient is like FetchTokenMetadata but treats\n// GetCurrentUser as best-effort. Use this for scoped tokens that may lack\n// permission to call /current_user. At least one of GetCurrentUser or\n// GetTokenSelf must succeed to confirm the token is valid.\nfunc FetchTokenMetadataLenient(g *global.Data, token string) (*TokenMetadata, error) {\n\tendpoint, _ := g.APIEndpoint()\n\tapiClient, err := g.APIClientFactory(token, endpoint, g.Flags.Debug)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error creating API client: %w\", err)\n\t}\n\n\tmd := &TokenMetadata{}\n\tanyOK := false\n\n\tuser, err := apiClient.GetCurrentUser(context.TODO())\n\tif err != nil {\n\t\tg.ErrLog.Add(fmt.Errorf(\"GetCurrentUser failed (best-effort): %w\", err))\n\t} else {\n\t\tmd.Email = fastly.ToValue(user.Login)\n\t\tmd.AccountID = fastly.ToValue(user.CustomerID)\n\t\tanyOK = true\n\t}\n\n\tif fetchTokenSelf(g, apiClient, md) {\n\t\tanyOK = true\n\t}\n\n\tif !anyOK {\n\t\treturn nil, fmt.Errorf(\"token validation failed: neither /current_user nor /tokens/self responded successfully\")\n\t}\n\n\treturn md, nil\n}\n\n// EnrichWithTokenSelf calls GetTokenSelf to populate API token metadata on\n// an existing AuthToken. It constructs its own API client from the token.\n// This is best-effort: failures are logged and existing fields are preserved.\nfunc EnrichWithTokenSelf(g *global.Data, at *config.AuthToken) {\n\tendpoint, _ := g.APIEndpoint()\n\tapiClient, err := g.APIClientFactory(at.Token, endpoint, g.Flags.Debug)\n\tif err != nil {\n\t\tg.ErrLog.Add(fmt.Errorf(\"EnrichWithTokenSelf: error creating API client: %w\", err))\n\t\treturn\n\t}\n\n\tvar md TokenMetadata\n\tif fetchTokenSelf(g, apiClient, &md) {\n\t\tat.APITokenName = md.APITokenName\n\t\tat.APITokenScope = md.APITokenScope\n\t\tat.APITokenExpiresAt = md.APITokenExpiresAt\n\t\tat.APITokenID = md.APITokenID\n\t}\n}\n\n// BuildAndStoreStaticToken constructs an AuthToken from pre-fetched metadata\n// and stores it under the given name. Does NOT write config to disk.\nfunc BuildAndStoreStaticToken(g *global.Data, token, name string, md *TokenMetadata, makeDefault bool) {\n\tentry := &config.AuthToken{\n\t\tType:              config.AuthTokenTypeStatic,\n\t\tToken:             token,\n\t\tEmail:             md.Email,\n\t\tAccountID:         md.AccountID,\n\t\tAPITokenName:      md.APITokenName,\n\t\tAPITokenScope:     md.APITokenScope,\n\t\tAPITokenExpiresAt: md.APITokenExpiresAt,\n\t\tAPITokenID:        md.APITokenID,\n\t}\n\n\tg.Config.SetAuthToken(name, entry)\n\n\tif makeDefault {\n\t\tg.Config.Auth.Default = name\n\t}\n}\n\n// StoreStaticToken validates a raw API token, fetches metadata, and stores it\n// in the auth config as the default token. Returns the stored name and\n// metadata.\nfunc StoreStaticToken(g *global.Data, token string) (name string, md *TokenMetadata, err error) {\n\tmd, err = FetchTokenMetadata(g, token)\n\tif err != nil {\n\t\treturn \"\", nil, err\n\t}\n\n\tname = md.APITokenName\n\tif name == \"\" {\n\t\tname = \"default\"\n\t}\n\n\tBuildAndStoreStaticToken(g, token, name, md, true)\n\n\tif err := g.Config.Write(g.ConfigPath); err != nil {\n\t\treturn \"\", nil, fmt.Errorf(\"error saving config: %w\", err)\n\t}\n\n\treturn name, md, nil\n}\n\n// fetchTokenSelf calls GetTokenSelf and populates md on success.\n// Returns true if the call succeeded and md was populated, false otherwise.\n// Failures (including panics from unmocked test doubles) are logged.\nfunc fetchTokenSelf(g *global.Data, apiClient api.Interface, md *TokenMetadata) (ok bool) {\n\tdefer func() {\n\t\tif r := recover(); r != nil {\n\t\t\tg.ErrLog.Add(fmt.Errorf(\"GetTokenSelf panicked (best-effort): %v\", r))\n\t\t\tok = false\n\t\t}\n\t}()\n\n\ttok, err := apiClient.GetTokenSelf(context.TODO())\n\tif err != nil {\n\t\tg.ErrLog.Add(fmt.Errorf(\"GetTokenSelf failed (best-effort): %w\", err))\n\t\treturn false\n\t}\n\n\tmd.APITokenName = fastly.ToValue(tok.Name)\n\tif tok.Scope != nil {\n\t\tmd.APITokenScope = string(*tok.Scope)\n\t}\n\tif tok.ExpiresAt != nil {\n\t\tmd.APITokenExpiresAt = tok.ExpiresAt.Format(time.RFC3339)\n\t}\n\tmd.APITokenID = fastly.ToValue(tok.TokenID)\n\treturn true\n}\n"
  },
  {
    "path": "pkg/commands/auth/metadata_test.go",
    "content": "package auth_test\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\tauthcmd \"github.com/fastly/cli/pkg/commands/auth\"\n\t\"github.com/fastly/cli/pkg/config\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/mock\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n\t\"github.com/fastly/cli/pkg/threadsafe\"\n)\n\nvar (\n\ttestTokenScope    = fastly.GlobalScope\n\ttestTokenExpiry   = time.Date(2025, 12, 31, 23, 59, 59, 0, time.UTC)\n\ttestTokenSelfFull = func(_ context.Context) (*fastly.Token, error) {\n\t\treturn &fastly.Token{\n\t\t\tTokenID: fastly.ToPointer(\"tok-id-123\"),\n\t\t\tName:    fastly.ToPointer(\"my-api-token\"),\n\t\t\tScope:   &testTokenScope,\n\t\t}, nil\n\t}\n\ttestTokenSelfWithExpiry = func(_ context.Context) (*fastly.Token, error) {\n\t\treturn &fastly.Token{\n\t\t\tTokenID:   fastly.ToPointer(\"tok-id-expiry\"),\n\t\t\tName:      fastly.ToPointer(\"expiring-token\"),\n\t\t\tScope:     &testTokenScope,\n\t\t\tExpiresAt: &testTokenExpiry,\n\t\t}, nil\n\t}\n\ttestTokenSelfNoName = func(_ context.Context) (*fastly.Token, error) {\n\t\treturn &fastly.Token{\n\t\t\tTokenID: fastly.ToPointer(\"tok-id-noname\"),\n\t\t\tScope:   &testTokenScope,\n\t\t}, nil\n\t}\n\ttestGetCurrentUser = func(_ context.Context) (*fastly.User, error) {\n\t\treturn &fastly.User{\n\t\t\tLogin:      fastly.ToPointer(\"alice@example.com\"),\n\t\t\tCustomerID: fastly.ToPointer(\"cust-abc123\"),\n\t\t}, nil\n\t}\n)\n\nfunc TestAuthAdd(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName: \"add with explicit name stores metadata\",\n\t\t\tArgs: \"add mytoken --api-token test-token-value\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetCurrentUserFn: testGetCurrentUser,\n\t\t\t\tGetTokenSelfFn:   testTokenSelfFull,\n\t\t\t},\n\t\t\tWantOutputs: []string{`Token \"mytoken\" added`, \"Token saved to\"},\n\t\t\tValidator: func(t *testing.T, _ *testutil.CLIScenario, opts *global.Data, _ *threadsafe.Buffer) {\n\t\t\t\tt.Helper()\n\t\t\t\tat := opts.Config.GetAuthToken(\"mytoken\")\n\t\t\t\tif at == nil {\n\t\t\t\t\tt.Fatal(\"expected auth token 'mytoken' to exist\")\n\t\t\t\t}\n\t\t\t\tif at.Email != \"alice@example.com\" {\n\t\t\t\t\tt.Errorf(\"want email alice@example.com, got %s\", at.Email)\n\t\t\t\t}\n\t\t\t\tif at.AccountID != \"cust-abc123\" {\n\t\t\t\t\tt.Errorf(\"want account ID cust-abc123, got %s\", at.AccountID)\n\t\t\t\t}\n\t\t\t\tif at.APITokenName != \"my-api-token\" {\n\t\t\t\t\tt.Errorf(\"want APITokenName my-api-token, got %s\", at.APITokenName)\n\t\t\t\t}\n\t\t\t\tif at.APITokenScope != \"global\" {\n\t\t\t\t\tt.Errorf(\"want APITokenScope global, got %s\", at.APITokenScope)\n\t\t\t\t}\n\t\t\t\tif at.APITokenID != \"tok-id-123\" {\n\t\t\t\t\tt.Errorf(\"want APITokenID tok-id-123, got %s\", at.APITokenID)\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tName: \"add without name derives from API token name\",\n\t\t\tArgs: \"add --api-token test-token-value\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetCurrentUserFn: testGetCurrentUser,\n\t\t\t\tGetTokenSelfFn:   testTokenSelfFull,\n\t\t\t},\n\t\t\tWantOutputs: []string{`Token \"my-api-token\" added`, \"Token saved to\"},\n\t\t\tValidator: func(t *testing.T, _ *testutil.CLIScenario, opts *global.Data, _ *threadsafe.Buffer) {\n\t\t\t\tt.Helper()\n\t\t\t\tat := opts.Config.GetAuthToken(\"my-api-token\")\n\t\t\t\tif at == nil {\n\t\t\t\t\tt.Fatal(\"expected auth token 'my-api-token' to exist\")\n\t\t\t\t}\n\t\t\t\tif at.APITokenName != \"my-api-token\" {\n\t\t\t\t\tt.Errorf(\"want APITokenName my-api-token, got %s\", at.APITokenName)\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tName: \"add without name fails when API token has no name\",\n\t\t\tArgs: \"add --api-token test-token-value\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetCurrentUserFn: testGetCurrentUser,\n\t\t\t\tGetTokenSelfFn:   testTokenSelfNoName,\n\t\t\t},\n\t\t\tWantError: \"could not determine a name for this token\",\n\t\t},\n\t\t{\n\t\t\tName: \"add stores expiry when present\",\n\t\t\tArgs: \"add expiring --api-token test-token-value\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetCurrentUserFn: testGetCurrentUser,\n\t\t\t\tGetTokenSelfFn:   testTokenSelfWithExpiry,\n\t\t\t},\n\t\t\tWantOutputs: []string{`Token \"expiring\" added`, \"Token saved to\"},\n\t\t\tValidator: func(t *testing.T, _ *testutil.CLIScenario, opts *global.Data, _ *threadsafe.Buffer) {\n\t\t\t\tt.Helper()\n\t\t\t\tat := opts.Config.GetAuthToken(\"expiring\")\n\t\t\t\tif at == nil {\n\t\t\t\t\tt.Fatal(\"expected auth token to exist\")\n\t\t\t\t}\n\t\t\t\tif at.APITokenExpiresAt == \"\" {\n\t\t\t\t\tt.Error(\"expected APITokenExpiresAt to be set\")\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tName: \"add sets default when no default exists\",\n\t\t\tArgs: \"add first-token --api-token test-token-value\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetCurrentUserFn: testGetCurrentUser,\n\t\t\t\tGetTokenSelfFn:   testTokenSelfFull,\n\t\t\t},\n\t\t\tConfigFile: &config.File{\n\t\t\t\tAuth: config.Auth{},\n\t\t\t},\n\t\t\tWantOutputs: []string{`Token \"first-token\" added`, \"set as default\"},\n\t\t\tValidator: func(t *testing.T, _ *testutil.CLIScenario, opts *global.Data, _ *threadsafe.Buffer) {\n\t\t\t\tt.Helper()\n\t\t\t\tif opts.Config.Auth.Default != \"first-token\" {\n\t\t\t\t\tt.Errorf(\"want Auth.Default first-token, got %s\", opts.Config.Auth.Default)\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tName: \"add rejects duplicate name\",\n\t\t\tArgs: \"add existing --api-token test-token-value\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetCurrentUserFn: testGetCurrentUser,\n\t\t\t\tGetTokenSelfFn:   testTokenSelfFull,\n\t\t\t},\n\t\t\tConfigFile: &config.File{\n\t\t\t\tAuth: config.Auth{\n\t\t\t\t\tDefault: \"existing\",\n\t\t\t\t\tTokens: config.AuthTokens{\n\t\t\t\t\t\t\"existing\": &config.AuthToken{\n\t\t\t\t\t\t\tType:  config.AuthTokenTypeStatic,\n\t\t\t\t\t\t\tToken: \"old-token\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: `token \"existing\" already exists`,\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{\"auth\"}, scenarios)\n}\n\nfunc TestAuthLogin(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName: \"login stores metadata under API token name\",\n\t\t\tArgs: \"login\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetCurrentUserFn: testGetCurrentUser,\n\t\t\t\tGetTokenSelfFn:   testTokenSelfFull,\n\t\t\t},\n\t\t\tStdin: []string{\n\t\t\t\t\"my-login-token\",\n\t\t\t},\n\t\t\tWantOutputs: []string{`Authenticated as alice@example.com (token stored as \"my-api-token\")`, \"Token saved to\"},\n\t\t\tValidator: func(t *testing.T, _ *testutil.CLIScenario, opts *global.Data, _ *threadsafe.Buffer) {\n\t\t\t\tt.Helper()\n\t\t\t\tat := opts.Config.GetAuthToken(\"my-api-token\")\n\t\t\t\tif at == nil {\n\t\t\t\t\tt.Fatal(\"expected auth token 'my-api-token' to exist\")\n\t\t\t\t}\n\t\t\t\tif at.APITokenName != \"my-api-token\" {\n\t\t\t\t\tt.Errorf(\"want APITokenName my-api-token, got %s\", at.APITokenName)\n\t\t\t\t}\n\t\t\t\tif at.APITokenScope != \"global\" {\n\t\t\t\t\tt.Errorf(\"want APITokenScope global, got %s\", at.APITokenScope)\n\t\t\t\t}\n\t\t\t\tif at.APITokenID != \"tok-id-123\" {\n\t\t\t\t\tt.Errorf(\"want APITokenID tok-id-123, got %s\", at.APITokenID)\n\t\t\t\t}\n\t\t\t\tif at.Email != \"alice@example.com\" {\n\t\t\t\t\tt.Errorf(\"want email alice@example.com, got %s\", at.Email)\n\t\t\t\t}\n\t\t\t\tif opts.Config.Auth.Default != \"my-api-token\" {\n\t\t\t\t\tt.Errorf(\"want Auth.Default my-api-token, got %s\", opts.Config.Auth.Default)\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tName: \"login falls back to default when API token has no name\",\n\t\t\tArgs: \"login\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetCurrentUserFn: testGetCurrentUser,\n\t\t\t\tGetTokenSelfFn:   testTokenSelfNoName,\n\t\t\t},\n\t\t\tStdin: []string{\n\t\t\t\t\"my-login-token\",\n\t\t\t},\n\t\t\tWantOutputs: []string{`Authenticated as alice@example.com (token stored as \"default\")`, \"Token saved to\"},\n\t\t\tValidator: func(t *testing.T, _ *testutil.CLIScenario, opts *global.Data, _ *threadsafe.Buffer) {\n\t\t\t\tt.Helper()\n\t\t\t\tat := opts.Config.GetAuthToken(\"default\")\n\t\t\t\tif at == nil {\n\t\t\t\t\tt.Fatal(\"expected auth token 'default' to exist\")\n\t\t\t\t}\n\t\t\t\tif opts.Config.Auth.Default != \"default\" {\n\t\t\t\t\tt.Errorf(\"want Auth.Default default, got %s\", opts.Config.Auth.Default)\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{\"auth\"}, scenarios)\n}\n\nfunc TestAuthShow(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName: \"show displays metadata when present\",\n\t\t\tArgs: \"show mytoken\",\n\t\t\tConfigFile: &config.File{\n\t\t\t\tAuth: config.Auth{\n\t\t\t\t\tDefault: \"mytoken\",\n\t\t\t\t\tTokens: config.AuthTokens{\n\t\t\t\t\t\t\"mytoken\": &config.AuthToken{\n\t\t\t\t\t\t\tType:              config.AuthTokenTypeStatic,\n\t\t\t\t\t\t\tToken:             \"test-token-value\",\n\t\t\t\t\t\t\tEmail:             \"alice@example.com\",\n\t\t\t\t\t\t\tAccountID:         \"cust-abc123\",\n\t\t\t\t\t\t\tAPITokenName:      \"my-api-token\",\n\t\t\t\t\t\t\tAPITokenScope:     \"global\",\n\t\t\t\t\t\t\tAPITokenExpiresAt: \"2025-12-31T23:59:59Z\",\n\t\t\t\t\t\t\tAPITokenID:        \"tok-id-123\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutputs: []string{\n\t\t\t\t\"API token name: my-api-token\",\n\t\t\t\t\"API token scope: global\",\n\t\t\t\t\"API token expires at: 2025-12-31T23:59:59Z\",\n\t\t\t\t\"API token ID: tok-id-123\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tName: \"show omits metadata when absent\",\n\t\t\tArgs: \"show basic\",\n\t\t\tConfigFile: &config.File{\n\t\t\t\tAuth: config.Auth{\n\t\t\t\t\tDefault: \"basic\",\n\t\t\t\t\tTokens: config.AuthTokens{\n\t\t\t\t\t\t\"basic\": &config.AuthToken{\n\t\t\t\t\t\t\tType:  config.AuthTokenTypeStatic,\n\t\t\t\t\t\t\tToken: \"test-token-value\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tDontWantOutputs: []string{\n\t\t\t\t\"API token name:\",\n\t\t\t\t\"API token scope:\",\n\t\t\t\t\"API token expires at:\",\n\t\t\t\t\"API token ID:\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tName: \"show without name uses default token\",\n\t\t\tArgs: \"show\",\n\t\t\tConfigFile: &config.File{\n\t\t\t\tAuth: config.Auth{\n\t\t\t\t\tDefault: \"mytoken\",\n\t\t\t\t\tTokens: config.AuthTokens{\n\t\t\t\t\t\t\"mytoken\": &config.AuthToken{\n\t\t\t\t\t\t\tType:         config.AuthTokenTypeStatic,\n\t\t\t\t\t\t\tToken:        \"test-token-value\",\n\t\t\t\t\t\t\tEmail:        \"alice@example.com\",\n\t\t\t\t\t\t\tAPITokenName: \"my-api-token\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutputs: []string{\n\t\t\t\t\"Name: mytoken (default)\",\n\t\t\t\t\"Email: alice@example.com\",\n\t\t\t\t\"API token name: my-api-token\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tName: \"show without name errors when env token set\",\n\t\t\tArgs: \"show\",\n\t\t\tSetup: func(_ *testing.T, _ *testutil.CLIScenario, opts *global.Data) {\n\t\t\t\topts.Env.APIToken = \"env-token-value\"\n\t\t\t},\n\t\t\tConfigFile: &config.File{},\n\t\t\tWantError:  \"current token is not stored\",\n\t\t},\n\t\t{\n\t\t\tName: \"show without name resolves manifest profile\",\n\t\t\tArgs: \"show\",\n\t\t\tConfigFile: &config.File{\n\t\t\t\tAuth: config.Auth{\n\t\t\t\t\tDefault: \"other\",\n\t\t\t\t\tTokens: config.AuthTokens{\n\t\t\t\t\t\t\"other\": &config.AuthToken{\n\t\t\t\t\t\t\tType:  config.AuthTokenTypeStatic,\n\t\t\t\t\t\t\tToken: \"other-token\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"from-manifest\": &config.AuthToken{\n\t\t\t\t\t\t\tType:         config.AuthTokenTypeStatic,\n\t\t\t\t\t\t\tToken:        \"manifest-token\",\n\t\t\t\t\t\t\tEmail:        \"manifest@example.com\",\n\t\t\t\t\t\t\tAPITokenName: \"manifest-api-token\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tSetup: func(_ *testing.T, _ *testutil.CLIScenario, opts *global.Data) {\n\t\t\t\topts.Manifest.File.Profile = \"from-manifest\"\n\t\t\t},\n\t\t\tWantOutputs: []string{\n\t\t\t\t\"Name: from-manifest\",\n\t\t\t\t\"Email: manifest@example.com\",\n\t\t\t\t\"API token name: manifest-api-token\",\n\t\t\t},\n\t\t\tDontWantOutputs: []string{\n\t\t\t\t\"(default)\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tName: \"show with unknown --profile rejected\",\n\t\t\tArgs: \"show --profile bogus\",\n\t\t\tConfigFile: &config.File{\n\t\t\t\tAuth: config.Auth{\n\t\t\t\t\tDefault: \"user\",\n\t\t\t\t\tTokens: config.AuthTokens{\n\t\t\t\t\t\t\"user\": &config.AuthToken{Type: config.AuthTokenTypeStatic, Token: \"t\"},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError:       `profile \"bogus\"`,\n\t\t\tWantRemediation: \"fastly auth\",\n\t\t},\n\t\t{\n\t\t\tName: \"show with known --profile uses that profile\",\n\t\t\tArgs: \"show --profile alt\",\n\t\t\tConfigFile: &config.File{\n\t\t\t\tAuth: config.Auth{\n\t\t\t\t\tDefault: \"user\",\n\t\t\t\t\tTokens: config.AuthTokens{\n\t\t\t\t\t\t\"user\": &config.AuthToken{Type: config.AuthTokenTypeStatic, Token: \"t\"},\n\t\t\t\t\t\t\"alt\":  &config.AuthToken{Type: config.AuthTokenTypeStatic, Token: \"alt-token\", Email: \"a@example.com\"},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutputs: []string{\"Name: alt\", \"a@example.com\"},\n\t\t},\n\t\t{\n\t\t\tName: \"show with --token raw --profile bogus skips profile validation\",\n\t\t\tArgs: \"show --token raw --profile bogus\",\n\t\t\tConfigFile: &config.File{\n\t\t\t\tAuth: config.Auth{\n\t\t\t\t\tDefault: \"user\",\n\t\t\t\t\tTokens: config.AuthTokens{\n\t\t\t\t\t\t\"user\": &config.AuthToken{Type: config.AuthTokenTypeStatic, Token: \"t\"},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError:      \"current token is not stored\",\n\t\t\tDontWantOutput: \"not found in auth config\",\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{\"auth\"}, scenarios)\n}\n\nfunc TestAuthAddScopedToken(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName: \"add with name succeeds when GetCurrentUser fails but GetTokenSelf succeeds\",\n\t\t\tArgs: \"add purge-token --api-token scoped-token-value\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetCurrentUserFn: func(_ context.Context) (*fastly.User, error) {\n\t\t\t\t\treturn nil, fmt.Errorf(\"403 Forbidden: Access denied to purge token\")\n\t\t\t\t},\n\t\t\t\tGetTokenSelfFn: testTokenSelfFull,\n\t\t\t},\n\t\t\tWantOutputs: []string{`Token \"purge-token\" added`, \"Token saved to\"},\n\t\t\tValidator: func(t *testing.T, _ *testutil.CLIScenario, opts *global.Data, _ *threadsafe.Buffer) {\n\t\t\t\tt.Helper()\n\t\t\t\tat := opts.Config.GetAuthToken(\"purge-token\")\n\t\t\t\tif at == nil {\n\t\t\t\t\tt.Fatal(\"expected auth token 'purge-token' to exist\")\n\t\t\t\t}\n\t\t\t\tif at.Token != \"scoped-token-value\" {\n\t\t\t\t\tt.Errorf(\"want token scoped-token-value, got %s\", at.Token)\n\t\t\t\t}\n\t\t\t\tif at.Email != \"\" {\n\t\t\t\t\tt.Errorf(\"want empty email for scoped token, got %s\", at.Email)\n\t\t\t\t}\n\t\t\t\tif at.APITokenName != \"my-api-token\" {\n\t\t\t\t\tt.Errorf(\"want APITokenName my-api-token, got %s\", at.APITokenName)\n\t\t\t\t}\n\t\t\t\tif at.APITokenScope != \"global\" {\n\t\t\t\t\tt.Errorf(\"want APITokenScope global, got %s\", at.APITokenScope)\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tName: \"add with name fails when both API calls fail (invalid token)\",\n\t\t\tArgs: \"add bad-token --api-token invalid-token-value\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetCurrentUserFn: func(_ context.Context) (*fastly.User, error) {\n\t\t\t\t\treturn nil, fmt.Errorf(\"403 Forbidden\")\n\t\t\t\t},\n\t\t\t\tGetTokenSelfFn: func(_ context.Context) (*fastly.Token, error) {\n\t\t\t\t\treturn nil, fmt.Errorf(\"403 Forbidden\")\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"token validation failed: neither /current_user nor /tokens/self responded successfully\",\n\t\t},\n\t\t{\n\t\t\tName: \"add without name gives friendly error for scoped token\",\n\t\t\tArgs: \"add --api-token scoped-token-value\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetCurrentUserFn: func(_ context.Context) (*fastly.User, error) {\n\t\t\t\t\treturn nil, fmt.Errorf(\"403 Forbidden: Access denied to purge token\")\n\t\t\t\t},\n\t\t\t\tGetTokenSelfFn: testTokenSelfNoName,\n\t\t\t},\n\t\t\tWantError: \"could not determine a name for this token\",\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{\"auth\"}, scenarios)\n}\n\nfunc TestEnrichWithTokenSelfPreservesOnFailure(t *testing.T) {\n\tvar stdout threadsafe.Buffer\n\tdata := testutil.MockGlobalData([]string{\"fastly\"}, &stdout)\n\n\tdata.APIClientFactory = mock.APIClient(mock.API{\n\t\tGetTokenSelfFn: func(_ context.Context) (*fastly.Token, error) {\n\t\t\treturn nil, fmt.Errorf(\"403 forbidden\")\n\t\t},\n\t})\n\n\tat := &config.AuthToken{\n\t\tToken:             \"existing-token\",\n\t\tAPITokenName:      \"original-name\",\n\t\tAPITokenScope:     \"global\",\n\t\tAPITokenExpiresAt: \"2025-06-01T00:00:00Z\",\n\t\tAPITokenID:        \"original-id\",\n\t}\n\n\tauthcmd.EnrichWithTokenSelf(data, at)\n\n\tif at.APITokenName != \"original-name\" {\n\t\tt.Errorf(\"want APITokenName preserved as original-name, got %s\", at.APITokenName)\n\t}\n\tif at.APITokenScope != \"global\" {\n\t\tt.Errorf(\"want APITokenScope preserved as global, got %s\", at.APITokenScope)\n\t}\n\tif at.APITokenExpiresAt != \"2025-06-01T00:00:00Z\" {\n\t\tt.Errorf(\"want APITokenExpiresAt preserved, got %s\", at.APITokenExpiresAt)\n\t}\n\tif at.APITokenID != \"original-id\" {\n\t\tt.Errorf(\"want APITokenID preserved as original-id, got %s\", at.APITokenID)\n\t}\n}\n\nfunc TestAuthDelete(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName: \"delete non-default token\",\n\t\t\tArgs: \"delete secondary\",\n\t\t\tConfigFile: &config.File{\n\t\t\t\tAuth: config.Auth{\n\t\t\t\t\tDefault: \"primary\",\n\t\t\t\t\tTokens: config.AuthTokens{\n\t\t\t\t\t\t\"primary\":   &config.AuthToken{Type: config.AuthTokenTypeStatic, Token: \"tok1\"},\n\t\t\t\t\t\t\"secondary\": &config.AuthToken{Type: config.AuthTokenTypeStatic, Token: \"tok2\"},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutputs:    []string{`Token \"secondary\" removed`},\n\t\t\tDontWantOutput: \"reassigned\",\n\t\t},\n\t\t{\n\t\t\tName: \"delete default token confirms and reassigns\",\n\t\t\tArgs: \"delete primary\",\n\t\t\tConfigFile: &config.File{\n\t\t\t\tAuth: config.Auth{\n\t\t\t\t\tDefault: \"primary\",\n\t\t\t\t\tTokens: config.AuthTokens{\n\t\t\t\t\t\t\"primary\":   &config.AuthToken{Type: config.AuthTokenTypeStatic, Token: \"tok1\"},\n\t\t\t\t\t\t\"secondary\": &config.AuthToken{Type: config.AuthTokenTypeStatic, Token: \"tok2\"},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tStdin:       []string{\"y\"},\n\t\t\tWantOutputs: []string{\"current default token\", \"FASTLY_API_TOKEN\", \"Are you sure\", `Token \"primary\" removed`, \"Default token reassigned\"},\n\t\t\tValidator: func(t *testing.T, _ *testutil.CLIScenario, opts *global.Data, _ *threadsafe.Buffer) {\n\t\t\t\tt.Helper()\n\t\t\t\tif opts.Config.Auth.Default == \"primary\" {\n\t\t\t\t\tt.Error(\"expected default to no longer be the deleted token\")\n\t\t\t\t}\n\t\t\t\tif opts.Config.Auth.Default == \"\" {\n\t\t\t\t\tt.Error(\"expected default to be reassigned to remaining token\")\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tName: \"delete default token aborted by user\",\n\t\t\tArgs: \"delete primary\",\n\t\t\tConfigFile: &config.File{\n\t\t\t\tAuth: config.Auth{\n\t\t\t\t\tDefault: \"primary\",\n\t\t\t\t\tTokens: config.AuthTokens{\n\t\t\t\t\t\t\"primary\":   &config.AuthToken{Type: config.AuthTokenTypeStatic, Token: \"tok1\"},\n\t\t\t\t\t\t\"secondary\": &config.AuthToken{Type: config.AuthTokenTypeStatic, Token: \"tok2\"},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tStdin:          []string{\"n\"},\n\t\t\tDontWantOutput: \"removed\",\n\t\t\tValidator: func(t *testing.T, _ *testutil.CLIScenario, opts *global.Data, _ *threadsafe.Buffer) {\n\t\t\t\tt.Helper()\n\t\t\t\tif opts.Config.Auth.Default != \"primary\" {\n\t\t\t\t\tt.Error(\"expected default to remain unchanged after user declined\")\n\t\t\t\t}\n\t\t\t\tif opts.Config.GetAuthToken(\"primary\") == nil {\n\t\t\t\t\tt.Error(\"expected token to still exist after user declined\")\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tName: \"delete last token confirms and warns no default\",\n\t\t\tArgs: \"delete only\",\n\t\t\tConfigFile: &config.File{\n\t\t\t\tAuth: config.Auth{\n\t\t\t\t\tDefault: \"only\",\n\t\t\t\t\tTokens: config.AuthTokens{\n\t\t\t\t\t\t\"only\": &config.AuthToken{Type: config.AuthTokenTypeStatic, Token: \"tok1\"},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tStdin:       []string{\"y\"},\n\t\t\tWantOutputs: []string{`Token \"only\" removed`, \"No default token configured\"},\n\t\t},\n\t\t{\n\t\t\tName: \"delete stale default returns not found without prompting\",\n\t\t\tArgs: \"delete ghost\",\n\t\t\tConfigFile: &config.File{\n\t\t\t\tAuth: config.Auth{\n\t\t\t\t\tDefault: \"ghost\",\n\t\t\t\t\tTokens:  config.AuthTokens{},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError:      `token \"ghost\" not found`,\n\t\t\tDontWantOutput: \"Are you sure\",\n\t\t},\n\t\t{\n\t\t\tName:      \"delete nonexistent token fails\",\n\t\t\tArgs:      \"delete ghost\",\n\t\t\tWantError: `token \"ghost\" not found`,\n\t\t},\n\t\t{\n\t\t\tName: \"delete default token skips prompt with --auto-yes\",\n\t\t\tArgs: \"delete primary -y\",\n\t\t\tConfigFile: &config.File{\n\t\t\t\tAuth: config.Auth{\n\t\t\t\t\tDefault: \"primary\",\n\t\t\t\t\tTokens: config.AuthTokens{\n\t\t\t\t\t\t\"primary\":   &config.AuthToken{Type: config.AuthTokenTypeStatic, Token: \"tok1\"},\n\t\t\t\t\t\t\"secondary\": &config.AuthToken{Type: config.AuthTokenTypeStatic, Token: \"tok2\"},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tDontWantOutput: \"Are you sure\",\n\t\t\tWantOutputs:    []string{`Token \"primary\" removed`, \"Default token reassigned\"},\n\t\t},\n\t\t{\n\t\t\tName: \"delete default token skips prompt with --non-interactive\",\n\t\t\tArgs: \"delete primary -i\",\n\t\t\tConfigFile: &config.File{\n\t\t\t\tAuth: config.Auth{\n\t\t\t\t\tDefault: \"primary\",\n\t\t\t\t\tTokens: config.AuthTokens{\n\t\t\t\t\t\t\"primary\":   &config.AuthToken{Type: config.AuthTokenTypeStatic, Token: \"tok1\"},\n\t\t\t\t\t\t\"secondary\": &config.AuthToken{Type: config.AuthTokenTypeStatic, Token: \"tok2\"},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tDontWantOutput: \"Are you sure\",\n\t\t\tWantOutputs:    []string{`Token \"primary\" removed`, \"Default token reassigned\"},\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{\"auth\"}, scenarios)\n}\n"
  },
  {
    "path": "pkg/commands/auth/revoke.go",
    "content": "package auth\n\nimport (\n\t\"bufio\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/api\"\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/config\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// errCancelled is returned when a user declines a confirmation prompt.\n// It signals intentional cancellation, not a failure condition.\nvar errCancelled = errors.New(\"cancelled\")\n\n// RevokeCommand revokes a token via the API and removes it from local config.\ntype RevokeCommand struct {\n\targparser.Base\n\tcurrent    bool\n\tname       string\n\ttokenValue string\n\tid         string\n\tfile       string\n}\n\nfunc NewRevokeCommand(parent argparser.Registerer, g *global.Data) *RevokeCommand {\n\tvar c RevokeCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(\"revoke\", \"Revoke a token via the API and remove it from local config\")\n\tc.CmdClause.Flag(\"current\", \"Revoke the token used to authenticate the current request\").BoolVar(&c.current)\n\tc.CmdClause.Flag(\"name\", \"Name of a locally stored token to revoke\").StringVar(&c.name)\n\tc.CmdClause.Flag(\"token-value\", \"Raw API token string to revoke (pass '-' to read from stdin)\").StringVar(&c.tokenValue)\n\tc.CmdClause.Flag(\"id\", \"Alphanumeric string identifying a token to revoke\").StringVar(&c.id)\n\tc.CmdClause.Flag(\"file\", \"Path to a newline-delimited file of token IDs to revoke in bulk\").StringVar(&c.file)\n\treturn &c\n}\n\nfunc (c *RevokeCommand) Exec(in io.Reader, out io.Writer) error {\n\tif err := c.validateFlags(); err != nil {\n\t\treturn err\n\t}\n\n\tif err := c.Globals.ValidateProfileFlag(); err != nil {\n\t\treturn err\n\t}\n\n\tswitch {\n\tcase c.current:\n\t\treturn c.revokeCurrent(in, out)\n\tcase c.name != \"\":\n\t\treturn c.revokeByName(in, out)\n\tcase c.tokenValue != \"\":\n\t\treturn c.revokeByTokenValue(in, out)\n\tcase c.id != \"\":\n\t\treturn c.revokeByID(out)\n\tcase c.file != \"\":\n\t\treturn c.revokeByFile(out)\n\t}\n\n\treturn nil\n}\n\nfunc (c *RevokeCommand) validateFlags() error {\n\tcount := 0\n\tif c.current {\n\t\tcount++\n\t}\n\tif c.name != \"\" {\n\t\tcount++\n\t}\n\tif c.tokenValue != \"\" {\n\t\tcount++\n\t}\n\tif c.id != \"\" {\n\t\tcount++\n\t}\n\tif c.file != \"\" {\n\t\tcount++\n\t}\n\tif count == 0 {\n\t\treturn fmt.Errorf(\"error parsing arguments: must provide one of --current, --name, --token-value, --id, or --file\")\n\t}\n\tif count > 1 {\n\t\treturn fmt.Errorf(\"error parsing arguments: only one of --current, --name, --token-value, --id, or --file may be used\")\n\t}\n\treturn nil\n}\n\nfunc (c *RevokeCommand) revokeCurrent(in io.Reader, out io.Writer) error {\n\ttok, _ := c.Globals.Token()\n\n\tnames := findLocalTokensByValue(&c.Globals.Config, tok)\n\tif err := c.confirmDefaultRevocation(names, in, out); err != nil {\n\t\tif errors.Is(err, errCancelled) {\n\t\t\treturn nil\n\t\t}\n\t\treturn err\n\t}\n\n\tclient, err := c.authClient()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\terr = client.DeleteTokenSelf(context.TODO())\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Revoked current token\")\n\treturn c.removeLocalTokens(names, out)\n}\n\nfunc (c *RevokeCommand) revokeByName(in io.Reader, out io.Writer) error {\n\tentry := c.Globals.Config.GetAuthToken(c.name)\n\tif entry == nil {\n\t\treturn fmt.Errorf(\"token %q not found\", c.name)\n\t}\n\n\tif err := c.confirmDefaultRevocation([]string{c.name}, in, out); err != nil {\n\t\tif errors.Is(err, errCancelled) {\n\t\t\treturn nil\n\t\t}\n\t\treturn err\n\t}\n\n\tclient, err := c.buildClient(entry.Token)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\terr = client.DeleteTokenSelf(context.TODO())\n\tif err != nil {\n\t\tif isSelfAlreadyGone(err) {\n\t\t\ttext.Warning(out, \"Token was already revoked remotely\\n\")\n\t\t} else {\n\t\t\tc.Globals.ErrLog.Add(err)\n\t\t\treturn err\n\t\t}\n\t} else {\n\t\ttext.Success(out, \"Revoked token %q\", c.name)\n\t}\n\n\tnames := []string{c.name}\n\tfor _, n := range findLocalTokensByValue(&c.Globals.Config, entry.Token) {\n\t\tif n != c.name {\n\t\t\tnames = append(names, n)\n\t\t}\n\t}\n\treturn c.removeLocalTokens(names, out)\n}\n\nfunc (c *RevokeCommand) revokeByTokenValue(in io.Reader, out io.Writer) error {\n\traw, err := readTokenValue(c.tokenValue, in)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tnames := findLocalTokensByValue(&c.Globals.Config, raw)\n\tif err := c.confirmDefaultRevocation(names, in, out); err != nil {\n\t\tif errors.Is(err, errCancelled) {\n\t\t\treturn nil\n\t\t}\n\t\treturn err\n\t}\n\n\tclient, err := c.buildClient(raw)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\terr = client.DeleteTokenSelf(context.TODO())\n\tif err != nil {\n\t\tif isSelfAlreadyGone(err) {\n\t\t\ttext.Warning(out, \"Token was already revoked remotely\\n\")\n\t\t} else {\n\t\t\tc.Globals.ErrLog.Add(err)\n\t\t\treturn err\n\t\t}\n\t} else {\n\t\ttext.Success(out, \"Revoked token\")\n\t}\n\n\tif len(names) == 0 {\n\t\ttext.Info(out, \"No matching local token entry found\\n\")\n\t\treturn nil\n\t}\n\treturn c.removeLocalTokens(names, out)\n}\n\nfunc (c *RevokeCommand) revokeByID(out io.Writer) error {\n\tclient, err := c.authClient()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\terr = client.DeleteToken(context.TODO(), &fastly.DeleteTokenInput{\n\t\tTokenID: c.id,\n\t})\n\tif err != nil {\n\t\tif isAlreadyGone(err) {\n\t\t\ttext.Warning(out, \"Token was already revoked remotely\\n\")\n\t\t} else {\n\t\t\tc.Globals.ErrLog.Add(err)\n\t\t\treturn err\n\t\t}\n\t} else {\n\t\ttext.Success(out, \"Revoked token '%s'\", c.id)\n\t}\n\tnames := findLocalTokensByID(&c.Globals.Config, c.id)\n\tif len(names) == 0 {\n\t\ttext.Info(out, \"No local token entry with matching API token ID found; local cleanup skipped\\n\")\n\t\treturn nil\n\t}\n\treturn c.removeLocalTokens(names, out)\n}\n\nfunc (c *RevokeCommand) revokeByFile(out io.Writer) error {\n\tids, err := readTokenIDFile(c.file)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tclient, err := c.authClient()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\ttokens := make([]*fastly.BatchToken, len(ids))\n\tfor i, id := range ids {\n\t\ttokens[i] = &fastly.BatchToken{ID: id}\n\t}\n\n\terr = client.BatchDeleteTokens(context.TODO(), &fastly.BatchDeleteTokensInput{\n\t\tTokens: tokens,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Revoked %d token(s)\", len(ids))\n\tif c.Globals.Verbose() {\n\t\ttbl := text.NewTable(out)\n\t\ttbl.AddHeader(\"TOKEN ID\")\n\t\tfor _, id := range ids {\n\t\t\ttbl.AddLine(id)\n\t\t}\n\t\ttbl.Print()\n\t}\n\n\tvar names []string\n\tfor _, id := range ids {\n\t\tnames = append(names, findLocalTokensByID(&c.Globals.Config, id)...)\n\t}\n\tif len(names) == 0 {\n\t\ttext.Info(out, \"No local token entries with matching API token IDs found; local cleanup skipped\\n\")\n\t\treturn nil\n\t}\n\treturn c.removeLocalTokens(names, out)\n}\n\nfunc (c *RevokeCommand) authClient() (api.Interface, error) {\n\ttok, _ := c.Globals.Token()\n\tif tok == \"\" {\n\t\treturn nil, fsterr.RemediationError{\n\t\t\tInner:       fmt.Errorf(\"no token available for authentication\"),\n\t\t\tRemediation: fsterr.AuthRemediation(),\n\t\t}\n\t}\n\treturn c.buildClient(tok)\n}\n\nfunc (c *RevokeCommand) buildClient(token string) (api.Interface, error) {\n\tendpoint, _ := c.Globals.APIEndpoint()\n\tclient, err := c.Globals.APIClientFactory(token, endpoint, c.Globals.Flags.Debug)\n\tif err != nil {\n\t\treturn nil, fsterr.RemediationError{\n\t\t\tInner:       fmt.Errorf(\"error creating API client: %w\", err),\n\t\t\tRemediation: \"Check your network connection and API endpoint configuration.\",\n\t\t}\n\t}\n\treturn client, nil\n}\n\nfunc (c *RevokeCommand) confirmDefaultRevocation(names []string, in io.Reader, out io.Writer) error {\n\tdef := c.Globals.Config.Auth.Default\n\tif def == \"\" {\n\t\treturn nil\n\t}\n\n\tisDefault := false\n\tfor _, n := range names {\n\t\tif n == def {\n\t\t\tisDefault = true\n\t\t\tbreak\n\t\t}\n\t}\n\tif !isDefault {\n\t\treturn nil\n\t}\n\n\tif c.Globals.Flags.AutoYes || c.Globals.Flags.NonInteractive {\n\t\treturn nil\n\t}\n\n\ttext.Warning(out, \"%q is your current default token. Revoking it will invalidate it remotely and remove it from local config.\", def)\n\tcont, err := text.AskYesNo(out, \"Are you sure? [y/N]: \", in)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif !cont {\n\t\treturn errCancelled\n\t}\n\treturn nil\n}\n\nfunc isAlreadyGone(err error) bool {\n\tvar httpErr *fastly.HTTPError\n\treturn errors.As(err, &httpErr) && httpErr.StatusCode == http.StatusNotFound\n}\n\nfunc isSelfAlreadyGone(err error) bool {\n\tvar httpErr *fastly.HTTPError\n\treturn isAlreadyGone(err) || (errors.As(err, &httpErr) && httpErr.StatusCode == http.StatusUnauthorized)\n}\n\nfunc readTokenValue(flag string, in io.Reader) (string, error) {\n\tif flag == \"-\" {\n\t\tconst maxTokenSize = 4096\n\t\tb, err := io.ReadAll(io.LimitReader(in, maxTokenSize+1))\n\t\tif err != nil {\n\t\t\treturn \"\", fsterr.RemediationError{\n\t\t\t\tInner:       fmt.Errorf(\"failed to read token from stdin: %w\", err),\n\t\t\t\tRemediation: \"Pipe a token value, e.g.: echo $TOKEN | fastly auth revoke --token-value=-\",\n\t\t\t}\n\t\t}\n\t\tif len(b) > maxTokenSize {\n\t\t\treturn \"\", fsterr.RemediationError{\n\t\t\t\tInner:       fmt.Errorf(\"stdin input exceeds %d bytes\", maxTokenSize),\n\t\t\t\tRemediation: \"Pipe a single token value, not a file. Example: echo $TOKEN | fastly auth revoke --token-value=-\",\n\t\t\t}\n\t\t}\n\t\tval := strings.TrimSpace(string(b))\n\t\tif val == \"\" {\n\t\t\treturn \"\", fsterr.RemediationError{\n\t\t\t\tInner:       fmt.Errorf(\"no token provided on stdin\"),\n\t\t\t\tRemediation: \"Pipe a token value, e.g.: echo $TOKEN | fastly auth revoke --token-value=-\",\n\t\t\t}\n\t\t}\n\t\treturn val, nil\n\t}\n\n\treturn flag, nil\n}\n\nfunc readTokenIDFile(path string) ([]string, error) {\n\tabs, err := filepath.Abs(path)\n\tif err != nil {\n\t\treturn nil, fsterr.RemediationError{\n\t\t\tInner:       fmt.Errorf(\"invalid file path %q: %w\", path, err),\n\t\t\tRemediation: \"Check the file path and try again.\",\n\t\t}\n\t}\n\n\tf, err := os.Open(abs) // #nosec\n\tif err != nil {\n\t\treturn nil, fsterr.RemediationError{\n\t\t\tInner:       fmt.Errorf(\"failed to open %q: %w\", abs, err),\n\t\t\tRemediation: \"Check the file path and permissions, then try again.\",\n\t\t}\n\t}\n\tdefer f.Close() // #nosec\n\n\tvar ids []string\n\tscanner := bufio.NewScanner(f)\n\tfor scanner.Scan() {\n\t\tline := strings.TrimSpace(scanner.Text())\n\t\tif line != \"\" {\n\t\t\tids = append(ids, line)\n\t\t}\n\t}\n\tif err := scanner.Err(); err != nil {\n\t\treturn nil, fsterr.RemediationError{\n\t\t\tInner:       fmt.Errorf(\"error reading %q: %w\", abs, err),\n\t\t\tRemediation: \"Check the file for encoding issues or try recreating it.\",\n\t\t}\n\t}\n\tif len(ids) == 0 {\n\t\treturn nil, fsterr.RemediationError{\n\t\t\tInner:       fmt.Errorf(\"file %q contains no token IDs\", abs),\n\t\t\tRemediation: \"The file should contain one token ID per line.\",\n\t\t}\n\t}\n\treturn ids, nil\n}\n\nfunc findLocalTokensByValue(cfg *config.File, raw string) []string {\n\tvar names []string\n\tfor name, entry := range cfg.Auth.Tokens {\n\t\tif entry.Token == raw {\n\t\t\tnames = append(names, name)\n\t\t}\n\t}\n\treturn names\n}\n\nfunc findLocalTokensByID(cfg *config.File, id string) []string {\n\tvar names []string\n\tfor name, entry := range cfg.Auth.Tokens {\n\t\tif entry.APITokenID == id {\n\t\t\tnames = append(names, name)\n\t\t}\n\t}\n\treturn names\n}\n\nfunc (c *RevokeCommand) removeLocalTokens(names []string, out io.Writer) error {\n\tif len(names) == 0 {\n\t\treturn nil\n\t}\n\n\toriginalDefault := c.Globals.Config.Auth.Default\n\tremovedDefault := false\n\tfor _, name := range names {\n\t\tif name == originalDefault {\n\t\t\tremovedDefault = true\n\t\t}\n\t\tc.Globals.Config.DeleteAuthToken(name)\n\t}\n\n\tif err := c.Globals.Config.Write(c.Globals.ConfigPath); err != nil {\n\t\treturn fsterr.RemediationError{\n\t\t\tInner:       fmt.Errorf(\"token(s) revoked remotely but failed to update local config: %w\", err),\n\t\t\tRemediation: fmt.Sprintf(\"Check file permissions on %s. The local config may be stale; use 'fastly auth delete' to clean up manually.\", c.Globals.ConfigPath),\n\t\t}\n\t}\n\n\tfor _, name := range names {\n\t\ttext.Info(out, \"Removed local token entry %q\\n\", name)\n\t}\n\n\tif removedDefault {\n\t\tif c.Globals.Config.Auth.Default != \"\" {\n\t\t\ttext.Info(out, \"Default token reassigned to %q\\n\", c.Globals.Config.Auth.Default)\n\t\t} else {\n\t\t\ttext.Warning(out, \"No default token configured; use 'fastly auth use <name>' to set one\\n\")\n\t\t}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/auth/revoke_test.go",
    "content": "package auth_test\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/api\"\n\t\"github.com/fastly/cli/pkg/config\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/mock\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n\t\"github.com/fastly/cli/pkg/threadsafe\"\n)\n\nfunc TestAuthRevoke(t *testing.T) {\n\tdeleteTokenSelfOK := func(_ context.Context) error { return nil }\n\tdeleteTokenOK := func(_ context.Context, _ *fastly.DeleteTokenInput) error { return nil }\n\tbatchDeleteTokensOK := func(_ context.Context, _ *fastly.BatchDeleteTokensInput) error { return nil }\n\n\tdeleteTokenSelf401 := func(_ context.Context) error {\n\t\treturn &fastly.HTTPError{StatusCode: http.StatusUnauthorized}\n\t}\n\tdeleteTokenSelf500 := func(_ context.Context) error {\n\t\treturn &fastly.HTTPError{StatusCode: http.StatusInternalServerError}\n\t}\n\n\ttwoTokenConfig := &config.File{\n\t\tAuth: config.Auth{\n\t\t\tDefault: \"primary\",\n\t\t\tTokens: config.AuthTokens{\n\t\t\t\t\"primary\":   &config.AuthToken{Type: config.AuthTokenTypeStatic, Token: \"tok-primary\", APITokenID: \"id-primary\"},\n\t\t\t\t\"secondary\": &config.AuthToken{Type: config.AuthTokenTypeStatic, Token: \"tok-secondary\", APITokenID: \"id-secondary\"},\n\t\t\t},\n\t\t},\n\t}\n\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"no flags provided\",\n\t\t\tArgs:      \"revoke\",\n\t\t\tWantError: \"must provide one of\",\n\t\t},\n\t\t{\n\t\t\tName:      \"multiple flags provided\",\n\t\t\tArgs:      \"revoke --current --name foo\",\n\t\t\tWantError: \"only one of\",\n\t\t},\n\n\t\t// --current\n\t\t{\n\t\t\tName: \"revoke current token\",\n\t\t\tArgs: \"revoke --current --token tok-stored\",\n\t\t\tAPI:  &mock.API{DeleteTokenSelfFn: deleteTokenSelfOK},\n\t\t\tConfigFile: &config.File{\n\t\t\t\tAuth: config.Auth{\n\t\t\t\t\tDefault: \"mytoken\",\n\t\t\t\t\tTokens: config.AuthTokens{\n\t\t\t\t\t\t\"mytoken\": &config.AuthToken{Type: config.AuthTokenTypeStatic, Token: \"tok-stored\"},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tStdin:       []string{\"y\"},\n\t\t\tWantOutputs: []string{\"Revoked current token\", `Removed local token entry \"mytoken\"`},\n\t\t},\n\t\t{\n\t\t\tName: \"revoke current default declined is clean exit\",\n\t\t\tArgs: \"revoke --current --token tok-stored\",\n\t\t\tAPI:  &mock.API{DeleteTokenSelfFn: deleteTokenSelfOK},\n\t\t\tConfigFile: &config.File{\n\t\t\t\tAuth: config.Auth{\n\t\t\t\t\tDefault: \"mytoken\",\n\t\t\t\t\tTokens: config.AuthTokens{\n\t\t\t\t\t\t\"mytoken\": &config.AuthToken{Type: config.AuthTokenTypeStatic, Token: \"tok-stored\"},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tStdin:          []string{\"n\"},\n\t\t\tWantOutput:     \"current default token\",\n\t\t\tDontWantOutput: \"Revoked\",\n\t\t\tValidator: func(t *testing.T, _ *testutil.CLIScenario, opts *global.Data, _ *threadsafe.Buffer) {\n\t\t\t\tt.Helper()\n\t\t\t\tif opts.Config.GetAuthToken(\"mytoken\") == nil {\n\t\t\t\t\tt.Error(\"expected token to still exist after decline\")\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tName:           \"revoke current skips prompt with --auto-yes\",\n\t\t\tArgs:           \"revoke --current --token 123 -y\",\n\t\t\tAPI:            &mock.API{DeleteTokenSelfFn: deleteTokenSelfOK},\n\t\t\tDontWantOutput: \"Are you sure\",\n\t\t\tWantOutput:     \"Revoked current token\",\n\t\t},\n\t\t{\n\t\t\tName:           \"revoke current with --token flag (unstored token)\",\n\t\t\tArgs:           \"revoke --current --token raw-ephemeral-token\",\n\t\t\tAPI:            &mock.API{DeleteTokenSelfFn: deleteTokenSelfOK},\n\t\t\tWantOutputs:    []string{\"Revoked current token\"},\n\t\t\tDontWantOutput: \"Removed local\",\n\t\t},\n\n\t\t// --name\n\t\t{\n\t\t\tName: \"revoke by name success\",\n\t\t\tArgs: \"revoke --name secondary\",\n\t\t\tAPI:  &mock.API{DeleteTokenSelfFn: deleteTokenSelfOK},\n\t\t\tConfigFile: &config.File{\n\t\t\t\tAuth: config.Auth{\n\t\t\t\t\tDefault: \"primary\",\n\t\t\t\t\tTokens: config.AuthTokens{\n\t\t\t\t\t\t\"primary\":   &config.AuthToken{Type: config.AuthTokenTypeStatic, Token: \"tok-primary\"},\n\t\t\t\t\t\t\"secondary\": &config.AuthToken{Type: config.AuthTokenTypeStatic, Token: \"tok-secondary\"},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutputs: []string{`Revoked token \"secondary\"`, `Removed local token entry \"secondary\"`},\n\t\t\tValidator: func(t *testing.T, _ *testutil.CLIScenario, opts *global.Data, _ *threadsafe.Buffer) {\n\t\t\t\tt.Helper()\n\t\t\t\tif opts.Config.GetAuthToken(\"secondary\") != nil {\n\t\t\t\t\tt.Error(\"expected secondary token to be removed\")\n\t\t\t\t}\n\t\t\t\tif opts.Config.GetAuthToken(\"primary\") == nil {\n\t\t\t\t\tt.Error(\"expected primary token to still exist\")\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tName:      \"revoke by name not found\",\n\t\t\tArgs:      \"revoke --name ghost\",\n\t\t\tWantError: `token \"ghost\" not found`,\n\t\t},\n\t\t{\n\t\t\tName: \"revoke by name remote 401 still cleans up locally\",\n\t\t\tArgs: \"revoke --name secondary\",\n\t\t\tAPI:  &mock.API{DeleteTokenSelfFn: deleteTokenSelf401},\n\t\t\tConfigFile: &config.File{\n\t\t\t\tAuth: config.Auth{\n\t\t\t\t\tDefault: \"primary\",\n\t\t\t\t\tTokens: config.AuthTokens{\n\t\t\t\t\t\t\"primary\":   &config.AuthToken{Type: config.AuthTokenTypeStatic, Token: \"tok-primary\"},\n\t\t\t\t\t\t\"secondary\": &config.AuthToken{Type: config.AuthTokenTypeStatic, Token: \"tok-secondary\"},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutputs: []string{\"already revoked\", `Removed local token entry \"secondary\"`},\n\t\t\tValidator: func(t *testing.T, _ *testutil.CLIScenario, opts *global.Data, _ *threadsafe.Buffer) {\n\t\t\t\tt.Helper()\n\t\t\t\tif opts.Config.GetAuthToken(\"secondary\") != nil {\n\t\t\t\t\tt.Error(\"expected secondary token to be removed after 401\")\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tName: \"revoke by name remote 5xx does not clean up locally\",\n\t\t\tArgs: \"revoke --name secondary\",\n\t\t\tAPI:  &mock.API{DeleteTokenSelfFn: deleteTokenSelf500},\n\t\t\tConfigFile: &config.File{\n\t\t\t\tAuth: config.Auth{\n\t\t\t\t\tDefault: \"primary\",\n\t\t\t\t\tTokens: config.AuthTokens{\n\t\t\t\t\t\t\"primary\":   &config.AuthToken{Type: config.AuthTokenTypeStatic, Token: \"tok-primary\"},\n\t\t\t\t\t\t\"secondary\": &config.AuthToken{Type: config.AuthTokenTypeStatic, Token: \"tok-secondary\"},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError:      \"500\",\n\t\t\tDontWantOutput: \"Removed\",\n\t\t\tValidator: func(t *testing.T, _ *testutil.CLIScenario, opts *global.Data, _ *threadsafe.Buffer) {\n\t\t\t\tt.Helper()\n\t\t\t\tif opts.Config.GetAuthToken(\"secondary\") == nil {\n\t\t\t\t\tt.Error(\"expected secondary token to still exist after 5xx\")\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tName: \"revoke by name default token reassigns\",\n\t\t\tArgs: \"revoke --name primary -y\",\n\t\t\tAPI:  &mock.API{DeleteTokenSelfFn: deleteTokenSelfOK},\n\t\t\tConfigFile: &config.File{\n\t\t\t\tAuth: config.Auth{\n\t\t\t\t\tDefault: \"primary\",\n\t\t\t\t\tTokens: config.AuthTokens{\n\t\t\t\t\t\t\"primary\":   &config.AuthToken{Type: config.AuthTokenTypeStatic, Token: \"tok-primary\"},\n\t\t\t\t\t\t\"secondary\": &config.AuthToken{Type: config.AuthTokenTypeStatic, Token: \"tok-secondary\"},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutputs: []string{`Removed local token entry \"primary\"`, \"Default token reassigned\"},\n\t\t\tValidator: func(t *testing.T, _ *testutil.CLIScenario, opts *global.Data, _ *threadsafe.Buffer) {\n\t\t\t\tt.Helper()\n\t\t\t\tif opts.Config.Auth.Default == \"primary\" {\n\t\t\t\t\tt.Error(\"expected default to no longer be primary\")\n\t\t\t\t}\n\t\t\t\tif opts.Config.Auth.Default == \"\" {\n\t\t\t\t\tt.Error(\"expected default to be reassigned\")\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\n\t\t// --token-value\n\t\t{\n\t\t\tName: \"revoke by token value success with local match\",\n\t\t\tArgs: \"revoke --token-value tok-secondary\",\n\t\t\tAPI:  &mock.API{DeleteTokenSelfFn: deleteTokenSelfOK},\n\t\t\tConfigFile: &config.File{\n\t\t\t\tAuth: config.Auth{\n\t\t\t\t\tDefault: \"primary\",\n\t\t\t\t\tTokens: config.AuthTokens{\n\t\t\t\t\t\t\"primary\":   &config.AuthToken{Type: config.AuthTokenTypeStatic, Token: \"tok-primary\"},\n\t\t\t\t\t\t\"secondary\": &config.AuthToken{Type: config.AuthTokenTypeStatic, Token: \"tok-secondary\"},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutputs: []string{\"Revoked token\", `Removed local token entry \"secondary\"`},\n\t\t},\n\t\t{\n\t\t\tName:        \"revoke by token value no local match\",\n\t\t\tArgs:        \"revoke --token-value tok-unknown\",\n\t\t\tAPI:         &mock.API{DeleteTokenSelfFn: deleteTokenSelfOK},\n\t\t\tWantOutputs: []string{\"Revoked token\", \"No matching local token entry found\"},\n\t\t},\n\t\t{\n\t\t\tName: \"revoke by token value from stdin\",\n\t\t\tArgs: \"revoke --token-value=-\",\n\t\t\tAPI:  &mock.API{DeleteTokenSelfFn: deleteTokenSelfOK},\n\t\t\tConfigFile: &config.File{\n\t\t\t\tAuth: config.Auth{\n\t\t\t\t\tDefault: \"other\",\n\t\t\t\t\tTokens: config.AuthTokens{\n\t\t\t\t\t\t\"other\":  &config.AuthToken{Type: config.AuthTokenTypeStatic, Token: \"other-tok\"},\n\t\t\t\t\t\t\"target\": &config.AuthToken{Type: config.AuthTokenTypeStatic, Token: \"tok-from-stdin\"},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tStdin:       []string{\"tok-from-stdin\"},\n\t\t\tWantOutputs: []string{\"Revoked token\", `Removed local token entry \"target\"`},\n\t\t},\n\t\t{\n\t\t\tName:            \"revoke by token value rejects oversized stdin\",\n\t\t\tArgs:            \"revoke --token-value=-\",\n\t\t\tStdin:           []string{strings.Repeat(\"x\", 5000)},\n\t\t\tWantError:       \"exceeds 4096 bytes\",\n\t\t\tWantRemediation: \"single token value\",\n\t\t},\n\t\t{\n\t\t\tName: \"revoke by token value removes duplicate local entries\",\n\t\t\tArgs: \"revoke --token-value shared-tok\",\n\t\t\tAPI:  &mock.API{DeleteTokenSelfFn: deleteTokenSelfOK},\n\t\t\tConfigFile: &config.File{\n\t\t\t\tAuth: config.Auth{\n\t\t\t\t\tDefault: \"alias1\",\n\t\t\t\t\tTokens: config.AuthTokens{\n\t\t\t\t\t\t\"alias1\": &config.AuthToken{Type: config.AuthTokenTypeStatic, Token: \"shared-tok\"},\n\t\t\t\t\t\t\"alias2\": &config.AuthToken{Type: config.AuthTokenTypeStatic, Token: \"shared-tok\"},\n\t\t\t\t\t\t\"other\":  &config.AuthToken{Type: config.AuthTokenTypeStatic, Token: \"other-tok\"},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tStdin: []string{\"y\"},\n\t\t\tValidator: func(t *testing.T, _ *testutil.CLIScenario, opts *global.Data, _ *threadsafe.Buffer) {\n\t\t\t\tt.Helper()\n\t\t\t\tif opts.Config.GetAuthToken(\"alias1\") != nil {\n\t\t\t\t\tt.Error(\"expected alias1 to be removed\")\n\t\t\t\t}\n\t\t\t\tif opts.Config.GetAuthToken(\"alias2\") != nil {\n\t\t\t\t\tt.Error(\"expected alias2 to be removed\")\n\t\t\t\t}\n\t\t\t\tif opts.Config.GetAuthToken(\"other\") == nil {\n\t\t\t\t\tt.Error(\"expected other token to still exist\")\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\n\t\t{\n\t\t\tName: \"revoke by token value confirms when revoking default\",\n\t\t\tArgs: \"revoke --token-value tok-default\",\n\t\t\tAPI:  &mock.API{DeleteTokenSelfFn: deleteTokenSelfOK},\n\t\t\tConfigFile: &config.File{\n\t\t\t\tAuth: config.Auth{\n\t\t\t\t\tDefault: \"mydefault\",\n\t\t\t\t\tTokens: config.AuthTokens{\n\t\t\t\t\t\t\"mydefault\": &config.AuthToken{Type: config.AuthTokenTypeStatic, Token: \"tok-default\"},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tStdin:          []string{\"n\"},\n\t\t\tWantOutput:     \"current default token\",\n\t\t\tDontWantOutput: \"Revoked\",\n\t\t},\n\n\t\t// --id\n\t\t{\n\t\t\tName: \"revoke by ID with local match\",\n\t\t\tArgs: \"revoke --id id-secondary --token 123\",\n\t\t\tAPI:  &mock.API{DeleteTokenFn: deleteTokenOK},\n\t\t\tConfigFile: func() *config.File {\n\t\t\t\tc := *twoTokenConfig\n\t\t\t\tc.Auth.Tokens = make(config.AuthTokens)\n\t\t\t\tfor k, v := range twoTokenConfig.Auth.Tokens {\n\t\t\t\t\tcp := *v\n\t\t\t\t\tc.Auth.Tokens[k] = &cp\n\t\t\t\t}\n\t\t\t\treturn &c\n\t\t\t}(),\n\t\t\tWantOutputs: []string{\"Revoked token 'id-secondary'\", `Removed local token entry \"secondary\"`},\n\t\t},\n\t\t{\n\t\t\tName: \"revoke by ID no local match warns\",\n\t\t\tArgs: \"revoke --id id-unknown --token 123\",\n\t\t\tAPI:  &mock.API{DeleteTokenFn: deleteTokenOK},\n\t\t\tConfigFile: func() *config.File {\n\t\t\t\tc := *twoTokenConfig\n\t\t\t\tc.Auth.Tokens = make(config.AuthTokens)\n\t\t\t\tfor k, v := range twoTokenConfig.Auth.Tokens {\n\t\t\t\t\tcp := *v\n\t\t\t\t\tc.Auth.Tokens[k] = &cp\n\t\t\t\t}\n\t\t\t\treturn &c\n\t\t\t}(),\n\t\t\tWantOutputs: []string{\"Revoked token 'id-unknown'\", \"local cleanup skipped\"},\n\t\t},\n\t\t{\n\t\t\tName: \"revoke by ID API 401 returns error without local cleanup\",\n\t\t\tArgs: \"revoke --id some-id --token 123\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tDeleteTokenFn: func(_ context.Context, _ *fastly.DeleteTokenInput) error {\n\t\t\t\t\treturn &fastly.HTTPError{StatusCode: http.StatusUnauthorized}\n\t\t\t\t},\n\t\t\t},\n\t\t\tConfigFile: &config.File{\n\t\t\t\tAuth: config.Auth{\n\t\t\t\t\tDefault: \"stored\",\n\t\t\t\t\tTokens: config.AuthTokens{\n\t\t\t\t\t\t\"stored\": &config.AuthToken{Type: config.AuthTokenTypeStatic, Token: \"tok\", APITokenID: \"some-id\"},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError:      \"401\",\n\t\t\tDontWantOutput: \"Removed\",\n\t\t\tValidator: func(t *testing.T, _ *testutil.CLIScenario, opts *global.Data, _ *threadsafe.Buffer) {\n\t\t\t\tt.Helper()\n\t\t\t\tif opts.Config.GetAuthToken(\"stored\") == nil {\n\t\t\t\t\tt.Error(\"expected token to still exist after 401 on --id path\")\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tName: \"revoke by ID legacy token without APITokenID\",\n\t\t\tArgs: \"revoke --id id-legacy --token 123\",\n\t\t\tAPI:  &mock.API{DeleteTokenFn: deleteTokenOK},\n\t\t\tConfigFile: &config.File{\n\t\t\t\tAuth: config.Auth{\n\t\t\t\t\tDefault: \"legacy\",\n\t\t\t\t\tTokens: config.AuthTokens{\n\t\t\t\t\t\t\"legacy\": &config.AuthToken{Type: config.AuthTokenTypeStatic, Token: \"tok-legacy\"},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutputs: []string{\"Revoked token 'id-legacy'\", \"local cleanup skipped\"},\n\t\t\tValidator: func(t *testing.T, _ *testutil.CLIScenario, opts *global.Data, _ *threadsafe.Buffer) {\n\t\t\t\tt.Helper()\n\t\t\t\tif opts.Config.GetAuthToken(\"legacy\") == nil {\n\t\t\t\t\tt.Error(\"expected legacy token to still exist (no APITokenID)\")\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\n\t\t// --file\n\t\t{\n\t\t\tName: \"revoke by file success\",\n\t\t\tArgs: fmt.Sprintf(\"revoke --file %s --token 123\", writeTokenIDFile(t, \"id-1\\nid-2\\n\")),\n\t\t\tAPI:  &mock.API{BatchDeleteTokensFn: batchDeleteTokensOK},\n\t\t\tConfigFile: &config.File{\n\t\t\t\tAuth: config.Auth{\n\t\t\t\t\tDefault: \"tok1\",\n\t\t\t\t\tTokens: config.AuthTokens{\n\t\t\t\t\t\t\"tok1\":  &config.AuthToken{Type: config.AuthTokenTypeStatic, Token: \"t1\", APITokenID: \"id-1\"},\n\t\t\t\t\t\t\"tok2\":  &config.AuthToken{Type: config.AuthTokenTypeStatic, Token: \"t2\", APITokenID: \"id-2\"},\n\t\t\t\t\t\t\"other\": &config.AuthToken{Type: config.AuthTokenTypeStatic, Token: \"t3\", APITokenID: \"id-other\"},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutputs: []string{\"Revoked 2 token(s)\", \"Removed local token entry\"},\n\t\t\tValidator: func(t *testing.T, _ *testutil.CLIScenario, opts *global.Data, _ *threadsafe.Buffer) {\n\t\t\t\tt.Helper()\n\t\t\t\tif opts.Config.GetAuthToken(\"tok1\") != nil {\n\t\t\t\t\tt.Error(\"expected tok1 to be removed\")\n\t\t\t\t}\n\t\t\t\tif opts.Config.GetAuthToken(\"tok2\") != nil {\n\t\t\t\t\tt.Error(\"expected tok2 to be removed\")\n\t\t\t\t}\n\t\t\t\tif opts.Config.GetAuthToken(\"other\") == nil {\n\t\t\t\t\tt.Error(\"expected other to still exist\")\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tName:            \"revoke by file unreadable\",\n\t\t\tArgs:            \"revoke --file /nonexistent/path/tokens.txt --token 123\",\n\t\t\tWantError:       \"failed to open\",\n\t\t\tWantRemediation: \"file path and permissions\",\n\t\t},\n\t\t{\n\t\t\tName:            \"revoke by file empty\",\n\t\t\tArgs:            fmt.Sprintf(\"revoke --file %s --token 123\", writeTokenIDFile(t, \"\\n\\n\")),\n\t\t\tWantError:       \"contains no token IDs\",\n\t\t\tWantRemediation: \"one token ID per line\",\n\t\t},\n\n\t\t{\n\t\t\tName:            \"revoke --current with unknown --profile rejected\",\n\t\t\tArgs:            \"revoke --current --profile bogus\",\n\t\t\tWantError:       `profile \"bogus\"`,\n\t\t\tWantRemediation: \"fastly auth\",\n\t\t},\n\t\t{\n\t\t\tName:            \"revoke --name with unknown --profile rejected\",\n\t\t\tArgs:            \"revoke --name secondary --profile bogus\",\n\t\t\tWantError:       `profile \"bogus\"`,\n\t\t\tWantRemediation: \"fastly auth\",\n\t\t},\n\t\t{\n\t\t\tName:            \"revoke --token-value with unknown --profile rejected\",\n\t\t\tArgs:            \"revoke --token-value tok-secondary --profile bogus\",\n\t\t\tWantError:       `profile \"bogus\"`,\n\t\t\tWantRemediation: \"fastly auth\",\n\t\t},\n\t\t{\n\t\t\tName:            \"revoke --id with unknown --profile rejected\",\n\t\t\tArgs:            \"revoke --id some-id --profile bogus\",\n\t\t\tWantError:       `profile \"bogus\"`,\n\t\t\tWantRemediation: \"fastly auth\",\n\t\t},\n\t\t{\n\t\t\tName:            \"revoke --file with unknown --profile rejected\",\n\t\t\tArgs:            fmt.Sprintf(\"revoke --file %s --profile bogus\", writeTokenIDFile(t, \"id-1\\n\")),\n\t\t\tWantError:       `profile \"bogus\"`,\n\t\t\tWantRemediation: \"fastly auth\",\n\t\t},\n\t\t{\n\t\t\tName: \"revoke --current with --token flag wins over unknown --profile\",\n\t\t\tArgs: \"revoke --current --token tok-stored --profile bogus\",\n\t\t\tAPI:  &mock.API{DeleteTokenSelfFn: deleteTokenSelfOK},\n\t\t\tConfigFile: &config.File{\n\t\t\t\tAuth: config.Auth{\n\t\t\t\t\tDefault: \"mytoken\",\n\t\t\t\t\tTokens: config.AuthTokens{\n\t\t\t\t\t\t\"mytoken\": &config.AuthToken{Type: config.AuthTokenTypeStatic, Token: \"tok-stored\"},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tStdin:       []string{\"y\"},\n\t\t\tWantOutputs: []string{\"Revoked current token\"},\n\t\t},\n\n\t\t// API client factory failure\n\t\t{\n\t\t\tName: \"API client factory failure on --name\",\n\t\t\tArgs: \"revoke --name secondary\",\n\t\t\tSetup: func(_ *testing.T, _ *testutil.CLIScenario, opts *global.Data) {\n\t\t\t\topts.APIClientFactory = func(_, _ string, _ bool) (api.Interface, error) {\n\t\t\t\t\treturn nil, fmt.Errorf(\"connection refused\")\n\t\t\t\t}\n\t\t\t},\n\t\t\tConfigFile: &config.File{\n\t\t\t\tAuth: config.Auth{\n\t\t\t\t\tDefault: \"primary\",\n\t\t\t\t\tTokens: config.AuthTokens{\n\t\t\t\t\t\t\"primary\":   &config.AuthToken{Type: config.AuthTokenTypeStatic, Token: \"tok-primary\"},\n\t\t\t\t\t\t\"secondary\": &config.AuthToken{Type: config.AuthTokenTypeStatic, Token: \"tok-secondary\"},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError:       \"connection refused\",\n\t\t\tWantRemediation: \"network connection\",\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{\"auth\"}, scenarios)\n}\n\nfunc writeTokenIDFile(t *testing.T, content string) string {\n\tt.Helper()\n\tdir := t.TempDir()\n\tp := filepath.Join(dir, \"token-ids.txt\")\n\tif err := os.WriteFile(p, []byte(content), 0o600); err != nil {\n\t\tt.Fatal(err)\n\t}\n\treturn p\n}\n"
  },
  {
    "path": "pkg/commands/auth/root.go",
    "content": "package auth\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\ntype RootCommand struct {\n\targparser.Base\n}\n\nconst CommandName = \"auth\"\n\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manage stored Fastly API tokens and token policies\")\n\treturn &c\n}\n\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/auth/show.go",
    "content": "package auth\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"time\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/config\"\n\t\"github.com/fastly/cli/pkg/env\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/lookup\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// ShowCommand shows token details.\ntype ShowCommand struct {\n\targparser.Base\n\tname   string\n\treveal bool\n}\n\nfunc NewShowCommand(parent argparser.Registerer, g *global.Data) *ShowCommand {\n\tvar c ShowCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(\"show\", \"Show details for a stored token\")\n\t// Optional.\n\tc.CmdClause.Arg(\"name\", \"Name of the token to show (defaults to the current token)\").StringVar(&c.name)\n\t// Optional.\n\tc.CmdClause.Flag(\"reveal\", \"Show the full token value (use with care)\").BoolVar(&c.reveal)\n\treturn &c\n}\n\nfunc (c *ShowCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif err := c.Globals.ValidateProfileFlag(); err != nil {\n\t\treturn err\n\t}\n\n\tif c.name == \"\" {\n\t\t_, src := c.Globals.Token()\n\t\tswitch src {\n\t\tcase lookup.SourceUndefined:\n\t\t\treturn fmt.Errorf(\"no token configured; run `fastly auth login` or pass a token name\")\n\t\tcase lookup.SourceFlag, lookup.SourceEnvironment:\n\t\t\treturn fmt.Errorf(\"current token is not stored (provided via --token or %s); use `fastly auth add` or `fastly auth show <name>`\", env.APIToken)\n\t\tcase lookup.SourceFile, lookup.SourceDefault, lookup.SourceAuth:\n\t\t\tc.name = c.Globals.AuthTokenName()\n\t\t\tif c.name == \"\" {\n\t\t\t\tc.name = c.Globals.Config.Auth.Default\n\t\t\t}\n\t\t}\n\t}\n\n\tentry := c.Globals.Config.GetAuthToken(c.name)\n\tif entry == nil {\n\t\treturn fmt.Errorf(\"token %q not found\", c.name)\n\t}\n\n\tisDefault := c.name == c.Globals.Config.Auth.Default\n\tdefaultStr := \"\"\n\tif isDefault {\n\t\tdefaultStr = \" (default)\"\n\t}\n\n\ttext.Output(out, \"Name: %s%s\\n\", c.name, defaultStr)\n\ttext.Output(out, \"Type: %s\\n\", entry.Type)\n\n\tif entry.Email != \"\" {\n\t\ttext.Output(out, \"Email: %s\\n\", entry.Email)\n\t}\n\tif entry.AccountID != \"\" {\n\t\ttext.Output(out, \"Account ID: %s\\n\", entry.AccountID)\n\t}\n\tif entry.Label != \"\" {\n\t\ttext.Output(out, \"Label: %s\\n\", entry.Label)\n\t}\n\tif entry.APITokenName != \"\" {\n\t\ttext.Output(out, \"API token name: %s\\n\", entry.APITokenName)\n\t}\n\tif entry.APITokenScope != \"\" {\n\t\ttext.Output(out, \"API token scope: %s\\n\", entry.APITokenScope)\n\t}\n\tnow := time.Now()\n\tstatus, expires, parseErr := GetExpirationStatus(entry, now)\n\tif parseErr != nil && c.Globals.ErrLog != nil {\n\t\tc.Globals.ErrLog.Add(parseErr)\n\t}\n\n\tif entry.APITokenExpiresAt != \"\" {\n\t\tline := \"API token expires at: \" + entry.APITokenExpiresAt\n\t\tif summary := apiTokenExpirySummary(entry, expires, now); summary != \"\" {\n\t\t\tline += \" (\" + summary + \")\"\n\t\t}\n\t\ttext.Output(out, \"%s\\n\", line)\n\t}\n\tif entry.APITokenID != \"\" {\n\t\ttext.Output(out, \"API token ID: %s\\n\", entry.APITokenID)\n\t}\n\n\t// For SSO tokens, show the session (refresh) expiry as the user-actionable deadline.\n\tif entry.Type == config.AuthTokenTypeSSO && entry.RefreshExpiresAt != \"\" && !entry.NeedsReauth {\n\t\tline := \"SSO session expires at: \" + entry.RefreshExpiresAt\n\t\tif summary := ExpirationSummary(status, expires, now); summary != \"\" {\n\t\t\tline += \" (\" + summary + \")\"\n\t\t}\n\t\ttext.Output(out, \"%s\\n\", line)\n\t}\n\n\tif c.reveal {\n\t\ttext.Output(out, \"Token: %s\\n\", entry.Token)\n\t} else {\n\t\tif len(entry.Token) > 8 {\n\t\t\ttext.Output(out, \"Token: %s...%s\\n\", entry.Token[:4], entry.Token[len(entry.Token)-4:])\n\t\t} else {\n\t\t\ttext.Output(out, \"Token: ****\\n\")\n\t\t}\n\t}\n\n\tif entry.NeedsReauth {\n\t\ttext.Warning(out, \"This token needs re-authentication. %s\\n\", ExpirationRemediation(entry.Type))\n\t} else if status == StatusExpired {\n\t\ttext.Warning(out, \"This token has expired. %s\\n\", ExpirationRemediation(entry.Type))\n\t}\n\n\treturn nil\n}\n\n// apiTokenExpirySummary returns a relative-time string for the APITokenExpiresAt\n// field specifically. For static tokens this uses the main expiry status; for SSO\n// tokens the APITokenExpiresAt is secondary so we parse it independently.\nfunc apiTokenExpirySummary(entry *config.AuthToken, mainExpires time.Time, now time.Time) string {\n\tif entry.Type == config.AuthTokenTypeStatic {\n\t\t// For static tokens, APITokenExpiresAt IS the effective expiry.\n\t\tif mainExpires.IsZero() {\n\t\t\treturn \"\"\n\t\t}\n\t\tif now.After(mainExpires) {\n\t\t\treturn \"expired \" + humanDuration(now.Sub(mainExpires)) + \" ago\"\n\t\t}\n\t\treturn \"in \" + humanDuration(mainExpires.Sub(now))\n\t}\n\n\t// For SSO tokens, parse APITokenExpiresAt independently since the main\n\t// expiry status tracks RefreshExpiresAt.\n\tif entry.APITokenExpiresAt == \"\" {\n\t\treturn \"\"\n\t}\n\tapiExpires, err := time.Parse(time.RFC3339, entry.APITokenExpiresAt)\n\tif err != nil {\n\t\treturn \"\"\n\t}\n\tif now.After(apiExpires) {\n\t\treturn \"expired \" + humanDuration(now.Sub(apiExpires)) + \" ago\"\n\t}\n\treturn \"in \" + humanDuration(apiExpires.Sub(now))\n}\n"
  },
  {
    "path": "pkg/commands/auth/sso.go",
    "content": "package auth\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"github.com/fastly/cli/pkg/api/undocumented\"\n\t\"github.com/fastly/cli/pkg/auth\"\n\t\"github.com/fastly/cli/pkg/config\"\n\t\"github.com/fastly/cli/pkg/env\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n\t\"github.com/fastly/cli/pkg/useragent\"\n)\n\n// RunSSO executes the SSO authentication flow as a plain function.\n// It derives the token name from the current context via identifyTokenName,\n// which preserves naming behavior for re-auth flows (refresh/expired tokens).\nfunc RunSSO(in io.Reader, out io.Writer, g *global.Data, forceReAuth bool, skipPrompt bool) error {\n\treturn RunSSOWithTokenName(in, out, g, forceReAuth, skipPrompt, identifyTokenName(g))\n}\n\n// RunSSOWithTokenName is like RunSSO but accepts an explicit token name\n// instead of deriving it from the current context.\nfunc RunSSOWithTokenName(in io.Reader, out io.Writer, g *global.Data, forceReAuth bool, skipPrompt bool, tokenName string) error {\n\tif forceReAuth {\n\t\tg.AuthServer.SetParam(\"prompt\", \"login select_account\")\n\t} else {\n\t\tif at := g.Config.GetAuthToken(tokenName); at != nil {\n\t\t\tg.AuthServer.SetParam(\"prompt\", \"login\")\n\t\t\tif at.Email != \"\" {\n\t\t\t\tg.AuthServer.SetParam(\"login_hint\", at.Email)\n\t\t\t}\n\t\t\tif at.AccountID != \"\" {\n\t\t\t\tg.AuthServer.SetParam(\"account_hint\", at.AccountID)\n\t\t\t}\n\t\t} else {\n\t\t\tg.AuthServer.SetParam(\"prompt\", \"login select_account\")\n\t\t}\n\t}\n\n\tif !skipPrompt && !g.Flags.AutoYes && !g.Flags.NonInteractive {\n\t\tmsg := fmt.Sprintf(\"We're going to authenticate the '%s' token\", tokenName)\n\t\ttext.Important(out, \"%s. We need to open your browser to authenticate you.\", msg)\n\t\ttext.Break(out)\n\t\tcont, err := text.AskYesNo(out, text.BoldYellow(\"Do you want to continue? [y/N]: \"), in)\n\t\ttext.Break(out)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif !cont {\n\t\t\treturn fsterr.SkipExitError{\n\t\t\t\tSkip: true,\n\t\t\t\tErr:  fsterr.ErrDontContinue,\n\t\t\t}\n\t\t}\n\t}\n\n\tvar serverErr error\n\tgo func() {\n\t\terr := g.AuthServer.Start()\n\t\tif err != nil {\n\t\t\tserverErr = err\n\t\t}\n\t}()\n\tif serverErr != nil {\n\t\treturn serverErr\n\t}\n\n\ttext.Info(out, \"Starting a local server to handle the authentication flow.\")\n\n\tauthorizationURL, err := g.AuthServer.AuthURL()\n\tif err != nil {\n\t\treturn fsterr.RemediationError{\n\t\t\tInner:       fmt.Errorf(\"failed to generate an authorization URL: %w\", err),\n\t\t\tRemediation: auth.Remediation,\n\t\t}\n\t}\n\n\ttext.Break(out)\n\ttext.Description(out, \"We're opening the following URL in your default web browser so you may authenticate with Fastly\", authorizationURL)\n\n\terr = g.Opener(authorizationURL)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to open your default browser: %w\", err)\n\t}\n\n\tar := <-g.AuthServer.GetResult()\n\tif ar.Err != nil || ar.SessionToken == \"\" {\n\t\terr := ar.Err\n\t\tif ar.Err == nil {\n\t\t\terr = errors.New(\"no session token\")\n\t\t}\n\t\treturn fsterr.RemediationError{\n\t\t\tInner:       fmt.Errorf(\"failed to authorize: %w\", err),\n\t\t\tRemediation: auth.Remediation,\n\t\t}\n\t}\n\n\tcustomerID, customerName, err := processCustomer(g, ar)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to use session token to get customer data: %w\", err)\n\t}\n\n\terr = storeAuthToken(g, ar, tokenName, customerID, customerName)\n\tif err != nil {\n\t\tg.ErrLog.Add(err)\n\t\treturn fmt.Errorf(\"failed to store auth token: %w\", err)\n\t}\n\n\tmsg := fmt.Sprintf(\"Session token '%s' has been stored.\", tokenName)\n\tif !env.AuthCommandDisabled() {\n\t\tmsg += \" Use 'fastly auth list' to view tokens.\"\n\t}\n\ttext.Success(out, msg)\n\ttext.Info(out, \"Token saved to %s\", g.ConfigPath)\n\treturn nil\n}\n\n// identifyTokenName determines which auth token name to use for SSO.\nfunc identifyTokenName(g *global.Data) string {\n\tif g.Flags.Token != \"\" {\n\t\treturn g.Flags.Token\n\t}\n\tif g.Manifest != nil && g.Manifest.File.Profile != \"\" {\n\t\treturn g.Manifest.File.Profile\n\t}\n\tif name, _ := g.Config.GetDefaultAuthToken(); name != \"\" {\n\t\treturn name\n\t}\n\treturn \"default\"\n}\n\n// CurrentCustomerResponse models the Fastly API response for the\n// /current_customer endpoint.\ntype CurrentCustomerResponse struct {\n\tID   string `json:\"id\"`\n\tName string `json:\"name\"`\n}\n\nfunc processCustomer(g *global.Data, ar auth.AuthorizationResult) (customerID, customerName string, err error) {\n\tdebugMode, _ := strconv.ParseBool(g.Env.DebugMode)\n\tapiEndpoint, _ := g.APIEndpoint()\n\tdata, err := undocumented.Call(undocumented.CallOptions{\n\t\tAPIEndpoint: apiEndpoint,\n\t\tHTTPClient:  g.HTTPClient,\n\t\tHTTPHeaders: []undocumented.HTTPHeader{\n\t\t\t{\n\t\t\t\tKey:   \"Accept\",\n\t\t\t\tValue: \"application/json\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tKey:   \"User-Agent\",\n\t\t\t\tValue: useragent.Name,\n\t\t\t},\n\t\t},\n\t\tMethod: http.MethodGet,\n\t\tPath:   \"/current_customer\",\n\t\tToken:  ar.SessionToken,\n\t\tDebug:  debugMode,\n\t})\n\tif err != nil {\n\t\tg.ErrLog.Add(err)\n\t\treturn \"\", \"\", fmt.Errorf(\"error executing current_customer API request: %w\", err)\n\t}\n\n\tvar response CurrentCustomerResponse\n\tif err := json.Unmarshal(data, &response); err != nil {\n\t\tg.ErrLog.Add(err)\n\t\treturn \"\", \"\", fmt.Errorf(\"error decoding current_customer API response: %w\", err)\n\t}\n\n\treturn response.ID, response.Name, nil\n}\n\nfunc storeAuthToken(g *global.Data, ar auth.AuthorizationResult, tokenName, customerID, customerName string) error {\n\tnow := time.Now()\n\tlabel := customerName\n\tif ar.Email != \"\" {\n\t\tlabel = fmt.Sprintf(\"%s (%s)\", customerName, ar.Email)\n\t}\n\n\tat := &config.AuthToken{\n\t\tType:             config.AuthTokenTypeSSO,\n\t\tToken:            ar.SessionToken,\n\t\tLabel:            label,\n\t\tAccountID:        customerID,\n\t\tEmail:            ar.Email,\n\t\tAccessToken:      ar.Jwt.AccessToken,\n\t\tRefreshToken:     ar.Jwt.RefreshToken,\n\t\tAccessExpiresAt:  now.Add(time.Duration(ar.Jwt.ExpiresIn) * time.Second).Format(time.RFC3339),\n\t\tRefreshExpiresAt: now.Add(time.Duration(ar.Jwt.RefreshExpiresIn) * time.Second).Format(time.RFC3339),\n\t}\n\n\tEnrichWithTokenSelf(g, at)\n\n\tg.Config.SetAuthToken(tokenName, at)\n\n\tif g.Config.Auth.Default == \"\" {\n\t\tg.Config.Auth.Default = tokenName\n\t}\n\n\tif err := g.Config.Write(g.ConfigPath); err != nil {\n\t\treturn fmt.Errorf(\"failed to update config file: %w\", err)\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/auth/sso_test.go",
    "content": "package auth_test\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/auth\"\n\tauthcmd \"github.com/fastly/cli/pkg/commands/auth\"\n\t\"github.com/fastly/cli/pkg/config\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/mock\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n\t\"github.com/fastly/cli/pkg/threadsafe\"\n)\n\nfunc TestSSO(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t// 0. User cancels authentication prompt\n\t\t{\n\t\t\tArgs: \"auth login --sso --token testname\",\n\t\t\tStdin: []string{\n\t\t\t\t\"N\", // when prompted to open a web browser to start authentication\n\t\t\t},\n\t\t\tWantError: \"will not continue\",\n\t\t},\n\t\t// 1. Error opening web browser\n\t\t{\n\t\t\tArgs: \"auth login --sso --token testname\",\n\t\t\tStdin: []string{\n\t\t\t\t\"Y\", // when prompted to open a web browser to start authentication\n\t\t\t},\n\t\t\tSetup: func(_ *testing.T, _ *testutil.CLIScenario, opts *global.Data) {\n\t\t\t\topts.Opener = func(_ string) error {\n\t\t\t\t\treturn errors.New(\"failed to open web browser\")\n\t\t\t\t}\n\t\t\t},\n\t\t\tWantError: \"failed to open web browser\",\n\t\t},\n\t\t// 2. Error processing OAuth flow (error encountered)\n\t\t{\n\t\t\tArgs: \"auth login --sso --token testname\",\n\t\t\tStdin: []string{\n\t\t\t\t\"Y\", // when prompted to open a web browser to start authentication\n\t\t\t},\n\t\t\tSetup: func(_ *testing.T, _ *testutil.CLIScenario, opts *global.Data) {\n\t\t\t\tresult := make(chan auth.AuthorizationResult)\n\t\t\t\topts.AuthServer = testutil.MockAuthServer{\n\t\t\t\t\tResult: result,\n\t\t\t\t}\n\t\t\t\tgo func() {\n\t\t\t\t\tresult <- auth.AuthorizationResult{\n\t\t\t\t\t\tErr: errors.New(\"no authorization code returned\"),\n\t\t\t\t\t}\n\t\t\t\t}()\n\t\t\t},\n\t\t\tWantError: \"failed to authorize: no authorization code returned\",\n\t\t},\n\t\t// 3. Error processing OAuth flow (empty SessionToken field)\n\t\t{\n\t\t\tArgs: \"auth login --sso --token testname\",\n\t\t\tStdin: []string{\n\t\t\t\t\"Y\", // when prompted to open a web browser to start authentication\n\t\t\t},\n\t\t\tSetup: func(_ *testing.T, _ *testutil.CLIScenario, opts *global.Data) {\n\t\t\t\tresult := make(chan auth.AuthorizationResult)\n\t\t\t\topts.AuthServer = testutil.MockAuthServer{\n\t\t\t\t\tResult: result,\n\t\t\t\t}\n\t\t\t\tgo func() {\n\t\t\t\t\tresult <- auth.AuthorizationResult{\n\t\t\t\t\t\tSessionToken: \"\",\n\t\t\t\t\t}\n\t\t\t\t}()\n\t\t\t},\n\t\t\tWantError: \"failed to authorize: no session token\",\n\t\t},\n\t\t// 4. auth login --sso without --token fails with remediation error\n\t\t{\n\t\t\tArgs:            \"auth login --sso\",\n\t\t\tWantError:       \"SSO login requires a token name via --token\",\n\t\t\tWantRemediation: \"Provide a name for the stored token, e.g.: fastly auth login --sso --token work-sso\",\n\t\t},\n\t\t// 5. Success processing OAuth flow targeting specific auth token via --token flag\n\t\t{\n\t\t\tArgs: \"auth login --sso --token test_user\",\n\t\t\tConfigFile: &config.File{\n\t\t\t\tAuth: config.Auth{\n\t\t\t\t\tDefault: \"test_user\",\n\t\t\t\t\tTokens: config.AuthTokens{\n\t\t\t\t\t\t\"test_user\": &config.AuthToken{\n\t\t\t\t\t\t\tType:  config.AuthTokenTypeStatic,\n\t\t\t\t\t\t\tToken: \"old-token\",\n\t\t\t\t\t\t\tEmail: \"test@example.com\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tStdin: []string{\n\t\t\t\t\"Y\", // when prompted to open a web browser to start authentication\n\t\t\t},\n\t\t\tSetup: func(_ *testing.T, _ *testutil.CLIScenario, opts *global.Data) {\n\t\t\t\tresult := make(chan auth.AuthorizationResult)\n\t\t\t\topts.AuthServer = testutil.MockAuthServer{\n\t\t\t\t\tResult: result,\n\t\t\t\t}\n\t\t\t\tgo func() {\n\t\t\t\t\tresult <- auth.AuthorizationResult{\n\t\t\t\t\t\tSessionToken: \"123\",\n\t\t\t\t\t}\n\t\t\t\t}()\n\t\t\t\topts.HTTPClient = testutil.CurrentCustomerClient(testutil.CurrentCustomerResponse)\n\t\t\t},\n\t\t\tWantOutputs: []string{\n\t\t\t\t\"We're going to authenticate the 'test_user' token\",\n\t\t\t\t\"We need to open your browser to authenticate you.\",\n\t\t\t\t\"has been stored. Use 'fastly auth list' to view tokens.\",\n\t\t\t\t\"Token saved to\",\n\t\t\t},\n\t\t\tValidator: func(t *testing.T, _ *testutil.CLIScenario, opts *global.Data, _ *threadsafe.Buffer) {\n\t\t\t\tconst expectedToken = \"123\"\n\t\t\t\tat := opts.Config.GetAuthToken(\"test_user\")\n\t\t\t\tif at == nil {\n\t\t\t\t\tt.Fatal(\"expected auth token 'test_user' to exist\")\n\t\t\t\t}\n\t\t\t\tif at.Token != expectedToken {\n\t\t\t\t\tt.Errorf(\"want token: %s, got token: %s\", expectedToken, at.Token)\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t// 6. Success processing `pops` command with a static (non-SSO) auth token.\n\t\t{\n\t\t\tArgs: \"pops\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tAllDatacentersFn: func(_ context.Context) ([]fastly.Datacenter, error) {\n\t\t\t\t\treturn []fastly.Datacenter{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName:   fastly.ToPointer(\"Foobar\"),\n\t\t\t\t\t\t\tCode:   fastly.ToPointer(\"FBR\"),\n\t\t\t\t\t\t\tGroup:  fastly.ToPointer(\"Bar\"),\n\t\t\t\t\t\t\tShield: fastly.ToPointer(\"Baz\"),\n\t\t\t\t\t\t\tCoordinates: &fastly.Coordinates{\n\t\t\t\t\t\t\t\tLatitude:  fastly.ToPointer(float64(1)),\n\t\t\t\t\t\t\t\tLongitude: fastly.ToPointer(float64(2)),\n\t\t\t\t\t\t\t\tX:         fastly.ToPointer(float64(3)),\n\t\t\t\t\t\t\t\tY:         fastly.ToPointer(float64(4)),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tConfigFile: &config.File{\n\t\t\t\tAuth: config.Auth{\n\t\t\t\t\tDefault: \"user\",\n\t\t\t\t\tTokens: config.AuthTokens{\n\t\t\t\t\t\t\"user\": &config.AuthToken{\n\t\t\t\t\t\t\tType:  config.AuthTokenTypeStatic,\n\t\t\t\t\t\t\tToken: \"mock-token\",\n\t\t\t\t\t\t\tEmail: \"test@example.com\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tSetup: func(_ *testing.T, _ *testutil.CLIScenario, opts *global.Data) {\n\t\t\t\topts.HTTPClient = testutil.CurrentCustomerClient(testutil.CurrentCustomerResponse)\n\t\t\t},\n\t\t\tWantOutputs: []string{\n\t\t\t\t\"{Latitude:1 Longitude:2 X:3 Y:4}\",\n\t\t\t},\n\t\t\tValidator: func(t *testing.T, _ *testutil.CLIScenario, opts *global.Data, _ *threadsafe.Buffer) {\n\t\t\t\tconst expectedToken = \"mock-token\"\n\t\t\t\tat := opts.Config.GetAuthToken(\"user\")\n\t\t\t\tif at == nil {\n\t\t\t\t\tt.Fatal(\"expected auth token 'user' to exist\")\n\t\t\t\t}\n\t\t\t\tif at.Token != expectedToken {\n\t\t\t\t\tt.Errorf(\"want token: %s, got token: %s\", expectedToken, at.Token)\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t// 7. SSO token with both access and refresh expired -> triggers re-auth prompt.\n\t\t// The user declines, so the command does not execute.\n\t\t{\n\t\t\tArgs: \"whoami\",\n\t\t\tConfigFile: &config.File{\n\t\t\t\tAuth: config.Auth{\n\t\t\t\t\tDefault: \"user\",\n\t\t\t\t\tTokens: config.AuthTokens{\n\t\t\t\t\t\t\"user\": &config.AuthToken{\n\t\t\t\t\t\t\tType:             config.AuthTokenTypeSSO,\n\t\t\t\t\t\t\tToken:            \"mock-token\",\n\t\t\t\t\t\t\tEmail:            \"test@example.com\",\n\t\t\t\t\t\t\tRefreshToken:     \"mock-refresh\",\n\t\t\t\t\t\t\tAccessExpiresAt:  time.Now().Add(-10 * time.Minute).Format(time.RFC3339),\n\t\t\t\t\t\t\tRefreshExpiresAt: time.Now().Add(-5 * time.Minute).Format(time.RFC3339),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tStdin: []string{\n\t\t\t\t\"N\", // decline re-authentication\n\t\t\t},\n\t\t\tSetup: func(_ *testing.T, _ *testutil.CLIScenario, opts *global.Data) {\n\t\t\t\topts.HTTPClient = testutil.CurrentCustomerClient(testutil.CurrentCustomerResponse)\n\t\t\t},\n\t\t\tWantOutput:     \"Your auth token has expired and needs re-authentication\",\n\t\t\tDontWantOutput: \"{Latitude:1 Longitude:2 X:3 Y:4}\",\n\t\t},\n\t\t// 8. SSO token with both access and refresh expired -> user accepts re-auth.\n\t\t// The SSO flow succeeds and the command executes afterward.\n\t\t{\n\t\t\tArgs: \"pops\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tAllDatacentersFn: func(_ context.Context) ([]fastly.Datacenter, error) {\n\t\t\t\t\treturn []fastly.Datacenter{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName:   fastly.ToPointer(\"Foobar\"),\n\t\t\t\t\t\t\tCode:   fastly.ToPointer(\"FBR\"),\n\t\t\t\t\t\t\tGroup:  fastly.ToPointer(\"Bar\"),\n\t\t\t\t\t\t\tShield: fastly.ToPointer(\"Baz\"),\n\t\t\t\t\t\t\tCoordinates: &fastly.Coordinates{\n\t\t\t\t\t\t\t\tLatitude:  fastly.ToPointer(float64(1)),\n\t\t\t\t\t\t\t\tLongitude: fastly.ToPointer(float64(2)),\n\t\t\t\t\t\t\t\tX:         fastly.ToPointer(float64(3)),\n\t\t\t\t\t\t\t\tY:         fastly.ToPointer(float64(4)),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tConfigFile: &config.File{\n\t\t\t\tAuth: config.Auth{\n\t\t\t\t\tDefault: \"user\",\n\t\t\t\t\tTokens: config.AuthTokens{\n\t\t\t\t\t\t\"user\": &config.AuthToken{\n\t\t\t\t\t\t\tType:             config.AuthTokenTypeSSO,\n\t\t\t\t\t\t\tToken:            \"mock-token\",\n\t\t\t\t\t\t\tEmail:            \"test@example.com\",\n\t\t\t\t\t\t\tRefreshToken:     \"mock-refresh\",\n\t\t\t\t\t\t\tAccessExpiresAt:  time.Now().Add(-10 * time.Minute).Format(time.RFC3339),\n\t\t\t\t\t\t\tRefreshExpiresAt: time.Now().Add(-5 * time.Minute).Format(time.RFC3339),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tStdin: []string{\n\t\t\t\t\"Y\", // accept re-authentication\n\t\t\t},\n\t\t\tSetup: func(_ *testing.T, _ *testutil.CLIScenario, opts *global.Data) {\n\t\t\t\tresult := make(chan auth.AuthorizationResult)\n\t\t\t\topts.AuthServer = testutil.MockAuthServer{\n\t\t\t\t\tResult: result,\n\t\t\t\t}\n\t\t\t\tgo func() {\n\t\t\t\t\tresult <- auth.AuthorizationResult{\n\t\t\t\t\t\tSessionToken: \"new-123\",\n\t\t\t\t\t}\n\t\t\t\t}()\n\t\t\t\topts.HTTPClient = testutil.CurrentCustomerClient(testutil.CurrentCustomerResponse)\n\t\t\t},\n\t\t\tWantOutputs: []string{\n\t\t\t\t\"Your auth token has expired and needs re-authentication\",\n\t\t\t\t\"Starting a local server to handle the authentication flow.\",\n\t\t\t\t\"has been stored. Use 'fastly auth list' to view tokens.\",\n\t\t\t\t\"Token saved to\",\n\t\t\t\t\"{Latitude:1 Longitude:2 X:3 Y:4}\",\n\t\t\t},\n\t\t\tValidator: func(t *testing.T, _ *testutil.CLIScenario, opts *global.Data, _ *threadsafe.Buffer) {\n\t\t\t\tconst expectedToken = \"new-123\"\n\t\t\t\tat := opts.Config.GetAuthToken(\"user\")\n\t\t\t\tif at == nil {\n\t\t\t\t\tt.Fatal(\"expected auth token 'user' to exist\")\n\t\t\t\t}\n\t\t\t\tif at.Token != expectedToken {\n\t\t\t\t\tt.Errorf(\"want token: %s, got token: %s\", expectedToken, at.Token)\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t// 9. Migration before token resolution: legacy profiles are migrated to\n\t\t// [auth] before processToken() runs, so the token resolves correctly.\n\t\t{\n\t\t\tArgs: \"pops\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tAllDatacentersFn: func(_ context.Context) ([]fastly.Datacenter, error) {\n\t\t\t\t\treturn []fastly.Datacenter{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName:   fastly.ToPointer(\"Foobar\"),\n\t\t\t\t\t\t\tCode:   fastly.ToPointer(\"FBR\"),\n\t\t\t\t\t\t\tGroup:  fastly.ToPointer(\"Bar\"),\n\t\t\t\t\t\t\tShield: fastly.ToPointer(\"Baz\"),\n\t\t\t\t\t\t\tCoordinates: &fastly.Coordinates{\n\t\t\t\t\t\t\t\tLatitude:  fastly.ToPointer(float64(1)),\n\t\t\t\t\t\t\t\tLongitude: fastly.ToPointer(float64(2)),\n\t\t\t\t\t\t\t\tX:         fastly.ToPointer(float64(3)),\n\t\t\t\t\t\t\t\tY:         fastly.ToPointer(float64(4)),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tConfigFile: &config.File{\n\t\t\t\tProfiles: config.Profiles{\n\t\t\t\t\t\"legacy\": &config.Profile{\n\t\t\t\t\t\tDefault: true,\n\t\t\t\t\t\tEmail:   \"legacy@example.com\",\n\t\t\t\t\t\tToken:   \"legacy-token\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tSetup: func(_ *testing.T, _ *testutil.CLIScenario, opts *global.Data) {\n\t\t\t\topts.HTTPClient = testutil.CurrentCustomerClient(testutil.CurrentCustomerResponse)\n\t\t\t},\n\t\t\tWantOutputs: []string{\n\t\t\t\t\"{Latitude:1 Longitude:2 X:3 Y:4}\",\n\t\t\t},\n\t\t\tValidator: func(t *testing.T, _ *testutil.CLIScenario, opts *global.Data, _ *threadsafe.Buffer) {\n\t\t\t\tat := opts.Config.GetAuthToken(\"legacy\")\n\t\t\t\tif at == nil {\n\t\t\t\t\tt.Fatal(\"expected migrated auth token 'legacy' to exist\")\n\t\t\t\t}\n\t\t\t\tif at.Token != \"legacy-token\" {\n\t\t\t\t\tt.Errorf(\"want token: legacy-token, got token: %s\", at.Token)\n\t\t\t\t}\n\t\t\t\tif opts.Config.Auth.Default != \"legacy\" {\n\t\t\t\t\tt.Errorf(\"want default auth: legacy, got: %s\", opts.Config.Auth.Default)\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t// 10. Mixed config: both [auth] and [profile] present.\n\t\t// Profile-only entries must be merged into [auth], not dropped.\n\t\t{\n\t\t\tArgs: \"pops\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tAllDatacentersFn: func(_ context.Context) ([]fastly.Datacenter, error) {\n\t\t\t\t\treturn []fastly.Datacenter{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName:   fastly.ToPointer(\"Foobar\"),\n\t\t\t\t\t\t\tCode:   fastly.ToPointer(\"FBR\"),\n\t\t\t\t\t\t\tGroup:  fastly.ToPointer(\"Bar\"),\n\t\t\t\t\t\t\tShield: fastly.ToPointer(\"Baz\"),\n\t\t\t\t\t\t\tCoordinates: &fastly.Coordinates{\n\t\t\t\t\t\t\t\tLatitude:  fastly.ToPointer(float64(1)),\n\t\t\t\t\t\t\t\tLongitude: fastly.ToPointer(float64(2)),\n\t\t\t\t\t\t\t\tX:         fastly.ToPointer(float64(3)),\n\t\t\t\t\t\t\t\tY:         fastly.ToPointer(float64(4)),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tConfigFile: &config.File{\n\t\t\t\tProfiles: config.Profiles{\n\t\t\t\t\t\"profile-only\": &config.Profile{\n\t\t\t\t\t\tEmail: \"profile@example.com\",\n\t\t\t\t\t\tToken: \"profile-token\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tAuth: config.Auth{\n\t\t\t\t\tDefault: \"existing\",\n\t\t\t\t\tTokens: config.AuthTokens{\n\t\t\t\t\t\t\"existing\": &config.AuthToken{\n\t\t\t\t\t\t\tType:  config.AuthTokenTypeStatic,\n\t\t\t\t\t\t\tToken: \"existing-token\",\n\t\t\t\t\t\t\tEmail: \"existing@example.com\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tSetup: func(_ *testing.T, _ *testutil.CLIScenario, opts *global.Data) {\n\t\t\t\topts.HTTPClient = testutil.CurrentCustomerClient(testutil.CurrentCustomerResponse)\n\t\t\t},\n\t\t\tWantOutputs: []string{\n\t\t\t\t\"{Latitude:1 Longitude:2 X:3 Y:4}\",\n\t\t\t},\n\t\t\tValidator: func(t *testing.T, _ *testutil.CLIScenario, opts *global.Data, _ *threadsafe.Buffer) {\n\t\t\t\t// The existing auth token must be preserved.\n\t\t\t\tat := opts.Config.GetAuthToken(\"existing\")\n\t\t\t\tif at == nil {\n\t\t\t\t\tt.Fatal(\"expected 'existing' auth token to be preserved\")\n\t\t\t\t}\n\t\t\t\tif at.Token != \"existing-token\" {\n\t\t\t\t\tt.Errorf(\"want token: existing-token, got: %s\", at.Token)\n\t\t\t\t}\n\t\t\t\t// The profile-only entry must be merged in.\n\t\t\t\tmerged := opts.Config.GetAuthToken(\"profile-only\")\n\t\t\t\tif merged == nil {\n\t\t\t\t\tt.Fatal(\"expected 'profile-only' profile to be merged into auth tokens\")\n\t\t\t\t}\n\t\t\t\tif merged.Token != \"profile-token\" {\n\t\t\t\t\tt.Errorf(\"want token: profile-token, got: %s\", merged.Token)\n\t\t\t\t}\n\t\t\t\t// Default must remain unchanged.\n\t\t\t\tif opts.Config.Auth.Default != \"existing\" {\n\t\t\t\t\tt.Errorf(\"want default: existing, got: %s\", opts.Config.Auth.Default)\n\t\t\t\t}\n\t\t\t\t// Profiles must be cleared.\n\t\t\t\tif len(opts.Config.Profiles) > 0 {\n\t\t\t\t\tt.Errorf(\"expected Profiles to be cleared, got %d entries\", len(opts.Config.Profiles))\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t// 11. auth login --sso --token newname creates a new token that doesn't exist yet.\n\t\t{\n\t\t\tArgs: \"auth login --sso --token brandnew\",\n\t\t\tConfigFile: &config.File{\n\t\t\t\tAuth: config.Auth{\n\t\t\t\t\tDefault: \"existing\",\n\t\t\t\t\tTokens: config.AuthTokens{\n\t\t\t\t\t\t\"existing\": &config.AuthToken{\n\t\t\t\t\t\t\tType:  config.AuthTokenTypeStatic,\n\t\t\t\t\t\t\tToken: \"existing-token\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tStdin: []string{\n\t\t\t\t\"Y\", // when prompted to open a web browser to start authentication\n\t\t\t},\n\t\t\tSetup: func(_ *testing.T, _ *testutil.CLIScenario, opts *global.Data) {\n\t\t\t\tresult := make(chan auth.AuthorizationResult)\n\t\t\t\topts.AuthServer = testutil.MockAuthServer{\n\t\t\t\t\tResult: result,\n\t\t\t\t}\n\t\t\t\tgo func() {\n\t\t\t\t\tresult <- auth.AuthorizationResult{\n\t\t\t\t\t\tSessionToken: \"brand-new-token\",\n\t\t\t\t\t}\n\t\t\t\t}()\n\t\t\t\topts.HTTPClient = testutil.CurrentCustomerClient(testutil.CurrentCustomerResponse)\n\t\t\t},\n\t\t\tWantOutputs: []string{\n\t\t\t\t\"We're going to authenticate the 'brandnew' token\",\n\t\t\t\t\"Session token 'brandnew' has been stored.\",\n\t\t\t\t\"Token saved to\",\n\t\t\t},\n\t\t\tValidator: func(t *testing.T, _ *testutil.CLIScenario, opts *global.Data, _ *threadsafe.Buffer) {\n\t\t\t\tat := opts.Config.GetAuthToken(\"brandnew\")\n\t\t\t\tif at == nil {\n\t\t\t\t\tt.Fatal(\"expected auth token 'brandnew' to exist\")\n\t\t\t\t}\n\t\t\t\tif at.Token != \"brand-new-token\" {\n\t\t\t\t\tt.Errorf(\"want token: brand-new-token, got token: %s\", at.Token)\n\t\t\t\t}\n\t\t\t\tif opts.Config.Auth.Default != \"brandnew\" {\n\t\t\t\t\tt.Errorf(\"want default auth: brandnew, got: %s\", opts.Config.Auth.Default)\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t// 12. Missing manifest profile emits a single warning.\n\t\t{\n\t\t\tArgs: \"pops\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tAllDatacentersFn: func(_ context.Context) ([]fastly.Datacenter, error) {\n\t\t\t\t\treturn []fastly.Datacenter{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName:   fastly.ToPointer(\"Foobar\"),\n\t\t\t\t\t\t\tCode:   fastly.ToPointer(\"FBR\"),\n\t\t\t\t\t\t\tGroup:  fastly.ToPointer(\"Bar\"),\n\t\t\t\t\t\t\tShield: fastly.ToPointer(\"Baz\"),\n\t\t\t\t\t\t\tCoordinates: &fastly.Coordinates{\n\t\t\t\t\t\t\t\tLatitude:  fastly.ToPointer(float64(1)),\n\t\t\t\t\t\t\t\tLongitude: fastly.ToPointer(float64(2)),\n\t\t\t\t\t\t\t\tX:         fastly.ToPointer(float64(3)),\n\t\t\t\t\t\t\t\tY:         fastly.ToPointer(float64(4)),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tConfigFile: &config.File{\n\t\t\t\tAuth: config.Auth{\n\t\t\t\t\tDefault: \"user\",\n\t\t\t\t\tTokens: config.AuthTokens{\n\t\t\t\t\t\t\"user\": &config.AuthToken{\n\t\t\t\t\t\t\tType:  config.AuthTokenTypeStatic,\n\t\t\t\t\t\t\tToken: \"mock-token\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tSetup: func(_ *testing.T, _ *testutil.CLIScenario, opts *global.Data) {\n\t\t\t\topts.Manifest.File.Profile = \"nonexistent\"\n\t\t\t\topts.HTTPClient = testutil.CurrentCustomerClient(testutil.CurrentCustomerResponse)\n\t\t\t},\n\t\t\tWantOutputs: []string{\n\t\t\t\t`profile \"nonexistent\" not found in auth config, using default token \"user\"`,\n\t\t\t\t\"{Latitude:1 Longitude:2 X:3 Y:4}\",\n\t\t\t},\n\t\t},\n\t\t// 13. Missing manifest profile warning is suppressed under --quiet.\n\t\t{\n\t\t\tArgs: \"pops --quiet\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tAllDatacentersFn: func(_ context.Context) ([]fastly.Datacenter, error) {\n\t\t\t\t\treturn []fastly.Datacenter{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName:   fastly.ToPointer(\"Foobar\"),\n\t\t\t\t\t\t\tCode:   fastly.ToPointer(\"FBR\"),\n\t\t\t\t\t\t\tGroup:  fastly.ToPointer(\"Bar\"),\n\t\t\t\t\t\t\tShield: fastly.ToPointer(\"Baz\"),\n\t\t\t\t\t\t\tCoordinates: &fastly.Coordinates{\n\t\t\t\t\t\t\t\tLatitude:  fastly.ToPointer(float64(1)),\n\t\t\t\t\t\t\t\tLongitude: fastly.ToPointer(float64(2)),\n\t\t\t\t\t\t\t\tX:         fastly.ToPointer(float64(3)),\n\t\t\t\t\t\t\t\tY:         fastly.ToPointer(float64(4)),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tConfigFile: &config.File{\n\t\t\t\tAuth: config.Auth{\n\t\t\t\t\tDefault: \"user\",\n\t\t\t\t\tTokens: config.AuthTokens{\n\t\t\t\t\t\t\"user\": &config.AuthToken{\n\t\t\t\t\t\t\tType:  config.AuthTokenTypeStatic,\n\t\t\t\t\t\t\tToken: \"mock-token\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tSetup: func(_ *testing.T, _ *testutil.CLIScenario, opts *global.Data) {\n\t\t\t\topts.Manifest.File.Profile = \"nonexistent\"\n\t\t\t\topts.HTTPClient = testutil.CurrentCustomerClient(testutil.CurrentCustomerResponse)\n\t\t\t},\n\t\t\tDontWantOutput: \"not found in auth config\",\n\t\t},\n\t\t// 14. Missing manifest profile warning suppressed when --token overrides.\n\t\t{\n\t\t\tArgs: \"pops --token override-token\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tAllDatacentersFn: func(_ context.Context) ([]fastly.Datacenter, error) {\n\t\t\t\t\treturn []fastly.Datacenter{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName:   fastly.ToPointer(\"Foobar\"),\n\t\t\t\t\t\t\tCode:   fastly.ToPointer(\"FBR\"),\n\t\t\t\t\t\t\tGroup:  fastly.ToPointer(\"Bar\"),\n\t\t\t\t\t\t\tShield: fastly.ToPointer(\"Baz\"),\n\t\t\t\t\t\t\tCoordinates: &fastly.Coordinates{\n\t\t\t\t\t\t\t\tLatitude:  fastly.ToPointer(float64(1)),\n\t\t\t\t\t\t\t\tLongitude: fastly.ToPointer(float64(2)),\n\t\t\t\t\t\t\t\tX:         fastly.ToPointer(float64(3)),\n\t\t\t\t\t\t\t\tY:         fastly.ToPointer(float64(4)),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tConfigFile: &config.File{\n\t\t\t\tAuth: config.Auth{\n\t\t\t\t\tDefault: \"user\",\n\t\t\t\t\tTokens: config.AuthTokens{\n\t\t\t\t\t\t\"user\": &config.AuthToken{\n\t\t\t\t\t\t\tType:  config.AuthTokenTypeStatic,\n\t\t\t\t\t\t\tToken: \"mock-token\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tSetup: func(_ *testing.T, _ *testutil.CLIScenario, opts *global.Data) {\n\t\t\t\topts.Manifest.File.Profile = \"nonexistent\"\n\t\t\t\topts.HTTPClient = testutil.CurrentCustomerClient(testutil.CurrentCustomerResponse)\n\t\t\t},\n\t\t\tDontWantOutput: \"not found in auth config\",\n\t\t},\n\t\t// 15. Auto-prompt: directly prompts for a static API token.\n\t\t{\n\t\t\tName: \"auto-prompt static token\",\n\t\t\tArgs: \"whoami\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetCurrentUserFn: func(_ context.Context) (*fastly.User, error) {\n\t\t\t\t\treturn &fastly.User{\n\t\t\t\t\t\tLogin:      fastly.ToPointer(\"alice@example.com\"),\n\t\t\t\t\t\tCustomerID: fastly.ToPointer(\"abc123\"),\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t\tGetTokenSelfFn: testTokenSelfFull,\n\t\t\t},\n\t\t\tConfigFile: &config.File{},\n\t\t\tStdin: []string{\n\t\t\t\t\"my-static-token\",\n\t\t\t},\n\t\t\tSetup: func(_ *testing.T, _ *testutil.CLIScenario, opts *global.Data) {\n\t\t\t\topts.HTTPClient = testutil.WhoamiVerifyClient(testutil.WhoamiBasicResponse)\n\t\t\t},\n\t\t\tWantOutputs: []string{\n\t\t\t\t\"This command requires authentication to access your Fastly account.\",\n\t\t\t\t\"Paste your API token\",\n\t\t\t\t`Authenticated as alice@example.com (token stored as \"my-api-token\")`,\n\t\t\t\t\"Token saved to\",\n\t\t\t},\n\t\t\tDontWantOutput: \"Log in with browser\",\n\t\t\tValidator: func(t *testing.T, _ *testutil.CLIScenario, opts *global.Data, _ *threadsafe.Buffer) {\n\t\t\t\tat := opts.Config.GetAuthToken(\"my-api-token\")\n\t\t\t\tif at == nil {\n\t\t\t\t\tt.Fatal(\"expected auth token 'my-api-token' to exist\")\n\t\t\t\t}\n\t\t\t\tif at.Token != \"my-static-token\" {\n\t\t\t\t\tt.Errorf(\"want token: my-static-token, got token: %s\", at.Token)\n\t\t\t\t}\n\t\t\t\tif at.Type != config.AuthTokenTypeStatic {\n\t\t\t\t\tt.Errorf(\"want type: static, got type: %s\", at.Type)\n\t\t\t\t}\n\t\t\t\tif opts.Config.Auth.Default != \"my-api-token\" {\n\t\t\t\t\tt.Errorf(\"want Auth.Default my-api-token, got %s\", opts.Config.Auth.Default)\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t// 16. Non-interactive: returns ErrNonInteractiveNoToken without prompting.\n\t\t{\n\t\t\tName:            \"non-interactive no prompt\",\n\t\t\tArgs:            \"whoami -i\",\n\t\t\tConfigFile:      &config.File{},\n\t\t\tDontWantOutput:  \"requires authentication\",\n\t\t\tWantError:       \"no token provided\",\n\t\t\tWantRemediation: \"Interactive authentication is not available\",\n\t\t},\n\t\t// 17. Auto-yes: errors immediately from processToken (no prompt).\n\t\t{\n\t\t\tName:            \"auto-prompt auto-yes\",\n\t\t\tArgs:            \"pops -y\",\n\t\t\tConfigFile:      &config.File{},\n\t\t\tDontWantOutput:  \"requires authentication\",\n\t\t\tWantError:       \"no token provided\",\n\t\t\tWantRemediation: \"Interactive authentication is not available\",\n\t\t},\n\t\t// 18. Accept-defaults: errors immediately from processToken (no prompt).\n\t\t{\n\t\t\tName:            \"auto-prompt accept-defaults\",\n\t\t\tArgs:            \"pops -d\",\n\t\t\tConfigFile:      &config.File{},\n\t\t\tDontWantOutput:  \"requires authentication\",\n\t\t\tWantError:       \"no token provided\",\n\t\t\tWantRemediation: \"Interactive authentication is not available\",\n\t\t},\n\t\t// 19. FASTLY_USE_SSO=1 triggers SSO flow instead of token prompt.\n\t\t{\n\t\t\tName: \"auto-prompt use-sso env\",\n\t\t\tArgs: \"pops\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tAllDatacentersFn: func(_ context.Context) ([]fastly.Datacenter, error) {\n\t\t\t\t\treturn []fastly.Datacenter{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName:   fastly.ToPointer(\"Foobar\"),\n\t\t\t\t\t\t\tCode:   fastly.ToPointer(\"FBR\"),\n\t\t\t\t\t\t\tGroup:  fastly.ToPointer(\"Bar\"),\n\t\t\t\t\t\t\tShield: fastly.ToPointer(\"Baz\"),\n\t\t\t\t\t\t\tCoordinates: &fastly.Coordinates{\n\t\t\t\t\t\t\t\tLatitude:  fastly.ToPointer(float64(1)),\n\t\t\t\t\t\t\t\tLongitude: fastly.ToPointer(float64(2)),\n\t\t\t\t\t\t\t\tX:         fastly.ToPointer(float64(3)),\n\t\t\t\t\t\t\t\tY:         fastly.ToPointer(float64(4)),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tConfigFile: &config.File{},\n\t\t\tStdin: []string{\n\t\t\t\t\"y\",\n\t\t\t},\n\t\t\tSetup: func(_ *testing.T, _ *testutil.CLIScenario, opts *global.Data) {\n\t\t\t\topts.Env.UseSSO = \"1\"\n\t\t\t\tresult := make(chan auth.AuthorizationResult)\n\t\t\t\topts.AuthServer = testutil.MockAuthServer{\n\t\t\t\t\tResult: result,\n\t\t\t\t}\n\t\t\t\tgo func() {\n\t\t\t\t\tresult <- auth.AuthorizationResult{\n\t\t\t\t\t\tSessionToken: \"sso-env-token\",\n\t\t\t\t\t}\n\t\t\t\t}()\n\t\t\t\topts.HTTPClient = testutil.CurrentCustomerClient(testutil.CurrentCustomerResponse)\n\t\t\t},\n\t\t\tWantOutputs: []string{\n\t\t\t\t\"Do you want to continue\",\n\t\t\t\t\"has been stored\",\n\t\t\t\t\"Token saved to\",\n\t\t\t\t\"{Latitude:1 Longitude:2 X:3 Y:4}\",\n\t\t\t},\n\t\t\tDontWantOutput: \"Paste your API token\",\n\t\t\tValidator: func(t *testing.T, _ *testutil.CLIScenario, opts *global.Data, _ *threadsafe.Buffer) {\n\t\t\t\tat := opts.Config.GetAuthToken(\"default\")\n\t\t\t\tif at == nil {\n\t\t\t\t\tt.Fatal(\"expected auth token 'default' to exist\")\n\t\t\t\t}\n\t\t\t\tif at.Token != \"sso-env-token\" {\n\t\t\t\t\tt.Errorf(\"want token: sso-env-token, got token: %s\", at.Token)\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t// 21. FASTLY_USE_SSO=1 ignored under --non-interactive.\n\t\t{\n\t\t\tName:       \"use-sso ignored non-interactive\",\n\t\t\tArgs:       \"whoami -i\",\n\t\t\tConfigFile: &config.File{},\n\t\t\tSetup: func(_ *testing.T, _ *testutil.CLIScenario, opts *global.Data) {\n\t\t\t\topts.Env.UseSSO = \"1\"\n\t\t\t},\n\t\t\tDontWantOutput:  \"requires authentication\",\n\t\t\tWantError:       \"no token provided\",\n\t\t\tWantRemediation: \"Interactive authentication is not available\",\n\t\t},\n\t\t// 22. FASTLY_USE_SSO=1 ignored under --auto-yes.\n\t\t{\n\t\t\tName:       \"use-sso ignored auto-yes\",\n\t\t\tArgs:       \"pops -y\",\n\t\t\tConfigFile: &config.File{},\n\t\t\tSetup: func(_ *testing.T, _ *testutil.CLIScenario, opts *global.Data) {\n\t\t\t\topts.Env.UseSSO = \"1\"\n\t\t\t},\n\t\t\tDontWantOutput:  \"Do you want to continue\",\n\t\t\tWantError:       \"no token provided\",\n\t\t\tWantRemediation: \"Interactive authentication is not available\",\n\t\t},\n\t\t// 23. FASTLY_USE_SSO=1 ignored under --accept-defaults.\n\t\t{\n\t\t\tName:       \"use-sso ignored accept-defaults\",\n\t\t\tArgs:       \"pops -d\",\n\t\t\tConfigFile: &config.File{},\n\t\t\tSetup: func(_ *testing.T, _ *testutil.CLIScenario, opts *global.Data) {\n\t\t\t\topts.Env.UseSSO = \"1\"\n\t\t\t},\n\t\t\tDontWantOutput:  \"Do you want to continue\",\n\t\t\tWantError:       \"no token provided\",\n\t\t\tWantRemediation: \"Interactive authentication is not available\",\n\t\t},\n\t\t// 24. SSO login with --token sets default to that name but does not overwrite an existing static token's value.\n\t\t{\n\t\t\tName: \"sso login switches default preserves static token\",\n\t\t\tArgs: \"auth login --sso --token work-sso\",\n\t\t\tConfigFile: &config.File{\n\t\t\t\tAuth: config.Auth{\n\t\t\t\t\tDefault: \"mytoken\",\n\t\t\t\t\tTokens: config.AuthTokens{\n\t\t\t\t\t\t\"mytoken\": &config.AuthToken{\n\t\t\t\t\t\t\tType:  config.AuthTokenTypeStatic,\n\t\t\t\t\t\t\tToken: \"static-secret\",\n\t\t\t\t\t\t\tEmail: \"static@example.com\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tStdin: []string{\n\t\t\t\t\"Y\",\n\t\t\t},\n\t\t\tSetup: func(_ *testing.T, _ *testutil.CLIScenario, opts *global.Data) {\n\t\t\t\tresult := make(chan auth.AuthorizationResult)\n\t\t\t\topts.AuthServer = testutil.MockAuthServer{\n\t\t\t\t\tResult: result,\n\t\t\t\t}\n\t\t\t\tgo func() {\n\t\t\t\t\tresult <- auth.AuthorizationResult{\n\t\t\t\t\t\tSessionToken: \"sso-new-token\",\n\t\t\t\t\t}\n\t\t\t\t}()\n\t\t\t\topts.HTTPClient = testutil.CurrentCustomerClient(testutil.CurrentCustomerResponse)\n\t\t\t},\n\t\t\tWantOutputs: []string{\n\t\t\t\t\"We're going to authenticate the 'work-sso' token\",\n\t\t\t\t\"Session token 'work-sso' has been stored.\",\n\t\t\t},\n\t\t\tValidator: func(t *testing.T, _ *testutil.CLIScenario, opts *global.Data, _ *threadsafe.Buffer) {\n\t\t\t\tt.Helper()\n\t\t\t\t// SSO token was created\n\t\t\t\tssoAt := opts.Config.GetAuthToken(\"work-sso\")\n\t\t\t\tif ssoAt == nil {\n\t\t\t\t\tt.Fatal(\"expected auth token 'work-sso' to exist\")\n\t\t\t\t}\n\t\t\t\tif ssoAt.Token != \"sso-new-token\" {\n\t\t\t\t\tt.Errorf(\"want sso token: sso-new-token, got: %s\", ssoAt.Token)\n\t\t\t\t}\n\t\t\t\t// Static token is untouched\n\t\t\t\tstaticAt := opts.Config.GetAuthToken(\"mytoken\")\n\t\t\t\tif staticAt == nil {\n\t\t\t\t\tt.Fatal(\"expected auth token 'mytoken' to still exist\")\n\t\t\t\t}\n\t\t\t\tif staticAt.Token != \"static-secret\" {\n\t\t\t\t\tt.Errorf(\"want static token: static-secret, got: %s\", staticAt.Token)\n\t\t\t\t}\n\t\t\t\t// Default switched to the SSO token (login always sets default)\n\t\t\t\tif opts.Config.Auth.Default != \"work-sso\" {\n\t\t\t\t\tt.Errorf(\"want default: work-sso, got: %s\", opts.Config.Auth.Default)\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t// 25. SSO login stores API token metadata via EnrichWithTokenSelf.\n\t\t{\n\t\t\tName: \"sso stores api token metadata\",\n\t\t\tArgs: \"auth login --sso --token sso-meta\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetTokenSelfFn: func(_ context.Context) (*fastly.Token, error) {\n\t\t\t\t\tscope := fastly.GlobalScope\n\t\t\t\t\treturn &fastly.Token{\n\t\t\t\t\t\tTokenID: fastly.ToPointer(\"sso-tok-id\"),\n\t\t\t\t\t\tName:    fastly.ToPointer(\"sso-api-token\"),\n\t\t\t\t\t\tScope:   &scope,\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tStdin: []string{\n\t\t\t\t\"Y\",\n\t\t\t},\n\t\t\tSetup: func(_ *testing.T, _ *testutil.CLIScenario, opts *global.Data) {\n\t\t\t\tresult := make(chan auth.AuthorizationResult)\n\t\t\t\topts.AuthServer = testutil.MockAuthServer{\n\t\t\t\t\tResult: result,\n\t\t\t\t}\n\t\t\t\tgo func() {\n\t\t\t\t\tresult <- auth.AuthorizationResult{\n\t\t\t\t\t\tSessionToken: \"sso-session-token\",\n\t\t\t\t\t\tEmail:        \"sso@example.com\",\n\t\t\t\t\t}\n\t\t\t\t}()\n\t\t\t\topts.HTTPClient = testutil.CurrentCustomerClient(testutil.CurrentCustomerResponse)\n\t\t\t},\n\t\t\tWantOutputs: []string{\"has been stored\", \"Token saved to\"},\n\t\t\tValidator: func(t *testing.T, _ *testutil.CLIScenario, opts *global.Data, _ *threadsafe.Buffer) {\n\t\t\t\tt.Helper()\n\t\t\t\tat := opts.Config.GetAuthToken(\"sso-meta\")\n\t\t\t\tif at == nil {\n\t\t\t\t\tt.Fatal(\"expected auth token 'sso-meta' to exist\")\n\t\t\t\t}\n\t\t\t\tif at.APITokenName != \"sso-api-token\" {\n\t\t\t\t\tt.Errorf(\"want APITokenName sso-api-token, got %s\", at.APITokenName)\n\t\t\t\t}\n\t\t\t\tif at.APITokenScope != \"global\" {\n\t\t\t\t\tt.Errorf(\"want APITokenScope global, got %s\", at.APITokenScope)\n\t\t\t\t}\n\t\t\t\tif at.APITokenID != \"sso-tok-id\" {\n\t\t\t\t\tt.Errorf(\"want APITokenID sso-tok-id, got %s\", at.APITokenID)\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{}, scenarios)\n}\n\n// TestSSOSuccessMessageDisableAuthCommand verifies RunSSO omits the\n// \"fastly auth list\" hint when FASTLY_DISABLE_AUTH_COMMAND is set.\nfunc TestSSOSuccessMessageDisableAuthCommand(t *testing.T) {\n\tt.Setenv(\"FASTLY_DISABLE_AUTH_COMMAND\", \"1\")\n\n\tvar stdout threadsafe.Buffer\n\tdata := testutil.MockGlobalData([]string{\"fastly\"}, &stdout)\n\n\tresult := make(chan auth.AuthorizationResult)\n\tdata.AuthServer = testutil.MockAuthServer{Result: result}\n\tgo func() {\n\t\tresult <- auth.AuthorizationResult{SessionToken: \"test-token\"}\n\t}()\n\tdata.HTTPClient = testutil.CurrentCustomerClient(testutil.CurrentCustomerResponse)\n\tdata.Flags.AutoYes = true // skip interactive prompt\n\n\terr := authcmd.RunSSO(nil, &stdout, data, false, true)\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\tout := stdout.String()\n\tif !strings.Contains(out, \"has been stored.\") {\n\t\tt.Errorf(\"expected success message, got: %s\", out)\n\t}\n\tif strings.Contains(out, \"fastly auth list\") {\n\t\tt.Errorf(\"expected no 'fastly auth list' hint when env var set, got: %s\", out)\n\t}\n\tif !strings.Contains(out, \"Token saved to\") {\n\t\tt.Errorf(\"expected 'Token saved to' message, got: %s\", out)\n\t}\n}\n"
  },
  {
    "path": "pkg/commands/auth/token.go",
    "content": "package auth\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/lookup\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// TokenCommand prints the active API token to non-terminal stdout.\ntype TokenCommand struct {\n\targparser.Base\n}\n\n// NewTokenCommand returns a new command registered under the parent.\nfunc NewTokenCommand(parent argparser.Registerer, g *global.Data) *TokenCommand {\n\tvar c TokenCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(\"token\", \"Output the active API token (for use in shell substitutions)\")\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *TokenCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif text.IsTTY(out) {\n\t\treturn fsterr.RemediationError{\n\t\t\tInner:       fmt.Errorf(\"refusing to print token to a terminal\"),\n\t\t\tRemediation: \"Use this command in a shell substitution or pipe, e.g. $(fastly auth token).\",\n\t\t}\n\t}\n\n\tif err := c.Globals.ValidateProfileFlag(); err != nil {\n\t\treturn err\n\t}\n\n\ttoken, src := c.Globals.Token()\n\tif src == lookup.SourceUndefined || token == \"\" {\n\t\treturn fsterr.RemediationError{\n\t\t\tInner:       fmt.Errorf(\"no API token configured\"),\n\t\t\tRemediation: fsterr.ProfileRemediation(),\n\t\t}\n\t}\n\n\tfmt.Fprint(out, token)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/auth/token_test.go",
    "content": "package auth_test\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/fastly/kingpin\"\n\n\tauthcmd \"github.com/fastly/cli/pkg/commands/auth\"\n\t\"github.com/fastly/cli/pkg/config\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\nfunc newTokenCommand(g *global.Data) *authcmd.TokenCommand {\n\tapp := kingpin.New(\"fastly\", \"test\")\n\tparent := app.Command(\"auth\", \"test auth\")\n\treturn authcmd.NewTokenCommand(parent, g)\n}\n\nfunc globalDataWithToken(token string) *global.Data {\n\treturn &global.Data{\n\t\tConfig: config.File{\n\t\t\tAuth: config.Auth{\n\t\t\t\tDefault: \"user\",\n\t\t\t\tTokens: config.AuthTokens{\n\t\t\t\t\t\"user\": &config.AuthToken{\n\t\t\t\t\t\tType:  config.AuthTokenTypeStatic,\n\t\t\t\t\t\tToken: token,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n}\n\nfunc TestToken_NonTTY_Success(t *testing.T) {\n\tvar buf bytes.Buffer\n\tcmd := newTokenCommand(globalDataWithToken(\"test-api-token-value\"))\n\terr := cmd.Exec(nil, &buf)\n\tif err != nil {\n\t\tt.Fatalf(\"expected no error, got: %v\", err)\n\t}\n\tif got := buf.String(); got != \"test-api-token-value\" {\n\t\tt.Errorf(\"expected token %q, got %q\", \"test-api-token-value\", got)\n\t}\n\tif got := buf.Bytes(); got[len(got)-1] == '\\n' {\n\t\tt.Error(\"output should not have a trailing newline\")\n\t}\n}\n\nfunc TestToken_NonTTY_NoToken(t *testing.T) {\n\tvar buf bytes.Buffer\n\tg := &global.Data{\n\t\tConfig: config.File{},\n\t}\n\n\tcmd := newTokenCommand(g)\n\terr := cmd.Exec(nil, &buf)\n\tif err == nil {\n\t\tt.Fatal(\"expected error for missing token\")\n\t}\n\tvar re fsterr.RemediationError\n\tif !errors.As(err, &re) {\n\t\tt.Fatalf(\"expected RemediationError, got %T: %v\", err, err)\n\t}\n\tif re.Inner == nil || re.Inner.Error() != \"no API token configured\" {\n\t\tt.Errorf(\"unexpected inner error: %v\", re.Inner)\n\t}\n}\n\nfunc TestToken_NonTTY_ProfileFlagUnknown(t *testing.T) {\n\tvar buf bytes.Buffer\n\tg := globalDataWithToken(\"test-api-token-value\")\n\tg.Flags.Profile = \"bogus\"\n\n\tcmd := newTokenCommand(g)\n\terr := cmd.Exec(nil, &buf)\n\tif err == nil {\n\t\tt.Fatal(\"expected error for unknown --profile\")\n\t}\n\tvar re fsterr.RemediationError\n\tif !errors.As(err, &re) {\n\t\tt.Fatalf(\"expected RemediationError, got %T: %v\", err, err)\n\t}\n\tif re.Inner == nil || !strings.Contains(re.Inner.Error(), `\"bogus\"`) {\n\t\tt.Errorf(\"unexpected inner error: %v\", re.Inner)\n\t}\n\tif buf.Len() != 0 {\n\t\tt.Errorf(\"expected no token to be written, got: %q\", buf.String())\n\t}\n}\n\nfunc TestToken_NonTTY_ProfileFlagKnown(t *testing.T) {\n\tvar buf bytes.Buffer\n\tg := globalDataWithToken(\"default-token\")\n\tg.Config.Auth.Tokens[\"alt\"] = &config.AuthToken{Type: config.AuthTokenTypeStatic, Token: \"alt-token\"}\n\tg.Flags.Profile = \"alt\"\n\n\tcmd := newTokenCommand(g)\n\terr := cmd.Exec(nil, &buf)\n\tif err != nil {\n\t\tt.Fatalf(\"expected no error, got: %v\", err)\n\t}\n\tif got := buf.String(); got != \"alt-token\" {\n\t\tt.Errorf(\"expected token %q, got %q\", \"alt-token\", got)\n\t}\n}\n\nfunc TestToken_NonTTY_TokenFlagBeatsProfileFlag(t *testing.T) {\n\tvar buf bytes.Buffer\n\tg := globalDataWithToken(\"default-token\")\n\tg.Flags.Token = \"raw-xyz\"\n\tg.Flags.Profile = \"bogus\"\n\n\tcmd := newTokenCommand(g)\n\terr := cmd.Exec(nil, &buf)\n\tif err != nil {\n\t\tt.Fatalf(\"expected no error, got: %v\", err)\n\t}\n\tif got := buf.String(); got != \"raw-xyz\" {\n\t\tt.Errorf(\"expected token %q, got %q\", \"raw-xyz\", got)\n\t}\n}\n"
  },
  {
    "path": "pkg/commands/auth/token_tty_unix_test.go",
    "content": "//go:build !windows\n\npackage auth_test\n\nimport (\n\t\"errors\"\n\t\"testing\"\n\n\t\"github.com/creack/pty\"\n\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n)\n\nfunc TestToken_TTY_Refused(t *testing.T) {\n\t// Create a PTY pair so we have a writable *os.File that\n\t// term.IsTerminal recognises as a terminal. This runs reliably\n\t// on Unix CI (no /dev/tty required) and, unlike os.Stdout, never\n\t// risks leaking a token to the developer's real terminal.\n\tptm, pts, err := pty.Open()\n\tif err != nil {\n\t\tt.Fatalf(\"failed to open pty: %v\", err)\n\t}\n\tdefer ptm.Close()\n\tdefer pts.Close()\n\n\tcmd := newTokenCommand(globalDataWithToken(\"secret-token\"))\n\terr = cmd.Exec(nil, pts)\n\tif err == nil {\n\t\tt.Fatal(\"expected error when stdout is a terminal\")\n\t}\n\tvar re fsterr.RemediationError\n\tif !errors.As(err, &re) {\n\t\tt.Fatalf(\"expected RemediationError, got %T: %v\", err, err)\n\t}\n\tif re.Inner == nil || re.Inner.Error() != \"refusing to print token to a terminal\" {\n\t\tt.Errorf(\"unexpected inner error: %v\", re.Inner)\n\t}\n}\n"
  },
  {
    "path": "pkg/commands/auth/use.go",
    "content": "package auth\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// UseCommand switches the default token.\ntype UseCommand struct {\n\targparser.Base\n\tname string\n}\n\nfunc NewUseCommand(parent argparser.Registerer, g *global.Data) *UseCommand {\n\tvar c UseCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(\"use\", \"Set the default stored token for CLI commands\")\n\t// Required.\n\tc.CmdClause.Arg(\"name\", \"Name of the token to use as default\").Required().StringVar(&c.name)\n\treturn &c\n}\n\nfunc (c *UseCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif err := c.Globals.Config.SetDefaultAuthToken(c.name); err != nil {\n\t\treturn err\n\t}\n\n\tif err := c.Globals.Config.Write(c.Globals.ConfigPath); err != nil {\n\t\treturn fmt.Errorf(\"error saving config: %w\", err)\n\t}\n\n\ttext.Success(out, \"Default token switched to %q\", c.name)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/authtoken/authtoken_test.go",
    "content": "package authtoken_test\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\troot \"github.com/fastly/cli/pkg/commands/authtoken\"\n\t\"github.com/fastly/cli/pkg/mock\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n)\n\nfunc TestAuthTokenCreate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --password flag\",\n\t\t\tWantError: \"error parsing arguments: required flag --password not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate CreateToken API error\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tCreateTokenFn: func(_ context.Context, _ *fastly.CreateTokenInput) (*fastly.Token, error) {\n\t\t\t\t\treturn nil, testutil.Err\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:      \"--password secure --token 123\",\n\t\t\tWantError: testutil.Err.Error(),\n\t\t},\n\t\t{\n\t\t\tName: \"validate CreateToken API success with no flags\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tCreateTokenFn: func(_ context.Context, _ *fastly.CreateTokenInput) (*fastly.Token, error) {\n\t\t\t\t\treturn &fastly.Token{\n\t\t\t\t\t\tExpiresAt:   &testutil.Date,\n\t\t\t\t\t\tTokenID:     fastly.ToPointer(\"123\"),\n\t\t\t\t\t\tName:        fastly.ToPointer(\"Example\"),\n\t\t\t\t\t\tScope:       fastly.ToPointer(fastly.TokenScope(\"foobar\")),\n\t\t\t\t\t\tAccessToken: fastly.ToPointer(\"123abc\"),\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:       \"--password secure --token 123\",\n\t\t\tWantOutput: \"Created token '123abc' (name: Example, id: 123, scope: foobar, expires: 2021-06-15 23:00:00 +0000 UTC)\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate CreateToken API success with all flags\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tCreateTokenFn: func(_ context.Context, i *fastly.CreateTokenInput) (*fastly.Token, error) {\n\t\t\t\t\treturn &fastly.Token{\n\t\t\t\t\t\tExpiresAt:   i.ExpiresAt,\n\t\t\t\t\t\tTokenID:     fastly.ToPointer(\"123\"),\n\t\t\t\t\t\tName:        i.Name,\n\t\t\t\t\t\tScope:       i.Scope,\n\t\t\t\t\t\tAccessToken: fastly.ToPointer(\"123abc\"),\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:       \"--expires 2021-09-15T23:00:00Z --name Testing --password secure --scope purge_all --scope global:read --services a,b,c --token 123\",\n\t\t\tWantOutput: \"Created token '123abc' (name: Testing, id: 123, scope: purge_all global:read, expires: 2021-09-15 23:00:00 +0000 UTC)\",\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, \"create\"}, scenarios)\n}\n\nfunc TestAuthTokenDelete(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing optional flags\",\n\t\t\tArgs:      \"--token 123\",\n\t\t\tWantError: \"error parsing arguments: must provide either the --current, --file or --id flag\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate DeleteTokenSelf API error with --current\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tDeleteTokenSelfFn: func(_ context.Context) error {\n\t\t\t\t\treturn testutil.Err\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:      \"--current --token 123\",\n\t\t\tWantError: testutil.Err.Error(),\n\t\t},\n\t\t{\n\t\t\tName: \"validate BatchDeleteTokens API error with --file\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tBatchDeleteTokensFn: func(_ context.Context, _ *fastly.BatchDeleteTokensInput) error {\n\t\t\t\t\treturn testutil.Err\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:      \"--file ./testdata/tokens --token 123\",\n\t\t\tWantError: testutil.Err.Error(),\n\t\t},\n\t\t{\n\t\t\tName: \"validate DeleteToken API error with --id\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tDeleteTokenFn: func(_ context.Context, _ *fastly.DeleteTokenInput) error {\n\t\t\t\t\treturn testutil.Err\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:      \"--id 123 --token 123\",\n\t\t\tWantError: testutil.Err.Error(),\n\t\t},\n\t\t{\n\t\t\tName: \"validate DeleteTokenSelf API success with --current\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tDeleteTokenSelfFn: func(_ context.Context) error {\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:       \"--current --token 123\",\n\t\t\tWantOutput: \"Deleted current token\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate BatchDeleteTokens API success with --file\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tBatchDeleteTokensFn: func(_ context.Context, _ *fastly.BatchDeleteTokensInput) error {\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:       \"--file ./testdata/tokens --token 123\",\n\t\t\tWantOutput: \"Deleted tokens\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate BatchDeleteTokens API success with --file and --verbose\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tBatchDeleteTokensFn: func(_ context.Context, _ *fastly.BatchDeleteTokensInput) error {\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:       \"--file ./testdata/tokens --token 123 --verbose\",\n\t\t\tWantOutput: fileTokensOutput(),\n\t\t},\n\t\t{\n\t\t\tName: \"validate DeleteToken API success with --id\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tDeleteTokenFn: func(_ context.Context, _ *fastly.DeleteTokenInput) error {\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:       \"--id 123 --token 123\",\n\t\t\tWantOutput: \"Deleted token '123'\",\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, \"delete\"}, scenarios)\n}\n\nfunc TestAuthTokenDescribe(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName: \"validate GetTokenSelf API error\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetTokenSelfFn: func(_ context.Context) (*fastly.Token, error) {\n\t\t\t\t\treturn nil, testutil.Err\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:      \"--token 123\",\n\t\t\tWantError: testutil.Err.Error(),\n\t\t},\n\t\t{\n\t\t\tName: \"validate GetTokenSelf API success\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetTokenSelfFn: getToken,\n\t\t\t},\n\t\t\tArgs:       \"--token 123\",\n\t\t\tWantOutput: describeTokenOutput(),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, \"describe\"}, scenarios)\n}\n\nfunc TestAuthTokenList(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName: \"validate ListTokens API error\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tListTokensFn: func(_ context.Context, _ *fastly.ListTokensInput) ([]*fastly.Token, error) {\n\t\t\t\t\treturn nil, testutil.Err\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: testutil.Err.Error(),\n\t\t},\n\t\t{\n\t\t\tName: \"validate ListCustomerTokens API error\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tListCustomerTokensFn: func(_ context.Context, _ *fastly.ListCustomerTokensInput) ([]*fastly.Token, error) {\n\t\t\t\t\treturn nil, testutil.Err\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:      \"--customer-id 123\",\n\t\t\tWantError: testutil.Err.Error(),\n\t\t},\n\t\t{\n\t\t\tName: \"validate ListTokens API success\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tListTokensFn: listTokens,\n\t\t\t},\n\t\t\tWantOutput: listTokenOutputSummary(false),\n\t\t},\n\t\t{\n\t\t\tName: \"validate ListCustomerTokens API success\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tListCustomerTokensFn: listCustomerTokens,\n\t\t\t},\n\t\t\tArgs:       \"--customer-id 123\",\n\t\t\tWantOutput: listTokenOutputSummary(false),\n\t\t},\n\t\t{\n\t\t\tName: \"validate ListCustomerTokens API success with env var\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tListCustomerTokensFn: listCustomerTokens,\n\t\t\t},\n\t\t\tWantOutput: listTokenOutputSummary(true),\n\t\t\tEnvVars:    map[string]string{\"FASTLY_CUSTOMER_ID\": \"123\"},\n\t\t},\n\t\t{\n\t\t\tName: \"validate --verbose flag\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tListTokensFn: listTokens,\n\t\t\t},\n\t\t\tArgs:       \"--verbose\",\n\t\t\tWantOutput: listTokenOutputVerbose(),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, \"list\"}, scenarios)\n}\n\nfunc getToken(_ context.Context) (*fastly.Token, error) {\n\tt := testutil.Date\n\n\treturn &fastly.Token{\n\t\tTokenID:    fastly.ToPointer(\"123\"),\n\t\tName:       fastly.ToPointer(\"Foo\"),\n\t\tUserID:     fastly.ToPointer(\"456\"),\n\t\tServices:   []string{\"a\", \"b\"},\n\t\tScope:      fastly.ToPointer(fastly.TokenScope(fmt.Sprintf(\"%s %s\", fastly.PurgeAllScope, fastly.GlobalReadScope))),\n\t\tIP:         fastly.ToPointer(\"127.0.0.1\"),\n\t\tCreatedAt:  &t,\n\t\tExpiresAt:  &t,\n\t\tLastUsedAt: &t,\n\t}, nil\n}\n\nfunc listTokens(ctx context.Context, _ *fastly.ListTokensInput) ([]*fastly.Token, error) {\n\tt := testutil.Date\n\ttoken, _ := getToken(ctx)\n\tvs := []*fastly.Token{\n\t\ttoken,\n\t\t{\n\t\t\tTokenID:    fastly.ToPointer(\"456\"),\n\t\t\tName:       fastly.ToPointer(\"Bar\"),\n\t\t\tUserID:     fastly.ToPointer(\"789\"),\n\t\t\tServices:   []string{\"a\", \"b\"},\n\t\t\tScope:      fastly.ToPointer(fastly.GlobalScope),\n\t\t\tIP:         fastly.ToPointer(\"127.0.0.2\"),\n\t\t\tCreatedAt:  &t,\n\t\t\tExpiresAt:  &t,\n\t\t\tLastUsedAt: &t,\n\t\t},\n\t}\n\treturn vs, nil\n}\n\nfunc listCustomerTokens(ctx context.Context, _ *fastly.ListCustomerTokensInput) ([]*fastly.Token, error) {\n\treturn listTokens(ctx, nil)\n}\n\nfunc fileTokensOutput() string {\n\treturn `Deleted tokens\n\nTOKEN ID\nabc\ndef\nxyz`\n}\n\nfunc describeTokenOutput() string {\n\treturn `\nID: 123\nName: Foo\nUser ID: 456\nServices: a, b\nScope: purge_all global:read\nIP: 127.0.0.1\n\nCreated at: 2021-06-15 23:00:00 +0000 UTC\nLast used at: 2021-06-15 23:00:00 +0000 UTC\nExpires at: 2021-06-15 23:00:00 +0000 UTC`\n}\n\nfunc listTokenOutputVerbose() string {\n\treturn `Fastly API endpoint: https://api.fastly.com\nFastly API token provided via config file (auth: user)\n\n\nID: 123\nName: Foo\nUser ID: 456\nServices: a, b\nScope: purge_all global:read\nIP: 127.0.0.1\n\nCreated at: 2021-06-15 23:00:00 +0000 UTC\nLast used at: 2021-06-15 23:00:00 +0000 UTC\nExpires at: 2021-06-15 23:00:00 +0000 UTC\n\nID: 456\nName: Bar\nUser ID: 789\nServices: a, b\nScope: global\nIP: 127.0.0.2\n\nCreated at: 2021-06-15 23:00:00 +0000 UTC\nLast used at: 2021-06-15 23:00:00 +0000 UTC\nExpires at: 2021-06-15 23:00:00 +0000 UTC\n\n`\n}\n\nfunc listTokenOutputSummary(env bool) string {\n\tvar msg string\n\tif env {\n\t\tmsg = \"INFO: Listing customer tokens for the FASTLY_CUSTOMER_ID environment variable\\n\\n\"\n\t}\n\treturn fmt.Sprintf(`%sNAME  TOKEN ID  USER ID  SCOPE                  SERVICES\nFoo   123       456      purge_all global:read  a, b\nBar   456       789      global                 a, b`, msg)\n}\n"
  },
  {
    "path": "pkg/commands/authtoken/create.go",
    "content": "package authtoken\n\nimport (\n\t\"context\"\n\t\"io\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/kingpin\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// Scopes is a list of purging scope options.\n// https://www.fastly.com/documentation/reference/api/auth-tokens#scopes\nvar Scopes = []string{\"global\", \"purge_select\", \"purge_all\", \"global:read\"}\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"create\", \"Create an API token (deprecated: use the Fastly API directly)\").Alias(\"add\")\n\n\t// Required.\n\t//\n\t// NOTE: The go-fastly client internally calls `/sudo` before `/tokens` and\n\t// the sudo endpoint requires a password to be provided alongside an API\n\t// token. The password must be for the user account that created the token\n\t// being passed as authentication to the API endpoint.\n\tc.CmdClause.Flag(\"password\", \"User password corresponding with --token or $FASTLY_API_TOKEN\").Required().StringVar(&c.password)\n\n\t// Optional.\n\t//\n\t// NOTE: The API describes 'scope' as being space-delimited but we've opted\n\t// for comma-separated as it means users don't have to worry about how best\n\t// to handle issues with passing a flag value with whitespace. When\n\t// constructing the input for the API call we convert from a comma-separated\n\t// value to a space-delimited value.\n\tc.CmdClause.Flag(\"expires\", \"Time-stamp (UTC) of when the token will expire\").HintOptions(\"2016-07-28T19:24:50+00:00\").TimeVar(time.RFC3339, &c.expires)\n\tc.CmdClause.Flag(\"name\", \"Name of the token\").StringVar(&c.name)\n\tc.CmdClause.Flag(\"scope\", \"Authorization scope (repeat flag per scope)\").HintOptions(Scopes...).EnumsVar(&c.scope, Scopes...)\n\tc.CmdClause.Flag(\"services\", \"A comma-separated list of alphanumeric strings identifying services (default: access to all services)\").StringsVar(&c.services, kingpin.Separator(\",\"))\n\treturn &c\n}\n\n// CreateCommand calls the Fastly API to create an appropriate resource.\ntype CreateCommand struct {\n\targparser.Base\n\n\texpires  time.Time\n\tname     string\n\tpassword string\n\tscope    []string\n\tservices []string\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *CreateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif !c.Globals.Flags.Quiet {\n\t\ttext.Deprecated(\"The 'auth-token' command tree will be removed in a future release. Use the Fastly API directly to manage API tokens.\\n\\n\")\n\t}\n\n\tinput := c.constructInput()\n\n\tr, err := c.Globals.APIClient.CreateToken(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\texpires := \"never\"\n\tif r.ExpiresAt != nil {\n\t\texpires = r.ExpiresAt.String()\n\t}\n\n\ttext.Success(out, \"Created token '%s' (name: %s, id: %s, scope: %s, expires: %s)\", fastly.ToValue(r.AccessToken), fastly.ToValue(r.Name), fastly.ToValue(r.TokenID), fastly.ToValue(r.Scope), expires)\n\treturn nil\n}\n\n// constructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *CreateCommand) constructInput() *fastly.CreateTokenInput {\n\tvar input fastly.CreateTokenInput\n\n\tinput.Password = fastly.ToPointer(c.password)\n\n\tif !c.expires.IsZero() {\n\t\tinput.ExpiresAt = &c.expires\n\t}\n\tif c.name != \"\" {\n\t\tinput.Name = fastly.ToPointer(c.name)\n\t}\n\tif len(c.scope) > 0 {\n\t\tinput.Scope = fastly.ToPointer(fastly.TokenScope(strings.Join(c.scope, \" \")))\n\t}\n\tif len(c.services) > 0 {\n\t\tinput.Services = c.services\n\t}\n\n\treturn &input\n}\n"
  },
  {
    "path": "pkg/commands/authtoken/delete.go",
    "content": "package authtoken\n\nimport (\n\t\"bufio\"\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"path/filepath\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"delete\", \"Revoke an API token (deprecated: use the Fastly API directly)\").Alias(\"remove\")\n\n\tc.CmdClause.Flag(\"current\", \"Revoke the token used to authenticate the request\").BoolVar(&c.current)\n\tc.CmdClause.Flag(\"file\", \"Revoke tokens in bulk from a newline delimited list of tokens\").StringVar(&c.file)\n\tc.CmdClause.Flag(\"id\", \"Alphanumeric string identifying a token\").StringVar(&c.id)\n\treturn &c\n}\n\n// DeleteCommand calls the Fastly API to delete an appropriate resource.\ntype DeleteCommand struct {\n\targparser.Base\n\n\tcurrent bool\n\tfile    string\n\tid      string\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DeleteCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif !c.Globals.Flags.Quiet {\n\t\ttext.Deprecated(\"The 'auth-token' command tree will be removed in a future release. Use the Fastly API directly to manage API tokens.\\n\\n\")\n\t}\n\n\tif !c.current && c.file == \"\" && c.id == \"\" {\n\t\treturn fmt.Errorf(\"error parsing arguments: must provide either the --current, --file or --id flag\")\n\t}\n\n\tif c.current {\n\t\terr := c.Globals.APIClient.DeleteTokenSelf(context.TODO())\n\t\tif err != nil {\n\t\t\tc.Globals.ErrLog.Add(err)\n\t\t\treturn err\n\t\t}\n\n\t\ttext.Success(out, \"Deleted current token\")\n\t\treturn nil\n\t}\n\n\tif c.file != \"\" {\n\t\tinput, err := c.constructInputBatch()\n\t\tif err != nil {\n\t\t\tc.Globals.ErrLog.Add(err)\n\t\t\treturn err\n\t\t}\n\n\t\terr = c.Globals.APIClient.BatchDeleteTokens(context.TODO(), input)\n\t\tif err != nil {\n\t\t\tc.Globals.ErrLog.Add(err)\n\t\t\treturn err\n\t\t}\n\n\t\ttext.Success(out, \"Deleted tokens\")\n\t\tif c.Globals.Verbose() {\n\t\t\ttext.Break(out)\n\t\t\tc.printTokens(out, input.Tokens)\n\t\t}\n\t\treturn nil\n\t}\n\n\tif c.id != \"\" {\n\t\tinput := c.constructInput()\n\n\t\terr := c.Globals.APIClient.DeleteToken(context.TODO(), input)\n\t\tif err != nil {\n\t\t\tc.Globals.ErrLog.Add(err)\n\t\t\treturn err\n\t\t}\n\n\t\ttext.Success(out, \"Deleted token '%s'\", c.id)\n\t\treturn nil\n\t}\n\n\treturn nil\n}\n\n// constructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *DeleteCommand) constructInput() *fastly.DeleteTokenInput {\n\tvar input fastly.DeleteTokenInput\n\n\tinput.TokenID = c.id\n\n\treturn &input\n}\n\n// constructInputBatch transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *DeleteCommand) constructInputBatch() (*fastly.BatchDeleteTokensInput, error) {\n\tvar (\n\t\terr    error\n\t\tfile   io.Reader\n\t\tinput  fastly.BatchDeleteTokensInput\n\t\tpath   string\n\t\ttokens []*fastly.BatchToken\n\t)\n\n\tif path, err = filepath.Abs(c.file); err == nil {\n\t\tif _, err = os.Stat(path); err == nil {\n\t\t\tif file, err = os.Open(path); err == nil /* #nosec */ {\n\t\t\t\tscanner := bufio.NewScanner(file)\n\t\t\t\tfor scanner.Scan() {\n\t\t\t\t\ttokens = append(tokens, &fastly.BatchToken{ID: scanner.Text()})\n\t\t\t\t}\n\t\t\t\terr = scanner.Err()\n\t\t\t}\n\t\t}\n\t}\n\n\tinput.Tokens = tokens\n\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &input, nil\n}\n\n// printTokens displays the tokens provided by a user.\nfunc (c *DeleteCommand) printTokens(out io.Writer, rs []*fastly.BatchToken) {\n\tt := text.NewTable(out)\n\tt.AddHeader(\"TOKEN ID\")\n\tfor _, r := range rs {\n\t\tt.AddLine(r.ID)\n\t}\n\tt.Print()\n}\n"
  },
  {
    "path": "pkg/commands/authtoken/describe.go",
    "content": "package authtoken\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"strings\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// NewDescribeCommand returns a usable command registered under the parent.\nfunc NewDescribeCommand(parent argparser.Registerer, g *global.Data) *DescribeCommand {\n\tc := DescribeCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"describe\", \"Get the current API token (deprecated: use the Fastly API directly)\").Alias(\"get\")\n\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\treturn &c\n}\n\n// DescribeCommand calls the Fastly API to describe an appropriate resource.\ntype DescribeCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DescribeCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif !c.Globals.Flags.Quiet && !c.JSONOutput.Enabled {\n\t\ttext.Deprecated(\"The 'auth-token' command tree will be removed in a future release. Use the Fastly API directly to manage API tokens.\\n\\n\")\n\t}\n\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\to, err := c.Globals.APIClient.GetTokenSelf(context.TODO())\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\treturn c.print(out, o)\n}\n\n// print displays the information returned from the API.\nfunc (c *DescribeCommand) print(out io.Writer, t *fastly.Token) error {\n\tfmt.Fprintf(out, \"\\nID: %s\\n\", fastly.ToValue(t.TokenID))\n\tfmt.Fprintf(out, \"Name: %s\\n\", fastly.ToValue(t.Name))\n\tfmt.Fprintf(out, \"User ID: %s\\n\", fastly.ToValue(t.UserID))\n\tfmt.Fprintf(out, \"Services: %s\\n\", strings.Join(t.Services, \", \"))\n\tfmt.Fprintf(out, \"Scope: %s\\n\", fastly.ToValue(t.Scope))\n\tfmt.Fprintf(out, \"IP: %s\\n\\n\", fastly.ToValue(t.IP))\n\n\tif t.CreatedAt != nil {\n\t\tfmt.Fprintf(out, \"Created at: %s\\n\", t.CreatedAt)\n\t}\n\tif t.LastUsedAt != nil {\n\t\tfmt.Fprintf(out, \"Last used at: %s\\n\", t.LastUsedAt)\n\t}\n\tif t.ExpiresAt != nil {\n\t\tfmt.Fprintf(out, \"Expires at: %s\\n\", t.ExpiresAt)\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/authtoken/doc.go",
    "content": "// Package authtoken contains commands to manage API tokens for Fastly service\n// users.\npackage authtoken\n"
  },
  {
    "path": "pkg/commands/authtoken/list.go",
    "content": "package authtoken\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"strings\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"list\", \"List API tokens (deprecated: use the Fastly API directly)\")\n\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagCustomerIDName,\n\t\tDescription: argparser.FlagCustomerIDDesc,\n\t\tDst:         &c.customerID.Value,\n\t\tAction:      c.customerID.Set,\n\t})\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\treturn &c\n}\n\n// ListCommand calls the Fastly API to list appropriate resources.\ntype ListCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tcustomerID argparser.OptionalCustomerID\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *ListCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif !c.Globals.Flags.Quiet && !c.JSONOutput.Enabled {\n\t\ttext.Deprecated(\"The 'auth-token' command tree will be removed in a future release. Use the Fastly API directly to manage API tokens.\\n\\n\")\n\t}\n\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tvar (\n\t\terr error\n\t\to   []*fastly.Token\n\t)\n\n\tif err = c.customerID.Parse(); err == nil {\n\t\tif !c.customerID.WasSet && !c.Globals.Flags.Quiet && !c.JSONOutput.Enabled {\n\t\t\ttext.Info(out, \"Listing customer tokens for the FASTLY_CUSTOMER_ID environment variable\\n\\n\")\n\t\t}\n\t\tinput := c.constructInput()\n\t\to, err = c.Globals.APIClient.ListCustomerTokens(context.TODO(), input)\n\t\tif err != nil {\n\t\t\tc.Globals.ErrLog.Add(err)\n\t\t\treturn err\n\t\t}\n\t} else {\n\t\to, err = c.Globals.APIClient.ListTokens(context.TODO(), &fastly.ListTokensInput{})\n\t\tif err != nil {\n\t\t\tc.Globals.ErrLog.Add(err)\n\t\t\treturn err\n\t\t}\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\tif c.Globals.Verbose() {\n\t\tc.printVerbose(out, o)\n\t} else {\n\t\terr = c.printSummary(out, o)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\n// constructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *ListCommand) constructInput() *fastly.ListCustomerTokensInput {\n\tvar input fastly.ListCustomerTokensInput\n\n\tinput.CustomerID = c.customerID.Value\n\n\treturn &input\n}\n\n// printVerbose displays the information returned from the API in a verbose\n// format.\nfunc (c *ListCommand) printVerbose(out io.Writer, rs []*fastly.Token) {\n\tfor _, r := range rs {\n\t\tfmt.Fprintf(out, \"\\nID: %s\\n\", fastly.ToValue(r.TokenID))\n\t\tfmt.Fprintf(out, \"Name: %s\\n\", fastly.ToValue(r.Name))\n\t\tfmt.Fprintf(out, \"User ID: %s\\n\", fastly.ToValue(r.UserID))\n\t\tfmt.Fprintf(out, \"Services: %s\\n\", strings.Join(r.Services, \", \"))\n\t\tfmt.Fprintf(out, \"Scope: %s\\n\", fastly.ToValue(r.Scope))\n\t\tfmt.Fprintf(out, \"IP: %s\\n\\n\", fastly.ToValue(r.IP))\n\n\t\tif r.CreatedAt != nil {\n\t\t\tfmt.Fprintf(out, \"Created at: %s\\n\", r.CreatedAt)\n\t\t}\n\t\tif r.LastUsedAt != nil {\n\t\t\tfmt.Fprintf(out, \"Last used at: %s\\n\", r.LastUsedAt)\n\t\t}\n\t\tif r.ExpiresAt != nil {\n\t\t\tfmt.Fprintf(out, \"Expires at: %s\\n\", r.ExpiresAt)\n\t\t}\n\t}\n\tfmt.Fprintf(out, \"\\n\")\n}\n\n// printSummary displays the information returned from the API in a summarised\n// format.\nfunc (c *ListCommand) printSummary(out io.Writer, ts []*fastly.Token) error {\n\ttbl := text.NewTable(out)\n\ttbl.AddHeader(\"NAME\", \"TOKEN ID\", \"USER ID\", \"SCOPE\", \"SERVICES\")\n\tfor _, t := range ts {\n\t\ttbl.AddLine(\n\t\t\tfastly.ToValue(t.Name),\n\t\t\tfastly.ToValue(t.TokenID),\n\t\t\tfastly.ToValue(t.UserID),\n\t\t\tfastly.ToValue(t.Scope),\n\t\t\tstrings.Join(t.Services, \", \"),\n\t\t)\n\t}\n\ttbl.Print()\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/authtoken/root.go",
    "content": "package authtoken\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"auth-token\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manage API tokens (deprecated: use 'fastly auth' subcommands instead)\").Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/authtoken/testdata/tokens",
    "content": "abc\ndef\nxyz\n"
  },
  {
    "path": "pkg/commands/commands.go",
    "content": "package commands\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/kingpin\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\taliasacl \"github.com/fastly/cli/pkg/commands/alias/acl\"\n\taliasaclentry \"github.com/fastly/cli/pkg/commands/alias/aclentry\"\n\taliasalerts \"github.com/fastly/cli/pkg/commands/alias/alerts\"\n\taliasbackend \"github.com/fastly/cli/pkg/commands/alias/backend\"\n\taliasdictionary \"github.com/fastly/cli/pkg/commands/alias/dictionary\"\n\taliasdictionaryentry \"github.com/fastly/cli/pkg/commands/alias/dictionaryentry\"\n\taliashealthcheck \"github.com/fastly/cli/pkg/commands/alias/healthcheck\"\n\taliasimageoptimizerdefaults \"github.com/fastly/cli/pkg/commands/alias/imageoptimizerdefaults\"\n\taliaslogging \"github.com/fastly/cli/pkg/commands/alias/logging\"\n\taliasazureblob \"github.com/fastly/cli/pkg/commands/alias/logging/azureblob\"\n\taliasbigquery \"github.com/fastly/cli/pkg/commands/alias/logging/bigquery\"\n\taliascloudfiles \"github.com/fastly/cli/pkg/commands/alias/logging/cloudfiles\"\n\taliasdatadog \"github.com/fastly/cli/pkg/commands/alias/logging/datadog\"\n\taliasdigitalocean \"github.com/fastly/cli/pkg/commands/alias/logging/digitalocean\"\n\taliaselasticsearch \"github.com/fastly/cli/pkg/commands/alias/logging/elasticsearch\"\n\taliasftp \"github.com/fastly/cli/pkg/commands/alias/logging/ftp\"\n\taliasgcs \"github.com/fastly/cli/pkg/commands/alias/logging/gcs\"\n\taliasgooglepubsub \"github.com/fastly/cli/pkg/commands/alias/logging/googlepubsub\"\n\taliasgrafanacloudlogs \"github.com/fastly/cli/pkg/commands/alias/logging/grafanacloudlogs\"\n\taliasheroku \"github.com/fastly/cli/pkg/commands/alias/logging/heroku\"\n\taliashoneycomb \"github.com/fastly/cli/pkg/commands/alias/logging/honeycomb\"\n\taliashttps \"github.com/fastly/cli/pkg/commands/alias/logging/https\"\n\taliaskafka \"github.com/fastly/cli/pkg/commands/alias/logging/kafka\"\n\taliaskinesis \"github.com/fastly/cli/pkg/commands/alias/logging/kinesis\"\n\taliasloggly \"github.com/fastly/cli/pkg/commands/alias/logging/loggly\"\n\taliaslogshuttle \"github.com/fastly/cli/pkg/commands/alias/logging/logshuttle\"\n\taliasnewrelic \"github.com/fastly/cli/pkg/commands/alias/logging/newrelic\"\n\taliasnewrelicotlp \"github.com/fastly/cli/pkg/commands/alias/logging/newrelicotlp\"\n\taliasopenstack \"github.com/fastly/cli/pkg/commands/alias/logging/openstack\"\n\taliaspapertrail \"github.com/fastly/cli/pkg/commands/alias/logging/papertrail\"\n\taliass3 \"github.com/fastly/cli/pkg/commands/alias/logging/s3\"\n\taliasscalyr \"github.com/fastly/cli/pkg/commands/alias/logging/scalyr\"\n\taliassftp \"github.com/fastly/cli/pkg/commands/alias/logging/sftp\"\n\taliassplunk \"github.com/fastly/cli/pkg/commands/alias/logging/splunk\"\n\taliassumologic \"github.com/fastly/cli/pkg/commands/alias/logging/sumologic\"\n\taliassyslog \"github.com/fastly/cli/pkg/commands/alias/logging/syslog\"\n\taliaspurge \"github.com/fastly/cli/pkg/commands/alias/purge\"\n\taliasratelimit \"github.com/fastly/cli/pkg/commands/alias/ratelimit\"\n\taliasresourcelink \"github.com/fastly/cli/pkg/commands/alias/resourcelink\"\n\taliasserviceauth \"github.com/fastly/cli/pkg/commands/alias/serviceauth\"\n\taliasserviceversion \"github.com/fastly/cli/pkg/commands/alias/serviceversion\"\n\taliasvcl \"github.com/fastly/cli/pkg/commands/alias/vcl\"\n\taliasvclcondition \"github.com/fastly/cli/pkg/commands/alias/vcl/condition\"\n\taliasvclcustom \"github.com/fastly/cli/pkg/commands/alias/vcl/custom\"\n\taliasvclsnippet \"github.com/fastly/cli/pkg/commands/alias/vcl/snippet\"\n\t\"github.com/fastly/cli/pkg/commands/apisecurity\"\n\t\"github.com/fastly/cli/pkg/commands/apisecurity/discoveredoperations\"\n\t\"github.com/fastly/cli/pkg/commands/apisecurity/operations\"\n\t\"github.com/fastly/cli/pkg/commands/apisecurity/tags\"\n\tauthcmd \"github.com/fastly/cli/pkg/commands/auth\"\n\t\"github.com/fastly/cli/pkg/commands/authtoken\"\n\t\"github.com/fastly/cli/pkg/commands/compute\"\n\t\"github.com/fastly/cli/pkg/commands/compute/computeacl\"\n\t\"github.com/fastly/cli/pkg/commands/config\"\n\t\"github.com/fastly/cli/pkg/commands/configstore\"\n\t\"github.com/fastly/cli/pkg/commands/configstoreentry\"\n\t\"github.com/fastly/cli/pkg/commands/dashboard\"\n\tdashboardItem \"github.com/fastly/cli/pkg/commands/dashboard/item\"\n\t\"github.com/fastly/cli/pkg/commands/domain\"\n\t\"github.com/fastly/cli/pkg/commands/install\"\n\t\"github.com/fastly/cli/pkg/commands/ip\"\n\t\"github.com/fastly/cli/pkg/commands/kvstore\"\n\t\"github.com/fastly/cli/pkg/commands/kvstoreentry\"\n\t\"github.com/fastly/cli/pkg/commands/logtail\"\n\t\"github.com/fastly/cli/pkg/commands/ngwaf\"\n\t\"github.com/fastly/cli/pkg/commands/ngwaf/countrylist\"\n\t\"github.com/fastly/cli/pkg/commands/ngwaf/customsignal\"\n\t\"github.com/fastly/cli/pkg/commands/ngwaf/iplist\"\n\t\"github.com/fastly/cli/pkg/commands/ngwaf/rule\"\n\t\"github.com/fastly/cli/pkg/commands/ngwaf/signallist\"\n\t\"github.com/fastly/cli/pkg/commands/ngwaf/stringlist\"\n\t\"github.com/fastly/cli/pkg/commands/ngwaf/wildcardlist\"\n\t\"github.com/fastly/cli/pkg/commands/ngwaf/workspace\"\n\t\"github.com/fastly/cli/pkg/commands/ngwaf/workspace/alert\"\n\tworkspaceAlertDatadog \"github.com/fastly/cli/pkg/commands/ngwaf/workspace/alert/datadog\"\n\tworkspaceAlertJira \"github.com/fastly/cli/pkg/commands/ngwaf/workspace/alert/jira\"\n\tworkspaceAlertMailinglist \"github.com/fastly/cli/pkg/commands/ngwaf/workspace/alert/mailinglist\"\n\tworkspaceAlertMicrosoftteams \"github.com/fastly/cli/pkg/commands/ngwaf/workspace/alert/microsoftteams\"\n\tworkspaceAlertOpsgenie \"github.com/fastly/cli/pkg/commands/ngwaf/workspace/alert/opsgenie\"\n\tworkspaceAlertPagerduty \"github.com/fastly/cli/pkg/commands/ngwaf/workspace/alert/pagerduty\"\n\tworkspaceAlertSlack \"github.com/fastly/cli/pkg/commands/ngwaf/workspace/alert/slack\"\n\tworkspaceAlertWebhook \"github.com/fastly/cli/pkg/commands/ngwaf/workspace/alert/webhook\"\n\twscountrylist \"github.com/fastly/cli/pkg/commands/ngwaf/workspace/countrylist\"\n\twscustomsignal \"github.com/fastly/cli/pkg/commands/ngwaf/workspace/customsignal\"\n\twsiplist \"github.com/fastly/cli/pkg/commands/ngwaf/workspace/iplist\"\n\t\"github.com/fastly/cli/pkg/commands/ngwaf/workspace/redaction\"\n\tworkspaceRule \"github.com/fastly/cli/pkg/commands/ngwaf/workspace/rule\"\n\twssignallistlist \"github.com/fastly/cli/pkg/commands/ngwaf/workspace/signallist\"\n\twsstringlistlist \"github.com/fastly/cli/pkg/commands/ngwaf/workspace/stringlist\"\n\t\"github.com/fastly/cli/pkg/commands/ngwaf/workspace/threshold\"\n\t\"github.com/fastly/cli/pkg/commands/ngwaf/workspace/virtualpatch\"\n\twswildcardlistlist \"github.com/fastly/cli/pkg/commands/ngwaf/workspace/wildcardlist\"\n\t\"github.com/fastly/cli/pkg/commands/objectstorage\"\n\t\"github.com/fastly/cli/pkg/commands/objectstorage/accesskeys\"\n\t\"github.com/fastly/cli/pkg/commands/pop\"\n\t\"github.com/fastly/cli/pkg/commands/products\"\n\t\"github.com/fastly/cli/pkg/commands/profile\"\n\t\"github.com/fastly/cli/pkg/commands/secretstore\"\n\t\"github.com/fastly/cli/pkg/commands/secretstoreentry\"\n\t\"github.com/fastly/cli/pkg/commands/service\"\n\tserviceacl \"github.com/fastly/cli/pkg/commands/service/acl\"\n\tserviceaclentry \"github.com/fastly/cli/pkg/commands/service/aclentry\"\n\tservicealert \"github.com/fastly/cli/pkg/commands/service/alert\"\n\tserviceauth \"github.com/fastly/cli/pkg/commands/service/auth\"\n\tservicebackend \"github.com/fastly/cli/pkg/commands/service/backend\"\n\tservicedictionary \"github.com/fastly/cli/pkg/commands/service/dictionary\"\n\tservicedictionaryentry \"github.com/fastly/cli/pkg/commands/service/dictionaryentry\"\n\tservicedomain \"github.com/fastly/cli/pkg/commands/service/domain\"\n\tservicehealthcheck \"github.com/fastly/cli/pkg/commands/service/healthcheck\"\n\tserviceimageoptimizerdefaults \"github.com/fastly/cli/pkg/commands/service/imageoptimizerdefaults\"\n\tservicelogging \"github.com/fastly/cli/pkg/commands/service/logging\"\n\tserviceloggingazureblob \"github.com/fastly/cli/pkg/commands/service/logging/azureblob\"\n\tserviceloggingbigquery \"github.com/fastly/cli/pkg/commands/service/logging/bigquery\"\n\tserviceloggingcloudfiles \"github.com/fastly/cli/pkg/commands/service/logging/cloudfiles\"\n\tserviceloggingdatadog \"github.com/fastly/cli/pkg/commands/service/logging/datadog\"\n\tserviceloggingdebug \"github.com/fastly/cli/pkg/commands/service/logging/debug\"\n\tserviceloggingdigitalocean \"github.com/fastly/cli/pkg/commands/service/logging/digitalocean\"\n\tserviceloggingelasticsearch \"github.com/fastly/cli/pkg/commands/service/logging/elasticsearch\"\n\tserviceloggingftp \"github.com/fastly/cli/pkg/commands/service/logging/ftp\"\n\tservicelogginggcs \"github.com/fastly/cli/pkg/commands/service/logging/gcs\"\n\tservicelogginggooglepubsub \"github.com/fastly/cli/pkg/commands/service/logging/googlepubsub\"\n\tservicelogginggrafanacloudlogs \"github.com/fastly/cli/pkg/commands/service/logging/grafanacloudlogs\"\n\tserviceloggingheroku \"github.com/fastly/cli/pkg/commands/service/logging/heroku\"\n\tservicelogginghoneycomb \"github.com/fastly/cli/pkg/commands/service/logging/honeycomb\"\n\tservicelogginghttps \"github.com/fastly/cli/pkg/commands/service/logging/https\"\n\tserviceloggingkafka \"github.com/fastly/cli/pkg/commands/service/logging/kafka\"\n\tserviceloggingkinesis \"github.com/fastly/cli/pkg/commands/service/logging/kinesis\"\n\tserviceloggingloggly \"github.com/fastly/cli/pkg/commands/service/logging/loggly\"\n\tservicelogginglogshuttle \"github.com/fastly/cli/pkg/commands/service/logging/logshuttle\"\n\tserviceloggingnewrelic \"github.com/fastly/cli/pkg/commands/service/logging/newrelic\"\n\tserviceloggingnewrelicotlp \"github.com/fastly/cli/pkg/commands/service/logging/newrelicotlp\"\n\tserviceloggingopenstack \"github.com/fastly/cli/pkg/commands/service/logging/openstack\"\n\tserviceloggingpapertrail \"github.com/fastly/cli/pkg/commands/service/logging/papertrail\"\n\tserviceloggings3 \"github.com/fastly/cli/pkg/commands/service/logging/s3\"\n\tserviceloggingscalyr \"github.com/fastly/cli/pkg/commands/service/logging/scalyr\"\n\tserviceloggingsftp \"github.com/fastly/cli/pkg/commands/service/logging/sftp\"\n\tserviceloggingsplunk \"github.com/fastly/cli/pkg/commands/service/logging/splunk\"\n\tserviceloggingsumologic \"github.com/fastly/cli/pkg/commands/service/logging/sumologic\"\n\tserviceloggingsyslog \"github.com/fastly/cli/pkg/commands/service/logging/syslog\"\n\tservicepurge \"github.com/fastly/cli/pkg/commands/service/purge\"\n\tserviceratelimit \"github.com/fastly/cli/pkg/commands/service/ratelimit\"\n\tserviceresourcelink \"github.com/fastly/cli/pkg/commands/service/resourcelink\"\n\tservicevcl \"github.com/fastly/cli/pkg/commands/service/vcl\"\n\tservicevclcondition \"github.com/fastly/cli/pkg/commands/service/vcl/condition\"\n\tservicevclcustom \"github.com/fastly/cli/pkg/commands/service/vcl/custom\"\n\tservicevclsnippet \"github.com/fastly/cli/pkg/commands/service/vcl/snippet\"\n\tserviceversion \"github.com/fastly/cli/pkg/commands/service/version\"\n\t\"github.com/fastly/cli/pkg/commands/shellcomplete\"\n\t\"github.com/fastly/cli/pkg/commands/sso\"\n\t\"github.com/fastly/cli/pkg/commands/stats\"\n\ttlsconfig \"github.com/fastly/cli/pkg/commands/tls/config\"\n\ttlscustom \"github.com/fastly/cli/pkg/commands/tls/custom\"\n\ttlscustomactivation \"github.com/fastly/cli/pkg/commands/tls/custom/activation\"\n\ttlscustomcertificate \"github.com/fastly/cli/pkg/commands/tls/custom/certificate\"\n\ttlscustomdomain \"github.com/fastly/cli/pkg/commands/tls/custom/domain\"\n\ttlscustomprivatekey \"github.com/fastly/cli/pkg/commands/tls/custom/privatekey\"\n\ttlsplatform \"github.com/fastly/cli/pkg/commands/tls/platform\"\n\ttlssubscription \"github.com/fastly/cli/pkg/commands/tls/subscription\"\n\t\"github.com/fastly/cli/pkg/commands/tools\"\n\tdomainTools \"github.com/fastly/cli/pkg/commands/tools/domain\"\n\t\"github.com/fastly/cli/pkg/commands/update\"\n\t\"github.com/fastly/cli/pkg/commands/user\"\n\t\"github.com/fastly/cli/pkg/commands/version\"\n\t\"github.com/fastly/cli/pkg/commands/whoami\"\n\t\"github.com/fastly/cli/pkg/env\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// Define constructs all the commands exposed by the CLI.\nfunc Define( // nolint:revive // function-length\n\tapp *kingpin.Application,\n\tdata *global.Data,\n) []argparser.Command {\n\tshellcompleteCmdRoot := shellcomplete.NewRootCommand(app, data)\n\n\tdisableAuthCmd := env.AuthCommandDisabled()\n\n\t// All authentication-related commands (auth, auth-token, sso, profile,\n\t// whoami) are skipped when FASTLY_DISABLE_AUTH_COMMAND is set.\n\tvar authCommands []argparser.Command\n\tvar ssoCommands []argparser.Command\n\tvar authtokenCommands []argparser.Command\n\tvar profileCommands []argparser.Command\n\tvar whoamiCommands []argparser.Command\n\n\tif !disableAuthCmd {\n\t\tssoCmdRoot := sso.NewRootCommand(app, data)\n\t\tssoCommands = []argparser.Command{ssoCmdRoot}\n\n\t\tauthCmdRoot := authcmd.NewRootCommand(app, data)\n\t\tauthLogin := authcmd.NewLoginCommand(authCmdRoot.CmdClause, data)\n\t\tauthAdd := authcmd.NewAddCommand(authCmdRoot.CmdClause, data)\n\t\tauthDelete := authcmd.NewDeleteCommand(authCmdRoot.CmdClause, data)\n\t\tauthList := authcmd.NewListCommand(authCmdRoot.CmdClause, data)\n\t\tauthShow := authcmd.NewShowCommand(authCmdRoot.CmdClause, data)\n\t\tauthUse := authcmd.NewUseCommand(authCmdRoot.CmdClause, data)\n\t\tauthRevoke := authcmd.NewRevokeCommand(authCmdRoot.CmdClause, data)\n\t\tauthToken := authcmd.NewTokenCommand(authCmdRoot.CmdClause, data)\n\t\tauthCommands = []argparser.Command{\n\t\t\tauthCmdRoot, authLogin, authAdd, authDelete,\n\t\t\tauthList, authShow, authUse, authRevoke, authToken,\n\t\t}\n\n\t\tauthtokenCmdRoot := authtoken.NewRootCommand(app, data)\n\t\tauthtokenCreate := authtoken.NewCreateCommand(authtokenCmdRoot.CmdClause, data)\n\t\tauthtokenDelete := authtoken.NewDeleteCommand(authtokenCmdRoot.CmdClause, data)\n\t\tauthtokenDescribe := authtoken.NewDescribeCommand(authtokenCmdRoot.CmdClause, data)\n\t\tauthtokenList := authtoken.NewListCommand(authtokenCmdRoot.CmdClause, data)\n\t\tauthtokenCommands = []argparser.Command{\n\t\t\tauthtokenCmdRoot, authtokenCreate, authtokenDelete,\n\t\t\tauthtokenDescribe, authtokenList,\n\t\t}\n\t}\n\n\t// API Security commands\n\tapisecurityRoot := apisecurity.NewRootCommand(app, data)\n\tdiscoveredoperationsRoot := discoveredoperations.NewRootCommand(apisecurityRoot.CmdClause, data)\n\tdiscoveredoperationsList := discoveredoperations.NewListCommand(discoveredoperationsRoot.CmdClause, data)\n\tdiscoveredoperationsUpdate := discoveredoperations.NewUpdateCommand(discoveredoperationsRoot.CmdClause, data)\n\toperationsRoot := operations.NewRootCommand(apisecurityRoot.CmdClause, data)\n\toperationsList := operations.NewListCommand(operationsRoot.CmdClause, data)\n\toperationsCreate := operations.NewCreateCommand(operationsRoot.CmdClause, data)\n\toperationsDescribe := operations.NewDescribeCommand(operationsRoot.CmdClause, data)\n\toperationsUpdate := operations.NewUpdateCommand(operationsRoot.CmdClause, data)\n\toperationsDelete := operations.NewDeleteCommand(operationsRoot.CmdClause, data)\n\toperationsAddTags := operations.NewAddTagsCommand(operationsRoot.CmdClause, data)\n\ttagsRoot := tags.NewRootCommand(apisecurityRoot.CmdClause, data)\n\ttagsCreate := tags.NewCreateCommand(tagsRoot.CmdClause, data)\n\ttagsDelete := tags.NewDeleteCommand(tagsRoot.CmdClause, data)\n\ttagsGet := tags.NewGetCommand(tagsRoot.CmdClause, data)\n\ttagsList := tags.NewListCommand(tagsRoot.CmdClause, data)\n\ttagsUpdate := tags.NewUpdateCommand(tagsRoot.CmdClause, data)\n\tcomputeCmdRoot := compute.NewRootCommand(app, data)\n\tcomputeACLCmdRoot := computeacl.NewRootCommand(computeCmdRoot.CmdClause, data)\n\tcomputeACLCreate := computeacl.NewCreateCommand(computeACLCmdRoot.CmdClause, data)\n\tcomputeACLList := computeacl.NewListCommand(computeACLCmdRoot.CmdClause, data)\n\tcomputeACLDescribe := computeacl.NewDescribeCommand(computeACLCmdRoot.CmdClause, data)\n\tcomputeACLUpdate := computeacl.NewUpdateCommand(computeACLCmdRoot.CmdClause, data)\n\tcomputeACLLookup := computeacl.NewLookupCommand(computeACLCmdRoot.CmdClause, data)\n\tcomputeACLDelete := computeacl.NewDeleteCommand(computeACLCmdRoot.CmdClause, data)\n\tcomputeACLEntriesList := computeacl.NewListEntriesCommand(computeACLCmdRoot.CmdClause, data)\n\tcomputeBuild := compute.NewBuildCommand(computeCmdRoot.CmdClause, data)\n\tcomputeDeploy := compute.NewDeployCommand(computeCmdRoot.CmdClause, data)\n\tcomputeHashFiles := compute.NewHashFilesCommand(computeCmdRoot.CmdClause, data, computeBuild)\n\tcomputeInit := compute.NewInitCommand(computeCmdRoot.CmdClause, data)\n\tcomputeMetadata := compute.NewMetadataCommand(computeCmdRoot.CmdClause, data)\n\tcomputePack := compute.NewPackCommand(computeCmdRoot.CmdClause, data)\n\tcomputePublish := compute.NewPublishCommand(computeCmdRoot.CmdClause, data, computeBuild, computeDeploy)\n\tcomputeServe := compute.NewServeCommand(computeCmdRoot.CmdClause, data, computeBuild)\n\tcomputeUpdate := compute.NewUpdateCommand(computeCmdRoot.CmdClause, data)\n\tcomputeValidate := compute.NewValidateCommand(computeCmdRoot.CmdClause, data)\n\tconfigCmdRoot := config.NewRootCommand(app, data)\n\tconfigstoreCmdRoot := configstore.NewRootCommand(app, data)\n\tconfigstoreCreate := configstore.NewCreateCommand(configstoreCmdRoot.CmdClause, data)\n\tconfigstoreDelete := configstore.NewDeleteCommand(configstoreCmdRoot.CmdClause, data)\n\tconfigstoreDescribe := configstore.NewDescribeCommand(configstoreCmdRoot.CmdClause, data)\n\tconfigstoreList := configstore.NewListCommand(configstoreCmdRoot.CmdClause, data)\n\tconfigstoreListServices := configstore.NewListServicesCommand(configstoreCmdRoot.CmdClause, data)\n\tconfigstoreUpdate := configstore.NewUpdateCommand(configstoreCmdRoot.CmdClause, data)\n\tconfigstoreentryCmdRoot := configstoreentry.NewRootCommand(app, data)\n\tconfigstoreentryCreate := configstoreentry.NewCreateCommand(configstoreentryCmdRoot.CmdClause, data)\n\tconfigstoreentryDelete := configstoreentry.NewDeleteCommand(configstoreentryCmdRoot.CmdClause, data)\n\tconfigstoreentryDescribe := configstoreentry.NewDescribeCommand(configstoreentryCmdRoot.CmdClause, data)\n\tconfigstoreentryList := configstoreentry.NewListCommand(configstoreentryCmdRoot.CmdClause, data)\n\tconfigstoreentryUpdate := configstoreentry.NewUpdateCommand(configstoreentryCmdRoot.CmdClause, data)\n\tdashboardCmdRoot := dashboard.NewRootCommand(app, data)\n\tdashboardList := dashboard.NewListCommand(dashboardCmdRoot.CmdClause, data)\n\tdashboardCreate := dashboard.NewCreateCommand(dashboardCmdRoot.CmdClause, data)\n\tdashboardDescribe := dashboard.NewDescribeCommand(dashboardCmdRoot.CmdClause, data)\n\tdashboardUpdate := dashboard.NewUpdateCommand(dashboardCmdRoot.CmdClause, data)\n\tdashboardDelete := dashboard.NewDeleteCommand(dashboardCmdRoot.CmdClause, data)\n\tdashboardItemCmdRoot := dashboardItem.NewRootCommand(dashboardCmdRoot.CmdClause, data)\n\tdashboardItemCreate := dashboardItem.NewCreateCommand(dashboardItemCmdRoot.CmdClause, data)\n\tdashboardItemDescribe := dashboardItem.NewDescribeCommand(dashboardItemCmdRoot.CmdClause, data)\n\tdashboardItemUpdate := dashboardItem.NewUpdateCommand(dashboardItemCmdRoot.CmdClause, data)\n\tdashboardItemDelete := dashboardItem.NewDeleteCommand(dashboardItemCmdRoot.CmdClause, data)\n\tdomainCmdRoot := domain.NewRootCommand(app, data)\n\tdomainCreate := domain.NewCreateCommand(domainCmdRoot.CmdClause, data)\n\tdomainDelete := domain.NewDeleteCommand(domainCmdRoot.CmdClause, data)\n\tdomainDescribe := domain.NewDescribeCommand(domainCmdRoot.CmdClause, data)\n\tdomainList := domain.NewListCommand(domainCmdRoot.CmdClause, data)\n\tdomainUpdate := domain.NewUpdateCommand(domainCmdRoot.CmdClause, data)\n\tinstallRoot := install.NewRootCommand(app, data)\n\tipCmdRoot := ip.NewRootCommand(app, data)\n\tkvstoreCmdRoot := kvstore.NewRootCommand(app, data)\n\tkvstoreCreate := kvstore.NewCreateCommand(kvstoreCmdRoot.CmdClause, data)\n\tkvstoreDelete := kvstore.NewDeleteCommand(kvstoreCmdRoot.CmdClause, data)\n\tkvstoreDescribe := kvstore.NewDescribeCommand(kvstoreCmdRoot.CmdClause, data)\n\tkvstoreList := kvstore.NewListCommand(kvstoreCmdRoot.CmdClause, data)\n\tkvstoreentryCmdRoot := kvstoreentry.NewRootCommand(app, data)\n\tkvstoreentryCreate := kvstoreentry.NewCreateCommand(kvstoreentryCmdRoot.CmdClause, data)\n\tkvstoreentryDelete := kvstoreentry.NewDeleteCommand(kvstoreentryCmdRoot.CmdClause, data)\n\tkvstoreentryGet := kvstoreentry.NewGetCommand(kvstoreentryCmdRoot.CmdClause, data)\n\tkvstoreentryDescribe := kvstoreentry.NewDescribeCommand(kvstoreentryCmdRoot.CmdClause, data)\n\tkvstoreentryList := kvstoreentry.NewListCommand(kvstoreentryCmdRoot.CmdClause, data)\n\tlogtailCmdRoot := logtail.NewRootCommand(app, data)\n\tngwafRoot := ngwaf.NewRootCommand(app, data)\n\tngwafWorkspaceRoot := workspace.NewRootCommand(ngwafRoot.CmdClause, data)\n\tngwafWorkspaceCreate := workspace.NewCreateCommand(ngwafWorkspaceRoot.CmdClause, data)\n\tngwafWorkspaceDelete := workspace.NewDeleteCommand(ngwafWorkspaceRoot.CmdClause, data)\n\tngwafWorkspaceGet := workspace.NewGetCommand(ngwafWorkspaceRoot.CmdClause, data)\n\tngwafWorkspaceList := workspace.NewListCommand(ngwafWorkspaceRoot.CmdClause, data)\n\tngwafWorkspaceUpdate := workspace.NewUpdateCommand(ngwafWorkspaceRoot.CmdClause, data)\n\tngwafRedactionRoot := redaction.NewRootCommand(ngwafWorkspaceRoot.CmdClause, data)\n\tngwafRedactionCreate := redaction.NewCreateCommand(ngwafRedactionRoot.CmdClause, data)\n\tngwafRedactionDelete := redaction.NewDeleteCommand(ngwafRedactionRoot.CmdClause, data)\n\tngwafRedactionList := redaction.NewListCommand(ngwafRedactionRoot.CmdClause, data)\n\tngwafRedactionRetrieve := redaction.NewRetrieveCommand(ngwafRedactionRoot.CmdClause, data)\n\tngwafRedactionUpdate := redaction.NewUpdateCommand(ngwafRedactionRoot.CmdClause, data)\n\tngwafCountryListRoot := countrylist.NewRootCommand(ngwafRoot.CmdClause, data)\n\tngwafCountryListCreate := countrylist.NewCreateCommand(ngwafCountryListRoot.CmdClause, data)\n\tngwafCountryListDelete := countrylist.NewDeleteCommand(ngwafCountryListRoot.CmdClause, data)\n\tngwafCountryListGet := countrylist.NewGetCommand(ngwafCountryListRoot.CmdClause, data)\n\tngwafCountryListList := countrylist.NewListCommand(ngwafCountryListRoot.CmdClause, data)\n\tngwafCountryListUpdate := countrylist.NewUpdateCommand(ngwafCountryListRoot.CmdClause, data)\n\tngwafCustomSignalRoot := customsignal.NewRootCommand(ngwafRoot.CmdClause, data)\n\tngwafCustomSignalCreate := customsignal.NewCreateCommand(ngwafCustomSignalRoot.CmdClause, data)\n\tngwafCustomSignalDelete := customsignal.NewDeleteCommand(ngwafCustomSignalRoot.CmdClause, data)\n\tngwafCustomSignalGet := customsignal.NewGetCommand(ngwafCustomSignalRoot.CmdClause, data)\n\tngwafCustomSignalList := customsignal.NewListCommand(ngwafCustomSignalRoot.CmdClause, data)\n\tngwafCustomSignalUpdate := customsignal.NewUpdateCommand(ngwafCustomSignalRoot.CmdClause, data)\n\tngwafIPListRoot := iplist.NewRootCommand(ngwafRoot.CmdClause, data)\n\tngwafIPListCreate := iplist.NewCreateCommand(ngwafIPListRoot.CmdClause, data)\n\tngwafIPListDelete := iplist.NewDeleteCommand(ngwafIPListRoot.CmdClause, data)\n\tngwafIPListGet := iplist.NewGetCommand(ngwafIPListRoot.CmdClause, data)\n\tngwafIPListList := iplist.NewListCommand(ngwafIPListRoot.CmdClause, data)\n\tngwafIPListUpdate := iplist.NewUpdateCommand(ngwafIPListRoot.CmdClause, data)\n\tngwafRuleRoot := rule.NewRootCommand(ngwafRoot.CmdClause, data)\n\tngwafRuleCreate := rule.NewCreateCommand(ngwafRuleRoot.CmdClause, data)\n\tngwafRuleDelete := rule.NewDeleteCommand(ngwafRuleRoot.CmdClause, data)\n\tngwafRuleGet := rule.NewGetCommand(ngwafRuleRoot.CmdClause, data)\n\tngwafRuleList := rule.NewListCommand(ngwafRuleRoot.CmdClause, data)\n\tngwafRuleUpdate := rule.NewUpdateCommand(ngwafRuleRoot.CmdClause, data)\n\tngwafSignalListRoot := signallist.NewRootCommand(ngwafRoot.CmdClause, data)\n\tngwafSignalListCreate := signallist.NewCreateCommand(ngwafSignalListRoot.CmdClause, data)\n\tngwafSignalListDelete := signallist.NewDeleteCommand(ngwafSignalListRoot.CmdClause, data)\n\tngwafSignalListGet := signallist.NewGetCommand(ngwafSignalListRoot.CmdClause, data)\n\tngwafSignalListList := signallist.NewListCommand(ngwafSignalListRoot.CmdClause, data)\n\tngwafSignalListUpdate := signallist.NewUpdateCommand(ngwafSignalListRoot.CmdClause, data)\n\tngwafStringListRoot := stringlist.NewRootCommand(ngwafRoot.CmdClause, data)\n\tngwafStringListCreate := stringlist.NewCreateCommand(ngwafStringListRoot.CmdClause, data)\n\tngwafStringListDelete := stringlist.NewDeleteCommand(ngwafStringListRoot.CmdClause, data)\n\tngwafStringListGet := stringlist.NewGetCommand(ngwafStringListRoot.CmdClause, data)\n\tngwafStringListList := stringlist.NewListCommand(ngwafStringListRoot.CmdClause, data)\n\tngwafStringListUpdate := stringlist.NewUpdateCommand(ngwafStringListRoot.CmdClause, data)\n\tngwafWildcardListRoot := wildcardlist.NewRootCommand(ngwafRoot.CmdClause, data)\n\tngwafWildcardListCreate := wildcardlist.NewCreateCommand(ngwafWildcardListRoot.CmdClause, data)\n\tngwafWildcardListDelete := wildcardlist.NewDeleteCommand(ngwafWildcardListRoot.CmdClause, data)\n\tngwafWildcardListGet := wildcardlist.NewGetCommand(ngwafWildcardListRoot.CmdClause, data)\n\tngwafWildcardListList := wildcardlist.NewListCommand(ngwafWildcardListRoot.CmdClause, data)\n\tngwafWildcardListUpdate := wildcardlist.NewUpdateCommand(ngwafWildcardListRoot.CmdClause, data)\n\tngwafWorkspaceCountryListRoot := wscountrylist.NewRootCommand(ngwafWorkspaceRoot.CmdClause, data)\n\tngwafWorkspaceCountryListCreate := wscountrylist.NewCreateCommand(ngwafWorkspaceCountryListRoot.CmdClause, data)\n\tngwafWorkspaceCountryListDelete := wscountrylist.NewDeleteCommand(ngwafWorkspaceCountryListRoot.CmdClause, data)\n\tngwafWorkspaceCountryListGet := wscountrylist.NewGetCommand(ngwafWorkspaceCountryListRoot.CmdClause, data)\n\tngwafWorkspaceCountryListList := wscountrylist.NewListCommand(ngwafWorkspaceCountryListRoot.CmdClause, data)\n\tngwafWorkspaceCountryListUpdate := wscountrylist.NewUpdateCommand(ngwafWorkspaceCountryListRoot.CmdClause, data)\n\tngwafWorkspaceCustomSignalRoot := wscustomsignal.NewRootCommand(ngwafWorkspaceRoot.CmdClause, data)\n\tngwafWorkspaceCustomSignalCreate := wscustomsignal.NewCreateCommand(ngwafWorkspaceCustomSignalRoot.CmdClause, data)\n\tngwafWorkspaceCustomSignalDelete := wscustomsignal.NewDeleteCommand(ngwafWorkspaceCustomSignalRoot.CmdClause, data)\n\tngwafWorkspaceCustomSignalGet := wscustomsignal.NewGetCommand(ngwafWorkspaceCustomSignalRoot.CmdClause, data)\n\tngwafWorkspaceCustomSignalList := wscustomsignal.NewListCommand(ngwafWorkspaceCustomSignalRoot.CmdClause, data)\n\tngwafWorkspaceCustomSignalUpdate := wscustomsignal.NewUpdateCommand(ngwafWorkspaceCustomSignalRoot.CmdClause, data)\n\tngwafWorkspaceIPListRoot := wsiplist.NewRootCommand(ngwafWorkspaceRoot.CmdClause, data)\n\tngwafWorkspaceIPListCreate := wsiplist.NewCreateCommand(ngwafWorkspaceIPListRoot.CmdClause, data)\n\tngwafWorkspaceIPListDelete := wsiplist.NewDeleteCommand(ngwafWorkspaceIPListRoot.CmdClause, data)\n\tngwafWorkspaceIPListGet := wsiplist.NewGetCommand(ngwafWorkspaceIPListRoot.CmdClause, data)\n\tngwafWorkspaceIPListList := wsiplist.NewListCommand(ngwafWorkspaceIPListRoot.CmdClause, data)\n\tngwafWorkspaceIPListUpdate := wsiplist.NewUpdateCommand(ngwafWorkspaceIPListRoot.CmdClause, data)\n\tngwafWorkspaceRuleRoot := workspaceRule.NewRootCommand(ngwafWorkspaceRoot.CmdClause, data)\n\tngwafWorkspaceRuleCreate := workspaceRule.NewCreateCommand(ngwafWorkspaceRuleRoot.CmdClause, data)\n\tngwafWorkspaceRuleDelete := workspaceRule.NewDeleteCommand(ngwafWorkspaceRuleRoot.CmdClause, data)\n\tngwafWorkspaceRuleGet := workspaceRule.NewGetCommand(ngwafWorkspaceRuleRoot.CmdClause, data)\n\tngwafWorkspaceRuleList := workspaceRule.NewListCommand(ngwafWorkspaceRuleRoot.CmdClause, data)\n\tngwafWorkspaceRuleUpdate := workspaceRule.NewUpdateCommand(ngwafWorkspaceRuleRoot.CmdClause, data)\n\tngwafWorkspaceSignalListRoot := wssignallistlist.NewRootCommand(ngwafWorkspaceRoot.CmdClause, data)\n\tngwafWorkspaceSignalListCreate := wssignallistlist.NewCreateCommand(ngwafWorkspaceSignalListRoot.CmdClause, data)\n\tngwafWorkspaceSignalListDelete := wssignallistlist.NewDeleteCommand(ngwafWorkspaceSignalListRoot.CmdClause, data)\n\tngwafWorkspaceSignalListGet := wssignallistlist.NewGetCommand(ngwafWorkspaceSignalListRoot.CmdClause, data)\n\tngwafWorkspaceSignalListList := wssignallistlist.NewListCommand(ngwafWorkspaceSignalListRoot.CmdClause, data)\n\tngwafWorkspaceSignalListUpdate := wssignallistlist.NewUpdateCommand(ngwafWorkspaceSignalListRoot.CmdClause, data)\n\tngwafWorkspaceStringListRoot := wsstringlistlist.NewRootCommand(ngwafWorkspaceRoot.CmdClause, data)\n\tngwafWorkspaceStringListCreate := wsstringlistlist.NewCreateCommand(ngwafWorkspaceStringListRoot.CmdClause, data)\n\tngwafWorkspaceStringListDelete := wsstringlistlist.NewDeleteCommand(ngwafWorkspaceStringListRoot.CmdClause, data)\n\tngwafWorkspaceStringListGet := wsstringlistlist.NewGetCommand(ngwafWorkspaceStringListRoot.CmdClause, data)\n\tngwafWorkspaceStringListList := wsstringlistlist.NewListCommand(ngwafWorkspaceStringListRoot.CmdClause, data)\n\tngwafWorkspaceStringListUpdate := wsstringlistlist.NewUpdateCommand(ngwafWorkspaceStringListRoot.CmdClause, data)\n\tngwafWorkspaceThresholdRoot := threshold.NewRootCommand(ngwafWorkspaceRoot.CmdClause, data)\n\tngwafWorkspaceThresholdCreate := threshold.NewCreateCommand(ngwafWorkspaceThresholdRoot.CmdClause, data)\n\tngwafWorkspaceThresholdDelete := threshold.NewDeleteCommand(ngwafWorkspaceThresholdRoot.CmdClause, data)\n\tngwafWorkspaceThresholdGet := threshold.NewGetCommand(ngwafWorkspaceThresholdRoot.CmdClause, data)\n\tngwafWorkspaceThresholdList := threshold.NewListCommand(ngwafWorkspaceThresholdRoot.CmdClause, data)\n\tngwafWorkspaceThresholdUpdate := threshold.NewUpdateCommand(ngwafWorkspaceThresholdRoot.CmdClause, data)\n\tngwafWorkspaceWildcardListRoot := wildcardlist.NewRootCommand(ngwafWorkspaceRoot.CmdClause, data)\n\tngwafWorkspaceWildcardListCreate := wswildcardlistlist.NewCreateCommand(ngwafWorkspaceWildcardListRoot.CmdClause, data)\n\tngwafWorkspaceWildcardListDelete := wswildcardlistlist.NewDeleteCommand(ngwafWorkspaceWildcardListRoot.CmdClause, data)\n\tngwafWorkspaceWildcardListGet := wswildcardlistlist.NewGetCommand(ngwafWorkspaceWildcardListRoot.CmdClause, data)\n\tngwafWorkspaceWildcardListList := wswildcardlistlist.NewListCommand(ngwafWorkspaceWildcardListRoot.CmdClause, data)\n\tngwafWorkspaceWildcardListUpdate := wswildcardlistlist.NewUpdateCommand(ngwafWorkspaceWildcardListRoot.CmdClause, data)\n\tngwafVirtualpatchRoot := virtualpatch.NewRootCommand(ngwafWorkspaceRoot.CmdClause, data)\n\tngwafVirtualpatchList := virtualpatch.NewListCommand(ngwafVirtualpatchRoot.CmdClause, data)\n\tngwafVirtualpatchUpdate := virtualpatch.NewUpdateCommand(ngwafVirtualpatchRoot.CmdClause, data)\n\tngwafVirtualpatchRetrieve := virtualpatch.NewRetrieveCommand(ngwafVirtualpatchRoot.CmdClause, data)\n\tngwafWorkspaceAlertRoot := alert.NewRootCommand(ngwafWorkspaceRoot.CmdClause, data)\n\tngwafWorkspaceAlertDatadogRoot := workspaceAlertDatadog.NewRootCommand(ngwafWorkspaceAlertRoot.CmdClause, data)\n\tngwafWorkspaceAlertDatadogCreate := workspaceAlertDatadog.NewCreateCommand(ngwafWorkspaceAlertDatadogRoot.CmdClause, data)\n\tngwafWorkspaceAlertDatadogDelete := workspaceAlertDatadog.NewDeleteCommand(ngwafWorkspaceAlertDatadogRoot.CmdClause, data)\n\tngwafWorkspaceAlertDatadogGet := workspaceAlertDatadog.NewGetCommand(ngwafWorkspaceAlertDatadogRoot.CmdClause, data)\n\tngwafWorkspaceAlertDatadogList := workspaceAlertDatadog.NewListCommand(ngwafWorkspaceAlertDatadogRoot.CmdClause, data)\n\tngwafWorkspaceAlertDatadogUpdate := workspaceAlertDatadog.NewUpdateCommand(ngwafWorkspaceAlertDatadogRoot.CmdClause, data)\n\tngwafWorkspaceAlertJiraRoot := workspaceAlertJira.NewRootCommand(ngwafWorkspaceAlertRoot.CmdClause, data)\n\tngwafWorkspaceAlertJiraCreate := workspaceAlertJira.NewCreateCommand(ngwafWorkspaceAlertJiraRoot.CmdClause, data)\n\tngwafWorkspaceAlertJiraDelete := workspaceAlertJira.NewDeleteCommand(ngwafWorkspaceAlertJiraRoot.CmdClause, data)\n\tngwafWorkspaceAlertJiraGet := workspaceAlertJira.NewGetCommand(ngwafWorkspaceAlertJiraRoot.CmdClause, data)\n\tngwafWorkspaceAlertJiraList := workspaceAlertJira.NewListCommand(ngwafWorkspaceAlertJiraRoot.CmdClause, data)\n\tngwafWorkspaceAlertJiraUpdate := workspaceAlertJira.NewUpdateCommand(ngwafWorkspaceAlertJiraRoot.CmdClause, data)\n\tngwafWorkspaceAlertMailinglistRoot := workspaceAlertMailinglist.NewRootCommand(ngwafWorkspaceAlertRoot.CmdClause, data)\n\tngwafWorkspaceAlertMailinglistCreate := workspaceAlertMailinglist.NewCreateCommand(ngwafWorkspaceAlertMailinglistRoot.CmdClause, data)\n\tngwafWorkspaceAlertMailinglistDelete := workspaceAlertMailinglist.NewDeleteCommand(ngwafWorkspaceAlertMailinglistRoot.CmdClause, data)\n\tngwafWorkspaceAlertMailinglistGet := workspaceAlertMailinglist.NewGetCommand(ngwafWorkspaceAlertMailinglistRoot.CmdClause, data)\n\tngwafWorkspaceAlertMailinglistList := workspaceAlertMailinglist.NewListCommand(ngwafWorkspaceAlertMailinglistRoot.CmdClause, data)\n\tngwafWorkspaceAlertMailinglistUpdate := workspaceAlertMailinglist.NewUpdateCommand(ngwafWorkspaceAlertMailinglistRoot.CmdClause, data)\n\tngwafWorkspaceAlertMicrosoftteamsRoot := workspaceAlertMicrosoftteams.NewRootCommand(ngwafWorkspaceAlertRoot.CmdClause, data)\n\tngwafWorkspaceAlertMicrosoftteamsCreate := workspaceAlertMicrosoftteams.NewCreateCommand(ngwafWorkspaceAlertMicrosoftteamsRoot.CmdClause, data)\n\tngwafWorkspaceAlertMicrosoftteamsDelete := workspaceAlertMicrosoftteams.NewDeleteCommand(ngwafWorkspaceAlertMicrosoftteamsRoot.CmdClause, data)\n\tngwafWorkspaceAlertMicrosoftteamsGet := workspaceAlertMicrosoftteams.NewGetCommand(ngwafWorkspaceAlertMicrosoftteamsRoot.CmdClause, data)\n\tngwafWorkspaceAlertMicrosoftteamsList := workspaceAlertMicrosoftteams.NewListCommand(ngwafWorkspaceAlertMicrosoftteamsRoot.CmdClause, data)\n\tngwafWorkspaceAlertMicrosoftteamsUpdate := workspaceAlertMicrosoftteams.NewUpdateCommand(ngwafWorkspaceAlertMicrosoftteamsRoot.CmdClause, data)\n\tngwafWorkspaceAlertOpsgenieRoot := workspaceAlertOpsgenie.NewRootCommand(ngwafWorkspaceAlertRoot.CmdClause, data)\n\tngwafWorkspaceAlertOpsgenieCreate := workspaceAlertOpsgenie.NewCreateCommand(ngwafWorkspaceAlertOpsgenieRoot.CmdClause, data)\n\tngwafWorkspaceAlertOpsgenieDelete := workspaceAlertOpsgenie.NewDeleteCommand(ngwafWorkspaceAlertOpsgenieRoot.CmdClause, data)\n\tngwafWorkspaceAlertOpsgenieGet := workspaceAlertOpsgenie.NewGetCommand(ngwafWorkspaceAlertOpsgenieRoot.CmdClause, data)\n\tngwafWorkspaceAlertOpsgenieList := workspaceAlertOpsgenie.NewListCommand(ngwafWorkspaceAlertOpsgenieRoot.CmdClause, data)\n\tngwafWorkspaceAlertOpsgenieUpdate := workspaceAlertOpsgenie.NewUpdateCommand(ngwafWorkspaceAlertOpsgenieRoot.CmdClause, data)\n\tngwafWorkspaceAlertPagerdutyRoot := workspaceAlertPagerduty.NewRootCommand(ngwafWorkspaceAlertRoot.CmdClause, data)\n\tngwafWorkspaceAlertPagerdutyCreate := workspaceAlertPagerduty.NewCreateCommand(ngwafWorkspaceAlertPagerdutyRoot.CmdClause, data)\n\tngwafWorkspaceAlertPagerdutyDelete := workspaceAlertPagerduty.NewDeleteCommand(ngwafWorkspaceAlertPagerdutyRoot.CmdClause, data)\n\tngwafWorkspaceAlertPagerdutyGet := workspaceAlertPagerduty.NewGetCommand(ngwafWorkspaceAlertPagerdutyRoot.CmdClause, data)\n\tngwafWorkspaceAlertPagerdutyList := workspaceAlertPagerduty.NewListCommand(ngwafWorkspaceAlertPagerdutyRoot.CmdClause, data)\n\tngwafWorkspaceAlertPagerdutyUpdate := workspaceAlertPagerduty.NewUpdateCommand(ngwafWorkspaceAlertPagerdutyRoot.CmdClause, data)\n\tngwafWorkspaceAlertSlackRoot := workspaceAlertSlack.NewRootCommand(ngwafWorkspaceAlertRoot.CmdClause, data)\n\tngwafWorkspaceAlertSlackCreate := workspaceAlertSlack.NewCreateCommand(ngwafWorkspaceAlertSlackRoot.CmdClause, data)\n\tngwafWorkspaceAlertSlackDelete := workspaceAlertSlack.NewDeleteCommand(ngwafWorkspaceAlertSlackRoot.CmdClause, data)\n\tngwafWorkspaceAlertSlackGet := workspaceAlertSlack.NewGetCommand(ngwafWorkspaceAlertSlackRoot.CmdClause, data)\n\tngwafWorkspaceAlertSlackList := workspaceAlertSlack.NewListCommand(ngwafWorkspaceAlertSlackRoot.CmdClause, data)\n\tngwafWorkspaceAlertSlackUpdate := workspaceAlertSlack.NewUpdateCommand(ngwafWorkspaceAlertSlackRoot.CmdClause, data)\n\tngwafWorkspaceAlertWebhookRoot := workspaceAlertWebhook.NewRootCommand(ngwafWorkspaceAlertRoot.CmdClause, data)\n\tngwafWorkspaceAlertWebhookCreate := workspaceAlertWebhook.NewCreateCommand(ngwafWorkspaceAlertWebhookRoot.CmdClause, data)\n\tngwafWorkspaceAlertWebhookDelete := workspaceAlertWebhook.NewDeleteCommand(ngwafWorkspaceAlertWebhookRoot.CmdClause, data)\n\tngwafWorkspaceAlertWebhookGet := workspaceAlertWebhook.NewGetCommand(ngwafWorkspaceAlertWebhookRoot.CmdClause, data)\n\tngwafWorkspaceAlertWebhookGetSigningKey := workspaceAlertWebhook.NewGetSigningKeyCommand(ngwafWorkspaceAlertWebhookRoot.CmdClause, data)\n\tngwafWorkspaceAlertWebhookList := workspaceAlertWebhook.NewListCommand(ngwafWorkspaceAlertWebhookRoot.CmdClause, data)\n\tngwafWorkspaceAlertWebhookRotateSigningKey := workspaceAlertWebhook.NewRotateSigningKeyCommand(ngwafWorkspaceAlertWebhookRoot.CmdClause, data)\n\tngwafWorkspaceAlertWebhookUpdate := workspaceAlertWebhook.NewUpdateCommand(ngwafWorkspaceAlertWebhookRoot.CmdClause, data)\n\tobjectStorageRoot := objectstorage.NewRootCommand(app, data)\n\tobjectStorageAccesskeysRoot := accesskeys.NewRootCommand(objectStorageRoot.CmdClause, data)\n\tobjectStorageAccesskeysCreate := accesskeys.NewCreateCommand(objectStorageAccesskeysRoot.CmdClause, data)\n\tobjectStorageAccesskeysDelete := accesskeys.NewDeleteCommand(objectStorageAccesskeysRoot.CmdClause, data)\n\tobjectStorageAccesskeysGet := accesskeys.NewGetCommand(objectStorageAccesskeysRoot.CmdClause, data)\n\tobjectStorageAccesskeysList := accesskeys.NewListCommand(objectStorageAccesskeysRoot.CmdClause, data)\n\tpopCmdRoot := pop.NewRootCommand(app, data)\n\tproductsCmdRoot := products.NewRootCommand(app, data)\n\tif !disableAuthCmd {\n\t\tprofileCmdRoot := profile.NewRootCommand(app, data)\n\t\tprofileCreate := profile.NewCreateCommand(profileCmdRoot.CmdClause, data)\n\t\tprofileDelete := profile.NewDeleteCommand(profileCmdRoot.CmdClause, data)\n\t\tprofileList := profile.NewListCommand(profileCmdRoot.CmdClause, data)\n\t\tprofileSwitch := profile.NewSwitchCommand(profileCmdRoot.CmdClause, data)\n\t\tprofileToken := profile.NewTokenCommand(profileCmdRoot.CmdClause, data)\n\t\tprofileUpdate := profile.NewUpdateCommand(profileCmdRoot.CmdClause, data)\n\t\tprofileCommands = []argparser.Command{\n\t\t\tprofileCmdRoot, profileCreate, profileDelete,\n\t\t\tprofileList, profileSwitch, profileToken, profileUpdate,\n\t\t}\n\t}\n\tsecretstoreCmdRoot := secretstore.NewRootCommand(app, data)\n\tsecretstoreCreate := secretstore.NewCreateCommand(secretstoreCmdRoot.CmdClause, data)\n\tsecretstoreDescribe := secretstore.NewDescribeCommand(secretstoreCmdRoot.CmdClause, data)\n\tsecretstoreDelete := secretstore.NewDeleteCommand(secretstoreCmdRoot.CmdClause, data)\n\tsecretstoreList := secretstore.NewListCommand(secretstoreCmdRoot.CmdClause, data)\n\tsecretstoreentryCmdRoot := secretstoreentry.NewRootCommand(app, data)\n\tsecretstoreentryCreate := secretstoreentry.NewCreateCommand(secretstoreentryCmdRoot.CmdClause, data)\n\tsecretstoreentryDescribe := secretstoreentry.NewDescribeCommand(secretstoreentryCmdRoot.CmdClause, data)\n\tsecretstoreentryDelete := secretstoreentry.NewDeleteCommand(secretstoreentryCmdRoot.CmdClause, data)\n\tsecretstoreentryList := secretstoreentry.NewListCommand(secretstoreentryCmdRoot.CmdClause, data)\n\tserviceCmdRoot := service.NewRootCommand(app, data)\n\tserviceCreate := service.NewCreateCommand(serviceCmdRoot.CmdClause, data)\n\tserviceDelete := service.NewDeleteCommand(serviceCmdRoot.CmdClause, data)\n\tserviceDescribe := service.NewDescribeCommand(serviceCmdRoot.CmdClause, data)\n\tserviceList := service.NewListCommand(serviceCmdRoot.CmdClause, data)\n\tserviceSearch := service.NewSearchCommand(serviceCmdRoot.CmdClause, data)\n\tserviceUpdate := service.NewUpdateCommand(serviceCmdRoot.CmdClause, data)\n\tservicePurge := servicepurge.NewPurgeCommand(serviceCmdRoot.CmdClause, data)\n\tservicealertCmdRoot := servicealert.NewRootCommand(serviceCmdRoot.CmdClause, data)\n\tservicealertCreate := servicealert.NewCreateCommand(servicealertCmdRoot.CmdClause, data)\n\tservicealertDelete := servicealert.NewDeleteCommand(servicealertCmdRoot.CmdClause, data)\n\tservicealertDescribe := servicealert.NewDescribeCommand(servicealertCmdRoot.CmdClause, data)\n\tservicealertList := servicealert.NewListCommand(servicealertCmdRoot.CmdClause, data)\n\tservicealertListHistory := servicealert.NewListHistoryCommand(servicealertCmdRoot.CmdClause, data)\n\tservicealertUpdate := servicealert.NewUpdateCommand(servicealertCmdRoot.CmdClause, data)\n\tserviceaclCmdRoot := serviceacl.NewRootCommand(serviceCmdRoot.CmdClause, data)\n\tserviceaclCreate := serviceacl.NewCreateCommand(serviceaclCmdRoot.CmdClause, data)\n\tserviceaclDelete := serviceacl.NewDeleteCommand(serviceaclCmdRoot.CmdClause, data)\n\tserviceaclDescribe := serviceacl.NewDescribeCommand(serviceaclCmdRoot.CmdClause, data)\n\tserviceaclList := serviceacl.NewListCommand(serviceaclCmdRoot.CmdClause, data)\n\tserviceaclUpdate := serviceacl.NewUpdateCommand(serviceaclCmdRoot.CmdClause, data)\n\tserviceaclentryCmdRoot := serviceaclentry.NewRootCommand(serviceCmdRoot.CmdClause, data)\n\tserviceaclentryCreate := serviceaclentry.NewCreateCommand(serviceaclentryCmdRoot.CmdClause, data)\n\tserviceaclentryDelete := serviceaclentry.NewDeleteCommand(serviceaclentryCmdRoot.CmdClause, data)\n\tserviceaclentryDescribe := serviceaclentry.NewDescribeCommand(serviceaclentryCmdRoot.CmdClause, data)\n\tserviceaclentryList := serviceaclentry.NewListCommand(serviceaclentryCmdRoot.CmdClause, data)\n\tserviceaclentryUpdate := serviceaclentry.NewUpdateCommand(serviceaclentryCmdRoot.CmdClause, data)\n\tserviceauthCmdRoot := serviceauth.NewRootCommand(serviceCmdRoot.CmdClause, data)\n\tserviceauthCreate := serviceauth.NewCreateCommand(serviceauthCmdRoot.CmdClause, data)\n\tserviceauthDelete := serviceauth.NewDeleteCommand(serviceauthCmdRoot.CmdClause, data)\n\tserviceauthDescribe := serviceauth.NewDescribeCommand(serviceauthCmdRoot.CmdClause, data)\n\tserviceauthList := serviceauth.NewListCommand(serviceauthCmdRoot.CmdClause, data)\n\tserviceauthUpdate := serviceauth.NewUpdateCommand(serviceauthCmdRoot.CmdClause, data)\n\tservicedictionaryCmdRoot := servicedictionary.NewRootCommand(serviceCmdRoot.CmdClause, data)\n\tservicedictionaryCreate := servicedictionary.NewCreateCommand(servicedictionaryCmdRoot.CmdClause, data)\n\tservicedictionaryDelete := servicedictionary.NewDeleteCommand(servicedictionaryCmdRoot.CmdClause, data)\n\tservicedictionaryDescribe := servicedictionary.NewDescribeCommand(servicedictionaryCmdRoot.CmdClause, data)\n\tservicedictionaryList := servicedictionary.NewListCommand(servicedictionaryCmdRoot.CmdClause, data)\n\tservicedictionaryUpdate := servicedictionary.NewUpdateCommand(servicedictionaryCmdRoot.CmdClause, data)\n\tservicevclCmdRoot := servicevcl.NewRootCommand(serviceCmdRoot.CmdClause, data)\n\tservicevclDescribe := servicevcl.NewDescribeCommand(servicevclCmdRoot.CmdClause, data)\n\tservicevclConditionCmdRoot := servicevclcondition.NewRootCommand(servicevclCmdRoot.CmdClause, data)\n\tservicevclConditionCreate := servicevclcondition.NewCreateCommand(servicevclConditionCmdRoot.CmdClause, data)\n\tservicevclConditionDelete := servicevclcondition.NewDeleteCommand(servicevclConditionCmdRoot.CmdClause, data)\n\tservicevclConditionDescribe := servicevclcondition.NewDescribeCommand(servicevclConditionCmdRoot.CmdClause, data)\n\tservicevclConditionList := servicevclcondition.NewListCommand(servicevclConditionCmdRoot.CmdClause, data)\n\tservicevclConditionUpdate := servicevclcondition.NewUpdateCommand(servicevclConditionCmdRoot.CmdClause, data)\n\tservicevclCustomCmdRoot := servicevclcustom.NewRootCommand(servicevclCmdRoot.CmdClause, data)\n\tservicevclCustomCreate := servicevclcustom.NewCreateCommand(servicevclCustomCmdRoot.CmdClause, data)\n\tservicevclCustomDelete := servicevclcustom.NewDeleteCommand(servicevclCustomCmdRoot.CmdClause, data)\n\tservicevclCustomDescribe := servicevclcustom.NewDescribeCommand(servicevclCustomCmdRoot.CmdClause, data)\n\tservicevclCustomList := servicevclcustom.NewListCommand(servicevclCustomCmdRoot.CmdClause, data)\n\tservicevclCustomUpdate := servicevclcustom.NewUpdateCommand(servicevclCustomCmdRoot.CmdClause, data)\n\tservicevclSnippetCmdRoot := servicevclsnippet.NewRootCommand(servicevclCmdRoot.CmdClause, data)\n\tservicevclSnippetCreate := servicevclsnippet.NewCreateCommand(servicevclSnippetCmdRoot.CmdClause, data)\n\tservicevclSnippetDelete := servicevclsnippet.NewDeleteCommand(servicevclSnippetCmdRoot.CmdClause, data)\n\tservicevclSnippetDescribe := servicevclsnippet.NewDescribeCommand(servicevclSnippetCmdRoot.CmdClause, data)\n\tservicevclSnippetList := servicevclsnippet.NewListCommand(servicevclSnippetCmdRoot.CmdClause, data)\n\tservicevclSnippetUpdate := servicevclsnippet.NewUpdateCommand(servicevclSnippetCmdRoot.CmdClause, data)\n\tserviceloggingCmdRoot := servicelogging.NewRootCommand(serviceCmdRoot.CmdClause, data)\n\tserviceloggingDebugCmd := serviceloggingdebug.NewDebugCommand(serviceloggingCmdRoot.CmdClause, data)\n\tserviceloggingAzureblobCmdRoot := serviceloggingazureblob.NewRootCommand(serviceloggingCmdRoot.CmdClause, data)\n\tserviceloggingAzureblobCreate := serviceloggingazureblob.NewCreateCommand(serviceloggingAzureblobCmdRoot.CmdClause, data)\n\tserviceloggingAzureblobDelete := serviceloggingazureblob.NewDeleteCommand(serviceloggingAzureblobCmdRoot.CmdClause, data)\n\tserviceloggingAzureblobDescribe := serviceloggingazureblob.NewDescribeCommand(serviceloggingAzureblobCmdRoot.CmdClause, data)\n\tserviceloggingAzureblobList := serviceloggingazureblob.NewListCommand(serviceloggingAzureblobCmdRoot.CmdClause, data)\n\tserviceloggingAzureblobUpdate := serviceloggingazureblob.NewUpdateCommand(serviceloggingAzureblobCmdRoot.CmdClause, data)\n\tserviceloggingBigQueryCmdRoot := serviceloggingbigquery.NewRootCommand(serviceloggingCmdRoot.CmdClause, data)\n\tserviceloggingBigQueryCreate := serviceloggingbigquery.NewCreateCommand(serviceloggingBigQueryCmdRoot.CmdClause, data)\n\tserviceloggingBigQueryDelete := serviceloggingbigquery.NewDeleteCommand(serviceloggingBigQueryCmdRoot.CmdClause, data)\n\tserviceloggingBigQueryDescribe := serviceloggingbigquery.NewDescribeCommand(serviceloggingBigQueryCmdRoot.CmdClause, data)\n\tserviceloggingBigQueryList := serviceloggingbigquery.NewListCommand(serviceloggingBigQueryCmdRoot.CmdClause, data)\n\tserviceloggingBigQueryUpdate := serviceloggingbigquery.NewUpdateCommand(serviceloggingBigQueryCmdRoot.CmdClause, data)\n\tserviceloggingCloudfilesCmdRoot := serviceloggingcloudfiles.NewRootCommand(serviceloggingCmdRoot.CmdClause, data)\n\tserviceloggingCloudfilesCreate := serviceloggingcloudfiles.NewCreateCommand(serviceloggingCloudfilesCmdRoot.CmdClause, data)\n\tserviceloggingCloudfilesDelete := serviceloggingcloudfiles.NewDeleteCommand(serviceloggingCloudfilesCmdRoot.CmdClause, data)\n\tserviceloggingCloudfilesDescribe := serviceloggingcloudfiles.NewDescribeCommand(serviceloggingCloudfilesCmdRoot.CmdClause, data)\n\tserviceloggingCloudfilesList := serviceloggingcloudfiles.NewListCommand(serviceloggingCloudfilesCmdRoot.CmdClause, data)\n\tserviceloggingCloudfilesUpdate := serviceloggingcloudfiles.NewUpdateCommand(serviceloggingCloudfilesCmdRoot.CmdClause, data)\n\tserviceloggingDatadogCmdRoot := serviceloggingdatadog.NewRootCommand(serviceloggingCmdRoot.CmdClause, data)\n\tserviceloggingDatadogCreate := serviceloggingdatadog.NewCreateCommand(serviceloggingDatadogCmdRoot.CmdClause, data)\n\tserviceloggingDatadogDelete := serviceloggingdatadog.NewDeleteCommand(serviceloggingDatadogCmdRoot.CmdClause, data)\n\tserviceloggingDatadogDescribe := serviceloggingdatadog.NewDescribeCommand(serviceloggingDatadogCmdRoot.CmdClause, data)\n\tserviceloggingDatadogList := serviceloggingdatadog.NewListCommand(serviceloggingDatadogCmdRoot.CmdClause, data)\n\tserviceloggingDatadogUpdate := serviceloggingdatadog.NewUpdateCommand(serviceloggingDatadogCmdRoot.CmdClause, data)\n\tserviceloggingDigitaloceanCmdRoot := serviceloggingdigitalocean.NewRootCommand(serviceloggingCmdRoot.CmdClause, data)\n\tserviceloggingDigitaloceanCreate := serviceloggingdigitalocean.NewCreateCommand(serviceloggingDigitaloceanCmdRoot.CmdClause, data)\n\tserviceloggingDigitaloceanDelete := serviceloggingdigitalocean.NewDeleteCommand(serviceloggingDigitaloceanCmdRoot.CmdClause, data)\n\tserviceloggingDigitaloceanDescribe := serviceloggingdigitalocean.NewDescribeCommand(serviceloggingDigitaloceanCmdRoot.CmdClause, data)\n\tserviceloggingDigitaloceanList := serviceloggingdigitalocean.NewListCommand(serviceloggingDigitaloceanCmdRoot.CmdClause, data)\n\tserviceloggingDigitaloceanUpdate := serviceloggingdigitalocean.NewUpdateCommand(serviceloggingDigitaloceanCmdRoot.CmdClause, data)\n\tserviceloggingElasticsearchCmdRoot := serviceloggingelasticsearch.NewRootCommand(serviceloggingCmdRoot.CmdClause, data)\n\tserviceloggingElasticsearchCreate := serviceloggingelasticsearch.NewCreateCommand(serviceloggingElasticsearchCmdRoot.CmdClause, data)\n\tserviceloggingElasticsearchDelete := serviceloggingelasticsearch.NewDeleteCommand(serviceloggingElasticsearchCmdRoot.CmdClause, data)\n\tserviceloggingElasticsearchDescribe := serviceloggingelasticsearch.NewDescribeCommand(serviceloggingElasticsearchCmdRoot.CmdClause, data)\n\tserviceloggingElasticsearchList := serviceloggingelasticsearch.NewListCommand(serviceloggingElasticsearchCmdRoot.CmdClause, data)\n\tserviceloggingElasticsearchUpdate := serviceloggingelasticsearch.NewUpdateCommand(serviceloggingElasticsearchCmdRoot.CmdClause, data)\n\tserviceloggingFtpCmdRoot := serviceloggingftp.NewRootCommand(serviceloggingCmdRoot.CmdClause, data)\n\tserviceloggingFtpCreate := serviceloggingftp.NewCreateCommand(serviceloggingFtpCmdRoot.CmdClause, data)\n\tserviceloggingFtpDelete := serviceloggingftp.NewDeleteCommand(serviceloggingFtpCmdRoot.CmdClause, data)\n\tserviceloggingFtpDescribe := serviceloggingftp.NewDescribeCommand(serviceloggingFtpCmdRoot.CmdClause, data)\n\tserviceloggingFtpList := serviceloggingftp.NewListCommand(serviceloggingFtpCmdRoot.CmdClause, data)\n\tserviceloggingFtpUpdate := serviceloggingftp.NewUpdateCommand(serviceloggingFtpCmdRoot.CmdClause, data)\n\tserviceloggingGcsCmdRoot := servicelogginggcs.NewRootCommand(serviceloggingCmdRoot.CmdClause, data)\n\tserviceloggingGcsCreate := servicelogginggcs.NewCreateCommand(serviceloggingGcsCmdRoot.CmdClause, data)\n\tserviceloggingGcsDelete := servicelogginggcs.NewDeleteCommand(serviceloggingGcsCmdRoot.CmdClause, data)\n\tserviceloggingGcsDescribe := servicelogginggcs.NewDescribeCommand(serviceloggingGcsCmdRoot.CmdClause, data)\n\tserviceloggingGcsList := servicelogginggcs.NewListCommand(serviceloggingGcsCmdRoot.CmdClause, data)\n\tserviceloggingGcsUpdate := servicelogginggcs.NewUpdateCommand(serviceloggingGcsCmdRoot.CmdClause, data)\n\tserviceloggingGooglepubsubCmdRoot := servicelogginggooglepubsub.NewRootCommand(serviceloggingCmdRoot.CmdClause, data)\n\tserviceloggingGooglepubsubCreate := servicelogginggooglepubsub.NewCreateCommand(serviceloggingGooglepubsubCmdRoot.CmdClause, data)\n\tserviceloggingGooglepubsubDelete := servicelogginggooglepubsub.NewDeleteCommand(serviceloggingGooglepubsubCmdRoot.CmdClause, data)\n\tserviceloggingGooglepubsubDescribe := servicelogginggooglepubsub.NewDescribeCommand(serviceloggingGooglepubsubCmdRoot.CmdClause, data)\n\tserviceloggingGooglepubsubList := servicelogginggooglepubsub.NewListCommand(serviceloggingGooglepubsubCmdRoot.CmdClause, data)\n\tserviceloggingGooglepubsubUpdate := servicelogginggooglepubsub.NewUpdateCommand(serviceloggingGooglepubsubCmdRoot.CmdClause, data)\n\tserviceloggingGrafanacloudlogsCmdRoot := servicelogginggrafanacloudlogs.NewRootCommand(serviceloggingCmdRoot.CmdClause, data)\n\tserviceloggingGrafanacloudlogsCreate := servicelogginggrafanacloudlogs.NewCreateCommand(serviceloggingGrafanacloudlogsCmdRoot.CmdClause, data)\n\tserviceloggingGrafanacloudlogsDelete := servicelogginggrafanacloudlogs.NewDeleteCommand(serviceloggingGrafanacloudlogsCmdRoot.CmdClause, data)\n\tserviceloggingGrafanacloudlogsDescribe := servicelogginggrafanacloudlogs.NewDescribeCommand(serviceloggingGrafanacloudlogsCmdRoot.CmdClause, data)\n\tserviceloggingGrafanacloudlogsList := servicelogginggrafanacloudlogs.NewListCommand(serviceloggingGrafanacloudlogsCmdRoot.CmdClause, data)\n\tserviceloggingGrafanacloudlogsUpdate := servicelogginggrafanacloudlogs.NewUpdateCommand(serviceloggingGrafanacloudlogsCmdRoot.CmdClause, data)\n\tserviceloggingHerokuCmdRoot := serviceloggingheroku.NewRootCommand(serviceloggingCmdRoot.CmdClause, data)\n\tserviceloggingHerokuCreate := serviceloggingheroku.NewCreateCommand(serviceloggingHerokuCmdRoot.CmdClause, data)\n\tserviceloggingHerokuDelete := serviceloggingheroku.NewDeleteCommand(serviceloggingHerokuCmdRoot.CmdClause, data)\n\tserviceloggingHerokuDescribe := serviceloggingheroku.NewDescribeCommand(serviceloggingHerokuCmdRoot.CmdClause, data)\n\tserviceloggingHerokuList := serviceloggingheroku.NewListCommand(serviceloggingHerokuCmdRoot.CmdClause, data)\n\tserviceloggingHerokuUpdate := serviceloggingheroku.NewUpdateCommand(serviceloggingHerokuCmdRoot.CmdClause, data)\n\tserviceloggingHoneycombCmdRoot := servicelogginghoneycomb.NewRootCommand(serviceloggingCmdRoot.CmdClause, data)\n\tserviceloggingHoneycombCreate := servicelogginghoneycomb.NewCreateCommand(serviceloggingHoneycombCmdRoot.CmdClause, data)\n\tserviceloggingHoneycombDelete := servicelogginghoneycomb.NewDeleteCommand(serviceloggingHoneycombCmdRoot.CmdClause, data)\n\tserviceloggingHoneycombDescribe := servicelogginghoneycomb.NewDescribeCommand(serviceloggingHoneycombCmdRoot.CmdClause, data)\n\tserviceloggingHoneycombList := servicelogginghoneycomb.NewListCommand(serviceloggingHoneycombCmdRoot.CmdClause, data)\n\tserviceloggingHoneycombUpdate := servicelogginghoneycomb.NewUpdateCommand(serviceloggingHoneycombCmdRoot.CmdClause, data)\n\tserviceloggingHTTPSCmdRoot := servicelogginghttps.NewRootCommand(serviceloggingCmdRoot.CmdClause, data)\n\tserviceloggingHTTPSCreate := servicelogginghttps.NewCreateCommand(serviceloggingHTTPSCmdRoot.CmdClause, data)\n\tserviceloggingHTTPSDelete := servicelogginghttps.NewDeleteCommand(serviceloggingHTTPSCmdRoot.CmdClause, data)\n\tserviceloggingHTTPSDescribe := servicelogginghttps.NewDescribeCommand(serviceloggingHTTPSCmdRoot.CmdClause, data)\n\tserviceloggingHTTPSList := servicelogginghttps.NewListCommand(serviceloggingHTTPSCmdRoot.CmdClause, data)\n\tserviceloggingHTTPSUpdate := servicelogginghttps.NewUpdateCommand(serviceloggingHTTPSCmdRoot.CmdClause, data)\n\tserviceloggingKafkaCmdRoot := serviceloggingkafka.NewRootCommand(serviceloggingCmdRoot.CmdClause, data)\n\tserviceloggingKafkaCreate := serviceloggingkafka.NewCreateCommand(serviceloggingKafkaCmdRoot.CmdClause, data)\n\tserviceloggingKafkaDelete := serviceloggingkafka.NewDeleteCommand(serviceloggingKafkaCmdRoot.CmdClause, data)\n\tserviceloggingKafkaDescribe := serviceloggingkafka.NewDescribeCommand(serviceloggingKafkaCmdRoot.CmdClause, data)\n\tserviceloggingKafkaList := serviceloggingkafka.NewListCommand(serviceloggingKafkaCmdRoot.CmdClause, data)\n\tserviceloggingKafkaUpdate := serviceloggingkafka.NewUpdateCommand(serviceloggingKafkaCmdRoot.CmdClause, data)\n\tserviceloggingKinesisCmdRoot := serviceloggingkinesis.NewRootCommand(serviceloggingCmdRoot.CmdClause, data)\n\tserviceloggingKinesisCreate := serviceloggingkinesis.NewCreateCommand(serviceloggingKinesisCmdRoot.CmdClause, data)\n\tserviceloggingKinesisDelete := serviceloggingkinesis.NewDeleteCommand(serviceloggingKinesisCmdRoot.CmdClause, data)\n\tserviceloggingKinesisDescribe := serviceloggingkinesis.NewDescribeCommand(serviceloggingKinesisCmdRoot.CmdClause, data)\n\tserviceloggingKinesisList := serviceloggingkinesis.NewListCommand(serviceloggingKinesisCmdRoot.CmdClause, data)\n\tserviceloggingKinesisUpdate := serviceloggingkinesis.NewUpdateCommand(serviceloggingKinesisCmdRoot.CmdClause, data)\n\tserviceloggingLogglyCmdRoot := serviceloggingloggly.NewRootCommand(serviceloggingCmdRoot.CmdClause, data)\n\tserviceloggingLogglyCreate := serviceloggingloggly.NewCreateCommand(serviceloggingLogglyCmdRoot.CmdClause, data)\n\tserviceloggingLogglyDelete := serviceloggingloggly.NewDeleteCommand(serviceloggingLogglyCmdRoot.CmdClause, data)\n\tserviceloggingLogglyDescribe := serviceloggingloggly.NewDescribeCommand(serviceloggingLogglyCmdRoot.CmdClause, data)\n\tserviceloggingLogglyList := serviceloggingloggly.NewListCommand(serviceloggingLogglyCmdRoot.CmdClause, data)\n\tserviceloggingLogglyUpdate := serviceloggingloggly.NewUpdateCommand(serviceloggingLogglyCmdRoot.CmdClause, data)\n\tserviceloggingLogshuttleCmdRoot := servicelogginglogshuttle.NewRootCommand(serviceloggingCmdRoot.CmdClause, data)\n\tserviceloggingLogshuttleCreate := servicelogginglogshuttle.NewCreateCommand(serviceloggingLogshuttleCmdRoot.CmdClause, data)\n\tserviceloggingLogshuttleDelete := servicelogginglogshuttle.NewDeleteCommand(serviceloggingLogshuttleCmdRoot.CmdClause, data)\n\tserviceloggingLogshuttleDescribe := servicelogginglogshuttle.NewDescribeCommand(serviceloggingLogshuttleCmdRoot.CmdClause, data)\n\tserviceloggingLogshuttleList := servicelogginglogshuttle.NewListCommand(serviceloggingLogshuttleCmdRoot.CmdClause, data)\n\tserviceloggingLogshuttleUpdate := servicelogginglogshuttle.NewUpdateCommand(serviceloggingLogshuttleCmdRoot.CmdClause, data)\n\tserviceloggingNewRelicCmdRoot := serviceloggingnewrelic.NewRootCommand(serviceloggingCmdRoot.CmdClause, data)\n\tserviceloggingNewRelicCreate := serviceloggingnewrelic.NewCreateCommand(serviceloggingNewRelicCmdRoot.CmdClause, data)\n\tserviceloggingNewRelicDelete := serviceloggingnewrelic.NewDeleteCommand(serviceloggingNewRelicCmdRoot.CmdClause, data)\n\tserviceloggingNewRelicDescribe := serviceloggingnewrelic.NewDescribeCommand(serviceloggingNewRelicCmdRoot.CmdClause, data)\n\tserviceloggingNewRelicList := serviceloggingnewrelic.NewListCommand(serviceloggingNewRelicCmdRoot.CmdClause, data)\n\tserviceloggingNewRelicUpdate := serviceloggingnewrelic.NewUpdateCommand(serviceloggingNewRelicCmdRoot.CmdClause, data)\n\tserviceloggingNewRelicOTLPCmdRoot := serviceloggingnewrelicotlp.NewRootCommand(serviceloggingCmdRoot.CmdClause, data)\n\tserviceloggingNewRelicOTLPCreate := serviceloggingnewrelicotlp.NewCreateCommand(serviceloggingNewRelicOTLPCmdRoot.CmdClause, data)\n\tserviceloggingNewRelicOTLPDelete := serviceloggingnewrelicotlp.NewDeleteCommand(serviceloggingNewRelicOTLPCmdRoot.CmdClause, data)\n\tserviceloggingNewRelicOTLPDescribe := serviceloggingnewrelicotlp.NewDescribeCommand(serviceloggingNewRelicOTLPCmdRoot.CmdClause, data)\n\tserviceloggingNewRelicOTLPList := serviceloggingnewrelicotlp.NewListCommand(serviceloggingNewRelicOTLPCmdRoot.CmdClause, data)\n\tserviceloggingNewRelicOTLPUpdate := serviceloggingnewrelicotlp.NewUpdateCommand(serviceloggingNewRelicOTLPCmdRoot.CmdClause, data)\n\tserviceloggingOpenstackCmdRoot := serviceloggingopenstack.NewRootCommand(serviceloggingCmdRoot.CmdClause, data)\n\tserviceloggingOpenstackCreate := serviceloggingopenstack.NewCreateCommand(serviceloggingOpenstackCmdRoot.CmdClause, data)\n\tserviceloggingOpenstackDelete := serviceloggingopenstack.NewDeleteCommand(serviceloggingOpenstackCmdRoot.CmdClause, data)\n\tserviceloggingOpenstackDescribe := serviceloggingopenstack.NewDescribeCommand(serviceloggingOpenstackCmdRoot.CmdClause, data)\n\tserviceloggingOpenstackList := serviceloggingopenstack.NewListCommand(serviceloggingOpenstackCmdRoot.CmdClause, data)\n\tserviceloggingOpenstackUpdate := serviceloggingopenstack.NewUpdateCommand(serviceloggingOpenstackCmdRoot.CmdClause, data)\n\tserviceloggingPapertrailCmdRoot := serviceloggingpapertrail.NewRootCommand(serviceloggingCmdRoot.CmdClause, data)\n\tserviceloggingPapertrailCreate := serviceloggingpapertrail.NewCreateCommand(serviceloggingPapertrailCmdRoot.CmdClause, data)\n\tserviceloggingPapertrailDelete := serviceloggingpapertrail.NewDeleteCommand(serviceloggingPapertrailCmdRoot.CmdClause, data)\n\tserviceloggingPapertrailDescribe := serviceloggingpapertrail.NewDescribeCommand(serviceloggingPapertrailCmdRoot.CmdClause, data)\n\tserviceloggingPapertrailList := serviceloggingpapertrail.NewListCommand(serviceloggingPapertrailCmdRoot.CmdClause, data)\n\tserviceloggingPapertrailUpdate := serviceloggingpapertrail.NewUpdateCommand(serviceloggingPapertrailCmdRoot.CmdClause, data)\n\tserviceloggingS3CmdRoot := serviceloggings3.NewRootCommand(serviceloggingCmdRoot.CmdClause, data)\n\tserviceloggingS3Create := serviceloggings3.NewCreateCommand(serviceloggingS3CmdRoot.CmdClause, data)\n\tserviceloggingS3Delete := serviceloggings3.NewDeleteCommand(serviceloggingS3CmdRoot.CmdClause, data)\n\tserviceloggingS3Describe := serviceloggings3.NewDescribeCommand(serviceloggingS3CmdRoot.CmdClause, data)\n\tserviceloggingS3List := serviceloggings3.NewListCommand(serviceloggingS3CmdRoot.CmdClause, data)\n\tserviceloggingS3Update := serviceloggings3.NewUpdateCommand(serviceloggingS3CmdRoot.CmdClause, data)\n\tserviceloggingScalyrCmdRoot := serviceloggingscalyr.NewRootCommand(serviceloggingCmdRoot.CmdClause, data)\n\tserviceloggingScalyrCreate := serviceloggingscalyr.NewCreateCommand(serviceloggingScalyrCmdRoot.CmdClause, data)\n\tserviceloggingScalyrDelete := serviceloggingscalyr.NewDeleteCommand(serviceloggingScalyrCmdRoot.CmdClause, data)\n\tserviceloggingScalyrDescribe := serviceloggingscalyr.NewDescribeCommand(serviceloggingScalyrCmdRoot.CmdClause, data)\n\tserviceloggingScalyrList := serviceloggingscalyr.NewListCommand(serviceloggingScalyrCmdRoot.CmdClause, data)\n\tserviceloggingScalyrUpdate := serviceloggingscalyr.NewUpdateCommand(serviceloggingScalyrCmdRoot.CmdClause, data)\n\tserviceloggingSftpCmdRoot := serviceloggingsftp.NewRootCommand(serviceloggingCmdRoot.CmdClause, data)\n\tserviceloggingSftpCreate := serviceloggingsftp.NewCreateCommand(serviceloggingSftpCmdRoot.CmdClause, data)\n\tserviceloggingSftpDelete := serviceloggingsftp.NewDeleteCommand(serviceloggingSftpCmdRoot.CmdClause, data)\n\tserviceloggingSftpDescribe := serviceloggingsftp.NewDescribeCommand(serviceloggingSftpCmdRoot.CmdClause, data)\n\tserviceloggingSftpList := serviceloggingsftp.NewListCommand(serviceloggingSftpCmdRoot.CmdClause, data)\n\tserviceloggingSftpUpdate := serviceloggingsftp.NewUpdateCommand(serviceloggingSftpCmdRoot.CmdClause, data)\n\tserviceloggingSplunkCmdRoot := serviceloggingsplunk.NewRootCommand(serviceloggingCmdRoot.CmdClause, data)\n\tserviceloggingSplunkCreate := serviceloggingsplunk.NewCreateCommand(serviceloggingSplunkCmdRoot.CmdClause, data)\n\tserviceloggingSplunkDelete := serviceloggingsplunk.NewDeleteCommand(serviceloggingSplunkCmdRoot.CmdClause, data)\n\tserviceloggingSplunkDescribe := serviceloggingsplunk.NewDescribeCommand(serviceloggingSplunkCmdRoot.CmdClause, data)\n\tserviceloggingSplunkList := serviceloggingsplunk.NewListCommand(serviceloggingSplunkCmdRoot.CmdClause, data)\n\tserviceloggingSplunkUpdate := serviceloggingsplunk.NewUpdateCommand(serviceloggingSplunkCmdRoot.CmdClause, data)\n\tserviceloggingSumologicCmdRoot := serviceloggingsumologic.NewRootCommand(serviceloggingCmdRoot.CmdClause, data)\n\tserviceloggingSumologicCreate := serviceloggingsumologic.NewCreateCommand(serviceloggingSumologicCmdRoot.CmdClause, data)\n\tserviceloggingSumologicDelete := serviceloggingsumologic.NewDeleteCommand(serviceloggingSumologicCmdRoot.CmdClause, data)\n\tserviceloggingSumologicDescribe := serviceloggingsumologic.NewDescribeCommand(serviceloggingSumologicCmdRoot.CmdClause, data)\n\tserviceloggingSumologicList := serviceloggingsumologic.NewListCommand(serviceloggingSumologicCmdRoot.CmdClause, data)\n\tserviceloggingSumologicUpdate := serviceloggingsumologic.NewUpdateCommand(serviceloggingSumologicCmdRoot.CmdClause, data)\n\tserviceloggingSyslogCmdRoot := serviceloggingsyslog.NewRootCommand(serviceloggingCmdRoot.CmdClause, data)\n\tserviceloggingSyslogCreate := serviceloggingsyslog.NewCreateCommand(serviceloggingSyslogCmdRoot.CmdClause, data)\n\tserviceloggingSyslogDelete := serviceloggingsyslog.NewDeleteCommand(serviceloggingSyslogCmdRoot.CmdClause, data)\n\tserviceloggingSyslogDescribe := serviceloggingsyslog.NewDescribeCommand(serviceloggingSyslogCmdRoot.CmdClause, data)\n\tserviceloggingSyslogList := serviceloggingsyslog.NewListCommand(serviceloggingSyslogCmdRoot.CmdClause, data)\n\tserviceloggingSyslogUpdate := serviceloggingsyslog.NewUpdateCommand(serviceloggingSyslogCmdRoot.CmdClause, data)\n\tserviceVersionCmdRoot := serviceversion.NewRootCommand(serviceCmdRoot.CmdClause, data)\n\tserviceVersionActivate := serviceversion.NewActivateCommand(serviceVersionCmdRoot.CmdClause, data)\n\tserviceVersionClone := serviceversion.NewCloneCommand(serviceVersionCmdRoot.CmdClause, data)\n\tserviceVersionDeactivate := serviceversion.NewDeactivateCommand(serviceVersionCmdRoot.CmdClause, data)\n\tserviceVersionList := serviceversion.NewListCommand(serviceVersionCmdRoot.CmdClause, data)\n\tserviceVersionLock := serviceversion.NewLockCommand(serviceVersionCmdRoot.CmdClause, data)\n\tserviceVersionStage := serviceversion.NewStageCommand(serviceVersionCmdRoot.CmdClause, data)\n\tserviceVersionUnstage := serviceversion.NewUnstageCommand(serviceVersionCmdRoot.CmdClause, data)\n\tserviceVersionUpdate := serviceversion.NewUpdateCommand(serviceVersionCmdRoot.CmdClause, data)\n\tserviceVersionValidate := serviceversion.NewValidateCommand(serviceVersionCmdRoot.CmdClause, data)\n\tservicedomainCmdRoot := servicedomain.NewRootCommand(serviceCmdRoot.CmdClause, data)\n\tservicedomainCreate := servicedomain.NewCreateCommand(servicedomainCmdRoot.CmdClause, data)\n\tservicedomainDelete := servicedomain.NewDeleteCommand(servicedomainCmdRoot.CmdClause, data)\n\tservicedomainDescribe := servicedomain.NewDescribeCommand(servicedomainCmdRoot.CmdClause, data)\n\tservicedomainList := servicedomain.NewListCommand(servicedomainCmdRoot.CmdClause, data)\n\tservicedomainUpdate := servicedomain.NewUpdateCommand(servicedomainCmdRoot.CmdClause, data)\n\tservicedomainValidate := servicedomain.NewValidateCommand(servicedomainCmdRoot.CmdClause, data)\n\tservicedictionaryentryCmdRoot := servicedictionaryentry.NewRootCommand(serviceCmdRoot.CmdClause, data)\n\tservicedictionaryentryCreate := servicedictionaryentry.NewCreateCommand(servicedictionaryentryCmdRoot.CmdClause, data)\n\tservicedictionaryentryDelete := servicedictionaryentry.NewDeleteCommand(servicedictionaryentryCmdRoot.CmdClause, data)\n\tservicedictionaryentryDescribe := servicedictionaryentry.NewDescribeCommand(servicedictionaryentryCmdRoot.CmdClause, data)\n\tservicedictionaryentryList := servicedictionaryentry.NewListCommand(servicedictionaryentryCmdRoot.CmdClause, data)\n\tservicedictionaryentryUpdate := servicedictionaryentry.NewUpdateCommand(servicedictionaryentryCmdRoot.CmdClause, data)\n\tservicebackendCmdRoot := servicebackend.NewRootCommand(serviceCmdRoot.CmdClause, data)\n\tservicebackendCreate := servicebackend.NewCreateCommand(servicebackendCmdRoot.CmdClause, data)\n\tservicebackendDelete := servicebackend.NewDeleteCommand(servicebackendCmdRoot.CmdClause, data)\n\tservicebackendDescribe := servicebackend.NewDescribeCommand(servicebackendCmdRoot.CmdClause, data)\n\tservicebackendList := servicebackend.NewListCommand(servicebackendCmdRoot.CmdClause, data)\n\tservicebackendUpdate := servicebackend.NewUpdateCommand(servicebackendCmdRoot.CmdClause, data)\n\tservicehealthcheckCmdRoot := servicehealthcheck.NewRootCommand(serviceCmdRoot.CmdClause, data)\n\tservicehealthcheckCreate := servicehealthcheck.NewCreateCommand(servicehealthcheckCmdRoot.CmdClause, data)\n\tservicehealthcheckDelete := servicehealthcheck.NewDeleteCommand(servicehealthcheckCmdRoot.CmdClause, data)\n\tservicehealthcheckDescribe := servicehealthcheck.NewDescribeCommand(servicehealthcheckCmdRoot.CmdClause, data)\n\tservicehealthcheckList := servicehealthcheck.NewListCommand(servicehealthcheckCmdRoot.CmdClause, data)\n\tservicehealthcheckUpdate := servicehealthcheck.NewUpdateCommand(servicehealthcheckCmdRoot.CmdClause, data)\n\tserviceimageoptimizerdefaultsCmdRoot := serviceimageoptimizerdefaults.NewRootCommand(serviceCmdRoot.CmdClause, data)\n\tserviceimageoptimizerdefaultsGet := serviceimageoptimizerdefaults.NewGetCommand(serviceimageoptimizerdefaultsCmdRoot.CmdClause, data)\n\tserviceimageoptimizerdefaultsUpdate := serviceimageoptimizerdefaults.NewUpdateCommand(serviceimageoptimizerdefaultsCmdRoot.CmdClause, data)\n\tserviceratelimitCmdRoot := serviceratelimit.NewRootCommand(serviceCmdRoot.CmdClause, data)\n\tserviceratelimitCreate := serviceratelimit.NewCreateCommand(serviceratelimitCmdRoot.CmdClause, data)\n\tserviceratelimitDelete := serviceratelimit.NewDeleteCommand(serviceratelimitCmdRoot.CmdClause, data)\n\tserviceratelimitDescribe := serviceratelimit.NewDescribeCommand(serviceratelimitCmdRoot.CmdClause, data)\n\tserviceratelimitList := serviceratelimit.NewListCommand(serviceratelimitCmdRoot.CmdClause, data)\n\tserviceratelimitUpdate := serviceratelimit.NewUpdateCommand(serviceratelimitCmdRoot.CmdClause, data)\n\tserviceresourcelinkCmdRoot := serviceresourcelink.NewRootCommand(serviceCmdRoot.CmdClause, data)\n\tserviceresourcelinkCreate := serviceresourcelink.NewCreateCommand(serviceresourcelinkCmdRoot.CmdClause, data)\n\tserviceresourcelinkDelete := serviceresourcelink.NewDeleteCommand(serviceresourcelinkCmdRoot.CmdClause, data)\n\tserviceresourcelinkDescribe := serviceresourcelink.NewDescribeCommand(serviceresourcelinkCmdRoot.CmdClause, data)\n\tserviceresourcelinkList := serviceresourcelink.NewListCommand(serviceresourcelinkCmdRoot.CmdClause, data)\n\tserviceresourcelinkUpdate := serviceresourcelink.NewUpdateCommand(serviceresourcelinkCmdRoot.CmdClause, data)\n\tstatsCmdRoot := stats.NewRootCommand(app, data)\n\tstatsAggregate := stats.NewAggregateCommand(statsCmdRoot.CmdClause, data)\n\tstatsDomainInspector := stats.NewDomainInspectorCommand(statsCmdRoot.CmdClause, data)\n\tstatsHistorical := stats.NewHistoricalCommand(statsCmdRoot.CmdClause, data)\n\tstatsOriginInspector := stats.NewOriginInspectorCommand(statsCmdRoot.CmdClause, data)\n\tstatsRealtime := stats.NewRealtimeCommand(statsCmdRoot.CmdClause, data)\n\tstatsRegions := stats.NewRegionsCommand(statsCmdRoot.CmdClause, data)\n\tstatsUsage := stats.NewUsageCommand(statsCmdRoot.CmdClause, data)\n\ttlsConfigCmdRoot := tlsconfig.NewRootCommand(app, data)\n\ttlsConfigDescribe := tlsconfig.NewDescribeCommand(tlsConfigCmdRoot.CmdClause, data)\n\ttlsConfigList := tlsconfig.NewListCommand(tlsConfigCmdRoot.CmdClause, data)\n\ttlsConfigUpdate := tlsconfig.NewUpdateCommand(tlsConfigCmdRoot.CmdClause, data)\n\ttlsCustomCmdRoot := tlscustom.NewRootCommand(app, data)\n\ttlsCustomActivationCmdRoot := tlscustomactivation.NewRootCommand(tlsCustomCmdRoot.CmdClause, data)\n\ttlsCustomActivationCreate := tlscustomactivation.NewCreateCommand(tlsCustomActivationCmdRoot.CmdClause, data)\n\ttlsCustomActivationDelete := tlscustomactivation.NewDeleteCommand(tlsCustomActivationCmdRoot.CmdClause, data)\n\ttlsCustomActivationDescribe := tlscustomactivation.NewDescribeCommand(tlsCustomActivationCmdRoot.CmdClause, data)\n\ttlsCustomActivationList := tlscustomactivation.NewListCommand(tlsCustomActivationCmdRoot.CmdClause, data)\n\ttlsCustomActivationUpdate := tlscustomactivation.NewUpdateCommand(tlsCustomActivationCmdRoot.CmdClause, data)\n\ttlsCustomCertificateCmdRoot := tlscustomcertificate.NewRootCommand(tlsCustomCmdRoot.CmdClause, data)\n\ttlsCustomCertificateCreate := tlscustomcertificate.NewCreateCommand(tlsCustomCertificateCmdRoot.CmdClause, data)\n\ttlsCustomCertificateDelete := tlscustomcertificate.NewDeleteCommand(tlsCustomCertificateCmdRoot.CmdClause, data)\n\ttlsCustomCertificateDescribe := tlscustomcertificate.NewDescribeCommand(tlsCustomCertificateCmdRoot.CmdClause, data)\n\ttlsCustomCertificateList := tlscustomcertificate.NewListCommand(tlsCustomCertificateCmdRoot.CmdClause, data)\n\ttlsCustomCertificateUpdate := tlscustomcertificate.NewUpdateCommand(tlsCustomCertificateCmdRoot.CmdClause, data)\n\ttlsCustomDomainCmdRoot := tlscustomdomain.NewRootCommand(tlsCustomCmdRoot.CmdClause, data)\n\ttlsCustomDomainList := tlscustomdomain.NewListCommand(tlsCustomDomainCmdRoot.CmdClause, data)\n\ttlsCustomPrivateKeyCmdRoot := tlscustomprivatekey.NewRootCommand(tlsCustomCmdRoot.CmdClause, data)\n\ttlsCustomPrivateKeyCreate := tlscustomprivatekey.NewCreateCommand(tlsCustomPrivateKeyCmdRoot.CmdClause, data)\n\ttlsCustomPrivateKeyDelete := tlscustomprivatekey.NewDeleteCommand(tlsCustomPrivateKeyCmdRoot.CmdClause, data)\n\ttlsCustomPrivateKeyDescribe := tlscustomprivatekey.NewDescribeCommand(tlsCustomPrivateKeyCmdRoot.CmdClause, data)\n\ttlsCustomPrivateKeyList := tlscustomprivatekey.NewListCommand(tlsCustomPrivateKeyCmdRoot.CmdClause, data)\n\ttlsPlatformCmdRoot := tlsplatform.NewRootCommand(app, data)\n\ttlsPlatformCreate := tlsplatform.NewCreateCommand(tlsPlatformCmdRoot.CmdClause, data)\n\ttlsPlatformDelete := tlsplatform.NewDeleteCommand(tlsPlatformCmdRoot.CmdClause, data)\n\ttlsPlatformDescribe := tlsplatform.NewDescribeCommand(tlsPlatformCmdRoot.CmdClause, data)\n\ttlsPlatformList := tlsplatform.NewListCommand(tlsPlatformCmdRoot.CmdClause, data)\n\ttlsPlatformUpdate := tlsplatform.NewUpdateCommand(tlsPlatformCmdRoot.CmdClause, data)\n\ttlsSubscriptionCmdRoot := tlssubscription.NewRootCommand(app, data)\n\ttlsSubscriptionCreate := tlssubscription.NewCreateCommand(tlsSubscriptionCmdRoot.CmdClause, data)\n\ttlsSubscriptionDelete := tlssubscription.NewDeleteCommand(tlsSubscriptionCmdRoot.CmdClause, data)\n\ttlsSubscriptionDescribe := tlssubscription.NewDescribeCommand(tlsSubscriptionCmdRoot.CmdClause, data)\n\ttlsSubscriptionList := tlssubscription.NewListCommand(tlsSubscriptionCmdRoot.CmdClause, data)\n\ttlsSubscriptionUpdate := tlssubscription.NewUpdateCommand(tlsSubscriptionCmdRoot.CmdClause, data)\n\ttoolsCmdRoot := tools.NewRootCommand(app, data)\n\ttoolsDomainCmdRoot := domainTools.NewRootCommand(toolsCmdRoot.CmdClause, data)\n\ttoolsDomainStatus := domainTools.NewDomainStatusCommand(toolsDomainCmdRoot.CmdClause, data)\n\ttoolsDomainSuggestions := domainTools.NewDomainSuggestionsCommand(toolsDomainCmdRoot.CmdClause, data)\n\tupdateRoot := update.NewRootCommand(app, data)\n\tuserCmdRoot := user.NewRootCommand(app, data)\n\tuserCreate := user.NewCreateCommand(userCmdRoot.CmdClause, data)\n\tuserDelete := user.NewDeleteCommand(userCmdRoot.CmdClause, data)\n\tuserDescribe := user.NewDescribeCommand(userCmdRoot.CmdClause, data)\n\tuserList := user.NewListCommand(userCmdRoot.CmdClause, data)\n\tuserUpdate := user.NewUpdateCommand(userCmdRoot.CmdClause, data)\n\tversionCmdRoot := version.NewRootCommand(app, data)\n\tif !disableAuthCmd {\n\t\twhoamiCommands = []argparser.Command{whoami.NewRootCommand(app, data)}\n\t}\n\n\t// Aliases for deprecated commands\n\taliasBackendRoot := aliasbackend.NewRootCommand(app, data)\n\taliasBackendCreate := aliasbackend.NewCreateCommand(aliasBackendRoot.CmdClause, data)\n\taliasBackendDelete := aliasbackend.NewDeleteCommand(aliasBackendRoot.CmdClause, data)\n\taliasBackendDescribe := aliasbackend.NewDescribeCommand(aliasBackendRoot.CmdClause, data)\n\taliasBackendList := aliasbackend.NewListCommand(aliasBackendRoot.CmdClause, data)\n\taliasBackendUpdate := aliasbackend.NewUpdateCommand(aliasBackendRoot.CmdClause, data)\n\taliasDictionaryEntryRoot := aliasdictionaryentry.NewRootCommand(app, data)\n\taliasDictionaryEntryCreate := aliasdictionaryentry.NewCreateCommand(aliasDictionaryEntryRoot.CmdClause, data)\n\taliasDictionaryEntryDelete := aliasdictionaryentry.NewDeleteCommand(aliasDictionaryEntryRoot.CmdClause, data)\n\taliasDictionaryEntryDescribe := aliasdictionaryentry.NewDescribeCommand(aliasDictionaryEntryRoot.CmdClause, data)\n\taliasDictionaryEntryList := aliasdictionaryentry.NewListCommand(aliasDictionaryEntryRoot.CmdClause, data)\n\taliasDictionaryEntryUpdate := aliasdictionaryentry.NewUpdateCommand(aliasDictionaryEntryRoot.CmdClause, data)\n\taliasDictionaryRoot := aliasdictionary.NewRootCommand(app, data)\n\taliasDictionaryCreate := aliasdictionary.NewCreateCommand(aliasDictionaryRoot.CmdClause, data)\n\taliasDictionaryDelete := aliasdictionary.NewDeleteCommand(aliasDictionaryRoot.CmdClause, data)\n\taliasDictionaryDescribe := aliasdictionary.NewDescribeCommand(aliasDictionaryRoot.CmdClause, data)\n\taliasDictionaryList := aliasdictionary.NewListCommand(aliasDictionaryRoot.CmdClause, data)\n\taliasDictionaryUpdate := aliasdictionary.NewUpdateCommand(aliasDictionaryRoot.CmdClause, data)\n\taliasHealthcheckRoot := aliashealthcheck.NewRootCommand(app, data)\n\taliasHealthcheckCreate := aliashealthcheck.NewCreateCommand(aliasHealthcheckRoot.CmdClause, data)\n\taliasHealthcheckDelete := aliashealthcheck.NewDeleteCommand(aliasHealthcheckRoot.CmdClause, data)\n\taliasHealthcheckDescribe := aliashealthcheck.NewDescribeCommand(aliasHealthcheckRoot.CmdClause, data)\n\taliasHealthcheckList := aliashealthcheck.NewListCommand(aliasHealthcheckRoot.CmdClause, data)\n\taliasHealthcheckUpdate := aliashealthcheck.NewUpdateCommand(aliasHealthcheckRoot.CmdClause, data)\n\taliasimageoptimizerdefaultsRoot := aliasimageoptimizerdefaults.NewRootCommand(app, data)\n\taliasimageoptimizerdefaultsGet := aliasimageoptimizerdefaults.NewGetCommand(aliasimageoptimizerdefaultsRoot.CmdClause, data)\n\taliasimageoptimizerdefaultsUpdate := aliasimageoptimizerdefaults.NewUpdateCommand(aliasimageoptimizerdefaultsRoot.CmdClause, data)\n\taliasPurge := aliaspurge.NewCommand(app, data)\n\taliasAlertRoot := aliasalerts.NewRootCommand(app, data)\n\taliasAlertCreate := aliasalerts.NewCreateCommand(aliasAlertRoot.CmdClause, data)\n\taliasAlertDelete := aliasalerts.NewDeleteCommand(aliasAlertRoot.CmdClause, data)\n\taliasAlertDescribe := aliasalerts.NewDescribeCommand(aliasAlertRoot.CmdClause, data)\n\taliasAlertList := aliasalerts.NewListCommand(aliasAlertRoot.CmdClause, data)\n\taliasAlertListHistory := aliasalerts.NewListHistoryCommand(aliasAlertRoot.CmdClause, data)\n\taliasAlertUpdate := aliasalerts.NewUpdateCommand(aliasAlertRoot.CmdClause, data)\n\taliasACLRoot := aliasacl.NewRootCommand(app, data)\n\taliasACLCreate := aliasacl.NewCreateCommand(aliasACLRoot.CmdClause, data)\n\taliasACLDelete := aliasacl.NewDeleteCommand(aliasACLRoot.CmdClause, data)\n\taliasACLDescribe := aliasacl.NewDescribeCommand(aliasACLRoot.CmdClause, data)\n\taliasACLList := aliasacl.NewListCommand(aliasACLRoot.CmdClause, data)\n\taliasACLUpdate := aliasacl.NewUpdateCommand(aliasACLRoot.CmdClause, data)\n\taliasACLEntryRoot := aliasaclentry.NewRootCommand(app, data)\n\taliasACLEntryCreate := aliasaclentry.NewCreateCommand(aliasACLEntryRoot.CmdClause, data)\n\taliasACLEntryDelete := aliasaclentry.NewDeleteCommand(aliasACLEntryRoot.CmdClause, data)\n\taliasACLEntryDescribe := aliasaclentry.NewDescribeCommand(aliasACLEntryRoot.CmdClause, data)\n\taliasACLEntryList := aliasaclentry.NewListCommand(aliasACLEntryRoot.CmdClause, data)\n\taliasACLEntryUpdate := aliasaclentry.NewUpdateCommand(aliasACLEntryRoot.CmdClause, data)\n\taliasRateLimitRoot := aliasratelimit.NewRootCommand(app, data)\n\taliasRateLimitCreate := aliasratelimit.NewCreateCommand(aliasRateLimitRoot.CmdClause, data)\n\taliasRateLimitDelete := aliasratelimit.NewDeleteCommand(aliasRateLimitRoot.CmdClause, data)\n\taliasRateLimitDescribe := aliasratelimit.NewDescribeCommand(aliasRateLimitRoot.CmdClause, data)\n\taliasRateLimitList := aliasratelimit.NewListCommand(aliasRateLimitRoot.CmdClause, data)\n\taliasRateLimitUpdate := aliasratelimit.NewUpdateCommand(aliasRateLimitRoot.CmdClause, data)\n\taliasResourceLinkRoot := aliasresourcelink.NewRootCommand(app, data)\n\taliasResourceLinkCreate := aliasresourcelink.NewCreateCommand(aliasResourceLinkRoot.CmdClause, data)\n\taliasResourceLinkDelete := aliasresourcelink.NewDeleteCommand(aliasResourceLinkRoot.CmdClause, data)\n\taliasResourceLinkDescribe := aliasresourcelink.NewDescribeCommand(aliasResourceLinkRoot.CmdClause, data)\n\taliasResourceLinkList := aliasresourcelink.NewListCommand(aliasResourceLinkRoot.CmdClause, data)\n\taliasResourceLinkUpdate := aliasresourcelink.NewUpdateCommand(aliasResourceLinkRoot.CmdClause, data)\n\taliasServiceAuthRoot := aliasserviceauth.NewRootCommand(app, data)\n\taliasServiceAuthCreate := aliasserviceauth.NewCreateCommand(aliasServiceAuthRoot.CmdClause, data)\n\taliasServiceAuthDelete := aliasserviceauth.NewDeleteCommand(aliasServiceAuthRoot.CmdClause, data)\n\taliasServiceAuthDescribe := aliasserviceauth.NewDescribeCommand(aliasServiceAuthRoot.CmdClause, data)\n\taliasServiceAuthList := aliasserviceauth.NewListCommand(aliasServiceAuthRoot.CmdClause, data)\n\taliasServiceAuthUpdate := aliasserviceauth.NewUpdateCommand(aliasServiceAuthRoot.CmdClause, data)\n\taliasVclRoot := aliasvcl.NewRootCommand(app, data)\n\taliasVclDescribe := aliasvcl.NewDescribeCommand(aliasVclRoot.CmdClause, data)\n\taliasVclConditionRoot := aliasvclcondition.NewRootCommand(aliasVclRoot.CmdClause, data)\n\taliasVclConditionCreate := aliasvclcondition.NewCreateCommand(aliasVclConditionRoot.CmdClause, data)\n\taliasVclConditionDelete := aliasvclcondition.NewDeleteCommand(aliasVclConditionRoot.CmdClause, data)\n\taliasVclConditionDescribe := aliasvclcondition.NewDescribeCommand(aliasVclConditionRoot.CmdClause, data)\n\taliasVclConditionList := aliasvclcondition.NewListCommand(aliasVclConditionRoot.CmdClause, data)\n\taliasVclConditionUpdate := aliasvclcondition.NewUpdateCommand(aliasVclConditionRoot.CmdClause, data)\n\taliasVclCustomRoot := aliasvclcustom.NewRootCommand(aliasVclRoot.CmdClause, data)\n\taliasVclCustomCreate := aliasvclcustom.NewCreateCommand(aliasVclCustomRoot.CmdClause, data)\n\taliasVclCustomDelete := aliasvclcustom.NewDeleteCommand(aliasVclCustomRoot.CmdClause, data)\n\taliasVclCustomDescribe := aliasvclcustom.NewDescribeCommand(aliasVclCustomRoot.CmdClause, data)\n\taliasVclCustomList := aliasvclcustom.NewListCommand(aliasVclCustomRoot.CmdClause, data)\n\taliasVclCustomUpdate := aliasvclcustom.NewUpdateCommand(aliasVclCustomRoot.CmdClause, data)\n\taliasVclSnippetRoot := aliasvclsnippet.NewRootCommand(aliasVclRoot.CmdClause, data)\n\taliasVclSnippetCreate := aliasvclsnippet.NewCreateCommand(aliasVclSnippetRoot.CmdClause, data)\n\taliasVclSnippetDelete := aliasvclsnippet.NewDeleteCommand(aliasVclSnippetRoot.CmdClause, data)\n\taliasVclSnippetDescribe := aliasvclsnippet.NewDescribeCommand(aliasVclSnippetRoot.CmdClause, data)\n\taliasVclSnippetList := aliasvclsnippet.NewListCommand(aliasVclSnippetRoot.CmdClause, data)\n\taliasVclSnippetUpdate := aliasvclsnippet.NewUpdateCommand(aliasVclSnippetRoot.CmdClause, data)\n\taliasServiceVersionRoot := aliasserviceversion.NewRootCommand(app, data)\n\taliasServiceVersionActivate := aliasserviceversion.NewActivateCommand(aliasServiceVersionRoot.CmdClause, data)\n\taliasServiceVersionClone := aliasserviceversion.NewCloneCommand(aliasServiceVersionRoot.CmdClause, data)\n\taliasServiceVersionDeactivate := aliasserviceversion.NewDeactivateCommand(aliasServiceVersionRoot.CmdClause, data)\n\taliasServiceVersionList := aliasserviceversion.NewListCommand(aliasServiceVersionRoot.CmdClause, data)\n\taliasServiceVersionLock := aliasserviceversion.NewLockCommand(aliasServiceVersionRoot.CmdClause, data)\n\taliasServiceVersionStage := aliasserviceversion.NewStageCommand(aliasServiceVersionRoot.CmdClause, data)\n\taliasServiceVersionUnstage := aliasserviceversion.NewUnstageCommand(aliasServiceVersionRoot.CmdClause, data)\n\taliasServiceVersionUpdate := aliasserviceversion.NewUpdateCommand(aliasServiceVersionRoot.CmdClause, data)\n\taliasLoggingRoot := aliaslogging.NewRootCommand(app, data)\n\taliasAzureblobRoot := aliasazureblob.NewRootCommand(aliasLoggingRoot.CmdClause, data)\n\taliasAzureblobCreate := aliasazureblob.NewCreateCommand(aliasAzureblobRoot.CmdClause, data)\n\taliasAzureblobDelete := aliasazureblob.NewDeleteCommand(aliasAzureblobRoot.CmdClause, data)\n\taliasAzureblobDescribe := aliasazureblob.NewDescribeCommand(aliasAzureblobRoot.CmdClause, data)\n\taliasAzureblobList := aliasazureblob.NewListCommand(aliasAzureblobRoot.CmdClause, data)\n\taliasAzureblobUpdate := aliasazureblob.NewUpdateCommand(aliasAzureblobRoot.CmdClause, data)\n\taliasBigqueryRoot := aliasbigquery.NewRootCommand(aliasLoggingRoot.CmdClause, data)\n\taliasBigqueryCreate := aliasbigquery.NewCreateCommand(aliasBigqueryRoot.CmdClause, data)\n\taliasBigqueryDelete := aliasbigquery.NewDeleteCommand(aliasBigqueryRoot.CmdClause, data)\n\taliasBigqueryDescribe := aliasbigquery.NewDescribeCommand(aliasBigqueryRoot.CmdClause, data)\n\taliasBigqueryList := aliasbigquery.NewListCommand(aliasBigqueryRoot.CmdClause, data)\n\taliasBigqueryUpdate := aliasbigquery.NewUpdateCommand(aliasBigqueryRoot.CmdClause, data)\n\taliasCloudfilesRoot := aliascloudfiles.NewRootCommand(aliasLoggingRoot.CmdClause, data)\n\taliasCloudfilesCreate := aliascloudfiles.NewCreateCommand(aliasCloudfilesRoot.CmdClause, data)\n\taliasCloudfilesDelete := aliascloudfiles.NewDeleteCommand(aliasCloudfilesRoot.CmdClause, data)\n\taliasCloudfilesDescribe := aliascloudfiles.NewDescribeCommand(aliasCloudfilesRoot.CmdClause, data)\n\taliasCloudfilesList := aliascloudfiles.NewListCommand(aliasCloudfilesRoot.CmdClause, data)\n\taliasCloudfilesUpdate := aliascloudfiles.NewUpdateCommand(aliasCloudfilesRoot.CmdClause, data)\n\taliasDatadogRoot := aliasdatadog.NewRootCommand(aliasLoggingRoot.CmdClause, data)\n\taliasDatadogCreate := aliasdatadog.NewCreateCommand(aliasDatadogRoot.CmdClause, data)\n\taliasDatadogDelete := aliasdatadog.NewDeleteCommand(aliasDatadogRoot.CmdClause, data)\n\taliasDatadogDescribe := aliasdatadog.NewDescribeCommand(aliasDatadogRoot.CmdClause, data)\n\taliasDatadogList := aliasdatadog.NewListCommand(aliasDatadogRoot.CmdClause, data)\n\taliasDatadogUpdate := aliasdatadog.NewUpdateCommand(aliasDatadogRoot.CmdClause, data)\n\taliasDigitaloceanRoot := aliasdigitalocean.NewRootCommand(aliasLoggingRoot.CmdClause, data)\n\taliasDigitaloceanCreate := aliasdigitalocean.NewCreateCommand(aliasDigitaloceanRoot.CmdClause, data)\n\taliasDigitaloceanDelete := aliasdigitalocean.NewDeleteCommand(aliasDigitaloceanRoot.CmdClause, data)\n\taliasDigitaloceanDescribe := aliasdigitalocean.NewDescribeCommand(aliasDigitaloceanRoot.CmdClause, data)\n\taliasDigitaloceanList := aliasdigitalocean.NewListCommand(aliasDigitaloceanRoot.CmdClause, data)\n\taliasDigitaloceanUpdate := aliasdigitalocean.NewUpdateCommand(aliasDigitaloceanRoot.CmdClause, data)\n\taliasElasticsearchRoot := aliaselasticsearch.NewRootCommand(aliasLoggingRoot.CmdClause, data)\n\taliasElasticsearchCreate := aliaselasticsearch.NewCreateCommand(aliasElasticsearchRoot.CmdClause, data)\n\taliasElasticsearchDelete := aliaselasticsearch.NewDeleteCommand(aliasElasticsearchRoot.CmdClause, data)\n\taliasElasticsearchDescribe := aliaselasticsearch.NewDescribeCommand(aliasElasticsearchRoot.CmdClause, data)\n\taliasElasticsearchList := aliaselasticsearch.NewListCommand(aliasElasticsearchRoot.CmdClause, data)\n\taliasElasticsearchUpdate := aliaselasticsearch.NewUpdateCommand(aliasElasticsearchRoot.CmdClause, data)\n\taliasFtpRoot := aliasftp.NewRootCommand(aliasLoggingRoot.CmdClause, data)\n\taliasFtpCreate := aliasftp.NewCreateCommand(aliasFtpRoot.CmdClause, data)\n\taliasFtpDelete := aliasftp.NewDeleteCommand(aliasFtpRoot.CmdClause, data)\n\taliasFtpDescribe := aliasftp.NewDescribeCommand(aliasFtpRoot.CmdClause, data)\n\taliasFtpList := aliasftp.NewListCommand(aliasFtpRoot.CmdClause, data)\n\taliasFtpUpdate := aliasftp.NewUpdateCommand(aliasFtpRoot.CmdClause, data)\n\taliasGcsRoot := aliasgcs.NewRootCommand(aliasLoggingRoot.CmdClause, data)\n\taliasGcsCreate := aliasgcs.NewCreateCommand(aliasGcsRoot.CmdClause, data)\n\taliasGcsDelete := aliasgcs.NewDeleteCommand(aliasGcsRoot.CmdClause, data)\n\taliasGcsDescribe := aliasgcs.NewDescribeCommand(aliasGcsRoot.CmdClause, data)\n\taliasGcsList := aliasgcs.NewListCommand(aliasGcsRoot.CmdClause, data)\n\taliasGcsUpdate := aliasgcs.NewUpdateCommand(aliasGcsRoot.CmdClause, data)\n\taliasGooglepubsubRoot := aliasgooglepubsub.NewRootCommand(aliasLoggingRoot.CmdClause, data)\n\taliasGooglepubsubCreate := aliasgooglepubsub.NewCreateCommand(aliasGooglepubsubRoot.CmdClause, data)\n\taliasGooglepubsubDelete := aliasgooglepubsub.NewDeleteCommand(aliasGooglepubsubRoot.CmdClause, data)\n\taliasGooglepubsubDescribe := aliasgooglepubsub.NewDescribeCommand(aliasGooglepubsubRoot.CmdClause, data)\n\taliasGooglepubsubList := aliasgooglepubsub.NewListCommand(aliasGooglepubsubRoot.CmdClause, data)\n\taliasGooglepubsubUpdate := aliasgooglepubsub.NewUpdateCommand(aliasGooglepubsubRoot.CmdClause, data)\n\taliasGrafanacloudlogsRoot := aliasgrafanacloudlogs.NewRootCommand(aliasLoggingRoot.CmdClause, data)\n\taliasGrafanacloudlogsCreate := aliasgrafanacloudlogs.NewCreateCommand(aliasGrafanacloudlogsRoot.CmdClause, data)\n\taliasGrafanacloudlogsDelete := aliasgrafanacloudlogs.NewDeleteCommand(aliasGrafanacloudlogsRoot.CmdClause, data)\n\taliasGrafanacloudlogsDescribe := aliasgrafanacloudlogs.NewDescribeCommand(aliasGrafanacloudlogsRoot.CmdClause, data)\n\taliasGrafanacloudlogsList := aliasgrafanacloudlogs.NewListCommand(aliasGrafanacloudlogsRoot.CmdClause, data)\n\taliasGrafanacloudlogsUpdate := aliasgrafanacloudlogs.NewUpdateCommand(aliasGrafanacloudlogsRoot.CmdClause, data)\n\taliasHerokuRoot := aliasheroku.NewRootCommand(aliasLoggingRoot.CmdClause, data)\n\taliasHerokuCreate := aliasheroku.NewCreateCommand(aliasHerokuRoot.CmdClause, data)\n\taliasHerokuDelete := aliasheroku.NewDeleteCommand(aliasHerokuRoot.CmdClause, data)\n\taliasHerokuDescribe := aliasheroku.NewDescribeCommand(aliasHerokuRoot.CmdClause, data)\n\taliasHerokuList := aliasheroku.NewListCommand(aliasHerokuRoot.CmdClause, data)\n\taliasHerokuUpdate := aliasheroku.NewUpdateCommand(aliasHerokuRoot.CmdClause, data)\n\taliasHoneycombRoot := aliashoneycomb.NewRootCommand(aliasLoggingRoot.CmdClause, data)\n\taliasHoneycombCreate := aliashoneycomb.NewCreateCommand(aliasHoneycombRoot.CmdClause, data)\n\taliasHoneycombDelete := aliashoneycomb.NewDeleteCommand(aliasHoneycombRoot.CmdClause, data)\n\taliasHoneycombDescribe := aliashoneycomb.NewDescribeCommand(aliasHoneycombRoot.CmdClause, data)\n\taliasHoneycombList := aliashoneycomb.NewListCommand(aliasHoneycombRoot.CmdClause, data)\n\taliasHoneycombUpdate := aliashoneycomb.NewUpdateCommand(aliasHoneycombRoot.CmdClause, data)\n\taliasHTTPSRoot := aliashttps.NewRootCommand(aliasLoggingRoot.CmdClause, data)\n\taliasHTTPSCreate := aliashttps.NewCreateCommand(aliasHTTPSRoot.CmdClause, data)\n\taliasHTTPSDelete := aliashttps.NewDeleteCommand(aliasHTTPSRoot.CmdClause, data)\n\taliasHTTPSDescribe := aliashttps.NewDescribeCommand(aliasHTTPSRoot.CmdClause, data)\n\taliasHTTPSList := aliashttps.NewListCommand(aliasHTTPSRoot.CmdClause, data)\n\taliasHTTPSUpdate := aliashttps.NewUpdateCommand(aliasHTTPSRoot.CmdClause, data)\n\taliasKafkaRoot := aliaskafka.NewRootCommand(aliasLoggingRoot.CmdClause, data)\n\taliasKafkaCreate := aliaskafka.NewCreateCommand(aliasKafkaRoot.CmdClause, data)\n\taliasKafkaDelete := aliaskafka.NewDeleteCommand(aliasKafkaRoot.CmdClause, data)\n\taliasKafkaDescribe := aliaskafka.NewDescribeCommand(aliasKafkaRoot.CmdClause, data)\n\taliasKafkaList := aliaskafka.NewListCommand(aliasKafkaRoot.CmdClause, data)\n\taliasKafkaUpdate := aliaskafka.NewUpdateCommand(aliasKafkaRoot.CmdClause, data)\n\taliasKinesisRoot := aliaskinesis.NewRootCommand(aliasLoggingRoot.CmdClause, data)\n\taliasKinesisCreate := aliaskinesis.NewCreateCommand(aliasKinesisRoot.CmdClause, data)\n\taliasKinesisDelete := aliaskinesis.NewDeleteCommand(aliasKinesisRoot.CmdClause, data)\n\taliasKinesisDescribe := aliaskinesis.NewDescribeCommand(aliasKinesisRoot.CmdClause, data)\n\taliasKinesisList := aliaskinesis.NewListCommand(aliasKinesisRoot.CmdClause, data)\n\taliasKinesisUpdate := aliaskinesis.NewUpdateCommand(aliasKinesisRoot.CmdClause, data)\n\taliasLogglyRoot := aliasloggly.NewRootCommand(aliasLoggingRoot.CmdClause, data)\n\taliasLogglyCreate := aliasloggly.NewCreateCommand(aliasLogglyRoot.CmdClause, data)\n\taliasLogglyDelete := aliasloggly.NewDeleteCommand(aliasLogglyRoot.CmdClause, data)\n\taliasLogglyDescribe := aliasloggly.NewDescribeCommand(aliasLogglyRoot.CmdClause, data)\n\taliasLogglyList := aliasloggly.NewListCommand(aliasLogglyRoot.CmdClause, data)\n\taliasLogglyUpdate := aliasloggly.NewUpdateCommand(aliasLogglyRoot.CmdClause, data)\n\taliasLogshuttleRoot := aliaslogshuttle.NewRootCommand(aliasLoggingRoot.CmdClause, data)\n\taliasLogshuttleCreate := aliaslogshuttle.NewCreateCommand(aliasLogshuttleRoot.CmdClause, data)\n\taliasLogshuttleDelete := aliaslogshuttle.NewDeleteCommand(aliasLogshuttleRoot.CmdClause, data)\n\taliasLogshuttleDescribe := aliaslogshuttle.NewDescribeCommand(aliasLogshuttleRoot.CmdClause, data)\n\taliasLogshuttleList := aliaslogshuttle.NewListCommand(aliasLogshuttleRoot.CmdClause, data)\n\taliasLogshuttleUpdate := aliaslogshuttle.NewUpdateCommand(aliasLogshuttleRoot.CmdClause, data)\n\taliasNewrelicRoot := aliasnewrelic.NewRootCommand(aliasLoggingRoot.CmdClause, data)\n\taliasNewrelicCreate := aliasnewrelic.NewCreateCommand(aliasNewrelicRoot.CmdClause, data)\n\taliasNewrelicDelete := aliasnewrelic.NewDeleteCommand(aliasNewrelicRoot.CmdClause, data)\n\taliasNewrelicDescribe := aliasnewrelic.NewDescribeCommand(aliasNewrelicRoot.CmdClause, data)\n\taliasNewrelicList := aliasnewrelic.NewListCommand(aliasNewrelicRoot.CmdClause, data)\n\taliasNewrelicUpdate := aliasnewrelic.NewUpdateCommand(aliasNewrelicRoot.CmdClause, data)\n\taliasNewrelicotlpRoot := aliasnewrelicotlp.NewRootCommand(aliasLoggingRoot.CmdClause, data)\n\taliasNewrelicotlpCreate := aliasnewrelicotlp.NewCreateCommand(aliasNewrelicotlpRoot.CmdClause, data)\n\taliasNewrelicotlpDelete := aliasnewrelicotlp.NewDeleteCommand(aliasNewrelicotlpRoot.CmdClause, data)\n\taliasNewrelicotlpDescribe := aliasnewrelicotlp.NewDescribeCommand(aliasNewrelicotlpRoot.CmdClause, data)\n\taliasNewrelicotlpList := aliasnewrelicotlp.NewListCommand(aliasNewrelicotlpRoot.CmdClause, data)\n\taliasNewrelicotlpUpdate := aliasnewrelicotlp.NewUpdateCommand(aliasNewrelicotlpRoot.CmdClause, data)\n\taliasOpenstackRoot := aliasopenstack.NewRootCommand(aliasLoggingRoot.CmdClause, data)\n\taliasOpenstackCreate := aliasopenstack.NewCreateCommand(aliasOpenstackRoot.CmdClause, data)\n\taliasOpenstackDelete := aliasopenstack.NewDeleteCommand(aliasOpenstackRoot.CmdClause, data)\n\taliasOpenstackDescribe := aliasopenstack.NewDescribeCommand(aliasOpenstackRoot.CmdClause, data)\n\taliasOpenstackList := aliasopenstack.NewListCommand(aliasOpenstackRoot.CmdClause, data)\n\taliasOpenstackUpdate := aliasopenstack.NewUpdateCommand(aliasOpenstackRoot.CmdClause, data)\n\taliasPapertrailRoot := aliaspapertrail.NewRootCommand(aliasLoggingRoot.CmdClause, data)\n\taliasPapertrailCreate := aliaspapertrail.NewCreateCommand(aliasPapertrailRoot.CmdClause, data)\n\taliasPapertrailDelete := aliaspapertrail.NewDeleteCommand(aliasPapertrailRoot.CmdClause, data)\n\taliasPapertrailDescribe := aliaspapertrail.NewDescribeCommand(aliasPapertrailRoot.CmdClause, data)\n\taliasPapertrailList := aliaspapertrail.NewListCommand(aliasPapertrailRoot.CmdClause, data)\n\taliasPapertrailUpdate := aliaspapertrail.NewUpdateCommand(aliasPapertrailRoot.CmdClause, data)\n\taliasS3Root := aliass3.NewRootCommand(aliasLoggingRoot.CmdClause, data)\n\taliasS3Create := aliass3.NewCreateCommand(aliasS3Root.CmdClause, data)\n\taliasS3Delete := aliass3.NewDeleteCommand(aliasS3Root.CmdClause, data)\n\taliasS3Describe := aliass3.NewDescribeCommand(aliasS3Root.CmdClause, data)\n\taliasS3List := aliass3.NewListCommand(aliasS3Root.CmdClause, data)\n\taliasS3Update := aliass3.NewUpdateCommand(aliasS3Root.CmdClause, data)\n\taliasScalyrRoot := aliasscalyr.NewRootCommand(aliasLoggingRoot.CmdClause, data)\n\taliasScalyrCreate := aliasscalyr.NewCreateCommand(aliasScalyrRoot.CmdClause, data)\n\taliasScalyrDelete := aliasscalyr.NewDeleteCommand(aliasScalyrRoot.CmdClause, data)\n\taliasScalyrDescribe := aliasscalyr.NewDescribeCommand(aliasScalyrRoot.CmdClause, data)\n\taliasScalyrList := aliasscalyr.NewListCommand(aliasScalyrRoot.CmdClause, data)\n\taliasScalyrUpdate := aliasscalyr.NewUpdateCommand(aliasScalyrRoot.CmdClause, data)\n\taliasSftpRoot := aliassftp.NewRootCommand(aliasLoggingRoot.CmdClause, data)\n\taliasSftpCreate := aliassftp.NewCreateCommand(aliasSftpRoot.CmdClause, data)\n\taliasSftpDelete := aliassftp.NewDeleteCommand(aliasSftpRoot.CmdClause, data)\n\taliasSftpDescribe := aliassftp.NewDescribeCommand(aliasSftpRoot.CmdClause, data)\n\taliasSftpList := aliassftp.NewListCommand(aliasSftpRoot.CmdClause, data)\n\taliasSftpUpdate := aliassftp.NewUpdateCommand(aliasSftpRoot.CmdClause, data)\n\taliasSplunkRoot := aliassplunk.NewRootCommand(aliasLoggingRoot.CmdClause, data)\n\taliasSplunkCreate := aliassplunk.NewCreateCommand(aliasSplunkRoot.CmdClause, data)\n\taliasSplunkDelete := aliassplunk.NewDeleteCommand(aliasSplunkRoot.CmdClause, data)\n\taliasSplunkDescribe := aliassplunk.NewDescribeCommand(aliasSplunkRoot.CmdClause, data)\n\taliasSplunkList := aliassplunk.NewListCommand(aliasSplunkRoot.CmdClause, data)\n\taliasSplunkUpdate := aliassplunk.NewUpdateCommand(aliasSplunkRoot.CmdClause, data)\n\taliasSumologicRoot := aliassumologic.NewRootCommand(aliasLoggingRoot.CmdClause, data)\n\taliasSumologicCreate := aliassumologic.NewCreateCommand(aliasSumologicRoot.CmdClause, data)\n\taliasSumologicDelete := aliassumologic.NewDeleteCommand(aliasSumologicRoot.CmdClause, data)\n\taliasSumologicDescribe := aliassumologic.NewDescribeCommand(aliasSumologicRoot.CmdClause, data)\n\taliasSumologicList := aliassumologic.NewListCommand(aliasSumologicRoot.CmdClause, data)\n\taliasSumologicUpdate := aliassumologic.NewUpdateCommand(aliasSumologicRoot.CmdClause, data)\n\taliasSyslogRoot := aliassyslog.NewRootCommand(aliasLoggingRoot.CmdClause, data)\n\taliasSyslogCreate := aliassyslog.NewCreateCommand(aliasSyslogRoot.CmdClause, data)\n\taliasSyslogDelete := aliassyslog.NewDeleteCommand(aliasSyslogRoot.CmdClause, data)\n\taliasSyslogDescribe := aliassyslog.NewDescribeCommand(aliasSyslogRoot.CmdClause, data)\n\taliasSyslogList := aliassyslog.NewListCommand(aliasSyslogRoot.CmdClause, data)\n\taliasSyslogUpdate := aliassyslog.NewUpdateCommand(aliasSyslogRoot.CmdClause, data)\n\n\tif data.SSORunner == nil {\n\t\tdata.SSORunner = func(in io.Reader, out io.Writer, forceReAuth bool, skipPrompt bool) error {\n\t\t\treturn authcmd.RunSSO(in, out, data, forceReAuth, skipPrompt)\n\t\t}\n\t}\n\n\tcmds := []argparser.Command{\n\t\tshellcompleteCmdRoot,\n\t}\n\tcmds = append(cmds, authCommands...)\n\tcmds = append(cmds, authtokenCommands...)\n\tcmds = append(cmds, []argparser.Command{\n\t\tapisecurityRoot,\n\t\tdiscoveredoperationsRoot,\n\t\tdiscoveredoperationsList,\n\t\tdiscoveredoperationsUpdate,\n\t\toperationsRoot,\n\t\toperationsList,\n\t\toperationsCreate,\n\t\toperationsDescribe,\n\t\toperationsUpdate,\n\t\toperationsDelete,\n\t\toperationsAddTags,\n\t\ttagsRoot,\n\t\ttagsCreate,\n\t\ttagsDelete,\n\t\ttagsGet,\n\t\ttagsList,\n\t\ttagsUpdate,\n\t\tcomputeCmdRoot,\n\t\tcomputeACLCmdRoot,\n\t\tcomputeACLCreate,\n\t\tcomputeACLList,\n\t\tcomputeACLDescribe,\n\t\tcomputeACLDelete,\n\t\tcomputeACLUpdate,\n\t\tcomputeACLLookup,\n\t\tcomputeACLEntriesList,\n\t\tcomputeBuild,\n\t\tcomputeDeploy,\n\t\tcomputeHashFiles,\n\t\tcomputeInit,\n\t\tcomputeMetadata,\n\t\tcomputePack,\n\t\tcomputePublish,\n\t\tcomputeServe,\n\t\tcomputeUpdate,\n\t\tcomputeValidate,\n\t\tconfigCmdRoot,\n\t\tconfigstoreCmdRoot,\n\t\tconfigstoreCreate,\n\t\tconfigstoreDelete,\n\t\tconfigstoreDescribe,\n\t\tconfigstoreList,\n\t\tconfigstoreListServices,\n\t\tconfigstoreUpdate,\n\t\tconfigstoreentryCmdRoot,\n\t\tconfigstoreentryCreate,\n\t\tconfigstoreentryDelete,\n\t\tconfigstoreentryDescribe,\n\t\tconfigstoreentryList,\n\t\tconfigstoreentryUpdate,\n\t\tdashboardCmdRoot,\n\t\tdashboardList,\n\t\tdashboardCreate,\n\t\tdashboardDescribe,\n\t\tdashboardUpdate,\n\t\tdashboardDelete,\n\t\tdashboardItemCmdRoot,\n\t\tdashboardItemCreate,\n\t\tdashboardItemDescribe,\n\t\tdashboardItemUpdate,\n\t\tdashboardItemDelete,\n\t\tdomainCmdRoot,\n\t\tdomainCreate,\n\t\tdomainDelete,\n\t\tdomainDescribe,\n\t\tdomainList,\n\t\tdomainUpdate,\n\t\tinstallRoot,\n\t\tipCmdRoot,\n\t\tkvstoreCreate,\n\t\tkvstoreDelete,\n\t\tkvstoreDescribe,\n\t\tkvstoreList,\n\t\tkvstoreentryCreate,\n\t\tkvstoreentryDelete,\n\t\tkvstoreentryGet,\n\t\tkvstoreentryDescribe,\n\t\tkvstoreentryList,\n\t\tlogtailCmdRoot,\n\t\tserviceloggingDebugCmd,\n\t\tserviceloggingAzureblobCmdRoot,\n\t\tserviceloggingAzureblobCreate,\n\t\tserviceloggingAzureblobDelete,\n\t\tserviceloggingAzureblobDescribe,\n\t\tserviceloggingAzureblobList,\n\t\tserviceloggingAzureblobUpdate,\n\t\tserviceloggingBigQueryCmdRoot,\n\t\tserviceloggingBigQueryCreate,\n\t\tserviceloggingBigQueryDelete,\n\t\tserviceloggingBigQueryDescribe,\n\t\tserviceloggingBigQueryList,\n\t\tserviceloggingBigQueryUpdate,\n\t\tserviceloggingCloudfilesCmdRoot,\n\t\tserviceloggingCloudfilesCreate,\n\t\tserviceloggingCloudfilesDelete,\n\t\tserviceloggingCloudfilesDescribe,\n\t\tserviceloggingCloudfilesList,\n\t\tserviceloggingCloudfilesUpdate,\n\t\tserviceloggingCmdRoot,\n\t\tserviceloggingDatadogCmdRoot,\n\t\tserviceloggingDatadogCreate,\n\t\tserviceloggingDatadogDelete,\n\t\tserviceloggingDatadogDescribe,\n\t\tserviceloggingDatadogList,\n\t\tserviceloggingDatadogUpdate,\n\t\tserviceloggingDigitaloceanCmdRoot,\n\t\tserviceloggingDigitaloceanCreate,\n\t\tserviceloggingDigitaloceanDelete,\n\t\tserviceloggingDigitaloceanDescribe,\n\t\tserviceloggingDigitaloceanList,\n\t\tserviceloggingDigitaloceanUpdate,\n\t\tserviceloggingElasticsearchCmdRoot,\n\t\tserviceloggingElasticsearchCreate,\n\t\tserviceloggingElasticsearchDelete,\n\t\tserviceloggingElasticsearchDescribe,\n\t\tserviceloggingElasticsearchList,\n\t\tserviceloggingElasticsearchUpdate,\n\t\tserviceloggingFtpCmdRoot,\n\t\tserviceloggingFtpCreate,\n\t\tserviceloggingFtpDelete,\n\t\tserviceloggingFtpDescribe,\n\t\tserviceloggingFtpList,\n\t\tserviceloggingFtpUpdate,\n\t\tserviceloggingGcsCmdRoot,\n\t\tserviceloggingGcsCreate,\n\t\tserviceloggingGcsDelete,\n\t\tserviceloggingGcsDescribe,\n\t\tserviceloggingGcsList,\n\t\tserviceloggingGcsUpdate,\n\t\tserviceloggingGooglepubsubCmdRoot,\n\t\tserviceloggingGooglepubsubCreate,\n\t\tserviceloggingGooglepubsubDelete,\n\t\tserviceloggingGooglepubsubDescribe,\n\t\tserviceloggingGooglepubsubList,\n\t\tserviceloggingGooglepubsubUpdate,\n\t\tserviceloggingGrafanacloudlogsCmdRoot,\n\t\tserviceloggingGrafanacloudlogsCreate,\n\t\tserviceloggingGrafanacloudlogsDelete,\n\t\tserviceloggingGrafanacloudlogsDescribe,\n\t\tserviceloggingGrafanacloudlogsList,\n\t\tserviceloggingGrafanacloudlogsUpdate,\n\t\tserviceloggingHerokuCmdRoot,\n\t\tserviceloggingHerokuCreate,\n\t\tserviceloggingHerokuDelete,\n\t\tserviceloggingHerokuDescribe,\n\t\tserviceloggingHerokuList,\n\t\tserviceloggingHerokuUpdate,\n\t\tserviceloggingHoneycombCmdRoot,\n\t\tserviceloggingHoneycombCreate,\n\t\tserviceloggingHoneycombDelete,\n\t\tserviceloggingHoneycombDescribe,\n\t\tserviceloggingHoneycombList,\n\t\tserviceloggingHoneycombUpdate,\n\t\tserviceloggingHTTPSCmdRoot,\n\t\tserviceloggingHTTPSCreate,\n\t\tserviceloggingHTTPSDelete,\n\t\tserviceloggingHTTPSDescribe,\n\t\tserviceloggingHTTPSList,\n\t\tserviceloggingHTTPSUpdate,\n\t\tserviceloggingKafkaCmdRoot,\n\t\tserviceloggingKafkaCreate,\n\t\tserviceloggingKafkaDelete,\n\t\tserviceloggingKafkaDescribe,\n\t\tserviceloggingKafkaList,\n\t\tserviceloggingKafkaUpdate,\n\t\tserviceloggingKinesisCmdRoot,\n\t\tserviceloggingKinesisCreate,\n\t\tserviceloggingKinesisDelete,\n\t\tserviceloggingKinesisDescribe,\n\t\tserviceloggingKinesisList,\n\t\tserviceloggingKinesisUpdate,\n\t\tserviceloggingLogglyCmdRoot,\n\t\tserviceloggingLogglyCreate,\n\t\tserviceloggingLogglyDelete,\n\t\tserviceloggingLogglyDescribe,\n\t\tserviceloggingLogglyList,\n\t\tserviceloggingLogglyUpdate,\n\t\tserviceloggingLogshuttleCmdRoot,\n\t\tserviceloggingLogshuttleCreate,\n\t\tserviceloggingLogshuttleDelete,\n\t\tserviceloggingLogshuttleDescribe,\n\t\tserviceloggingLogshuttleList,\n\t\tserviceloggingLogshuttleUpdate,\n\t\tserviceloggingNewRelicCmdRoot,\n\t\tserviceloggingNewRelicCreate,\n\t\tserviceloggingNewRelicDelete,\n\t\tserviceloggingNewRelicDescribe,\n\t\tserviceloggingNewRelicList,\n\t\tserviceloggingNewRelicUpdate,\n\t\tserviceloggingNewRelicOTLPCmdRoot,\n\t\tserviceloggingNewRelicOTLPCreate,\n\t\tserviceloggingNewRelicOTLPDelete,\n\t\tserviceloggingNewRelicOTLPDescribe,\n\t\tserviceloggingNewRelicOTLPList,\n\t\tserviceloggingNewRelicOTLPUpdate,\n\t\tserviceloggingOpenstackCmdRoot,\n\t\tserviceloggingOpenstackCreate,\n\t\tserviceloggingOpenstackDelete,\n\t\tserviceloggingOpenstackDescribe,\n\t\tserviceloggingOpenstackList,\n\t\tserviceloggingOpenstackUpdate,\n\t\tserviceloggingPapertrailCmdRoot,\n\t\tserviceloggingPapertrailCreate,\n\t\tserviceloggingPapertrailDelete,\n\t\tserviceloggingPapertrailDescribe,\n\t\tserviceloggingPapertrailList,\n\t\tserviceloggingPapertrailUpdate,\n\t\tserviceloggingS3CmdRoot,\n\t\tserviceloggingS3Create,\n\t\tserviceloggingS3Delete,\n\t\tserviceloggingS3Describe,\n\t\tserviceloggingS3List,\n\t\tserviceloggingS3Update,\n\t\tserviceloggingScalyrCmdRoot,\n\t\tserviceloggingScalyrCreate,\n\t\tserviceloggingScalyrDelete,\n\t\tserviceloggingScalyrDescribe,\n\t\tserviceloggingScalyrList,\n\t\tserviceloggingScalyrUpdate,\n\t\tserviceloggingSftpCmdRoot,\n\t\tserviceloggingSftpCreate,\n\t\tserviceloggingSftpDelete,\n\t\tserviceloggingSftpDescribe,\n\t\tserviceloggingSftpList,\n\t\tserviceloggingSftpUpdate,\n\t\tserviceloggingSplunkCmdRoot,\n\t\tserviceloggingSplunkCreate,\n\t\tserviceloggingSplunkDelete,\n\t\tserviceloggingSplunkDescribe,\n\t\tserviceloggingSplunkList,\n\t\tserviceloggingSplunkUpdate,\n\t\tserviceloggingSumologicCmdRoot,\n\t\tserviceloggingSumologicCreate,\n\t\tserviceloggingSumologicDelete,\n\t\tserviceloggingSumologicDescribe,\n\t\tserviceloggingSumologicList,\n\t\tserviceloggingSumologicUpdate,\n\t\tserviceloggingSyslogCmdRoot,\n\t\tserviceloggingSyslogCreate,\n\t\tserviceloggingSyslogDelete,\n\t\tserviceloggingSyslogDescribe,\n\t\tserviceloggingSyslogList,\n\t\tserviceloggingSyslogUpdate,\n\t\tngwafRoot,\n\t\tngwafRedactionCreate,\n\t\tngwafRedactionDelete,\n\t\tngwafRedactionList,\n\t\tngwafRedactionRetrieve,\n\t\tngwafRedactionUpdate,\n\t\tngwafRedactionRoot,\n\t\tngwafCountryListRoot,\n\t\tngwafCountryListCreate,\n\t\tngwafCountryListDelete,\n\t\tngwafCountryListGet,\n\t\tngwafCountryListList,\n\t\tngwafCountryListUpdate,\n\t\tngwafCustomSignalRoot,\n\t\tngwafCustomSignalCreate,\n\t\tngwafCustomSignalDelete,\n\t\tngwafCustomSignalGet,\n\t\tngwafCustomSignalList,\n\t\tngwafCustomSignalUpdate,\n\t\tngwafIPListRoot,\n\t\tngwafIPListCreate,\n\t\tngwafIPListDelete,\n\t\tngwafIPListGet,\n\t\tngwafIPListList,\n\t\tngwafIPListUpdate,\n\t\tngwafRuleRoot,\n\t\tngwafRuleCreate,\n\t\tngwafRuleDelete,\n\t\tngwafRuleGet,\n\t\tngwafRuleList,\n\t\tngwafRuleUpdate,\n\t\tngwafSignalListRoot,\n\t\tngwafSignalListCreate,\n\t\tngwafSignalListDelete,\n\t\tngwafSignalListGet,\n\t\tngwafSignalListList,\n\t\tngwafSignalListUpdate,\n\t\tngwafStringListRoot,\n\t\tngwafStringListCreate,\n\t\tngwafStringListDelete,\n\t\tngwafStringListGet,\n\t\tngwafStringListList,\n\t\tngwafStringListUpdate,\n\t\tngwafWildcardListCreate,\n\t\tngwafWildcardListDelete,\n\t\tngwafWildcardListGet,\n\t\tngwafWildcardListList,\n\t\tngwafWildcardListUpdate,\n\t\tngwafWorkspaceCountryListRoot,\n\t\tngwafWorkspaceCountryListCreate,\n\t\tngwafWorkspaceCountryListDelete,\n\t\tngwafWorkspaceCountryListGet,\n\t\tngwafWorkspaceCountryListList,\n\t\tngwafWorkspaceCountryListUpdate,\n\t\tngwafWorkspaceCustomSignalRoot,\n\t\tngwafWorkspaceCustomSignalCreate,\n\t\tngwafWorkspaceCustomSignalDelete,\n\t\tngwafWorkspaceCustomSignalGet,\n\t\tngwafWorkspaceCustomSignalList,\n\t\tngwafWorkspaceCustomSignalUpdate,\n\t\tngwafWorkspaceIPListRoot,\n\t\tngwafWorkspaceIPListCreate,\n\t\tngwafWorkspaceIPListDelete,\n\t\tngwafWorkspaceIPListGet,\n\t\tngwafWorkspaceIPListList,\n\t\tngwafWorkspaceIPListUpdate,\n\t\tngwafWorkspaceRuleRoot,\n\t\tngwafWorkspaceRuleCreate,\n\t\tngwafWorkspaceRuleDelete,\n\t\tngwafWorkspaceRuleGet,\n\t\tngwafWorkspaceRuleList,\n\t\tngwafWorkspaceRuleUpdate,\n\t\tngwafWorkspaceSignalListRoot,\n\t\tngwafWorkspaceSignalListCreate,\n\t\tngwafWorkspaceSignalListDelete,\n\t\tngwafWorkspaceSignalListGet,\n\t\tngwafWorkspaceSignalListList,\n\t\tngwafWorkspaceSignalListUpdate,\n\t\tngwafWorkspaceStringListRoot,\n\t\tngwafWorkspaceStringListCreate,\n\t\tngwafWorkspaceStringListDelete,\n\t\tngwafWorkspaceStringListGet,\n\t\tngwafWorkspaceStringListList,\n\t\tngwafWorkspaceStringListUpdate,\n\t\tngwafWorkspaceThresholdRoot,\n\t\tngwafWorkspaceThresholdCreate,\n\t\tngwafWorkspaceThresholdDelete,\n\t\tngwafWorkspaceThresholdGet,\n\t\tngwafWorkspaceThresholdList,\n\t\tngwafWorkspaceThresholdUpdate,\n\t\tngwafWorkspaceWildcardListCreate,\n\t\tngwafWorkspaceWildcardListDelete,\n\t\tngwafWorkspaceWildcardListGet,\n\t\tngwafWorkspaceWildcardListList,\n\t\tngwafWorkspaceWildcardListUpdate,\n\t\tngwafVirtualpatchList,\n\t\tngwafVirtualpatchRetrieve,\n\t\tngwafVirtualpatchRoot,\n\t\tngwafVirtualpatchUpdate,\n\t\tngwafWorkspaceAlertRoot,\n\t\tngwafWorkspaceAlertDatadogRoot,\n\t\tngwafWorkspaceAlertDatadogCreate,\n\t\tngwafWorkspaceAlertDatadogDelete,\n\t\tngwafWorkspaceAlertDatadogGet,\n\t\tngwafWorkspaceAlertDatadogList,\n\t\tngwafWorkspaceAlertDatadogUpdate,\n\t\tngwafWorkspaceAlertJiraRoot,\n\t\tngwafWorkspaceAlertJiraCreate,\n\t\tngwafWorkspaceAlertJiraDelete,\n\t\tngwafWorkspaceAlertJiraGet,\n\t\tngwafWorkspaceAlertJiraList,\n\t\tngwafWorkspaceAlertJiraUpdate,\n\t\tngwafWorkspaceAlertMailinglistRoot,\n\t\tngwafWorkspaceAlertMailinglistCreate,\n\t\tngwafWorkspaceAlertMailinglistDelete,\n\t\tngwafWorkspaceAlertMailinglistGet,\n\t\tngwafWorkspaceAlertMailinglistList,\n\t\tngwafWorkspaceAlertMailinglistUpdate,\n\t\tngwafWorkspaceAlertMicrosoftteamsRoot,\n\t\tngwafWorkspaceAlertMicrosoftteamsCreate,\n\t\tngwafWorkspaceAlertMicrosoftteamsDelete,\n\t\tngwafWorkspaceAlertMicrosoftteamsGet,\n\t\tngwafWorkspaceAlertMicrosoftteamsList,\n\t\tngwafWorkspaceAlertMicrosoftteamsUpdate,\n\t\tngwafWorkspaceAlertOpsgenieRoot,\n\t\tngwafWorkspaceAlertOpsgenieCreate,\n\t\tngwafWorkspaceAlertOpsgenieDelete,\n\t\tngwafWorkspaceAlertOpsgenieGet,\n\t\tngwafWorkspaceAlertOpsgenieList,\n\t\tngwafWorkspaceAlertOpsgenieUpdate,\n\t\tngwafWorkspaceAlertPagerdutyRoot,\n\t\tngwafWorkspaceAlertPagerdutyCreate,\n\t\tngwafWorkspaceAlertPagerdutyDelete,\n\t\tngwafWorkspaceAlertPagerdutyGet,\n\t\tngwafWorkspaceAlertPagerdutyList,\n\t\tngwafWorkspaceAlertPagerdutyUpdate,\n\t\tngwafWorkspaceAlertSlackRoot,\n\t\tngwafWorkspaceAlertSlackCreate,\n\t\tngwafWorkspaceAlertSlackDelete,\n\t\tngwafWorkspaceAlertSlackGet,\n\t\tngwafWorkspaceAlertSlackList,\n\t\tngwafWorkspaceAlertSlackUpdate,\n\t\tngwafWorkspaceAlertWebhookRoot,\n\t\tngwafWorkspaceAlertWebhookCreate,\n\t\tngwafWorkspaceAlertWebhookDelete,\n\t\tngwafWorkspaceAlertWebhookGet,\n\t\tngwafWorkspaceAlertWebhookGetSigningKey,\n\t\tngwafWorkspaceAlertWebhookList,\n\t\tngwafWorkspaceAlertWebhookRotateSigningKey,\n\t\tngwafWorkspaceAlertWebhookUpdate,\n\t\tngwafWorkspaceRoot,\n\t\tngwafWorkspaceCreate,\n\t\tngwafWorkspaceDelete,\n\t\tngwafWorkspaceGet,\n\t\tngwafWorkspaceList,\n\t\tngwafWorkspaceUpdate,\n\t\tobjectStorageRoot,\n\t\tobjectStorageAccesskeysRoot,\n\t\tobjectStorageAccesskeysCreate,\n\t\tobjectStorageAccesskeysDelete,\n\t\tobjectStorageAccesskeysGet,\n\t\tobjectStorageAccesskeysList,\n\t\tpopCmdRoot,\n\t\tproductsCmdRoot,\n\t}...)\n\tcmds = append(cmds, profileCommands...)\n\tcmds = append(cmds, []argparser.Command{\n\t\tsecretstoreCreate,\n\t\tsecretstoreDescribe,\n\t\tsecretstoreDelete,\n\t\tsecretstoreList,\n\t\tsecretstoreentryCreate,\n\t\tsecretstoreentryDescribe,\n\t\tsecretstoreentryDelete,\n\t\tsecretstoreentryList,\n\t\tserviceCmdRoot,\n\t\tserviceCreate,\n\t\tserviceDelete,\n\t\tserviceDescribe,\n\t\tserviceList,\n\t\tserviceSearch,\n\t\tserviceUpdate,\n\t\tservicePurge,\n\t\tservicealertCreate,\n\t\tservicealertDelete,\n\t\tservicealertDescribe,\n\t\tservicealertList,\n\t\tservicealertListHistory,\n\t\tservicealertUpdate,\n\t\tserviceaclCmdRoot,\n\t\tserviceaclCreate,\n\t\tserviceaclDelete,\n\t\tserviceaclDescribe,\n\t\tserviceaclList,\n\t\tserviceaclUpdate,\n\t\tserviceaclentryCmdRoot,\n\t\tserviceaclentryCreate,\n\t\tserviceaclentryDelete,\n\t\tserviceaclentryDescribe,\n\t\tserviceaclentryList,\n\t\tserviceaclentryUpdate,\n\t\tserviceauthCmdRoot,\n\t\tserviceauthCreate,\n\t\tserviceauthDelete,\n\t\tserviceauthDescribe,\n\t\tserviceauthList,\n\t\tserviceauthUpdate,\n\t\tservicedictionaryCmdRoot,\n\t\tservicedictionaryCreate,\n\t\tservicedictionaryDelete,\n\t\tservicedictionaryDescribe,\n\t\tservicedictionaryList,\n\t\tservicedictionaryUpdate,\n\t\tservicevclCmdRoot,\n\t\tservicevclDescribe,\n\t\tservicevclConditionCmdRoot,\n\t\tservicevclConditionCreate,\n\t\tservicevclConditionDelete,\n\t\tservicevclConditionDescribe,\n\t\tservicevclConditionList,\n\t\tservicevclConditionUpdate,\n\t\tservicevclCustomCmdRoot,\n\t\tservicevclCustomCreate,\n\t\tservicevclCustomDelete,\n\t\tservicevclCustomDescribe,\n\t\tservicevclCustomList,\n\t\tservicevclCustomUpdate,\n\t\tservicevclSnippetCmdRoot,\n\t\tservicevclSnippetCreate,\n\t\tservicevclSnippetDelete,\n\t\tservicevclSnippetDescribe,\n\t\tservicevclSnippetList,\n\t\tservicevclSnippetUpdate,\n\t\tservicedomainCmdRoot,\n\t\tservicedomainCreate,\n\t\tservicedomainDelete,\n\t\tservicedomainDescribe,\n\t\tservicedomainList,\n\t\tservicedomainUpdate,\n\t\tservicedomainValidate,\n\t\tservicedictionaryentryCmdRoot,\n\t\tservicedictionaryentryCreate,\n\t\tservicedictionaryentryDelete,\n\t\tservicedictionaryentryDescribe,\n\t\tservicedictionaryentryList,\n\t\tservicedictionaryentryUpdate,\n\t\tservicebackendCmdRoot,\n\t\tservicebackendCreate,\n\t\tservicebackendDelete,\n\t\tservicebackendDescribe,\n\t\tservicebackendList,\n\t\tservicebackendUpdate,\n\t\tservicehealthcheckCmdRoot,\n\t\tservicehealthcheckCreate,\n\t\tservicehealthcheckDelete,\n\t\tservicehealthcheckDescribe,\n\t\tservicehealthcheckList,\n\t\tservicehealthcheckUpdate,\n\t\tserviceimageoptimizerdefaultsCmdRoot,\n\t\tserviceimageoptimizerdefaultsGet,\n\t\tserviceimageoptimizerdefaultsUpdate,\n\t\tserviceratelimitCmdRoot,\n\t\tserviceratelimitCreate,\n\t\tserviceratelimitDelete,\n\t\tserviceratelimitDescribe,\n\t\tserviceratelimitList,\n\t\tserviceratelimitUpdate,\n\t\tserviceresourcelinkCmdRoot,\n\t\tserviceresourcelinkCreate,\n\t\tserviceresourcelinkDelete,\n\t\tserviceresourcelinkDescribe,\n\t\tserviceresourcelinkList,\n\t\tserviceresourcelinkUpdate,\n\t\tserviceVersionActivate,\n\t\tserviceVersionClone,\n\t\tserviceVersionCmdRoot,\n\t\tserviceVersionDeactivate,\n\t\tserviceVersionList,\n\t\tserviceVersionLock,\n\t\tserviceVersionStage,\n\t\tserviceVersionUnstage,\n\t\tserviceVersionUpdate,\n\t\tserviceVersionValidate,\n\t}...)\n\tcmds = append(cmds, ssoCommands...)\n\tcmds = append(cmds, []argparser.Command{\n\t\tstatsCmdRoot,\n\t\tstatsAggregate,\n\t\tstatsDomainInspector,\n\t\tstatsHistorical,\n\t\tstatsOriginInspector,\n\t\tstatsRealtime,\n\t\tstatsRegions,\n\t\tstatsUsage,\n\t\ttlsConfigCmdRoot,\n\t\ttlsConfigDescribe,\n\t\ttlsConfigList,\n\t\ttlsConfigUpdate,\n\t\ttlsCustomCmdRoot,\n\t\ttlsCustomActivationCmdRoot,\n\t\ttlsCustomActivationCreate,\n\t\ttlsCustomActivationDelete,\n\t\ttlsCustomActivationDescribe,\n\t\ttlsCustomActivationList,\n\t\ttlsCustomActivationUpdate,\n\t\ttlsCustomCertificateCmdRoot,\n\t\ttlsCustomCertificateCreate,\n\t\ttlsCustomCertificateDelete,\n\t\ttlsCustomCertificateDescribe,\n\t\ttlsCustomCertificateList,\n\t\ttlsCustomCertificateUpdate,\n\t\ttlsCustomDomainCmdRoot,\n\t\ttlsCustomDomainList,\n\t\ttlsCustomPrivateKeyCmdRoot,\n\t\ttlsCustomPrivateKeyCreate,\n\t\ttlsCustomPrivateKeyDelete,\n\t\ttlsCustomPrivateKeyDescribe,\n\t\ttlsCustomPrivateKeyList,\n\t\ttlsPlatformCmdRoot,\n\t\ttlsPlatformCreate,\n\t\ttlsPlatformDelete,\n\t\ttlsPlatformDescribe,\n\t\ttlsPlatformList,\n\t\ttlsPlatformUpdate,\n\t\ttlsSubscriptionCmdRoot,\n\t\ttlsSubscriptionCreate,\n\t\ttlsSubscriptionDelete,\n\t\ttlsSubscriptionDescribe,\n\t\ttlsSubscriptionList,\n\t\ttlsSubscriptionUpdate,\n\t\ttoolsCmdRoot,\n\t\ttoolsDomainCmdRoot,\n\t\ttoolsDomainStatus,\n\t\ttoolsDomainSuggestions,\n\t\tupdateRoot,\n\t\tuserCmdRoot,\n\t\tuserCreate,\n\t\tuserDelete,\n\t\tuserDescribe,\n\t\tuserList,\n\t\tuserUpdate,\n\t\tversionCmdRoot,\n\t}...)\n\tcmds = append(cmds, whoamiCommands...)\n\tcmds = append(cmds, []argparser.Command{\n\t\taliasBackendCreate,\n\t\taliasBackendDelete,\n\t\taliasBackendDescribe,\n\t\taliasBackendList,\n\t\taliasBackendUpdate,\n\t\taliasDictionaryEntryCreate,\n\t\taliasDictionaryEntryDelete,\n\t\taliasDictionaryEntryDescribe,\n\t\taliasDictionaryEntryList,\n\t\taliasDictionaryEntryUpdate,\n\t\taliasDictionaryCreate,\n\t\taliasDictionaryDelete,\n\t\taliasDictionaryDescribe,\n\t\taliasDictionaryList,\n\t\taliasDictionaryUpdate,\n\t\taliasHealthcheckCreate,\n\t\taliasHealthcheckDelete,\n\t\taliasHealthcheckDescribe,\n\t\taliasHealthcheckList,\n\t\taliasHealthcheckUpdate,\n\t\taliasimageoptimizerdefaultsGet,\n\t\taliasimageoptimizerdefaultsUpdate,\n\t\taliasPurge,\n\t\taliasAlertRoot,\n\t\taliasAlertCreate,\n\t\taliasAlertDelete,\n\t\taliasAlertDescribe,\n\t\taliasAlertList,\n\t\taliasAlertListHistory,\n\t\taliasAlertUpdate,\n\t\taliasACLCreate,\n\t\taliasACLDelete,\n\t\taliasACLDescribe,\n\t\taliasACLList,\n\t\taliasACLUpdate,\n\t\taliasACLEntryCreate,\n\t\taliasACLEntryDelete,\n\t\taliasACLEntryDescribe,\n\t\taliasACLEntryList,\n\t\taliasACLEntryUpdate,\n\t\taliasRateLimitCreate,\n\t\taliasRateLimitDelete,\n\t\taliasRateLimitDescribe,\n\t\taliasRateLimitList,\n\t\taliasRateLimitUpdate,\n\t\taliasResourceLinkCreate,\n\t\taliasResourceLinkDelete,\n\t\taliasResourceLinkDescribe,\n\t\taliasResourceLinkList,\n\t\taliasResourceLinkUpdate,\n\t\taliasServiceAuthCreate,\n\t\taliasServiceAuthDelete,\n\t\taliasServiceAuthDescribe,\n\t\taliasServiceAuthList,\n\t\taliasServiceAuthUpdate,\n\t\taliasVclDescribe,\n\t\taliasVclConditionCreate,\n\t\taliasVclConditionDelete,\n\t\taliasVclConditionDescribe,\n\t\taliasVclConditionList,\n\t\taliasVclConditionUpdate,\n\t\taliasVclCustomCreate,\n\t\taliasVclCustomDelete,\n\t\taliasVclCustomDescribe,\n\t\taliasVclCustomList,\n\t\taliasVclCustomUpdate,\n\t\taliasVclSnippetCreate,\n\t\taliasVclSnippetDelete,\n\t\taliasVclSnippetDescribe,\n\t\taliasVclSnippetList,\n\t\taliasVclSnippetUpdate,\n\t\taliasServiceVersionActivate,\n\t\taliasServiceVersionClone,\n\t\taliasServiceVersionDeactivate,\n\t\taliasServiceVersionList,\n\t\taliasServiceVersionLock,\n\t\taliasServiceVersionStage,\n\t\taliasServiceVersionUnstage,\n\t\taliasServiceVersionUpdate,\n\t\taliasLoggingRoot,\n\t\taliasAzureblobRoot,\n\t\taliasAzureblobCreate,\n\t\taliasAzureblobDelete,\n\t\taliasAzureblobDescribe,\n\t\taliasAzureblobList,\n\t\taliasAzureblobUpdate,\n\t\taliasBigqueryRoot,\n\t\taliasBigqueryCreate,\n\t\taliasBigqueryDelete,\n\t\taliasBigqueryDescribe,\n\t\taliasBigqueryList,\n\t\taliasBigqueryUpdate,\n\t\taliasCloudfilesRoot,\n\t\taliasCloudfilesCreate,\n\t\taliasCloudfilesDelete,\n\t\taliasCloudfilesDescribe,\n\t\taliasCloudfilesList,\n\t\taliasCloudfilesUpdate,\n\t\taliasDatadogRoot,\n\t\taliasDatadogCreate,\n\t\taliasDatadogDelete,\n\t\taliasDatadogDescribe,\n\t\taliasDatadogList,\n\t\taliasDatadogUpdate,\n\t\taliasDigitaloceanRoot,\n\t\taliasDigitaloceanCreate,\n\t\taliasDigitaloceanDelete,\n\t\taliasDigitaloceanDescribe,\n\t\taliasDigitaloceanList,\n\t\taliasDigitaloceanUpdate,\n\t\taliasElasticsearchRoot,\n\t\taliasElasticsearchCreate,\n\t\taliasElasticsearchDelete,\n\t\taliasElasticsearchDescribe,\n\t\taliasElasticsearchList,\n\t\taliasElasticsearchUpdate,\n\t\taliasFtpRoot,\n\t\taliasFtpCreate,\n\t\taliasFtpDelete,\n\t\taliasFtpDescribe,\n\t\taliasFtpList,\n\t\taliasFtpUpdate,\n\t\taliasGcsRoot,\n\t\taliasGcsCreate,\n\t\taliasGcsDelete,\n\t\taliasGcsDescribe,\n\t\taliasGcsList,\n\t\taliasGcsUpdate,\n\t\taliasGooglepubsubRoot,\n\t\taliasGooglepubsubCreate,\n\t\taliasGooglepubsubDelete,\n\t\taliasGooglepubsubDescribe,\n\t\taliasGooglepubsubList,\n\t\taliasGooglepubsubUpdate,\n\t\taliasGrafanacloudlogsRoot,\n\t\taliasGrafanacloudlogsCreate,\n\t\taliasGrafanacloudlogsDelete,\n\t\taliasGrafanacloudlogsDescribe,\n\t\taliasGrafanacloudlogsList,\n\t\taliasGrafanacloudlogsUpdate,\n\t\taliasHerokuRoot,\n\t\taliasHerokuCreate,\n\t\taliasHerokuDelete,\n\t\taliasHerokuDescribe,\n\t\taliasHerokuList,\n\t\taliasHerokuUpdate,\n\t\taliasHoneycombRoot,\n\t\taliasHoneycombCreate,\n\t\taliasHoneycombDelete,\n\t\taliasHoneycombDescribe,\n\t\taliasHoneycombList,\n\t\taliasHoneycombUpdate,\n\t\taliasHTTPSRoot,\n\t\taliasHTTPSCreate,\n\t\taliasHTTPSDelete,\n\t\taliasHTTPSDescribe,\n\t\taliasHTTPSList,\n\t\taliasHTTPSUpdate,\n\t\taliasKafkaRoot,\n\t\taliasKafkaCreate,\n\t\taliasKafkaDelete,\n\t\taliasKafkaDescribe,\n\t\taliasKafkaList,\n\t\taliasKafkaUpdate,\n\t\taliasKinesisRoot,\n\t\taliasKinesisCreate,\n\t\taliasKinesisDelete,\n\t\taliasKinesisDescribe,\n\t\taliasKinesisList,\n\t\taliasKinesisUpdate,\n\t\taliasLogglyRoot,\n\t\taliasLogglyCreate,\n\t\taliasLogglyDelete,\n\t\taliasLogglyDescribe,\n\t\taliasLogglyList,\n\t\taliasLogglyUpdate,\n\t\taliasLogshuttleRoot,\n\t\taliasLogshuttleCreate,\n\t\taliasLogshuttleDelete,\n\t\taliasLogshuttleDescribe,\n\t\taliasLogshuttleList,\n\t\taliasLogshuttleUpdate,\n\t\taliasNewrelicRoot,\n\t\taliasNewrelicCreate,\n\t\taliasNewrelicDelete,\n\t\taliasNewrelicDescribe,\n\t\taliasNewrelicList,\n\t\taliasNewrelicUpdate,\n\t\taliasNewrelicotlpRoot,\n\t\taliasNewrelicotlpCreate,\n\t\taliasNewrelicotlpDelete,\n\t\taliasNewrelicotlpDescribe,\n\t\taliasNewrelicotlpList,\n\t\taliasNewrelicotlpUpdate,\n\t\taliasOpenstackRoot,\n\t\taliasOpenstackCreate,\n\t\taliasOpenstackDelete,\n\t\taliasOpenstackDescribe,\n\t\taliasOpenstackList,\n\t\taliasOpenstackUpdate,\n\t\taliasPapertrailRoot,\n\t\taliasPapertrailCreate,\n\t\taliasPapertrailDelete,\n\t\taliasPapertrailDescribe,\n\t\taliasPapertrailList,\n\t\taliasPapertrailUpdate,\n\t\taliasS3Root,\n\t\taliasS3Create,\n\t\taliasS3Delete,\n\t\taliasS3Describe,\n\t\taliasS3List,\n\t\taliasS3Update,\n\t\taliasScalyrRoot,\n\t\taliasScalyrCreate,\n\t\taliasScalyrDelete,\n\t\taliasScalyrDescribe,\n\t\taliasScalyrList,\n\t\taliasScalyrUpdate,\n\t\taliasSftpRoot,\n\t\taliasSftpCreate,\n\t\taliasSftpDelete,\n\t\taliasSftpDescribe,\n\t\taliasSftpList,\n\t\taliasSftpUpdate,\n\t\taliasSplunkRoot,\n\t\taliasSplunkCreate,\n\t\taliasSplunkDelete,\n\t\taliasSplunkDescribe,\n\t\taliasSplunkList,\n\t\taliasSplunkUpdate,\n\t\taliasSumologicRoot,\n\t\taliasSumologicCreate,\n\t\taliasSumologicDelete,\n\t\taliasSumologicDescribe,\n\t\taliasSumologicList,\n\t\taliasSumologicUpdate,\n\t\taliasSyslogRoot,\n\t\taliasSyslogCreate,\n\t\taliasSyslogDelete,\n\t\taliasSyslogDescribe,\n\t\taliasSyslogList,\n\t\taliasSyslogUpdate,\n\t}...)\n\treturn cmds\n}\n"
  },
  {
    "path": "pkg/commands/commands_test.go",
    "content": "package commands_test\n\nimport (\n\t\"bytes\"\n\t\"io\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/fastly/kingpin\"\n\n\t\"github.com/fastly/cli/pkg/commands\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n)\n\n// authCommandPrefixes lists the command names (and prefixes for subcommands)\n// that should be excluded when FASTLY_DISABLE_AUTH_COMMAND is set.\nvar authCommandPrefixes = []string{\"auth\", \"auth-token\", \"sso\", \"profile\", \"whoami\"}\n\n// isAuthRelated reports whether a command name belongs to an auth-related\n// command group.\nfunc isAuthRelated(name string) bool {\n\tfor _, prefix := range authCommandPrefixes {\n\t\tif name == prefix || strings.HasPrefix(name, prefix+\" \") {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc TestDefineDisableAuthCommand(t *testing.T) {\n\tnewApp := func(stdout *bytes.Buffer) *kingpin.Application {\n\t\tapp := kingpin.New(\"fastly\", \"test\")\n\t\tapp.Writers(stdout, io.Discard)\n\t\tapp.Terminate(nil)\n\t\treturn app\n\t}\n\n\tt.Run(\"auth-related commands present by default\", func(t *testing.T) {\n\t\tvar stdout bytes.Buffer\n\t\tdata := testutil.MockGlobalData([]string{\"fastly\"}, &stdout)\n\t\tcmds := commands.Define(newApp(&stdout), data)\n\n\t\tfor _, want := range authCommandPrefixes {\n\t\t\tfound := false\n\t\t\tfor _, cmd := range cmds {\n\t\t\t\tif cmd.Name() == want || strings.HasPrefix(cmd.Name(), want+\" \") {\n\t\t\t\t\tfound = true\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif !found {\n\t\t\t\tt.Errorf(\"expected %q command to be present when FASTLY_DISABLE_AUTH_COMMAND is not set\", want)\n\t\t\t}\n\t\t}\n\t})\n\n\tt.Run(\"auth-related commands excluded when FASTLY_DISABLE_AUTH_COMMAND is set\", func(t *testing.T) {\n\t\tt.Setenv(\"FASTLY_DISABLE_AUTH_COMMAND\", \"1\")\n\n\t\tvar stdout bytes.Buffer\n\t\tdata := testutil.MockGlobalData([]string{\"fastly\"}, &stdout)\n\t\tcmds := commands.Define(newApp(&stdout), data)\n\n\t\tfor _, cmd := range cmds {\n\t\t\tif isAuthRelated(cmd.Name()) {\n\t\t\t\tt.Errorf(\"expected no auth-related commands, but found %q\", cmd.Name())\n\t\t\t}\n\t\t}\n\n\t\t// Non-auth commands still exist.\n\t\tfound := false\n\t\tfor _, cmd := range cmds {\n\t\t\tif cmd.Name() == \"compute\" {\n\t\t\t\tfound = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif !found {\n\t\t\tt.Error(\"expected compute command to still be present\")\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "pkg/commands/compute/build.go",
    "content": "package compute\n\nimport (\n\t\"bufio\"\n\t\"context\"\n\t\"crypto/rand\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"math\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"runtime\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/kennygrant/sanitize\"\n\t\"github.com/mholt/archives\"\n\t\"golang.org/x/text/cases\"\n\ttextlang \"golang.org/x/text/language\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/check\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/filesystem\"\n\t\"github.com/fastly/cli/pkg/github\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/manifest\"\n\t\"github.com/fastly/cli/pkg/revision\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// IgnoreFilePath is the filepath name of the Fastly ignore file.\nconst IgnoreFilePath = \".fastlyignore\"\n\n// CustomPostScriptMessage is the message displayed to a user when there is\n// either a post_init or post_build script defined.\nconst CustomPostScriptMessage = \"This project has a custom post_%s script defined in the %s manifest\"\n\n// ErrWasmtoolsNotFound represents an error finding the binary installed.\nvar ErrWasmtoolsNotFound = fsterr.RemediationError{\n\tInner:       fmt.Errorf(\"wasm-tools not found\"),\n\tRemediation: fsterr.BugRemediation,\n}\n\n// Flags represents the flags defined for the command.\ntype Flags struct {\n\tDir         string\n\tEnv         string\n\tIncludeSrc  bool\n\tLang        string\n\tPackageName string\n\tTimeout     int\n}\n\n// BuildCommand produces a deployable artifact from files on the local disk.\ntype BuildCommand struct {\n\targparser.Base\n\n\t// NOTE: Composite commands require these build flags to be public.\n\t// e.g. serve, publish, hash-files\n\t// This is so they can set values appropriately before calling Build.Exec().\n\tFlags                 Flags\n\tMetadataDisable       bool\n\tMetadataFilterEnvVars string\n\tMetadataShow          bool\n\tSkipChangeDir         bool // set by parent composite commands (e.g. serve, publish)\n}\n\n// NewBuildCommand returns a usable command registered under the parent.\nfunc NewBuildCommand(parent argparser.Registerer, g *global.Data) *BuildCommand {\n\tvar c BuildCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(\"build\", \"Build a Compute package locally\")\n\n\t// NOTE: when updating these flags, be sure to update the composite commands:\n\t// `compute publish` and `compute serve`.\n\tc.CmdClause.Flag(\"dir\", \"Project directory to build (default: current directory)\").Short('C').StringVar(&c.Flags.Dir)\n\tc.CmdClause.Flag(\"env\", \"The manifest environment config to use (e.g. 'stage' will attempt to read 'fastly.stage.toml')\").StringVar(&c.Flags.Env)\n\tc.CmdClause.Flag(\"include-source\", \"Include source code in built package\").BoolVar(&c.Flags.IncludeSrc)\n\tc.CmdClause.Flag(\"language\", \"Language type\").StringVar(&c.Flags.Lang)\n\tc.CmdClause.Flag(\"metadata-disable\", \"Disable Wasm binary metadata annotations\").BoolVar(&c.MetadataDisable)\n\tc.CmdClause.Flag(\"metadata-filter-envvars\", \"Redact specified environment variables from [scripts.env_vars] using comma-separated list\").StringVar(&c.MetadataFilterEnvVars)\n\tc.CmdClause.Flag(\"metadata-show\", \"Inspect the Wasm binary metadata\").BoolVar(&c.MetadataShow)\n\tc.CmdClause.Flag(\"package-name\", \"Package name\").StringVar(&c.Flags.PackageName)\n\tc.CmdClause.Flag(\"timeout\", \"Timeout, in seconds, for the build compilation step\").IntVar(&c.Flags.Timeout)\n\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *BuildCommand) Exec(in io.Reader, out io.Writer) (err error) {\n\t// We'll restore this at the end to print a final successful build output.\n\toriginalOut := out\n\tif c.Globals.Flags.Quiet {\n\t\tout = io.Discard\n\t}\n\n\tmanifestFilename := EnvironmentManifest(c.Flags.Env)\n\tif c.Flags.Env != \"\" {\n\t\tif c.Globals.Verbose() {\n\t\t\ttext.Info(out, EnvManifestMsg, manifestFilename, manifest.Filename)\n\t\t}\n\t}\n\twd, err := os.Getwd()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to get current working directory: %w\", err)\n\t}\n\tdefer func() {\n\t\t_ = os.Chdir(wd)\n\t}()\n\tmanifestPath := filepath.Join(wd, manifestFilename)\n\n\tvar projectDir string\n\tif !c.SkipChangeDir {\n\t\tprojectDir, err = ChangeProjectDirectory(c.Flags.Dir)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif projectDir != \"\" {\n\t\t\tif c.Globals.Verbose() {\n\t\t\t\ttext.Info(out, ProjectDirMsg, projectDir)\n\t\t\t}\n\t\t\tmanifestPath = filepath.Join(projectDir, manifestFilename)\n\t\t}\n\t}\n\n\tspinner, err := text.NewSpinner(out)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tdefer func(errLog fsterr.LogInterface) {\n\t\tif err != nil {\n\t\t\terrLog.Add(err)\n\t\t}\n\t}(c.Globals.ErrLog)\n\n\tif c.Globals.Verbose() {\n\t\ttext.Break(out)\n\t}\n\terr = spinner.Process(fmt.Sprintf(\"Verifying %s\", manifestFilename), func(_ *text.SpinnerWrapper) error {\n\t\t// The check for c.SkipChangeDir here is because we might need to attempt\n\t\t// another read of the manifest file. To explain: if we're skipping the\n\t\t// change of directory, it means we were called from a composite command,\n\t\t// which has already changed directory to one that contains the fastly.toml\n\t\t// file. This means we should try reading the manifest file from the new\n\t\t// location as the potential ReadError() would have been based on the\n\t\t// initial directory the CLI was invoked from.\n\t\tif c.SkipChangeDir || projectDir != \"\" || c.Flags.Env != \"\" {\n\t\t\terr = c.Globals.Manifest.File.Read(manifestPath)\n\t\t} else {\n\t\t\terr = c.Globals.Manifest.File.ReadError()\n\t\t}\n\t\tif err != nil {\n\t\t\tif errors.Is(err, os.ErrNotExist) {\n\t\t\t\terr = fsterr.ErrReadingManifest\n\t\t\t}\n\t\t\tc.Globals.ErrLog.Add(err)\n\t\t\treturn err\n\t\t}\n\t\treturn nil\n\t})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\twasmtools, wasmtoolsErr := GetWasmTools(spinner, out, c.Globals.Versioners.WasmTools, c.Globals)\n\n\tvar pkgName string\n\terr = spinner.Process(\"Identifying package name\", func(_ *text.SpinnerWrapper) error {\n\t\tpkgName, err = c.PackageName(manifestFilename)\n\t\treturn err\n\t})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tvar toolchain string\n\terr = spinner.Process(\"Identifying toolchain\", func(_ *text.SpinnerWrapper) error {\n\t\ttoolchain, err = identifyToolchain(c)\n\t\treturn err\n\t})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tlanguage, err := language(toolchain, manifestFilename, c, in, out, spinner)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\terr = binDir(c)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif err := language.Build(); err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Language\": language.Name,\n\t\t})\n\t\treturn err\n\t}\n\n\t// IMPORTANT: We ignore errors downloading wasm-tools.\n\t// This is because we don't want to block a user from building their project.\n\t// Annotating the compiled binary with metadata isn't that important.\n\tif wasmtoolsErr == nil {\n\t\tmetadataProcessedBy := fmt.Sprintf(\n\t\t\t\"--processed-by=fastly=%s (%s)\",\n\t\t\trevision.AppVersion, cases.Title(textlang.English).String(language.Name),\n\t\t)\n\t\tmetadataArgs := []string{\n\t\t\t\"metadata\", \"add\", binWasmPath, metadataProcessedBy,\n\t\t}\n\n\t\tmetadataDisable, _ := strconv.ParseBool(c.Globals.Env.WasmMetadataDisable)\n\t\tif !c.MetadataDisable && !metadataDisable {\n\t\t\tif err := c.AnnotateWasmBinaryLong(wasmtools, metadataArgs, language); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t} else {\n\t\t\tif err := c.AnnotateWasmBinaryShort(wasmtools, metadataArgs); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\tif c.MetadataShow {\n\t\t\tc.ShowMetadata(wasmtools, out)\n\t\t}\n\t} else {\n\t\tif !c.Globals.Verbose() {\n\t\t\ttext.Break(out)\n\t\t}\n\t\ttext.Info(out, \"There was an error downloading the wasm-tools (used for binary annotations) but we don't let that block you building your project. For reference here is the error (in case you want to let us know about it): %s\\n\\n\", wasmtoolsErr.Error())\n\t}\n\n\tdest := filepath.Join(\"pkg\", fmt.Sprintf(\"%s.tar.gz\", pkgName))\n\terr = spinner.Process(\"Creating package archive\", func(_ *text.SpinnerWrapper) error {\n\t\t// IMPORTANT: The minimum package requirement is `fastly.toml` and `main.wasm`.\n\t\t//\n\t\t// The Fastly platform will reject a package that doesn't have a manifest\n\t\t// named exactly fastly.toml which means if the user is building and\n\t\t// deploying a package with an environment manifest (e.g. fastly.stage.toml)\n\t\t// then we need to:\n\t\t//\n\t\t// 1. Rename any existing fastly.toml to fastly.toml.backup.<TIMESTAMP>\n\t\t// 2. Make a temp copy of the environment manifest and name it fastly.toml\n\t\t// 3. Remove the newly created fastly.toml once the packaging is done\n\t\t// 4. Rename the fastly.toml.backup back to fastly.toml\n\t\tif c.Flags.Env != \"\" {\n\t\t\t// 1. Rename any existing fastly.toml to fastly.toml.backup.<TIMESTAMP>\n\t\t\t//\n\t\t\t// For example, the user is trying to deploy a fastly.stage.toml rather\n\t\t\t// than the standard fastly.toml manifest.\n\t\t\tif _, err := os.Stat(manifest.Filename); err == nil {\n\t\t\t\tbackup := fmt.Sprintf(\"%s.backup.%d\", manifest.Filename, time.Now().Unix())\n\t\t\t\tif err := os.Rename(manifest.Filename, backup); err != nil {\n\t\t\t\t\treturn fmt.Errorf(\"failed to backup primary manifest file: %w\", err)\n\t\t\t\t}\n\t\t\t\tdefer func() {\n\t\t\t\t\t// 4. Rename the fastly.toml.backup back to fastly.toml\n\t\t\t\t\tif err = os.Rename(backup, manifest.Filename); err != nil {\n\t\t\t\t\t\ttext.Error(out, err.Error())\n\t\t\t\t\t}\n\t\t\t\t}()\n\t\t\t} else {\n\t\t\t\t// 3. Remove the newly created fastly.toml once the packaging is done\n\t\t\t\t//\n\t\t\t\t// If there wasn't an existing fastly.toml because the user only wants\n\t\t\t\t// to work with environment manifests (e.g. fastly.stage.toml and\n\t\t\t\t// fastly.production.toml) then we should remove the fastly.toml that we\n\t\t\t\t// created just for the packaging process (see step 2. below).\n\t\t\t\tdefer func() {\n\t\t\t\t\tif err = os.Remove(manifest.Filename); err != nil {\n\t\t\t\t\t\ttext.Error(out, err.Error())\n\t\t\t\t\t}\n\t\t\t\t}()\n\t\t\t}\n\t\t\t// 2. Make a temp copy of the environment manifest and name it fastly.toml\n\t\t\t//\n\t\t\t// If there was no existing fastly.toml then this step will create one, so\n\t\t\t// we need to make sure we remove it after packaging has finished so as to\n\t\t\t// not confuse the user with a fastly.toml that has suddenly appeared (see\n\t\t\t// step 3. above).\n\t\t\tif err := filesystem.CopyFile(manifestFilename, manifest.Filename); err != nil {\n\t\t\t\treturn fmt.Errorf(\"failed to copy environment manifest file: %w\", err)\n\t\t\t}\n\t\t}\n\n\t\tfiles := []string{\n\t\t\tmanifest.Filename,\n\t\t\tbinWasmPath,\n\t\t}\n\t\tfiles, err = c.includeSourceCode(files, language.SourceDirectory)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\terr = CreatePackageArchive(files, dest)\n\t\tif err != nil {\n\t\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\t\"Files\":       files,\n\t\t\t\t\"Destination\": dest,\n\t\t\t})\n\t\t\treturn fmt.Errorf(\"error creating package archive: %w\", err)\n\t\t}\n\t\treturn nil\n\t})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tout = originalOut\n\ttext.Success(out, \"\\nBuilt package (%s)\", dest)\n\treturn nil\n}\n\n// AnnotateWasmBinaryShort annotates the Wasm binary with only the CLI version.\nfunc (c *BuildCommand) AnnotateWasmBinaryShort(wasmtools string, args []string) error {\n\treturn c.Globals.ExecuteWasmTools(wasmtools, args, c.Globals)\n}\n\n// AnnotateWasmBinaryLong annotates the Wasm binary will all available data.\nfunc (c *BuildCommand) AnnotateWasmBinaryLong(wasmtools string, args []string, language *Language) error {\n\tvar ms runtime.MemStats\n\truntime.ReadMemStats(&ms)\n\n\t// Allow customer to specify their own env variables to be filtered.\n\tExtendStaticSecretEnvVars(c.MetadataFilterEnvVars)\n\n\tdc := DataCollection{}\n\n\tmetadata := c.Globals.Config.WasmMetadata\n\n\t// Only record basic data if user has disabled all other metadata collection.\n\tif metadata.BuildInfo == \"disable\" && metadata.MachineInfo == \"disable\" && metadata.PackageInfo == \"disable\" && metadata.ScriptInfo == \"disable\" {\n\t\treturn c.AnnotateWasmBinaryShort(wasmtools, args)\n\t}\n\n\tif metadata.BuildInfo == \"enable\" {\n\t\tdc.BuildInfo = DataCollectionBuildInfo{\n\t\t\tMemoryHeapAlloc: bucketMB(bytesToMB(ms.HeapAlloc)) + \"MB\",\n\t\t}\n\t}\n\tif metadata.MachineInfo == \"enable\" {\n\t\tdc.MachineInfo = DataCollectionMachineInfo{\n\t\t\tArch:      runtime.GOARCH,\n\t\t\tCPUs:      runtime.NumCPU(),\n\t\t\tCompiler:  runtime.Compiler,\n\t\t\tGoVersion: runtime.Version(),\n\t\t\tOS:        runtime.GOOS,\n\t\t}\n\t}\n\tif metadata.PackageInfo == \"enable\" {\n\t\tdc.PackageInfo = DataCollectionPackageInfo{\n\t\t\tClonedFrom: c.Globals.Manifest.File.ClonedFrom,\n\t\t\tPackages:   language.Dependencies(),\n\t\t}\n\t}\n\tif metadata.ScriptInfo == \"enable\" {\n\t\tdc.ScriptInfo = DataCollectionScriptInfo{\n\t\t\tDefaultBuildUsed: language.DefaultBuildScript(),\n\t\t\tBuildScript:      FilterSecretsFromString(c.Globals.Manifest.File.Scripts.Build),\n\t\t\tEnvVars:          FilterSecretsFromSlice(c.Globals.Manifest.File.Scripts.EnvVars),\n\t\t\tPostInitScript:   FilterSecretsFromString(c.Globals.Manifest.File.Scripts.PostInit),\n\t\t\tPostBuildScript:  FilterSecretsFromString(c.Globals.Manifest.File.Scripts.PostBuild),\n\t\t}\n\t}\n\n\tdata, err := json.Marshal(dc)\n\tif err != nil {\n\t\ttext.Info(c.Globals.Output, \"failed to marshal DataCollection struct into JSON: %s\", err)\n\t}\n\targs = append(args, fmt.Sprintf(\"--processed-by=fastly_data=%s\", data))\n\treturn c.Globals.ExecuteWasmTools(wasmtools, args, c.Globals)\n}\n\n// ShowMetadata displays the metadata attached to the Wasm binary.\nfunc (c *BuildCommand) ShowMetadata(wasmtools string, out io.Writer) {\n\t// gosec flagged this:\n\t// G204 (CWE-78): Subprocess launched with variable\n\t// Disabling as the variables come from trusted sources.\n\t// #nosec\n\t// nosemgrep\n\tcommand := exec.Command(wasmtools, \"metadata\", \"show\", binWasmPath)\n\twasmtoolsOutput, err := command.Output()\n\tif err != nil {\n\t\ttext.Error(out, \"failed to execute wasm-tools metadata command: %s\\n\\n\", err)\n\t\treturn\n\t}\n\ttext.Info(out, \"\\nBelow is the metadata attached to the Wasm binary\\n\\n\")\n\tfmt.Fprintln(out, string(wasmtoolsOutput))\n\ttext.Break(out)\n}\n\n// includeSourceCode calculates what source code files to include in the final\n// package.tar.gz that is uploaded to the Fastly API.\n//\n// TODO: Investigate possible change to --include-source flag.\n// The following implementation presumes source code is stored in a constant\n// location, which might not be true for all users. We should look at whether\n// we should change the --include-source flag to not be a boolean but to\n// accept a 'source code' path instead.\nfunc (c *BuildCommand) includeSourceCode(files []string, srcDir string) ([]string, error) {\n\tempty := make([]string, 0)\n\n\tif c.Flags.IncludeSrc {\n\t\tignoreFiles, err := GetIgnoredFiles(IgnoreFilePath)\n\t\tif err != nil {\n\t\t\tc.Globals.ErrLog.Add(err)\n\t\t\treturn empty, err\n\t\t}\n\n\t\tbinFiles, err := GetNonIgnoredFiles(\"bin\", ignoreFiles)\n\t\tif err != nil {\n\t\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\t\"Ignore files\": ignoreFiles,\n\t\t\t})\n\t\t\treturn empty, err\n\t\t}\n\t\tfiles = append(files, binFiles...)\n\n\t\tsrcFiles, err := GetNonIgnoredFiles(srcDir, ignoreFiles)\n\t\tif err != nil {\n\t\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\t\"Source directory\": srcDir,\n\t\t\t\t\"Ignore files\":     ignoreFiles,\n\t\t\t})\n\t\t\treturn empty, err\n\t\t}\n\t\tfiles = append(files, srcFiles...)\n\t}\n\n\treturn files, nil\n}\n\n// PackageName acquires the package name from either a flag or manifest.\n// Additionally it will sanitize the name.\nfunc (c *BuildCommand) PackageName(manifestFilename string) (string, error) {\n\tvar name string\n\n\tswitch {\n\tcase c.Flags.PackageName != \"\":\n\t\tname = c.Flags.PackageName\n\tcase c.Globals.Manifest.File.Name != \"\":\n\t\tname = c.Globals.Manifest.File.Name // use the project name as a fallback\n\tdefault:\n\t\treturn \"\", fsterr.RemediationError{\n\t\t\tInner:       fmt.Errorf(\"package name is missing\"),\n\t\t\tRemediation: fmt.Sprintf(\"Add a name to the %s 'name' field. Reference: https://www.fastly.com/documentation/reference/compute/fastly-toml\", manifestFilename),\n\t\t}\n\t}\n\n\treturn sanitize.BaseName(name), nil\n}\n\n// ExecuteWasmTools calls the wasm-tools binary.\nfunc ExecuteWasmTools(wasmtools string, args []string, d *global.Data) error {\n\terrMsg := \"failed to annotate binary with metadata: %s\\n\\n\"\n\t// gosec flagged this:\n\t// G204 (CWE-78): Subprocess launched with function call as argument or command arguments\n\t// Disabling as we trust the source of the variable.\n\t// #nosec\n\t// nosemgrep: go.lang.security.audit.dangerous-exec-command.dangerous-exec-command\n\tcommand := exec.Command(wasmtools, args...)\n\twasmtoolsOutput, err := command.Output()\n\tif err != nil && d.Verbose() {\n\t\ttext.Info(d.Output, errMsg, err)\n\t}\n\tif len(wasmtoolsOutput) == 0 {\n\t\treturn nil\n\t}\n\n\t// Make a backup of the original Wasm binary (before being annotated).\n\toriginalBin, err := os.ReadFile(binWasmPath)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Overwrite the original Wasm binary with the annotated version.\n\t//\n\t// G302 (CWE-276): Expect file permissions to be 0600 or less\n\t// gosec flagged this:\n\t// Disabling as we want all users to be able to execute this binary.\n\t// #nosec\n\terr = os.WriteFile(binWasmPath, wasmtoolsOutput, 0o777)\n\tif err != nil {\n\t\tif d.Verbose() {\n\t\t\ttext.Info(d.Output, errMsg, err)\n\t\t}\n\n\t\t// Restore the original Wasm binary.\n\t\t//\n\t\t// G302 (CWE-276): Expect file permissions to be 0600 or less\n\t\t// gosec flagged this:\n\t\t// Disabling as we want all users to be able to execute this binary.\n\t\t// #nosec\n\t\terr = os.WriteFile(binWasmPath, originalBin, 0o777)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to restore %s: %w\", binWasmPath, err)\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// GetWasmTools returns the path to the wasm-tools binary.\n// If there is no version installed, install the latest version.\n// If there is a version installed, update to the latest version if not already.\n//\n// But only update the version if the CLI installed it.\n// Otherwise updating a binary in the $PATH could cause problems if it's managed\n// by a third-party software management tool like Homebrew, MacPorts etc.\nfunc GetWasmTools(spinner text.Spinner, out io.Writer, wasmtoolsVersioner github.AssetVersioner, g *global.Data) (binPath string, err error) {\n\tbinPath, err = exec.LookPath(\"wasm-tools\")\n\tif err == nil {\n\t\tif g.Verbose() {\n\t\t\ttext.Info(out, \"\\nUsing wasm-tools binary found in user $PATH\\n\\n\")\n\t\t}\n\t\treturn binPath, nil\n\t}\n\tif g.Verbose() {\n\t\ttext.Info(out, \"\\nFailed to lookup wasm-tools binary in user $PATH. We'll attempt to locate it inside a Fastly CLI managed directory.\")\n\t}\n\n\tbinPath = wasmtoolsVersioner.InstallPath()\n\n\t// NOTE: When checking if wasm-tools is installed we don't use $PATH.\n\t//\n\t// $PATH is unreliable across OS platforms, but also we actually install\n\t// wasm-tools in the same location as the CLI's app config, which means it\n\t// wouldn't be found in the $PATH any way. We could pass the path for the app\n\t// config into exec.LookPath() but it's simpler to attempt executing the binary.\n\t//\n\t// gosec flagged this:\n\t// G204 (CWE-78): Subprocess launched with variable\n\t// Disabling as the variables come from trusted sources.\n\t// #nosec\n\t// nosemgrep\n\tc := exec.Command(binPath, \"--version\")\n\n\tvar installedVersion string\n\n\tstdoutStderr, err := c.CombinedOutput()\n\tif err != nil {\n\t\tg.ErrLog.Add(err)\n\t} else {\n\t\t// Check the version output has the expected format: `wasm-tools 1.0.40`\n\t\tinstalledVersion = strings.TrimSpace(string(stdoutStderr))\n\t\tsegs := strings.Split(installedVersion, \" \")\n\t\tif len(segs) < 2 {\n\t\t\treturn binPath, ErrWasmtoolsNotFound\n\t\t}\n\t\tinstalledVersion = segs[1]\n\t}\n\n\tif installedVersion == \"\" {\n\t\tif g.Verbose() {\n\t\t\ttext.Info(out, \"\\nwasm-tools is not already installed, so we will install the latest version.\\n\\n\")\n\t\t}\n\t\terr = installLatestWasmtools(binPath, spinner, wasmtoolsVersioner)\n\t\tif err != nil {\n\t\t\tg.ErrLog.Add(err)\n\t\t\treturn binPath, err\n\t\t}\n\n\t\tlatestVersion, err := wasmtoolsVersioner.LatestVersion()\n\t\tif err != nil {\n\t\t\treturn binPath, fmt.Errorf(\"failed to retrieve wasm-tools latest version: %w\", err)\n\t\t}\n\n\t\tg.Config.WasmTools.LatestVersion = latestVersion\n\t\tg.Config.WasmTools.LastChecked = time.Now().Format(time.RFC3339)\n\n\t\terr = g.Config.Write(g.ConfigPath)\n\t\tif err != nil {\n\t\t\treturn binPath, err\n\t\t}\n\t}\n\n\tif installedVersion != \"\" {\n\t\terr = updateWasmtools(binPath, spinner, out, g, wasmtoolsVersioner, installedVersion)\n\t\tif err != nil {\n\t\t\tg.ErrLog.Add(err)\n\t\t\treturn binPath, err\n\t\t}\n\t}\n\n\terr = github.SetBinPerms(binPath)\n\tif err != nil {\n\t\tg.ErrLog.Add(err)\n\t\treturn binPath, err\n\t}\n\n\treturn binPath, nil\n}\n\nfunc installLatestWasmtools(binPath string, spinner text.Spinner, wasmtoolsVersioner github.AssetVersioner) error {\n\treturn spinner.Process(\"Fetching latest wasm-tools release\", func(_ *text.SpinnerWrapper) error {\n\t\ttmpBin, err := wasmtoolsVersioner.DownloadLatest()\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to download latest wasm-tools release: %w\", err)\n\t\t}\n\t\tdefer os.RemoveAll(tmpBin)\n\t\tif err := os.Rename(tmpBin, binPath); err != nil {\n\t\t\tif err := filesystem.CopyFile(tmpBin, binPath); err != nil {\n\t\t\t\treturn fmt.Errorf(\"failed to move wasm-tools binary to accessible location: %w\", err)\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t})\n}\n\nfunc updateWasmtools(\n\tbinPath string,\n\tspinner text.Spinner,\n\tout io.Writer,\n\tg *global.Data,\n\twasmtoolsVersioner github.AssetVersioner,\n\tinstalledVersion string,\n) error {\n\tcfg := g.Config\n\tcfgPath := g.ConfigPath\n\n\t// NOTE: We shouldn't see LastChecked with no value if wasm-tools installed.\n\tif cfg.WasmTools.LastChecked == \"\" {\n\t\tcfg.WasmTools.LastChecked = time.Now().Format(time.RFC3339)\n\t\tif err := cfg.Write(cfgPath); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tif !check.Stale(cfg.WasmTools.LastChecked, cfg.WasmTools.TTL) {\n\t\tif g.Verbose() {\n\t\t\ttext.Info(out, \"\\nwasm-tools is installed but the CLI config (`fastly config`) shows the TTL, checking for a newer version, hasn't expired.\\n\\n\")\n\t\t}\n\t\treturn nil\n\t}\n\n\tvar latestVersion string\n\terr := spinner.Process(\"Checking latest wasm-tools release\", func(_ *text.SpinnerWrapper) error {\n\t\tvar err error\n\t\tlatestVersion, err = wasmtoolsVersioner.LatestVersion()\n\t\tif err != nil {\n\t\t\treturn fsterr.RemediationError{\n\t\t\t\tInner:       fmt.Errorf(\"error fetching latest version: %w\", err),\n\t\t\t\tRemediation: fsterr.NetworkRemediation,\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tcfg.WasmTools.LatestVersion = latestVersion\n\tcfg.WasmTools.LastChecked = time.Now().Format(time.RFC3339)\n\n\terr = cfg.Write(cfgPath)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif g.Verbose() {\n\t\ttext.Info(out, \"\\nThe CLI config (`fastly config`) has been updated with the latest wasm-tools version: %s\\n\\n\", latestVersion)\n\t}\n\tif installedVersion == latestVersion {\n\t\treturn nil\n\t}\n\n\treturn installLatestWasmtools(binPath, spinner, wasmtoolsVersioner)\n}\n\n// identifyToolchain determines the programming language.\n//\n// It prioritizes the --language flag over the manifest field.\n// Will error if neither are provided.\n// Lastly, it will normalise with a trim and lowercase.\nfunc identifyToolchain(c *BuildCommand) (string, error) {\n\tvar toolchain string\n\n\tswitch {\n\tcase c.Flags.Lang != \"\":\n\t\ttoolchain = c.Flags.Lang\n\tcase c.Globals.Manifest.File.Language != \"\":\n\t\ttoolchain = c.Globals.Manifest.File.Language\n\tdefault:\n\t\treturn \"\", fmt.Errorf(\"language cannot be empty, please provide a language\")\n\t}\n\n\treturn strings.ToLower(strings.TrimSpace(toolchain)), nil\n}\n\n// language returns a pointer to a supported language.\n//\n// TODO: Fix the mess that is New<language>()'s argument list.\nfunc language(toolchain, manifestFilename string, c *BuildCommand, in io.Reader, out io.Writer, spinner text.Spinner) (*Language, error) {\n\tvar language *Language\n\tswitch toolchain {\n\tcase \"cpp\":\n\t\tlanguage = NewLanguage(&LanguageOptions{\n\t\t\tName:            \"cpp\",\n\t\t\tSourceDirectory: CPPSourceDirectory,\n\t\t\tToolchain:       NewCPP(c, in, manifestFilename, out, spinner),\n\t\t})\n\tcase \"go\":\n\t\tlanguage = NewLanguage(&LanguageOptions{\n\t\t\tName:            \"go\",\n\t\t\tSourceDirectory: GoSourceDirectory,\n\t\t\tToolchain:       NewGo(c, in, manifestFilename, out, spinner),\n\t\t})\n\tcase \"javascript\":\n\t\tlanguage = NewLanguage(&LanguageOptions{\n\t\t\tName:            \"javascript\",\n\t\t\tSourceDirectory: JsSourceDirectory,\n\t\t\tToolchain:       NewJavaScript(c, in, manifestFilename, out, spinner),\n\t\t})\n\tcase \"rust\":\n\t\tlanguage = NewLanguage(&LanguageOptions{\n\t\t\tName:            \"rust\",\n\t\t\tSourceDirectory: RustSourceDirectory,\n\t\t\tToolchain:       NewRust(c, in, manifestFilename, out, spinner),\n\t\t})\n\tcase \"other\":\n\t\tlanguage = NewLanguage(&LanguageOptions{\n\t\t\tName:      \"other\",\n\t\t\tToolchain: NewOther(c, in, manifestFilename, out, spinner),\n\t\t})\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported language %s\", toolchain)\n\t}\n\n\treturn language, nil\n}\n\n// binDir ensures a ./bin directory exists.\n// The directory is required so a main.wasm can be placed inside it.\nfunc binDir(c *BuildCommand) error {\n\tif c.Globals.Verbose() {\n\t\ttext.Info(c.Globals.Output, \"\\nCreating ./bin directory (for Wasm binary)\\n\\n\")\n\t}\n\tdir, err := os.Getwd()\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn fmt.Errorf(\"failed to identify the current working directory: %w\", err)\n\t}\n\tbinDir := filepath.Join(dir, \"bin\")\n\tif err := filesystem.MakeDirectoryIfNotExists(binDir); err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn fmt.Errorf(\"failed to create bin directory: %w\", err)\n\t}\n\treturn nil\n}\n\n// CreatePackageArchive packages build artifacts as a Fastly package.\n// The package must be a GZipped Tar archive.\n//\n// Due to a behavior of archiver.Archive() which recursively writes all files in\n// a provided directory to the archive we first copy our input files to a\n// temporary directory to ensure only the specified files are included and not\n// any in the directory which may be ignored.\nfunc CreatePackageArchive(files []string, destination string) error {\n\t// Create temporary directory to copy files into.\n\tp := make([]byte, 8)\n\tn, err := rand.Read(p)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error creating temporary directory: %w\", err)\n\t}\n\n\ttmpDir := filepath.Join(\n\t\tos.TempDir(),\n\t\tfmt.Sprintf(\"fastly-build-%x\", p[:n]),\n\t)\n\n\tif err := os.MkdirAll(tmpDir, 0o700); err != nil {\n\t\treturn fmt.Errorf(\"error creating temporary directory: %w\", err)\n\t}\n\tdefer os.RemoveAll(tmpDir)\n\n\t// Create implicit top-level directory within temp which will become the\n\t// root of the archive. This replaces the `tar.ImplicitTopLevelFolder`\n\t// behavior.\n\tdir := filepath.Join(tmpDir, FileNameWithoutExtension(destination))\n\tif err := os.Mkdir(dir, 0o700); err != nil {\n\t\treturn fmt.Errorf(\"error creating temporary directory: %w\", err)\n\t}\n\n\tfor _, src := range files {\n\t\tdst := filepath.Join(dir, src)\n\t\tif err = filesystem.CopyFile(src, dst); err != nil {\n\t\t\treturn fmt.Errorf(\"error copying file: %w\", err)\n\t\t}\n\t}\n\n\treturn createTarGz(dir, destination)\n}\n\n// createTarGz creates a .tar.gz archive from a directory.\nfunc createTarGz(sourceDir, destFile string) error {\n\tctx := context.Background()\n\n\t// Map files from disk\n\tfiles, err := archives.FilesFromDisk(ctx, nil, map[string]string{\n\t\tsourceDir: \"\",\n\t})\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to map files from disk: %w\", err)\n\t}\n\n\t// Ensure parent directory exists\n\tdestDir := filepath.Dir(destFile)\n\tif err := os.MkdirAll(destDir, 0o755); err != nil {\n\t\treturn fmt.Errorf(\"failed to create destination directory: %w\", err)\n\t}\n\n\t// Create output file\n\tout, err := os.Create(destFile)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to create output file: %w\", err)\n\t}\n\tdefer out.Close()\n\n\t// Create compressed archive format\n\tformat := archives.CompressedArchive{\n\t\tCompression: archives.Gz{},\n\t\tArchival:    archives.Tar{},\n\t}\n\n\t// Create the archive\n\terr = format.Archive(ctx, out, files)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to create archive: %w\", err)\n\t}\n\n\treturn nil\n}\n\n// FileNameWithoutExtension returns a filename with its extension stripped.\nfunc FileNameWithoutExtension(filename string) string {\n\tbase := filepath.Base(filename)\n\tfirstDot := strings.Index(base, \".\")\n\tif firstDot > -1 {\n\t\treturn base[:firstDot]\n\t}\n\treturn base\n}\n\n// GetIgnoredFiles reads the .fastlyignore file line-by-line and expands the\n// glob pattern into a map containing all files it matches. If no ignore file\n// is present it returns an empty map.\nfunc GetIgnoredFiles(filePath string) (files map[string]bool, err error) {\n\tfiles = make(map[string]bool)\n\n\tif !filesystem.FileExists(filePath) {\n\t\treturn files, nil\n\t}\n\n\t// gosec flagged this:\n\t// G304 (CWE-22): Potential file inclusion via variable\n\t// Disabling as we trust the source of the filepath variable as it comes\n\t// from the IgnoreFilePath constant.\n\t/* #nosec */\n\tfile, err := os.Open(filePath)\n\tif err != nil {\n\t\treturn files, err\n\t}\n\tdefer func() {\n\t\tcerr := file.Close()\n\t\tif err == nil {\n\t\t\terr = cerr\n\t\t}\n\t}()\n\n\tscanner := bufio.NewScanner(file)\n\tfor scanner.Scan() {\n\t\tglob := strings.TrimSpace(scanner.Text())\n\t\tglobFiles, err := filepath.Glob(glob)\n\t\tif err != nil {\n\t\t\treturn files, fmt.Errorf(\"parsing glob %s: %w\", glob, err)\n\t\t}\n\t\tfor _, f := range globFiles {\n\t\t\tfiles[f] = true\n\t\t}\n\t}\n\n\tif err := scanner.Err(); err != nil {\n\t\treturn files, fmt.Errorf(\"reading %s file: %w\", filePath, err)\n\t}\n\n\treturn files, nil\n}\n\n// GetNonIgnoredFiles walks a filepath and returns all files that don't exist in\n// the provided ignore files map.\nfunc GetNonIgnoredFiles(base string, ignoredFiles map[string]bool) ([]string, error) {\n\tvar files []string\n\terr := filepath.Walk(base, func(path string, info os.FileInfo, err error) error {\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif info.IsDir() {\n\t\t\treturn nil\n\t\t}\n\t\tif ignoredFiles[path] {\n\t\t\treturn nil\n\t\t}\n\t\tfiles = append(files, path)\n\t\treturn nil\n\t})\n\n\treturn files, err\n}\n\n// bytesToMB converts the runtime.MemStats.HeapAlloc bytes into megabytes.\nfunc bytesToMB(bytes uint64) uint64 {\n\treturn uint64(math.Round(float64(bytes) / (1024 * 1024)))\n}\n\n// bucketMB determines a consistent bucket size for heap allocation.\n// NOTE: This is to avoid building a package with a fluctuating hash.\n// e.g. `fastly compute hash-files` should be consistent unless memory increase is significant.\nfunc bucketMB(mb uint64) string {\n\tswitch {\n\tcase mb < 2:\n\t\treturn \"<2\"\n\tcase mb >= 2 && mb < 5:\n\t\treturn \"2-5\"\n\tcase mb >= 5 && mb < 10:\n\t\treturn \"5-10\"\n\tcase mb >= 10 && mb < 20:\n\t\treturn \"10-20\"\n\tcase mb >= 20 && mb < 30:\n\t\treturn \"20-30\"\n\tcase mb >= 30 && mb < 40:\n\t\treturn \"30-40\"\n\tcase mb >= 40 && mb < 50:\n\t\treturn \"40-50\"\n\tdefault:\n\t\treturn \">50\"\n\t}\n}\n\n// DataCollection represents data annotated onto the Wasm binary.\ntype DataCollection struct {\n\tBuildInfo   DataCollectionBuildInfo   `json:\"build_info,omitempty\"`\n\tMachineInfo DataCollectionMachineInfo `json:\"machine_info,omitempty\"`\n\tPackageInfo DataCollectionPackageInfo `json:\"package_info,omitempty\"`\n\tScriptInfo  DataCollectionScriptInfo  `json:\"script_info,omitempty\"`\n}\n\n// DataCollectionBuildInfo represents build data annotated onto the Wasm binary.\ntype DataCollectionBuildInfo struct {\n\tMemoryHeapAlloc string `json:\"mem_heap_alloc,omitempty\"`\n}\n\n// DataCollectionMachineInfo represents machine data annotated onto the Wasm binary.\ntype DataCollectionMachineInfo struct {\n\tArch      string `json:\"arch,omitempty\"`\n\tCPUs      int    `json:\"cpus,omitempty\"`\n\tCompiler  string `json:\"compiler,omitempty\"`\n\tGoVersion string `json:\"go_version,omitempty\"`\n\tOS        string `json:\"os,omitempty\"`\n}\n\n// DataCollectionPackageInfo represents package data annotated onto the Wasm binary.\ntype DataCollectionPackageInfo struct {\n\t// ClonedFrom indicates if the Starter Kit used was cloned from a specific\n\t// repository (e.g. using the `compute init` --from flag).\n\tClonedFrom string `json:\"cloned_from,omitempty\"`\n\t// Packages is a map where the key is the name of the package and the value is\n\t// the package version.\n\tPackages map[string]string `json:\"packages,omitempty\"`\n}\n\n// DataCollectionScriptInfo represents script data annotated onto the Wasm binary.\ntype DataCollectionScriptInfo struct {\n\tDefaultBuildUsed bool     `json:\"default_build_used,omitempty\"`\n\tBuildScript      string   `json:\"build_script,omitempty\"`\n\tEnvVars          []string `json:\"env_vars,omitempty\"`\n\tPostInitScript   string   `json:\"post_init_script,omitempty\"`\n\tPostBuildScript  string   `json:\"post_build_script,omitempty\"`\n}\n"
  },
  {
    "path": "pkg/commands/compute/build_test.go",
    "content": "package compute_test\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/fastly/cli/pkg/app\"\n\t\"github.com/fastly/cli/pkg/commands/compute\"\n\t\"github.com/fastly/cli/pkg/config\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/manifest\"\n\t\"github.com/fastly/cli/pkg/mock\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n\t\"github.com/fastly/cli/pkg/threadsafe\"\n)\n\nfunc TestBuildUnknownLanguage(t *testing.T) {\n\tif os.Getenv(\"TEST_COMPUTE_BUILD\") == \"\" {\n\t\tt.Log(\"skipping test\")\n\t\tt.Skip(\"Set TEST_COMPUTE_BUILD to run this test\")\n\t}\n\n\targs := testutil.SplitArgs\n\n\tscenarios := []struct {\n\t\tname           string\n\t\targs           []string\n\t\tfastlyManifest string\n\t\twantError      string\n\t}{\n\t\t{\n\t\t\tname: \"empty language\",\n\t\t\targs: args(\"compute build\"),\n\t\t\tfastlyManifest: `\n\t\t\t\tmanifest_version = 2\n\t\t\t\tname = \"test\"`,\n\t\t\twantError: \"language cannot be empty, please provide a language\",\n\t\t},\n\t\t{\n\t\t\tname: \"unknown language\",\n\t\t\targs: args(\"compute build\"),\n\t\t\tfastlyManifest: `\n\t\t\t\tmanifest_version = 2\n\t\t\t\tname = \"test\"\n\t\t\t\tlanguage = \"foobar\"`,\n\t\t\twantError: \"unsupported language foobar\",\n\t\t},\n\t}\n\n\tfor testcaseIdx := range scenarios {\n\t\ttestcase := &scenarios[testcaseIdx]\n\t\tt.Run(testcase.name, func(t *testing.T) {\n\t\t\tpwd, err := os.Getwd()\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\twasmtoolsBinName := \"wasm-tools\"\n\t\t\tlatestDownloaded := wasmtoolsBinName + \"-latest-downloaded\"\n\n\t\t\trootdir := testutil.NewEnv(testutil.EnvOpts{\n\t\t\t\tT: t,\n\t\t\t\tWrite: []testutil.FileIO{\n\t\t\t\t\t{Src: `#!/usr/bin/env bash\n            echo wasm-tools 1.0.4`, Dst: wasmtoolsBinName, Executable: true},\n\t\t\t\t\t{Src: `#!/usr/bin/env bash\n            echo wasm-tools 2.0.0`, Dst: latestDownloaded, Executable: true},\n\t\t\t\t\t{Src: testcase.fastlyManifest, Dst: manifest.Filename},\n\t\t\t\t},\n\t\t\t})\n\t\t\tdefer os.RemoveAll(rootdir)\n\t\t\twasmtoolsBinPath := filepath.Join(rootdir, wasmtoolsBinName)\n\n\t\t\tif err := os.Chdir(rootdir); err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tdefer func() {\n\t\t\t\t_ = os.Chdir(pwd)\n\t\t\t}()\n\n\t\t\tvar stdout threadsafe.Buffer\n\t\t\tapp.Init = func(_ []string, _ io.Reader) (*global.Data, error) {\n\t\t\t\topts := testutil.MockGlobalData(testcase.args, &stdout)\n\t\t\t\topts.Versioners = global.Versioners{\n\t\t\t\t\tWasmTools: mock.AssetVersioner{\n\t\t\t\t\t\tAssetVersion:    \"1.2.3\",\n\t\t\t\t\t\tBinaryFilename:  wasmtoolsBinName,\n\t\t\t\t\t\tDownloadOK:      true,\n\t\t\t\t\t\tDownloadedFile:  latestDownloaded,\n\t\t\t\t\t\tInstallFilePath: wasmtoolsBinPath,\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t\treturn opts, nil\n\t\t\t}\n\t\t\terr = app.Run(testcase.args, nil)\n\n\t\t\tt.Log(stdout.String())\n\n\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t})\n\t}\n}\n\nfunc TestBuildRust(t *testing.T) {\n\tif os.Getenv(\"TEST_COMPUTE_BUILD_RUST\") == \"\" && os.Getenv(\"TEST_COMPUTE_BUILD\") == \"\" {\n\t\tt.Log(\"skipping test\")\n\t\tt.Skip(\"Set TEST_COMPUTE_BUILD to run this test\")\n\t}\n\n\targs := testutil.SplitArgs\n\n\tscenarios := []struct {\n\t\tname                 string\n\t\targs                 []string\n\t\tapplicationConfig    *config.File // a pointer so we can assert if configured\n\t\tfastlyManifest       string\n\t\tcargoManifest        string\n\t\twantError            string\n\t\twantRemediationError string\n\t\twantOutput           []string\n\t}{\n\t\t{\n\t\t\tname:                 \"no fastly.toml manifest\",\n\t\t\targs:                 args(\"compute build\"),\n\t\t\twantError:            \"error reading fastly.toml: file not found\",\n\t\t\twantRemediationError: \"Run `fastly compute init` to ensure a correctly configured manifest.\",\n\t\t},\n\t\t// The following test validates that the project compiles successfully even\n\t\t// though the fastly.toml manifest has no build script. There should be a\n\t\t// default build script inserted and it should use the same name as the\n\t\t// project/package name in the Cargo.toml.\n\t\t//\n\t\t// NOTE: This test passes --verbose so we can validate specific outputs.\n\t\t{\n\t\t\tname: \"build script inserted dynamically when missing\",\n\t\t\targs: args(\"compute build --verbose\"),\n\t\t\tapplicationConfig: &config.File{\n\t\t\t\tLanguage: config.Language{\n\t\t\t\t\tRust: config.Rust{\n\t\t\t\t\t\tToolchainConstraint: \">= 1.78.0\",\n\t\t\t\t\t\tWasmWasiTarget:      \"wasm32-wasip1\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tcargoManifest: `\n\t\t\t[package]\n\t\t\tname = \"my-project\"\n\t\t\tversion = \"0.1.0\"\n\n\t\t\t[dependencies]\n\t\t\tfastly = \"=0.6.0\"`,\n\t\t\tfastlyManifest: `\n\t\t\tmanifest_version = 2\n\t\t\tname = \"test\"\n\t\t    language = \"rust\"`,\n\t\t\twantOutput: []string{\n\t\t\t\t\"No [scripts.build] found in fastly.toml.\", // requires --verbose\n\t\t\t\t\"The following default build command for\",\n\t\t\t\t\"cargo build --bin my-project\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"build error\",\n\t\t\targs: args(\"compute build\"),\n\t\t\tapplicationConfig: &config.File{\n\t\t\t\tLanguage: config.Language{\n\t\t\t\t\tRust: config.Rust{\n\t\t\t\t\t\tToolchainConstraint: \">= 1.78.0\",\n\t\t\t\t\t\tWasmWasiTarget:      \"wasm32-wasip1\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tcargoManifest: `\n\t\t\t[package]\n\t\t\tname = \"fastly-compute-project\"\n\t\t\tversion = \"0.1.0\"\n\n\t\t\t[dependencies]\n\t\t\tfastly = \"=0.6.0\"`,\n\t\t\tfastlyManifest: `\n\t\t\tmanifest_version = 2\n\t\t\tname = \"test\"\n\t\t\tlanguage = \"rust\"\n\n\t\t    [scripts]\n\t\t    build = \"echo no compilation happening\"`,\n\t\t\twantRemediationError: compute.DefaultBuildErrorRemediation,\n\t\t},\n\t\t{\n\t\t\tname: \"wasmwasi target error\",\n\t\t\targs: args(\"compute build --verbose\"),\n\t\t\tapplicationConfig: &config.File{\n\t\t\t\tLanguage: config.Language{\n\t\t\t\t\tRust: config.Rust{\n\t\t\t\t\t\tToolchainConstraint: \">= 1.78.0\",\n\t\t\t\t\t\tWasmWasiTarget:      \"wasm32-wasi\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tcargoManifest: `\n\t\t\t[package]\n\t\t\tname = \"fastly-compute-project\"\n\t\t\tversion = \"0.1.0\"\n\n\t\t\t[dependencies]\n\t\t\tfastly = \"=0.6.0\"`,\n\t\t\tfastlyManifest: fmt.Sprintf(`\n\t\t\tmanifest_version = 2\n\t\t\tname = \"test\"\n\t\t\tlanguage = \"rust\"\n\n\t\t    [scripts]\n\t\t    build = \"%s\"`, fmt.Sprintf(compute.RustDefaultBuildCommand, compute.RustDefaultPackageName, compute.RustDefaultWasmWasiTarget)),\n\t\t\twantError: \"the default build in .fastly/config.toml should produce a wasm32-wasip1 binary, but was instead set to produce a wasm32-wasi binary\",\n\t\t},\n\t\t// NOTE: This test passes --verbose so we can validate specific outputs.\n\t\t{\n\t\t\tname: \"successful build\",\n\t\t\targs: args(\"compute build --verbose\"),\n\t\t\tapplicationConfig: &config.File{\n\t\t\t\tLanguage: config.Language{\n\t\t\t\t\tRust: config.Rust{\n\t\t\t\t\t\tToolchainConstraint: \">= 1.78.0\",\n\t\t\t\t\t\tWasmWasiTarget:      \"wasm32-wasip1\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tcargoManifest: `\n\t\t\t[package]\n\t\t\tname = \"fastly-compute-project\"\n\t\t\tversion = \"0.1.0\"\n\n\t\t\t[dependencies]\n\t\t\tfastly = \"=0.6.0\"`,\n\t\t\tfastlyManifest: fmt.Sprintf(`\n\t\t\tmanifest_version = 2\n\t\t\tname = \"test\"\n\t\t\tlanguage = \"rust\"\n\n\t\t    [scripts]\n\t\t    build = \"%s\"`, fmt.Sprintf(compute.RustDefaultBuildCommand, compute.RustDefaultPackageName, compute.RustDefaultWasmWasiTarget)),\n\t\t\twantOutput: []string{\n\t\t\t\t\"Creating ./bin directory (for Wasm binary)\",\n\t\t\t\t\"Built package\",\n\t\t\t},\n\t\t},\n\t}\n\tfor testcaseIdx := range scenarios {\n\t\ttestcase := &scenarios[testcaseIdx]\n\t\tt.Run(testcase.name, func(t *testing.T) {\n\t\t\t// We're going to chdir to a build environment,\n\t\t\t// so save the PWD to return to, afterwards.\n\t\t\tpwd, err := os.Getwd()\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\twasmtoolsBinName := \"wasm-tools\"\n\n\t\t\t// Windows was having issues when trying to move a tmpBin file (which\n\t\t\t// represents the latest binary downloaded from GitHub) to binPath (which\n\t\t\t// represents the existing binary installed on a user's machine).\n\t\t\t//\n\t\t\t// The problem was, for the sake of the tests, I just create one file\n\t\t\t// `wasmtoolsBinName` and used that for both `tmpBin` and `binPath` and\n\t\t\t// this works fine on *nix systems. But once Windows did `os.Rename()` and\n\t\t\t// move tmpBin to binPath it would no longer be able to set permissions on\n\t\t\t// the binPath because it didn't think the file existed any more. My guess\n\t\t\t// is that moving a file over itself causes Windows to remove the file.\n\t\t\t//\n\t\t\t// So to work around that issue I just create two separate files because\n\t\t\t// in reality that's what the CLI will be dealing with. I only used one\n\t\t\t// file for the sake of test case convenience (which ironically became\n\t\t\t// very INCONVENIENT when the tests started unexpectedly failing on\n\t\t\t// Windows and caused me a long time debugging).\n\t\t\tlatestDownloaded := wasmtoolsBinName + \"-latest-downloaded\"\n\n\t\t\t// Create test environment\n\t\t\trootdir := testutil.NewEnv(testutil.EnvOpts{\n\t\t\t\tT: t,\n\t\t\t\tCopy: []testutil.FileIO{\n\t\t\t\t\t{Src: filepath.Join(\"testdata\", \"build\", \"rust\", \"Cargo.lock\"), Dst: \"Cargo.lock\"},\n\t\t\t\t\t{Src: filepath.Join(\"testdata\", \"build\", \"rust\", \"Cargo.toml\"), Dst: \"Cargo.toml\"},\n\t\t\t\t\t{Src: filepath.Join(\"testdata\", \"build\", \"rust\", \"src\", \"main.rs\"), Dst: filepath.Join(\"src\", \"main.rs\")},\n\t\t\t\t\t{Src: filepath.Join(\"testdata\", \"deploy\", \"pkg\", \"package.tar.gz\"), Dst: filepath.Join(\"pkg\", \"package.tar.gz\")},\n\t\t\t\t},\n\t\t\t\tWrite: []testutil.FileIO{\n\t\t\t\t\t{Src: `#!/usr/bin/env bash\n            echo wasm-tools 1.0.4`, Dst: wasmtoolsBinName, Executable: true},\n\t\t\t\t\t{Src: `#!/usr/bin/env bash\n            echo wasm-tools 2.0.0`, Dst: latestDownloaded, Executable: true},\n\t\t\t\t\t{Src: testcase.fastlyManifest, Dst: manifest.Filename},\n\t\t\t\t\t{Src: testcase.cargoManifest, Dst: \"Cargo.toml\"},\n\t\t\t\t},\n\t\t\t})\n\t\t\tdefer os.RemoveAll(rootdir)\n\t\t\twasmtoolsBinPath := filepath.Join(rootdir, wasmtoolsBinName)\n\n\t\t\t// Before running the test, chdir into the build environment.\n\t\t\t// When we're done, chdir back to our original location.\n\t\t\t// This is so we can reliably copy the testdata/ fixtures.\n\t\t\tif err := os.Chdir(rootdir); err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tdefer func() {\n\t\t\t\t_ = os.Chdir(pwd)\n\t\t\t}()\n\n\t\t\tvar stdout threadsafe.Buffer\n\t\t\tapp.Init = func(_ []string, _ io.Reader) (*global.Data, error) {\n\t\t\t\topts := testutil.MockGlobalData(testcase.args, &stdout)\n\t\t\t\tif testcase.applicationConfig != nil {\n\t\t\t\t\topts.Config = *testcase.applicationConfig\n\t\t\t\t}\n\t\t\t\topts.Versioners = global.Versioners{\n\t\t\t\t\tWasmTools: mock.AssetVersioner{\n\t\t\t\t\t\tAssetVersion:    \"1.2.3\",\n\t\t\t\t\t\tBinaryFilename:  wasmtoolsBinName,\n\t\t\t\t\t\tDownloadOK:      true,\n\t\t\t\t\t\tDownloadedFile:  latestDownloaded,\n\t\t\t\t\t\tInstallFilePath: wasmtoolsBinPath, // avoid overwriting developer's actual wasm-tools install\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t\treturn opts, nil\n\t\t\t}\n\t\t\terr = app.Run(testcase.args, nil)\n\n\t\t\tt.Log(stdout.String())\n\n\t\t\ttestutil.AssertRemediationErrorContains(t, err, testcase.wantRemediationError)\n\n\t\t\t// NOTE: Some errors we want to assert only the remediation.\n\t\t\t// e.g. a 'stat' error isn't the same across operating systems/platforms.\n\t\t\tif testcase.wantError != \"\" {\n\t\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\t}\n\t\t\tfor _, s := range testcase.wantOutput {\n\t\t\t\ttestutil.AssertStringContains(t, stdout.String(), s)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestBuildGo(t *testing.T) {\n\tif os.Getenv(\"TEST_COMPUTE_BUILD_GO\") == \"\" && os.Getenv(\"TEST_COMPUTE_BUILD\") == \"\" {\n\t\tt.Log(\"skipping test\")\n\t\tt.Skip(\"Set TEST_COMPUTE_BUILD to run this test\")\n\t}\n\n\targs := testutil.SplitArgs\n\n\tscenarios := []struct {\n\t\tname                 string\n\t\targs                 []string\n\t\tapplicationConfig    *config.File\n\t\tfastlyManifest       string\n\t\twantError            string\n\t\twantRemediationError string\n\t\twantOutput           []string\n\t}{\n\t\t{\n\t\t\tname:                 \"no fastly.toml manifest\",\n\t\t\targs:                 args(\"compute build\"),\n\t\t\twantError:            \"error reading fastly.toml: file not found\",\n\t\t\twantRemediationError: \"Run `fastly compute init` to ensure a correctly configured manifest.\",\n\t\t},\n\t\t// The following test validates that the project compiles successfully even\n\t\t// though the fastly.toml manifest has no build script. There should be a\n\t\t// default build script inserted.\n\t\t//\n\t\t// NOTE: This test passes --verbose so we can validate specific outputs.\n\t\t{\n\t\t\tname: \"build success\",\n\t\t\targs: args(\"compute build --verbose\"),\n\t\t\tapplicationConfig: &config.File{\n\t\t\t\tLanguage: config.Language{\n\t\t\t\t\tGo: config.Go{\n\t\t\t\t\t\tTinyGoConstraint:          \">= 0.26.0-0\",\n\t\t\t\t\t\tToolchainConstraintTinyGo: \">= 1.18\",\n\t\t\t\t\t\tToolchainConstraint:       \">= 1.21\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tfastlyManifest: `\n\t\t\tmanifest_version = 2\n\t\t\tname = \"test\"\n      language = \"go\"\n      [scripts]\n      build = \"go build -o bin/main.wasm ./\"\n      env_vars = [\"GOARCH=wasm\", \"GOOS=wasip1\"]\n      `,\n\t\t\twantOutput: []string{\n\t\t\t\t\"The Fastly CLI build step requires a go version '>= 1.21'\",\n\t\t\t\t\"Build script to execute\",\n\t\t\t\t\"Build environment variables set\",\n\t\t\t\t\"GOARCH=wasm GOOS=wasip1\",\n\t\t\t\t\"Creating ./bin directory (for Wasm binary)\",\n\t\t\t\t\"Built package\",\n\t\t\t},\n\t\t},\n\t\t// The following test case is expected to fail because we specify a custom\n\t\t// build script that doesn't actually produce a ./bin/main.wasm\n\t\t{\n\t\t\tname: \"build error\",\n\t\t\targs: args(\"compute build\"),\n\t\t\tapplicationConfig: &config.File{\n\t\t\t\tLanguage: config.Language{\n\t\t\t\t\tGo: config.Go{\n\t\t\t\t\t\tTinyGoConstraint:          \">= 0.26.0-0\",\n\t\t\t\t\t\tToolchainConstraintTinyGo: \">= 1.18\",\n\t\t\t\t\t\tToolchainConstraint:       \">= 1.21\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tfastlyManifest: `\n\t\t\tmanifest_version = 2\n\t\t\tname = \"test\"\n\t\t\tlanguage = \"go\"\n\n      [scripts]\n      build = \"echo no compilation happening\"`,\n\t\t\twantRemediationError: compute.DefaultBuildErrorRemediation,\n\t\t},\n\t}\n\tfor testcaseIdx := range scenarios {\n\t\ttestcase := &scenarios[testcaseIdx]\n\t\tt.Run(testcase.name, func(t *testing.T) {\n\t\t\t// We're going to chdir to a build environment,\n\t\t\t// so save the PWD to return to, afterwards.\n\t\t\tpwd, err := os.Getwd()\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\twasmtoolsBinName := \"wasm-tools\"\n\n\t\t\t// Windows was having issues when trying to move a tmpBin file (which\n\t\t\t// represents the latest binary downloaded from GitHub) to binPath (which\n\t\t\t// represents the existing binary installed on a user's machine).\n\t\t\t//\n\t\t\t// The problem was, for the sake of the tests, I just create one file\n\t\t\t// `wasmtoolsBinName` and used that for both `tmpBin` and `binPath` and\n\t\t\t// this works fine on *nix systems. But once Windows did `os.Rename()` and\n\t\t\t// move tmpBin to binPath it would no longer be able to set permissions on\n\t\t\t// the binPath because it didn't think the file existed any more. My guess\n\t\t\t// is that moving a file over itself causes Windows to remove the file.\n\t\t\t//\n\t\t\t// So to work around that issue I just create two separate files because\n\t\t\t// in reality that's what the CLI will be dealing with. I only used one\n\t\t\t// file for the sake of test case convenience (which ironically became\n\t\t\t// very INCONVENIENT when the tests started unexpectedly failing on\n\t\t\t// Windows and caused me a long time debugging).\n\t\t\tlatestDownloaded := wasmtoolsBinName + \"-latest-downloaded\"\n\n\t\t\t// Create test environment\n\t\t\trootdir := testutil.NewEnv(testutil.EnvOpts{\n\t\t\t\tT: t,\n\t\t\t\tCopy: []testutil.FileIO{\n\t\t\t\t\t{Src: filepath.Join(\"testdata\", \"build\", \"go\", \"go.mod\"), Dst: \"go.mod\"},\n\t\t\t\t\t{Src: filepath.Join(\"testdata\", \"build\", \"go\", \"main.go\"), Dst: \"main.go\"},\n\t\t\t\t},\n\t\t\t\tWrite: []testutil.FileIO{\n\t\t\t\t\t{Src: `#!/usr/bin/env bash\n            echo wasm-tools 1.0.4`, Dst: wasmtoolsBinName, Executable: true},\n\t\t\t\t\t{Src: `#!/usr/bin/env bash\n            echo wasm-tools 2.0.0`, Dst: latestDownloaded, Executable: true},\n\t\t\t\t\t{Src: testcase.fastlyManifest, Dst: manifest.Filename},\n\t\t\t\t},\n\t\t\t})\n\t\t\tdefer os.RemoveAll(rootdir)\n\t\t\twasmtoolsBinPath := filepath.Join(rootdir, wasmtoolsBinName)\n\n\t\t\t// Before running the test, chdir into the build environment.\n\t\t\t// When we're done, chdir back to our original location.\n\t\t\t// This is so we can reliably copy the testdata/ fixtures.\n\t\t\tif err := os.Chdir(rootdir); err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tdefer func() {\n\t\t\t\t_ = os.Chdir(pwd)\n\t\t\t}()\n\n\t\t\tvar stdout threadsafe.Buffer\n\t\t\tapp.Init = func(_ []string, _ io.Reader) (*global.Data, error) {\n\t\t\t\topts := testutil.MockGlobalData(testcase.args, &stdout)\n\t\t\t\tif testcase.applicationConfig != nil {\n\t\t\t\t\topts.Config = *testcase.applicationConfig\n\t\t\t\t}\n\t\t\t\topts.Versioners = global.Versioners{\n\t\t\t\t\tWasmTools: mock.AssetVersioner{\n\t\t\t\t\t\tAssetVersion:    \"1.2.3\",\n\t\t\t\t\t\tBinaryFilename:  wasmtoolsBinName,\n\t\t\t\t\t\tDownloadOK:      true,\n\t\t\t\t\t\tDownloadedFile:  latestDownloaded,\n\t\t\t\t\t\tInstallFilePath: wasmtoolsBinPath, // avoid overwriting developer's actual wasm-tools install\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t\treturn opts, nil\n\t\t\t}\n\t\t\terr = app.Run(testcase.args, nil)\n\n\t\t\tt.Log(stdout.String())\n\n\t\t\ttestutil.AssertRemediationErrorContains(t, err, testcase.wantRemediationError)\n\n\t\t\t// NOTE: Some errors we want to assert only the remediation.\n\t\t\t// e.g. a 'stat' error isn't the same across operating systems/platforms.\n\t\t\tif testcase.wantError != \"\" {\n\t\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\t}\n\t\t\tfor _, s := range testcase.wantOutput {\n\t\t\t\ttestutil.AssertStringContains(t, stdout.String(), s)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestBuildJavaScript(t *testing.T) {\n\tif os.Getenv(\"TEST_COMPUTE_BUILD_JAVASCRIPT\") == \"\" && os.Getenv(\"TEST_COMPUTE_BUILD\") == \"\" {\n\t\tt.Log(\"skipping test\")\n\t\tt.Skip(\"Set TEST_COMPUTE_BUILD to run this test\")\n\t}\n\n\targs := testutil.SplitArgs\n\n\tscenarios := []struct {\n\t\tname                 string\n\t\targs                 []string\n\t\tfastlyManifest       string\n\t\twantError            string\n\t\twantRemediationError string\n\t\twantOutput           []string\n\t\tnpmInstall           bool\n\t\tversioners           *global.Versioners\n\t}{\n\t\t{\n\t\t\tname:                 \"no fastly.toml manifest\",\n\t\t\targs:                 args(\"compute build\"),\n\t\t\twantError:            \"error reading fastly.toml: file not found\",\n\t\t\twantRemediationError: \"Run `fastly compute init` to ensure a correctly configured manifest.\",\n\t\t},\n\t\t// The following test validates that the project compiles successfully even\n\t\t// though the fastly.toml manifest has no build script. There should be a\n\t\t// default build script inserted.\n\t\t//\n\t\t// NOTE: This test passes --verbose so we can validate specific outputs.\n\t\t// NOTE: npmInstall is required because toolchain verification checks for node_modules.\n\t\t{\n\t\t\tname: \"build script inserted dynamically when missing\",\n\t\t\targs: args(\"compute build --verbose\"),\n\t\t\tfastlyManifest: `\n\t\t\tmanifest_version = 2\n\t\t\tname = \"test\"\n      language = \"javascript\"`,\n\t\t\twantOutput: []string{\n\t\t\t\t\"No [scripts.build] found in fastly.toml.\", // requires --verbose\n\t\t\t\t\"The following default build command for\",\n\t\t\t},\n\t\t\tnpmInstall: true,\n\t\t},\n\t\t{\n\t\t\tname: \"build error\",\n\t\t\targs: args(\"compute build\"),\n\t\t\tfastlyManifest: `\n\t\t\tmanifest_version = 2\n\t\t\tname = \"test\"\n\t\t\tlanguage = \"javascript\"\n\n      [scripts]\n      build = \"echo no compilation happening\"`,\n\t\t\twantRemediationError: compute.DefaultBuildErrorRemediation,\n\t\t},\n\t\t// NOTE: This test passes --verbose so we can validate specific outputs.\n\t\t{\n\t\t\tname: \"successful build\",\n\t\t\targs: args(\"compute build --verbose\"),\n\t\t\tfastlyManifest: fmt.Sprintf(`\n\t\t\tmanifest_version = 2\n\t\t\tname = \"test\"\n\t\t\tlanguage = \"javascript\"\n\n      [scripts]\n      build = \"%s\"`, compute.JsDefaultBuildCommand),\n\t\t\twantOutput: []string{\n\t\t\t\t\"Creating ./bin directory (for Wasm binary)\",\n\t\t\t\t\"Built package\",\n\t\t\t},\n\t\t\tnpmInstall: true,\n\t\t},\n\t}\n\tfor testcaseIdx := range scenarios {\n\t\ttestcase := &scenarios[testcaseIdx]\n\t\tt.Run(testcase.name, func(t *testing.T) {\n\t\t\t// We're going to chdir to a build environment,\n\t\t\t// so save the PWD to return to, afterwards.\n\t\t\tpwd, err := os.Getwd()\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\twasmtoolsBinName := \"wasm-tools\"\n\n\t\t\t// Windows was having issues when trying to move a tmpBin file (which\n\t\t\t// represents the latest binary downloaded from GitHub) to binPath (which\n\t\t\t// represents the existing binary installed on a user's machine).\n\t\t\t//\n\t\t\t// The problem was, for the sake of the tests, I just create one file\n\t\t\t// `wasmtoolsBinName` and used that for both `tmpBin` and `binPath` and\n\t\t\t// this works fine on *nix systems. But once Windows did `os.Rename()` and\n\t\t\t// move tmpBin to binPath it would no longer be able to set permissions on\n\t\t\t// the binPath because it didn't think the file existed any more. My guess\n\t\t\t// is that moving a file over itself causes Windows to remove the file.\n\t\t\t//\n\t\t\t// So to work around that issue I just create two separate files because\n\t\t\t// in reality that's what the CLI will be dealing with. I only used one\n\t\t\t// file for the sake of test case convenience (which ironically became\n\t\t\t// very INCONVENIENT when the tests started unexpectedly failing on\n\t\t\t// Windows and caused me a long time debugging).\n\t\t\tlatestDownloaded := wasmtoolsBinName + \"-latest-downloaded\"\n\n\t\t\t// Create test environment\n\t\t\trootdir := testutil.NewEnv(testutil.EnvOpts{\n\t\t\t\tT: t,\n\t\t\t\tCopy: []testutil.FileIO{\n\t\t\t\t\t{Src: filepath.Join(\"testdata\", \"build\", \"javascript\", \"package.json\"), Dst: \"package.json\"},\n\t\t\t\t\t{Src: filepath.Join(\"testdata\", \"build\", \"javascript\", \"src\", \"index.js\"), Dst: filepath.Join(\"src\", \"index.js\")},\n\t\t\t\t},\n\t\t\t\tWrite: []testutil.FileIO{\n\t\t\t\t\t{Src: `#!/usr/bin/env bash\n            echo wasm-tools 1.0.4`, Dst: wasmtoolsBinName, Executable: true},\n\t\t\t\t\t{Src: `#!/usr/bin/env bash\n            echo wasm-tools 2.0.0`, Dst: latestDownloaded, Executable: true},\n\t\t\t\t\t{Src: testcase.fastlyManifest, Dst: manifest.Filename},\n\t\t\t\t},\n\t\t\t})\n\t\t\tdefer os.RemoveAll(rootdir)\n\t\t\twasmtoolsBinPath := filepath.Join(rootdir, wasmtoolsBinName)\n\n\t\t\t// Before running the test, chdir into the build environment.\n\t\t\t// When we're done, chdir back to our original location.\n\t\t\t// This is so we can reliably copy the testdata/ fixtures.\n\t\t\tif err := os.Chdir(rootdir); err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tdefer func() {\n\t\t\t\t_ = os.Chdir(pwd)\n\t\t\t}()\n\n\t\t\t// NOTE: We only want to run `npm install` for the success case.\n\t\t\tif testcase.npmInstall {\n\t\t\t\t// gosec flagged this:\n\t\t\t\t// G204 (CWE-78): Subprocess launched with variable\n\t\t\t\t// Disabling as we control this command.\n\t\t\t\t// #nosec\n\t\t\t\t// nosemgrep\n\t\t\t\tc := exec.Command(\"npm\", \"install\")\n\n\t\t\t\terr = c.Run()\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tvar stdout threadsafe.Buffer\n\t\t\tapp.Init = func(_ []string, _ io.Reader) (*global.Data, error) {\n\t\t\t\topts := testutil.MockGlobalData(testcase.args, &stdout)\n\t\t\t\topts.Versioners = global.Versioners{\n\t\t\t\t\tWasmTools: mock.AssetVersioner{\n\t\t\t\t\t\tAssetVersion:    \"1.2.3\",\n\t\t\t\t\t\tBinaryFilename:  wasmtoolsBinName,\n\t\t\t\t\t\tDownloadOK:      true,\n\t\t\t\t\t\tDownloadedFile:  latestDownloaded,\n\t\t\t\t\t\tInstallFilePath: wasmtoolsBinPath, // avoid overwriting developer's actual wasm-tools install\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t\treturn opts, nil\n\t\t\t}\n\t\t\terr = app.Run(testcase.args, nil)\n\n\t\t\tt.Log(stdout.String())\n\n\t\t\ttestutil.AssertRemediationErrorContains(t, err, testcase.wantRemediationError)\n\n\t\t\t// NOTE: Some errors we want to assert only the remediation.\n\t\t\t// e.g. a 'stat' error isn't the same across operating systems/platforms.\n\t\t\tif testcase.wantError != \"\" {\n\t\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\t}\n\t\t\tfor _, s := range testcase.wantOutput {\n\t\t\t\ttestutil.AssertStringContains(t, stdout.String(), s)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestBuildCPP(t *testing.T) {\n\tif os.Getenv(\"TEST_COMPUTE_BUILD_CPP\") == \"\" && os.Getenv(\"TEST_COMPUTE_BUILD\") == \"\" {\n\t\tt.Log(\"skipping test\")\n\t\tt.Skip(\"Set TEST_COMPUTE_BUILD to run this test\")\n\t}\n\n\targs := testutil.SplitArgs\n\n\tscenarios := []struct {\n\t\tname                 string\n\t\targs                 []string\n\t\tapplicationConfig    *config.File\n\t\tfastlyManifest       string\n\t\twantError            string\n\t\twantRemediationError string\n\t\twantOutput           []string\n\t}{\n\t\t{\n\t\t\tname:                 \"no fastly.toml manifest\",\n\t\t\targs:                 args(\"compute build\"),\n\t\t\twantError:            \"error reading fastly.toml: file not found\",\n\t\t\twantRemediationError: \"Run `fastly compute init` to ensure a correctly configured manifest.\",\n\t\t},\n\t\t// The following test validates that the project compiles successfully even\n\t\t// though the fastly.toml manifest has no build script. There should be a\n\t\t// default build script inserted.\n\t\t//\n\t\t// NOTE: This test passes --verbose so we can validate specific outputs.\n\t\t{\n\t\t\tname: \"build script inserted dynamically when missing\",\n\t\t\targs: args(\"compute build --verbose\"),\n\t\t\tapplicationConfig: &config.File{\n\t\t\t\tLanguage: config.Language{\n\t\t\t\t\tCPP: config.CPP{\n\t\t\t\t\t\tToolchainConstraint: \">= 14.0.0\",\n\t\t\t\t\t\tWasmWasiTarget:      \"wasm32-wasip1\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tfastlyManifest: `\n\t\t\tmanifest_version = 2\n\t\t\tname = \"test\"\n\t\t\tlanguage = \"cpp\"`,\n\t\t\twantOutput: []string{\n\t\t\t\t\"No [scripts.build] found in fastly.toml.\", // requires --verbose\n\t\t\t\t\"The following default build command for C++ will be used\",\n\t\t\t\t\"clang++ -O3 --target=wasm32-wasip1 -o ./bin/main.wasm main.cpp\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"wasmwasi target error\",\n\t\t\targs: args(\"compute build --verbose\"),\n\t\t\tapplicationConfig: &config.File{\n\t\t\t\tLanguage: config.Language{\n\t\t\t\t\tCPP: config.CPP{\n\t\t\t\t\t\tToolchainConstraint: \">= 14.0.0\",\n\t\t\t\t\t\tWasmWasiTarget:      \"wasm32-wasi\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tfastlyManifest: `\n\t\t\tmanifest_version = 2\n\t\t\tname = \"test\"\n\t\t\tlanguage = \"cpp\"`,\n\t\t\twantError: \"the default build in .fastly/config.toml should produce a wasm32-wasip1 binary, but was instead set to produce a wasm32-wasi binary\",\n\t\t},\n\t\t{\n\t\t\tname: \"build error\",\n\t\t\targs: args(\"compute build\"),\n\t\t\tapplicationConfig: &config.File{\n\t\t\t\tLanguage: config.Language{\n\t\t\t\t\tCPP: config.CPP{\n\t\t\t\t\t\tToolchainConstraint: \">= 14.0.0\",\n\t\t\t\t\t\tWasmWasiTarget:      \"wasm32-wasip1\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tfastlyManifest: `\n\t\t\tmanifest_version = 2\n\t\t\tname = \"test\"\n\t\t\tlanguage = \"cpp\"\n\n\t\t\t[scripts]\n\t\t\tbuild = \"echo no compilation happening\"`,\n\t\t\twantRemediationError: compute.DefaultBuildErrorRemediation,\n\t\t},\n\t\t// NOTE: This test passes --verbose so we can validate specific outputs.\n\t\t{\n\t\t\tname: \"successful build\",\n\t\t\targs: args(\"compute build --verbose\"),\n\t\t\tapplicationConfig: &config.File{\n\t\t\t\tLanguage: config.Language{\n\t\t\t\t\tCPP: config.CPP{\n\t\t\t\t\t\tToolchainConstraint: \">= 14.0.0\",\n\t\t\t\t\t\tWasmWasiTarget:      \"wasm32-wasip1\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tfastlyManifest: `\n\t\t\tmanifest_version = 2\n\t\t\tname = \"test\"\n\t\t\tlanguage = \"cpp\"\n\n\t\t\t[scripts]\n\t\t\tbuild = \"clang++ -O3 --target=wasm32-wasip1 -o bin/main.wasm main.cpp\"`,\n\t\t\twantOutput: []string{\n\t\t\t\t\"Creating ./bin directory (for Wasm binary)\",\n\t\t\t\t\"Built package\",\n\t\t\t},\n\t\t},\n\t}\n\tfor testcaseIdx := range scenarios {\n\t\ttestcase := &scenarios[testcaseIdx]\n\t\tt.Run(testcase.name, func(t *testing.T) {\n\t\t\t// We're going to chdir to a build environment,\n\t\t\t// so save the PWD to return to, afterwards.\n\t\t\tpwd, err := os.Getwd()\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\twasmtoolsBinName := \"wasm-tools\"\n\n\t\t\t// Windows was having issues when trying to move a tmpBin file (which\n\t\t\t// represents the latest binary downloaded from GitHub) to binPath (which\n\t\t\t// represents the existing binary installed on a user's machine).\n\t\t\t//\n\t\t\t// The problem was, for the sake of the tests, I just create one file\n\t\t\t// `wasmtoolsBinName` and used that for both `tmpBin` and `binPath` and\n\t\t\t// this works fine on *nix systems. But once Windows did `os.Rename()` and\n\t\t\t// move tmpBin to binPath it would no longer be able to set permissions on\n\t\t\t// the binPath because it didn't think the file existed any more. My guess\n\t\t\t// is that moving a file over itself causes Windows to remove the file.\n\t\t\t//\n\t\t\t// So to work around that issue I just create two separate files because\n\t\t\t// in reality that's what the CLI will be dealing with. I only used one\n\t\t\t// file for the sake of test case convenience (which ironically became\n\t\t\t// very INCONVENIENT when the tests started unexpectedly failing on\n\t\t\t// Windows and caused me a long time debugging).\n\t\t\tlatestDownloaded := wasmtoolsBinName + \"-latest-downloaded\"\n\n\t\t\t// Create test environment\n\t\t\trootdir := testutil.NewEnv(testutil.EnvOpts{\n\t\t\t\tT: t,\n\t\t\t\tCopy: []testutil.FileIO{\n\t\t\t\t\t{Src: filepath.Join(\"testdata\", \"build\", \"cpp\", \"main.cpp\"), Dst: \"main.cpp\"},\n\t\t\t\t},\n\t\t\t\tWrite: []testutil.FileIO{\n\t\t\t\t\t{Src: `#!/usr/bin/env bash\n            echo wasm-tools 1.0.4`, Dst: wasmtoolsBinName, Executable: true},\n\t\t\t\t\t{Src: `#!/usr/bin/env bash\n            echo wasm-tools 2.0.0`, Dst: latestDownloaded, Executable: true},\n\t\t\t\t\t{Src: testcase.fastlyManifest, Dst: manifest.Filename},\n\t\t\t\t},\n\t\t\t})\n\t\t\tdefer os.RemoveAll(rootdir)\n\t\t\twasmtoolsBinPath := filepath.Join(rootdir, wasmtoolsBinName)\n\n\t\t\t// Before running the test, chdir into the build environment.\n\t\t\t// When we're done, chdir back to our original location.\n\t\t\t// This is so we can reliably copy the testdata/ fixtures.\n\t\t\tif err := os.Chdir(rootdir); err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tdefer func() {\n\t\t\t\t_ = os.Chdir(pwd)\n\t\t\t}()\n\n\t\t\tvar stdout threadsafe.Buffer\n\t\t\tapp.Init = func(_ []string, _ io.Reader) (*global.Data, error) {\n\t\t\t\topts := testutil.MockGlobalData(testcase.args, &stdout)\n\t\t\t\tif testcase.applicationConfig != nil {\n\t\t\t\t\topts.Config = *testcase.applicationConfig\n\t\t\t\t}\n\t\t\t\topts.Versioners = global.Versioners{\n\t\t\t\t\tWasmTools: mock.AssetVersioner{\n\t\t\t\t\t\tAssetVersion:    \"1.2.3\",\n\t\t\t\t\t\tBinaryFilename:  wasmtoolsBinName,\n\t\t\t\t\t\tDownloadOK:      true,\n\t\t\t\t\t\tDownloadedFile:  latestDownloaded,\n\t\t\t\t\t\tInstallFilePath: wasmtoolsBinPath, // avoid overwriting developer's actual wasm-tools install\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t\treturn opts, nil\n\t\t\t}\n\t\t\terr = app.Run(testcase.args, nil)\n\n\t\t\tt.Log(stdout.String())\n\n\t\t\ttestutil.AssertRemediationErrorContains(t, err, testcase.wantRemediationError)\n\n\t\t\t// NOTE: Some errors we want to assert only the remediation.\n\t\t\t// e.g. a 'stat' error isn't the same across operating systems/platforms.\n\t\t\tif testcase.wantError != \"\" {\n\t\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\t}\n\t\t\tfor _, s := range testcase.wantOutput {\n\t\t\t\ttestutil.AssertStringContains(t, stdout.String(), s)\n\t\t\t}\n\t\t})\n\t}\n}\n\n// NOTE: TestBuildOther also validates the post_build settings.\nfunc TestBuildOther(t *testing.T) {\n\targs := testutil.SplitArgs\n\tif os.Getenv(\"TEST_COMPUTE_BUILD\") == \"\" {\n\t\tt.Log(\"skipping test\")\n\t\tt.Skip(\"Set TEST_COMPUTE_BUILD to run this test\")\n\t}\n\n\tfor _, testcase := range []struct {\n\t\targs                 []string\n\t\tdontWantOutput       []string\n\t\tfastlyManifest       string\n\t\tname                 string\n\t\tstdin                string\n\t\twantError            string\n\t\twantOutput           []string\n\t\twantRemediationError string\n\t}{\n\t\t{\n\t\t\tname: \"stop build process\",\n\t\t\targs: args(\"compute build --language other\"),\n\t\t\tfastlyManifest: `\n\t\t\tmanifest_version = 2\n\t\t\tname = \"test\"\n\t\t\t[scripts]\n\t\t\tbuild = \"cp ./bin/test.main.wasm ./bin/main.wasm\"\n      post_build = \"echo doing a post build\"`,\n\t\t\tstdin: \"N\",\n\t\t\twantOutput: []string{\n\t\t\t\t\"echo doing a post build\",\n\t\t\t\t\"Do you want to run this now?\",\n\t\t\t},\n\t\t\twantError: \"build process stopped by user\",\n\t\t},\n\t\t// NOTE: All following tests pass --verbose so we can see post_build output.\n\t\t{\n\t\t\tname: \"allow build process\",\n\t\t\targs: args(\"compute build --language other --verbose\"),\n\t\t\tfastlyManifest: `\n\t\t\tmanifest_version = 2\n\t\t\tname = \"test\"\n\t\t\t[scripts]\n\t\t\tbuild = \"cp ./bin/test.main.wasm ./bin/main.wasm\"\n      post_build = \"echo doing a post build\"`,\n\t\t\tstdin: \"Y\",\n\t\t\twantOutput: []string{\n\t\t\t\t\"echo doing a post build\",\n\t\t\t\t\"Do you want to run this now?\",\n\t\t\t\t\"Built package\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"language pulled from manifest\",\n\t\t\targs: args(\"compute build --verbose\"),\n\t\t\tfastlyManifest: `\n\t\t\tmanifest_version = 2\n\t\t\tname = \"test\"\n\t\t\tlanguage = \"other\"\n\t\t\t[scripts]\n\t\t\tbuild = \"cp ./bin/test.main.wasm ./bin/main.wasm\"\n      post_build = \"echo doing a post build\"`,\n\t\t\tstdin: \"Y\",\n\t\t\twantOutput: []string{\n\t\t\t\t\"echo doing a post build\",\n\t\t\t\t\"Do you want to run this now?\",\n\t\t\t\t\"Built package\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"avoid prompt confirmation\",\n\t\t\targs: args(\"compute build --auto-yes --language other --verbose\"),\n\t\t\tfastlyManifest: `\n\t\t\tmanifest_version = 2\n\t\t\tname = \"test\"\n\t\t\t[scripts]\n\t\t\tbuild = \"cp ./bin/test.main.wasm ./bin/main.wasm\"\n      post_build = \"echo doing a post build with no confirmation prompt && exit 1\"`, // force an error so post_build is displayed to validate it was run.\n\t\t\twantOutput: []string{\n\t\t\t\t\"doing a post build with no confirmation prompt\",\n\t\t\t},\n\t\t\tdontWantOutput: []string{\n\t\t\t\t\"Do you want to run this now?\",\n\t\t\t},\n\t\t\twantError: \"exit status 1\", // because we have to trigger an error to see the post_build output\n\t\t},\n\t} {\n\t\tt.Run(testcase.name, func(t *testing.T) {\n\t\t\t// We're going to chdir to a build environment,\n\t\t\t// so save the PWD to return to, afterwards.\n\t\t\tpwd, err := os.Getwd()\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\twasmtoolsBinName := \"wasm-tools\"\n\n\t\t\t// Windows was having issues when trying to move a tmpBin file (which\n\t\t\t// represents the latest binary downloaded from GitHub) to binPath (which\n\t\t\t// represents the existing binary installed on a user's machine).\n\t\t\t//\n\t\t\t// The problem was, for the sake of the tests, I just create one file\n\t\t\t// `wasmtoolsBinName` and used that for both `tmpBin` and `binPath` and\n\t\t\t// this works fine on *nix systems. But once Windows did `os.Rename()` and\n\t\t\t// move tmpBin to binPath it would no longer be able to set permissions on\n\t\t\t// the binPath because it didn't think the file existed any more. My guess\n\t\t\t// is that moving a file over itself causes Windows to remove the file.\n\t\t\t//\n\t\t\t// So to work around that issue I just create two separate files because\n\t\t\t// in reality that's what the CLI will be dealing with. I only used one\n\t\t\t// file for the sake of test case convenience (which ironically became\n\t\t\t// very INCONVENIENT when the tests started unexpectedly failing on\n\t\t\t// Windows and caused me a long time debugging).\n\t\t\tlatestDownloaded := wasmtoolsBinName + \"-latest-downloaded\"\n\n\t\t\t// Create test environment\n\t\t\t//\n\t\t\t// NOTE: Our only requirement is that there be a bin directory. The custom\n\t\t\t// build script we're using in the test is not going to use any files in the\n\t\t\t// directory (the script will just copy a test binary into the expected\n\t\t\t// location of the final main.wasm binary).\n\t\t\t//\n\t\t\t// NOTE: We create a \"valid\" main.wasm file with a quick shell script.\n\t\t\t//\n\t\t\t// Previously we set the build script to \"touch ./bin/main.wasm\" but since\n\t\t\t// adding Wasm validation this no longer works as it's an empty file.\n\t\t\t//\n\t\t\t// So we use the following script to produce a file that LOOKS valid but isn't.\n\t\t\t//\n\t\t\t// magic=\"\\x00\\x61\\x73\\x6d\\x01\\x00\\x00\\x00\"\n\t\t\t// printf \"$magic\" > ./pkg/commands/compute/testdata/main.wasm\n\t\t\trootdir := testutil.NewEnv(testutil.EnvOpts{\n\t\t\t\tT: t,\n\t\t\t\tCopy: []testutil.FileIO{\n\t\t\t\t\t{Src: \"./testdata/main.wasm\", Dst: \"bin/test.main.wasm\"},\n\t\t\t\t},\n\t\t\t\tWrite: []testutil.FileIO{\n\t\t\t\t\t{Src: `#!/usr/bin/env bash\n            echo wasm-tools 1.0.4`, Dst: wasmtoolsBinName, Executable: true},\n\t\t\t\t\t{Src: `#!/usr/bin/env bash\n            echo wasm-tools 2.0.0`, Dst: latestDownloaded, Executable: true},\n\t\t\t\t\t{Src: \"mock content\", Dst: \"bin/testfile\"},\n\t\t\t\t},\n\t\t\t})\n\t\t\tdefer os.RemoveAll(rootdir)\n\t\t\twasmtoolsBinPath := filepath.Join(rootdir, wasmtoolsBinName)\n\n\t\t\t// Before running the test, chdir into the build environment.\n\t\t\t// When we're done, chdir back to our original location.\n\t\t\t// This is so we can reliably copy the testdata/ fixtures.\n\t\t\tif err := os.Chdir(rootdir); err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tdefer func() {\n\t\t\t\t_ = os.Chdir(pwd)\n\t\t\t}()\n\n\t\t\tif testcase.fastlyManifest != \"\" {\n\t\t\t\tif err := os.WriteFile(filepath.Join(rootdir, manifest.Filename), []byte(testcase.fastlyManifest), 0o600); err != nil {\n\t\t\t\t\tt.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tvar stdout threadsafe.Buffer\n\t\t\tapp.Init = func(_ []string, _ io.Reader) (*global.Data, error) {\n\t\t\t\topts := testutil.MockGlobalData(testcase.args, &stdout)\n\t\t\t\topts.Input = strings.NewReader(testcase.stdin) // NOTE: build only has one prompt when dealing with a custom build\n\t\t\t\topts.Versioners = global.Versioners{\n\t\t\t\t\tWasmTools: mock.AssetVersioner{\n\t\t\t\t\t\tAssetVersion:    \"1.2.3\",\n\t\t\t\t\t\tBinaryFilename:  wasmtoolsBinName,\n\t\t\t\t\t\tDownloadOK:      true,\n\t\t\t\t\t\tDownloadedFile:  latestDownloaded,\n\t\t\t\t\t\tInstallFilePath: wasmtoolsBinPath, // avoid overwriting developer's actual wasm-tools install\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t\treturn opts, nil\n\t\t\t}\n\t\t\terr = app.Run(testcase.args, nil)\n\n\t\t\tt.Log(stdout.String())\n\n\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\ttestutil.AssertRemediationErrorContains(t, err, testcase.wantRemediationError)\n\t\t\tfor _, s := range testcase.wantOutput {\n\t\t\t\ttestutil.AssertStringContains(t, stdout.String(), s)\n\t\t\t}\n\t\t\tfor _, s := range testcase.dontWantOutput {\n\t\t\t\ttestutil.AssertStringDoesntContain(t, stdout.String(), s)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/commands/compute/compute_mocks_test.go",
    "content": "package compute_test\n\n// NOTE: This file doesn't contain any tests. It only contains code that is\n// shared across some of the other test files (mostly mocked API responses, but\n// also a mocked HTTP client).\n\nimport (\n\t\"context\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/testutil\"\n)\n\nfunc getServiceOK(_ context.Context, _ *fastly.GetServiceInput) (*fastly.Service, error) {\n\treturn &fastly.Service{\n\t\tServiceID: fastly.ToPointer(\"12345\"),\n\t\tName:      fastly.ToPointer(\"test\"),\n\t}, nil\n}\n\nfunc createDomainOK(_ context.Context, i *fastly.CreateDomainInput) (*fastly.Domain, error) {\n\treturn &fastly.Domain{\n\t\tServiceID:      fastly.ToPointer(i.ServiceID),\n\t\tServiceVersion: fastly.ToPointer(i.ServiceVersion),\n\t\tName:           i.Name,\n\t}, nil\n}\n\nfunc createBackendOK(_ context.Context, i *fastly.CreateBackendInput) (*fastly.Backend, error) {\n\treturn &fastly.Backend{\n\t\tServiceID:      fastly.ToPointer(i.ServiceID),\n\t\tServiceVersion: fastly.ToPointer(i.ServiceVersion),\n\t\tName:           i.Name,\n\t}, nil\n}\n\nfunc createConfigStoreOK(_ context.Context, i *fastly.CreateConfigStoreInput) (*fastly.ConfigStore, error) {\n\treturn &fastly.ConfigStore{\n\t\tName: i.Name,\n\t}, nil\n}\n\nfunc updateConfigStoreItemOK(_ context.Context, i *fastly.UpdateConfigStoreItemInput) (*fastly.ConfigStoreItem, error) {\n\treturn &fastly.ConfigStoreItem{\n\t\tKey:   i.Key,\n\t\tValue: i.Value,\n\t}, nil\n}\n\nfunc createDictionaryOK(_ context.Context, i *fastly.CreateDictionaryInput) (*fastly.Dictionary, error) {\n\treturn &fastly.Dictionary{\n\t\tServiceID:      fastly.ToPointer(i.ServiceID),\n\t\tServiceVersion: fastly.ToPointer(i.ServiceVersion),\n\t\tName:           i.Name,\n\t}, nil\n}\n\nfunc createDictionaryItemOK(_ context.Context, i *fastly.CreateDictionaryItemInput) (*fastly.DictionaryItem, error) {\n\treturn &fastly.DictionaryItem{\n\t\tServiceID:    fastly.ToPointer(i.ServiceID),\n\t\tDictionaryID: fastly.ToPointer(i.DictionaryID),\n\t\tItemKey:      i.ItemKey,\n\t\tItemValue:    i.ItemValue,\n\t}, nil\n}\n\nfunc createKVStoreOK(_ context.Context, i *fastly.CreateKVStoreInput) (*fastly.KVStore, error) {\n\treturn &fastly.KVStore{\n\t\tStoreID: \"example-store\",\n\t\tName:    i.Name,\n\t}, nil\n}\n\nfunc createKVStoreItemOK(_ context.Context, _ *fastly.InsertKVStoreKeyInput) error {\n\treturn nil\n}\n\nfunc createResourceOK(_ context.Context, _ *fastly.CreateResourceInput) (*fastly.Resource, error) {\n\treturn nil, nil\n}\n\nfunc getPackageOk(_ context.Context, i *fastly.GetPackageInput) (*fastly.Package, error) {\n\treturn &fastly.Package{ServiceID: fastly.ToPointer(i.ServiceID), ServiceVersion: fastly.ToPointer(i.ServiceVersion)}, nil\n}\n\nfunc updatePackageOk(_ context.Context, i *fastly.UpdatePackageInput) (*fastly.Package, error) {\n\treturn &fastly.Package{ServiceID: fastly.ToPointer(i.ServiceID), ServiceVersion: fastly.ToPointer(i.ServiceVersion)}, nil\n}\n\nfunc updatePackageError(_ context.Context, _ *fastly.UpdatePackageInput) (*fastly.Package, error) {\n\treturn nil, testutil.Err\n}\n\nfunc activateVersionOk(_ context.Context, i *fastly.ActivateVersionInput) (*fastly.Version, error) {\n\treturn &fastly.Version{ServiceID: fastly.ToPointer(i.ServiceID), Number: fastly.ToPointer(i.ServiceVersion)}, nil\n}\n\nfunc updateVersionOk(_ context.Context, i *fastly.UpdateVersionInput) (*fastly.Version, error) {\n\treturn &fastly.Version{ServiceID: fastly.ToPointer(i.ServiceID), Number: fastly.ToPointer(i.ServiceVersion), Comment: i.Comment}, nil\n}\n\nfunc listDomainsOk(_ context.Context, _ *fastly.ListDomainsInput) ([]*fastly.Domain, error) {\n\treturn []*fastly.Domain{\n\t\t{Name: fastly.ToPointer(\"https://directly-careful-coyote.edgecompute.app\")},\n\t}, nil\n}\n\nfunc listKVStoresOk(_ context.Context, _ *fastly.ListKVStoresInput) (*fastly.ListKVStoresResponse, error) {\n\treturn &fastly.ListKVStoresResponse{\n\t\tData: []fastly.KVStore{\n\t\t\t{\n\t\t\t\tStoreID: \"123\",\n\t\t\t\tName:    \"store_one\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tStoreID: \"456\",\n\t\t\t\tName:    \"store_two\",\n\t\t\t},\n\t\t},\n\t}, nil\n}\n\nfunc listKVStoresEmpty(_ context.Context, _ *fastly.ListKVStoresInput) (*fastly.ListKVStoresResponse, error) {\n\treturn &fastly.ListKVStoresResponse{}, nil\n}\n\nfunc getKVStoreOk(_ context.Context, _ *fastly.GetKVStoreInput) (*fastly.KVStore, error) {\n\treturn &fastly.KVStore{\n\t\tStoreID: \"123\",\n\t\tName:    \"store_one\",\n\t}, nil\n}\n\nfunc listSecretStoresOk(_ context.Context, _ *fastly.ListSecretStoresInput) (*fastly.SecretStores, error) {\n\treturn &fastly.SecretStores{\n\t\tData: []fastly.SecretStore{\n\t\t\t{\n\t\t\t\tStoreID: \"123\",\n\t\t\t\tName:    \"store_one\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tStoreID: \"456\",\n\t\t\t\tName:    \"store_two\",\n\t\t\t},\n\t\t},\n\t}, nil\n}\n\nfunc listSecretStoresEmpty(_ context.Context, _ *fastly.ListSecretStoresInput) (*fastly.SecretStores, error) {\n\treturn &fastly.SecretStores{}, nil\n}\n\nfunc getSecretStoreOk(_ context.Context, _ *fastly.GetSecretStoreInput) (*fastly.SecretStore, error) {\n\treturn &fastly.SecretStore{\n\t\tStoreID: \"123\",\n\t\tName:    \"store_one\",\n\t}, nil\n}\n\nfunc createSecretStoreOk(_ context.Context, _ *fastly.CreateSecretStoreInput) (*fastly.SecretStore, error) {\n\treturn &fastly.SecretStore{\n\t\tStoreID: \"123\",\n\t\tName:    \"store_one\",\n\t}, nil\n}\n\nfunc createSecretOk(_ context.Context, _ *fastly.CreateSecretInput) (*fastly.Secret, error) {\n\treturn &fastly.Secret{\n\t\tDigest: []byte(\"123\"),\n\t\tName:   \"foo\",\n\t}, nil\n}\n\nfunc listConfigStoresOk(_ context.Context, _ *fastly.ListConfigStoresInput) ([]*fastly.ConfigStore, error) {\n\treturn []*fastly.ConfigStore{\n\t\t{\n\t\t\tStoreID: \"123\",\n\t\t\tName:    \"example\",\n\t\t},\n\t\t{\n\t\t\tStoreID: \"456\",\n\t\t\tName:    \"example_two\",\n\t\t},\n\t}, nil\n}\n\nfunc listConfigStoresEmpty(_ context.Context, _ *fastly.ListConfigStoresInput) ([]*fastly.ConfigStore, error) {\n\treturn []*fastly.ConfigStore{}, nil\n}\n\nfunc getConfigStoreOk(_ context.Context, _ *fastly.GetConfigStoreInput) (*fastly.ConfigStore, error) {\n\treturn &fastly.ConfigStore{\n\t\tStoreID: \"123\",\n\t\tName:    \"example\",\n\t}, nil\n}\n\nfunc getServiceDetailsWasm(_ context.Context, i *fastly.GetServiceDetailsInput) (*fastly.ServiceDetail, error) {\n\tdetail := &fastly.ServiceDetail{\n\t\tType: fastly.ToPointer(\"wasm\"),\n\t\tVersion: &fastly.Version{\n\t\t\tNumber: fastly.ToPointer(1),\n\t\t},\n\t}\n\n\t// If filtering for active version, also set ActiveVersion field\n\tfor _, filter := range i.Filters {\n\t\tif filter.Key == \"versions.active\" && filter.Value {\n\t\t\tdetail.ActiveVersion = &fastly.Version{\n\t\t\t\tNumber: fastly.ToPointer(1),\n\t\t\t\tActive: fastly.ToPointer(true),\n\t\t\t}\n\t\t}\n\t}\n\n\treturn detail, nil\n}\n\nfunc getServiceDetailsWasmNoActive(_ context.Context, _ *fastly.GetServiceDetailsInput) (*fastly.ServiceDetail, error) {\n\t// Returns service details with no active version, forcing fallback to latest\n\treturn &fastly.ServiceDetail{\n\t\tType: fastly.ToPointer(\"wasm\"),\n\t\tVersion: &fastly.Version{\n\t\t\tNumber: fastly.ToPointer(1),\n\t\t},\n\t}, nil\n}\n"
  },
  {
    "path": "pkg/commands/compute/compute_test.go",
    "content": "package compute_test\n\nimport (\n\t\"context\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"reflect\"\n\t\"testing\"\n\n\t\"github.com/mholt/archives\"\n\n\t\"github.com/fastly/kingpin\"\n\n\t\"github.com/fastly/cli/pkg/commands/compute\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/manifest\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n)\n\n// TestFlagDivergencePublish validates that the manually curated list of flags\n// within the `compute publish` command doesn't fall out of sync with the\n// `compute build` and `compute deploy` commands from which publish is composed.\nfunc TestFlagDivergencePublish(t *testing.T) {\n\tvar g global.Data\n\tg.Manifest = &manifest.Data{}\n\tacmd := kingpin.New(\"foo\", \"bar\")\n\n\trcmd := compute.NewRootCommand(acmd, &g)\n\tbcmd := compute.NewBuildCommand(rcmd.CmdClause, &g)\n\tdcmd := compute.NewDeployCommand(rcmd.CmdClause, &g)\n\tpcmd := compute.NewPublishCommand(rcmd.CmdClause, &g, bcmd, dcmd)\n\n\tbuildFlags := getFlags(bcmd.CmdClause)\n\tdeployFlags := getFlags(dcmd.CmdClause)\n\tpublishFlags := getFlags(pcmd.CmdClause)\n\n\tvar (\n\t\texpect = make(map[string]int)\n\t\thave   = make(map[string]int)\n\t)\n\n\t// Some flags on `compute build` are unique to it.\n\t// NOTE: There are no flags to ignore but I'm keeping the logic for future.\n\tignoreBuildFlags := []string{}\n\n\titer := buildFlags.MapRange()\n\tfor iter.Next() {\n\t\tflag := iter.Key().String()\n\t\tif !ignoreFlag(ignoreBuildFlags, flag) {\n\t\t\texpect[flag] = 1\n\t\t}\n\t}\n\n\titer = deployFlags.MapRange()\n\tfor iter.Next() {\n\t\texpect[iter.Key().String()] = 1\n\t}\n\n\titer = publishFlags.MapRange()\n\tfor iter.Next() {\n\t\thave[iter.Key().String()] = 1\n\t}\n\n\tif !reflect.DeepEqual(expect, have) {\n\t\tt.Fatalf(\"the flags between build/deploy and publish don't match\\n\\nexpect: %+v\\nhave:   %+v\\n\\n\", expect, have)\n\t}\n}\n\n// TestFlagDivergenceServe validates that the manually curated list of flags\n// within the `compute serve` command doesn't fall out of sync with the\n// `compute build` command as `compute serve` delegates to build.\nfunc TestFlagDivergenceServe(t *testing.T) {\n\tvar cfg global.Data\n\tacmd := kingpin.New(\"foo\", \"bar\")\n\n\trcmd := compute.NewRootCommand(acmd, &cfg)\n\tbcmd := compute.NewBuildCommand(rcmd.CmdClause, &cfg)\n\tscmd := compute.NewServeCommand(rcmd.CmdClause, &cfg, bcmd)\n\n\tbuildFlags := getFlags(bcmd.CmdClause)\n\tserveFlags := getFlags(scmd.CmdClause)\n\n\tvar (\n\t\texpect = make(map[string]int)\n\t\thave   = make(map[string]int)\n\t)\n\n\t// Some flags on `compute build` are unique to it.\n\t// NOTE: There are no flags to ignore but I'm keeping the logic for future.\n\tignoreBuildFlags := []string{}\n\n\titer := buildFlags.MapRange()\n\tfor iter.Next() {\n\t\tflag := iter.Key().String()\n\t\tif !ignoreFlag(ignoreBuildFlags, flag) {\n\t\t\texpect[flag] = 1\n\t\t}\n\t}\n\n\t// Some flags on `compute serve` are unique to it.\n\t// We only want to be sure serve contains all build flags.\n\tignoreServeFlags := []string{\n\t\t\"addr\",\n\t\t\"debug\",\n\t\t\"experimental-enable-pushpin\",\n\t\t\"file\",\n\t\t\"profile-guest\",\n\t\t\"pushpin-path\",\n\t\t\"pushpin-proxy-port\",\n\t\t\"pushpin-publish-port\",\n\t\t\"profile-guest-dir\",\n\t\t\"skip-build\",\n\t\t\"viceroy-args\",\n\t\t\"viceroy-check\",\n\t\t\"viceroy-path\",\n\t\t\"watch\",\n\t\t\"watch-dir\",\n\t}\n\n\titer = serveFlags.MapRange()\n\tfor iter.Next() {\n\t\tflag := iter.Key().String()\n\t\tif !ignoreFlag(ignoreServeFlags, flag) {\n\t\t\thave[flag] = 1\n\t\t}\n\t}\n\n\tif !reflect.DeepEqual(expect, have) {\n\t\tt.Fatalf(\"the flags between build and serve don't match\\n\\nexpect: %+v\\nhave:   %+v\\n\\n\", expect, have)\n\t}\n}\n\n// TestFlagDivergenceHashFiles validates that the manually curated list of flags\n// within the `compute hash-files` command doesn't fall out of sync with the\n// `compute build` command as `compute hash-files` delegates to build.\nfunc TestFlagDivergenceHashFiles(t *testing.T) {\n\tvar cfg global.Data\n\tacmd := kingpin.New(\"foo\", \"bar\")\n\n\trcmd := compute.NewRootCommand(acmd, &cfg)\n\tbcmd := compute.NewBuildCommand(rcmd.CmdClause, &cfg)\n\thcmd := compute.NewHashFilesCommand(rcmd.CmdClause, &cfg, bcmd)\n\n\tbuildFlags := getFlags(bcmd.CmdClause)\n\thashfilesFlags := getFlags(hcmd.CmdClause)\n\n\tvar (\n\t\texpect = make(map[string]int)\n\t\thave   = make(map[string]int)\n\t)\n\n\t// Some flags on `compute build` are unique to it.\n\t// NOTE: There are no flags to ignore but I'm keeping the logic for future.\n\tignoreBuildFlags := []string{}\n\n\titer := buildFlags.MapRange()\n\tfor iter.Next() {\n\t\tflag := iter.Key().String()\n\t\tif !ignoreFlag(ignoreBuildFlags, flag) {\n\t\t\texpect[flag] = 1\n\t\t}\n\t}\n\n\t// Some flags on `compute hash-files` are unique to it.\n\t// We only want to be sure hash-files contains all build flags.\n\tignoreHashfilesFlags := []string{\n\t\t\"package\",\n\t\t\"skip-build\",\n\t}\n\n\titer = hashfilesFlags.MapRange()\n\tfor iter.Next() {\n\t\tflag := iter.Key().String()\n\t\tif !ignoreFlag(ignoreHashfilesFlags, flag) {\n\t\t\thave[flag] = 1\n\t\t}\n\t}\n\n\tif !reflect.DeepEqual(expect, have) {\n\t\tt.Fatalf(\"the flags between build and hash-files don't match\\n\\nexpect: %+v\\nhave:   %+v\\n\\n\", expect, have)\n\t}\n}\n\n// ignoreFlag indicates if needle should be omitted from comparison.\nfunc ignoreFlag(ignore []string, flag string) bool {\n\tfor _, i := range ignore {\n\t\tif i == flag {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc getFlags(cmd *kingpin.CmdClause) reflect.Value {\n\treturn reflect.ValueOf(cmd).Elem().FieldByName(\"cmdMixin\").FieldByName(\"flagGroup\").Elem().FieldByName(\"long\")\n}\n\nfunc TestCreatePackageArchive(t *testing.T) {\n\t// we're going to chdir to a build environment,\n\t// so save the pwd to return to, afterwards.\n\tpwd, err := os.Getwd()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t// Create test environment\n\trootdir := testutil.NewEnv(testutil.EnvOpts{\n\t\tT: t,\n\t\tCopy: []testutil.FileIO{\n\t\t\t{Src: filepath.Join(\"testdata\", \"build\", \"rust\", \"Cargo.lock\"), Dst: \"Cargo.lock\"},\n\t\t\t{Src: filepath.Join(\"testdata\", \"build\", \"rust\", \"Cargo.toml\"), Dst: \"Cargo.toml\"},\n\t\t\t{Src: filepath.Join(\"testdata\", \"build\", \"rust\", \"src\", \"main.rs\"), Dst: filepath.Join(\"src\", \"main.rs\")},\n\t\t},\n\t})\n\tdefer os.RemoveAll(rootdir)\n\n\t// before running the test, chdir into the build environment.\n\t// when we're done, chdir back to our original location.\n\t// this is so we can reliably copy the testdata/ fixtures.\n\tif err := os.Chdir(rootdir); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer func() {\n\t\t_ = os.Chdir(pwd)\n\t}()\n\n\tdestination := \"cli.tar.gz\"\n\n\terr = compute.CreatePackageArchive([]string{\"Cargo.toml\", \"Cargo.lock\", \"src/main.rs\"}, destination)\n\ttestutil.AssertNoError(t, err)\n\n\tvar files, directories []string\n\n\t// Walk the archive using archives API\n\tinput, err := os.Open(destination)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer input.Close()\n\n\tformat, stream, err := archives.Identify(context.Background(), destination, input)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif ex, ok := format.(archives.Extractor); ok {\n\t\terr = ex.Extract(context.Background(), stream, func(_ context.Context, f archives.FileInfo) error {\n\t\t\tname := filepath.Base(f.NameInArchive)\n\t\t\tif f.IsDir() {\n\t\t\t\tdirectories = append(directories, name)\n\t\t\t} else {\n\t\t\t\tfiles = append(files, name)\n\t\t\t}\n\t\t\treturn nil\n\t\t})\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t} else {\n\t\tt.Fatal(\"format does not support extraction\")\n\t}\n\n\twantDirectories := []string{\"cli\", \"src\"}\n\ttestutil.AssertEqual(t, wantDirectories, directories)\n\n\twantFiles := []string{\"Cargo.lock\", \"Cargo.toml\", \"main.rs\"}\n\ttestutil.AssertEqual(t, wantFiles, files)\n}\n\nfunc TestFileNameWithoutExtension(t *testing.T) {\n\tfor _, testcase := range []struct {\n\t\tinput      string\n\t\twantOutput string\n\t}{\n\t\t{\n\t\t\tinput:      \"foo/bar/baz.tar.gz\",\n\t\t\twantOutput: \"baz\",\n\t\t},\n\t\t{\n\t\t\tinput:      \"foo/bar/baz.wasm\",\n\t\t\twantOutput: \"baz\",\n\t\t},\n\t\t{\n\t\t\tinput:      \"foo.tar\",\n\t\t\twantOutput: \"foo\",\n\t\t},\n\t} {\n\t\tt.Run(testcase.input, func(t *testing.T) {\n\t\t\toutput := compute.FileNameWithoutExtension(testcase.input)\n\t\t\ttestutil.AssertString(t, testcase.wantOutput, output)\n\t\t})\n\t}\n}\n\nfunc TestGetIgnoredFiles(t *testing.T) {\n\t// we're going to chdir to a build environment,\n\t// so save the pwd to return to, afterwards.\n\tpwd, err := os.Getwd()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t// Create test environment\n\trootdir := testutil.NewEnv(testutil.EnvOpts{\n\t\tT: t,\n\t\tCopy: []testutil.FileIO{\n\t\t\t{Src: filepath.Join(\"testdata\", \"build\", \"rust\", \"Cargo.lock\"), Dst: \"Cargo.lock\"},\n\t\t\t{Src: filepath.Join(\"testdata\", \"build\", \"rust\", \"Cargo.toml\"), Dst: \"Cargo.toml\"},\n\t\t\t{Src: filepath.Join(\"testdata\", \"build\", \"rust\", \"src\", \"main.rs\"), Dst: filepath.Join(\"src\", \"main.rs\")},\n\t\t},\n\t})\n\tdefer os.RemoveAll(rootdir)\n\n\t// before running the test, chdir into the build environment.\n\t// when we're done, chdir back to our original location.\n\t// this is so we can reliably copy the testdata/ fixtures.\n\tif err := os.Chdir(rootdir); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer func() {\n\t\t_ = os.Chdir(pwd)\n\t}()\n\n\tfor _, testcase := range []struct {\n\t\tname         string\n\t\tfastlyignore string\n\t\twantfiles    map[string]bool\n\t}{\n\t\t{\n\t\t\tname:         \"ignore src\",\n\t\t\tfastlyignore: \"src/*\",\n\t\t\twantfiles: map[string]bool{\n\t\t\t\tfilepath.Join(\"src\", \"main.rs\"): true,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:         \"ignore cargo files\",\n\t\t\tfastlyignore: \"Cargo.*\",\n\t\t\twantfiles: map[string]bool{\n\t\t\t\t\"Cargo.lock\": true,\n\t\t\t\t\"Cargo.toml\": true,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:         \"ignore all\",\n\t\t\tfastlyignore: \"*\",\n\t\t\twantfiles: map[string]bool{\n\t\t\t\t\".fastlyignore\": true,\n\t\t\t\t\"Cargo.lock\":    true,\n\t\t\t\t\"Cargo.toml\":    true,\n\t\t\t\t\"src\":           true,\n\t\t\t},\n\t\t},\n\t} {\n\t\tt.Run(testcase.name, func(t *testing.T) {\n\t\t\tif err := os.WriteFile(filepath.Join(rootdir, compute.IgnoreFilePath), []byte(testcase.fastlyignore), 0o600); err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\toutput, err := compute.GetIgnoredFiles(compute.IgnoreFilePath)\n\t\t\ttestutil.AssertNoError(t, err)\n\t\t\ttestutil.AssertEqual(t, testcase.wantfiles, output)\n\t\t})\n\t}\n}\n\nfunc TestGetNonIgnoredFiles(t *testing.T) {\n\t// We're going to chdir to a build environment,\n\t// so save the PWD to return to, afterwards.\n\tpwd, err := os.Getwd()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t// Create test environment\n\trootdir := testutil.NewEnv(testutil.EnvOpts{\n\t\tT: t,\n\t\tCopy: []testutil.FileIO{\n\t\t\t{Src: filepath.Join(\"testdata\", \"build\", \"rust\", \"Cargo.lock\"), Dst: \"Cargo.lock\"},\n\t\t\t{Src: filepath.Join(\"testdata\", \"build\", \"rust\", \"Cargo.toml\"), Dst: \"Cargo.toml\"},\n\t\t\t{Src: filepath.Join(\"testdata\", \"build\", \"rust\", \"src\", \"main.rs\"), Dst: filepath.Join(\"src\", \"main.rs\")},\n\t\t},\n\t})\n\tdefer os.RemoveAll(rootdir)\n\n\t// Before running the test, chdir into the build environment.\n\t// When we're done, chdir back to our original location.\n\t// This is so we can reliably copy the testdata/ fixtures.\n\tif err := os.Chdir(rootdir); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer func() {\n\t\t_ = os.Chdir(pwd)\n\t}()\n\n\tfor _, testcase := range []struct {\n\t\tname         string\n\t\tpath         string\n\t\tignoredFiles map[string]bool\n\t\twantFiles    []string\n\t}{\n\t\t{\n\t\t\tname:         \"no ignored files\",\n\t\t\tpath:         \".\",\n\t\t\tignoredFiles: map[string]bool{},\n\t\t\twantFiles: []string{\n\t\t\t\t\"Cargo.lock\",\n\t\t\t\t\"Cargo.toml\",\n\t\t\t\tfilepath.Join(\"src\", \"main.rs\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"one ignored file\",\n\t\t\tpath: \".\",\n\t\t\tignoredFiles: map[string]bool{\n\t\t\t\tfilepath.Join(\"src\", \"main.rs\"): true,\n\t\t\t},\n\t\t\twantFiles: []string{\n\t\t\t\t\"Cargo.lock\",\n\t\t\t\t\"Cargo.toml\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"multiple ignored files\",\n\t\t\tpath: \".\",\n\t\t\tignoredFiles: map[string]bool{\n\t\t\t\t\"Cargo.toml\": true,\n\t\t\t\t\"Cargo.lock\": true,\n\t\t\t},\n\t\t\twantFiles: []string{\n\t\t\t\tfilepath.Join(\"src\", \"main.rs\"),\n\t\t\t},\n\t\t},\n\t} {\n\t\tt.Run(testcase.name, func(t *testing.T) {\n\t\t\toutput, err := compute.GetNonIgnoredFiles(testcase.path, testcase.ignoredFiles)\n\t\t\ttestutil.AssertNoError(t, err)\n\t\t\ttestutil.AssertEqual(t, testcase.wantFiles, output)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/commands/compute/computeacl/computeacl_test.go",
    "content": "package computeacl_test\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\t\"testing\"\n\n\troot \"github.com/fastly/cli/pkg/commands/compute\"\n\tsub \"github.com/fastly/cli/pkg/commands/compute/computeacl\"\n\tfstfmt \"github.com/fastly/cli/pkg/fmt\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n\t\"github.com/fastly/go-fastly/v15/fastly/computeacls\"\n)\n\nfunc TestComputeACLCreate(t *testing.T) {\n\tconst (\n\t\taclName = \"foo\"\n\t\taclID   = \"bar\"\n\t)\n\n\tacl := computeacls.ComputeACL{\n\t\tName:         aclName,\n\t\tComputeACLID: aclID,\n\t}\n\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --name flag\",\n\t\t\tArgs:      \"\",\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate internal server error\",\n\t\t\tArgs: fmt.Sprintf(\"--name %s\", aclName),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusInternalServerError,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusInternalServerError),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"500 - Internal Server Error\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--name %s\", aclName),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader((testutil.GenJSON(acl)))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Created compute ACL '%s' (id: %s)\", aclName, aclID),\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--name %s --json\", aclName),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(acl))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(acl),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"create\"}, scenarios)\n}\n\nfunc TestComputeACLDelete(t *testing.T) {\n\tconst aclID = \"foo\"\n\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --acl-id flag\",\n\t\t\tArgs:      \"\",\n\t\t\tWantError: \"error parsing arguments: required flag --acl-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate bad request\",\n\t\t\tArgs: \"--acl-id bar\",\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusBadRequest,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusBadRequest),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(`\n\t\t\t\t\t\t\t{\n    \t\t\t\t\t\t\t\"title\": \"invalid ACL ID\",\n    \t\t\t\t\t\t\t\"status\": 400\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t`))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"400 - Bad Request\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--acl-id %s\", aclID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Deleted compute ACL (id: %s)\", aclID),\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--acl-id %s --json\", aclID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.JSON(`{\"id\": %q, \"deleted\": true}`, aclID),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"delete\"}, scenarios)\n}\n\nfunc TestComputeACLDescribe(t *testing.T) {\n\tconst (\n\t\taclName = \"foo\"\n\t\taclID   = \"bar\"\n\t)\n\n\tacl := computeacls.ComputeACL{\n\t\tName:         aclName,\n\t\tComputeACLID: aclID,\n\t}\n\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --acl-id flag\",\n\t\t\tArgs:      \"\",\n\t\t\tWantError: \"error parsing arguments: required flag --acl-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate bad request\",\n\t\t\tArgs: \"--acl-id baz\",\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusBadRequest,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusBadRequest),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(`\n\t\t\t\t\t\t\t{\n    \t\t\t\t\t\t\t\"title\": \"invalid ACL ID\",\n    \t\t\t\t\t\t\t\"status\": 400\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t`))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"400 - Bad Request\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--acl-id %s\", aclID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader((testutil.GenJSON(acl)))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: computeACL,\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--acl-id %s --json\", aclID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader((testutil.GenJSON(acl)))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(acl),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"describe\"}, scenarios)\n}\n\nfunc TestComputeACLList(t *testing.T) {\n\tacls := computeacls.ComputeACLs{\n\t\tData: []computeacls.ComputeACL{\n\t\t\t{\n\t\t\t\tName:         \"foo\",\n\t\t\t\tComputeACLID: \"bar\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tName:         \"foobar\",\n\t\t\t\tComputeACLID: \"baz\",\n\t\t\t},\n\t\t},\n\t\tMeta: computeacls.MetaACLs{\n\t\t\tTotal: 2,\n\t\t},\n\t}\n\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName: \"validate internal server error\",\n\t\t\tArgs: \"\",\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusInternalServerError,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusInternalServerError),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"500 - Internal Server Error\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success (zero compute ACLs)\",\n\t\t\tArgs: \"\",\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(computeacls.ComputeACLs{\n\t\t\t\t\t\t\tData: []computeacls.ComputeACL{},\n\t\t\t\t\t\t\tMeta: computeacls.MetaACLs{\n\t\t\t\t\t\t\t\tTotal: 0,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t}))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: zeroComputeACLs,\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: \"\",\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(acls))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: computeACLs,\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: \"--json\",\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(acls))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(acls),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"list-acls\"}, scenarios)\n}\n\nfunc TestComputeACLLookup(t *testing.T) {\n\tconst (\n\t\taclID = \"foo\"\n\t\taclIP = \"1.2.3.4\"\n\t)\n\n\tentry := computeacls.ComputeACLEntry{\n\t\tPrefix: \"1.2.3.4/32\",\n\t\tAction: \"ALLOW\",\n\t}\n\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --ip flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--acl-id %s\", aclID),\n\t\t\tWantError: \"error parsing arguments: required flag --ip not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --acl-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--ip %s\", aclIP),\n\t\t\tWantError: \"error parsing arguments: required flag --acl-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate bad request\",\n\t\t\tArgs: fmt.Sprintf(\"--acl-id baz --ip %s\", aclIP),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusBadRequest,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusBadRequest),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(`\n\t\t\t\t\t\t\t{\n    \t\t\t\t\t\t\t\"title\": \"invalid ACL ID\",\n    \t\t\t\t\t\t\t\"status\": 400\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t`))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"400 - Bad Request\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API status 204 (No Content)\",\n\t\t\tArgs: fmt.Sprintf(\"--acl-id %s --ip 192.168.0.0\", aclID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusNoContent,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusNoContent),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Info(\"Compute ACL (%s) has no entry with IP (192.168.0.0)\", aclID),\n\t\t},\n\t\t{\n\t\t\tName: \"validate API status 204 (No Content) with --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--acl-id %s --ip 192.168.0.0 --json\", aclID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusNoContent,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusNoContent),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(nil),\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--acl-id %s --ip %s\", aclID, aclIP),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader((testutil.GenJSON(entry)))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: computeACLEntry,\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--acl-id %s --ip %s --json\", aclID, aclIP),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader((testutil.GenJSON(entry)))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(&entry),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"lookup\"}, scenarios)\n}\n\nfunc TestComputeACLUpdate(t *testing.T) {\n\tconst aclID = \"foo\"\n\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --acl-id flag\",\n\t\t\tArgs:      \"--file testdata/batch.json\",\n\t\t\tWantError: \"error parsing arguments: required flag --acl-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate bad request\",\n\t\t\tArgs: \"--acl-id bar --file testdata/entries.json\",\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusBadRequest,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusBadRequest),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(`\n\t\t\t\t\t\t\t{\n    \t\t\t\t\t\t\t\"title\": \"invalid ACL ID\",\n    \t\t\t\t\t\t\t\"status\": 400\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t`))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"400 - Bad Request\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate error from --file set with invalid json\",\n\t\t\tArgs: fmt.Sprintf(`--acl-id %s --file {\"foo\":\"bar\"}`, aclID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusBadRequest,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusBadRequest),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(`\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\"title\": \"can't parse body\",\n\t\t\t\t\t\t\t\t\"status\": 400,\n\t\t\t\t\t\t\t\t\"detail\": \"missing field 'entries' at line 1 column 13\"\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t`))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"missing 'entries' {\\\"foo\\\":\\\"bar\\\"}\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate error from --file set with zero json entries\",\n\t\t\tArgs: fmt.Sprintf(`--acl-id %s --file {\"entries\":[]}`, aclID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusAccepted,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusAccepted),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"missing 'entries' {\\\"entries\\\":[]}\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate success with --file\",\n\t\t\tArgs: fmt.Sprintf(\"--acl-id %s --file testdata/entries.json\", aclID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusAccepted,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusAccepted),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Updated %d compute ACL entries (id: %s)\", 4, aclID),\n\t\t},\n\t\t{\n\t\t\tName: \"validate success with --file as inline json\",\n\t\t\tArgs: fmt.Sprintf(`--acl-id %s --file {\"entries\":[{\"op\":\"create\",\"prefix\":\"1.2.3.0/24\",\"action\":\"BLOCK\"}]}`, aclID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusAccepted,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusAccepted),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Updated %d compute ACL entries (id: %s)\", 1, aclID),\n\t\t},\n\t\t{\n\t\t\tName: \"validate success for updating a single entry with --operation, --prefix, and --action\",\n\t\t\tArgs: fmt.Sprintf(\"--acl-id %s --operation create --prefix 1.2.3.0/24 --action BLOCK\", aclID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusAccepted,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusAccepted),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Updated compute ACL entry (prefix: 1.2.3.0/24, id: %s)\", aclID),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"update\"}, scenarios)\n}\n\nfunc TestComputeACLListEntries(t *testing.T) {\n\tconst aclID = \"foo\"\n\n\tentries := &computeacls.ComputeACLEntries{\n\t\tEntries: []computeacls.ComputeACLEntry{\n\t\t\t{\n\t\t\t\tPrefix: \"1.2.3.0/24\",\n\t\t\t\tAction: \"BLOCK\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tPrefix: \"1.2.3.4/32\",\n\t\t\t\tAction: \"ALLOW\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tPrefix: \"23.23.23.23/32\",\n\t\t\t\tAction: \"ALLOW\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tPrefix: \"192.168.0.0/16\",\n\t\t\t\tAction: \"BLOCK\",\n\t\t\t},\n\t\t},\n\t\tMeta: computeacls.MetaEntries{\n\t\t\tLimit: 100,\n\t\t},\n\t}\n\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --acl-id flag\",\n\t\t\tArgs:      \"\",\n\t\t\tWantError: \"error parsing arguments: required flag --acl-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate bad request\",\n\t\t\tArgs: \"--acl-id bar\",\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusBadRequest,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusBadRequest),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(`\n\t\t\t\t\t\t\t{\n    \t\t\t\t\t\t\t\"title\": \"invalid ACL ID\",\n    \t\t\t\t\t\t\t\"status\": 400\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t`))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"400 - Bad Request\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success (zero compute ACL entries)\",\n\t\t\tArgs: fmt.Sprintf(\"--acl-id %s\", aclID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(computeacls.ComputeACLEntries{\n\t\t\t\t\t\t\tEntries: []computeacls.ComputeACLEntry{},\n\t\t\t\t\t\t\tMeta: computeacls.MetaEntries{\n\t\t\t\t\t\t\t\tLimit: 100,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t}))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: zeroComputeACLEntries,\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--acl-id %s\", aclID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(entries))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: computeACLEntries,\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--acl-id %s --json\", aclID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(entries))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(entries.Entries),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"list-entries\"}, scenarios)\n}\n\nvar computeACL = strings.TrimSpace(`\nID: bar\nName: foo\n`) + \"\\n\"\n\nvar computeACLs = strings.TrimSpace(`\nName    ID\nfoo     bar\nfoobar  baz\n`) + \"\\n\"\n\nvar zeroComputeACLs = strings.TrimSpace(`\nName  ID\n`) + \"\\n\"\n\nvar computeACLEntry = strings.TrimSpace(`\nPrefix: 1.2.3.4/32\nAction: ALLOW\n`) + \"\\n\"\n\nvar computeACLEntries = strings.TrimSpace(`\nPrefix          Action\n1.2.3.0/24      BLOCK\n1.2.3.4/32      ALLOW\n23.23.23.23/32  ALLOW\n192.168.0.0/16  BLOCK\n`) + \"\\n\"\n\nvar zeroComputeACLEntries = strings.TrimSpace(`\nPrefix  Action\n`) + \"\\n\"\n"
  },
  {
    "path": "pkg/commands/compute/computeacl/create.go",
    "content": "package computeacl\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly/computeacls\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// CreateCommand calls the Fastly API to create a compute ACL.\ntype CreateCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tname string\n}\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\n\tc.CmdClause = parent.Command(\"create\", \"Create a compute ACL\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"Name of the compute ACL\").Required().StringVar(&c.name)\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *CreateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tfc, ok := c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tacl, err := computeacls.Create(context.TODO(), fc, &computeacls.CreateInput{\n\t\tName: &c.name,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, acl); ok {\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Created compute ACL '%s' (id: %s)\", acl.Name, acl.ComputeACLID)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/compute/computeacl/delete.go",
    "content": "package computeacl\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly/computeacls\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DeleteCommand calls the Fastly API to delete a compute ACL.\ntype DeleteCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tid string\n}\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\n\tc.CmdClause = parent.Command(\"delete\", \"Delete a compute ACL\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"acl-id\", \"Compute ACL ID\").Required().StringVar(&c.id)\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DeleteCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tfc, ok := c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\terr := computeacls.Delete(context.TODO(), fc, &computeacls.DeleteInput{\n\t\tComputeACLID: &c.id,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif c.JSONOutput.Enabled {\n\t\to := struct {\n\t\t\tID      string `json:\"id\"`\n\t\t\tDeleted bool   `json:\"deleted\"`\n\t\t}{\n\t\t\tc.id,\n\t\t\ttrue,\n\t\t}\n\t\t_, err := c.WriteJSON(out, o)\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Deleted compute ACL (id: %s)\", c.id)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/compute/computeacl/describe.go",
    "content": "package computeacl\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly/computeacls\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DescribeCommand calls the Fastly API to describe a compute ACL.\ntype DescribeCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tid string\n}\n\n// NewDescribeCommand returns a usable command registered under the parent.\nfunc NewDescribeCommand(parent argparser.Registerer, g *global.Data) *DescribeCommand {\n\tc := DescribeCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\n\tc.CmdClause = parent.Command(\"describe\", \"Describe a compute ACL\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"acl-id\", \"Compute ACL ID\").Required().StringVar(&c.id)\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DescribeCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tfc, ok := c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tacl, err := computeacls.Describe(context.TODO(), fc, &computeacls.DescribeInput{\n\t\tComputeACLID: &c.id,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, acl); ok {\n\t\treturn err\n\t}\n\n\ttext.PrintComputeACL(out, \"\", acl)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/compute/computeacl/doc.go",
    "content": "// Package computeacl contains commands to inspect and manipulate Fastly compute ACLs.\npackage computeacl\n"
  },
  {
    "path": "pkg/commands/compute/computeacl/listacls.go",
    "content": "package computeacl\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly/computeacls\"\n)\n\n// ListCommand calls the Fastly API to list all compute ACLs.\ntype ListCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n}\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\n\tc.CmdClause = parent.Command(\"list-acls\", \"List all compute ACLs\")\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *ListCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tfc, ok := c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tacls, err := computeacls.ListACLs(context.TODO(), fc)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, acls); ok {\n\t\treturn err\n\t}\n\n\ttext.PrintComputeACLsTbl(out, acls.Data)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/compute/computeacl/listentries.go",
    "content": "package computeacl\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly/computeacls\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// ListEntriesCommand calls the Fastly API to list all entries of a compute ACLs.\ntype ListEntriesCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tid string\n\n\t// Optional.\n\tcursor string\n\tlimit  int\n}\n\n// NewListEntriesCommand returns a usable command registered under the parent.\nfunc NewListEntriesCommand(parent argparser.Registerer, g *global.Data) *ListEntriesCommand {\n\tc := ListEntriesCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\n\tc.CmdClause = parent.Command(\"list-entries\", \"List all entries of a compute ACL\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"acl-id\", \"Compute ACL ID\").Required().StringVar(&c.id)\n\n\t// Optional.\n\tc.RegisterFlag(argparser.CursorFlag(&c.cursor))\n\tc.RegisterFlagInt(argparser.LimitFlag(&c.limit))\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *ListEntriesCommand) Exec(in io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tfc, ok := c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tvar entries []computeacls.ComputeACLEntry\n\tloadAllPages := c.JSONOutput.Enabled || c.Globals.Flags.NonInteractive || c.Globals.Flags.AutoYes\n\n\tfor {\n\t\to, err := computeacls.ListEntries(context.TODO(), fc, &computeacls.ListEntriesInput{\n\t\t\tComputeACLID: &c.id,\n\t\t\tCursor:       &c.cursor,\n\t\t\tLimit:        &c.limit,\n\t\t})\n\t\tif err != nil {\n\t\t\tc.Globals.ErrLog.Add(err)\n\t\t\treturn err\n\t\t}\n\n\t\tif o != nil {\n\t\t\tentries = append(entries, o.Entries...)\n\n\t\t\tif loadAllPages {\n\t\t\t\tif next := o.Meta.NextCursor; next != \"\" {\n\t\t\t\t\tc.cursor = next\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\ttext.PrintComputeACLEntriesTbl(out, o.Entries)\n\n\t\t\tif next := o.Meta.NextCursor; next != \"\" {\n\t\t\t\ttext.Break(out)\n\t\t\t\tprintNextPage, err := text.AskYesNo(out, \"Print next page [y/N]: \", in)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tif printNextPage {\n\t\t\t\t\tc.cursor = next\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tbreak\n\t}\n\n\tok, err := c.WriteJSON(out, entries)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Only print output here if we've not already printed JSON.\n\t// And only if we're non interactive.\n\t// Otherwise interactive mode would have displayed each page of data.\n\tif !ok && (c.Globals.Flags.NonInteractive || c.Globals.Flags.AutoYes) {\n\t\ttext.PrintComputeACLEntriesTbl(out, entries)\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/compute/computeacl/lookup.go",
    "content": "package computeacl\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly/computeacls\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// LookupCommand calls the Fastly API to lookup a compute ACL entry.\ntype LookupCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tid string\n\tip string\n}\n\n// NewLookupCommand returns a usable command registered under the parent.\nfunc NewLookupCommand(parent argparser.Registerer, g *global.Data) *LookupCommand {\n\tc := LookupCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\n\tc.CmdClause = parent.Command(\"lookup\", \"Find a matching ACL entry for an IP address\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"acl-id\", \"Compute ACL ID\").Required().StringVar(&c.id)\n\tc.CmdClause.Flag(\"ip\", \"Valid IPv4 or IPv6 address\").Required().StringVar(&c.ip)\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *LookupCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tfc, ok := c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tentry, err := computeacls.Lookup(context.TODO(), fc, &computeacls.LookupInput{\n\t\tComputeACLID: &c.id,\n\t\tComputeACLIP: &c.ip,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, entry); ok {\n\t\treturn err\n\t}\n\n\t// Status 204 - No Content\n\tif entry == nil {\n\t\ttext.Info(out, \"Compute ACL (%s) has no entry with IP (%s)\", c.id, c.ip)\n\t\treturn nil\n\t}\n\n\ttext.PrintComputeACLEntry(out, \"\", entry)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/compute/computeacl/root.go",
    "content": "package computeacl\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"acl\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manipulate Fastly compute ACLs\")\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/compute/computeacl/testdata/entries.json",
    "content": "{\n  \"entries\": [\n    {\n      \"op\": \"create\",\n      \"prefix\": \"1.2.3.0/24\",\n      \"action\": \"BLOCK\"\n    },\n    {\n      \"op\": \"update\",\n      \"prefix\": \"192.168.0.0/16\",\n      \"action\": \"BLOCK\"\n    },\n    {\n      \"op\": \"create\",\n      \"prefix\": \"23.23.23.23/32\",\n      \"action\": \"ALLOW\"\n    },\n    {\n      \"op\": \"update\",\n      \"prefix\": \"1.2.3.4/32\",\n      \"action\": \"ALLOW\"\n    }\n  ]\n}\n"
  },
  {
    "path": "pkg/commands/compute/computeacl/update.go",
    "content": "package computeacl\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly/computeacls\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// UpdateCommand calls the Fastly API to update a compute ACL.\ntype UpdateCommand struct {\n\targparser.Base\n\n\t// Required.\n\tcomputeACLID string\n\n\t// Optional.\n\tfile      argparser.OptionalString\n\toperation argparser.OptionalString\n\tprefix    argparser.OptionalString\n\taction    argparser.OptionalString\n}\n\n// operations is a list of supported operation options.\nvar operations = []string{\"create\", \"update\"}\n\n// actions is a list of supported action options.\nvar actions = []string{\"BLOCK\", \"ALLOW\"}\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\n\tc.CmdClause = parent.Command(\"update\", \"Update a compute ACL\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"acl-id\", \"Alphanumeric string identifying a compute ACL\").Required().StringVar(&c.computeACLID)\n\n\t// Optional.\n\tc.CmdClause.Flag(\"file\", \"Batch update JSON file passed as file path or content, e.g. $(< batch.json)\").Action(c.file.Set).StringVar(&c.file.Value)\n\tc.CmdClause.Flag(\"operation\", \"Indicating that this entry is to be added to/updated in the ACL\").HintOptions(operations...).EnumVar(&c.operation.Value, operations...)\n\tc.CmdClause.Flag(\"prefix\", \"An IP prefix defined in Classless Inter-Domain Routing (CIDR) format, i.e. a valid IP address (v4 or v6) followed by a forward slash (/) and a prefix length (0-32 or 0-128, depending on address family)\").Action(c.prefix.Set).StringVar(&c.prefix.Value)\n\tc.CmdClause.Flag(\"action\", \"The action taken on the IP address\").HintOptions(actions...).EnumVar(&c.action.Value, actions...)\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *UpdateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tfc, ok := c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tif c.file.WasSet {\n\t\tinput, err := c.constructBatchInput()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\terr = computeacls.Update(context.TODO(), fc, input)\n\t\tif err != nil {\n\t\t\tc.Globals.ErrLog.Add(err)\n\t\t\treturn err\n\t\t}\n\n\t\ttext.Success(out, \"Updated %d compute ACL entries (id: %s)\", len(input.Entries), c.computeACLID)\n\t\treturn nil\n\t}\n\n\tinput, err := c.constructInput()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\terr = computeacls.Update(context.TODO(), fc, input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Updated compute ACL entry (prefix: %s, id: %s)\", c.prefix.Value, c.computeACLID)\n\treturn nil\n}\n\n// constructBatchInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *UpdateCommand) constructBatchInput() (*computeacls.UpdateInput, error) {\n\tvar input computeacls.UpdateInput\n\n\tinput.ComputeACLID = &c.computeACLID\n\n\ts := argparser.Content(c.file.Value)\n\tbs := []byte(s)\n\n\terr := json.Unmarshal(bs, &input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"File\": s,\n\t\t})\n\t\treturn nil, err\n\t}\n\n\tif len(input.Entries) == 0 {\n\t\terr := fsterr.RemediationError{\n\t\t\tInner:       fmt.Errorf(\"missing 'entries' %s\", c.file.Value),\n\t\t\tRemediation: \"Consult the API documentation for the JSON format: https://www.fastly.com/documentation/reference/api/acls/acls/#compute-acl-update-acls\",\n\t\t}\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"File\": string(bs),\n\t\t})\n\t\treturn nil, err\n\t}\n\n\treturn &input, nil\n}\n\n// constructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *UpdateCommand) constructInput() (*computeacls.UpdateInput, error) {\n\tvar input computeacls.UpdateInput\n\n\tif c.operation.Value == \"\" || c.prefix.Value == \"\" || c.action.Value == \"\" {\n\t\treturn nil, fsterr.ErrInvalidComputeACLCombo\n\t}\n\n\tinput.ComputeACLID = &c.computeACLID\n\tinput.Entries = []*computeacls.BatchComputeACLEntry{\n\t\t{\n\t\t\tPrefix:    &c.prefix.Value,\n\t\t\tAction:    &c.action.Value,\n\t\t\tOperation: &c.operation.Value,\n\t\t},\n\t}\n\n\treturn &input, nil\n}\n"
  },
  {
    "path": "pkg/commands/compute/deploy.go",
    "content": "package compute\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"io/fs\"\n\t\"net/http\"\n\t\"os\"\n\t\"os/signal\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"syscall\"\n\t\"time\"\n\n\t\"github.com/kennygrant/sanitize\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/api\"\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/compute/setup\"\n\t\"github.com/fastly/cli/pkg/debug\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/file\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/internal/beacon\"\n\t\"github.com/fastly/cli/pkg/lookup\"\n\t\"github.com/fastly/cli/pkg/manifest\"\n\t\"github.com/fastly/cli/pkg/text\"\n\t\"github.com/fastly/cli/pkg/undo\"\n)\n\nconst (\n\tmanageServiceBaseURL = \"https://manage.fastly.com/configure/services/\"\n)\n\n// ErrPackageUnchanged is an error that indicates the package hasn't changed.\nvar ErrPackageUnchanged = errors.New(\"package is unchanged\")\n\n// DeployCommand deploys an artifact previously produced by build.\ntype DeployCommand struct {\n\targparser.Base\n\tmanifestPath string\n\n\t// NOTE: these are public so that the \"publish\" composite command can set the\n\t// values appropriately before calling the Exec() function.\n\tComment            argparser.OptionalString\n\tDir                string\n\tDomain             string\n\tEnv                string\n\tNoDefaultDomain    argparser.OptionalBool\n\tPackagePath        string\n\tServiceName        argparser.OptionalServiceNameID\n\tServiceVersion     argparser.OptionalServiceVersion\n\tStatusCheckCode    int\n\tStatusCheckOff     bool\n\tStatusCheckPath    string\n\tStatusCheckTimeout int\n\tSkipChangeDir      bool // set by parent composite commands (e.g. serve, publish)\n}\n\n// NewDeployCommand returns a usable command registered under the parent.\nfunc NewDeployCommand(parent argparser.Registerer, g *global.Data) *DeployCommand {\n\tvar c DeployCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(\"deploy\", \"Deploy a package to a Fastly Compute service\")\n\n\t// NOTE: when updating these flags, be sure to update the composite command:\n\t// `compute publish`.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &c.Globals.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.ServiceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.ServiceName.Value,\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.ServiceVersion.Set,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.ServiceVersion.Value,\n\t\tName:        argparser.FlagVersionName,\n\t})\n\tc.CmdClause.Flag(\"comment\", \"Human-readable comment\").Action(c.Comment.Set).StringVar(&c.Comment.Value)\n\tc.CmdClause.Flag(\"dir\", \"Project directory (default: current directory)\").Short('C').StringVar(&c.Dir)\n\tc.CmdClause.Flag(\"domain\", \"The name of the domain associated to the package\").StringVar(&c.Domain)\n\tc.CmdClause.Flag(\"env\", \"The manifest environment config to use (e.g. 'stage' will attempt to read 'fastly.stage.toml')\").StringVar(&c.Env)\n\tc.CmdClause.Flag(\"no-default-domain\", \"Skip default domain creation\").Action(c.NoDefaultDomain.Set).BoolVar(&c.NoDefaultDomain.Value)\n\tc.CmdClause.Flag(\"package\", \"Path to a package tar.gz\").Short('p').StringVar(&c.PackagePath)\n\tc.CmdClause.Flag(\"status-check-code\", \"Set the expected status response for the service availability check\").IntVar(&c.StatusCheckCode)\n\tc.CmdClause.Flag(\"status-check-off\", \"Disable the service availability check\").BoolVar(&c.StatusCheckOff)\n\tc.CmdClause.Flag(\"status-check-path\", \"Specify the URL path for the service availability check\").Default(\"/\").StringVar(&c.StatusCheckPath)\n\tc.CmdClause.Flag(\"status-check-timeout\", \"Set a timeout (in seconds) for the service availability check\").Default(\"120\").IntVar(&c.StatusCheckTimeout)\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *DeployCommand) Exec(in io.Reader, out io.Writer) (err error) {\n\tmanifestFilename := EnvironmentManifest(c.Env)\n\tif c.Env != \"\" {\n\t\tif c.Globals.Verbose() {\n\t\t\ttext.Info(out, EnvManifestMsg, manifestFilename, manifest.Filename)\n\t\t}\n\t}\n\twd, err := os.Getwd()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to get current working directory: %w\", err)\n\t}\n\tdefer func() {\n\t\t_ = os.Chdir(wd)\n\t}()\n\tc.manifestPath = filepath.Join(wd, manifestFilename)\n\n\tvar projectDir string\n\tif !c.SkipChangeDir {\n\t\tprojectDir, err = ChangeProjectDirectory(c.Dir)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif projectDir != \"\" {\n\t\t\tif c.Globals.Verbose() {\n\t\t\t\ttext.Info(out, ProjectDirMsg, projectDir)\n\t\t\t}\n\t\t\tc.manifestPath = filepath.Join(projectDir, manifestFilename)\n\t\t}\n\t}\n\n\tspinner, err := text.NewSpinner(out)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\terr = spinner.Process(fmt.Sprintf(\"Verifying %s\", manifestFilename), func(_ *text.SpinnerWrapper) error {\n\t\t// The check for c.SkipChangeDir here is because we might need to attempt\n\t\t// another read of the manifest file. To explain: if we're skipping the\n\t\t// change of directory, it means we were called from a composite command,\n\t\t// which has already changed directory to one that contains the fastly.toml\n\t\t// file. This means we should try reading the manifest file from the new\n\t\t// location as the potential ReadError() would have been based on the\n\t\t// initial directory the CLI was invoked from.\n\t\tif c.SkipChangeDir || projectDir != \"\" || c.Env != \"\" {\n\t\t\terr = c.Globals.Manifest.File.Read(c.manifestPath)\n\t\t} else {\n\t\t\terr = c.Globals.Manifest.File.ReadError()\n\t\t}\n\t\tif err != nil {\n\t\t\t// If the user hasn't specified a package to deploy, then we'll just check\n\t\t\t// the read error and return it.\n\t\t\tif c.PackagePath == \"\" {\n\t\t\t\tif errors.Is(err, os.ErrNotExist) {\n\t\t\t\t\terr = fsterr.ErrReadingManifest\n\t\t\t\t}\n\t\t\t\tc.Globals.ErrLog.Add(err)\n\t\t\t\treturn err\n\t\t\t}\n\t\t\t// Otherwise, we'll attempt to read the manifest from within the given\n\t\t\t// package archive.\n\t\t\tif err := readManifestFromPackageArchive(c.Globals.Manifest, c.PackagePath, manifestFilename); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif c.Globals.Verbose() {\n\t\t\t\ttext.Info(out, \"Using %s within --package archive: %s\\n\\n\", manifestFilename, c.PackagePath)\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t})\n\tif err != nil {\n\t\treturn err\n\t}\n\ttext.Break(out)\n\n\tserviceID, err := c.Setup(out)\n\tif err != nil {\n\t\treturn err\n\t}\n\tnoExistingService := serviceID == \"\"\n\n\tundoStack := undo.NewStack()\n\tundoStack.Push(func() error {\n\t\tif noExistingService && serviceID != \"\" {\n\t\t\treturn c.CleanupNewService(serviceID, manifestFilename, out)\n\t\t}\n\t\treturn nil\n\t})\n\n\tdefer func(errLog fsterr.LogInterface) {\n\t\tif err != nil {\n\t\t\terrLog.Add(err)\n\t\t}\n\t\tundoStack.RunIfError(out, err)\n\t}(c.Globals.ErrLog)\n\n\tsignalCh := make(chan os.Signal, 1)\n\tsignal.Notify(signalCh, syscall.SIGINT, syscall.SIGTERM)\n\tgo monitorSignals(signalCh, noExistingService, out, undoStack, spinner)\n\n\tvar serviceVersion *fastly.Version\n\tif noExistingService {\n\t\tserviceID, serviceVersion, err = c.NewService(manifestFilename, spinner, in, out)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif serviceID == \"\" {\n\t\t\treturn nil // user declined service creation prompt\n\t\t}\n\t} else {\n\t\t// ErrPackageUnchanged is returned AFTER identifying the service version.\n\t\t// nosemgrep: trailofbits.go.invalid-usage-of-modified-variable.invalid-usage-of-modified-variable\n\t\tserviceVersion, err = c.ExistingServiceVersion(serviceID, out)\n\t\tif err != nil {\n\t\t\tif errors.Is(err, ErrPackageUnchanged) {\n\t\t\t\ttext.Info(out, \"Skipping package deployment, local and service version are identical. (service %s, version %d) \", serviceID, fastly.ToValue(serviceVersion.Number))\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\treturn err\n\t\t}\n\t\tif c.Globals.Manifest.File.Setup.Defined() && !c.Globals.Flags.Quiet {\n\t\t\ttext.Info(out, \"\\nProcessing of the %s [setup] configuration happens only for a new service. Once a service is created, any further changes to the service or its resources must be made manually.\\n\\n\", manifestFilename)\n\t\t}\n\t}\n\n\tvar sr ServiceResources\n\tserviceVersionNumber := fastly.ToValue(serviceVersion.Number)\n\n\t// NOTE: A 'domain' resource isn't strictly part of the [setup] config.\n\t// It's part of the implementation so that we can utilise the same interface.\n\t// A domain is required regardless of whether it's a new service or existing.\n\tsr.domains = &setup.Domains{\n\t\tAPIClient:       c.Globals.APIClient,\n\t\tAcceptDefaults:  c.Globals.Flags.AcceptDefaults,\n\t\tNoDefaultDomain: c.NoDefaultDomain.WasSet,\n\t\tNonInteractive:  c.Globals.Flags.NonInteractive,\n\t\tPackageDomain:   c.Domain,\n\t\tRetryLimit:      5,\n\t\tServiceID:       serviceID,\n\t\tServiceVersion:  serviceVersionNumber,\n\t\tStdin:           in,\n\t\tStdout:          out,\n\t\tVerbose:         c.Globals.Verbose(),\n\t}\n\tif err = sr.domains.Validate(); err != nil {\n\t\terrLogService(c.Globals.ErrLog, err, serviceID, serviceVersionNumber)\n\t\treturn fmt.Errorf(\"error configuring service domains: %w\", err)\n\t}\n\tif noExistingService {\n\t\tc.ConstructNewServiceResources(\n\t\t\t&sr, serviceID, serviceVersionNumber, in, out,\n\t\t)\n\t}\n\n\tif sr.domains.Missing() {\n\t\tif err := sr.domains.Configure(); err != nil {\n\t\t\terrLogService(c.Globals.ErrLog, err, serviceID, serviceVersionNumber)\n\t\t\treturn fmt.Errorf(\"error configuring service domains: %w\", err)\n\t\t}\n\t}\n\tif noExistingService {\n\t\tif err = c.ConfigureServiceResources(sr, serviceID, serviceVersionNumber); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tif sr.domains.Missing() {\n\t\tsr.domains.Spinner = spinner\n\t\tif err := sr.domains.Create(); err != nil {\n\t\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\t\"Accept defaults\": c.Globals.Flags.AcceptDefaults,\n\t\t\t\t\"Auto-yes\":        c.Globals.Flags.AutoYes,\n\t\t\t\t\"Non-interactive\": c.Globals.Flags.NonInteractive,\n\t\t\t\t\"Service ID\":      serviceID,\n\t\t\t\t\"Service Version\": serviceVersion,\n\t\t\t})\n\t\t\treturn err\n\t\t}\n\t}\n\tif noExistingService {\n\t\tif err = c.CreateServiceResources(sr, spinner, serviceID, serviceVersionNumber); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\terr = c.UploadPackage(spinner, serviceID, serviceVersionNumber)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Package path\":    c.PackagePath,\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": serviceVersion,\n\t\t})\n\t\treturn err\n\t}\n\n\tif err = c.ProcessService(serviceID, serviceVersionNumber, spinner); err != nil {\n\t\treturn err\n\t}\n\n\tserviceURL, err := c.GetServiceURL(serviceID, serviceVersionNumber)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif !c.StatusCheckOff && noExistingService && serviceURL != \"\" {\n\t\tc.StatusCheck(serviceURL, spinner, out)\n\t}\n\n\tif !noExistingService {\n\t\ttext.Break(out)\n\t}\n\tdisplayDeployOutput(out, manageServiceBaseURL, serviceID, serviceURL, serviceVersionNumber)\n\treturn nil\n}\n\n// StatusCheck checks the service URL and identifies when it's ready.\nfunc (c *DeployCommand) StatusCheck(serviceURL string, spinner text.Spinner, out io.Writer) {\n\tvar (\n\t\terr    error\n\t\tstatus int\n\t)\n\tif status, err = checkingServiceAvailability(serviceURL+c.StatusCheckPath, spinner, c); err != nil {\n\t\tif re, ok := err.(fsterr.RemediationError); ok {\n\t\t\ttext.Warning(out, re.Remediation)\n\t\t}\n\t}\n\n\t// Because the service availability can return an error (which we ignore),\n\t// then we need to check for the 'no error' scenarios.\n\tif err == nil {\n\t\tswitch {\n\t\tcase validStatusCodeRange(c.StatusCheckCode) && status != c.StatusCheckCode:\n\t\t\t// If the user set a specific status code expectation...\n\t\t\ttext.Warning(out, \"The service path `%s` responded with a status code (%d) that didn't match what was expected (%d).\", c.StatusCheckPath, status, c.StatusCheckCode)\n\t\tcase !validStatusCodeRange(c.StatusCheckCode) && status >= http.StatusBadRequest:\n\t\t\t// If no status code was specified, and the actual status response was an error...\n\t\t\ttext.Info(out, \"The service path `%s` responded with a non-successful status code (%d). Please check your application code if this is an unexpected response.\", c.StatusCheckPath, status)\n\t\tdefault:\n\t\t\ttext.Break(out)\n\t\t}\n\t}\n}\n\nfunc displayDeployOutput(out io.Writer, manageServiceBaseURL, serviceID, serviceURL string, serviceVersion int) {\n\ttext.Description(out, \"Manage this service at\", fmt.Sprintf(\"%s%s\", manageServiceBaseURL, serviceID))\n\tif serviceURL != \"\" {\n\t\ttext.Description(out, \"View this service at\", serviceURL)\n\t}\n\ttext.Success(out, \"Deployed package (service %s, version %v)\", serviceID, serviceVersion)\n}\n\n// validStatusCodeRange checks the status is a valid status code.\n// e.g. >= 100 and <= 999.\nfunc validStatusCodeRange(status int) bool {\n\tif status >= 100 && status <= 999 {\n\t\treturn true\n\t}\n\treturn false\n}\n\n// Setup prepares the environment.\n//\n// - Check if there is an API token missing.\n// - Acquire the Service ID/Version.\n// - Validate there is a package to deploy.\nfunc (c *DeployCommand) Setup(out io.Writer) (serviceID string, err error) {\n\t_, s := c.Globals.Token()\n\tif s == lookup.SourceUndefined {\n\t\treturn \"\", fsterr.ErrNoToken()\n\t}\n\n\t// IMPORTANT: We don't handle the error when looking up the Service ID.\n\t// This is because later in the Exec() flow we might create a 'new' service.\n\tserviceID, source, flag, err := argparser.ServiceID(c.ServiceName, *c.Globals.Manifest, c.Globals.APIClient, c.Globals.ErrLog)\n\tif err == nil && c.Globals.Verbose() {\n\t\targparser.DisplayServiceID(serviceID, flag, source, out)\n\t}\n\n\tif c.PackagePath == \"\" {\n\t\tprojectName, source := c.Globals.Manifest.Name()\n\t\tif source == manifest.SourceUndefined {\n\t\t\treturn serviceID, fsterr.ErrReadingManifest\n\t\t}\n\t\tc.PackagePath = filepath.Join(\"pkg\", fmt.Sprintf(\"%s.tar.gz\", sanitize.BaseName(projectName)))\n\t}\n\n\terr = validatePackage(c.PackagePath)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Package path\": c.PackagePath,\n\t\t})\n\t\treturn serviceID, err\n\t}\n\n\treturn serviceID, err\n}\n\n// validatePackage checks the package and returns its path, which can change\n// depending on the user flow scenario.\nfunc validatePackage(pkgPath string) error {\n\tpkgSize, err := packageSize(pkgPath)\n\tif err != nil {\n\t\treturn fsterr.RemediationError{\n\t\t\tInner:       fmt.Errorf(\"error reading package size: %w\", err),\n\t\t\tRemediation: \"Run `fastly compute build` to produce a Compute package, alternatively use the --package flag to reference a package outside of the current project.\",\n\t\t}\n\t}\n\tif pkgSize > MaxPackageSize {\n\t\treturn fsterr.RemediationError{\n\t\t\tInner:       fmt.Errorf(\"package size is too large (%d bytes)\", pkgSize),\n\t\t\tRemediation: fsterr.PackageSizeRemediation,\n\t\t}\n\t}\n\treturn validatePackageContent(pkgPath)\n}\n\n// readManifestFromPackageArchive extracts the manifest file from the given\n// package archive file and reads it into memory.\nfunc readManifestFromPackageArchive(data *manifest.Data, packageFlag, manifestFilename string) error {\n\tdst, err := os.MkdirTemp(\"\", fmt.Sprintf(\"%s-*\", manifestFilename))\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer os.RemoveAll(dst)\n\n\t// Extract archive using shared utility\n\tif err = file.ExtractArchive(packageFlag, dst, nil); err != nil {\n\t\treturn fmt.Errorf(\"error extracting package '%s': %w\", packageFlag, err)\n\t}\n\n\tfiles, err := os.ReadDir(dst)\n\tif err != nil {\n\t\treturn err\n\t}\n\textractedDirName := files[0].Name()\n\n\tmanifestPath, err := locateManifest(filepath.Join(dst, extractedDirName), manifestFilename)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\terr = data.File.Read(manifestPath)\n\tif err != nil {\n\t\tif errors.Is(err, os.ErrNotExist) {\n\t\t\terr = fsterr.ErrReadingManifest\n\t\t}\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\n// locateManifest attempts to find the manifest within the given path's\n// directory tree.\nfunc locateManifest(path, manifestFilename string) (string, error) {\n\troot, err := filepath.Abs(path)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tvar foundManifest string\n\n\terr = filepath.WalkDir(root, func(path string, entry fs.DirEntry, err error) error {\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif !entry.IsDir() && filepath.Base(path) == manifestFilename {\n\t\t\tfoundManifest = path\n\t\t\treturn fsterr.ErrStopWalk\n\t\t}\n\t\treturn nil\n\t})\n\tif err != nil {\n\t\t// If the error isn't ErrStopWalk, then the WalkDir() function had an\n\t\t// issue processing the directory tree.\n\t\tif err != fsterr.ErrStopWalk {\n\t\t\treturn \"\", err\n\t\t}\n\n\t\treturn foundManifest, nil\n\t}\n\n\treturn \"\", fmt.Errorf(\"error locating manifest within the given path: %s\", path)\n}\n\n// packageSize returns the size of the .tar.gz package.\n//\n// Reference:\n// https://docs.fastly.com/products/compute-at-edge-billing-and-resource-limits#resource-limits\nfunc packageSize(path string) (size int64, err error) {\n\tfi, err := os.Stat(path)\n\tif err != nil {\n\t\treturn size, err\n\t}\n\treturn fi.Size(), nil\n}\n\n// NewService handles creating a new service when no Service ID is found.\nfunc (c *DeployCommand) NewService(manifestFilename string, spinner text.Spinner, in io.Reader, out io.Writer) (string, *fastly.Version, error) {\n\tvar (\n\t\terr            error\n\t\tserviceID      string\n\t\tserviceVersion *fastly.Version\n\t)\n\n\tif !c.Globals.Flags.AutoYes && !c.Globals.Flags.NonInteractive {\n\t\ttext.Output(out, \"There is no Fastly service associated with this package. To connect to an existing service add the Service ID to the %s file, otherwise follow the prompts to create a service now.\\n\\n\", manifestFilename)\n\t\ttext.Output(out, \"Press ^C at any time to quit.\")\n\n\t\tif c.Globals.Manifest.File.Setup.Defined() {\n\t\t\ttext.Info(out, \"\\nProcessing of the %s [setup] configuration happens only when there is no existing service. Once a service is created, any further changes to the service or its resources must be made manually.\", manifestFilename)\n\t\t}\n\n\t\ttext.Break(out)\n\t\tanswer, err := text.AskYesNo(out, \"Create new service: [y/N] \", in)\n\t\tif err != nil {\n\t\t\treturn serviceID, serviceVersion, err\n\t\t}\n\t\tif !answer {\n\t\t\treturn serviceID, serviceVersion, nil\n\t\t}\n\t\ttext.Break(out)\n\t}\n\n\tdefaultServiceName := c.Globals.Manifest.File.Name\n\tvar serviceName string\n\n\t// The service name will be whatever is set in the --service-name flag.\n\t// If the flag isn't set, and we're non-interactive, we'll use the default.\n\t// If the flag isn't set, and we're interactive, we'll prompt the user.\n\tswitch {\n\tcase c.ServiceName.WasSet:\n\t\tserviceName = c.ServiceName.Value\n\tcase c.Globals.Flags.AcceptDefaults || c.Globals.Flags.NonInteractive:\n\t\tserviceName = defaultServiceName\n\tdefault:\n\t\tserviceName, err = text.Input(out, text.Prompt(fmt.Sprintf(\"Service name: [%s] \", defaultServiceName)), in)\n\t\tif err != nil || serviceName == \"\" {\n\t\t\tserviceName = defaultServiceName\n\t\t}\n\t}\n\n\t// There is no service and so we'll do a one time creation of the service\n\t//\n\t// NOTE: we're shadowing the `serviceID` and `serviceVersion` variables.\n\tserviceID, serviceVersion, err = createService(c.Globals, serviceName, spinner, out)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service name\": serviceName,\n\t\t})\n\t\treturn serviceID, serviceVersion, err\n\t}\n\n\terr = c.UpdateManifestServiceID(serviceID, c.manifestPath)\n\n\t// NOTE: Skip error if --package flag is set.\n\t//\n\t// This is because the use of the --package flag suggests the user is not\n\t// within a project directory. If that is the case, then we don't want the\n\t// error to be returned because of course there is no manifest to update.\n\t//\n\t// If the user does happen to be in a project directory and they use the\n\t// --package flag, then the above function call to update the manifest will\n\t// have succeeded and so there will be no error.\n\tif err != nil && c.PackagePath == \"\" {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\": serviceID,\n\t\t})\n\t\treturn serviceID, serviceVersion, err\n\t}\n\n\treturn serviceID, serviceVersion, nil\n}\n\n// createService creates a service to associate with the compute package.\nfunc createService(\n\tg *global.Data,\n\tserviceName string,\n\tspinner text.Spinner,\n\tout io.Writer,\n) (serviceID string, serviceVersion *fastly.Version, err error) {\n\tf := g.Flags\n\tapiClient := g.APIClient\n\terrLog := g.ErrLog\n\n\tif !f.AcceptDefaults && !f.NonInteractive {\n\t\ttext.Break(out)\n\t}\n\n\terr = spinner.Start()\n\tif err != nil {\n\t\treturn \"\", nil, err\n\t}\n\tmsg := \"Creating service\"\n\tspinner.Message(msg + \"...\")\n\n\tservice, err := apiClient.CreateService(context.TODO(), &fastly.CreateServiceInput{\n\t\tName: &serviceName,\n\t\tType: fastly.ToPointer(\"wasm\"),\n\t})\n\tif err != nil {\n\t\tspinner.StopFailMessage(msg)\n\t\tspinErr := spinner.StopFail()\n\t\tif spinErr != nil {\n\t\t\treturn \"\", nil, spinErr\n\t\t}\n\n\t\terrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service Name\": serviceName,\n\t\t})\n\t\treturn serviceID, serviceVersion, fmt.Errorf(\"error creating service: %w\", err)\n\t}\n\n\tspinner.StopMessage(msg)\n\terr = spinner.Stop()\n\tif err != nil {\n\t\treturn \"\", nil, err\n\t}\n\treturn fastly.ToValue(service.ServiceID), &fastly.Version{Number: fastly.ToPointer(1)}, nil\n}\n\n// CleanupNewService is executed if a new service flow has errors.\n// It deletes the service, which will cause any contained resources to be deleted.\n// It will also strip the Service ID from the fastly.toml manifest file.\nfunc (c *DeployCommand) CleanupNewService(serviceID, manifestFilename string, out io.Writer) error {\n\ttext.Info(out, \"\\nCleaning up service\\n\\n\")\n\terr := c.Globals.APIClient.DeleteService(context.TODO(), &fastly.DeleteServiceInput{\n\t\tServiceID: serviceID,\n\t})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\ttext.Info(out, \"Removing Service ID from %s\\n\\n\", manifestFilename)\n\terr = c.UpdateManifestServiceID(\"\", c.manifestPath)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\ttext.Output(out, \"Cleanup complete\")\n\treturn nil\n}\n\n// UpdateManifestServiceID updates the Service ID in the manifest.\n//\n// There are two scenarios where this function is called. The first is when we\n// have a Service ID to insert into the manifest. The other is when there is an\n// error in the deploy flow, and for which the Service ID will be set to an\n// empty string (otherwise the service itself will be deleted while the\n// manifest will continue to hold a reference to it).\nfunc (c *DeployCommand) UpdateManifestServiceID(serviceID, manifestPath string) error {\n\tif err := c.Globals.Manifest.File.Read(manifestPath); err != nil {\n\t\treturn fmt.Errorf(\"error reading %s: %w\", manifestPath, err)\n\t}\n\tc.Globals.Manifest.File.ServiceID = serviceID\n\tif err := c.Globals.Manifest.File.Write(manifestPath); err != nil {\n\t\treturn fmt.Errorf(\"error saving %s: %w\", manifestPath, err)\n\t}\n\treturn nil\n}\n\n// errLogService records the error, service id and version into the error log.\nfunc errLogService(l fsterr.LogInterface, err error, sid string, sv int) {\n\tl.AddWithContext(err, map[string]any{\n\t\t\"Service ID\":      sid,\n\t\t\"Service Version\": sv,\n\t})\n}\n\n// CompareLocalRemotePackage compares the local package files hash against the\n// existing service package version and exits early with message if identical.\n//\n// NOTE: We can't avoid the first 'no-changes' upload after the initial deploy.\n// This is because the fastly.toml manifest does actual change after first deploy.\n// When user first deploys, there is no value for service_id.\n// That version of the manifest is inside the package we're checking against.\n// So on the second deploy, even if user has made no changes themselves, we will\n// still upload that package because technically there was a change made by the\n// CLI to add the Service ID. Any subsequent deploys will be aborted because\n// there will be no changes made by the CLI nor the user.\nfunc (c *DeployCommand) CompareLocalRemotePackage(serviceID string, version int) error {\n\tfilesHash, err := getFilesHash(c.PackagePath)\n\tif err != nil {\n\t\treturn err\n\t}\n\tp, err := c.Globals.APIClient.GetPackage(context.TODO(), &fastly.GetPackageInput{\n\t\tServiceID:      serviceID,\n\t\tServiceVersion: version,\n\t})\n\t// IMPORTANT: Skip error as some services won't have a package to compare.\n\t// This happens in situations where a user will create the service outside of\n\t// the CLI and then reference the Service ID in their fastly.toml manifest.\n\t// In that scenario the service might just be an empty service and so trying\n\t// to get the package from the service with 404.\n\tif err == nil && p.Metadata != nil && filesHash == fastly.ToValue(p.Metadata.FilesHash) {\n\t\treturn ErrPackageUnchanged\n\t}\n\treturn nil\n}\n\n// UploadPackage uploads the package to the specified service and version.\nfunc (c *DeployCommand) UploadPackage(spinner text.Spinner, serviceID string, version int) error {\n\treturn spinner.Process(\"Uploading package\", func(_ *text.SpinnerWrapper) error {\n\t\t_, err := c.Globals.APIClient.UpdatePackage(context.TODO(), &fastly.UpdatePackageInput{\n\t\t\tServiceID:      serviceID,\n\t\t\tServiceVersion: version,\n\t\t\tPackagePath:    fastly.ToPointer(c.PackagePath),\n\t\t})\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"error uploading package: %w\", err)\n\t\t}\n\t\treturn nil\n\t})\n}\n\n// ServiceResources is a collection of backend objects created during setup.\n// Objects may be nil.\ntype ServiceResources struct {\n\tdomains      *setup.Domains\n\tbackends     *setup.Backends\n\tconfigStores *setup.ConfigStores\n\tloggers      *setup.Loggers\n\tobjectStores *setup.KVStores\n\tkvStores     *setup.KVStores\n\tsecretStores *setup.SecretStores\n}\n\n// ConstructNewServiceResources instantiates multiple [setup] config resources for a\n// new Service to process.\nfunc (c *DeployCommand) ConstructNewServiceResources(\n\tsr *ServiceResources,\n\tserviceID string,\n\tserviceVersion int,\n\tin io.Reader,\n\tout io.Writer,\n) {\n\tsr.backends = &setup.Backends{\n\t\tAPIClient:      c.Globals.APIClient,\n\t\tAcceptDefaults: c.Globals.Flags.AcceptDefaults,\n\t\tNonInteractive: c.Globals.Flags.NonInteractive,\n\t\tServiceID:      serviceID,\n\t\tServiceVersion: serviceVersion,\n\t\tSetup:          c.Globals.Manifest.File.Setup.Backends,\n\t\tStdin:          in,\n\t\tStdout:         out,\n\t}\n\n\tsr.configStores = &setup.ConfigStores{\n\t\tAPIClient:      c.Globals.APIClient,\n\t\tAcceptDefaults: c.Globals.Flags.AcceptDefaults,\n\t\tNonInteractive: c.Globals.Flags.NonInteractive,\n\t\tServiceID:      serviceID,\n\t\tServiceVersion: serviceVersion,\n\t\tSetup:          c.Globals.Manifest.File.Setup.ConfigStores,\n\t\tStdin:          in,\n\t\tStdout:         out,\n\t}\n\n\tsr.loggers = &setup.Loggers{\n\t\tSetup:  c.Globals.Manifest.File.Setup.Loggers,\n\t\tStdout: out,\n\t}\n\n\tsr.objectStores = &setup.KVStores{\n\t\tAPIClient:      c.Globals.APIClient,\n\t\tAcceptDefaults: c.Globals.Flags.AcceptDefaults,\n\t\tNonInteractive: c.Globals.Flags.NonInteractive,\n\t\tServiceID:      serviceID,\n\t\tServiceVersion: serviceVersion,\n\t\tSetup:          c.Globals.Manifest.File.Setup.ObjectStores,\n\t\tStdin:          in,\n\t\tStdout:         out,\n\t}\n\n\tsr.kvStores = &setup.KVStores{\n\t\tAPIClient:      c.Globals.APIClient,\n\t\tAcceptDefaults: c.Globals.Flags.AcceptDefaults,\n\t\tNonInteractive: c.Globals.Flags.NonInteractive,\n\t\tServiceID:      serviceID,\n\t\tServiceVersion: serviceVersion,\n\t\tSetup:          c.Globals.Manifest.File.Setup.KVStores,\n\t\tStdin:          in,\n\t\tStdout:         out,\n\t}\n\n\tsr.secretStores = &setup.SecretStores{\n\t\tAPIClient:      c.Globals.APIClient,\n\t\tAcceptDefaults: c.Globals.Flags.AcceptDefaults,\n\t\tNonInteractive: c.Globals.Flags.NonInteractive,\n\t\tServiceID:      serviceID,\n\t\tServiceVersion: serviceVersion,\n\t\tSetup:          c.Globals.Manifest.File.Setup.SecretStores,\n\t\tStdin:          in,\n\t\tStdout:         out,\n\t}\n}\n\n// ConfigureServiceResources calls the .Predefined() and .Configure() methods\n// for each [setup] resource, which first checks if a [setup] config has been\n// defined for the resource type, and if so it prompts the user for details.\nfunc (c *DeployCommand) ConfigureServiceResources(sr ServiceResources, serviceID string, serviceVersion int) error {\n\t// NOTE: A service can't be activated without at least one backend defined.\n\t// This explains why the following block of code isn't wrapped in a call to\n\t// the .Predefined() method, as the call to .Configure() will ensure the\n\t// user is prompted regardless of whether there is a [setup.backends]\n\t// defined in the fastly.toml configuration.\n\tif err := sr.backends.Configure(); err != nil {\n\t\terrLogService(c.Globals.ErrLog, err, serviceID, serviceVersion)\n\t\treturn fmt.Errorf(\"error configuring service backends: %w\", err)\n\t}\n\n\tif sr.configStores.Predefined() {\n\t\tif err := sr.configStores.Configure(); err != nil {\n\t\t\terrLogService(c.Globals.ErrLog, err, serviceID, serviceVersion)\n\t\t\treturn fmt.Errorf(\"error configuring service config stores: %w\", err)\n\t\t}\n\t}\n\n\tif sr.loggers.Predefined() {\n\t\t// NOTE: We don't handle errors from the Configure() method because we\n\t\t// don't actually do anything other than display a message to the user\n\t\t// informing them that they need to create a log endpoint and which\n\t\t// provider type they should be. The reason we don't implement logic for\n\t\t// creating logging objects is because the API input fields vary\n\t\t// significantly between providers.\n\t\t_ = sr.loggers.Configure()\n\t}\n\n\tif sr.objectStores.Predefined() {\n\t\tif err := sr.objectStores.Configure(); err != nil {\n\t\t\terrLogService(c.Globals.ErrLog, err, serviceID, serviceVersion)\n\t\t\treturn fmt.Errorf(\"error configuring service object stores: %w\", err)\n\t\t}\n\t}\n\n\tif sr.kvStores.Predefined() {\n\t\tif err := sr.kvStores.Configure(); err != nil {\n\t\t\terrLogService(c.Globals.ErrLog, err, serviceID, serviceVersion)\n\t\t\treturn fmt.Errorf(\"error configuring service kv stores: %w\", err)\n\t\t}\n\t}\n\n\tif sr.secretStores.Predefined() {\n\t\tif err := sr.secretStores.Configure(); err != nil {\n\t\t\terrLogService(c.Globals.ErrLog, err, serviceID, serviceVersion)\n\t\t\treturn fmt.Errorf(\"error configuring service secret stores: %w\", err)\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// CreateServiceResources makes API calls to create resources that have been\n// defined in the fastly.toml [setup] configuration.\nfunc (c *DeployCommand) CreateServiceResources(\n\tsr ServiceResources,\n\tspinner text.Spinner,\n\tserviceID string,\n\tserviceVersion int,\n) error {\n\tsr.backends.Spinner = spinner\n\tsr.configStores.Spinner = spinner\n\tsr.objectStores.Spinner = spinner\n\tsr.kvStores.Spinner = spinner\n\tsr.secretStores.Spinner = spinner\n\n\tif err := sr.backends.Create(); err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Accept defaults\": c.Globals.Flags.AcceptDefaults,\n\t\t\t\"Auto-yes\":        c.Globals.Flags.AutoYes,\n\t\t\t\"Non-interactive\": c.Globals.Flags.NonInteractive,\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": serviceVersion,\n\t\t})\n\t\treturn err\n\t}\n\n\tif err := sr.configStores.Create(); err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Accept defaults\": c.Globals.Flags.AcceptDefaults,\n\t\t\t\"Auto-yes\":        c.Globals.Flags.AutoYes,\n\t\t\t\"Non-interactive\": c.Globals.Flags.NonInteractive,\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": serviceVersion,\n\t\t})\n\t\treturn err\n\t}\n\n\tif err := sr.objectStores.Create(); err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Accept defaults\": c.Globals.Flags.AcceptDefaults,\n\t\t\t\"Auto-yes\":        c.Globals.Flags.AutoYes,\n\t\t\t\"Non-interactive\": c.Globals.Flags.NonInteractive,\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": serviceVersion,\n\t\t})\n\t\treturn err\n\t}\n\n\tif err := sr.kvStores.Create(); err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Accept defaults\": c.Globals.Flags.AcceptDefaults,\n\t\t\t\"Auto-yes\":        c.Globals.Flags.AutoYes,\n\t\t\t\"Non-interactive\": c.Globals.Flags.NonInteractive,\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": serviceVersion,\n\t\t})\n\t\treturn err\n\t}\n\n\tif err := sr.secretStores.Create(); err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Accept defaults\": c.Globals.Flags.AcceptDefaults,\n\t\t\t\"Auto-yes\":        c.Globals.Flags.AutoYes,\n\t\t\t\"Non-interactive\": c.Globals.Flags.NonInteractive,\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": serviceVersion,\n\t\t})\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\n// ProcessService updates the service version comment and then activates the\n// service version.\nfunc (c *DeployCommand) ProcessService(serviceID string, serviceVersion int, spinner text.Spinner) (err error) {\n\tdefer func() {\n\t\tevent := beacon.Event{\n\t\t\tName: \"activate\",\n\t\t}\n\t\tif err != nil {\n\t\t\tevent.Status = beacon.StatusFail\n\t\t} else {\n\t\t\tevent.Status = beacon.StatusSuccess\n\t\t}\n\t\tbErr := beacon.Notify(c.Globals, serviceID, event)\n\t\tif bErr != nil {\n\t\t\tc.Globals.ErrLog.Add(bErr)\n\t\t}\n\t}()\n\n\tif c.Comment.WasSet {\n\t\t_, err = c.Globals.APIClient.UpdateVersion(context.TODO(), &fastly.UpdateVersionInput{\n\t\t\tServiceID:      serviceID,\n\t\t\tServiceVersion: serviceVersion,\n\t\t\tComment:        &c.Comment.Value,\n\t\t})\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"error setting comment for service version %d: %w\", serviceVersion, err)\n\t\t}\n\t}\n\n\treturn spinner.Process(fmt.Sprintf(\"Activating service (version %d)\", serviceVersion), func(_ *text.SpinnerWrapper) error {\n\t\t_, err = c.Globals.APIClient.ActivateVersion(context.TODO(), &fastly.ActivateVersionInput{\n\t\t\tServiceID:      serviceID,\n\t\t\tServiceVersion: serviceVersion,\n\t\t})\n\t\tif err != nil {\n\t\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\t\"Service ID\":      serviceID,\n\t\t\t\t\"Service Version\": serviceVersion,\n\t\t\t})\n\t\t\treturn fmt.Errorf(\"error activating version: %w\", err)\n\t\t}\n\t\treturn nil\n\t})\n}\n\n// GetServiceURL returns the service URL.\nfunc (c *DeployCommand) GetServiceURL(serviceID string, serviceVersion int) (string, error) {\n\tlatestDomains, err := c.Globals.APIClient.ListDomains(context.TODO(), &fastly.ListDomainsInput{\n\t\tServiceID:      serviceID,\n\t\tServiceVersion: serviceVersion,\n\t})\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tif len(latestDomains) == 0 {\n\t\treturn \"\", nil\n\t}\n\tname := fastly.ToValue(latestDomains[0].Name)\n\tif segs := strings.Split(name, \"*.\"); len(segs) > 1 {\n\t\tname = segs[1]\n\t}\n\treturn fmt.Sprintf(\"https://%s\", name), nil\n}\n\n// checkingServiceAvailability pings the service URL until either there is a\n// non-500 (or whatever status code is configured by the user) or if the\n// configured timeout is reached.\nfunc checkingServiceAvailability(\n\tserviceURL string,\n\tspinner text.Spinner,\n\tc *DeployCommand,\n) (status int, err error) {\n\tremediation := \"The service has been successfully deployed and activated, but the service 'availability' check %s (we were looking for a %s but the last status code response was: %d). If using a custom domain, please be sure to check your DNS settings. Otherwise, your application might be taking longer than usual to deploy across our global network. Please continue to check the service URL and if still unavailable please contact Fastly support.\"\n\n\tdur := time.Duration(c.StatusCheckTimeout) * time.Second\n\tend := time.Now().Add(dur)\n\ttimeout := time.After(dur)\n\tticker := time.NewTicker(1 * time.Second)\n\tdefer func() { ticker.Stop() }()\n\n\terr = spinner.Start()\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tmsg := \"Checking service availability\"\n\tspinner.Message(msg + generateTimeout(time.Until(end)))\n\n\texpected := \"non-500 status code\"\n\tif validStatusCodeRange(c.StatusCheckCode) {\n\t\texpected = fmt.Sprintf(\"%d status code\", c.StatusCheckCode)\n\t}\n\n\t// Keep trying until we're timed out, got a result or got an error\n\tfor {\n\t\tselect {\n\t\tcase <-timeout:\n\t\t\terr := errors.New(\"timeout: service not yet available\")\n\t\t\treturnedStatus := fmt.Sprintf(\" (status: %d)\", status)\n\t\t\tspinner.StopFailMessage(msg + returnedStatus)\n\t\t\tspinErr := spinner.StopFail()\n\t\t\tif spinErr != nil {\n\t\t\t\treturn status, fmt.Errorf(text.SpinnerErrWrapper, spinErr, err)\n\t\t\t}\n\t\t\treturn status, fsterr.RemediationError{\n\t\t\t\tInner:       err,\n\t\t\t\tRemediation: fmt.Sprintf(remediation, \"timed out\", expected, status),\n\t\t\t}\n\t\tcase t := <-ticker.C:\n\t\t\tvar (\n\t\t\t\tok  bool\n\t\t\t\terr error\n\t\t\t)\n\t\t\t// We overwrite the `status` variable in the parent scope (defined in the\n\t\t\t// return arguments list) so it can be used as part of both the timeout\n\t\t\t// and success scenarios.\n\t\t\tok, status, err = pingServiceURL(serviceURL, c.Globals.HTTPClient, c.StatusCheckCode, c.Globals.Flags.Debug)\n\t\t\tif err != nil {\n\t\t\t\terr := fmt.Errorf(\"failed to ping service URL: %w\", err)\n\t\t\t\treturnedStatus := fmt.Sprintf(\" (status: %d)\", status)\n\t\t\t\tspinner.StopFailMessage(msg + returnedStatus)\n\t\t\t\tspinErr := spinner.StopFail()\n\t\t\t\tif spinErr != nil {\n\t\t\t\t\treturn status, fmt.Errorf(text.SpinnerErrWrapper, spinErr, err)\n\t\t\t\t}\n\t\t\t\treturn status, fsterr.RemediationError{\n\t\t\t\t\tInner:       err,\n\t\t\t\t\tRemediation: fmt.Sprintf(remediation, \"failed\", expected, status),\n\t\t\t\t}\n\t\t\t}\n\t\t\tif ok {\n\t\t\t\treturnedStatus := fmt.Sprintf(\" (status: %d)\", status)\n\t\t\t\tspinner.StopMessage(msg + returnedStatus)\n\t\t\t\treturn status, spinner.Stop()\n\t\t\t}\n\t\t\t// Service not available, and no error, so jump back to top of loop\n\t\t\tspinner.Message(msg + generateTimeout(end.Sub(t)))\n\t\t}\n\t}\n}\n\n// generateTimeout inserts a dynamically generated message on each tick.\n// It notifies the user what's happening and how long is left on the timer.\nfunc generateTimeout(d time.Duration) string {\n\tremaining := fmt.Sprintf(\"timeout: %v\", d.Round(time.Second))\n\treturn fmt.Sprintf(\" (app deploying across Fastly's global network | %s)...\", remaining)\n}\n\n// pingServiceURL indicates if the service returned a non-5xx response (or\n// whatever the user defined with --status-check-code), which should help\n// signify if the service is generally available.\nfunc pingServiceURL(serviceURL string, httpClient api.HTTPClient, expectedStatusCode int, debugMode bool) (ok bool, status int, err error) {\n\treq, err := http.NewRequest(http.MethodGet, serviceURL, nil)\n\tif err != nil {\n\t\treturn false, 0, err\n\t}\n\n\t// gosec flagged this:\n\t// G107 (CWE-88): Potential HTTP request made with variable url\n\t// Disabling as we trust the source of the variable.\n\t// #nosec\n\tif debugMode {\n\t\tdebug.DumpHTTPRequest(req)\n\t}\n\tresp, err := httpClient.Do(req)\n\tif debugMode {\n\t\tdebug.DumpHTTPResponse(resp)\n\t}\n\tif err != nil {\n\t\treturn false, 0, err\n\t}\n\tdefer func() {\n\t\t_ = resp.Body.Close()\n\t}()\n\n\t// We check for the user's defined status code expectation.\n\t// Otherwise we'll default to checking for a non-500.\n\tif validStatusCodeRange(expectedStatusCode) && resp.StatusCode == expectedStatusCode {\n\t\treturn true, resp.StatusCode, nil\n\t} else if resp.StatusCode < http.StatusInternalServerError {\n\t\treturn true, resp.StatusCode, nil\n\t}\n\treturn false, resp.StatusCode, nil\n}\n\n// ExistingServiceVersion returns a Service Version for an existing service.\n// If the current service version is active or locked, we clone the version.\nfunc (c *DeployCommand) ExistingServiceVersion(serviceID string, out io.Writer) (*fastly.Version, error) {\n\tvar (\n\t\terr            error\n\t\tserviceVersion *fastly.Version\n\t)\n\n\t// There is a scenario where a user already has a Service ID within the\n\t// fastly.toml manifest but they want to deploy their project to a different\n\t// service (e.g. deploy to a staging service).\n\t//\n\t// In this scenario we end up here because we have found a Service ID in the\n\t// manifest but if the --service-name flag is set, then we need to ignore\n\t// what's set in the manifest and instead identify the ID of the service\n\t// name the user has provided.\n\tif c.ServiceName.WasSet {\n\t\tserviceID, err = c.ServiceName.Parse(c.Globals.APIClient)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tserviceVersion, err = c.ServiceVersion.Parse(serviceID, c.Globals.APIClient)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Package path\": c.PackagePath,\n\t\t\t\"Service ID\":   serviceID,\n\t\t})\n\t\treturn nil, err\n\t}\n\n\tserviceVersionNumber := fastly.ToValue(serviceVersion.Number)\n\n\t// Validate that we're dealing with a Compute 'wasm' service and not a\n\t// VCL service, for which we cannot upload a wasm package format to.\n\tserviceDetails, err := c.Globals.APIClient.GetServiceDetails(context.TODO(), &fastly.GetServiceDetailsInput{ServiceID: serviceID})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": serviceVersionNumber,\n\t\t})\n\t\treturn serviceVersion, err\n\t}\n\tserviceType := fastly.ToValue(serviceDetails.Type)\n\tif serviceType != \"wasm\" {\n\t\tc.Globals.ErrLog.AddWithContext(fmt.Errorf(\"error: invalid service type: '%s'\", serviceType), map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": serviceVersionNumber,\n\t\t\t\"Service Type\":    serviceType,\n\t\t})\n\t\treturn serviceVersion, fsterr.RemediationError{\n\t\t\tInner:       fmt.Errorf(\"invalid service type: %s\", serviceType),\n\t\t\tRemediation: \"Ensure the provided Service ID is associated with a 'Wasm' Fastly Service and not a 'VCL' Fastly service.\",\n\t\t}\n\t}\n\n\terr = c.CompareLocalRemotePackage(serviceID, serviceVersionNumber)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Package path\":    c.PackagePath,\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": serviceVersionNumber,\n\t\t})\n\t\treturn serviceVersion, err\n\t}\n\n\t// Unlike other CLI commands that are a direct mapping to an API endpoint,\n\t// the compute deploy command is a composite of behaviours, and so as we\n\t// already automatically activate a version we should autoclone without\n\t// requiring the user to explicitly provide an --autoclone flag.\n\tif fastly.ToValue(serviceVersion.Active) || fastly.ToValue(serviceVersion.Locked) {\n\t\tclonedVersion, err := c.Globals.APIClient.CloneVersion(context.TODO(), &fastly.CloneVersionInput{\n\t\t\tServiceID:      serviceID,\n\t\t\tServiceVersion: serviceVersionNumber,\n\t\t})\n\t\tif err != nil {\n\t\t\terrLogService(c.Globals.ErrLog, err, serviceID, serviceVersionNumber)\n\t\t\treturn serviceVersion, fmt.Errorf(\"error cloning service version: %w\", err)\n\t\t}\n\t\tif c.Globals.Verbose() {\n\t\t\tmsg := \"Service version %d is not editable, so it was automatically cloned. Now operating on version %d.\\n\\n\"\n\t\t\tformat := fmt.Sprintf(msg, serviceVersionNumber, fastly.ToValue(clonedVersion.Number))\n\t\t\ttext.Output(out, format)\n\t\t}\n\t\tserviceVersion = clonedVersion\n\t}\n\n\treturn serviceVersion, nil\n}\n\nfunc monitorSignals(signalCh chan os.Signal, noExistingService bool, out io.Writer, undoStack *undo.Stack, spinner text.Spinner) {\n\t<-signalCh\n\tsignal.Stop(signalCh)\n\tspinner.StopFailMessage(\"Signal received to interrupt/terminate the Fastly CLI process\")\n\t_ = spinner.StopFail()\n\ttext.Important(out, \"\\n\\nThe Fastly CLI process will be terminated after any clean-up tasks have been processed\")\n\tif noExistingService {\n\t\tundoStack.Unwind(out)\n\t}\n\tos.Exit(1)\n}\n"
  },
  {
    "path": "pkg/commands/compute/deploy_test.go",
    "content": "package compute_test\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/app\"\n\t\"github.com/fastly/cli/pkg/commands/compute\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/manifest\"\n\t\"github.com/fastly/cli/pkg/mock\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n\t\"github.com/fastly/cli/pkg/threadsafe\"\n)\n\n// NOTE: Some tests don't provide a Service ID via any mechanism (e.g. flag\n// or manifest) and if one is provided the test will fail due to a specific\n// API call not being mocked. Be careful not to add a Service ID to all tests\n// without first checking whether the Service ID is expected as the user flow\n// for when no Service ID is provided is to create a new service.\n//\n// Additionally, stdin can be mocked in one of two ways...\n//\n// 1. Provide a single value.\n// 2. Provide multiple values (one for each prompt expected).\n//\n// In the first case, the first prompt given to the user will get the value you\n// defined in the testcase.stdin field, all other prompts will get an empty\n// value. This has worked fine for the most part as the prompts have\n// historically provided default values when an empty value is encountered.\n//\n// The second case is to address running the test code successfully as the\n// business logic has changed over time to now 'require' values to be provided\n// for some prompts, this means an empty string will break the test flow. If\n// that's what you're encountering, then you should add multiple values for the\n// testcase.stdin field so that there is a value provided for every prompt your\n// testcase user flow expects to encounter.\nfunc TestDeploy(t *testing.T) {\n\tif os.Getenv(\"TEST_COMPUTE_DEPLOY\") == \"\" {\n\t\tt.Log(\"skipping test\")\n\t\tt.Skip(\"Set TEST_COMPUTE_DEPLOY to run this test\")\n\t}\n\n\t// We're going to chdir to a deploy environment,\n\t// so save the PWD to return to, afterwards.\n\tpwd, err := os.Getwd()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t// Create test environment\n\trootdir := testutil.NewEnv(testutil.EnvOpts{\n\t\tT: t,\n\t\tCopy: []testutil.FileIO{\n\t\t\t{\n\t\t\t\tSrc: filepath.Join(\"testdata\", \"deploy\", \"pkg\", \"package.tar.gz\"),\n\t\t\t\tDst: filepath.Join(\"pkg\", \"package.tar.gz\"),\n\t\t\t},\n\t\t},\n\t\tWrite: []testutil.FileIO{\n\t\t\t{\n\t\t\t\tSrc: \"This is my data for the KV Store 'store_one' baz field.\",\n\t\t\t\tDst: \"kv_store_one_baz.txt\",\n\t\t\t},\n\t\t},\n\t})\n\tdefer os.RemoveAll(rootdir)\n\n\t// Before running the test, chdir into the build environment.\n\t// When we're done, chdir back to our original location.\n\t// This is so we can reliably copy the testdata/ fixtures.\n\tif err := os.Chdir(rootdir); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer func() {\n\t\t_ = os.Chdir(pwd)\n\t}()\n\n\toriginalPackageSizeLimit := compute.MaxPackageSize\n\targs := testutil.SplitArgs\n\tscenarios := []struct {\n\t\tapi                  mock.API\n\t\targs                 []string\n\t\tdontWantOutput       []string\n\t\thttpClientRes        []*http.Response\n\t\thttpClientErr        []error\n\t\tmanifest             string\n\t\tname                 string\n\t\tnoManifest           bool\n\t\treduceSizeLimit      bool\n\t\tstdin                []string\n\t\twantError            string\n\t\twantRemediationError string\n\t\twantOutput           []string\n\t}{\n\t\t{\n\t\t\tname:                 \"no fastly.toml manifest\",\n\t\t\targs:                 args(\"compute deploy --token 123\"),\n\t\t\twantError:            \"error reading fastly.toml: file not found\",\n\t\t\twantRemediationError: errors.ComputeInitRemediation,\n\t\t\tnoManifest:           true,\n\t\t},\n\t\t{\n\t\t\t// If no Service ID defined via flag or manifest, then the expectation is\n\t\t\t// for the service to be created via the API and for the returned ID to\n\t\t\t// be stored into the manifest.\n\t\t\t//\n\t\t\t// Additionally it validates that the specified path (files generated by\n\t\t\t// the testutil.NewEnv()) cause no issues.\n\t\t\tname: \"path with no service ID\",\n\t\t\targs: args(\"compute deploy --token 123 -v --package pkg/package.tar.gz\"),\n\t\t\tapi: mock.API{\n\t\t\t\tActivateVersionFn: activateVersionOk,\n\t\t\t\tCreateBackendFn:   createBackendOK,\n\t\t\t\tCreateDomainFn:    createDomainOK,\n\t\t\t\tCreateServiceFn:   createServiceOK,\n\t\t\t\tGetPackageFn:      getPackageOk,\n\t\t\t\tListDomainsFn:     listDomainsOk,\n\t\t\t\tUpdatePackageFn:   updatePackageOk,\n\t\t\t},\n\t\t\thttpClientRes: []*http.Response{\n\t\t\t\tmock.NewHTTPResponse(http.StatusNoContent, nil, nil),\n\t\t\t\tmock.NewHTTPResponse(http.StatusOK, nil, io.NopCloser(strings.NewReader(\"success\"))),\n\t\t\t},\n\t\t\thttpClientErr: []error{\n\t\t\t\tnil,\n\t\t\t\tnil,\n\t\t\t},\n\t\t\tstdin: []string{\n\t\t\t\t\"Y\", // when prompted to create a new service\n\t\t\t},\n\t\t\twantOutput: []string{\n\t\t\t\t\"Deployed package (service 12345, version 1)\",\n\t\t\t},\n\t\t},\n\t\t// Same validation as above with the exception that we use the default path\n\t\t// parsing logic (i.e. we don't explicitly pass a path via `-p` flag).\n\t\t{\n\t\t\tname: \"empty service ID\",\n\t\t\targs: args(\"compute deploy --token 123 -v\"),\n\t\t\tapi: mock.API{\n\t\t\t\tActivateVersionFn: activateVersionOk,\n\t\t\t\tCreateBackendFn:   createBackendOK,\n\t\t\t\tCreateDomainFn:    createDomainOK,\n\t\t\t\tCreateServiceFn:   createServiceOK,\n\t\t\t\tGetPackageFn:      getPackageOk,\n\t\t\t\tListDomainsFn:     listDomainsOk,\n\t\t\t\tUpdatePackageFn:   updatePackageOk,\n\t\t\t},\n\t\t\thttpClientRes: []*http.Response{\n\t\t\t\tmock.NewHTTPResponse(http.StatusNoContent, nil, nil),\n\t\t\t\tmock.NewHTTPResponse(http.StatusOK, nil, io.NopCloser(strings.NewReader(\"success\"))),\n\t\t\t},\n\t\t\thttpClientErr: []error{\n\t\t\t\tnil,\n\t\t\t\tnil,\n\t\t\t},\n\t\t\tstdin: []string{\n\t\t\t\t\"Y\", // when prompted to create a new service\n\t\t\t},\n\t\t\twantOutput: []string{\n\t\t\t\t\"Deployed package (service 12345, version 1)\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"list versions error\",\n\t\t\targs: args(\"compute deploy --service-id 123 --token 123\"),\n\t\t\tapi: mock.API{\n\t\t\t\tGetServiceFn:        getServiceOK,\n\t\t\t\tGetServiceDetailsFn: getServiceDetailsWasmNoActive,\n\t\t\t\tListVersionsFn:      testutil.ListVersionsError,\n\t\t\t},\n\t\t\twantError: fmt.Sprintf(\"error listing service versions: %s\", testutil.Err.Error()),\n\t\t},\n\t\t{\n\t\t\tname: \"service version is active, clone version error\",\n\t\t\targs: args(\"compute deploy --service-id 123 --token 123 --version 1\"),\n\t\t\tapi: mock.API{\n\t\t\t\tCloneVersionFn:      testutil.CloneVersionError,\n\t\t\t\tGetPackageFn:        getPackageOk,\n\t\t\t\tGetServiceDetailsFn: getServiceDetailsWasm,\n\t\t\t\tGetVersionFn:        testutil.GetVersion,\n\t\t\t\tListDomainsFn:       listDomainsOk,\n\t\t\t},\n\t\t\twantError: fmt.Sprintf(\"error cloning service version: %s\", testutil.Err.Error()),\n\t\t},\n\t\t{\n\t\t\tname: \"service version is locked, clone version error\",\n\t\t\targs: args(\"compute deploy --service-id 123 --token 123 --version 2\"),\n\t\t\tapi: mock.API{\n\t\t\t\tCloneVersionFn:      testutil.CloneVersionError,\n\t\t\t\tGetPackageFn:        getPackageOk,\n\t\t\t\tGetServiceDetailsFn: getServiceDetailsWasm,\n\t\t\t\tGetVersionFn:        testutil.GetVersion,\n\t\t\t\tListDomainsFn:       listDomainsOk,\n\t\t\t},\n\t\t\twantError: fmt.Sprintf(\"error cloning service version: %s\", testutil.Err.Error()),\n\t\t},\n\t\t{\n\t\t\tname: \"list domains error\",\n\t\t\targs: args(\"compute deploy --service-id 123 --token 123\"),\n\t\t\tapi: mock.API{\n\t\t\t\tCloneVersionFn:      testutil.CloneVersionResult(4),\n\t\t\t\tGetPackageFn:        getPackageOk,\n\t\t\t\tGetServiceDetailsFn: getServiceDetailsWasm,\n\t\t\t\tGetServiceFn:        getServiceOK,\n\t\t\t\tListDomainsFn:       listDomainsError,\n\t\t\t\tListVersionsFn:      testutil.ListVersions,\n\t\t\t},\n\t\t\twantError: fmt.Sprintf(\"error fetching service domains: %s\", testutil.Err.Error()),\n\t\t},\n\t\t{\n\t\t\tname:                 \"package size too large\",\n\t\t\targs:                 args(\"compute deploy --package pkg/package.tar.gz --token 123\"),\n\t\t\treduceSizeLimit:      true,\n\t\t\twantError:            \"package size is too large\",\n\t\t\twantRemediationError: errors.PackageSizeRemediation,\n\t\t},\n\t\t// The following test doesn't just validate the package API error behaviour\n\t\t// but as a side effect it validates that when deleting the created\n\t\t// service, the Service ID is also cleared out from the manifest.\n\t\t{\n\t\t\tname: \"package API error\",\n\t\t\targs: args(\"compute deploy --token 123\"),\n\t\t\tapi: mock.API{\n\t\t\t\tCreateBackendFn: createBackendOK,\n\t\t\t\tCreateDomainFn:  createDomainOK,\n\t\t\t\tCreateServiceFn: createServiceOK,\n\t\t\t\tDeleteBackendFn: deleteBackendOK,\n\t\t\t\tDeleteDomainFn:  deleteDomainOK,\n\t\t\t\tDeleteServiceFn: deleteServiceOK,\n\t\t\t\tGetPackageFn:    getPackageOk,\n\t\t\t\tListDomainsFn:   listDomainsOk,\n\t\t\t\tUpdatePackageFn: updatePackageError,\n\t\t\t},\n\t\t\tstdin: []string{\n\t\t\t\t\"Y\", // when prompted to create a new service\n\t\t\t},\n\t\t\twantError: fmt.Sprintf(\"error uploading package: %s\", testutil.Err.Error()),\n\t\t\twantOutput: []string{\n\t\t\t\t\"Uploading package\",\n\t\t\t},\n\t\t},\n\t\t// The following test doesn't provide a Service ID by either a flag nor the\n\t\t// manifest, so this will result in the deploy script attempting to create\n\t\t// a new service. We mock the API call to fail, and we expect to see a\n\t\t// relevant error message related to that error.\n\t\t{\n\t\t\tname: \"service create error\",\n\t\t\targs: args(\"compute deploy --token 123\"),\n\t\t\tapi: mock.API{\n\t\t\t\tCreateServiceFn: createServiceError,\n\t\t\t\tDeleteServiceFn: deleteServiceOK,\n\t\t\t},\n\t\t\tstdin: []string{\n\t\t\t\t\"Y\", // when prompted to create a new service\n\t\t\t},\n\t\t\twantError: fmt.Sprintf(\"error creating service: %s\", testutil.Err.Error()),\n\t\t},\n\t\t{\n\t\t\tname: \"service create success\",\n\t\t\targs: args(\"compute deploy --token 123\"),\n\t\t\tapi: mock.API{\n\t\t\t\tActivateVersionFn: activateVersionOk,\n\t\t\t\tCreateBackendFn:   createBackendOK,\n\t\t\t\tCreateServiceFn:   createServiceOK,\n\t\t\t\tGetPackageFn:      getPackageOk,\n\t\t\t\tListDomainsFn:     listDomainsOk,\n\t\t\t\tUpdatePackageFn:   updatePackageOk,\n\t\t\t},\n\t\t\thttpClientRes: []*http.Response{\n\t\t\t\tmock.NewHTTPResponse(http.StatusNoContent, nil, nil),\n\t\t\t\tmock.NewHTTPResponse(http.StatusOK, nil, io.NopCloser(strings.NewReader(\"success\"))),\n\t\t\t},\n\t\t\thttpClientErr: []error{\n\t\t\t\tnil,\n\t\t\t\tnil,\n\t\t\t},\n\t\t\tstdin: []string{\n\t\t\t\t\"Y\", // when prompted to create a new service\n\t\t\t},\n\t\t\twantOutput: []string{\n\t\t\t\t\"Creating service\",\n\t\t\t},\n\t\t},\n\t\t// The following test doesn't provide a Service ID by either a flag nor the\n\t\t// manifest, so this will result in the deploy script attempting to create\n\t\t// a new service. We mock the service creation to be successful while we\n\t\t// mock the domain API call to fail, and we expect to see a relevant error\n\t\t// message related to that error.\n\t\t{\n\t\t\tname: \"service domain error\",\n\t\t\targs: args(\"compute deploy --token 123\"),\n\t\t\tapi: mock.API{\n\t\t\t\tCreateDomainFn:  createDomainError,\n\t\t\t\tCreateServiceFn: createServiceOK,\n\t\t\t\tDeleteDomainFn:  deleteDomainOK,\n\t\t\t\tDeleteServiceFn: deleteServiceOK,\n\t\t\t\tListDomainsFn:   listDomainsNone,\n\t\t\t},\n\t\t\tstdin: []string{\n\t\t\t\t\"Y\", // when prompted to create a new service\n\t\t\t},\n\t\t\twantError: fmt.Sprintf(\"error creating domain: %s\", testutil.Err.Error()),\n\t\t\twantOutput: []string{\n\t\t\t\t\"Creating service\",\n\t\t\t},\n\t\t},\n\t\t// The following test doesn't provide a Service ID by either a flag nor the\n\t\t// manifest, so this will result in the deploy script attempting to create\n\t\t// a new service. We mock the service creation to be successful while we\n\t\t// mock the backend API call to succeed but to return an unexpected empty\n\t\t// list of Backends.\n\t\t{\n\t\t\tname: \"service backend error\",\n\t\t\targs: args(\"compute deploy --token 123\"),\n\t\t\tapi: mock.API{\n\t\t\t\tCreateBackendFn: createBackendError,\n\t\t\t\tCreateDomainFn:  createDomainOK,\n\t\t\t\tCreateServiceFn: createServiceOK,\n\t\t\t\tDeleteBackendFn: deleteBackendOK,\n\t\t\t\tDeleteDomainFn:  deleteDomainOK,\n\t\t\t\tDeleteServiceFn: deleteServiceOK,\n\t\t\t\tListDomainsFn:   listDomainsOk,\n\t\t\t},\n\t\t\tstdin: []string{\n\t\t\t\t\"Y\", // when prompted to create a new service\n\t\t\t},\n\t\t\twantError: fmt.Sprintf(\"error configuring the service: %s\", testutil.Err.Error()),\n\t\t\twantOutput: []string{\n\t\t\t\t\"Creating service\",\n\t\t\t},\n\t\t\tdontWantOutput: []string{\n\t\t\t\t\"Creating domain '\",\n\t\t\t},\n\t\t},\n\t\t// The following test validates that the undoStack is executed as expected\n\t\t// e.g. the service is deleted when there is an error during the flow.\n\t\t// This only happens for new service flows.\n\t\t{\n\t\t\tname: \"undo stack is executed\",\n\t\t\targs: args(\"compute deploy --token 123\"),\n\t\t\tapi: mock.API{\n\t\t\t\tCreateBackendFn: createBackendError,\n\t\t\t\tCreateDomainFn:  createDomainOK,\n\t\t\t\tCreateServiceFn: createServiceOK,\n\t\t\t\tDeleteServiceFn: deleteServiceOK,\n\t\t\t\tListDomainsFn:   listDomainsNone,\n\t\t\t},\n\t\t\tstdin: []string{\n\t\t\t\t\"Y\", // when prompted to create a new service\n\t\t\t},\n\t\t\twantError: fmt.Sprintf(\"error configuring the service: %s\", testutil.Err.Error()),\n\t\t\twantOutput: []string{\n\t\t\t\t\"Cleaning up service\",\n\t\t\t\t\"Removing Service ID from fastly.toml\",\n\t\t\t\t\"Cleanup complete\",\n\t\t\t},\n\t\t},\n\t\t// The following test is the opposite to the above test.\n\t\t// It validates that we don't delete an existing service on-error.\n\t\t{\n\t\t\tname: \"undo stack is not executed for errors with existing services\",\n\t\t\targs: args(\"compute deploy --service-id 123 --token 123\"),\n\t\t\tapi: mock.API{\n\t\t\t\tActivateVersionFn:   activateVersionError,\n\t\t\t\tCloneVersionFn:      testutil.CloneVersionResult(4),\n\t\t\t\tGetPackageFn:        getPackageOk,\n\t\t\t\tGetServiceDetailsFn: getServiceDetailsWasm,\n\t\t\t\tGetServiceFn:        getServiceOK,\n\t\t\t\tListDomainsFn:       listDomainsOk,\n\t\t\t\tListVersionsFn:      testutil.ListVersions,\n\t\t\t\tUpdatePackageFn:     updatePackageOk,\n\t\t\t},\n\t\t\tdontWantOutput: []string{\n\t\t\t\t\"Cleaning up service\",\n\t\t\t\t\"Removing Service ID from fastly.toml\",\n\t\t\t\t\"Cleanup complete\",\n\t\t\t},\n\t\t\twantError: \"error activating version: test error\",\n\t\t\twantOutput: []string{\n\t\t\t\t\"Uploading package\",\n\t\t\t\t\"Activating service\",\n\t\t\t},\n\t\t},\n\t\t// The following test validates that if a package contains code that has\n\t\t// not changed since the last deploy, then the deployment is skipped.\n\t\t{\n\t\t\tname: \"identical package\",\n\t\t\targs: args(\"compute deploy --service-id 123 --token 123\"),\n\t\t\tapi: mock.API{\n\t\t\t\tCloneVersionFn:      testutil.CloneVersionResult(4),\n\t\t\t\tGetPackageFn:        getPackageIdentical,\n\t\t\t\tGetServiceDetailsFn: getServiceDetailsWasm,\n\t\t\t\tGetServiceFn:        getServiceOK,\n\t\t\t\tListDomainsFn:       listDomainsOk,\n\t\t\t\tListVersionsFn:      testutil.ListVersions,\n\t\t\t},\n\t\t\twantOutput: []string{\n\t\t\t\t\"Skipping package deployment\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"success with existing service\",\n\t\t\targs: args(\"compute deploy --service-id 123 --token 123\"),\n\t\t\tapi: mock.API{\n\t\t\t\tActivateVersionFn:   activateVersionOk,\n\t\t\t\tCloneVersionFn:      testutil.CloneVersionResult(4),\n\t\t\t\tGetPackageFn:        getPackageOk,\n\t\t\t\tGetServiceDetailsFn: getServiceDetailsWasm,\n\t\t\t\tGetServiceFn:        getServiceOK,\n\t\t\t\tListDomainsFn:       listDomainsOk,\n\t\t\t\tListVersionsFn:      testutil.ListVersions,\n\t\t\t\tUpdatePackageFn:     updatePackageOk,\n\t\t\t},\n\t\t\thttpClientRes: []*http.Response{\n\t\t\t\tmock.NewHTTPResponse(http.StatusNoContent, nil, nil),\n\t\t\t\tmock.NewHTTPResponse(http.StatusOK, nil, io.NopCloser(strings.NewReader(\"success\"))),\n\t\t\t},\n\t\t\thttpClientErr: []error{\n\t\t\t\tnil,\n\t\t\t\tnil,\n\t\t\t},\n\t\t\twantOutput: []string{\n\t\t\t\t\"Uploading package\",\n\t\t\t\t\"Activating service\",\n\t\t\t\t\"Manage this service at:\",\n\t\t\t\t\"https://manage.fastly.com/configure/services/123\",\n\t\t\t\t\"View this service at:\",\n\t\t\t\t\"https://directly-careful-coyote.edgecompute.app\",\n\t\t\t\t\"Deployed package (service 123, version 4)\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"success with path\",\n\t\t\targs: args(\"compute deploy --service-id 123 --token 123 --package pkg/package.tar.gz --version 3\"),\n\t\t\tapi: mock.API{\n\t\t\t\tActivateVersionFn:   activateVersionOk,\n\t\t\t\tGetPackageFn:        getPackageOk,\n\t\t\t\tGetServiceDetailsFn: getServiceDetailsWasm,\n\t\t\t\tGetServiceFn:        getServiceOK,\n\t\t\t\tGetVersionFn:        testutil.GetVersion,\n\t\t\t\tListDomainsFn:       listDomainsOk,\n\t\t\t\tUpdatePackageFn:     updatePackageOk,\n\t\t\t},\n\t\t\thttpClientRes: []*http.Response{\n\t\t\t\tmock.NewHTTPResponse(http.StatusNoContent, nil, nil),\n\t\t\t\tmock.NewHTTPResponse(http.StatusOK, nil, io.NopCloser(strings.NewReader(\"success\"))),\n\t\t\t},\n\t\t\thttpClientErr: []error{\n\t\t\t\tnil,\n\t\t\t\tnil,\n\t\t\t},\n\t\t\twantOutput: []string{\n\t\t\t\t\"Uploading package\",\n\t\t\t\t\"Activating service\",\n\t\t\t\t\"Manage this service at:\",\n\t\t\t\t\"https://manage.fastly.com/configure/services/123\",\n\t\t\t\t\"View this service at:\",\n\t\t\t\t\"https://directly-careful-coyote.edgecompute.app\",\n\t\t\t\t\"Deployed package (service 123, version 3)\",\n\t\t\t},\n\t\t},\n\t\t// NOTE: The following test ensures that if the user runs the CLI from a\n\t\t// directory that isn't a Compute project directory (i.e. it has no manifest\n\t\t// file present) then the deploy command should try to locate a manifest\n\t\t// inside the given package tar.gz archive.\n\t\t{\n\t\t\tname: \"success with path called from non project directory\",\n\t\t\targs: args(\"compute deploy --service-id 123 --token 123 --package pkg/package.tar.gz --version 3 --verbose\"),\n\t\t\tapi: mock.API{\n\t\t\t\tActivateVersionFn:   activateVersionOk,\n\t\t\t\tGetVersionFn:        testutil.GetVersion,\n\t\t\t\tGetPackageFn:        getPackageOk,\n\t\t\t\tGetServiceDetailsFn: getServiceDetailsWasm,\n\t\t\t\tGetServiceFn:        getServiceOK,\n\t\t\t\tListDomainsFn:       listDomainsOk,\n\t\t\t\tUpdatePackageFn:     updatePackageOk,\n\t\t\t},\n\t\t\thttpClientRes: []*http.Response{\n\t\t\t\tmock.NewHTTPResponse(http.StatusNoContent, nil, nil),\n\t\t\t\tmock.NewHTTPResponse(http.StatusOK, nil, io.NopCloser(strings.NewReader(\"success\"))),\n\t\t\t},\n\t\t\thttpClientErr: []error{\n\t\t\t\tnil,\n\t\t\t\tnil,\n\t\t\t},\n\t\t\tnoManifest: true,\n\t\t\twantOutput: []string{\n\t\t\t\t\"Using fastly.toml within --package archive:\",\n\t\t\t\t\"Uploading package\",\n\t\t\t\t\"Activating service\",\n\t\t\t\t\"Manage this service at:\",\n\t\t\t\t\"https://manage.fastly.com/configure/services/123\",\n\t\t\t\t\"View this service at:\",\n\t\t\t\t\"https://directly-careful-coyote.edgecompute.app\",\n\t\t\t\t\"Deployed package (service 123, version 3)\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"success with inactive version\",\n\t\t\targs: args(\"compute deploy --service-id 123 --token 123 --package pkg/package.tar.gz --version 3\"),\n\t\t\tapi: mock.API{\n\t\t\t\tActivateVersionFn:   activateVersionOk,\n\t\t\t\tGetPackageFn:        getPackageOk,\n\t\t\t\tGetServiceDetailsFn: getServiceDetailsWasm,\n\t\t\t\tGetServiceFn:        getServiceOK,\n\t\t\t\tGetVersionFn:        testutil.GetVersion,\n\t\t\t\tListDomainsFn:       listDomainsOk,\n\t\t\t\tUpdatePackageFn:     updatePackageOk,\n\t\t\t},\n\t\t\thttpClientRes: []*http.Response{\n\t\t\t\tmock.NewHTTPResponse(http.StatusNoContent, nil, nil),\n\t\t\t\tmock.NewHTTPResponse(http.StatusOK, nil, io.NopCloser(strings.NewReader(\"success\"))),\n\t\t\t},\n\t\t\thttpClientErr: []error{\n\t\t\t\tnil,\n\t\t\t\tnil,\n\t\t\t},\n\t\t\twantOutput: []string{\n\t\t\t\t\"Uploading package\",\n\t\t\t\t\"Activating service\",\n\t\t\t\t\"Deployed package (service 123, version 3)\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"success with specific locked version\",\n\t\t\targs: args(\"compute deploy --service-id 123 --token 123 --package pkg/package.tar.gz --version 2\"),\n\t\t\tapi: mock.API{\n\t\t\t\tActivateVersionFn:   activateVersionOk,\n\t\t\t\tCloneVersionFn:      testutil.CloneVersionResult(4),\n\t\t\t\tGetPackageFn:        getPackageOk,\n\t\t\t\tGetServiceDetailsFn: getServiceDetailsWasm,\n\t\t\t\tGetServiceFn:        getServiceOK,\n\t\t\t\tGetVersionFn:        testutil.GetVersion,\n\t\t\t\tListDomainsFn:       listDomainsOk,\n\t\t\t\tUpdatePackageFn:     updatePackageOk,\n\t\t\t},\n\t\t\thttpClientRes: []*http.Response{\n\t\t\t\tmock.NewHTTPResponse(http.StatusNoContent, nil, nil),\n\t\t\t\tmock.NewHTTPResponse(http.StatusOK, nil, io.NopCloser(strings.NewReader(\"success\"))),\n\t\t\t},\n\t\t\thttpClientErr: []error{\n\t\t\t\tnil,\n\t\t\t\tnil,\n\t\t\t},\n\t\t\twantOutput: []string{\n\t\t\t\t\"Uploading package\",\n\t\t\t\t\"Activating service\",\n\t\t\t\t\"Deployed package (service 123, version 4)\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"success with active version\",\n\t\t\targs: args(\"compute deploy --service-id 123 --token 123 --package pkg/package.tar.gz --version active\"),\n\t\t\tapi: mock.API{\n\t\t\t\tActivateVersionFn:   activateVersionOk,\n\t\t\t\tCloneVersionFn:      testutil.CloneVersionResult(4),\n\t\t\t\tGetPackageFn:        getPackageOk,\n\t\t\t\tGetServiceDetailsFn: getServiceDetailsWasm,\n\t\t\t\tGetServiceFn:        getServiceOK,\n\t\t\t\tListDomainsFn:       listDomainsOk,\n\t\t\t\tUpdatePackageFn:     updatePackageOk,\n\t\t\t},\n\t\t\thttpClientRes: []*http.Response{\n\t\t\t\tmock.NewHTTPResponse(http.StatusNoContent, nil, nil),\n\t\t\t\tmock.NewHTTPResponse(http.StatusOK, nil, io.NopCloser(strings.NewReader(\"success\"))),\n\t\t\t},\n\t\t\thttpClientErr: []error{\n\t\t\t\tnil,\n\t\t\t\tnil,\n\t\t\t},\n\t\t\twantOutput: []string{\n\t\t\t\t\"Uploading package\",\n\t\t\t\t\"Activating service\",\n\t\t\t\t\"Deployed package (service 123, version 4)\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"success with comment\",\n\t\t\targs: args(\"compute deploy --service-id 123 --token 123 --package pkg/package.tar.gz --version 2 --comment foo\"),\n\t\t\tapi: mock.API{\n\t\t\t\tActivateVersionFn:   activateVersionOk,\n\t\t\t\tCloneVersionFn:      testutil.CloneVersionResult(4),\n\t\t\t\tGetPackageFn:        getPackageOk,\n\t\t\t\tGetServiceDetailsFn: getServiceDetailsWasm,\n\t\t\t\tGetServiceFn:        getServiceOK,\n\t\t\t\tGetVersionFn:        testutil.GetVersion,\n\t\t\t\tListDomainsFn:       listDomainsOk,\n\t\t\t\tUpdatePackageFn:     updatePackageOk,\n\t\t\t\tUpdateVersionFn:     updateVersionOk,\n\t\t\t},\n\t\t\thttpClientRes: []*http.Response{\n\t\t\t\tmock.NewHTTPResponse(http.StatusNoContent, nil, nil),\n\t\t\t\tmock.NewHTTPResponse(http.StatusOK, nil, io.NopCloser(strings.NewReader(\"success\"))),\n\t\t\t},\n\t\t\thttpClientErr: []error{\n\t\t\t\tnil,\n\t\t\t\tnil,\n\t\t\t},\n\t\t\twantOutput: []string{\n\t\t\t\t\"Uploading package\",\n\t\t\t\t\"Activating service\",\n\t\t\t\t\"Deployed package (service 123, version 4)\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"success with --no-default-domain flag for new service\",\n\t\t\targs: args(\"compute deploy --no-default-domain --token 123\"),\n\t\t\tapi: mock.API{\n\t\t\t\tActivateVersionFn: activateVersionOk,\n\t\t\t\tCreateBackendFn:   createBackendOK,\n\t\t\t\tCreateServiceFn:   createServiceOK,\n\t\t\t\tDeleteServiceFn:   deleteServiceOK,\n\t\t\t\tGetPackageFn:      getPackageOk,\n\t\t\t\tListDomainsFn:     listDomainsNone,\n\t\t\t\tUpdatePackageFn:   updatePackageOk,\n\t\t\t},\n\t\t\tstdin: []string{\n\t\t\t\t\"Y\", // when prompted to create a new service\n\t\t\t},\n\t\t\twantOutput: []string{\n\t\t\t\t\"Deployed package (service 12345, version 1)\",\n\t\t\t},\n\t\t\tdontWantOutput: []string{\n\t\t\t\t\"Creating domain\",\n\t\t\t\t\"Domain:\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"success with --no-default-domain but explicit --domain provided\",\n\t\t\targs: args(\"compute deploy --token 123 --no-default-domain --domain example.com\"),\n\t\t\tapi: mock.API{\n\t\t\t\tActivateVersionFn: activateVersionOk,\n\t\t\t\tCreateBackendFn:   createBackendOK,\n\t\t\t\tCreateDomainFn:    createDomainOK,\n\t\t\t\tCreateServiceFn:   createServiceOK,\n\t\t\t\tGetPackageFn:      getPackageOk,\n\t\t\t\tListDomainsFn:     listDomainsNone,\n\t\t\t\tUpdatePackageFn:   updatePackageOk,\n\t\t\t},\n\t\t\thttpClientRes: []*http.Response{\n\t\t\t\tmock.NewHTTPResponse(http.StatusNoContent, nil, nil),\n\t\t\t\tmock.NewHTTPResponse(http.StatusOK, nil, io.NopCloser(strings.NewReader(\"success\"))),\n\t\t\t},\n\t\t\thttpClientErr: []error{\n\t\t\t\tnil,\n\t\t\t\tnil,\n\t\t\t},\n\t\t\tstdin: []string{\n\t\t\t\t\"Y\", // when prompted to create a new service\n\t\t\t},\n\t\t\twantOutput: []string{\n\t\t\t\t\"Creating domain 'example.com'\",\n\t\t\t\t\"Deployed package (service 12345, version 1)\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"success with --no-default-domain and existing service\",\n\t\t\targs: args(\"compute deploy --service-id 123 --token 123 --no-default-domain\"),\n\t\t\tapi: mock.API{\n\t\t\t\tActivateVersionFn:   activateVersionOk,\n\t\t\t\tCloneVersionFn:      testutil.CloneVersionResult(4),\n\t\t\t\tGetPackageFn:        getPackageOk,\n\t\t\t\tGetServiceDetailsFn: getServiceDetailsWasm,\n\t\t\t\tGetServiceFn:        getServiceOK,\n\t\t\t\tListDomainsFn:       listDomainsOk,\n\t\t\t\tListVersionsFn:      testutil.ListVersions,\n\t\t\t\tUpdatePackageFn:     updatePackageOk,\n\t\t\t},\n\t\t\twantOutput: []string{\n\t\t\t\t\"Uploading package\",\n\t\t\t\t\"Activating service\",\n\t\t\t\t\"Deployed package (service 123, version 4)\",\n\t\t\t},\n\t\t\tdontWantOutput: []string{\n\t\t\t\t\"Creating domain\",\n\t\t\t},\n\t\t},\n\t\t// The following test doesn't provide a Service ID by either a flag nor the\n\t\t// manifest, so this will result in the deploy script attempting to create\n\t\t// a new service. Our fastly.toml is configured with a [setup] section so\n\t\t// we expect to see the appropriate messaging in the output.\n\t\t{\n\t\t\tname: \"success with setup.backends configuration\",\n\t\t\targs: args(\"compute deploy --token 123\"),\n\t\t\tapi: mock.API{\n\t\t\t\tActivateVersionFn: activateVersionOk,\n\t\t\t\tCreateBackendFn:   createBackendOK,\n\t\t\t\tCreateDomainFn:    createDomainOK,\n\t\t\t\tCreateServiceFn:   createServiceOK,\n\t\t\t\tDeleteServiceFn:   deleteServiceOK,\n\t\t\t\tGetPackageFn:      getPackageOk,\n\t\t\t\tListDomainsFn:     listDomainsOk,\n\t\t\t\tUpdatePackageFn:   updatePackageOk,\n\t\t\t},\n\t\t\thttpClientRes: []*http.Response{\n\t\t\t\tmock.NewHTTPResponse(http.StatusNoContent, nil, nil),\n\t\t\t\tmock.NewHTTPResponse(http.StatusOK, nil, io.NopCloser(strings.NewReader(\"success\"))),\n\t\t\t},\n\t\t\thttpClientErr: []error{\n\t\t\t\tnil,\n\t\t\t\tnil,\n\t\t\t},\n\t\t\tmanifest: `\n\t\t\tname = \"package\"\n\t\t\tmanifest_version = 2\n\t\t\tlanguage = \"rust\"\n\n\t\t\t[setup.backends.backend_name]\n\t\t\tprompt = \"Backend 1\"\n\t\t\taddress = \"developer.fastly.com\"\n\t\t\tport = 443\n\t\t\t[setup.backends.other_backend_name]\n\t\t\tprompt = \"Backend 2\"\n\t\t\taddress = \"httpbin.org\"\n\t\t\tport = 443\n\t\t\t`,\n\t\t\tstdin: []string{\n\t\t\t\t\"Y\", // when prompted to create a new service\n\t\t\t},\n\t\t\twantOutput: []string{\n\t\t\t\t\"Hostname or IP address: [developer.fastly.com]\",\n\t\t\t\t\"Port: [443]\",\n\t\t\t\t\"Hostname or IP address: [httpbin.org]\",\n\t\t\t\t\"Port: [443]\",\n\t\t\t\t\"Creating service\",\n\t\t\t\t\"Creating backend 'backend_name' (host: developer.fastly.com, port: 443)\",\n\t\t\t\t\"Creating backend 'other_backend_name' (host: httpbin.org, port: 443)\",\n\t\t\t\t\"Uploading package\",\n\t\t\t\t\"Activating service\",\n\t\t\t\t\"SUCCESS: Deployed package (service 12345, version 1)\",\n\t\t\t},\n\t\t},\n\t\t// The following [setup] configuration doesn't define any prompts, nor any\n\t\t// ports, so we validate that the user prompts match our default expectations.\n\t\t{\n\t\t\tname: \"success with setup.backends configuration and no prompts or ports defined\",\n\t\t\targs: args(\"compute deploy --token 123\"),\n\t\t\tapi: mock.API{\n\t\t\t\tActivateVersionFn: activateVersionOk,\n\t\t\t\tCreateBackendFn:   createBackendOK,\n\t\t\t\tCreateDomainFn:    createDomainOK,\n\t\t\t\tCreateServiceFn:   createServiceOK,\n\t\t\t\tDeleteServiceFn:   deleteServiceOK,\n\t\t\t\tGetPackageFn:      getPackageOk,\n\t\t\t\tListDomainsFn:     listDomainsOk,\n\t\t\t\tUpdatePackageFn:   updatePackageOk,\n\t\t\t},\n\t\t\thttpClientRes: []*http.Response{\n\t\t\t\tmock.NewHTTPResponse(http.StatusNoContent, nil, nil),\n\t\t\t\tmock.NewHTTPResponse(http.StatusOK, nil, io.NopCloser(strings.NewReader(\"success\"))),\n\t\t\t},\n\t\t\thttpClientErr: []error{\n\t\t\t\tnil,\n\t\t\t\tnil,\n\t\t\t},\n\t\t\tmanifest: `\n\t\t\tname = \"package\"\n\t\t\tmanifest_version = 2\n\t\t\tlanguage = \"rust\"\n\n\t\t\t[setup.backends.foo_backend]\n\t\t\taddress = \"developer.fastly.com\"\n\t\t\t[setup.backends.bar_backend]\n\t\t\taddress = \"httpbin.org\"\n\t\t\t`,\n\t\t\tstdin: []string{\n\t\t\t\t\"Y\", // when prompted to create a new service\n\t\t\t},\n\t\t\twantOutput: []string{\n\t\t\t\t\"Hostname or IP address: [developer.fastly.com]\",\n\t\t\t\t\"Port: [443]\",\n\t\t\t\t\"Hostname or IP address: [httpbin.org]\",\n\t\t\t\t\"Port: [443]\",\n\t\t\t\t\"Creating service\",\n\t\t\t\t\"Creating backend 'foo_backend' (host: developer.fastly.com, port: 443)\",\n\t\t\t\t\"Creating backend 'bar_backend' (host: httpbin.org, port: 443)\",\n\t\t\t\t\"Uploading package\",\n\t\t\t\t\"Activating service\",\n\t\t\t\t\"SUCCESS: Deployed package (service 12345, version 1)\",\n\t\t\t},\n\t\t\tdontWantOutput: []string{\n\t\t\t\t\"Creating domain '\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"success with setup.backends configuration but no fields for the required resources\",\n\t\t\targs: args(\"compute deploy --token 123\"),\n\t\t\tapi: mock.API{\n\t\t\t\tActivateVersionFn: activateVersionOk,\n\t\t\t\tCreateBackendFn:   createBackendOK,\n\t\t\t\tCreateDomainFn:    createDomainOK,\n\t\t\t\tCreateServiceFn:   createServiceOK,\n\t\t\t\tDeleteServiceFn:   deleteServiceOK,\n\t\t\t\tGetPackageFn:      getPackageOk,\n\t\t\t\tListDomainsFn:     listDomainsOk,\n\t\t\t\tUpdatePackageFn:   updatePackageOk,\n\t\t\t},\n\t\t\thttpClientRes: []*http.Response{\n\t\t\t\tmock.NewHTTPResponse(http.StatusNoContent, nil, nil),\n\t\t\t\tmock.NewHTTPResponse(http.StatusOK, nil, io.NopCloser(strings.NewReader(\"success\"))),\n\t\t\t},\n\t\t\thttpClientErr: []error{\n\t\t\t\tnil,\n\t\t\t\tnil,\n\t\t\t},\n\t\t\tmanifest: `\n\t\t\tname = \"package\"\n\t\t\tmanifest_version = 2\n\t\t\tlanguage = \"rust\"\n\n\t\t\t[setup.backends.foo_backend]\n\t\t\t[setup.backends.bar_backend]\n\t\t\t`,\n\t\t\tstdin: []string{\n\t\t\t\t\"Y\", // when prompted to create a new service\n\t\t\t},\n\t\t\twantOutput: []string{\n\t\t\t\t\"Configure a backend called 'foo_backend'\",\n\t\t\t\t\"Hostname or IP address: [127.0.0.1]\",\n\t\t\t\t\"Port: [443]\",\n\t\t\t\t\"Configure a backend called 'bar_backend'\",\n\t\t\t\t\"Hostname or IP address: [127.0.0.1]\",\n\t\t\t\t\"Port: [443]\",\n\t\t\t\t\"Creating service\",\n\t\t\t\t\"Creating backend 'foo_backend' (host: 127.0.0.1, port: 443)\",\n\t\t\t\t\"Creating backend 'bar_backend' (host: 127.0.0.1, port: 443)\",\n\t\t\t\t\"Uploading package\",\n\t\t\t\t\"Activating service\",\n\t\t\t\t\"SUCCESS: Deployed package (service 12345, version 1)\",\n\t\t\t},\n\t\t\tdontWantOutput: []string{\n\t\t\t\t\"Creating domain '\",\n\t\t\t},\n\t\t},\n\t\t// The following test validates no prompts are displayed to the user due to\n\t\t// the use of the --non-interactive flag.\n\t\t{\n\t\t\tname: \"success with setup.backends configuration and non-interactive\",\n\t\t\targs: args(\"compute deploy --non-interactive --token 123\"),\n\t\t\tapi: mock.API{\n\t\t\t\tActivateVersionFn: activateVersionOk,\n\t\t\t\tCreateBackendFn:   createBackendOK,\n\t\t\t\tCreateDomainFn:    createDomainOK,\n\t\t\t\tCreateServiceFn:   createServiceOK,\n\t\t\t\tGetPackageFn:      getPackageOk,\n\t\t\t\tListDomainsFn:     listDomainsOk,\n\t\t\t\tUpdatePackageFn:   updatePackageOk,\n\t\t\t},\n\t\t\thttpClientRes: []*http.Response{\n\t\t\t\tmock.NewHTTPResponse(http.StatusNoContent, nil, nil),\n\t\t\t\tmock.NewHTTPResponse(http.StatusOK, nil, io.NopCloser(strings.NewReader(\"success\"))),\n\t\t\t},\n\t\t\thttpClientErr: []error{\n\t\t\t\tnil,\n\t\t\t\tnil,\n\t\t\t},\n\t\t\tmanifest: `\n\t\t\tname = \"package\"\n\t\t\tmanifest_version = 2\n\t\t\tlanguage = \"rust\"\n\n\t\t\t[setup.backends.backend_name]\n\t\t\tdescription = \"Backend 1\"\n\t\t\taddress = \"developer.fastly.com\"\n\t\t\tport = 443\n\t\t\t[setup.backends.other_backend_name]\n\t\t\tdescription = \"Backend 2\"\n\t\t\taddress = \"httpbin.org\"\n\t\t\tport = 443\n\t\t\t`,\n\t\t\twantOutput: []string{\n\t\t\t\t\"Creating service\",\n\t\t\t\t\"Creating backend 'backend_name' (host: developer.fastly.com, port: 443)\",\n\t\t\t\t\"Creating backend 'other_backend_name' (host: httpbin.org, port: 443)\",\n\t\t\t\t\"Uploading package\",\n\t\t\t\t\"Activating service\",\n\t\t\t\t\"SUCCESS: Deployed package (service 12345, version 1)\",\n\t\t\t},\n\t\t\tdontWantOutput: []string{\n\t\t\t\t\"Backend 1: [developer.fastly.com]\",\n\t\t\t\t\"Backend port number: [443]\",\n\t\t\t\t\"Backend 2: [httpbin.org]\",\n\t\t\t\t\"Backend port number: [443]\",\n\t\t\t\t\"Domain: [\",\n\t\t\t},\n\t\t},\n\t\t// The following test validates that a new 'originless' backend is created\n\t\t// when the user has no [setup] configuration and they also pass the\n\t\t// --non-interactive flag. This is done by ensuring we DON'T see the\n\t\t// standard 'Creating backend' output because we want to conceal the fact\n\t\t// that we require a backend for Compute services because it's a temporary\n\t\t// implementation detail.\n\t\t{\n\t\t\tname: \"success with no setup.backends configuration and non-interactive for new service creation\",\n\t\t\targs: args(\"compute deploy --non-interactive --token 123\"),\n\t\t\tapi: mock.API{\n\t\t\t\tActivateVersionFn: activateVersionOk,\n\t\t\t\tCreateBackendFn:   createBackendOK,\n\t\t\t\tCreateDomainFn:    createDomainOK,\n\t\t\t\tCreateServiceFn:   createServiceOK,\n\t\t\t\tGetPackageFn:      getPackageOk,\n\t\t\t\tListDomainsFn:     listDomainsOk,\n\t\t\t\tUpdatePackageFn:   updatePackageOk,\n\t\t\t},\n\t\t\thttpClientRes: []*http.Response{\n\t\t\t\tmock.NewHTTPResponse(http.StatusNoContent, nil, nil),\n\t\t\t\tmock.NewHTTPResponse(http.StatusOK, nil, io.NopCloser(strings.NewReader(\"success\"))),\n\t\t\t},\n\t\t\thttpClientErr: []error{\n\t\t\t\tnil,\n\t\t\t\tnil,\n\t\t\t},\n\t\t\twantOutput: []string{\n\t\t\t\t\"SUCCESS: Deployed package (service 12345, version 1)\",\n\t\t\t},\n\t\t\tdontWantOutput: []string{\n\t\t\t\t\"Creating backend\", // expect originless creation to be hidden\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"success with no setup.backends configuration and single backend entered at prompt for new service\",\n\t\t\targs: args(\"compute deploy --token 123\"),\n\t\t\tapi: mock.API{\n\t\t\t\tActivateVersionFn: activateVersionOk,\n\t\t\t\tCreateBackendFn:   createBackendOK,\n\t\t\t\tCreateDomainFn:    createDomainOK,\n\t\t\t\tCreateServiceFn:   createServiceOK,\n\t\t\t\tGetPackageFn:      getPackageOk,\n\t\t\t\tListDomainsFn:     listDomainsOk,\n\t\t\t\tUpdatePackageFn:   updatePackageOk,\n\t\t\t},\n\t\t\thttpClientRes: []*http.Response{\n\t\t\t\tmock.NewHTTPResponse(http.StatusNoContent, nil, nil),\n\t\t\t\tmock.NewHTTPResponse(http.StatusOK, nil, io.NopCloser(strings.NewReader(\"success\"))),\n\t\t\t},\n\t\t\thttpClientErr: []error{\n\t\t\t\tnil,\n\t\t\t\tnil,\n\t\t\t},\n\t\t\tstdin: []string{\n\t\t\t\t\"Y\",      // when prompted to create a new service\n\t\t\t\t\"foobar\", // when prompted for service name\n\t\t\t\t\"fastly.com\",\n\t\t\t\t\"443\",\n\t\t\t\t\"my_backend_name\",\n\t\t\t\t\"\", // this stops prompting for backends\n\t\t\t},\n\t\t\twantOutput: []string{\n\t\t\t\t\"Backend (hostname or IP address, or leave blank to stop adding backends):\",\n\t\t\t\t\"Backend port number: [443]\",\n\t\t\t\t\"Backend name:\",\n\t\t\t\t\"Creating backend 'my_backend_name' (host: fastly.com, port: 443)\",\n\t\t\t\t\"SUCCESS: Deployed package (service 12345, version 1)\",\n\t\t\t},\n\t\t},\n\t\t// This is the same test as above but when prompted it will provide two\n\t\t// backends instead of one, and will also allow the code to generate the\n\t\t// backend name using its predefined formula.\n\t\t{\n\t\t\tname: \"success with no setup.backends configuration and multiple backends entered at prompt for new service\",\n\t\t\targs: args(\"compute deploy --token 123\"),\n\t\t\tapi: mock.API{\n\t\t\t\tActivateVersionFn: activateVersionOk,\n\t\t\t\tCreateBackendFn:   createBackendOK,\n\t\t\t\tCreateDomainFn:    createDomainOK,\n\t\t\t\tCreateServiceFn:   createServiceOK,\n\t\t\t\tGetPackageFn:      getPackageOk,\n\t\t\t\tListDomainsFn:     listDomainsOk,\n\t\t\t\tUpdatePackageFn:   updatePackageOk,\n\t\t\t},\n\t\t\thttpClientRes: []*http.Response{\n\t\t\t\tmock.NewHTTPResponse(http.StatusNoContent, nil, nil),\n\t\t\t\tmock.NewHTTPResponse(http.StatusOK, nil, io.NopCloser(strings.NewReader(\"success\"))),\n\t\t\t},\n\t\t\thttpClientErr: []error{\n\t\t\t\tnil,\n\t\t\t\tnil,\n\t\t\t},\n\t\t\tstdin: []string{\n\t\t\t\t\"Y\",          // when prompted to create a new service\n\t\t\t\t\"foobar\",     // when prompted for service name\n\t\t\t\t\"fastly.com\", // when prompted for a backend\n\t\t\t\t\"443\",\n\t\t\t\t\"\", // this is so we generate a backend name using a built-in formula\n\t\t\t\t\"google.com\",\n\t\t\t\t\"123\",\n\t\t\t\t\"\", // this is so we generate a backend name using a built-in formula\n\t\t\t\t\"\", // this stops prompting for backends\n\t\t\t},\n\t\t\twantOutput: []string{\n\t\t\t\t\"Backend (hostname or IP address, or leave blank to stop adding backends):\",\n\t\t\t\t\"Backend port number: [443]\",\n\t\t\t\t\"Backend name:\",\n\t\t\t\t\"Creating backend 'backend_1' (host: fastly.com, port: 443)\",\n\t\t\t\t\"Creating backend 'backend_2' (host: google.com, port: 123)\",\n\t\t\t\t\"SUCCESS: Deployed package (service 12345, version 1)\",\n\t\t\t},\n\t\t},\n\t\t// The following test validates that when prompting the user for backends\n\t\t// that we'll default to creating an 'originless' backend if no value\n\t\t// provided at the prompt.\n\t\t{\n\t\t\tname: \"success with no setup.backends configuration and defaulting to originless\",\n\t\t\targs: args(\"compute deploy --token 123\"),\n\t\t\tapi: mock.API{\n\t\t\t\tActivateVersionFn: activateVersionOk,\n\t\t\t\tCreateBackendFn:   createBackendOK,\n\t\t\t\tCreateDomainFn:    createDomainOK,\n\t\t\t\tCreateServiceFn:   createServiceOK,\n\t\t\t\tGetPackageFn:      getPackageOk,\n\t\t\t\tListDomainsFn:     listDomainsOk,\n\t\t\t\tUpdatePackageFn:   updatePackageOk,\n\t\t\t},\n\t\t\thttpClientRes: []*http.Response{\n\t\t\t\tmock.NewHTTPResponse(http.StatusNoContent, nil, nil),\n\t\t\t\tmock.NewHTTPResponse(http.StatusOK, nil, io.NopCloser(strings.NewReader(\"success\"))),\n\t\t\t},\n\t\t\thttpClientErr: []error{\n\t\t\t\tnil,\n\t\t\t\tnil,\n\t\t\t},\n\t\t\tstdin: []string{\n\t\t\t\t\"Y\",      // when prompted to create a new service\n\t\t\t\t\"foobar\", // when prompted for service name\n\t\t\t\t\"\",       // this stops prompting for backends\n\t\t\t},\n\t\t\twantOutput: []string{\n\t\t\t\t\"Backend (hostname or IP address, or leave blank to stop adding backends):\",\n\t\t\t\t\"SUCCESS: Deployed package (service 12345, version 1)\",\n\t\t\t},\n\t\t\tdontWantOutput: []string{\n\t\t\t\t\"Creating backend\", // expect originless creation to be hidden\n\t\t\t},\n\t\t},\n\t\t// The following test is the same setup as above, but if the user provides\n\t\t// the --non-interactive flag we won't prompt for any backends.\n\t\t{\n\t\t\tname: \"success with no setup.backends configuration and use of --non-interactive\",\n\t\t\targs: args(\"compute deploy --non-interactive --token 123\"),\n\t\t\tapi: mock.API{\n\t\t\t\tActivateVersionFn:   activateVersionOk,\n\t\t\t\tCreateBackendFn:     createBackendOK,\n\t\t\t\tCreateServiceFn:     createServiceOK,\n\t\t\t\tGetPackageFn:        getPackageOk,\n\t\t\t\tGetServiceDetailsFn: getServiceDetailsWasm,\n\t\t\t\tGetServiceFn:        getServiceOK,\n\t\t\t\tListDomainsFn:       listDomainsOk,\n\t\t\t\tUpdatePackageFn:     updatePackageOk,\n\t\t\t},\n\t\t\thttpClientRes: []*http.Response{\n\t\t\t\tmock.NewHTTPResponse(http.StatusNoContent, nil, nil),\n\t\t\t\tmock.NewHTTPResponse(http.StatusOK, nil, io.NopCloser(strings.NewReader(\"success\"))),\n\t\t\t},\n\t\t\thttpClientErr: []error{\n\t\t\t\tnil,\n\t\t\t\tnil,\n\t\t\t},\n\t\t\twantOutput: []string{\n\t\t\t\t\"SUCCESS: Deployed package (service 12345, version 1)\",\n\t\t\t},\n\t\t\tdontWantOutput: []string{\n\t\t\t\t\"Create new service\",\n\t\t\t\t\"Creating backend\", // expect originless creation to be hidden\n\t\t\t},\n\t\t},\n\t\t// The following test validates that when dealing with an existing service,\n\t\t// no [setup.backends] configuration is utilised.\n\t\t//\n\t\t// i.e. we will not validate the service for missing backends, nor will we\n\t\t// prompt the user to create any backends.\n\t\t{\n\t\t\tname: \"success with setup.backends configuration and existing service\",\n\t\t\targs: args(\"compute deploy --service-id 123 --token 123\"),\n\t\t\tapi: mock.API{\n\t\t\t\tActivateVersionFn:   activateVersionOk,\n\t\t\t\tCloneVersionFn:      testutil.CloneVersionResult(4),\n\t\t\t\tCreateBackendFn:     createBackendOK,\n\t\t\t\tGetPackageFn:        getPackageOk,\n\t\t\t\tGetServiceDetailsFn: getServiceDetailsWasm,\n\t\t\t\tGetServiceFn:        getServiceOK,\n\t\t\t\tListDomainsFn:       listDomainsOk,\n\t\t\t\tListVersionsFn:      testutil.ListVersions,\n\t\t\t\tUpdatePackageFn:     updatePackageOk,\n\t\t\t},\n\t\t\thttpClientRes: []*http.Response{\n\t\t\t\tmock.NewHTTPResponse(http.StatusNoContent, nil, nil),\n\t\t\t\tmock.NewHTTPResponse(http.StatusOK, nil, io.NopCloser(strings.NewReader(\"success\"))),\n\t\t\t},\n\t\t\thttpClientErr: []error{\n\t\t\t\tnil,\n\t\t\t\tnil,\n\t\t\t},\n\t\t\tmanifest: `\n\t\t\tname = \"package\"\n\t\t\tmanifest_version = 2\n\t\t\tlanguage = \"rust\"\n\n\t\t\t[setup.backends.fastly]\n\t\t\tdescription = \"Backend 1\"\n\t\t\taddress = \"fastly.com\"\n\t\t\tport = 443\n\t\t\t[setup.backends.google]\n\t\t\tdescription = \"Backend 2\"\n\t\t\taddress = \"google.com\"\n\t\t\tport = 443\n\t\t\t[setup.backends.facebook]\n\t\t\tdescription = \"Backend 3\"\n\t\t\taddress = \"facebook.com\"\n\t\t\tport = 443\n\t\t\t`,\n\t\t\twantOutput: []string{\n\t\t\t\t\"Uploading package\",\n\t\t\t\t\"Activating service\",\n\t\t\t\t\"SUCCESS: Deployed package (service 123, version 4)\",\n\t\t\t},\n\t\t\tdontWantOutput: []string{\n\t\t\t\t\"Creating backend 'google' (host: beep.com, port: 123)\",\n\t\t\t\t\"Creating backend 'facebook' (host: boop.com, port: 456)\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"success with setup.config_stores configuration and existing service\",\n\t\t\targs: args(\"compute deploy --service-id 123 --token 123\"),\n\t\t\tapi: mock.API{\n\t\t\t\tActivateVersionFn:   activateVersionOk,\n\t\t\t\tCloneVersionFn:      testutil.CloneVersionResult(4),\n\t\t\t\tCreateBackendFn:     createBackendOK,\n\t\t\t\tGetPackageFn:        getPackageOk,\n\t\t\t\tGetServiceDetailsFn: getServiceDetailsWasm,\n\t\t\t\tGetServiceFn:        getServiceOK,\n\t\t\t\tListDomainsFn:       listDomainsOk,\n\t\t\t\tListVersionsFn:      testutil.ListVersions,\n\t\t\t\tUpdatePackageFn:     updatePackageOk,\n\t\t\t},\n\t\t\thttpClientRes: []*http.Response{\n\t\t\t\tmock.NewHTTPResponse(http.StatusNoContent, nil, nil),\n\t\t\t\tmock.NewHTTPResponse(http.StatusOK, nil, io.NopCloser(strings.NewReader(\"success\"))),\n\t\t\t},\n\t\t\thttpClientErr: []error{\n\t\t\t\tnil,\n\t\t\t\tnil,\n\t\t\t},\n\t\t\tmanifest: `\n\t\t\tname = \"package\"\n\t\t\tmanifest_version = 2\n\t\t\tlanguage = \"rust\"\n\n\t\t\t[setup.config_stores.example]\n\t\t\tdescription = \"My first dictionary\"\n\t\t\t[setup.config_stores.example.items.foo]\n\t\t\tvalue = \"my default value for foo\"\n\t\t\tdescription = \"a good description about foo\"\n\t\t\t[setup.config_stores.example.items.bar]\n\t\t\tvalue = \"my default value for bar\"\n\t\t\tdescription = \"a good description about bar\"\n\t\t\t`,\n\t\t\twantOutput: []string{\n\t\t\t\t\"Uploading package\",\n\t\t\t\t\"Activating service\",\n\t\t\t\t\"SUCCESS: Deployed package (service 123, version 4)\",\n\t\t\t},\n\t\t\tdontWantOutput: []string{\n\t\t\t\t\"Configuring dictionary 'dict_a'\",\n\t\t\t\t\"Create a config store key called 'foo'\",\n\t\t\t\t\"Create a config store key called 'bar'\",\n\t\t\t\t\"Creating config store 'example'\",\n\t\t\t\t\"Creating config store item 'foo'\",\n\t\t\t\t\"Creating config store item 'bar'\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"success with setup.config_stores configuration and no existing service\",\n\t\t\targs: args(\"compute deploy --token 123\"),\n\t\t\tapi: mock.API{\n\t\t\t\tActivateVersionFn:       activateVersionOk,\n\t\t\t\tCreateBackendFn:         createBackendOK,\n\t\t\t\tCreateConfigStoreFn:     createConfigStoreOK,\n\t\t\t\tCreateDomainFn:          createDomainOK,\n\t\t\t\tCreateResourceFn:        createResourceOK,\n\t\t\t\tCreateServiceFn:         createServiceOK,\n\t\t\t\tGetPackageFn:            getPackageOk,\n\t\t\t\tGetServiceDetailsFn:     getServiceDetailsWasm,\n\t\t\t\tGetServiceFn:            getServiceOK,\n\t\t\t\tListConfigStoresFn:      listConfigStoresEmpty,\n\t\t\t\tListDomainsFn:           listDomainsOk,\n\t\t\t\tUpdateConfigStoreItemFn: updateConfigStoreItemOK,\n\t\t\t\tUpdatePackageFn:         updatePackageOk,\n\t\t\t},\n\t\t\thttpClientRes: []*http.Response{\n\t\t\t\tmock.NewHTTPResponse(http.StatusNoContent, nil, nil),\n\t\t\t\tmock.NewHTTPResponse(http.StatusOK, nil, io.NopCloser(strings.NewReader(\"success\"))),\n\t\t\t},\n\t\t\thttpClientErr: []error{\n\t\t\t\tnil,\n\t\t\t\tnil,\n\t\t\t},\n\t\t\tmanifest: `\n\t\t\tname = \"package\"\n\t\t\tmanifest_version = 2\n\t\t\tlanguage = \"rust\"\n\n\t\t\t[setup.config_stores.example]\n\t\t\tdescription = \"My first store\"\n\t\t\t[setup.config_stores.example.items.foo]\n\t\t\tvalue = \"my default value for foo\"\n\t\t\tdescription = \"a good description about foo\"\n\t\t\t[setup.config_stores.example.items.bar]\n\t\t\tvalue = \"my default value for bar\"\n\t\t\tdescription = \"a good description about bar\"\n\t\t\t`,\n\t\t\tstdin: []string{\n\t\t\t\t\"Y\", // when prompted to create a new service\n\t\t\t},\n\t\t\twantOutput: []string{\n\t\t\t\t\"Configuring config store 'example'\",\n\t\t\t\t\"My first store\",\n\t\t\t\t\"Create a config store key called 'foo'\",\n\t\t\t\t\"my default value for foo\",\n\t\t\t\t\"Create a config store key called 'bar'\",\n\t\t\t\t\"my default value for bar\",\n\t\t\t\t\"Creating config store 'example'\",\n\t\t\t\t\"Creating config store item 'foo'\",\n\t\t\t\t\"Creating config store item 'bar'\",\n\t\t\t\t\"Uploading package\",\n\t\t\t\t\"Activating service\",\n\t\t\t\t\"SUCCESS: Deployed package (service 12345, version 1)\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"success with setup.config_stores configuration and no existing service and a conflicting store name\",\n\t\t\targs: args(\"compute deploy --token 123\"),\n\t\t\tapi: mock.API{\n\t\t\t\tActivateVersionFn:       activateVersionOk,\n\t\t\t\tCreateBackendFn:         createBackendOK,\n\t\t\t\tCreateConfigStoreFn:     createConfigStoreOK,\n\t\t\t\tCreateDomainFn:          createDomainOK,\n\t\t\t\tCreateResourceFn:        createResourceOK,\n\t\t\t\tCreateServiceFn:         createServiceOK,\n\t\t\t\tGetConfigStoreFn:        getConfigStoreOk,\n\t\t\t\tGetPackageFn:            getPackageOk,\n\t\t\t\tGetServiceDetailsFn:     getServiceDetailsWasm,\n\t\t\t\tGetServiceFn:            getServiceOK,\n\t\t\t\tListConfigStoresFn:      listConfigStoresOk,\n\t\t\t\tListDomainsFn:           listDomainsOk,\n\t\t\t\tUpdateConfigStoreItemFn: updateConfigStoreItemOK,\n\t\t\t\tUpdatePackageFn:         updatePackageOk,\n\t\t\t},\n\t\t\thttpClientRes: []*http.Response{\n\t\t\t\tmock.NewHTTPResponse(http.StatusNoContent, nil, nil),\n\t\t\t\tmock.NewHTTPResponse(http.StatusOK, nil, io.NopCloser(strings.NewReader(\"success\"))),\n\t\t\t},\n\t\t\thttpClientErr: []error{\n\t\t\t\tnil,\n\t\t\t\tnil,\n\t\t\t},\n\t\t\tmanifest: `\n\t\t\tname = \"package\"\n\t\t\tmanifest_version = 2\n\t\t\tlanguage = \"rust\"\n\n\t\t\t[setup.config_stores.example]\n\t\t\tdescription = \"My first store\"\n\t\t\t[setup.config_stores.example.items.foo]\n\t\t\tvalue = \"my default value for foo\"\n\t\t\tdescription = \"a good description about foo\"\n\t\t\t[setup.config_stores.example.items.bar]\n\t\t\tvalue = \"my default value for bar\"\n\t\t\tdescription = \"a good description about bar\"\n\t\t\t`,\n\t\t\tstdin: []string{\n\t\t\t\t\"Y\", // when prompted to create a new service\n\t\t\t},\n\t\t\twantOutput: []string{\n\t\t\t\t\"WARNING: A Config Store called 'example' already exists\",\n\t\t\t\t\"Retrieving existing Config Store 'example'\",\n\t\t\t\t\"Configuring config store 'example'\",\n\t\t\t\t\"My first store\",\n\t\t\t\t\"Create a config store key called 'foo'\",\n\t\t\t\t\"my default value for foo\",\n\t\t\t\t\"Create a config store key called 'bar'\",\n\t\t\t\t\"my default value for bar\",\n\t\t\t\t\"Creating config store item 'foo'\",\n\t\t\t\t\"Creating config store item 'bar'\",\n\t\t\t\t\"Uploading package\",\n\t\t\t\t\"Activating service\",\n\t\t\t\t\"SUCCESS: Deployed package (service 12345, version 1)\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"success with setup.config_stores configuration and no existing service and --non-interactive\",\n\t\t\targs: args(\"compute deploy --non-interactive --token 123\"),\n\t\t\tapi: mock.API{\n\t\t\t\tActivateVersionFn:       activateVersionOk,\n\t\t\t\tCreateBackendFn:         createBackendOK,\n\t\t\t\tCreateConfigStoreFn:     createConfigStoreOK,\n\t\t\t\tCreateDomainFn:          createDomainOK,\n\t\t\t\tCreateResourceFn:        createResourceOK,\n\t\t\t\tCreateServiceFn:         createServiceOK,\n\t\t\t\tGetPackageFn:            getPackageOk,\n\t\t\t\tGetServiceDetailsFn:     getServiceDetailsWasm,\n\t\t\t\tGetServiceFn:            getServiceOK,\n\t\t\t\tListConfigStoresFn:      listConfigStoresEmpty,\n\t\t\t\tListDomainsFn:           listDomainsOk,\n\t\t\t\tUpdateConfigStoreItemFn: updateConfigStoreItemOK,\n\t\t\t\tUpdatePackageFn:         updatePackageOk,\n\t\t\t},\n\t\t\thttpClientRes: []*http.Response{\n\t\t\t\tmock.NewHTTPResponse(http.StatusNoContent, nil, nil),\n\t\t\t\tmock.NewHTTPResponse(http.StatusOK, nil, io.NopCloser(strings.NewReader(\"success\"))),\n\t\t\t},\n\t\t\thttpClientErr: []error{\n\t\t\t\tnil,\n\t\t\t\tnil,\n\t\t\t},\n\t\t\tmanifest: `\n\t\t\tname = \"package\"\n\t\t\tmanifest_version = 2\n\t\t\tlanguage = \"rust\"\n\n\t\t\t[setup.config_stores.example]\n\t\t\tdescription = \"My first store\"\n\t\t\t[setup.config_stores.example.items.foo]\n\t\t\tvalue = \"my default value for foo\"\n\t\t\tdescription = \"a good description about foo\"\n\t\t\t[setup.config_stores.example.items.bar]\n\t\t\tvalue = \"my default value for bar\"\n\t\t\tdescription = \"a good description about bar\"\n\t\t\t`,\n\t\t\tstdin: []string{\n\t\t\t\t\"Y\", // when prompted to create a new service\n\t\t\t},\n\t\t\twantOutput: []string{\n\t\t\t\t\"Creating config store 'example'\",\n\t\t\t\t\"Creating config store item 'foo'\",\n\t\t\t\t\"Creating config store item 'bar'\",\n\t\t\t\t\"Uploading package\",\n\t\t\t\t\"Activating service\",\n\t\t\t\t\"SUCCESS: Deployed package (service 12345, version 1)\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"success with setup.config_stores configuration and no existing service and no predefined values\",\n\t\t\targs: args(\"compute deploy --token 123\"),\n\t\t\tapi: mock.API{\n\t\t\t\tActivateVersionFn:       activateVersionOk,\n\t\t\t\tCreateBackendFn:         createBackendOK,\n\t\t\t\tCreateConfigStoreFn:     createConfigStoreOK,\n\t\t\t\tCreateDomainFn:          createDomainOK,\n\t\t\t\tCreateResourceFn:        createResourceOK,\n\t\t\t\tCreateServiceFn:         createServiceOK,\n\t\t\t\tGetPackageFn:            getPackageOk,\n\t\t\t\tGetServiceDetailsFn:     getServiceDetailsWasm,\n\t\t\t\tGetServiceFn:            getServiceOK,\n\t\t\t\tListConfigStoresFn:      listConfigStoresEmpty,\n\t\t\t\tListDomainsFn:           listDomainsOk,\n\t\t\t\tUpdateConfigStoreItemFn: updateConfigStoreItemOK,\n\t\t\t\tUpdatePackageFn:         updatePackageOk,\n\t\t\t},\n\t\t\thttpClientRes: []*http.Response{\n\t\t\t\tmock.NewHTTPResponse(http.StatusNoContent, nil, nil),\n\t\t\t\tmock.NewHTTPResponse(http.StatusOK, nil, io.NopCloser(strings.NewReader(\"success\"))),\n\t\t\t},\n\t\t\thttpClientErr: []error{\n\t\t\t\tnil,\n\t\t\t\tnil,\n\t\t\t},\n\t\t\tmanifest: `\n\t\t\tname = \"package\"\n\t\t\tmanifest_version = 2\n\t\t\tlanguage = \"rust\"\n\n\t\t\t[setup.config_stores.example]\n\t\t\t[setup.config_stores.example.items.foo]\n\t\t\t[setup.config_stores.example.items.bar]\n\t\t\t`,\n\t\t\tstdin: []string{\n\t\t\t\t\"Y\", // when prompted to create a new service\n\t\t\t},\n\t\t\twantOutput: []string{\n\t\t\t\t\"Configuring config store 'example'\",\n\t\t\t\t\"Create a config store key called 'foo'\",\n\t\t\t\t\"Create a config store key called 'bar'\",\n\t\t\t\t\"Creating config store 'example'\",\n\t\t\t\t\"Creating config store item 'foo'\",\n\t\t\t\t\"Creating config store item 'bar'\",\n\t\t\t\t\"Uploading package\",\n\t\t\t\t\"Activating service\",\n\t\t\t\t\"SUCCESS: Deployed package (service 12345, version 1)\",\n\t\t\t},\n\t\t\t// The following are predefined values for the `description` and `value`\n\t\t\t// fields from the prior setup.config_stores tests that we expect to not\n\t\t\t// be present in the stdout/stderr as the [setup.config_stores]\n\t\t\t// configuration does not define them.\n\t\t\tdontWantOutput: []string{\n\t\t\t\t\"My first store\",\n\t\t\t\t\"my default value for foo\",\n\t\t\t\t\"my default value for bar\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"success with setup.log_entries configuration and existing service\",\n\t\t\targs: args(\"compute deploy --service-id 123 --token 123\"),\n\t\t\tapi: mock.API{\n\t\t\t\tActivateVersionFn:   activateVersionOk,\n\t\t\t\tCloneVersionFn:      testutil.CloneVersionResult(4),\n\t\t\t\tCreateBackendFn:     createBackendOK,\n\t\t\t\tGetPackageFn:        getPackageOk,\n\t\t\t\tGetServiceDetailsFn: getServiceDetailsWasm,\n\t\t\t\tGetServiceFn:        getServiceOK,\n\t\t\t\tListDomainsFn:       listDomainsOk,\n\t\t\t\tListVersionsFn:      testutil.ListVersions,\n\t\t\t\tUpdatePackageFn:     updatePackageOk,\n\t\t\t},\n\t\t\thttpClientRes: []*http.Response{\n\t\t\t\tmock.NewHTTPResponse(http.StatusNoContent, nil, nil),\n\t\t\t\tmock.NewHTTPResponse(http.StatusOK, nil, io.NopCloser(strings.NewReader(\"success\"))),\n\t\t\t},\n\t\t\thttpClientErr: []error{\n\t\t\t\tnil,\n\t\t\t\tnil,\n\t\t\t},\n\t\t\tmanifest: `\n\t\t\tname = \"package\"\n\t\t\tmanifest_version = 2\n\t\t\tlanguage = \"rust\"\n\n\t\t\t[setup.log_endpoints.foo]\n\t\t\tprovider = \"BigQuery\"\n\t\t\t`,\n\t\t\twantOutput: []string{\n\t\t\t\t\"Uploading package\",\n\t\t\t\t\"Activating service\",\n\t\t\t\t\"SUCCESS: Deployed package (service 123, version 4)\",\n\t\t\t},\n\t\t\tdontWantOutput: []string{\n\t\t\t\t\"The package code requires the following log endpoints to be created.\",\n\t\t\t\t\"Name: foo\",\n\t\t\t\t\"Provider: BigQuery\",\n\t\t\t\t\"Refer to the help documentation for each provider (if no provider shown, then select your own):\",\n\t\t\t\t\"fastly logging <provider> create --help\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"success with setup.log_entries configuration and no existing service\",\n\t\t\targs: args(\"compute deploy --token 123\"),\n\t\t\tapi: mock.API{\n\t\t\t\tActivateVersionFn:      activateVersionOk,\n\t\t\t\tCreateBackendFn:        createBackendOK,\n\t\t\t\tCreateDictionaryFn:     createDictionaryOK,\n\t\t\t\tCreateDictionaryItemFn: createDictionaryItemOK,\n\t\t\t\tCreateDomainFn:         createDomainOK,\n\t\t\t\tCreateServiceFn:        createServiceOK,\n\t\t\t\tGetPackageFn:           getPackageOk,\n\t\t\t\tGetServiceDetailsFn:    getServiceDetailsWasm,\n\t\t\t\tGetServiceFn:           getServiceOK,\n\t\t\t\tListDomainsFn:          listDomainsOk,\n\t\t\t\tUpdatePackageFn:        updatePackageOk,\n\t\t\t},\n\t\t\thttpClientRes: []*http.Response{\n\t\t\t\tmock.NewHTTPResponse(http.StatusNoContent, nil, nil),\n\t\t\t\tmock.NewHTTPResponse(http.StatusOK, nil, io.NopCloser(strings.NewReader(\"success\"))),\n\t\t\t},\n\t\t\thttpClientErr: []error{\n\t\t\t\tnil,\n\t\t\t\tnil,\n\t\t\t},\n\t\t\tmanifest: `\n\t\t\tname = \"package\"\n\t\t\tmanifest_version = 2\n\t\t\tlanguage = \"rust\"\n\n\t\t\t[setup.log_endpoints.foo]\n\t\t\tprovider = \"BigQuery\"\n\t\t\t`,\n\t\t\tstdin: []string{\n\t\t\t\t\"Y\", // when prompted to create a new service\n\t\t\t},\n\t\t\twantOutput: []string{\n\t\t\t\t\"The package code requires the following log endpoints to be created.\",\n\t\t\t\t\"Name: foo\",\n\t\t\t\t\"Provider: BigQuery\",\n\t\t\t\t\"Refer to the help documentation for each provider (if no provider shown, then select your own):\",\n\t\t\t\t\"fastly logging <provider> create --help\",\n\t\t\t\t\"Uploading package\",\n\t\t\t\t\"Activating service\",\n\t\t\t\t\"SUCCESS: Deployed package (service 12345, version 1)\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"success with setup.log_entries configuration and no existing service and no provider defined\",\n\t\t\targs: args(\"compute deploy --token 123\"),\n\t\t\tapi: mock.API{\n\t\t\t\tActivateVersionFn:      activateVersionOk,\n\t\t\t\tCreateBackendFn:        createBackendOK,\n\t\t\t\tCreateDictionaryFn:     createDictionaryOK,\n\t\t\t\tCreateDictionaryItemFn: createDictionaryItemOK,\n\t\t\t\tCreateDomainFn:         createDomainOK,\n\t\t\t\tCreateServiceFn:        createServiceOK,\n\t\t\t\tGetPackageFn:           getPackageOk,\n\t\t\t\tGetServiceDetailsFn:    getServiceDetailsWasm,\n\t\t\t\tGetServiceFn:           getServiceOK,\n\t\t\t\tListDomainsFn:          listDomainsOk,\n\t\t\t\tUpdatePackageFn:        updatePackageOk,\n\t\t\t},\n\t\t\thttpClientRes: []*http.Response{\n\t\t\t\tmock.NewHTTPResponse(http.StatusNoContent, nil, nil),\n\t\t\t\tmock.NewHTTPResponse(http.StatusOK, nil, io.NopCloser(strings.NewReader(\"success\"))),\n\t\t\t},\n\t\t\thttpClientErr: []error{\n\t\t\t\tnil,\n\t\t\t\tnil,\n\t\t\t},\n\t\t\tmanifest: `\n\t\t\tname = \"package\"\n\t\t\tmanifest_version = 2\n\t\t\tlanguage = \"rust\"\n\n\t\t\t[setup.log_endpoints.foo]\n\t\t\t`,\n\t\t\tstdin: []string{\n\t\t\t\t\"Y\", // when prompted to create a new service\n\t\t\t},\n\t\t\twantOutput: []string{\n\t\t\t\t\"The package code requires the following log endpoints to be created.\",\n\t\t\t\t\"Name: foo\",\n\t\t\t\t\"Refer to the help documentation for each provider (if no provider shown, then select your own):\",\n\t\t\t\t\"fastly logging <provider> create --help\",\n\t\t\t\t\"Uploading package\",\n\t\t\t\t\"Activating service\",\n\t\t\t\t\"SUCCESS: Deployed package (service 12345, version 1)\",\n\t\t\t},\n\t\t\tdontWantOutput: []string{\n\t\t\t\t\"Provider: BigQuery\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"success with setup.log_entries configuration and no existing service, but a provider defined\",\n\t\t\targs: args(\"compute deploy --token 123\"),\n\t\t\tapi: mock.API{\n\t\t\t\tActivateVersionFn:      activateVersionOk,\n\t\t\t\tCreateBackendFn:        createBackendOK,\n\t\t\t\tCreateDictionaryFn:     createDictionaryOK,\n\t\t\t\tCreateDictionaryItemFn: createDictionaryItemOK,\n\t\t\t\tCreateDomainFn:         createDomainOK,\n\t\t\t\tCreateServiceFn:        createServiceOK,\n\t\t\t\tGetPackageFn:           getPackageOk,\n\t\t\t\tGetServiceDetailsFn:    getServiceDetailsWasm,\n\t\t\t\tGetServiceFn:           getServiceOK,\n\t\t\t\tListDomainsFn:          listDomainsOk,\n\t\t\t\tUpdatePackageFn:        updatePackageOk,\n\t\t\t},\n\t\t\thttpClientRes: []*http.Response{\n\t\t\t\tmock.NewHTTPResponse(http.StatusNoContent, nil, nil),\n\t\t\t\tmock.NewHTTPResponse(http.StatusOK, nil, io.NopCloser(strings.NewReader(\"success\"))),\n\t\t\t},\n\t\t\thttpClientErr: []error{\n\t\t\t\tnil,\n\t\t\t\tnil,\n\t\t\t},\n\t\t\tmanifest: `\n\t\t\tname = \"package\"\n\t\t\tmanifest_version = 2\n\t\t\tlanguage = \"rust\"\n\n\t\t\t[setup.log_endpoints.foo]\n\t\t\tprovider = \"BigQuery\"\n\t\t\t`,\n\t\t\tstdin: []string{\n\t\t\t\t\"Y\", // when prompted to create a new service\n\t\t\t},\n\t\t\twantOutput: []string{\n\t\t\t\t\"The package code requires the following log endpoints to be created.\",\n\t\t\t\t\"Name: foo\",\n\t\t\t\t\"Provider: BigQuery\",\n\t\t\t\t\"Refer to the help documentation for each provider (if no provider shown, then select your own):\",\n\t\t\t\t\"fastly logging <provider> create --help\",\n\t\t\t\t\"Uploading package\",\n\t\t\t\t\"Activating service\",\n\t\t\t\t\"SUCCESS: Deployed package (service 12345, version 1)\",\n\t\t\t},\n\t\t},\n\t\t// NOTE: The following test validates [setup] only works for a new service.\n\t\t{\n\t\t\tname: \"success with setup.kv_stores configuration and existing service\",\n\t\t\targs: args(\"compute deploy --service-id 123 --token 123\"),\n\t\t\tapi: mock.API{\n\t\t\t\tActivateVersionFn:   activateVersionOk,\n\t\t\t\tCloneVersionFn:      testutil.CloneVersionResult(4),\n\t\t\t\tCreateBackendFn:     createBackendOK,\n\t\t\t\tGetPackageFn:        getPackageOk,\n\t\t\t\tGetServiceDetailsFn: getServiceDetailsWasm,\n\t\t\t\tGetServiceFn:        getServiceOK,\n\t\t\t\tListDomainsFn:       listDomainsOk,\n\t\t\t\tListVersionsFn:      testutil.ListVersions,\n\t\t\t\tUpdatePackageFn:     updatePackageOk,\n\t\t\t},\n\t\t\thttpClientRes: []*http.Response{\n\t\t\t\tmock.NewHTTPResponse(http.StatusNoContent, nil, nil),\n\t\t\t\tmock.NewHTTPResponse(http.StatusOK, nil, io.NopCloser(strings.NewReader(\"success\"))),\n\t\t\t},\n\t\t\thttpClientErr: []error{\n\t\t\t\tnil,\n\t\t\t\tnil,\n\t\t\t},\n\t\t\tmanifest: `\n\t\t\tname = \"package\"\n\t\t\tmanifest_version = 2\n\t\t\tlanguage = \"rust\"\n\n\t\t\t[setup.kv_stores.store_one]\n\t\t\tdescription = \"My first KV Store\"\n\t\t\t[setup.kv_stores.store_one.items.foo]\n\t\t\tvalue = \"my default value for foo\"\n\t\t\tdescription = \"a good description about foo\"\n\t\t\t[setup.kv_stores.store_one.items.bar]\n\t\t\tvalue = \"my default value for bar\"\n\t\t\tdescription = \"a good description about bar\"\n\t\t\t`,\n\t\t\twantOutput: []string{\n\t\t\t\t\"Uploading package\",\n\t\t\t\t\"Activating service\",\n\t\t\t\t\"SUCCESS: Deployed package (service 123, version 4)\",\n\t\t\t},\n\t\t\tdontWantOutput: []string{\n\t\t\t\t\"Configuring KV Store 'store_one'\",\n\t\t\t\t\"Create a KV Store key called 'foo'\",\n\t\t\t\t\"Create a KV Store key called 'bar'\",\n\t\t\t\t\"Creating KV Store 'store_one'\",\n\t\t\t\t\"Creating KV Store key 'foo'\",\n\t\t\t\t\"Creating KV Store key 'bar'\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"success with setup.kv_stores configuration and no existing service plus use of file and existing store\",\n\t\t\targs: args(\"compute deploy --token 123\"),\n\t\t\tapi: mock.API{\n\t\t\t\tActivateVersionFn:   activateVersionOk,\n\t\t\t\tCreateBackendFn:     createBackendOK,\n\t\t\t\tCreateDomainFn:      createDomainOK,\n\t\t\t\tCreateResourceFn:    createResourceOK,\n\t\t\t\tCreateServiceFn:     createServiceOK,\n\t\t\t\tGetKVStoreFn:        getKVStoreOk,\n\t\t\t\tGetPackageFn:        getPackageOk,\n\t\t\t\tGetServiceDetailsFn: getServiceDetailsWasm,\n\t\t\t\tGetServiceFn:        getServiceOK,\n\t\t\t\tInsertKVStoreKeyFn:  createKVStoreItemOK,\n\t\t\t\tListDomainsFn:       listDomainsOk,\n\t\t\t\tListKVStoresFn:      listKVStoresOk,\n\t\t\t\tUpdatePackageFn:     updatePackageOk,\n\t\t\t},\n\t\t\thttpClientRes: []*http.Response{\n\t\t\t\tmock.NewHTTPResponse(http.StatusNoContent, nil, nil),\n\t\t\t\tmock.NewHTTPResponse(http.StatusOK, nil, io.NopCloser(strings.NewReader(\"success\"))),\n\t\t\t},\n\t\t\thttpClientErr: []error{\n\t\t\t\tnil,\n\t\t\t\tnil,\n\t\t\t},\n\t\t\tmanifest: `\n\t\t\tname = \"package\"\n\t\t\tmanifest_version = 2\n\t\t\tlanguage = \"rust\"\n\n\t\t\t[setup.kv_stores.store_one]\n\t\t\tdescription = \"My first KV Store\"\n\t\t\t[setup.kv_stores.store_one.items.foo]\n\t\t\tvalue = \"my default value for foo\"\n\t\t\tdescription = \"a good description about foo\"\n\t\t\t[setup.kv_stores.store_one.items.bar]\n\t\t\tvalue = \"my default value for bar\"\n\t\t\tdescription = \"a good description about bar\"\n\t\t\t[setup.kv_stores.store_one.items.baz]\n\t\t\tfile = \"./kv_store_one_baz.txt\"\n\t\t\tdescription = \"a file containing the data for this key\"\n\t\t\t`,\n\t\t\tstdin: []string{\n\t\t\t\t\"Y\", // when prompted to create a new service\n\t\t\t},\n\t\t\twantOutput: []string{\n\t\t\t\t\"WARNING: A KV Store called 'store_one' already exists\",\n\t\t\t\t\"Retrieving existing KV Store 'store_one'\",\n\t\t\t\t\"Create a KV Store key called 'foo'\",\n\t\t\t\t\"Create a KV Store key called 'bar'\",\n\t\t\t\t\"Create a KV Store key called 'baz'\",\n\t\t\t\t\"Creating KV Store key 'foo'\",\n\t\t\t\t\"Creating KV Store key 'bar'\",\n\t\t\t\t\"Creating KV Store key 'baz'\",\n\t\t\t\t\"Uploading package\",\n\t\t\t\t\"Activating service\",\n\t\t\t\t\"SUCCESS: Deployed package (service 12345, version 1)\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"error with setup.kv_stores configuration and no existing service with file and value on same key\",\n\t\t\targs: args(\"compute deploy --token 123\"),\n\t\t\tapi: mock.API{\n\t\t\t\tCreateBackendFn:     createBackendOK,\n\t\t\t\tCreateDomainFn:      createDomainOK,\n\t\t\t\tCreateKVStoreFn:     createKVStoreOK,\n\t\t\t\tCreateResourceFn:    createResourceOK,\n\t\t\t\tCreateServiceFn:     createServiceOK,\n\t\t\t\tDeleteServiceFn:     deleteServiceOK,\n\t\t\t\tGetPackageFn:        getPackageOk,\n\t\t\t\tGetServiceDetailsFn: getServiceDetailsWasm,\n\t\t\t\tGetServiceFn:        getServiceOK,\n\t\t\t\tInsertKVStoreKeyFn:  createKVStoreItemOK,\n\t\t\t\tListDomainsFn:       listDomainsOk,\n\t\t\t\tListKVStoresFn:      listKVStoresEmpty,\n\t\t\t},\n\t\t\thttpClientRes: []*http.Response{\n\t\t\t\tmock.NewHTTPResponse(http.StatusNoContent, nil, nil),\n\t\t\t\tmock.NewHTTPResponse(http.StatusOK, nil, io.NopCloser(strings.NewReader(\"success\"))),\n\t\t\t},\n\t\t\thttpClientErr: []error{\n\t\t\t\tnil,\n\t\t\t\tnil,\n\t\t\t},\n\t\t\tmanifest: `\n\t\t\tname = \"package\"\n\t\t\tmanifest_version = 2\n\t\t\tlanguage = \"rust\"\n\n\t\t\t[setup.kv_stores.store_one]\n\t\t\tdescription = \"My first KV Store\"\n\t\t\t[setup.kv_stores.store_one.items.baz]\n      value = \"some_value\"\n\t\t\tfile = \"./kv_store_one_baz.txt\"\n\t\t\tdescription = \"a file containing the data for this key\"\n\t\t\t`,\n\t\t\tstdin: []string{\n\t\t\t\t\"Y\", // when prompted to create a new service\n\t\t\t},\n\t\t\twantOutput: []string{\n\t\t\t\t\"Configuring KV Store 'store_one'\",\n\t\t\t},\n\t\t\twantError: \"invalid config: both 'value' and 'file' were set\",\n\t\t},\n\t\t{\n\t\t\tname: \"success with setup.kv_stores configuration and no existing service and --non-interactive\",\n\t\t\targs: args(\"compute deploy --non-interactive --token 123\"),\n\t\t\tapi: mock.API{\n\t\t\t\tActivateVersionFn:   activateVersionOk,\n\t\t\t\tCreateBackendFn:     createBackendOK,\n\t\t\t\tCreateDomainFn:      createDomainOK,\n\t\t\t\tCreateKVStoreFn:     createKVStoreOK,\n\t\t\t\tCreateResourceFn:    createResourceOK,\n\t\t\t\tCreateServiceFn:     createServiceOK,\n\t\t\t\tGetPackageFn:        getPackageOk,\n\t\t\t\tGetServiceDetailsFn: getServiceDetailsWasm,\n\t\t\t\tGetServiceFn:        getServiceOK,\n\t\t\t\tInsertKVStoreKeyFn:  createKVStoreItemOK,\n\t\t\t\tListDomainsFn:       listDomainsOk,\n\t\t\t\tListKVStoresFn:      listKVStoresEmpty,\n\t\t\t\tUpdatePackageFn:     updatePackageOk,\n\t\t\t},\n\t\t\thttpClientRes: []*http.Response{\n\t\t\t\tmock.NewHTTPResponse(http.StatusNoContent, nil, nil),\n\t\t\t\tmock.NewHTTPResponse(http.StatusOK, nil, io.NopCloser(strings.NewReader(\"success\"))),\n\t\t\t},\n\t\t\thttpClientErr: []error{\n\t\t\t\tnil,\n\t\t\t\tnil,\n\t\t\t},\n\t\t\tmanifest: `\n\t\t\tname = \"package\"\n\t\t\tmanifest_version = 2\n\t\t\tlanguage = \"rust\"\n\n\t\t\t[setup.kv_stores.store_one]\n\t\t\tdescription = \"My first KV Store\"\n\t\t\t[setup.kv_stores.store_one.items.foo]\n\t\t\tvalue = \"my default value for foo\"\n\t\t\tdescription = \"a good description about foo\"\n\t\t\t[setup.kv_stores.store_one.items.bar]\n\t\t\tvalue = \"my default value for bar\"\n\t\t\tdescription = \"a good description about bar\"\n\t\t\t`,\n\t\t\tstdin: []string{\n\t\t\t\t\"Y\", // when prompted to create a new service\n\t\t\t},\n\t\t\twantOutput: []string{\n\t\t\t\t\"Creating KV Store 'store_one'\",\n\t\t\t\t\"Creating KV Store key 'foo'\",\n\t\t\t\t\"Creating KV Store key 'bar'\",\n\t\t\t\t\"Uploading package\",\n\t\t\t\t\"Activating service\",\n\t\t\t\t\"SUCCESS: Deployed package (service 12345, version 1)\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"success with setup.kv_stores configuration and no existing service and no predefined values\",\n\t\t\targs: args(\"compute deploy --token 123\"),\n\t\t\tapi: mock.API{\n\t\t\t\tActivateVersionFn:   activateVersionOk,\n\t\t\t\tCreateBackendFn:     createBackendOK,\n\t\t\t\tCreateDomainFn:      createDomainOK,\n\t\t\t\tCreateKVStoreFn:     createKVStoreOK,\n\t\t\t\tCreateResourceFn:    createResourceOK,\n\t\t\t\tCreateServiceFn:     createServiceOK,\n\t\t\t\tGetPackageFn:        getPackageOk,\n\t\t\t\tGetServiceDetailsFn: getServiceDetailsWasm,\n\t\t\t\tGetServiceFn:        getServiceOK,\n\t\t\t\tInsertKVStoreKeyFn:  createKVStoreItemOK,\n\t\t\t\tListDomainsFn:       listDomainsOk,\n\t\t\t\tListKVStoresFn:      listKVStoresEmpty,\n\t\t\t\tUpdatePackageFn:     updatePackageOk,\n\t\t\t},\n\t\t\thttpClientRes: []*http.Response{\n\t\t\t\tmock.NewHTTPResponse(http.StatusNoContent, nil, nil),\n\t\t\t\tmock.NewHTTPResponse(http.StatusOK, nil, io.NopCloser(strings.NewReader(\"success\"))),\n\t\t\t},\n\t\t\thttpClientErr: []error{\n\t\t\t\tnil,\n\t\t\t\tnil,\n\t\t\t},\n\t\t\tmanifest: `\n\t\t\tname = \"package\"\n\t\t\tmanifest_version = 2\n\t\t\tlanguage = \"rust\"\n\n\t\t\t[setup.kv_stores.store_one]\n\t\t\t[setup.kv_stores.store_one.items.foo]\n\t\t\t[setup.kv_stores.store_one.items.bar]\n\t\t\t`,\n\t\t\tstdin: []string{\n\t\t\t\t\"Y\", // when prompted to create a new service\n\t\t\t},\n\t\t\twantOutput: []string{\n\t\t\t\t\"Configuring KV Store 'store_one'\",\n\t\t\t\t\"Create a KV Store key called 'foo'\",\n\t\t\t\t\"Create a KV Store key called 'bar'\",\n\t\t\t\t\"Creating KV Store 'store_one'\",\n\t\t\t\t\"Creating KV Store key 'foo'\",\n\t\t\t\t\"Creating KV Store key 'bar'\",\n\t\t\t\t\"Uploading package\",\n\t\t\t\t\"Activating service\",\n\t\t\t\t\"SUCCESS: Deployed package (service 12345, version 1)\",\n\t\t\t},\n\t\t\t// The following are predefined values for the `description` and `value`\n\t\t\t// fields from the prior setup.dictionaries tests that we expect to not\n\t\t\t// be present in the stdout/stderr as the [setup/dictionaries]\n\t\t\t// configuration does not define them.\n\t\t\tdontWantOutput: []string{\n\t\t\t\t\"My first KV Store\",\n\t\t\t\t\"my default value for foo\",\n\t\t\t\t\"my default value for bar\",\n\t\t\t},\n\t\t},\n\t\t// NOTE: The following test validates [setup] only works for a new service.\n\t\t{\n\t\t\tname: \"success with setup.secret_stores configuration and existing service\",\n\t\t\targs: args(\"compute deploy --service-id 123 --token 123\"),\n\t\t\tapi: mock.API{\n\t\t\t\tActivateVersionFn:   activateVersionOk,\n\t\t\t\tCloneVersionFn:      testutil.CloneVersionResult(4),\n\t\t\t\tCreateBackendFn:     createBackendOK,\n\t\t\t\tGetPackageFn:        getPackageOk,\n\t\t\t\tGetServiceDetailsFn: getServiceDetailsWasm,\n\t\t\t\tGetServiceFn:        getServiceOK,\n\t\t\t\tListDomainsFn:       listDomainsOk,\n\t\t\t\tListVersionsFn:      testutil.ListVersions,\n\t\t\t\tUpdatePackageFn:     updatePackageOk,\n\t\t\t},\n\t\t\thttpClientRes: []*http.Response{\n\t\t\t\tmock.NewHTTPResponse(http.StatusNoContent, nil, nil),\n\t\t\t\tmock.NewHTTPResponse(http.StatusOK, nil, io.NopCloser(strings.NewReader(\"success\"))),\n\t\t\t},\n\t\t\thttpClientErr: []error{\n\t\t\t\tnil,\n\t\t\t\tnil,\n\t\t\t},\n\t\t\tmanifest: `\n\t\t\tname = \"package\"\n\t\t\tmanifest_version = 2\n\t\t\tlanguage = \"rust\"\n\n\t\t\t[setup.secret_stores.store_one]\n\t\t\tdescription = \"My first Secret Store\"\n\t\t\t[setup.secret_stores.store_one.entries.foo]\n\t\t\tdescription = \"a good description about foo\"\n\t\t\t[setup.secret_stores.store_one.entries.bar]\n\t\t\tdescription = \"a good description about bar\"\n\t\t\t`,\n\t\t\twantOutput: []string{\n\t\t\t\t\"Uploading package\",\n\t\t\t\t\"Activating service\",\n\t\t\t\t\"SUCCESS: Deployed package (service 123, version 4)\",\n\t\t\t},\n\t\t\tdontWantOutput: []string{\n\t\t\t\t\"Configuring Secret Store 'store_one'\",\n\t\t\t\t\"Create a Secret Store entry called 'foo'\",\n\t\t\t\t\"Create a Secret Store entry called 'bar'\",\n\t\t\t\t\"Creating Secret Store 'store_one'\",\n\t\t\t\t\"Creating Secret Store entry 'foo'\",\n\t\t\t\t\"Creating Secret Store entry 'bar'\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"success with setup.secret_stores configuration and no existing service but an existing store\",\n\t\t\targs: args(\"compute deploy --token 123\"),\n\t\t\tapi: mock.API{\n\t\t\t\tActivateVersionFn:   activateVersionOk,\n\t\t\t\tCreateBackendFn:     createBackendOK,\n\t\t\t\tCreateDomainFn:      createDomainOK,\n\t\t\t\tCreateResourceFn:    createResourceOK,\n\t\t\t\tCreateSecretFn:      createSecretOk,\n\t\t\t\tCreateServiceFn:     createServiceOK,\n\t\t\t\tGetPackageFn:        getPackageOk,\n\t\t\t\tGetSecretStoreFn:    getSecretStoreOk,\n\t\t\t\tGetServiceDetailsFn: getServiceDetailsWasm,\n\t\t\t\tGetServiceFn:        getServiceOK,\n\t\t\t\tListDomainsFn:       listDomainsOk,\n\t\t\t\tListSecretStoresFn:  listSecretStoresOk,\n\t\t\t\tUpdatePackageFn:     updatePackageOk,\n\t\t\t},\n\t\t\thttpClientRes: []*http.Response{\n\t\t\t\tmock.NewHTTPResponse(http.StatusNoContent, nil, nil),\n\t\t\t\tmock.NewHTTPResponse(http.StatusOK, nil, io.NopCloser(strings.NewReader(\"success\"))),\n\t\t\t},\n\t\t\thttpClientErr: []error{\n\t\t\t\tnil,\n\t\t\t\tnil,\n\t\t\t},\n\t\t\tmanifest: `\n\t\t\tname = \"package\"\n\t\t\tmanifest_version = 2\n\t\t\tlanguage = \"rust\"\n\n\t\t\t[setup.secret_stores.store_one]\n\t\t\tdescription = \"My first Secret Store\"\n\t\t\t[setup.secret_stores.store_one.entries.foo]\n\t\t\tdescription = \"a good description about foo\"\n\t\t\t[setup.secret_stores.store_one.entries.bar]\n\t\t\tdescription = \"a good description about bar\"\n\t\t\t[setup.secret_stores.store_one.entries.baz]\n\t\t\tdescription = \"a file containing the data for this entry\"\n\t\t\t`,\n\t\t\tstdin: []string{\n\t\t\t\t\"Y\",         // when prompted to create a new service\n\t\t\t\t\"\",          // leave blank for service name prompt\n\t\t\t\t\"\",          // leave blank for backend prompt\n\t\t\t\t\"\",          // leave blank for using existing store prompt\n\t\t\t\t\"my_secret\", // when prompted to add a secret for foo (this can't be empty)\n\t\t\t\t\"my_secret\", // when prompted to add a secret for bar (this can't be empty)\n\t\t\t\t\"my_secret\", // when prompted to add a secret for baz (this can't be empty)\n\t\t\t},\n\t\t\twantOutput: []string{\n\t\t\t\t\"WARNING: A Secret Store called 'store_one' already exists\",\n\t\t\t\t\"Retrieving existing Secret Store 'store_one'\",\n\t\t\t\t\"Create a Secret Store entry called 'foo'\",\n\t\t\t\t\"Create a Secret Store entry called 'bar'\",\n\t\t\t\t\"Create a Secret Store entry called 'baz'\",\n\t\t\t\t\"Creating Secret Store entry 'foo'\",\n\t\t\t\t\"Creating Secret Store entry 'bar'\",\n\t\t\t\t\"Creating Secret Store entry 'baz'\",\n\t\t\t\t\"Uploading package\",\n\t\t\t\t\"Activating service\",\n\t\t\t\t\"SUCCESS: Deployed package (service 12345, version 1)\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"success with setup.secret_stores configuration and no existing service and no predefined values\",\n\t\t\targs: args(\"compute deploy --token 123\"),\n\t\t\tapi: mock.API{\n\t\t\t\tActivateVersionFn:   activateVersionOk,\n\t\t\t\tCreateBackendFn:     createBackendOK,\n\t\t\t\tCreateDomainFn:      createDomainOK,\n\t\t\t\tCreateResourceFn:    createResourceOK,\n\t\t\t\tCreateSecretFn:      createSecretOk,\n\t\t\t\tCreateSecretStoreFn: createSecretStoreOk,\n\t\t\t\tCreateServiceFn:     createServiceOK,\n\t\t\t\tGetPackageFn:        getPackageOk,\n\t\t\t\tGetServiceDetailsFn: getServiceDetailsWasm,\n\t\t\t\tGetServiceFn:        getServiceOK,\n\t\t\t\tListDomainsFn:       listDomainsOk,\n\t\t\t\tListSecretStoresFn:  listSecretStoresEmpty,\n\t\t\t\tUpdatePackageFn:     updatePackageOk,\n\t\t\t},\n\t\t\thttpClientRes: []*http.Response{\n\t\t\t\tmock.NewHTTPResponse(http.StatusNoContent, nil, nil),\n\t\t\t\tmock.NewHTTPResponse(http.StatusOK, nil, io.NopCloser(strings.NewReader(\"success\"))),\n\t\t\t},\n\t\t\thttpClientErr: []error{\n\t\t\t\tnil,\n\t\t\t\tnil,\n\t\t\t},\n\t\t\tmanifest: `\n\t\t\tname = \"package\"\n\t\t\tmanifest_version = 2\n\t\t\tlanguage = \"rust\"\n\n\t\t\t[setup.secret_stores.store_one]\n\t\t\t[setup.secret_stores.store_one.entries.foo]\n\t\t\t[setup.secret_stores.store_one.entries.bar]\n\t\t\t`,\n\t\t\tstdin: []string{\n\t\t\t\t\"Y\",         // when prompted to create a new service\n\t\t\t\t\"\",          // leave blank for service name prompt\n\t\t\t\t\"\",          // leave blank for backend prompt\n\t\t\t\t\"my_secret\", // when prompted to add a secret for foo (this can't be empty)\n\t\t\t\t\"my_secret\", // when prompted to add a secret for bar (this can't be empty)\n\t\t\t},\n\t\t\twantOutput: []string{\n\t\t\t\t\"Configuring Secret Store 'store_one'\",\n\t\t\t\t\"Create a Secret Store entry called 'foo'\",\n\t\t\t\t\"Create a Secret Store entry called 'bar'\",\n\t\t\t\t\"Creating Secret Store 'store_one'\",\n\t\t\t\t\"Creating Secret Store entry 'foo'\",\n\t\t\t\t\"Creating Secret Store entry 'bar'\",\n\t\t\t\t\"Uploading package\",\n\t\t\t\t\"Activating service\",\n\t\t\t\t\"SUCCESS: Deployed package (service 12345, version 1)\",\n\t\t\t},\n\t\t\t// The following are predefined values for the `description` and `value`\n\t\t\t// fields from the prior setup.dictionaries tests that we expect to not\n\t\t\t// be present in the stdout/stderr as the [setup/dictionaries]\n\t\t\t// configuration does not define them.\n\t\t\tdontWantOutput: []string{\n\t\t\t\t\"My first Secret Store\",\n\t\t\t\t\"my default value for foo\",\n\t\t\t\t\"my default value for bar\",\n\t\t\t},\n\t\t},\n\t}\n\tfor testcaseIdx := range scenarios {\n\t\ttestcase := &scenarios[testcaseIdx]\n\t\tt.Run(testcase.name, func(t *testing.T) {\n\t\t\t// Clear FASTLY_SERVICE_ID for tests that create a new service\n\t\t\tif testcase.api.CreateServiceFn != nil {\n\t\t\t\tt.Setenv(\"FASTLY_SERVICE_ID\", \"\")\n\t\t\t}\n\t\t\t// Because the manifest can be mutated on each test scenario, we recreate\n\t\t\t// the file each time.\n\t\t\tmanifestContent := `manifest_version = 2\n\t\t\tname = \"package\"\n\t\t\t`\n\t\t\tif testcase.manifest != \"\" {\n\t\t\t\tmanifestContent = testcase.manifest\n\t\t\t}\n\t\t\tif err := os.WriteFile(filepath.Join(rootdir, manifest.Filename), []byte(manifestContent), 0o600); err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\t// For any test scenario that expects no manifest to exist, then instead\n\t\t\t// of deleting the manifest and having to recreate it, we'll simply\n\t\t\t// rename it, and then rename it back once the specific test scenario has\n\t\t\t// finished running.\n\t\t\tif testcase.noManifest {\n\t\t\t\told := filepath.Join(rootdir, manifest.Filename)\n\t\t\t\ttmp := filepath.Join(rootdir, manifest.Filename+\"Tmp\")\n\t\t\t\tif err := os.Rename(old, tmp); err != nil {\n\t\t\t\t\tt.Fatal(err)\n\t\t\t\t}\n\t\t\t\tdefer func() {\n\t\t\t\t\tif err := os.Rename(tmp, old); err != nil {\n\t\t\t\t\t\tt.Fatal(err)\n\t\t\t\t\t}\n\t\t\t\t}()\n\t\t\t}\n\n\t\t\tvar stdout threadsafe.Buffer\n\t\t\topts := testutil.MockGlobalData(testcase.args, &stdout)\n\t\t\topts.APIClientFactory = mock.APIClient(testcase.api)\n\n\t\t\tif testcase.httpClientRes != nil || testcase.httpClientErr != nil {\n\t\t\t\topts.HTTPClient = mock.HTMLClient(testcase.httpClientRes, testcase.httpClientErr)\n\t\t\t}\n\n\t\t\tif testcase.reduceSizeLimit {\n\t\t\t\tcompute.MaxPackageSize = 1000000 // 1mb (our test package should above this)\n\t\t\t} else {\n\t\t\t\t// As multiple test scenarios run within a single environment instance\n\t\t\t\t// we need to ensure each scenario resets the package variable.\n\t\t\t\tcompute.MaxPackageSize = originalPackageSizeLimit\n\t\t\t}\n\n\t\t\tif len(testcase.stdin) > 1 {\n\t\t\t\t// To handle multiple prompt input from the user we need to do some\n\t\t\t\t// coordination around io pipes to mimic the required user behaviour.\n\t\t\t\tstdin, prompt := io.Pipe()\n\t\t\t\topts.Input = stdin\n\n\t\t\t\t// Wait for user input and write it to the prompt\n\t\t\t\tinputc := make(chan string)\n\t\t\t\tgo func() {\n\t\t\t\t\tfor input := range inputc {\n\t\t\t\t\t\tfmt.Fprintln(prompt, input)\n\t\t\t\t\t}\n\t\t\t\t}()\n\n\t\t\t\t// We need a channel so we wait for `run()` to complete\n\t\t\t\tdone := make(chan bool)\n\n\t\t\t\t// Call `app.Run()` and wait for response\n\t\t\t\tgo func() {\n\t\t\t\t\tapp.Init = func(_ []string, _ io.Reader) (*global.Data, error) {\n\t\t\t\t\t\treturn opts, nil\n\t\t\t\t\t}\n\t\t\t\t\terr = app.Run(testcase.args, nil)\n\t\t\t\t\tdone <- true\n\t\t\t\t}()\n\n\t\t\t\t// User provides input\n\t\t\t\t//\n\t\t\t\t// NOTE: Must provide as much input as is expected to be waited on by `run()`.\n\t\t\t\t//       For example, if `run()` calls `input()` twice, then provide two messages.\n\t\t\t\t//       Otherwise the select statement will trigger the timeout error.\n\t\t\t\tfor _, input := range testcase.stdin {\n\t\t\t\t\tinputc <- input\n\t\t\t\t}\n\n\t\t\t\tselect {\n\t\t\t\tcase <-done:\n\t\t\t\t\t// Wait for app.Run() to finish\n\t\t\t\tcase <-time.After(10 * time.Second):\n\t\t\t\t\tt.Log(stdout.String())\n\t\t\t\t\tt.Fatalf(\"unexpected timeout waiting for mocked prompt inputs to be processed\")\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tstdin := \"\"\n\t\t\t\tif len(testcase.stdin) > 0 {\n\t\t\t\t\tstdin = testcase.stdin[0]\n\t\t\t\t}\n\t\t\t\topts.Input = strings.NewReader(stdin)\n\t\t\t\tapp.Init = func(_ []string, _ io.Reader) (*global.Data, error) {\n\t\t\t\t\treturn opts, nil\n\t\t\t\t}\n\t\t\t\terr = app.Run(testcase.args, nil)\n\t\t\t}\n\n\t\t\tt.Log(stdout.String())\n\n\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\ttestutil.AssertRemediationErrorContains(t, err, testcase.wantRemediationError)\n\n\t\t\tfor _, s := range testcase.wantOutput {\n\t\t\t\ttestutil.AssertStringContains(t, stdout.String(), s)\n\t\t\t}\n\n\t\t\tfor _, s := range testcase.dontWantOutput {\n\t\t\t\ttestutil.AssertStringDoesntContain(t, stdout.String(), s)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestDeploy_ActivateBeacon(t *testing.T) {\n\t// We're going to chdir to a deploy environment,\n\t// so save the PWD to return to, afterwards.\n\tpwd, err := os.Getwd()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t// Create test environment\n\trootdir := testutil.NewEnv(testutil.EnvOpts{\n\t\tT: t,\n\t\tCopy: []testutil.FileIO{\n\t\t\t{\n\t\t\t\tSrc: filepath.Join(\"testdata\", \"deploy\", \"pkg\", \"package.tar.gz\"),\n\t\t\t\tDst: filepath.Join(\"pkg\", \"package.tar.gz\"),\n\t\t\t},\n\t\t},\n\t\tWrite: []testutil.FileIO{\n\t\t\t{\n\t\t\t\tSrc: \"This is my data for the KV Store 'store_one' baz field.\",\n\t\t\t\tDst: \"kv_store_one_baz.txt\",\n\t\t\t},\n\t\t},\n\t})\n\tdefer os.RemoveAll(rootdir)\n\n\t// Before running the test, chdir into the build environment.\n\t// When we're done, chdir back to our original location.\n\t// This is so we can reliably copy the testdata/ fixtures.\n\tif err := os.Chdir(rootdir); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer func() {\n\t\t_ = os.Chdir(pwd)\n\t}()\n\n\tstdout := threadsafe.Buffer{}\n\targs := testutil.SplitArgs(\"compute deploy --auto-yes --non-interactive\")\n\trecordingHTTP := &mock.HTTPClient{\n\t\tResponses: []*http.Response{\n\t\t\t// the body is closed by beacon.Notify\n\t\t\t//nolint: bodyclose\n\t\t\tmock.NewHTTPResponse(http.StatusNoContent, nil, nil),\n\t\t},\n\t\tErrors: []error{\n\t\t\tnil,\n\t\t},\n\t\tIndex:        -1,\n\t\tSaveRequests: true,\n\t}\n\n\tmanifestContent := `\n\tname = \"package\"\n\tmanifest_version = 2\n\tlanguage = \"rust\"\n\t`\n\n\tif err := os.WriteFile(filepath.Join(rootdir, manifest.Filename), []byte(manifestContent), 0o600); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\topts := testutil.MockGlobalData(args, &stdout)\n\topts.HTTPClient = recordingHTTP\n\topts.APIClientFactory = mock.APIClient(mock.API{\n\t\tActivateVersionFn: func(_ context.Context, _ *fastly.ActivateVersionInput) (*fastly.Version, error) {\n\t\t\treturn nil, testutil.Err\n\t\t},\n\t\tCloneVersionFn:      testutil.CloneVersionResult(4),\n\t\tCreateBackendFn:     createBackendOK,\n\t\tCreateServiceFn:     createServiceOK,\n\t\tDeleteServiceFn:     deleteServiceOK,\n\t\tGetPackageFn:        getPackageOk,\n\t\tGetServiceDetailsFn: getServiceDetailsWasm,\n\t\tGetServiceFn:        getServiceOK,\n\t\tGetVersionFn:        testutil.GetVersion,\n\t\tListDomainsFn:       listDomainsOk,\n\t\tListVersionsFn:      testutil.ListVersions,\n\t\tUpdatePackageFn:     updatePackageOk,\n\t})\n\n\tapp.Init = func(_ []string, stdin io.Reader) (*global.Data, error) {\n\t\topts.Input = stdin\n\t\treturn opts, nil\n\t}\n\n\terr = app.Run(args, nil)\n\n\ttestutil.AssertErrorContains(t, err, \"error activating version:\")\n\ttestutil.AssertLength(t, 1, recordingHTTP.Requests)\n\n\tbeaconReq := recordingHTTP.Requests[0]\n\ttestutil.AssertEqual(t, \"fastly-notification-relay.edgecompute.app\", beaconReq.URL.Hostname())\n}\n\nfunc createServiceOK(_ context.Context, i *fastly.CreateServiceInput) (*fastly.Service, error) {\n\treturn &fastly.Service{\n\t\tServiceID: fastly.ToPointer(\"12345\"),\n\t\tName:      i.Name,\n\t\tType:      i.Type,\n\t}, nil\n}\n\nfunc createServiceError(_ context.Context, _ *fastly.CreateServiceInput) (*fastly.Service, error) {\n\treturn nil, testutil.Err\n}\n\nfunc deleteServiceOK(_ context.Context, _ *fastly.DeleteServiceInput) error {\n\treturn nil\n}\n\nfunc createDomainError(_ context.Context, _ *fastly.CreateDomainInput) (*fastly.Domain, error) {\n\treturn nil, testutil.Err\n}\n\nfunc deleteDomainOK(_ context.Context, _ *fastly.DeleteDomainInput) error {\n\treturn nil\n}\n\nfunc createBackendError(_ context.Context, _ *fastly.CreateBackendInput) (*fastly.Backend, error) {\n\treturn nil, testutil.Err\n}\n\nfunc deleteBackendOK(_ context.Context, _ *fastly.DeleteBackendInput) error {\n\treturn nil\n}\n\nfunc getPackageIdentical(_ context.Context, i *fastly.GetPackageInput) (*fastly.Package, error) {\n\treturn &fastly.Package{\n\t\tServiceID:      fastly.ToPointer(i.ServiceID),\n\t\tServiceVersion: fastly.ToPointer(i.ServiceVersion),\n\t\tMetadata: &fastly.PackageMetadata{\n\t\t\tFilesHash: fastly.ToPointer(\"d8786807216a37608ecd0bc2357c86f883faad89043141f0a147f2c186ce0212333d31229399c131539205908f5cf0884ea64552782544ff9b27416cd5b996b2\"),\n\t\t\tHashSum:   fastly.ToPointer(\"bf634ccf8be5c8417cf562466ece47ea61056ddeb07273a3d861e8ad757ed3577bc182006d04093c301467cadfd2b1805eedebd1e7cfa0404c723680f2dbc01e\"),\n\t\t},\n\t}, nil\n}\n\nfunc activateVersionError(_ context.Context, _ *fastly.ActivateVersionInput) (*fastly.Version, error) {\n\treturn nil, testutil.Err\n}\n\nfunc listDomainsError(_ context.Context, _ *fastly.ListDomainsInput) ([]*fastly.Domain, error) {\n\treturn nil, testutil.Err\n}\n\nfunc listDomainsNone(_ context.Context, _ *fastly.ListDomainsInput) ([]*fastly.Domain, error) {\n\treturn []*fastly.Domain{}, nil\n}\n"
  },
  {
    "path": "pkg/commands/compute/dir.go",
    "content": "package compute\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\n\t\"github.com/fastly/cli/pkg/manifest\"\n)\n\n// EnvManifestMsg informs the user that an environment manifest is being used.\nconst EnvManifestMsg = \"Using the '%s' environment manifest (it will be packaged up as %s)\\n\\n\"\n\n// ProjectDirMsg informs the user that we've changed the project directory.\nconst ProjectDirMsg = \"Changed project directory to '%s'\\n\\n\"\n\n// EnvironmentManifest returns the relevant manifest filename, taking into\n// account the user passing an --env flag.\nfunc EnvironmentManifest(env string) (manifestFilename string) {\n\tmanifestFilename = manifest.Filename\n\tif env != \"\" {\n\t\tmanifestFilename = fmt.Sprintf(\"fastly.%s.toml\", env)\n\t}\n\treturn manifestFilename\n}\n\n// ChangeProjectDirectory moves into `dir` and returns its absolute path.\nfunc ChangeProjectDirectory(dir string) (projectDirectory string, err error) {\n\tif dir != \"\" {\n\t\tprojectDirectory, err = filepath.Abs(dir)\n\t\tif err != nil {\n\t\t\treturn \"\", fmt.Errorf(\"failed to construct absolute path to directory '%s': %w\", dir, err)\n\t\t}\n\t\tif err := os.Chdir(projectDirectory); err != nil {\n\t\t\treturn \"\", fmt.Errorf(\"failed to change working directory to '%s': %w\", projectDirectory, err)\n\t\t}\n\t}\n\treturn projectDirectory, nil\n}\n"
  },
  {
    "path": "pkg/commands/compute/doc.go",
    "content": "// Package compute contains commands to manage Compute packages.\npackage compute\n"
  },
  {
    "path": "pkg/commands/compute/hashfiles.go",
    "content": "package compute\n\nimport (\n\t\"bytes\"\n\t\"crypto/sha512\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"sort\"\n\n\t\"github.com/kennygrant/sanitize\"\n\t\"github.com/mholt/archives\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/manifest\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// MaxPackageSize represents the max package size that can be uploaded to the\n// Fastly Package API endpoint.\n//\n// NOTE: This is variable not a constant for the sake of test manipulations.\n// https://www.fastly.com/documentation/guides/compute#limitations-and-constraints\nvar MaxPackageSize int64 = 100000000 // 100MB in bytes\n\n// HashFilesCommand produces a deployable artifact from files on the local disk.\ntype HashFilesCommand struct {\n\targparser.Base\n\n\t// Build fields\n\tdir                   argparser.OptionalString\n\tenv                   argparser.OptionalString\n\tincludeSrc            argparser.OptionalBool\n\tlang                  argparser.OptionalString\n\tmetadataDisable       argparser.OptionalBool\n\tmetadataFilterEnvVars argparser.OptionalString\n\tmetadataShow          argparser.OptionalBool\n\tpackageName           argparser.OptionalString\n\ttimeout               argparser.OptionalInt\n\n\tbuildCmd  *BuildCommand\n\tPackage   string\n\tSkipBuild bool\n}\n\n// NewHashFilesCommand returns a usable command registered under the parent.\nfunc NewHashFilesCommand(parent argparser.Registerer, g *global.Data, build *BuildCommand) *HashFilesCommand {\n\tvar c HashFilesCommand\n\tc.buildCmd = build\n\tc.Globals = g\n\tc.CmdClause = parent.Command(\"hash-files\", \"Generate a SHA512 digest from the contents of the Compute package\")\n\tc.CmdClause.Flag(\"dir\", \"Project directory to build (default: current directory)\").Short('C').Action(c.dir.Set).StringVar(&c.dir.Value)\n\tc.CmdClause.Flag(\"env\", \"The manifest environment config to use (e.g. 'stage' will attempt to read 'fastly.stage.toml')\").Action(c.env.Set).StringVar(&c.env.Value)\n\tc.CmdClause.Flag(\"include-source\", \"Include source code in built package\").Action(c.includeSrc.Set).BoolVar(&c.includeSrc.Value)\n\tc.CmdClause.Flag(\"language\", \"Language type\").Action(c.lang.Set).StringVar(&c.lang.Value)\n\tc.CmdClause.Flag(\"metadata-disable\", \"Disable Wasm binary metadata annotations\").Action(c.metadataDisable.Set).BoolVar(&c.metadataDisable.Value)\n\tc.CmdClause.Flag(\"metadata-filter-envvars\", \"Redact specified environment variables from [scripts.env_vars] using comma-separated list\").Action(c.metadataFilterEnvVars.Set).StringVar(&c.metadataFilterEnvVars.Value)\n\tc.CmdClause.Flag(\"metadata-show\", \"Inspect the Wasm binary metadata\").Action(c.metadataShow.Set).BoolVar(&c.metadataShow.Value)\n\tc.CmdClause.Flag(\"package\", \"Path to a package tar.gz\").Short('p').StringVar(&c.Package)\n\tc.CmdClause.Flag(\"package-name\", \"Package name\").Action(c.packageName.Set).StringVar(&c.packageName.Value)\n\tc.CmdClause.Flag(\"skip-build\", \"Skip the build step\").BoolVar(&c.SkipBuild)\n\tc.CmdClause.Flag(\"timeout\", \"Timeout, in seconds, for the build compilation step\").Action(c.timeout.Set).IntVar(&c.timeout.Value)\n\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *HashFilesCommand) Exec(in io.Reader, out io.Writer) (err error) {\n\tif !c.SkipBuild && c.Package == \"\" {\n\t\terr = c.Build(in, out)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif c.Globals.Verbose() {\n\t\t\ttext.Break(out)\n\t\t}\n\t}\n\n\tvar pkgPath string\n\n\tif c.Package == \"\" {\n\t\tmanifestFilename := EnvironmentManifest(c.env.Value)\n\t\twd, err := os.Getwd()\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to get current working directory: %w\", err)\n\t\t}\n\t\tdefer func() {\n\t\t\t_ = os.Chdir(wd)\n\t\t}()\n\t\tmanifestPath := filepath.Join(wd, manifestFilename)\n\n\t\tprojectDir, err := ChangeProjectDirectory(c.dir.Value)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif projectDir != \"\" {\n\t\t\tif c.Globals.Verbose() {\n\t\t\t\ttext.Info(out, ProjectDirMsg, projectDir)\n\t\t\t}\n\t\t\tmanifestPath = filepath.Join(projectDir, manifestFilename)\n\t\t}\n\n\t\tif projectDir != \"\" || c.env.WasSet {\n\t\t\terr = c.Globals.Manifest.File.Read(manifestPath)\n\t\t} else {\n\t\t\terr = c.Globals.Manifest.File.ReadError()\n\t\t}\n\t\tif err != nil {\n\t\t\tif errors.Is(err, os.ErrNotExist) {\n\t\t\t\terr = fsterr.ErrReadingManifest\n\t\t\t}\n\t\t\tc.Globals.ErrLog.Add(err)\n\t\t\treturn err\n\t\t}\n\n\t\tprojectName, source := c.Globals.Manifest.Name()\n\t\tif source == manifest.SourceUndefined {\n\t\t\treturn fsterr.ErrReadingManifest\n\t\t}\n\t\tpkgPath = filepath.Join(projectDir, \"pkg\", fmt.Sprintf(\"%s.tar.gz\", sanitize.BaseName(projectName)))\n\t} else {\n\t\tpkgPath, err = filepath.Abs(c.Package)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to locate package path '%s': %w\", c.Package, err)\n\t\t}\n\t}\n\n\thash, err := getFilesHash(pkgPath)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\ttext.Output(out, hash)\n\treturn nil\n}\n\n// Build constructs and executes the build logic.\nfunc (c *HashFilesCommand) Build(in io.Reader, out io.Writer) error {\n\toutput := out\n\tif !c.Globals.Verbose() {\n\t\toutput = io.Discard\n\t}\n\tif c.dir.WasSet {\n\t\tc.buildCmd.Flags.Dir = c.dir.Value\n\t}\n\tif c.env.WasSet {\n\t\tc.buildCmd.Flags.Env = c.env.Value\n\t}\n\tif c.includeSrc.WasSet {\n\t\tc.buildCmd.Flags.IncludeSrc = c.includeSrc.Value\n\t}\n\tif c.lang.WasSet {\n\t\tc.buildCmd.Flags.Lang = c.lang.Value\n\t}\n\tif c.packageName.WasSet {\n\t\tc.buildCmd.Flags.PackageName = c.packageName.Value\n\t}\n\tif c.timeout.WasSet {\n\t\tc.buildCmd.Flags.Timeout = c.timeout.Value\n\t}\n\tif c.metadataDisable.WasSet {\n\t\tc.buildCmd.MetadataDisable = c.metadataDisable.Value\n\t}\n\tif c.metadataFilterEnvVars.WasSet {\n\t\tc.buildCmd.MetadataFilterEnvVars = c.metadataFilterEnvVars.Value\n\t}\n\tif c.metadataShow.WasSet {\n\t\tc.buildCmd.MetadataShow = c.metadataShow.Value\n\t}\n\treturn c.buildCmd.Exec(in, output)\n}\n\n// getFilesHash returns a hash of all the files in the package in sorted filename order.\nfunc getFilesHash(pkgPath string) (string, error) {\n\tcontents := make(map[string]*bytes.Buffer)\n\n\tif err := packageFiles(pkgPath, func(f archives.FileInfo) error {\n\t\tentry := f.NameInArchive\n\t\tcontents[entry] = &bytes.Buffer{}\n\t\trc, err := f.Open()\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"error opening %s: %w\", entry, err)\n\t\t}\n\t\tdefer rc.Close()\n\t\tif _, err := io.Copy(contents[entry], rc); err != nil {\n\t\t\treturn fmt.Errorf(\"error reading %s: %w\", entry, err)\n\t\t}\n\t\treturn nil\n\t}); err != nil {\n\t\treturn \"\", err\n\t}\n\n\tkeys := make([]string, 0, len(contents))\n\tfor k := range contents {\n\t\tkeys = append(keys, k)\n\t}\n\tsort.Strings(keys)\n\n\th := sha512.New()\n\tfor _, entry := range keys {\n\t\tif _, err := io.Copy(h, contents[entry]); err != nil {\n\t\t\treturn \"\", fmt.Errorf(\"failed to generate hash from package files: %w\", err)\n\t\t}\n\t}\n\treturn fmt.Sprintf(\"%x\", h.Sum(nil)), nil\n}\n"
  },
  {
    "path": "pkg/commands/compute/init.go",
    "content": "package compute\n\nimport (\n\t\"context\"\n\t\"crypto/rand\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"io/fs\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\tcp \"github.com/otiai10/copy\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/config\"\n\t\"github.com/fastly/cli/pkg/debug\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\tfstexec \"github.com/fastly/cli/pkg/exec\"\n\t\"github.com/fastly/cli/pkg/file\"\n\t\"github.com/fastly/cli/pkg/filesystem\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/internal/beacon\"\n\t\"github.com/fastly/cli/pkg/manifest\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\nvar (\n\tgitRepositoryRegEx        = regexp.MustCompile(`((git|ssh|http(s)?)|(git@[\\w\\.]+))(:(//)?)([\\w\\.@\\:/\\-~]+)(\\.git)?(/)?`)\n\tfastlyOrgRegEx            = regexp.MustCompile(`^https:\\/\\/github\\.com\\/fastly`)\n\tfastlyFileIgnoreListRegEx = regexp.MustCompile(`\\.github|LICENSE|SECURITY\\.md|CHANGELOG\\.md|screenshot\\.png`)\n)\n\n// InitCommand initializes a Compute project package on the local machine.\ntype InitCommand struct {\n\targparser.Base\n\n\t// CloneFrom is the value of the --from flag.\n\t// NOTE: CloneFrom is public so that we can check to see if we need\n\t// a token (to use --from=service-id) or not (to use a git\n\t// repository).\n\tCloneFrom string\n\n\tbranch   string\n\tdir      string\n\tlanguage string\n\ttag      string\n}\n\n// Languages is a list of supported language options.\nvar Languages = []string{\"rust\", \"javascript\", \"go\", \"cpp\", \"other\"}\n\n// NewInitCommand returns a usable command registered under the parent.\nfunc NewInitCommand(parent argparser.Registerer, g *global.Data) *InitCommand {\n\tvar c InitCommand\n\tc.Globals = g\n\n\tc.CmdClause = parent.Command(\"init\", \"Initialize a new Compute package locally\")\n\tc.CmdClause.Flag(\"author\", \"Author(s) of the package\").Short('a').StringsVar(&g.Manifest.File.Authors)\n\tc.CmdClause.Flag(\"branch\", \"Git branch name to clone from package template repository\").Hidden().StringVar(&c.branch)\n\tc.CmdClause.Flag(\"directory\", \"Destination to write the new package, defaulting to the current directory\").Short('p').StringVar(&c.dir)\n\tc.CmdClause.Flag(\"from\", \"Local project directory, or Git repository URL, or URL referencing a .zip/.tar.gz file, containing a package template, or an existing service ID created from a starter kit\").Short('f').StringVar(&c.CloneFrom)\n\tc.CmdClause.Flag(\"language\", \"Language of the package\").Short('l').HintOptions(Languages...).EnumVar(&c.language, Languages...)\n\tc.CmdClause.Flag(\"tag\", \"Git tag name to clone from package template repository\").Hidden().StringVar(&c.tag)\n\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *InitCommand) Exec(in io.Reader, out io.Writer) (err error) {\n\tvar (\n\t\tintroContext      string\n\t\tisExistingService bool\n\t)\n\tif c.CloneFrom != \"\" {\n\t\tisExistingService = text.IsFastlyID(c.CloneFrom)\n\t\tif !isExistingService {\n\t\t\tintroContext = \" (using --from to locate package template)\"\n\t\t}\n\t}\n\n\tif isExistingService {\n\t\ttext.Output(out, \"Initializing Compute project from service %s.\\n\\n\", c.CloneFrom)\n\t} else {\n\t\ttext.Output(out, \"Creating a new Compute project%s.\\n\\n\", introContext)\n\t}\n\ttext.Output(out, \"Press ^C at any time to quit.\")\n\n\tif c.CloneFrom != \"\" && !isExistingService && c.language == \"\" {\n\t\ttext.Warning(out, \"\\nWhen using the --from flag, the project language cannot be inferred. Please either use the --language flag to explicitly set the language or ensure the project's fastly.toml sets a valid language.\")\n\t}\n\n\ttext.Break(out)\n\tcont, notEmpty, err := c.VerifyDirectory(in, out)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\tif !cont {\n\t\ttext.Break(out)\n\t\treturn fsterr.RemediationError{\n\t\t\tInner:       fmt.Errorf(\"project directory not empty\"),\n\t\t\tRemediation: fsterr.ExistingDirRemediation,\n\t\t}\n\t}\n\n\tdefer func(errLog fsterr.LogInterface) {\n\t\tif err != nil {\n\t\t\terrLog.Add(err)\n\t\t}\n\t}(c.Globals.ErrLog)\n\n\twd, err := os.Getwd()\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn fmt.Errorf(\"error determining current directory: %w\", err)\n\t}\n\n\tmf := c.Globals.Manifest.File\n\tif c.Globals.Flags.Quiet {\n\t\tmf.SetQuiet(true)\n\t}\n\tif c.dir == \"\" && !mf.Exists() && c.Globals.Verbose() {\n\t\ttext.Info(out, \"--directory not specified, using current directory\\n\\n\")\n\t\tc.dir = wd\n\t}\n\n\tspinner, err := text.NewSpinner(out)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tdst, err := c.VerifyDestination(spinner)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Directory\": c.dir,\n\t\t})\n\t\treturn err\n\t}\n\tc.dir = dst\n\n\tif notEmpty {\n\t\ttext.Break(out)\n\t}\n\terr = spinner.Process(\"Validating directory permissions\", validateDirectoryPermissions(dst))\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Assign the default auth token email if available.\n\temail := \"\"\n\tif _, at := c.Globals.Config.GetDefaultAuthToken(); at != nil && at.Email != \"\" {\n\t\temail = at.Email\n\t}\n\n\tvar (\n\t\tname    string\n\t\tdesc    string\n\t\tauthors []string\n\t)\n\tif !isExistingService {\n\t\tname, desc, authors, err = c.PromptOrReturn(email, in, out)\n\t\tif err != nil {\n\t\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\t\"Description\": desc,\n\t\t\t\t\"Directory\":   c.dir,\n\t\t\t})\n\t\t\treturn err\n\t\t}\n\t}\n\n\tlanguages := NewLanguages(c.Globals.Config.StarterKits)\n\n\tvar language *Language\n\n\tif c.language == \"\" && c.CloneFrom == \"\" && c.Globals.Manifest.File.Language == \"\" {\n\t\tlanguage, err = c.PromptForLanguage(languages, in, out)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\t// NOTE: The --language flag is an EnumVar, meaning it's already validated.\n\tif c.language != \"\" || mf.Language != \"\" {\n\t\tl := c.language\n\t\tif c.language == \"\" {\n\t\t\tl = mf.Language\n\t\t}\n\t\tfor _, recognisedLanguage := range languages {\n\t\t\tif strings.EqualFold(l, recognisedLanguage.Name) {\n\t\t\t\tlanguage = recognisedLanguage\n\t\t\t}\n\t\t}\n\t}\n\n\tvar from, branch, tag string\n\n\t// If the user doesn't tell us where to clone from, or there is already a\n\t// fastly.toml manifest, or the language they selected was \"other\" (meaning\n\t// they're bringing their own project code), then we'll prompt the user to\n\t// select a starter kit project.\n\ttriggerStarterKitPrompt := c.CloneFrom == \"\" && !mf.Exists() && language.Name != \"other\"\n\tif triggerStarterKitPrompt {\n\t\tfrom, branch, tag, err = c.PromptForStarterKit(language.StarterKits, in, out)\n\t\tif err != nil {\n\t\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\t\"From\":           c.CloneFrom,\n\t\t\t\t\"Branch\":         c.branch,\n\t\t\t\t\"Tag\":            c.tag,\n\t\t\t\t\"Manifest Exist\": false,\n\t\t\t})\n\t\t\treturn err\n\t\t}\n\t\tc.CloneFrom = from\n\t}\n\n\tdefer func() {\n\t\tif triggerStarterKitPrompt || !isExistingService {\n\t\t\treturn\n\t\t}\n\n\t\tevt := beacon.Event{\n\t\t\tName: \"init\",\n\t\t}\n\t\tif err != nil {\n\t\t\tevt.Status = beacon.StatusFail\n\t\t} else {\n\t\t\tevt.Status = beacon.StatusSuccess\n\t\t}\n\n\t\tbErr := beacon.Notify(c.Globals, c.CloneFrom, evt)\n\t\tif bErr != nil {\n\t\t\tc.Globals.ErrLog.Add(bErr)\n\t\t}\n\t}()\n\n\t// There are three situations in which we might fetch something\n\t// here. We might fetch a template if:\n\t//\n\t// 1. --from flag is set to a template repository, or\n\t// 2. user selects starter kit when prompted\n\t//\n\t// Or we fetch an existing, deployed package if\n\t//\n\t// 3. --from flag is set to a serviceID\n\t//\n\t// We don't fetch if the user has indicated their language of choice is\n\t// \"other\" because this means they intend on handling the compilation of code\n\t// that isn't natively supported by the platform.\n\tif c.CloneFrom != \"\" {\n\t\tif !isExistingService {\n\t\t\terr = c.FetchPackageTemplate(branch, tag, file.Archives, spinner, out)\n\t\t\tif err != nil {\n\t\t\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\t\t\"From\":      from,\n\t\t\t\t\t\"Branch\":    branch,\n\t\t\t\t\t\"Tag\":       tag,\n\t\t\t\t\t\"Directory\": c.dir,\n\t\t\t\t})\n\t\t\t\treturn err\n\t\t\t}\n\t\t} else {\n\t\t\tvar (\n\t\t\t\tserviceDetails *fastly.ServiceDetail\n\t\t\t\tpack           *fastly.Package\n\t\t\t\tserviceVersion int\n\t\t\t)\n\t\t\terr = spinner.Process(\"Fetching service details\", func(_ *text.SpinnerWrapper) error {\n\t\t\t\tserviceDetails, err = c.Globals.APIClient.GetServiceDetails(context.TODO(), &fastly.GetServiceDetailsInput{\n\t\t\t\t\tServiceID: c.CloneFrom,\n\t\t\t\t})\n\t\t\t\tif err != nil {\n\t\t\t\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\t\t\t\"From\":      c.CloneFrom,\n\t\t\t\t\t\t\"Directory\": c.dir,\n\t\t\t\t\t})\n\t\t\t\t\tif hErr, ok := err.(*fastly.HTTPError); ok && hErr.IsNotFound() {\n\t\t\t\t\t\treturn fmt.Errorf(\"the service %s could not be found\", c.CloneFrom)\n\t\t\t\t\t}\n\t\t\t\t\treturn err\n\t\t\t\t}\n\n\t\t\t\tif fastly.ToValue(serviceDetails.Type) != \"wasm\" {\n\t\t\t\t\treturn fmt.Errorf(\"service %s is not a Compute service (type is %s)\", c.CloneFrom, fastly.ToValue(serviceDetails.Type))\n\t\t\t\t}\n\n\t\t\t\tif serviceDetails.ActiveVersion != nil {\n\t\t\t\t\tserviceVersion = fastly.ToValue(serviceDetails.ActiveVersion.Number)\n\t\t\t\t\tpack, err = c.Globals.APIClient.GetPackage(context.TODO(), &fastly.GetPackageInput{\n\t\t\t\t\t\tServiceID:      c.CloneFrom,\n\t\t\t\t\t\tServiceVersion: serviceVersion,\n\t\t\t\t\t})\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn err\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tfor i := len(serviceDetails.Versions) - 1; i >= 0; i-- {\n\t\t\t\t\t\tserviceVersion = fastly.ToValue(serviceDetails.Versions[i].Number)\n\t\t\t\t\t\tpack, err = c.Globals.APIClient.GetPackage(context.TODO(), &fastly.GetPackageInput{\n\t\t\t\t\t\t\tServiceID:      c.CloneFrom,\n\t\t\t\t\t\t\tServiceVersion: serviceVersion,\n\t\t\t\t\t\t})\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\tif hErr, ok := err.(*fastly.HTTPError); ok {\n\t\t\t\t\t\t\t\tif hErr.IsNotFound() {\n\t\t\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn err\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif pack != nil {\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// were not able to find any service versions with an\n\t\t\t\t// existing package\n\t\t\t\tif pack == nil {\n\t\t\t\t\treturn fmt.Errorf(\"unable to find any version of service %s with an existing package\", c.CloneFrom)\n\t\t\t\t}\n\n\t\t\t\treturn nil\n\t\t\t})\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tif pack.Metadata != nil {\n\t\t\t\tclonedFrom := fastly.ToValue(pack.Metadata.ClonedFrom)\n\t\t\t\tif serviceVersion > 1 {\n\t\t\t\t\ttext.Info(out, \"\\nService has active versions, not fetching starter kit source\\n\\n\")\n\t\t\t\t} else if gitRepositoryRegEx.MatchString(clonedFrom) {\n\t\t\t\t\terr = spinner.Process(\"Initializing file structure from selected starter kit\", func(*text.SpinnerWrapper) error {\n\t\t\t\t\t\terr := c.ClonePackageFromEndpoint(clonedFrom, \"\", \"\")\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\t\t\t\t\t\"cloned_from\": clonedFrom,\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\treturn fmt.Errorf(\"could not fetch original source code: %w\", err)\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn nil\n\t\t\t\t\t})\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn err\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif pack.Metadata.Name != nil {\n\t\t\t\t\tname = *pack.Metadata.Name\n\t\t\t\t}\n\n\t\t\t\tif name == \"\" {\n\t\t\t\t\tname = *serviceDetails.Name\n\t\t\t\t}\n\n\t\t\t\tif pack.Metadata.Description != nil {\n\t\t\t\t\tdesc = *pack.Metadata.Description\n\t\t\t\t}\n\n\t\t\t\tif desc == \"\" {\n\t\t\t\t\tdesc = fastly.ToValue(serviceDetails.Comment)\n\t\t\t\t}\n\n\t\t\t\tauthors = append(authors, pack.Metadata.Authors...)\n\t\t\t\tmf.Language = fastly.ToValue(pack.Metadata.Language)\n\t\t\t}\n\n\t\t\tmf.Name = name\n\t\t\tmf.ServiceID = *pack.ServiceID\n\t\t\tmf.Description = desc\n\t\t\t// mf.Profile = profileName\n\t\t\tmf.Authors = authors\n\n\t\t\tmp := filepath.Join(c.dir, manifest.Filename)\n\t\t\terr = mf.Write(mp)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"error creating fastly.toml: %w\", err)\n\t\t\t}\n\t\t}\n\t}\n\n\t// If the user was prompted to fill the name/desc/authors/lang, then we insert\n\t// a line break so the following spinner instances have spacing. But only if\n\t// the starter kit wasn't prompted for as that already handles spacing.\n\tif (mf.Name == \"\" || mf.Description == \"\" || mf.Language == \"\" || len(mf.Authors) == 0) && !triggerStarterKitPrompt {\n\t\ttext.Break(out)\n\t}\n\n\tmf, err = c.UpdateManifest(mf, spinner, name, desc, authors, language)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Directory\":   c.dir,\n\t\t\t\"Description\": desc,\n\t\t\t\"Language\":    language,\n\t\t})\n\t\treturn err\n\t}\n\n\tlanguage, err = c.InitializeLanguage(spinner, language, languages, mf.Language, wd)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn fmt.Errorf(\"error initializing package: %w\", err)\n\t}\n\n\tvar md manifest.Data\n\terr = md.File.Read(manifest.Filename)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to read manifest after initialisation: %w\", err)\n\t}\n\n\tpostInit := md.File.Scripts.PostInit\n\tif postInit != \"\" {\n\t\tif !c.Globals.Flags.AutoYes && !c.Globals.Flags.NonInteractive {\n\t\t\tmsg := fmt.Sprintf(CustomPostScriptMessage, \"init\", manifest.Filename)\n\t\t\terr := promptForPostInitContinue(msg, postInit, out, in)\n\t\t\tif err != nil {\n\t\t\t\tif errors.Is(err, fsterr.ErrPostInitStopped) {\n\t\t\t\t\tdisplayInitOutput(mf.Name, dst, language.Name, out)\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\n\t\tif c.Globals.Flags.Verbose && len(md.File.Scripts.EnvVars) > 0 {\n\t\t\ttext.Description(out, \"Environment variables set\", strings.Join(md.File.Scripts.EnvVars, \" \"))\n\t\t}\n\n\t\t// If we're in verbose mode, the command output is shown.\n\t\t// So in that case we don't want to have a spinner as it'll interweave output.\n\t\t// In non-verbose mode we have a spinner running while the command execution is happening.\n\t\tmsg := \"Running [scripts.post_init]...\"\n\t\tif !c.Globals.Flags.Verbose {\n\t\t\terr = spinner.Start()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tspinner.Message(msg)\n\t\t}\n\n\t\ts := Shell{}\n\t\tcommand, args := s.Build(postInit)\n\t\t// gosec flagged this:\n\t\t// G204 (CWE-78): Subprocess launched with function call as argument or cmd arguments\n\t\t// Disabling as we require the user to provide this command.\n\t\t// #nosec\n\t\t// nosemgrep: go.lang.security.audit.dangerous-exec-command.dangerous-exec-command\n\t\terr := fstexec.Command(fstexec.CommandOpts{\n\t\t\tArgs:           args,\n\t\t\tCommand:        command,\n\t\t\tEnv:            md.File.Scripts.EnvVars,\n\t\t\tErrLog:         c.Globals.ErrLog,\n\t\t\tOutput:         out,\n\t\t\tSpinner:        spinner,\n\t\t\tSpinnerMessage: msg,\n\t\t\tTimeout:        0, // zero indicates no timeout\n\t\t\tVerbose:        c.Globals.Flags.Verbose,\n\t\t})\n\t\tif err != nil {\n\t\t\t// In verbose mode we'll have the failure status AFTER the error output.\n\t\t\t// But we can't just call StopFailMessage() without first starting the spinner.\n\t\t\tif c.Globals.Flags.Verbose {\n\t\t\t\ttext.Break(out)\n\t\t\t\tspinErr := spinner.Start()\n\t\t\t\tif spinErr != nil {\n\t\t\t\t\treturn fmt.Errorf(text.SpinnerErrWrapper, spinErr, err)\n\t\t\t\t}\n\t\t\t\tspinner.Message(msg + \"...\")\n\t\t\t\tspinner.StopFailMessage(msg)\n\t\t\t\tif spinErr := spinner.StopFail(); spinErr != nil {\n\t\t\t\t\treturn fmt.Errorf(text.SpinnerErrWrapper, spinErr, err)\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn err\n\t\t}\n\n\t\t// In verbose mode we'll have the failure status AFTER the error output.\n\t\t// But we can't just call StopMessage() without first starting the spinner.\n\t\tif c.Globals.Flags.Verbose {\n\t\t\terr = spinner.Start()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tspinner.Message(msg + \"...\")\n\t\t\ttext.Break(out)\n\t\t}\n\n\t\tspinner.StopMessage(msg)\n\t\terr = spinner.Stop()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tdisplayInitOutput(mf.Name, dst, language.Name, out)\n\treturn nil\n}\n\n// VerifyDirectory indicates if the user wants to continue with the execution\n// flow when presented with a prompt that suggests the current directory isn't\n// empty.\nfunc (c *InitCommand) VerifyDirectory(in io.Reader, out io.Writer) (cont, notEmpty bool, err error) {\n\tflags := c.Globals.Flags\n\tdir := c.dir\n\n\tif dir == \"\" {\n\t\tdir = \".\"\n\t}\n\tdir, err = filepath.Abs(dir)\n\tif err != nil {\n\t\treturn false, false, err\n\t}\n\n\tfiles, err := os.ReadDir(dir)\n\tif err != nil {\n\t\treturn false, false, err\n\t}\n\n\tif strings.Contains(dir, \" \") && !flags.Quiet {\n\t\ttext.Warning(out, \"Your project path contains spaces. In some cases this can result in issues with your installed language toolchain, e.g. `npm`. Consider removing any spaces.\\n\\n\")\n\t}\n\n\tif len(files) > 0 && !flags.AutoYes && !flags.NonInteractive {\n\t\tlabel := fmt.Sprintf(\"The current directory isn't empty. Are you sure you want to initialize a Compute project in %s? [y/N] \", dir)\n\t\tresult, err := text.AskYesNo(out, label, in)\n\t\tif err != nil {\n\t\t\treturn false, true, err\n\t\t}\n\t\treturn result, true, nil\n\t}\n\n\treturn true, false, nil\n}\n\n// VerifyDestination checks the provided path exists and is a directory.\n//\n// NOTE: For validating user permissions it will create a temporary file within\n// the directory and then remove it before returning the absolute path to the\n// directory itself.\nfunc (c *InitCommand) VerifyDestination(spinner text.Spinner) (dst string, err error) {\n\tdst, err = filepath.Abs(c.dir)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tfi, err := os.Stat(dst)\n\tif err != nil && !errors.Is(err, fs.ErrNotExist) {\n\t\treturn dst, fmt.Errorf(\"couldn't verify package directory: %w\", err) // generic error\n\t}\n\tif err == nil && !fi.IsDir() {\n\t\treturn dst, fmt.Errorf(\"package destination is not a directory\") // specific problem\n\t}\n\tif err != nil && errors.Is(err, fs.ErrNotExist) { // normal-ish case\n\t\terr := spinner.Process(fmt.Sprintf(\"Creating %s\", dst), func(_ *text.SpinnerWrapper) error {\n\t\t\tif err := os.MkdirAll(dst, 0o700); err != nil {\n\t\t\t\treturn fmt.Errorf(\"error creating package destination: %w\", err)\n\t\t\t}\n\t\t\treturn nil\n\t\t})\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t}\n\treturn dst, nil\n}\n\nfunc validateDirectoryPermissions(dst string) text.SpinnerProcess {\n\treturn func(_ *text.SpinnerWrapper) error {\n\t\ttmpname := make([]byte, 16)\n\t\tn, err := rand.Read(tmpname)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"error generating random filename: %w\", err)\n\t\t}\n\t\tif n != 16 {\n\t\t\treturn fmt.Errorf(\"failed to generate enough entropy (%d/%d)\", n, 16)\n\t\t}\n\n\t\t// gosec flagged this:\n\t\t// G304 (CWE-22): Potential file inclusion via variable\n\t\t//\n\t\t// Disabling as the input is determined by our own package.\n\t\t// #nosec\n\t\tf, err := os.Create(filepath.Join(dst, fmt.Sprintf(\"tmp_%x\", tmpname)))\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"error creating file in package destination: %w\", err)\n\t\t}\n\n\t\tif err := f.Close(); err != nil {\n\t\t\treturn fmt.Errorf(\"error closing file in package destination: %w\", err)\n\t\t}\n\n\t\tif err := os.Remove(f.Name()); err != nil {\n\t\t\treturn fmt.Errorf(\"error removing file in package destination: %w\", err)\n\t\t}\n\t\treturn nil\n\t}\n}\n\n// PromptOrReturn will prompt the user for information missing from the\n// fastly.toml manifest file, otherwise if it already exists then the value is\n// returned as is.\nfunc (c *InitCommand) PromptOrReturn(email string, in io.Reader, out io.Writer) (name, description string, authors []string, err error) {\n\tflags := c.Globals.Flags\n\tname, _ = c.Globals.Manifest.Name()\n\tdescription, _ = c.Globals.Manifest.Description()\n\tauthors, _ = c.Globals.Manifest.Authors()\n\n\tif name == \"\" && !flags.AcceptDefaults && !flags.NonInteractive {\n\t\ttext.Break(out)\n\t}\n\tname, err = c.PromptPackageName(flags, name, in, out)\n\tif err != nil {\n\t\treturn \"\", description, authors, err\n\t}\n\n\tif description == \"\" && !flags.AcceptDefaults && !flags.NonInteractive {\n\t\ttext.Break(out)\n\t}\n\tdescription, err = promptPackageDescription(flags, description, in, out)\n\tif err != nil {\n\t\treturn name, \"\", authors, err\n\t}\n\n\tif len(authors) == 0 && !flags.AcceptDefaults && !flags.NonInteractive {\n\t\ttext.Break(out)\n\t}\n\tauthors, err = promptPackageAuthors(flags, authors, email, in, out)\n\tif err != nil {\n\t\treturn name, description, []string{}, err\n\t}\n\n\treturn name, description, authors, nil\n}\n\n// PromptPackageName prompts the user for a package name unless already defined either\n// via the corresponding CLI flag or the manifest file.\n//\n// It will use a default of the current directory path if no value provided by\n// the user via the prompt.\nfunc (c *InitCommand) PromptPackageName(flags global.Flags, name string, in io.Reader, out io.Writer) (string, error) {\n\tdefaultName := filepath.Base(c.dir)\n\n\tif name == \"\" && (flags.AcceptDefaults || flags.NonInteractive) {\n\t\treturn defaultName, nil\n\t}\n\n\tif name == \"\" {\n\t\tvar err error\n\t\tname, err = text.Input(out, fmt.Sprintf(\"Name: [%s] \", defaultName), in)\n\t\tif err != nil {\n\t\t\treturn \"\", fmt.Errorf(\"error reading input: %w\", err)\n\t\t}\n\t\tif name == \"\" {\n\t\t\tname = defaultName\n\t\t}\n\t}\n\n\treturn name, nil\n}\n\n// promptPackageDescription prompts the user for a package description unless already\n// defined either via the corresponding CLI flag or the manifest file.\nfunc promptPackageDescription(flags global.Flags, desc string, in io.Reader, out io.Writer) (string, error) {\n\tif desc == \"\" && (flags.AcceptDefaults || flags.NonInteractive) {\n\t\treturn desc, nil\n\t}\n\n\tif desc == \"\" {\n\t\tvar err error\n\n\t\tdesc, err = text.Input(out, \"Description: \", in)\n\t\tif err != nil {\n\t\t\treturn \"\", fmt.Errorf(\"error reading input: %w\", err)\n\t\t}\n\t}\n\n\treturn desc, nil\n}\n\n// promptPackageAuthors prompts the user for a package name unless already defined\n// either via the corresponding CLI flag or the manifest file.\n//\n// It will use a default of the user's email found within the manifest, if set\n// there, otherwise the value will be an empty slice.\n//\n// FIXME: Handle prompting for multiple authors.\nfunc promptPackageAuthors(flags global.Flags, authors []string, manifestEmail string, in io.Reader, out io.Writer) ([]string, error) {\n\tdefaultValue := []string{manifestEmail}\n\tif len(authors) == 0 && (flags.AcceptDefaults || flags.NonInteractive) {\n\t\treturn defaultValue, nil\n\t}\n\tif len(authors) == 0 {\n\t\tlabel := \"Author (email): \"\n\n\t\tif manifestEmail != \"\" {\n\t\t\tlabel = fmt.Sprintf(\"%s[%s] \", label, manifestEmail)\n\t\t}\n\n\t\tauthor, err := text.Input(out, label, in)\n\t\tif err != nil {\n\t\t\treturn []string{}, fmt.Errorf(\"error reading input %w\", err)\n\t\t}\n\n\t\tif author != \"\" {\n\t\t\tauthors = []string{author}\n\t\t} else {\n\t\t\tauthors = defaultValue\n\t\t}\n\t}\n\n\treturn authors, nil\n}\n\n// PromptForLanguage prompts the user for a package language unless already\n// defined either via the corresponding CLI flag or the manifest file.\nfunc (c *InitCommand) PromptForLanguage(languages []*Language, in io.Reader, out io.Writer) (*Language, error) {\n\tvar (\n\t\tlanguage *Language\n\t\toption   string\n\t\terr      error\n\t)\n\tflags := c.Globals.Flags\n\n\tif !flags.AcceptDefaults && !flags.NonInteractive {\n\t\ttext.Output(out, \"\\n%s\", text.Bold(\"Language:\"))\n\t\ttext.Output(out, \"(Find out more about language support at https://www.fastly.com/documentation/guides/compute)\")\n\t\tfor i, lang := range languages {\n\t\t\ttext.Output(out, \"[%d] %s\", i+1, lang.DisplayName)\n\t\t}\n\n\t\ttext.Break(out)\n\t\toption, err = text.Input(out, \"Choose option: [1] \", in, validateLanguageOption(languages))\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"reading input %w\", err)\n\t\t}\n\t}\n\n\tif option == \"\" {\n\t\toption = \"1\"\n\t}\n\n\ti, err := strconv.Atoi(option)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to identify chosen language\")\n\t}\n\tlanguage = languages[i-1]\n\n\treturn language, nil\n}\n\n// validateLanguageOption ensures the user selects an appropriate value from\n// the prompt options displayed.\nfunc validateLanguageOption(languages []*Language) func(string) error {\n\treturn func(input string) error {\n\t\terrMsg := fmt.Errorf(\"must be a valid option\")\n\t\tif input == \"\" {\n\t\t\treturn nil\n\t\t}\n\t\tif option, err := strconv.Atoi(input); err == nil {\n\t\t\tif option > len(languages) {\n\t\t\t\treturn errMsg\n\t\t\t}\n\t\t\treturn nil\n\t\t}\n\t\treturn errMsg\n\t}\n}\n\n// PromptForStarterKit prompts the user for a package starter kit.\n//\n// It returns the path to the starter kit, and the corresponding branch/tag.\nfunc (c *InitCommand) PromptForStarterKit(kits []config.StarterKit, in io.Reader, out io.Writer) (from string, branch string, tag string, err error) {\n\tvar option string\n\tflags := c.Globals.Flags\n\n\tif !flags.AcceptDefaults && !flags.NonInteractive {\n\t\ttext.Output(out, \"\\n%s\", text.Bold(\"Starter kit:\"))\n\t\tfor i, kit := range kits {\n\t\t\tfmt.Fprintf(out, \"[%d] %s\\n\", i+1, text.Bold(kit.Name))\n\t\t\ttext.Indent(out, 4, \"%s\\n%s\", kit.Description, kit.Path)\n\t\t}\n\t\ttext.Info(out, \"\\nFor a complete list of Starter Kits:\")\n\t\ttext.Indent(out, 4, \"https://www.fastly.com/documentation/solutions/starters\")\n\t\ttext.Break(out)\n\n\t\toption, err = text.Input(out, \"Choose option or paste git URL: [1] \", in, validateTemplateOptionOrURL(kits))\n\t\tif err != nil {\n\t\t\treturn \"\", \"\", \"\", fmt.Errorf(\"error reading input: %w\", err)\n\t\t}\n\t\ttext.Break(out)\n\t}\n\n\tif option == \"\" {\n\t\toption = \"1\"\n\t}\n\n\tvar i int\n\tif i, err = strconv.Atoi(option); err == nil {\n\t\ttemplate := kits[i-1]\n\t\treturn template.Path, template.Branch, template.Tag, nil\n\t}\n\n\treturn option, \"\", \"\", nil\n}\n\nfunc validateTemplateOptionOrURL(templates []config.StarterKit) func(string) error {\n\treturn func(input string) error {\n\t\tmsg := \"must be a valid option or git URL\"\n\t\tif input == \"\" {\n\t\t\treturn nil\n\t\t}\n\t\tif option, err := strconv.Atoi(input); err == nil {\n\t\t\tif option > len(templates) {\n\t\t\t\treturn errors.New(msg)\n\t\t\t}\n\t\t\treturn nil\n\t\t}\n\t\tif !gitRepositoryRegEx.MatchString(input) {\n\t\t\treturn errors.New(msg)\n\t\t}\n\t\treturn nil\n\t}\n}\n\n// FetchPackageTemplate will determine if the package code should be fetched\n// from GitHub using the git binary to clone the source or a HTTP request that\n// uses content-negotiation to determine the type of archive format used.\nfunc (c *InitCommand) FetchPackageTemplate(branch, tag string, archives []file.Archive, spinner text.Spinner, out io.Writer) error {\n\terr := spinner.Start()\n\tif err != nil {\n\t\treturn err\n\t}\n\ttext.Break(out)\n\tmsg := \"Fetching package template\"\n\tspinner.Message(msg + \"...\")\n\n\t// If the user has provided a local file path, we'll recursively copy the\n\t// directory to c.dir.\n\tif fi, err := os.Stat(c.CloneFrom); err == nil && fi.IsDir() {\n\t\tif err := cp.Copy(c.CloneFrom, c.dir); err != nil {\n\t\t\tspinner.StopFailMessage(msg)\n\t\t\tif spinErr := spinner.StopFail(); spinErr != nil {\n\t\t\t\treturn fmt.Errorf(text.SpinnerErrWrapper, spinErr, err)\n\t\t\t}\n\t\t\treturn err\n\t\t}\n\t\tspinner.StopMessage(msg)\n\t\treturn spinner.Stop()\n\t}\n\tc.Globals.ErrLog.Add(err)\n\n\t// If this isn't a local file path, it should be a URL.\n\tu, err := url.Parse(c.CloneFrom)\n\tif err != nil {\n\t\tspinner.StopFailMessage(msg)\n\t\tif spinErr := spinner.StopFail(); spinErr != nil {\n\t\t\treturn fmt.Errorf(text.SpinnerErrWrapper, spinErr, err)\n\t\t}\n\t\treturn fmt.Errorf(\"could not read --from URL: %w\", err)\n\t}\n\n\t// If given an opaque string, the scheme and host are typically\n\t// empty and the string ends up in u.Path.\n\tif u.Host == \"\" && u.Scheme == \"\" {\n\t\tspinner.StopFailMessage(msg)\n\t\tif spinErr := spinner.StopFail(); spinErr != nil {\n\t\t\treturn fmt.Errorf(text.SpinnerErrWrapper, spinErr, err)\n\t\t}\n\t\treturn fmt.Errorf(\"--from url seems invalid: %s\", c.CloneFrom)\n\t}\n\n\treq, err := http.NewRequest(http.MethodGet, u.String(), nil)\n\tif err != nil {\n\t\terr = fmt.Errorf(\"failed to construct package request URL: %w\", err)\n\t\tc.Globals.ErrLog.Add(err)\n\n\t\tif gitRepositoryRegEx.MatchString(c.CloneFrom) {\n\t\t\tif err := c.ClonePackageFromEndpoint(c.CloneFrom, branch, tag); err != nil {\n\t\t\t\tspinner.StopFailMessage(msg)\n\t\t\t\tif spinErr := spinner.StopFail(); spinErr != nil {\n\t\t\t\t\treturn fmt.Errorf(text.SpinnerErrWrapper, spinErr, err)\n\t\t\t\t}\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tspinner.StopMessage(msg)\n\t\t\treturn spinner.Stop()\n\t\t}\n\n\t\tspinner.StopFailMessage(msg)\n\t\tif spinErr := spinner.StopFail(); spinErr != nil {\n\t\t\treturn fmt.Errorf(text.SpinnerErrWrapper, spinErr, err)\n\t\t}\n\t\treturn err\n\t}\n\n\tfor _, archive := range archives {\n\t\tfor _, mime := range archive.MimeTypes() {\n\t\t\treq.Header.Add(\"Accept\", mime)\n\t\t}\n\t}\n\n\tif c.Globals.Flags.Debug {\n\t\tdebug.DumpHTTPRequest(req)\n\t}\n\tres, err := c.Globals.HTTPClient.Do(req)\n\tif c.Globals.Flags.Debug {\n\t\tdebug.DumpHTTPResponse(res)\n\t}\n\n\tif err != nil {\n\t\terr = fmt.Errorf(\"failed to get package '%s': %w\", req.URL.String(), err)\n\t\tc.Globals.ErrLog.Add(err)\n\t\tspinner.StopFailMessage(msg)\n\t\tif spinErr := spinner.StopFail(); spinErr != nil {\n\t\t\treturn fmt.Errorf(text.SpinnerErrWrapper, spinErr, err)\n\t\t}\n\t\treturn err\n\t}\n\tdefer res.Body.Close() // #nosec G307\n\n\tif res.StatusCode != http.StatusOK {\n\t\terr := fmt.Errorf(\"failed to get package '%s': %s\", req.URL.String(), res.Status)\n\t\tc.Globals.ErrLog.Add(err)\n\t\tspinner.StopFailMessage(msg)\n\t\tif spinErr := spinner.StopFail(); spinErr != nil {\n\t\t\treturn fmt.Errorf(text.SpinnerErrWrapper, spinErr, err)\n\t\t}\n\t\treturn err\n\t}\n\n\ttempdir, err := tempDir(\"package-init-download\")\n\tif err != nil {\n\t\terr = fmt.Errorf(\"error creating temporary path for package template download: %w\", err)\n\t\tc.Globals.ErrLog.Add(err)\n\t\tspinner.StopFailMessage(msg)\n\t\tif spinErr := spinner.StopFail(); spinErr != nil {\n\t\t\treturn fmt.Errorf(text.SpinnerErrWrapper, spinErr, err)\n\t\t}\n\t\treturn err\n\t}\n\tdefer os.RemoveAll(tempdir)\n\n\tfilename := filepath.Join(\n\t\ttempdir,\n\t\tfilepath.Base(c.CloneFrom),\n\t)\n\text := filepath.Ext(filename)\n\n\t// gosec flagged this:\n\t// G304 (CWE-22): Potential file inclusion via variable\n\t//\n\t// Disabling as we require a user to configure their own environment.\n\t/* #nosec */\n\tf, err := os.Create(filename)\n\tif err != nil {\n\t\terr = fmt.Errorf(\"failed to create local %s archive: %w\", filename, err)\n\t\tc.Globals.ErrLog.Add(err)\n\t\tspinner.StopFailMessage(msg)\n\t\tif spinErr := spinner.StopFail(); spinErr != nil {\n\t\t\treturn fmt.Errorf(text.SpinnerErrWrapper, spinErr, err)\n\t\t}\n\t\treturn err\n\t}\n\n\t_, err = io.Copy(f, res.Body)\n\tif err != nil {\n\t\terr = fmt.Errorf(\"failed to write %s archive to disk: %w\", filename, err)\n\t\tc.Globals.ErrLog.Add(err)\n\t\tspinner.StopFailMessage(msg)\n\t\tif spinErr := spinner.StopFail(); spinErr != nil {\n\t\t\treturn fmt.Errorf(text.SpinnerErrWrapper, spinErr, err)\n\t\t}\n\t\treturn err\n\t}\n\n\t// NOTE: We used to `defer` the closing of the file after its creation but\n\t// realised that this caused issues on Windows as it was unable to rename the\n\t// file as we still have the descriptor `f` open.\n\tif err := f.Close(); err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t}\n\n\tvar archive file.Archive\n\nmimes:\n\tfor _, mimetype := range res.Header.Values(\"Content-Type\") {\n\t\tfor _, a := range archives {\n\t\t\tfor _, mime := range a.MimeTypes() {\n\t\t\t\tif mimetype == mime {\n\t\t\t\t\tarchive = a\n\t\t\t\t\tbreak mimes\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tif archive == nil {\n\t\tfor _, a := range archives {\n\t\t\tfor _, e := range a.Extensions() {\n\t\t\t\tif ext == e {\n\t\t\t\t\tarchive = a\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tif archive != nil {\n\t\t// Ensure there is a file extension on our filename, otherwise we won't\n\t\t// know what type of archive format we're dealing with when we come to call\n\t\t// the archive.Extract() method.\n\t\tif ext == \"\" {\n\t\t\tfilenameWithExt := filename + archive.Extensions()[0]\n\t\t\terr := os.Rename(filename, filenameWithExt)\n\t\t\tif err != nil {\n\t\t\t\tc.Globals.ErrLog.Add(err)\n\t\t\t\tspinner.StopFailMessage(msg)\n\t\t\t\tif spinErr := spinner.StopFail(); spinErr != nil {\n\t\t\t\t\treturn fmt.Errorf(text.SpinnerErrWrapper, spinErr, err)\n\t\t\t\t}\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tfilename = filenameWithExt\n\t\t}\n\n\t\tarchive.SetDestination(c.dir)\n\t\tarchive.SetFilename(filename)\n\n\t\terr = archive.Extract()\n\t\tif err != nil {\n\t\t\terr = fmt.Errorf(\"failed to extract %s archive content: %w\", filename, err)\n\t\t\tc.Globals.ErrLog.Add(err)\n\t\t\tspinner.StopFailMessage(msg)\n\t\t\tif spinErr := spinner.StopFail(); spinErr != nil {\n\t\t\t\treturn fmt.Errorf(text.SpinnerErrWrapper, spinErr, err)\n\t\t\t}\n\t\t\treturn err\n\t\t}\n\n\t\tspinner.StopMessage(msg)\n\t\treturn spinner.Stop()\n\t}\n\n\tif err := c.ClonePackageFromEndpoint(c.CloneFrom, branch, tag); err != nil {\n\t\tspinner.StopFailMessage(msg)\n\t\tif spinErr := spinner.StopFail(); spinErr != nil {\n\t\t\treturn fmt.Errorf(text.SpinnerErrWrapper, spinErr, err)\n\t\t}\n\t\treturn err\n\t}\n\n\tspinner.StopMessage(msg)\n\treturn spinner.Stop()\n}\n\n// ClonePackageFromEndpoint clones the given repo (from) into a temp directory,\n// then copies specific files to the destination directory (path).\nfunc (c *InitCommand) ClonePackageFromEndpoint(from, branch, tag string) error {\n\t_, err := exec.LookPath(\"git\")\n\tif err != nil {\n\t\treturn fsterr.RemediationError{\n\t\t\tInner:       fmt.Errorf(\"`git` not found in $PATH\"),\n\t\t\tRemediation: fmt.Sprintf(\"The Fastly CLI requires a local installation of git.  For installation instructions for your operating system see:\\n\\n\\t$ %s\", text.Bold(\"https://git-scm.com/book/en/v2/Getting-Started-Installing-Git\")),\n\t\t}\n\t}\n\n\ttempdir, err := tempDir(\"package-init\")\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error creating temporary path for package template: %w\", err)\n\t}\n\tdefer os.RemoveAll(tempdir)\n\n\tif branch != \"\" && tag != \"\" {\n\t\treturn fmt.Errorf(\"cannot use both git branch and tag name\")\n\t}\n\n\targs := []string{\n\t\t\"clone\",\n\t\t\"--depth\",\n\t\t\"1\",\n\t}\n\tvar ref string\n\tif branch != \"\" {\n\t\tref = branch\n\t}\n\tif tag != \"\" {\n\t\tref = tag\n\t}\n\tif ref != \"\" {\n\t\targs = append(args, \"--branch\", ref)\n\t}\n\targs = append(args, from, tempdir)\n\n\t// gosec flagged this:\n\t// G204 (CWE-78): Subprocess launched with variable\n\t// Disabling as there should be no vulnerability to cloning a remote repo.\n\t/* #nosec */\n\tcommand := exec.Command(\"git\", args...)\n\n\t// nosemgrep (invalid-usage-of-modified-variable)\n\tstdoutStderr, err := command.CombinedOutput()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error fetching package template: %w\\n\\n%s\", err, stdoutStderr)\n\t}\n\n\tif err := os.RemoveAll(filepath.Join(tempdir, \".git\")); err != nil {\n\t\treturn fmt.Errorf(\"error removing git metadata from package template: %w\", err)\n\t}\n\n\terr = filepath.Walk(tempdir, func(path string, info os.FileInfo, err error) error {\n\t\tif err != nil {\n\t\t\treturn err // abort\n\t\t}\n\n\t\tif info.IsDir() {\n\t\t\treturn nil // descend\n\t\t}\n\n\t\trel, err := filepath.Rel(tempdir, path)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\t// Filter any files we want to ignore in Fastly-owned templates.\n\t\tif fastlyOrgRegEx.MatchString(from) && fastlyFileIgnoreListRegEx.MatchString(rel) {\n\t\t\treturn nil\n\t\t}\n\n\t\tdst := filepath.Join(c.dir, rel)\n\t\tif err := os.MkdirAll(filepath.Dir(dst), 0o750); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\treturn filesystem.CopyFile(path, dst)\n\t})\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error copying files from package template: %w\", err)\n\t}\n\n\treturn nil\n}\n\nfunc tempDir(prefix string) (abspath string, err error) {\n\tabspath, err = filepath.Abs(filepath.Join(\n\t\tos.TempDir(),\n\t\tfmt.Sprintf(\"%s-%d\", prefix, time.Now().UnixNano()),\n\t))\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tif err = os.MkdirAll(abspath, 0o750); err != nil {\n\t\treturn \"\", err\n\t}\n\n\treturn abspath, nil\n}\n\n// UpdateManifest updates the manifest with data acquired from various sources.\n// e.g. prompting the user, existing manifest file.\n//\n// NOTE: The language argument might be nil (if the user passes --from flag).\nfunc (c *InitCommand) UpdateManifest(m manifest.File, spinner text.Spinner, name, desc string, authors []string, language *Language) (manifest.File, error) {\n\tvar returnEarly bool\n\tmp := filepath.Join(c.dir, manifest.Filename)\n\n\terr := spinner.Process(\"Reading fastly.toml\", func(_ *text.SpinnerWrapper) error {\n\t\tif err := m.Read(mp); err != nil {\n\t\t\tif language != nil {\n\t\t\t\tif language.Name == \"other\" {\n\t\t\t\t\t// We create a fastly.toml manifest on behalf of the user if they're\n\t\t\t\t\t// bringing their own pre-compiled Wasm binary to be packaged.\n\t\t\t\t\tm.ManifestVersion = manifest.ManifestLatestVersion\n\t\t\t\t\tm.Name = name\n\t\t\t\t\tm.Description = desc\n\t\t\t\t\tm.Authors = authors\n\t\t\t\t\tm.Language = language.Name\n\t\t\t\t\tm.ClonedFrom = c.CloneFrom\n\t\t\t\t\tif err := m.Write(mp); err != nil {\n\t\t\t\t\t\treturn fmt.Errorf(\"error saving fastly.toml: %w\", err)\n\t\t\t\t\t}\n\t\t\t\t\treturnEarly = true\n\t\t\t\t\treturn nil // EXIT updateManifest\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn fmt.Errorf(\"error reading fastly.toml: %w\", err)\n\t\t}\n\t\treturn nil\n\t})\n\tif err != nil {\n\t\treturn m, err\n\t}\n\tif returnEarly {\n\t\treturn m, nil\n\t}\n\n\terr = spinner.Process(fmt.Sprintf(\"Setting package name in manifest to %q\", name), func(_ *text.SpinnerWrapper) error {\n\t\tm.Name = name\n\t\treturn nil\n\t})\n\tif err != nil {\n\t\treturn m, err\n\t}\n\n\tvar descMsg string\n\tif desc != \"\" {\n\t\tdescMsg = \" to '\" + desc + \"'\"\n\t}\n\n\terr = spinner.Process(fmt.Sprintf(\"Setting description in manifest%s\", descMsg), func(_ *text.SpinnerWrapper) error {\n\t\t// NOTE: We allow an empty description to be set.\n\t\tm.Description = desc\n\t\treturn nil\n\t})\n\tif err != nil {\n\t\treturn m, err\n\t}\n\n\tif len(authors) > 0 {\n\t\terr = spinner.Process(fmt.Sprintf(\"Setting authors in manifest to '%s'\", strings.Join(authors, \", \")), func(_ *text.SpinnerWrapper) error {\n\t\t\tm.Authors = authors\n\t\t\treturn nil\n\t\t})\n\t\tif err != nil {\n\t\t\treturn m, err\n\t\t}\n\t}\n\n\tif language != nil {\n\t\terr = spinner.Process(fmt.Sprintf(\"Setting language in manifest to '%s'\", language.Name), func(_ *text.SpinnerWrapper) error {\n\t\t\tm.Language = language.Name\n\t\t\treturn nil\n\t\t})\n\t\tif err != nil {\n\t\t\treturn m, err\n\t\t}\n\t}\n\n\tm.ClonedFrom = c.CloneFrom\n\n\terr = spinner.Process(\"Saving manifest changes\", func(_ *text.SpinnerWrapper) error {\n\t\tif err := m.Write(mp); err != nil {\n\t\t\treturn fmt.Errorf(\"error saving fastly.toml: %w\", err)\n\t\t}\n\t\treturn nil\n\t})\n\treturn m, err\n}\n\n// InitializeLanguage for newly cloned package.\nfunc (c *InitCommand) InitializeLanguage(spinner text.Spinner, language *Language, languages []*Language, name, wd string) (*Language, error) {\n\terr := spinner.Process(\"Initializing package\", func(_ *text.SpinnerWrapper) error {\n\t\tif wd != c.dir {\n\t\t\terr := os.Chdir(c.dir)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"error changing to your project directory: %w\", err)\n\t\t\t}\n\t\t}\n\n\t\t// Language will not be set if user provides the --from flag. So we'll check\n\t\t// the manifest content and ensure what's set there is the language instance\n\t\t// used for the sake of `compute build` operations.\n\t\tif language == nil {\n\t\t\tvar match bool\n\t\t\tfor _, l := range languages {\n\t\t\t\tif strings.EqualFold(name, l.Name) {\n\t\t\t\t\tlanguage = l\n\t\t\t\t\tmatch = true\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif !match {\n\t\t\t\treturn fmt.Errorf(\"unrecognised package language\")\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn language, nil\n}\n\n// promptForPostInitContinue ensures the user is happy to continue with running\n// the define post_init script in the fastly.toml manifest file.\nfunc promptForPostInitContinue(msg, script string, out io.Writer, in io.Reader) error {\n\ttext.Info(out, \"\\n%s:\\n\", msg)\n\ttext.Indent(out, 4, \"%s\", script)\n\n\tlabel := \"\\nDo you want to run this now? [y/N] \"\n\tanswer, err := text.AskYesNo(out, label, in)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif !answer {\n\t\treturn fsterr.ErrPostInitStopped\n\t}\n\ttext.Break(out)\n\treturn nil\n}\n\n// displayInitOutput of package information and useful links.\nfunc displayInitOutput(name, dst, language string, out io.Writer) {\n\ttext.Break(out)\n\ttext.Description(out, fmt.Sprintf(\"Initialized package %s to\", text.Bold(name)), dst)\n\n\tif language == \"other\" {\n\t\ttext.Description(out, \"To package a pre-compiled Wasm binary for deployment, run\", \"fastly compute pack\")\n\t\ttext.Description(out, \"To deploy the package, run\", \"fastly compute deploy\")\n\t} else {\n\t\ttext.Description(out, \"To publish the package (build and deploy), run\", \"fastly compute publish\")\n\t}\n\n\ttext.Description(out, \"To learn about deploying Compute projects using third-party orchestration tools, visit\", \"https://www.fastly.com/documentation/guides/integrations/orchestration\")\n\ttext.Success(out, \"Initialized package %s\", text.Bold(name))\n}\n"
  },
  {
    "path": "pkg/commands/compute/init_test.go",
    "content": "package compute_test\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\t\"net/http\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/app\"\n\t\"github.com/fastly/cli/pkg/config\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/manifest\"\n\t\"github.com/fastly/cli/pkg/mock\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n\t\"github.com/fastly/cli/pkg/threadsafe\"\n)\n\nfunc TestInit(t *testing.T) {\n\targs := testutil.SplitArgs\n\tif os.Getenv(\"TEST_COMPUTE_INIT\") == \"\" {\n\t\tt.Log(\"skipping test\")\n\t\tt.Skip(\"Set TEST_COMPUTE_INIT to run this test\")\n\t}\n\n\tskRust := []config.StarterKit{\n\t\t{\n\t\t\tName:   \"Default\",\n\t\t\tPath:   \"https://github.com/fastly/compute-starter-kit-rust-default\",\n\t\t\tBranch: \"main\",\n\t\t},\n\t}\n\tskJS := []config.StarterKit{\n\t\t{\n\t\t\tName:   \"Default\",\n\t\t\tPath:   \"https://github.com/fastly/compute-starter-kit-javascript-default\",\n\t\t\tBranch: \"main\",\n\t\t},\n\t}\n\tskCPP := []config.StarterKit{\n\t\t{\n\t\t\tName:   \"Default\",\n\t\t\tPath:   \"https://github.com/fastly/compute-starter-kit-cpp-default\",\n\t\t\tBranch: \"main\",\n\t\t},\n\t\t{\n\t\t\tName:   \"Empty\",\n\t\t\tPath:   \"https://github.com/fastly/compute-starter-kit-cpp-empty\",\n\t\t\tBranch: \"main\",\n\t\t},\n\t}\n\n\tscenarios := []struct {\n\t\tname             string\n\t\targs             []string\n\t\tconfigFile       config.File\n\t\thttpClientRes    []*http.Response\n\t\thttpClientErr    []error\n\t\tmanifest         string\n\t\twantFiles        []string\n\t\tunwantedFiles    []string\n\t\twantError        string\n\t\twantOutput       []string\n\t\tmanifestIncludes string\n\t\tmanifestPath     string\n\t\tstdin            string\n\t\tsetupSteps       func() error\n\t}{\n\t\t{\n\t\t\tname:      \"broken endpoint\",\n\t\t\targs:      args(\"compute init --from https://example.com/i-dont-exist\"),\n\t\t\twantError: \"failed to get package 'https://example.com/i-dont-exist': Not Found\",\n\t\t\thttpClientRes: []*http.Response{\n\t\t\t\t{\n\t\t\t\t\tBody:       io.NopCloser(strings.NewReader(\"\")),\n\t\t\t\t\tStatus:     http.StatusText(http.StatusNotFound),\n\t\t\t\t\tStatusCode: http.StatusNotFound,\n\t\t\t\t},\n\t\t\t},\n\t\t\thttpClientErr: []error{\n\t\t\t\tnil,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"name prompt\",\n\t\t\targs: args(\"compute init\"),\n\t\t\tconfigFile: config.File{\n\t\t\t\tStarterKits: config.StarterKitLanguages{\n\t\t\t\t\tRust: skRust,\n\t\t\t\t},\n\t\t\t},\n\t\t\tstdin: \"foobar\", // expect the first prompt to be for the package name.\n\t\t\twantOutput: []string{\n\t\t\t\t\"Fetching package template\",\n\t\t\t\t\"Reading fastly.toml\",\n\t\t\t},\n\t\t\tmanifestIncludes: `name = \"foobar\"`,\n\t\t},\n\t\t{\n\t\t\tname: \"description prompt empty\",\n\t\t\targs: args(\"compute init\"),\n\t\t\tconfigFile: config.File{\n\t\t\t\tStarterKits: config.StarterKitLanguages{\n\t\t\t\t\tRust: skRust,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantOutput: []string{\n\t\t\t\t\"Fetching package template\",\n\t\t\t\t\"Reading fastly.toml\",\n\t\t\t},\n\t\t\tmanifestIncludes: `description = \"\"`, // expect this to be empty\n\t\t},\n\t\t{\n\t\t\tname: \"with author\",\n\t\t\targs: args(\"compute init --author test@example.com\"),\n\t\t\tconfigFile: config.File{\n\t\t\t\tStarterKits: config.StarterKitLanguages{\n\t\t\t\t\tRust: skRust,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantOutput: []string{\n\t\t\t\t\"Fetching package template\",\n\t\t\t\t\"Reading fastly.toml\",\n\t\t\t},\n\t\t\tmanifestIncludes: `authors = [\"test@example.com\"]`,\n\t\t},\n\t\t{\n\t\t\tname: \"with multiple authors\",\n\t\t\targs: args(\"compute init --author test1@example.com --author test2@example.com\"),\n\t\t\tconfigFile: config.File{\n\t\t\t\tStarterKits: config.StarterKitLanguages{\n\t\t\t\t\tRust: skRust,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantOutput: []string{\n\t\t\t\t\"Fetching package template\",\n\t\t\t\t\"Reading fastly.toml\",\n\t\t\t},\n\t\t\tmanifestIncludes: `authors = [\"test1@example.com\", \"test2@example.com\"]`,\n\t\t},\n\t\t{\n\t\t\tname: \"with --from set to starter kit repository\",\n\t\t\targs: args(\"compute init --from https://github.com/fastly/compute-starter-kit-rust-default\"),\n\t\t\tconfigFile: config.File{\n\t\t\t\tStarterKits: config.StarterKitLanguages{\n\t\t\t\t\tRust: []config.StarterKit{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName: \"Default\",\n\t\t\t\t\t\t\tPath: \"https://github.com/fastly/compute-starter-kit-rust-default.git\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantOutput: []string{\n\t\t\t\t\"Fetching package template\",\n\t\t\t\t\"Reading fastly.toml\",\n\t\t\t\t\"SUCCESS: Initialized package\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"with --from set to starter kit repository when dir with same name exists in pwd\",\n\t\t\targs: args(\"compute init --auto-yes --from https://github.com/fastly/compute-starter-kit-rust-default\"),\n\t\t\tconfigFile: config.File{\n\t\t\t\tStarterKits: config.StarterKitLanguages{\n\t\t\t\t\tRust: []config.StarterKit{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName: \"Default\",\n\t\t\t\t\t\t\tPath: \"https://github.com/fastly/compute-starter-kit-rust-default.git\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantOutput: []string{\n\t\t\t\t\"Fetching package template\",\n\t\t\t\t\"Reading fastly.toml\",\n\t\t\t\t\"SUCCESS: Initialized package\",\n\t\t\t},\n\t\t\tsetupSteps: func() error {\n\t\t\t\treturn os.MkdirAll(\"compute-starter-kit-rust-default\", 0o755)\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"with --from set to starter kit repository with .git extension and branch\",\n\t\t\targs: args(\"compute init --from https://github.com/fastly/compute-starter-kit-rust-default.git --branch main\"),\n\t\t\tconfigFile: config.File{\n\t\t\t\tStarterKits: config.StarterKitLanguages{\n\t\t\t\t\tRust: []config.StarterKit{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName: \"Default\",\n\t\t\t\t\t\t\tPath: \"https://github.com/fastly/compute-starter-kit-rust-default.git\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantOutput: []string{\n\t\t\t\t\"Fetching package template\",\n\t\t\t\t\"Reading fastly.toml\",\n\t\t\t\t\"SUCCESS: Initialized package\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"with --from set to starter kit repository with .git extension and branch when dir with same name exists in pwd\",\n\t\t\targs: args(\"compute init --auto-yes --from https://github.com/fastly/compute-starter-kit-rust-default.git --branch main\"),\n\t\t\tconfigFile: config.File{\n\t\t\t\tStarterKits: config.StarterKitLanguages{\n\t\t\t\t\tRust: []config.StarterKit{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName: \"Default\",\n\t\t\t\t\t\t\tPath: \"https://github.com/fastly/compute-starter-kit-rust-default.git\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantOutput: []string{\n\t\t\t\t\"Fetching package template\",\n\t\t\t\t\"Reading fastly.toml\",\n\t\t\t\t\"SUCCESS: Initialized package\",\n\t\t\t},\n\t\t\tsetupSteps: func() error {\n\t\t\t\treturn os.MkdirAll(\"compute-starter-kit-rust-default.git\", 0o755)\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"with --from set to zip archive\",\n\t\t\targs: args(\"compute init --from https://github.com/fastly/compute-starter-kit-rust-default/archive/refs/heads/main.zip\"),\n\t\t\tconfigFile: config.File{\n\t\t\t\tStarterKits: config.StarterKitLanguages{\n\t\t\t\t\tRust: []config.StarterKit{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName: \"Default\",\n\t\t\t\t\t\t\tPath: \"https://github.com/fastly/compute-starter-kit-rust-default.git\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantOutput: []string{\n\t\t\t\t\"Fetching package template\",\n\t\t\t\t\"Reading fastly.toml\",\n\t\t\t\t\"SUCCESS: Initialized package\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"with --from set to zip archive when file with same name exists in pwd\",\n\t\t\targs: args(\"compute init --auto-yes --from https://github.com/fastly/compute-starter-kit-rust-default/archive/refs/heads/main.zip\"),\n\t\t\tconfigFile: config.File{\n\t\t\t\tStarterKits: config.StarterKitLanguages{\n\t\t\t\t\tRust: []config.StarterKit{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName: \"Default\",\n\t\t\t\t\t\t\tPath: \"https://github.com/fastly/compute-starter-kit-rust-default.git\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantOutput: []string{\n\t\t\t\t\"Fetching package template\",\n\t\t\t\t\"Reading fastly.toml\",\n\t\t\t\t\"SUCCESS: Initialized package\",\n\t\t\t},\n\t\t\tsetupSteps: func() error {\n\t\t\t\tfile, err := os.Create(\"main.zip\")\n\t\t\t\tif file != nil {\n\t\t\t\t\tdefer file.Close()\n\t\t\t\t}\n\t\t\t\treturn err\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"with --from set to tar.gz archive\",\n\t\t\targs: args(\"compute init --from https://github.com/Integralist/devnull/files/7339887/compute-starter-kit-rust-default-main.tar.gz\"),\n\t\t\tconfigFile: config.File{\n\t\t\t\tStarterKits: config.StarterKitLanguages{\n\t\t\t\t\tRust: []config.StarterKit{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName: \"Default\",\n\t\t\t\t\t\t\tPath: \"https://github.com/fastly/compute-starter-kit-rust-default.git\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantOutput: []string{\n\t\t\t\t\"Fetching package template\",\n\t\t\t\t\"Reading fastly.toml\",\n\t\t\t\t\"SUCCESS: Initialized package\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"with existing fastly.toml\",\n\t\t\targs: args(\"compute init --auto-yes\"), // --force will ignore a directory that isn't empty\n\t\t\tconfigFile: config.File{\n\t\t\t\tStarterKits: config.StarterKitLanguages{\n\t\t\t\t\tRust: skRust,\n\t\t\t\t},\n\t\t\t},\n\t\t\tmanifest: `\n\t\t\tmanifest_version = 2\n\t\t\tservice_id = 1234\n\t\t\tname = \"test\"\n\t\t\tlanguage = \"rust\"\n\t\t\tdescription = \"test\"\n\t\t\tauthors = [\"test@fastly.com\"]`,\n\t\t\twantOutput: []string{\n\t\t\t\t\"Reading fastly.toml\",\n\t\t\t\t\"Saving manifest changes\",\n\t\t\t\t\"Initializing package\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"no args and no user profiles means no email set for author field\",\n\t\t\targs: args(\"compute init\"),\n\t\t\tconfigFile: config.File{\n\t\t\t\tStarterKits: config.StarterKitLanguages{\n\t\t\t\t\tRust: skRust,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantFiles: []string{\n\t\t\t\t\"Cargo.toml\",\n\t\t\t\t\"fastly.toml\",\n\t\t\t\t\"src/main.rs\",\n\t\t\t},\n\t\t\tunwantedFiles: []string{\n\t\t\t\t\"SECURITY.md\",\n\t\t\t},\n\t\t\twantOutput: []string{\n\t\t\t\t\"Author (email):\",\n\t\t\t\t\"Language:\",\n\t\t\t\t\"Fetching package template\",\n\t\t\t\t\"Reading fastly.toml\",\n\t\t\t\t\"Saving manifest changes\",\n\t\t\t\t\"Initializing package\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"no args but email defaults to config.toml value in author field\",\n\t\t\targs: args(\"compute init\"),\n\t\t\tconfigFile: config.File{\n\t\t\t\tAuth: config.Auth{\n\t\t\t\t\tDefault: \"user\",\n\t\t\t\t\tTokens: config.AuthTokens{\n\t\t\t\t\t\t\"user\": &config.AuthToken{\n\t\t\t\t\t\t\tType:  config.AuthTokenTypeStatic,\n\t\t\t\t\t\t\tEmail: \"test@example.com\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"non_default\": &config.AuthToken{\n\t\t\t\t\t\t\tType:  config.AuthTokenTypeStatic,\n\t\t\t\t\t\t\tEmail: \"no-default@example.com\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tStarterKits: config.StarterKitLanguages{\n\t\t\t\t\tRust: skRust,\n\t\t\t\t},\n\t\t\t},\n\t\t\tmanifestIncludes: `authors = [\"test@example.com\"]`,\n\t\t\twantFiles: []string{\n\t\t\t\t\"Cargo.toml\",\n\t\t\t\t\"fastly.toml\",\n\t\t\t\t\"src/main.rs\",\n\t\t\t},\n\t\t\tunwantedFiles: []string{\n\t\t\t\t\"SECURITY.md\",\n\t\t\t},\n\t\t\twantOutput: []string{\n\t\t\t\t\"Fetching package template\",\n\t\t\t\t\"Reading fastly.toml\",\n\t\t\t\t\"Saving manifest changes\",\n\t\t\t\t\"Initializing package\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"non empty directory\",\n\t\t\targs: args(\"compute init\"),\n\t\t\tconfigFile: config.File{\n\t\t\t\tStarterKits: config.StarterKitLanguages{\n\t\t\t\t\tRust: skRust,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantError: \"project directory not empty\",\n\t\t\tmanifest: `\n\t\t\tmanifest_version = 2\n\t\t\tname = \"test\"`,\n\t\t},\n\t\t{\n\t\t\tname: \"with default name inferred from directory\",\n\t\t\targs: args(\"compute init\"),\n\t\t\tconfigFile: config.File{\n\t\t\t\tStarterKits: config.StarterKitLanguages{\n\t\t\t\t\tRust: skRust,\n\t\t\t\t},\n\t\t\t},\n\t\t\tmanifestIncludes: `name = \"fastly-temp`,\n\t\t},\n\t\t{\n\t\t\tname: \"with directory name inferred from --directory\",\n\t\t\targs: args(\"compute init --directory ./foo\"),\n\t\t\tconfigFile: config.File{\n\t\t\t\tStarterKits: config.StarterKitLanguages{\n\t\t\t\t\tRust: skRust,\n\t\t\t\t},\n\t\t\t},\n\t\t\tstdin:            \"Y\",\n\t\t\tmanifest:         `manifest_version = 2`,\n\t\t\tmanifestPath:     \"foo\",\n\t\t\tmanifestIncludes: `name = \"foo`,\n\t\t},\n\t\t{\n\t\t\tname: \"with JavaScript language\",\n\t\t\targs: args(\"compute init --language javascript\"),\n\t\t\tconfigFile: config.File{\n\t\t\t\tStarterKits: config.StarterKitLanguages{\n\t\t\t\t\tJavaScript: skJS,\n\t\t\t\t},\n\t\t\t},\n\t\t\tmanifestIncludes: `name = \"fastly-temp`,\n\t\t},\n\t\t{\n\t\t\tname: \"with C++ language\",\n\t\t\targs: args(\"compute init --language cpp\"),\n\t\t\tconfigFile: config.File{\n\t\t\t\tStarterKits: config.StarterKitLanguages{\n\t\t\t\t\tCPP: skCPP,\n\t\t\t\t},\n\t\t\t},\n\t\t\tmanifestIncludes: `name = \"fastly-temp`,\n\t\t},\n\t\t{\n\t\t\tname: \"with --from set to C++ empty starter kit\",\n\t\t\targs: args(\"compute init --from https://github.com/fastly/compute-starter-kit-cpp-empty\"),\n\t\t\tconfigFile: config.File{\n\t\t\t\tStarterKits: config.StarterKitLanguages{\n\t\t\t\t\tCPP: skCPP,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantOutput: []string{\n\t\t\t\t\"Fetching package template\",\n\t\t\t\t\"Reading fastly.toml\",\n\t\t\t\t\"SUCCESS: Initialized package\",\n\t\t\t},\n\t\t},\n\t\t// NOTE: This test verifies that we don't fetch a remote project.\n\t\t// Whether that be a starter kit or custom project template.\n\t\t// This is because \"other\" indicates an unsupported platform language.\n\t\t{\n\t\t\tname:             \"with pre-compiled Wasm binary\",\n\t\t\targs:             args(\"compute init --language other\"),\n\t\t\tmanifestIncludes: `language = \"other\"`,\n\t\t\twantOutput: []string{\n\t\t\t\t\"Initialized package\",\n\t\t\t\t\"To package a pre-compiled Wasm binary for deployment\",\n\t\t\t\t\"SUCCESS: Initialized package\",\n\t\t\t},\n\t\t},\n\t}\n\tfor _, testcase := range scenarios {\n\t\tt.Run(testcase.name, func(t *testing.T) {\n\t\t\t// We're going to chdir to an init environment,\n\t\t\t// so save the PWD to return to, afterwards.\n\t\t\tpwd, err := os.Getwd()\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\tmanifestPath := filepath.Join(testcase.manifestPath, manifest.Filename)\n\n\t\t\t// Create test environment\n\t\t\trootdir := testutil.NewEnv(testutil.EnvOpts{\n\t\t\t\tT: t,\n\t\t\t\tWrite: []testutil.FileIO{\n\t\t\t\t\t{Src: testcase.manifest, Dst: manifestPath},\n\t\t\t\t},\n\t\t\t})\n\t\t\tdefer os.RemoveAll(rootdir)\n\n\t\t\t// Before running the test, chdir into the init environment.\n\t\t\t// When we're done, chdir back to our original location.\n\t\t\t// This is so we can reliably assert file structure.\n\t\t\tif err := os.Chdir(rootdir); err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tdefer func() {\n\t\t\t\t_ = os.Chdir(pwd)\n\t\t\t}()\n\n\t\t\t// Before running the test, run some steps to initialize the environment.\n\t\t\tif testcase.setupSteps != nil {\n\t\t\t\tif err := testcase.setupSteps(); err != nil {\n\t\t\t\t\tt.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tvar stdout threadsafe.Buffer\n\t\t\tapp.Init = func(_ []string, _ io.Reader) (*global.Data, error) {\n\t\t\t\topts := testutil.MockGlobalData(testcase.args, &stdout)\n\t\t\t\topts.Config = testcase.configFile\n\n\t\t\t\tif testcase.httpClientRes != nil || testcase.httpClientErr != nil {\n\t\t\t\t\topts.HTTPClient = mock.HTMLClient(testcase.httpClientRes, testcase.httpClientErr)\n\t\t\t\t}\n\n\t\t\t\t// we need to define stdin as the init process prompts the user multiple\n\t\t\t\t// times, but we don't need to provide any values as all our prompts will\n\t\t\t\t// fallback to default values if the input is unrecognised.\n\t\t\t\topts.Input = strings.NewReader(testcase.stdin)\n\t\t\t\treturn opts, nil\n\t\t\t}\n\t\t\terr = app.Run(testcase.args, nil)\n\n\t\t\tt.Log(stdout.String())\n\n\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\tfor _, file := range testcase.wantFiles {\n\t\t\t\tif _, err := os.Stat(filepath.Join(rootdir, file)); err != nil {\n\t\t\t\t\tt.Errorf(\"wanted file %s not found\", file)\n\t\t\t\t}\n\t\t\t}\n\t\t\tfor _, file := range testcase.unwantedFiles {\n\t\t\t\tif _, err := os.Stat(filepath.Join(rootdir, file)); !errors.Is(err, os.ErrNotExist) {\n\t\t\t\t\tt.Errorf(\"unwanted file %s found\", file)\n\t\t\t\t}\n\t\t\t}\n\t\t\tfor _, s := range testcase.wantOutput {\n\t\t\t\ttestutil.AssertStringContains(t, stdout.String(), s)\n\t\t\t}\n\t\t\tif testcase.manifestIncludes != \"\" {\n\t\t\t\tcontent, err := os.ReadFile(filepath.Join(rootdir, manifestPath))\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatal(err)\n\t\t\t\t}\n\t\t\t\ttestutil.AssertStringContains(t, string(content), testcase.manifestIncludes)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestInit_ExistingService(t *testing.T) {\n\tserviceID := fastly.NullString(\"LsyQ2UXDGk6d4ENjvgqTN4\")\n\tcustomerID := fastly.NullString(\"YflD2HKQTx6q4RAwitdGA4\")\n\tpackageID := fastly.NullString(\"4AGdtiwAR4q6xTQKH2DlfY\")\n\n\tscenarios := []struct {\n\t\tname              string\n\t\targs              []string\n\t\tgetServiceDetails func(context.Context, *fastly.GetServiceDetailsInput) (*fastly.ServiceDetail, error)\n\t\tgetPackage        func(context.Context, *fastly.GetPackageInput) (*fastly.Package, error)\n\t\texpectInOutput    []string\n\t\texpectInManifest  []string\n\t\texpectNoManifest  bool\n\t\texpectInError     string\n\t\tsuppressBeacon    bool\n\t}{\n\t\t{\n\t\t\tname: \"when the service exists\",\n\t\t\targs: testutil.SplitArgs(\"compute init --from LsyQ2UXDGk6d4ENjvgqTN4\"),\n\t\t\tgetServiceDetails: func(_ context.Context, gsi *fastly.GetServiceDetailsInput) (*fastly.ServiceDetail, error) {\n\t\t\t\tif gsi.ServiceID != *serviceID {\n\t\t\t\t\treturn nil, &fastly.HTTPError{\n\t\t\t\t\t\tStatusCode: http.StatusNotFound,\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn &fastly.ServiceDetail{\n\t\t\t\t\tServiceID:  serviceID,\n\t\t\t\t\tCustomerID: customerID,\n\t\t\t\t\tComment:    fastly.ToPointer(\"\"),\n\t\t\t\t\tName:       fastly.ToPointer(\"example service\"),\n\t\t\t\t\tType:       fastly.ToPointer(\"wasm\"),\n\t\t\t\t\tActiveVersion: &fastly.Version{\n\t\t\t\t\t\tNumber: fastly.ToPointer(1),\n\t\t\t\t\t},\n\t\t\t\t}, nil\n\t\t\t},\n\t\t\tgetPackage: func(_ context.Context, gpi *fastly.GetPackageInput) (*fastly.Package, error) {\n\t\t\t\tif gpi.ServiceID != *serviceID || gpi.ServiceVersion != 1 {\n\t\t\t\t\treturn nil, &fastly.HTTPError{\n\t\t\t\t\t\tStatusCode: http.StatusNotFound,\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn &fastly.Package{\n\t\t\t\t\tPackageID: packageID,\n\t\t\t\t\tServiceID: serviceID,\n\t\t\t\t\tMetadata: &fastly.PackageMetadata{\n\t\t\t\t\t\tAuthors:     []string{\"author@example.com\"},\n\t\t\t\t\t\tDescription: fastly.NullString(\"a description\"),\n\t\t\t\t\t\tName:        fastly.NullString(\"test-package\"),\n\t\t\t\t\t\tLanguage:    fastly.NullString(\"rust\"),\n\t\t\t\t\t},\n\t\t\t\t}, nil\n\t\t\t},\n\t\t\texpectInOutput: []string{\n\t\t\t\t\"Initializing Compute project from service LsyQ2UXDGk6d4ENjvgqTN4.\",\n\t\t\t\t\"SUCCESS: Initialized package test-package\",\n\t\t\t},\n\t\t\texpectInManifest: []string{\n\t\t\t\t`name = \"test-package\"`,\n\t\t\t\t`authors = [\"author@example.com\"]`,\n\t\t\t\t`description = \"a description\"`,\n\t\t\t\t`language = \"rust\"`,\n\t\t\t\t`service_id = \"LsyQ2UXDGk6d4ENjvgqTN4\"`,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"when the service doesn't exist\",\n\t\t\targs: testutil.SplitArgs(\"compute init --from LsyQ2UXDGk6d4ENjvgqTN4\"),\n\t\t\tgetServiceDetails: func(_ context.Context, _ *fastly.GetServiceDetailsInput) (*fastly.ServiceDetail, error) {\n\t\t\t\treturn nil, &fastly.HTTPError{\n\t\t\t\t\tStatusCode: http.StatusNotFound,\n\t\t\t\t}\n\t\t\t},\n\t\t\texpectInOutput: []string{\n\t\t\t\t\"Initializing Compute project from service LsyQ2UXDGk6d4ENjvgqTN4.\",\n\t\t\t},\n\t\t\texpectInError:    \"the service LsyQ2UXDGk6d4ENjvgqTN4 could not be found\",\n\t\t\texpectNoManifest: true,\n\t\t},\n\t\t{\n\t\t\tname: \"service has no versions that include package metadata\",\n\t\t\targs: testutil.SplitArgs(\"compute init --from LsyQ2UXDGk6d4ENjvgqTN4\"),\n\t\t\tgetServiceDetails: func(_ context.Context, _ *fastly.GetServiceDetailsInput) (*fastly.ServiceDetail, error) {\n\t\t\t\treturn &fastly.ServiceDetail{\n\t\t\t\t\tServiceID:     serviceID,\n\t\t\t\t\tName:          fastly.NullString(\"test-service\"),\n\t\t\t\t\tComment:       fastly.NullString(\"\"),\n\t\t\t\t\tType:          fastly.NullString(\"wasm\"),\n\t\t\t\t\tActiveVersion: nil,\n\t\t\t\t\tVersions: []*fastly.Version{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tActive:   fastly.ToPointer(false),\n\t\t\t\t\t\t\tDeployed: fastly.ToPointer(false),\n\t\t\t\t\t\t\tLocked:   fastly.ToPointer(false),\n\t\t\t\t\t\t\tNumber:   fastly.ToPointer(1),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}, nil\n\t\t\t},\n\t\t\tgetPackage: func(_ context.Context, _ *fastly.GetPackageInput) (*fastly.Package, error) {\n\t\t\t\treturn nil, &fastly.HTTPError{\n\t\t\t\t\tStatusCode: http.StatusNotFound,\n\t\t\t\t}\n\t\t\t},\n\t\t\texpectInError: \"unable to find any version of service LsyQ2UXDGk6d4ENjvgqTN4 with an existing package\",\n\t\t},\n\t\t{\n\t\t\tname: \"service is vcl\",\n\t\t\targs: testutil.SplitArgs(\"compute init --from LsyQ2UXDGk6d4ENjvgqTN4\"),\n\t\t\tgetServiceDetails: func(_ context.Context, _ *fastly.GetServiceDetailsInput) (*fastly.ServiceDetail, error) {\n\t\t\t\treturn &fastly.ServiceDetail{\n\t\t\t\t\tServiceID: serviceID,\n\t\t\t\t\tType:      fastly.NullString(\"vcl\"),\n\t\t\t\t}, nil\n\t\t\t},\n\t\t\texpectInError:    \"service LsyQ2UXDGk6d4ENjvgqTN4 is not a Compute service\",\n\t\t\texpectNoManifest: true,\n\t\t},\n\t\t{\n\t\t\tname:          \"service id does not look like a Fastly ID\",\n\t\t\targs:          testutil.SplitArgs(\"compute init --from LsyQ2UXDGk6d4EN\"),\n\t\t\texpectInError: \"--from url seems invalid\",\n\t\t\t// Not a valid URL OR Service ID\n\t\t\tsuppressBeacon: true,\n\t\t},\n\t\t{\n\t\t\tname: \"service has a cloned_from value\",\n\t\t\targs: testutil.SplitArgs(\"compute init --from LsyQ2UXDGk6d4ENjvgqTN4\"),\n\t\t\tgetServiceDetails: func(_ context.Context, _ *fastly.GetServiceDetailsInput) (*fastly.ServiceDetail, error) {\n\t\t\t\treturn &fastly.ServiceDetail{\n\t\t\t\t\tServiceID: serviceID,\n\t\t\t\t\tName:      fastly.NullString(\"cloned-service\"),\n\t\t\t\t\tComment:   fastly.NullString(\"\"),\n\t\t\t\t\tType:      fastly.NullString(\"wasm\"),\n\t\t\t\t\tActiveVersion: &fastly.Version{\n\t\t\t\t\t\tNumber: fastly.ToPointer(1),\n\t\t\t\t\t},\n\t\t\t\t}, nil\n\t\t\t},\n\t\t\tgetPackage: func(_ context.Context, _ *fastly.GetPackageInput) (*fastly.Package, error) {\n\t\t\t\treturn &fastly.Package{\n\t\t\t\t\tServiceID: serviceID,\n\t\t\t\t\tPackageID: fastly.NullString(\"hVPTrHgswnF5KFwFKoQz1f\"),\n\t\t\t\t\tMetadata: &fastly.PackageMetadata{\n\t\t\t\t\t\tClonedFrom: fastly.ToPointer(\"https://github.com/fastly/compute-starter-kit-rust-empty\"),\n\t\t\t\t\t\tLanguage:   fastly.ToPointer(\"rust\"),\n\t\t\t\t\t},\n\t\t\t\t}, nil\n\t\t\t},\n\t\t\texpectInOutput: []string{\"Initializing file structure from selected starter kit...\"},\n\t\t},\n\t\t{\n\t\t\tname: \"service has an unreachable cloned_from value\",\n\t\t\targs: testutil.SplitArgs(\"compute init --from LsyQ2UXDGk6d4ENjvgqTN4\"),\n\t\t\tgetServiceDetails: func(_ context.Context, _ *fastly.GetServiceDetailsInput) (*fastly.ServiceDetail, error) {\n\t\t\t\treturn &fastly.ServiceDetail{\n\t\t\t\t\tServiceID: serviceID,\n\t\t\t\t\tName:      fastly.NullString(\"cloned-service\"),\n\t\t\t\t\tComment:   fastly.NullString(\"\"),\n\t\t\t\t\tType:      fastly.NullString(\"wasm\"),\n\t\t\t\t\tActiveVersion: &fastly.Version{\n\t\t\t\t\t\tNumber: fastly.ToPointer(1),\n\t\t\t\t\t},\n\t\t\t\t}, nil\n\t\t\t},\n\t\t\tgetPackage: func(_ context.Context, _ *fastly.GetPackageInput) (*fastly.Package, error) {\n\t\t\t\treturn &fastly.Package{\n\t\t\t\t\tServiceID: serviceID,\n\t\t\t\t\tPackageID: fastly.NullString(\"hVPTrHgswnF5KFwFKoQz1f\"),\n\t\t\t\t\tMetadata: &fastly.PackageMetadata{\n\t\t\t\t\t\tClonedFrom: fastly.ToPointer(\"https://github.com/fastly/fake-template\"),\n\t\t\t\t\t\tLanguage:   fastly.ToPointer(\"rust\"),\n\t\t\t\t\t},\n\t\t\t\t}, nil\n\t\t\t},\n\t\t\texpectInError: \"could not fetch original source code\",\n\t\t},\n\t\t{\n\t\t\tname: \"service has active version greater than 1\",\n\t\t\targs: testutil.SplitArgs(\"compute init --from LsyQ2UXDGk6d4ENjvgqTN4\"),\n\t\t\tgetServiceDetails: func(_ context.Context, _ *fastly.GetServiceDetailsInput) (*fastly.ServiceDetail, error) {\n\t\t\t\treturn &fastly.ServiceDetail{\n\t\t\t\t\tServiceID: serviceID,\n\t\t\t\t\tName:      fastly.NullString(\"cloned-service\"),\n\t\t\t\t\tComment:   fastly.NullString(\"\"),\n\t\t\t\t\tType:      fastly.NullString(\"wasm\"),\n\t\t\t\t\tActiveVersion: &fastly.Version{\n\t\t\t\t\t\tNumber: fastly.ToPointer(2),\n\t\t\t\t\t},\n\t\t\t\t}, nil\n\t\t\t},\n\t\t\tgetPackage: func(_ context.Context, _ *fastly.GetPackageInput) (*fastly.Package, error) {\n\t\t\t\treturn &fastly.Package{\n\t\t\t\t\tServiceID: serviceID,\n\t\t\t\t\tPackageID: fastly.NullString(\"hVPTrHgswnF5KFwFKoQz1f\"),\n\t\t\t\t\tMetadata: &fastly.PackageMetadata{\n\t\t\t\t\t\tClonedFrom: fastly.ToPointer(\"https://github.com/fastly/fake-template\"),\n\t\t\t\t\t\tLanguage:   fastly.ToPointer(\"rust\"),\n\t\t\t\t\t},\n\t\t\t\t}, nil\n\t\t\t},\n\t\t\texpectInOutput: []string{\"not fetching starter kit source\"},\n\t\t},\n\t}\n\n\tfor _, testcase := range scenarios {\n\t\tt.Run(testcase.name, func(t *testing.T) {\n\t\t\t// We're going to chdir to an init environment,\n\t\t\t// so save the PWD to return to, afterwards.\n\t\t\tpwd, err := os.Getwd()\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tdefer func() {\n\t\t\t\t_ = os.Chdir(pwd)\n\t\t\t}()\n\n\t\t\t// Create test environment\n\t\t\trootdir := testutil.NewEnv(testutil.EnvOpts{\n\t\t\t\tT: t,\n\t\t\t\tWrite: []testutil.FileIO{\n\t\t\t\t\t{Src: \"\", Dst: manifest.Filename},\n\t\t\t\t},\n\t\t\t})\n\t\t\tdefer os.RemoveAll(rootdir)\n\n\t\t\tmanifestPath := filepath.Join(rootdir, manifest.Filename)\n\n\t\t\t// Before running the test, chdir into the init environment.\n\t\t\t// When we're done, chdir back to our original location.\n\t\t\t// This is so we can reliably assert file structure.\n\t\t\tif err := os.Chdir(rootdir); err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\thttpClient := &mock.HTTPClient{\n\t\t\t\tResponses: []*http.Response{\n\t\t\t\t\t// The body is closed by beacon.Notify.\n\t\t\t\t\t//nolint: bodyclose\n\t\t\t\t\tmock.NewHTTPResponse(http.StatusNoContent, nil, nil),\n\t\t\t\t},\n\t\t\t\tErrors: []error{\n\t\t\t\t\tnil,\n\t\t\t\t},\n\t\t\t\tIndex:        -1,\n\t\t\t\tSaveRequests: true,\n\t\t\t}\n\n\t\t\tstdout := &threadsafe.Buffer{}\n\t\t\tapp.Init = func(_ []string, _ io.Reader) (*global.Data, error) {\n\t\t\t\topts := testutil.MockGlobalData(testcase.args, stdout)\n\t\t\t\topts.APIClientFactory = mock.APIClient(mock.API{\n\t\t\t\t\tGetServiceDetailsFn: testcase.getServiceDetails,\n\t\t\t\t\tGetPackageFn:        testcase.getPackage,\n\t\t\t\t})\n\t\t\t\topts.Input = strings.NewReader(\"\")\n\t\t\t\topts.HTTPClient = httpClient\n\n\t\t\t\treturn opts, nil\n\t\t\t}\n\n\t\t\terr = app.Run(testcase.args, nil)\n\n\t\t\tif testcase.expectInError == \"\" {\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatal(err)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif err == nil {\n\t\t\t\t\tt.Log(\"expected an error and did not get one\")\n\t\t\t\t\tt.Fail()\n\t\t\t\t}\n\t\t\t\ttestutil.AssertErrorContains(t, err, testcase.expectInError)\n\t\t\t}\n\n\t\t\tt.Log(stdout.String())\n\n\t\t\tif testcase.suppressBeacon {\n\t\t\t\ttestutil.AssertLength(t, 0, httpClient.Requests)\n\t\t\t} else {\n\t\t\t\ttestutil.AssertLength(t, 1, httpClient.Requests)\n\t\t\t\tbeaconReq := httpClient.Requests[0]\n\t\t\t\ttestutil.AssertEqual(t, \"fastly-notification-relay.edgecompute.app\", beaconReq.URL.Hostname())\n\t\t\t}\n\n\t\t\tfor _, s := range testcase.expectInOutput {\n\t\t\t\ttestutil.AssertStringContains(t, stdout.String(), s)\n\t\t\t}\n\n\t\t\tif testcase.expectNoManifest {\n\t\t\t\t_, err = os.Stat(manifestPath)\n\t\t\t\tif err == nil {\n\t\t\t\t\tt.Log(\"found unexpected manifest file\", manifestPath)\n\t\t\t\t\tt.Fail()\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif len(testcase.expectInManifest) > 0 {\n\t\t\t\tmfContentBytes, err := os.ReadFile(manifestPath)\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatal(err)\n\t\t\t\t}\n\t\t\t\tmfContent := string(mfContentBytes)\n\t\t\t\tfor _, s := range testcase.expectInManifest {\n\t\t\t\t\ttestutil.AssertStringContains(t, mfContent, s)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/commands/compute/language.go",
    "content": "package compute\n\nimport (\n\t\"fmt\"\n\t\"runtime\"\n\t\"sort\"\n\t\"strings\"\n\n\t\"github.com/fastly/cli/pkg/config\"\n)\n\n// NewLanguages returns a list of supported programming languages.\n//\n// NOTE: The 'timeout' value zero is passed into each New<Language> call as it's\n// only useful during the `compute build` phase and is expected to be\n// provided by the user via a flag on the build command.\nfunc NewLanguages(kits config.StarterKitLanguages) []*Language {\n\t// WARNING: Do not reorder these options as they affect the rendered output.\n\t// They are placed in order of language maturity/importance.\n\t//\n\t// A change to this order will also break the tests, as the logic defaults to\n\t// the first language in the list if nothing entered at the relevant language\n\t// prompt.\n\treturn []*Language{\n\t\tNewLanguage(&LanguageOptions{\n\t\t\tName:        \"rust\",\n\t\t\tDisplayName: \"Rust\",\n\t\t\tStarterKits: kits.Rust,\n\t\t}),\n\t\tNewLanguage(&LanguageOptions{\n\t\t\tName:        \"javascript\",\n\t\t\tDisplayName: \"JavaScript\",\n\t\t\tStarterKits: kits.JavaScript,\n\t\t}),\n\t\tNewLanguage(&LanguageOptions{\n\t\t\tName:        \"go\",\n\t\t\tDisplayName: \"Go\",\n\t\t\tStarterKits: kits.Go,\n\t\t}),\n\t\tNewLanguage(&LanguageOptions{\n\t\t\tName:        \"cpp\",\n\t\t\tDisplayName: \"C++\",\n\t\t\tStarterKits: kits.CPP,\n\t\t}),\n\t\tNewLanguage(&LanguageOptions{\n\t\t\tName:        \"other\",\n\t\t\tDisplayName: \"Other ('bring your own' Wasm binary)\",\n\t\t}),\n\t}\n}\n\n// NewLanguage constructs a new Language from a LangaugeOptions.\nfunc NewLanguage(options *LanguageOptions) *Language {\n\t// Ensure the 'default' starter kit is always first.\n\tsort.Slice(options.StarterKits, func(i, j int) bool {\n\t\tsuffix := fmt.Sprintf(\"%s-default\", options.Name)\n\t\ta := strings.HasSuffix(options.StarterKits[i].Path, suffix)\n\t\tb := strings.HasSuffix(options.StarterKits[j].Path, suffix)\n\t\tvar (\n\t\t\tbitSetA int8\n\t\t\tbitSetB int8\n\t\t)\n\t\tif a {\n\t\t\tbitSetA = 1\n\t\t}\n\t\tif b {\n\t\t\tbitSetB = 1\n\t\t}\n\t\treturn bitSetA > bitSetB\n\t})\n\n\treturn &Language{\n\t\toptions.Name,\n\t\toptions.DisplayName,\n\t\toptions.StarterKits,\n\t\toptions.SourceDirectory,\n\t\toptions.Toolchain,\n\t}\n}\n\n// Language models a Compute source language.\ntype Language struct {\n\tName            string\n\tDisplayName     string\n\tStarterKits     []config.StarterKit\n\tSourceDirectory string\n\n\tToolchain\n}\n\n// LanguageOptions models configuration options for a Language.\ntype LanguageOptions struct {\n\tName            string\n\tDisplayName     string\n\tStarterKits     []config.StarterKit\n\tSourceDirectory string\n\tToolchain       Toolchain\n}\n\n// Shell represents a subprocess shell used by `compute` environment where\n// `[scripts.build]` has been defined within fastly.toml manifest.\ntype Shell struct{}\n\n// Build expects a command that can be prefixed with an appropriate subprocess\n// shell.\n//\n// Example:\n// build = \"yarn install && yarn build\"\n//\n// Should be converted into a command such as (on unix):\n// sh -c \"yarn install && yarn build\".\nfunc (s Shell) Build(command string) (cmd string, args []string) {\n\tcmd = \"sh\"\n\targs = []string{\"-c\"}\n\n\tif runtime.GOOS == \"windows\" {\n\t\tcmd = \"cmd.exe\"\n\t\targs = []string{\"/C\"}\n\t}\n\n\targs = append(args, command)\n\n\treturn cmd, args\n}\n"
  },
  {
    "path": "pkg/commands/compute/language_assemblyscript.go",
    "content": "package compute\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// AsDefaultBuildCommand is a build command compiled into the CLI binary so it\n// can be used as a fallback for customer's who have an existing Compute project and\n// are simply upgrading their CLI version and might not be familiar with the\n// changes in the 4.0.0 release with regards to how build logic has moved to the\n// fastly.toml manifest.\n//\n// NOTE: In the 5.x CLI releases we persisted the default to the fastly.toml\n// We no longer do that. In 6.x we use the default and just inform the user.\n// This makes the experience less confusing as users didn't expect file changes.\nvar AsDefaultBuildCommand = fmt.Sprintf(\"npm exec -- asc assembly/index.ts --outFile %s --optimize --noAssert\", binWasmPath)\n\n// AsDefaultBuildCommandForWebpack is a build command compiled into the CLI\n// binary so it can be used as a fallback for customer's who have an existing\n// Compute project using the 'default' JS Starter Kit, and are simply upgrading\n// their CLI version and might not be familiar with the changes in the 4.0.0\n// release with regards to how build logic has moved to the fastly.toml manifest.\n//\n// NOTE: For this variation of the build script to be added to the user's\n// fastly.toml will require a successful check for the webpack dependency.\nvar AsDefaultBuildCommandForWebpack = fmt.Sprintf(\"npm exec webpack && npm exec -- asc assembly/index.ts --outFile %s --optimize --noAssert\", binWasmPath)\n\n// AsSourceDirectory represents the source code directory.\nconst AsSourceDirectory = \"assembly\"\n\n// NewAssemblyScript constructs a new AssemblyScript toolchain.\nfunc NewAssemblyScript(\n\tc *BuildCommand,\n\tin io.Reader,\n\tmanifestFilename string,\n\tout io.Writer,\n\tspinner text.Spinner,\n) *AssemblyScript {\n\treturn &AssemblyScript{\n\t\tShell: Shell{},\n\n\t\tbuild:                 c.Globals.Manifest.File.Scripts.Build,\n\t\terrlog:                c.Globals.ErrLog,\n\t\tinput:                 in,\n\t\tmanifestFilename:      manifestFilename,\n\t\tmetadataFilterEnvVars: c.MetadataFilterEnvVars,\n\t\toutput:                out,\n\t\tpostBuild:             c.Globals.Manifest.File.Scripts.PostBuild,\n\t\tspinner:               spinner,\n\t\ttimeout:               c.Flags.Timeout,\n\t\tverbose:               c.Globals.Verbose(),\n\t}\n}\n\n// AssemblyScript implements a Toolchain for the AssemblyScript language.\ntype AssemblyScript struct {\n\tShell\n\n\t// autoYes is the --auto-yes flag.\n\tautoYes bool\n\t// build is a shell command defined in fastly.toml using [scripts.build].\n\tbuild string\n\t// defaultBuild indicates if the default build script was used.\n\tdefaultBuild bool\n\t// errlog is an abstraction for recording errors to disk.\n\terrlog fsterr.LogInterface\n\t// input is the user's terminal stdin stream\n\tinput io.Reader\n\t// manifestFilename is the name of the manifest file.\n\tmanifestFilename string\n\t// metadataFilterEnvVars is a comma-separated list of user defined env vars.\n\tmetadataFilterEnvVars string\n\t// nonInteractive is the --non-interactive flag.\n\tnonInteractive bool\n\t// output is the users terminal stdout stream\n\toutput io.Writer\n\t// postBuild is a custom script executed after the build but before the Wasm\n\t// binary is added to the .tar.gz archive.\n\tpostBuild string\n\t// spinner is a terminal progress status indicator.\n\tspinner text.Spinner\n\t// timeout is the build execution threshold.\n\ttimeout int\n\t// verbose indicates if the user set --verbose\n\tverbose bool\n}\n\n// DefaultBuildScript indicates if a custom build script was used.\nfunc (a *AssemblyScript) DefaultBuildScript() bool {\n\treturn a.defaultBuild\n}\n\n// Dependencies returns all dependencies used by the project.\nfunc (a *AssemblyScript) Dependencies() map[string]string {\n\tdeps := make(map[string]string)\n\n\tlockfile := \"npm-shrinkwrap.json\"\n\t_, err := os.Stat(lockfile)\n\tif errors.Is(err, os.ErrNotExist) {\n\t\tlockfile = \"package-lock.json\"\n\t}\n\n\tvar jlf JavaScriptLockFile\n\tif f, err := os.Open(lockfile); err == nil {\n\t\tif err := json.NewDecoder(f).Decode(&jlf); err == nil {\n\t\t\tfor k, v := range jlf.Packages {\n\t\t\t\tif k != \"\" { // avoid \"root\" package\n\t\t\t\t\tdeps[k] = v.Version\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn deps\n}\n\n// Build compiles the user's source code into a Wasm binary.\nfunc (a *AssemblyScript) Build() error {\n\tif !a.verbose {\n\t\ttext.Break(a.output)\n\t}\n\ttext.Deprecated(\"The Fastly AssemblyScript SDK is being deprecated in favor of the more up-to-date and feature-rich JavaScript SDK. You can learn more about the JavaScript SDK on our Developer Hub Page - https://www.fastly.com/documentation/guides/computejavascript/\\n\\n\")\n\n\tif a.build == \"\" {\n\t\ta.build = AsDefaultBuildCommand\n\t\ta.defaultBuild = true\n\t}\n\n\tusesWebpack, err := a.checkForWebpack()\n\tif err != nil {\n\t\treturn err\n\t}\n\tif usesWebpack {\n\t\ta.build = AsDefaultBuildCommandForWebpack\n\t}\n\n\tif a.defaultBuild && a.verbose {\n\t\ttext.Info(a.output, \"No [scripts.build] found in %s. The following default build command for AssemblyScript will be used: `%s`\\n\\n\", a.manifestFilename, a.build)\n\t}\n\n\tbt := BuildToolchain{\n\t\tautoYes:               a.autoYes,\n\t\tbuildFn:               a.Shell.Build,\n\t\tbuildScript:           a.build,\n\t\terrlog:                a.errlog,\n\t\tin:                    a.input,\n\t\tmanifestFilename:      a.manifestFilename,\n\t\tmetadataFilterEnvVars: a.metadataFilterEnvVars,\n\t\tnonInteractive:        a.nonInteractive,\n\t\tout:                   a.output,\n\t\tpostBuild:             a.postBuild,\n\t\tspinner:               a.spinner,\n\t\ttimeout:               a.timeout,\n\t\tverbose:               a.verbose,\n\t}\n\n\treturn bt.Build()\n}\n\nfunc (a AssemblyScript) checkForWebpack() (bool, error) {\n\twd, err := os.Getwd()\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\thome, err := os.UserHomeDir()\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tfound, path, err := search(\"package.json\", wd, home)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tif found {\n\t\t// gosec flagged this:\n\t\t// G304 (CWE-22): Potential file inclusion via variable\n\t\t//\n\t\t// Disabling as the path is determined by our own logic.\n\t\t/* #nosec */\n\t\tdata, err := os.ReadFile(path)\n\t\tif err != nil {\n\t\t\treturn false, err\n\t\t}\n\n\t\tvar pkg NPMPackage\n\n\t\terr = json.Unmarshal(data, &pkg)\n\t\tif err != nil {\n\t\t\treturn false, err\n\t\t}\n\n\t\tfor k := range pkg.DevDependencies {\n\t\t\tif k == \"webpack\" {\n\t\t\t\treturn true, nil\n\t\t\t}\n\t\t}\n\n\t\tfor k := range pkg.Dependencies {\n\t\t\tif k == \"webpack\" {\n\t\t\t\treturn true, nil\n\t\t\t}\n\t\t}\n\t}\n\n\treturn false, nil\n}\n"
  },
  {
    "path": "pkg/commands/compute/language_cpp.go",
    "content": "package compute\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"os/exec\"\n\t\"regexp\"\n\t\"strings\"\n\n\t\"github.com/Masterminds/semver/v3\"\n\n\t\"github.com/fastly/cli/pkg/config\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// CPPDefaultBuildCommand is a build command compiled into the CLI binary so it\n// can be used as a fallback for customers who have an existing Compute project and\n// are simply upgrading their CLI version and might not be familiar with the\n// changes in the 4.0.0 release with regards to how build logic has moved to the\n// fastly.toml manifest.\n//\n// NOTE: In the 5.x CLI releases we persisted the default to the fastly.toml\n// We no longer do that. In 6.x we use the default and just inform the user.\n// This makes the experience less confusing as users didn't expect file changes.\nconst CPPDefaultBuildCommand = \"clang++ -O3 --target=%s -o %s main.cpp\"\n\n// CPPDefaultWasmWasiTarget is the expected C++ WasmWasi build target.\nconst CPPDefaultWasmWasiTarget = \"wasm32-wasip1\"\n\n// CPPSourceDirectory represents the source code directory.\nconst CPPSourceDirectory = \".\"\n\n// NewCPP constructs a new C++ toolchain.\nfunc NewCPP(\n\tc *BuildCommand,\n\tin io.Reader,\n\tmanifestFilename string,\n\tout io.Writer,\n\tspinner text.Spinner,\n) *CPP {\n\treturn &CPP{\n\t\tShell: Shell{},\n\n\t\tautoYes:               c.Globals.Flags.AutoYes,\n\t\tbuild:                 c.Globals.Manifest.File.Scripts.Build,\n\t\tconfig:                c.Globals.Config.Language.CPP,\n\t\tenv:                   c.Globals.Manifest.File.Scripts.EnvVars,\n\t\terrlog:                c.Globals.ErrLog,\n\t\tinput:                 in,\n\t\tmanifestFilename:      manifestFilename,\n\t\tmetadataFilterEnvVars: c.MetadataFilterEnvVars,\n\t\tnonInteractive:        c.Globals.Flags.NonInteractive,\n\t\toutput:                out,\n\t\tpostBuild:             c.Globals.Manifest.File.Scripts.PostBuild,\n\t\tspinner:               spinner,\n\t\ttimeout:               c.Flags.Timeout,\n\t\tverbose:               c.Globals.Verbose(),\n\t}\n}\n\n// CPP implements a Toolchain for the C++ language.\ntype CPP struct {\n\tShell\n\n\t// autoYes is the --auto-yes flag.\n\tautoYes bool\n\t// build is a shell command defined in fastly.toml using [scripts.build].\n\tbuild string\n\t// config is the C++ specific application configuration.\n\tconfig config.CPP\n\t// defaultBuild indicates if the default build script was used.\n\tdefaultBuild bool\n\t// env is environment variables to be set.\n\tenv []string\n\t// errlog is an abstraction for recording errors to disk.\n\terrlog fsterr.LogInterface\n\t// input is the user's terminal stdin stream.\n\tinput io.Reader\n\t// manifestFilename is the name of the manifest file.\n\tmanifestFilename string\n\t// metadataFilterEnvVars is a comma-separated list of user defined env vars.\n\tmetadataFilterEnvVars string\n\t// nonInteractive is the --non-interactive flag.\n\tnonInteractive bool\n\t// output is the user's terminal stdout stream.\n\toutput io.Writer\n\t// postBuild is a custom script executed after the build but before the Wasm\n\t// binary is added to the .tar.gz archive.\n\tpostBuild string\n\t// spinner is a terminal progress status indicator.\n\tspinner text.Spinner\n\t// timeout is the build execution threshold.\n\ttimeout int\n\t// verbose indicates if the user set --verbose\n\tverbose bool\n}\n\n// DefaultBuildScript indicates if a custom build script was used.\nfunc (cpp *CPP) DefaultBuildScript() bool {\n\treturn cpp.defaultBuild\n}\n\n// Dependencies returns all dependencies used by the project.\nfunc (cpp *CPP) Dependencies() map[string]string {\n\t// For C++, dependencies are typically managed through various systems\n\t// (CMake, Conan, vcpkg, etc.). For now, return an empty map.\n\t// This could be extended in the future to parse CMakeLists.txt or other files.\n\treturn make(map[string]string)\n}\n\n// Build compiles the user's source code into a Wasm binary.\nfunc (cpp *CPP) Build() error {\n\tif cpp.build == \"\" {\n\t\tcpp.build = fmt.Sprintf(CPPDefaultBuildCommand, CPPDefaultWasmWasiTarget, binWasmPath)\n\t\tcpp.defaultBuild = true\n\t\tif !cpp.verbose {\n\t\t\ttext.Break(cpp.output)\n\t\t}\n\t\ttext.Info(cpp.output, \"No [scripts.build] found in %s. Visit https://www.fastly.com/documentation/guides/compute/ to learn more about building C++ projects.\\n\\n\", cpp.manifestFilename)\n\t\ttext.Description(cpp.output, \"The following default build command for C++ will be used\", cpp.build)\n\t}\n\n\tcpp.toolchainConstraint(\n\t\t\"clang++\", `clang version (?P<version>\\d+\\.\\d+\\.\\d+)`, cpp.config.ToolchainConstraint,\n\t)\n\n\twasmWasiTarget := cpp.config.WasmWasiTarget\n\tif wasmWasiTarget != \"\" && wasmWasiTarget != CPPDefaultWasmWasiTarget {\n\t\treturn fmt.Errorf(\"the default build in .fastly/config.toml should produce a %s binary, but was instead set to produce a %s binary\", CPPDefaultWasmWasiTarget, wasmWasiTarget)\n\t}\n\n\tbt := BuildToolchain{\n\t\tautoYes:               cpp.autoYes,\n\t\tbuildFn:               cpp.Shell.Build,\n\t\tbuildScript:           cpp.build,\n\t\tenv:                   cpp.env,\n\t\terrlog:                cpp.errlog,\n\t\tin:                    cpp.input,\n\t\tmanifestFilename:      cpp.manifestFilename,\n\t\tmetadataFilterEnvVars: cpp.metadataFilterEnvVars,\n\t\tnonInteractive:        cpp.nonInteractive,\n\t\tout:                   cpp.output,\n\t\tpostBuild:             cpp.postBuild,\n\t\tspinner:               cpp.spinner,\n\t\ttimeout:               cpp.timeout,\n\t\tverbose:               cpp.verbose,\n\t}\n\n\treturn bt.Build()\n}\n\n// toolchainConstraint warns the user if the required constraint is not met.\n//\n// NOTE: We don't stop the build as their toolchain may compile successfully.\n// The warning is to help a user know something isn't quite right and gives them\n// the opportunity to do something about it if they choose.\nfunc (cpp *CPP) toolchainConstraint(toolchain, pattern, constraint string) {\n\tif constraint == \"\" {\n\t\treturn\n\t}\n\n\tif cpp.verbose {\n\t\ttext.Info(cpp.output, \"The Fastly CLI build step requires a %s version '%s'.\\n\\n\", toolchain, constraint)\n\t}\n\n\tversionCommand := fmt.Sprintf(\"%s --version\", toolchain)\n\targs := strings.Split(versionCommand, \" \")\n\n\t// gosec flagged this:\n\t// G204 (CWE-78): Subprocess launched with function call as argument or cmd arguments\n\t// Disabling as we trust the source of the variable.\n\t// #nosec\n\t// nosemgrep\n\tcmd := exec.Command(args[0], args[1:]...)\n\tstdoutStderr, err := cmd.CombinedOutput()\n\toutput := string(stdoutStderr)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tversionPattern := regexp.MustCompile(pattern)\n\tmatch := versionPattern.FindStringSubmatch(output)\n\tif len(match) < 2 { // We expect a pattern with one capture group.\n\t\treturn\n\t}\n\tversion := match[1]\n\n\tv, err := semver.NewVersion(version)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tc, err := semver.NewConstraint(constraint)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tvalid, errs := c.Validate(v)\n\tif !valid {\n\t\ttext.Warning(cpp.output, \"The %s version requirement was not satisfied: %v\", toolchain, errs)\n\t}\n}\n"
  },
  {
    "path": "pkg/commands/compute/language_go.go",
    "content": "package compute\n\nimport (\n\t\"bufio\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"os/exec\"\n\t\"regexp\"\n\t\"strings\"\n\n\t\"github.com/Masterminds/semver/v3\"\n\t\"golang.org/x/mod/modfile\"\n\n\t\"github.com/fastly/cli/pkg/config\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// TinyGoDefaultBuildCommand is a build command compiled into the CLI binary so it\n// can be used as a fallback for customer's who have an existing Compute project and\n// are simply upgrading their CLI version and might not be familiar with the\n// changes in the 4.0.0 release with regards to how build logic has moved to the\n// fastly.toml manifest.\n//\n// NOTE: In the 5.x CLI releases we persisted the default to the fastly.toml\n// We no longer do that. In 6.x we use the default and just inform the user.\n// This makes the experience less confusing as users didn't expect file changes.\nvar TinyGoDefaultBuildCommand = fmt.Sprintf(\"tinygo build -target=wasi -gc=conservative -o %s ./\", binWasmPath)\n\n// GoSourceDirectory represents the source code directory.\nconst GoSourceDirectory = \".\"\n\n// NewGo constructs a new Go toolchain.\nfunc NewGo(\n\tc *BuildCommand,\n\tin io.Reader,\n\tmanifestFilename string,\n\tout io.Writer,\n\tspinner text.Spinner,\n) *Go {\n\treturn &Go{\n\t\tShell: Shell{},\n\n\t\tautoYes:               c.Globals.Flags.AutoYes,\n\t\tbuild:                 c.Globals.Manifest.File.Scripts.Build,\n\t\tconfig:                c.Globals.Config.Language.Go,\n\t\tenv:                   c.Globals.Manifest.File.Scripts.EnvVars,\n\t\terrlog:                c.Globals.ErrLog,\n\t\tinput:                 in,\n\t\tmanifestFilename:      manifestFilename,\n\t\tmetadataFilterEnvVars: c.MetadataFilterEnvVars,\n\t\tnonInteractive:        c.Globals.Flags.NonInteractive,\n\t\toutput:                out,\n\t\tpostBuild:             c.Globals.Manifest.File.Scripts.PostBuild,\n\t\tspinner:               spinner,\n\t\ttimeout:               c.Flags.Timeout,\n\t\tverbose:               c.Globals.Verbose(),\n\t}\n}\n\n// Go implements a Toolchain for the TinyGo language.\n//\n// NOTE: Two separate tools are required to support golang development.\n//\n// 1. Go: for defining required packages in a go.mod project module.\n// 2. TinyGo: used to compile the go project.\ntype Go struct {\n\tShell\n\n\t// autoYes is the --auto-yes flag.\n\tautoYes bool\n\t// build is a shell command defined in fastly.toml using [scripts.build].\n\tbuild string\n\t// config is the Go specific application configuration.\n\tconfig config.Go\n\t// defaultBuild indicates if the default build script was used.\n\tdefaultBuild bool\n\t// env is environment variables to be set.\n\tenv []string\n\t// errlog is an abstraction for recording errors to disk.\n\terrlog fsterr.LogInterface\n\t// input is the user's terminal stdin stream\n\tinput io.Reader\n\t// manifestFilename is the name of the manifest file.\n\tmanifestFilename string\n\t// metadataFilterEnvVars is a comma-separated list of user defined env vars.\n\tmetadataFilterEnvVars string\n\t// nonInteractive is the --non-interactive flag.\n\tnonInteractive bool\n\t// output is the users terminal stdout stream\n\toutput io.Writer\n\t// postBuild is a custom script executed after the build but before the Wasm\n\t// binary is added to the .tar.gz archive.\n\tpostBuild string\n\t// spinner is a terminal progress status indicator.\n\tspinner text.Spinner\n\t// timeout is the build execution threshold.\n\ttimeout int\n\t// verbose indicates if the user set --verbose\n\tverbose bool\n}\n\n// DefaultBuildScript indicates if a custom build script was used.\nfunc (g *Go) DefaultBuildScript() bool {\n\treturn g.defaultBuild\n}\n\n// Dependencies returns all dependencies used by the project.\nfunc (g *Go) Dependencies() map[string]string {\n\tdeps := make(map[string]string)\n\tdata, err := os.ReadFile(\"go.mod\")\n\tif err != nil {\n\t\treturn deps\n\t}\n\tf, err := modfile.ParseLax(\"go.mod\", data, nil)\n\tif err != nil {\n\t\treturn deps\n\t}\n\tfor _, req := range f.Require {\n\t\tif req.Indirect {\n\t\t\tcontinue\n\t\t}\n\t\tdeps[req.Mod.Path] = req.Mod.Version\n\t}\n\treturn deps\n}\n\n// Build compiles the user's source code into a Wasm binary.\nfunc (g *Go) Build() error {\n\tvar (\n\t\ttinygoToolchain     bool\n\t\ttoolchainConstraint string\n\t)\n\n\tif g.build == \"\" {\n\t\tg.build = TinyGoDefaultBuildCommand\n\t\tg.defaultBuild = true\n\t\ttinygoToolchain = true\n\t\ttoolchainConstraint = g.config.ToolchainConstraintTinyGo\n\t\tif !g.verbose {\n\t\t\ttext.Break(g.output)\n\t\t}\n\t\ttext.Info(g.output, \"No [scripts.build] found in %s. Visit https://www.fastly.com/documentation/guides/compute/go/ to learn how to target standard Go vs TinyGo.\\n\\n\", g.manifestFilename)\n\t\ttext.Description(g.output, \"The following default build command for TinyGo will be used\", g.build)\n\t}\n\n\tif g.build != \"\" {\n\t\t// IMPORTANT: All Fastly starter-kits for Go/TinyGo will have build script.\n\t\t//\n\t\t// So we'll need to parse the build script to identify if TinyGo is used so\n\t\t// we can set the constraints appropriately.\n\t\tif strings.Contains(g.build, \"tinygo build\") {\n\t\t\ttinygoToolchain = true\n\t\t\ttoolchainConstraint = g.config.ToolchainConstraintTinyGo\n\t\t} else {\n\t\t\ttoolchainConstraint = g.config.ToolchainConstraint\n\t\t}\n\t}\n\n\t// IMPORTANT: The Go SDK 0.2.0 bumps the tinygo requirement to 0.28.1\n\t//\n\t// This means we need to check the go.mod of the user's project for\n\t// `compute-sdk-go` and then parse the version and identify if it's less than\n\t// 0.2.0 version. If it less than, change the TinyGo constraint to 0.26.0\n\ttinygoConstraint := identifyTinyGoConstraint(g.config.TinyGoConstraint, g.config.TinyGoConstraintFallback)\n\n\tg.toolchainConstraint(\n\t\t\"go\", `go version go(?P<version>\\d[^\\s]+)`, toolchainConstraint,\n\t)\n\n\tif tinygoToolchain {\n\t\tg.toolchainConstraint(\n\t\t\t\"tinygo\", `tinygo version (?P<version>\\d[^\\s]+)`, tinygoConstraint,\n\t\t)\n\t}\n\n\tbt := BuildToolchain{\n\t\tautoYes:               g.autoYes,\n\t\tbuildFn:               g.Shell.Build,\n\t\tbuildScript:           g.build,\n\t\tenv:                   g.env,\n\t\terrlog:                g.errlog,\n\t\tin:                    g.input,\n\t\tmanifestFilename:      g.manifestFilename,\n\t\tmetadataFilterEnvVars: g.metadataFilterEnvVars,\n\t\tnonInteractive:        g.nonInteractive,\n\t\tout:                   g.output,\n\t\tpostBuild:             g.postBuild,\n\t\tspinner:               g.spinner,\n\t\ttimeout:               g.timeout,\n\t\tverbose:               g.verbose,\n\t}\n\n\treturn bt.Build()\n}\n\n// identifyTinyGoConstraint checks the compute-sdk-go version used by the\n// project and if it's less than 0.2.0 we'll change the TinyGo constraint to be\n// version 0.26.0\n//\n// We do this because the 0.2.0 release of the compute-sdk-go bumps the TinyGo\n// version requirement to 0.28.1 and we want to avoid any scenarios where a\n// bump in SDK version causes the user's build to break (which would happen for\n// users with a pre-existing project who happen to update their CLI version: the\n// new CLI version would have a TinyGo constraint that would be higher than\n// before and would stop their build from working).\n//\n// NOTE: The `configConstraint` is the latest CLI application config version.\n// If there are any errors trying to parse the go.mod we'll default to the\n// config constraint.\nfunc identifyTinyGoConstraint(configConstraint, fallback string) string {\n\tmoduleName := \"github.com/fastly/compute-sdk-go\"\n\tversion := \"\"\n\n\tf, err := os.Open(\"go.mod\")\n\tif err != nil {\n\t\treturn configConstraint\n\t}\n\tdefer f.Close()\n\n\tscanner := bufio.NewScanner(f)\n\tfor scanner.Scan() {\n\t\tline := scanner.Text()\n\t\tparts := strings.Fields(line)\n\n\t\t// go.mod has two separate definition possibilities:\n\t\t//\n\t\t// 1.\n\t\t// require github.com/fastly/compute-sdk-go v0.1.7\n\t\t//\n\t\t// 2.\n\t\t// require (\n\t\t//   github.com/fastly/compute-sdk-go v0.1.7\n\t\t// )\n\t\tif len(parts) >= 2 {\n\t\t\t// 1. require [github.com/fastly/compute-sdk-go] v0.1.7\n\t\t\tif parts[1] == moduleName {\n\t\t\t\tversion = strings.TrimPrefix(parts[2], \"v\")\n\t\t\t\tbreak\n\t\t\t}\n\t\t\t// 2. [github.com/fastly/compute-sdk-go] v0.1.7\n\t\t\tif parts[0] == moduleName {\n\t\t\t\tversion = strings.TrimPrefix(parts[1], \"v\")\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\tif err := scanner.Err(); err != nil {\n\t\treturn configConstraint\n\t}\n\n\tif version == \"\" {\n\t\treturn configConstraint\n\t}\n\n\tgomodVersion, err := semver.NewVersion(version)\n\tif err != nil {\n\t\treturn configConstraint\n\t}\n\n\t// 0.2.0 introduces the break by bumping the TinyGo minimum version to 0.28.1\n\tbreakingSDKVersion, err := semver.NewVersion(\"0.2.0\")\n\tif err != nil {\n\t\treturn configConstraint\n\t}\n\n\tif gomodVersion.LessThan(breakingSDKVersion) {\n\t\treturn fallback\n\t}\n\n\treturn configConstraint\n}\n\n// toolchainConstraint warns the user if the required constraint is not met.\n//\n// NOTE: We don't stop the build as their toolchain may compile successfully.\n// The warning is to help a user know something isn't quite right and gives them\n// the opportunity to do something about it if they choose.\nfunc (g *Go) toolchainConstraint(toolchain, pattern, constraint string) {\n\tif g.verbose {\n\t\ttext.Info(g.output, \"The Fastly CLI build step requires a %s version '%s'.\\n\\n\", toolchain, constraint)\n\t}\n\n\tversionCommand := fmt.Sprintf(\"%s version\", toolchain)\n\targs := strings.Split(versionCommand, \" \")\n\n\t// gosec flagged this:\n\t// G204 (CWE-78): Subprocess launched with function call as argument or cmd arguments\n\t// Disabling as we trust the source of the variable.\n\t// #nosec\n\t// nosemgrep\n\tcmd := exec.Command(args[0], args[1:]...)\n\tstdoutStderr, err := cmd.CombinedOutput()\n\toutput := string(stdoutStderr)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tversionPattern := regexp.MustCompile(pattern)\n\tmatch := versionPattern.FindStringSubmatch(output)\n\tif len(match) < 2 { // We expect a pattern with one capture group.\n\t\treturn\n\t}\n\tversion := match[1]\n\n\tv, err := semver.NewVersion(version)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tc, err := semver.NewConstraint(constraint)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tvalid, errs := c.Validate(v)\n\tif !valid {\n\t\ttext.Warning(g.output, \"The %s version requirement was not satisfied: %s\", toolchain, errors.Join(errs...))\n\t}\n}\n"
  },
  {
    "path": "pkg/commands/compute/language_javascript.go",
    "content": "package compute\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// JsDefaultBuildCommand is a build command compiled into the CLI binary so it\n// can be used as a fallback for customer's who have an existing Compute project and\n// are simply upgrading their CLI version and might not be familiar with the\n// changes in the 4.0.0 release with regards to how build logic has moved to the\n// fastly.toml manifest.\n//\n// NOTE: In the 5.x CLI releases we persisted the default to the fastly.toml\n// We no longer do that. In 6.x we use the default and just inform the user.\n// This makes the experience less confusing as users didn't expect file changes.\nvar JsDefaultBuildCommand = fmt.Sprintf(\"npm exec js-compute-runtime ./src/index.js %s\", binWasmPath)\n\n// BunDefaultBuildCommand is the default build command when Bun is the detected runtime.\nvar BunDefaultBuildCommand = fmt.Sprintf(\"bunx js-compute-runtime ./src/index.js %s\", binWasmPath)\n\n// JsSourceDirectory represents the source code directory.\nconst JsSourceDirectory = \"src\"\n\n// ErrNpmMissing is returned when Node.js is found but npm is not installed.\nvar ErrNpmMissing = errors.New(\"node found but npm missing\")\n\n// ErrNoJSRuntime is returned when neither node nor bun can be found on PATH.\nvar ErrNoJSRuntime = errors.New(\"no JavaScript runtime found (node or bun)\")\n\n// ErrPackageJSONMissing is returned when the project has no package.json.\nvar ErrPackageJSONMissing = errors.New(\"package.json not found\")\n\n// ErrNodeModulesMissing is returned when node_modules has not been installed.\nvar ErrNodeModulesMissing = errors.New(\"node_modules directory not found - dependencies not installed\")\n\n// ErrJsComputeMissing is returned when @fastly/js-compute is not installed.\nvar ErrJsComputeMissing = errors.New(\"@fastly/js-compute package not found\")\n\n// JSRuntime represents a detected JavaScript runtime.\ntype JSRuntime struct {\n\t// Name is the runtime name (node or bun).\n\tName string\n\t// Version is the runtime version string.\n\tVersion string\n\t// PkgMgr is the package manager to use (npm or bun).\n\tPkgMgr string\n}\n\n// NewJavaScript constructs a new JavaScript toolchain.\nfunc NewJavaScript(\n\tc *BuildCommand,\n\tin io.Reader,\n\tmanifestFilename string,\n\tout io.Writer,\n\tspinner text.Spinner,\n) *JavaScript {\n\treturn &JavaScript{\n\t\tShell: Shell{},\n\n\t\tautoYes:               c.Globals.Flags.AutoYes,\n\t\tbuild:                 c.Globals.Manifest.File.Scripts.Build,\n\t\tenv:                   c.Globals.Manifest.File.Scripts.EnvVars,\n\t\terrlog:                c.Globals.ErrLog,\n\t\tinput:                 in,\n\t\tmanifestFilename:      manifestFilename,\n\t\tmetadataFilterEnvVars: c.MetadataFilterEnvVars,\n\t\tnonInteractive:        c.Globals.Flags.NonInteractive,\n\t\toutput:                out,\n\t\tpostBuild:             c.Globals.Manifest.File.Scripts.PostBuild,\n\t\tspinner:               spinner,\n\t\ttimeout:               c.Flags.Timeout,\n\t\tverbose:               c.Globals.Verbose(),\n\t}\n}\n\n// JavaScript implements a Toolchain for the JavaScript language.\ntype JavaScript struct {\n\tShell\n\n\t// autoYes is the --auto-yes flag.\n\tautoYes bool\n\t// build is a shell command defined in fastly.toml using [scripts.build].\n\tbuild string\n\t// defaultBuild indicates if the default build script was used.\n\tdefaultBuild bool\n\t// env is environment variables to be set.\n\tenv []string\n\t// errlog is an abstraction for recording errors to disk.\n\terrlog fsterr.LogInterface\n\t// input is the user's terminal stdin stream\n\tinput io.Reader\n\t// manifestFilename is the name of the manifest file.\n\tmanifestFilename string\n\t// metadataFilterEnvVars is a comma-separated list of user defined env vars.\n\tmetadataFilterEnvVars string\n\t// nodeModulesDirs is the set of node_modules directories found walking up the tree.\n\t// Supports monorepo/hoisted setups where dependencies may be split across levels.\n\tnodeModulesDirs []string\n\t// nonInteractive is the --non-interactive flag.\n\tnonInteractive bool\n\t// output is the users terminal stdout stream\n\toutput io.Writer\n\t// postBuild is a custom script executed after the build but before the Wasm\n\t// binary is added to the .tar.gz archive.\n\tpostBuild string\n\t// runtime is the detected JavaScript runtime (node or bun).\n\truntime *JSRuntime\n\t// spinner is a terminal progress status indicator.\n\tspinner text.Spinner\n\t// timeout is the build execution threshold.\n\ttimeout int\n\t// verbose indicates if the user set --verbose\n\tverbose bool\n}\n\n// DefaultBuildScript indicates if a custom build script was used.\nfunc (j *JavaScript) DefaultBuildScript() bool {\n\treturn j.defaultBuild\n}\n\n// JavaScriptPackage represents a package within a JavaScript lockfile.\ntype JavaScriptPackage struct {\n\tVersion string `json:\"version\"`\n}\n\n// JavaScriptLockFile represents a JavaScript lockfile.\ntype JavaScriptLockFile struct {\n\tPackages map[string]JavaScriptPackage `json:\"packages\"`\n}\n\n// Dependencies returns all dependencies used by the project.\nfunc (j *JavaScript) Dependencies() map[string]string {\n\tdeps := make(map[string]string)\n\n\tlockfile := \"npm-shrinkwrap.json\"\n\t_, err := os.Stat(lockfile)\n\tif errors.Is(err, os.ErrNotExist) {\n\t\tlockfile = \"package-lock.json\"\n\t}\n\n\tvar jlf JavaScriptLockFile\n\tif f, err := os.Open(lockfile); err == nil {\n\t\tif err := json.NewDecoder(f).Decode(&jlf); err == nil {\n\t\t\tfor k, v := range jlf.Packages {\n\t\t\t\tif k != \"\" { // avoid \"root\" package\n\t\t\t\t\tdeps[k] = v.Version\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn deps\n}\n\n// isDefaultBuildScript reports whether the configured build script is the\n// well-known default used by Fastly starter kits (e.g. \"npm run build\" or\n// \"bun run build\"). Leading \"KEY=value\" environment-variable assignments are\n// tolerated so values like \"NODE_ENV=production npm run build\" still match.\n// These scripts delegate to the same toolchain that the CLI would invoke\n// directly, so the same verification logic applies.\nfunc (j *JavaScript) isDefaultBuildScript() bool {\n\ttokens := strings.Fields(j.build)\n\tfor len(tokens) > 0 && isEnvAssignment(tokens[0]) {\n\t\ttokens = tokens[1:]\n\t}\n\tif len(tokens) != 3 || tokens[1] != \"run\" || tokens[2] != \"build\" {\n\t\treturn false\n\t}\n\treturn tokens[0] == \"npm\" || tokens[0] == \"bun\"\n}\n\n// isEnvAssignment reports whether a token looks like a shell environment-variable\n// assignment (NAME=value). Only the name portion is validated; the value may\n// contain any characters, including path separators (e.g. PATH=./node_modules/.bin:$PATH).\nfunc isEnvAssignment(token string) bool {\n\tname, _, ok := strings.Cut(token, \"=\")\n\tif !ok || name == \"\" {\n\t\treturn false\n\t}\n\tfor i, r := range name {\n\t\tswitch {\n\t\tcase r == '_':\n\t\tcase r >= 'A' && r <= 'Z':\n\t\tcase r >= 'a' && r <= 'z':\n\t\tcase i > 0 && r >= '0' && r <= '9':\n\t\tdefault:\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\n// Build compiles the user's source code into a Wasm binary.\nfunc (j *JavaScript) Build() error {\n\tif j.build == \"\" {\n\t\tif err := j.verifyToolchain(); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tj.build = j.getDefaultBuildCommand()\n\t\tj.defaultBuild = true\n\t} else if j.isDefaultBuildScript() {\n\t\tif err := j.verifyToolchain(); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tif j.defaultBuild && j.verbose {\n\t\ttext.Info(j.output, \"No [scripts.build] found in %s. The following default build command for JavaScript will be used: `%s`\\n\\n\", j.manifestFilename, j.build)\n\t}\n\n\tbt := BuildToolchain{\n\t\tautoYes:               j.autoYes,\n\t\tbuildFn:               j.Shell.Build,\n\t\tbuildScript:           j.build,\n\t\tenv:                   j.env,\n\t\terrlog:                j.errlog,\n\t\tin:                    j.input,\n\t\tmanifestFilename:      j.manifestFilename,\n\t\tmetadataFilterEnvVars: j.metadataFilterEnvVars,\n\t\tnonInteractive:        j.nonInteractive,\n\t\tout:                   j.output,\n\t\tpostBuild:             j.postBuild,\n\t\tspinner:               j.spinner,\n\t\ttimeout:               j.timeout,\n\t\tverbose:               j.verbose,\n\t}\n\n\treturn bt.Build()\n}\n\n// search recurses up the directory tree looking for the given file.\nfunc search(filename, wd, home string) (found bool, path string, err error) {\n\tparent := filepath.Dir(wd)\n\n\tpath = filepath.Join(wd, filename)\n\t_, statErr := os.Stat(path)\n\tswitch {\n\tcase statErr == nil:\n\t\treturn true, path, nil\n\tcase !errors.Is(statErr, os.ErrNotExist):\n\t\treturn false, \"\", statErr\n\t}\n\n\tif wd != parent && wd != home {\n\t\treturn search(filename, parent, home)\n\t}\n\n\treturn false, \"\", nil\n}\n\n// NPMPackage represents a package.json manifest and its dependencies.\ntype NPMPackage struct {\n\tDevDependencies map[string]string `json:\"devDependencies\"`\n\tDependencies    map[string]string `json:\"dependencies\"`\n}\n\n// checkBun checks if Bun is installed and returns runtime info.\nfunc (j *JavaScript) checkBun() (*JSRuntime, error) {\n\tif _, err := exec.LookPath(\"bun\"); err != nil {\n\t\treturn nil, err\n\t}\n\tcmd := exec.Command(\"bun\", \"--version\")\n\toutput, err := cmd.CombinedOutput()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &JSRuntime{\n\t\tName:    \"bun\",\n\t\tVersion: strings.TrimSpace(string(output)),\n\t\tPkgMgr:  \"bun\",\n\t}, nil\n}\n\n// checkNode checks if Node.js and npm are installed and returns runtime info.\nfunc (j *JavaScript) checkNode() (*JSRuntime, error) {\n\tif _, err := exec.LookPath(\"node\"); err != nil {\n\t\treturn nil, err\n\t}\n\tif _, err := exec.LookPath(\"npm\"); err != nil {\n\t\treturn nil, ErrNpmMissing\n\t}\n\tnodeCmd := exec.Command(\"node\", \"--version\")\n\tnodeOutput, err := nodeCmd.CombinedOutput()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &JSRuntime{\n\t\tName:    \"node\",\n\t\tVersion: strings.TrimSpace(string(nodeOutput)),\n\t\tPkgMgr:  \"npm\",\n\t}, nil\n}\n\n// detectProjectRuntime checks lockfiles to determine which runtime the project\n// uses. It searches from package.json upward so workspace setups (lockfile at\n// the workspace root, package.json in a subpackage) are detected. A bun.lockb\n// counts only when it sits beside a package.json, which avoids picking up\n// unrelated lockfiles in parent directories. Returns \"bun\" if a Bun project\n// is detected, \"node\" otherwise.\nfunc (j *JavaScript) detectProjectRuntime() (string, error) {\n\twd, err := os.Getwd()\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\thome, err := os.UserHomeDir()\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tfound, pkgPath, err := search(\"package.json\", wd, home)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tif !found {\n\t\treturn \"node\", nil\n\t}\n\n\tdir := filepath.Dir(pkgPath)\n\tfor {\n\t\thasBunLock := false\n\t\tfor _, lockfile := range []string{\"bun.lockb\", \"bun.lock\"} {\n\t\t\tif _, err := os.Stat(filepath.Join(dir, lockfile)); err == nil {\n\t\t\t\thasBunLock = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif hasBunLock {\n\t\t\tif _, err := os.Stat(filepath.Join(dir, \"package.json\")); err == nil {\n\t\t\t\treturn \"bun\", nil\n\t\t\t}\n\t\t}\n\t\tparent := filepath.Dir(dir)\n\t\tif parent == dir || dir == home {\n\t\t\tbreak\n\t\t}\n\t\tdir = parent\n\t}\n\n\treturn \"node\", nil\n}\n\n// detectRuntime checks for available JavaScript runtimes.\n// Respects the project's lockfile to determine preferred runtime.\nfunc (j *JavaScript) detectRuntime() (*JSRuntime, error) {\n\tprojectRuntime, err := j.detectProjectRuntime()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar nodeErr, bunErr error\n\tvar nodeRuntime, bunRuntime *JSRuntime\n\n\tbunRuntime, bunErr = j.checkBun()\n\tnodeRuntime, nodeErr = j.checkNode()\n\n\tif j.verbose && bunRuntime == nil && bunErr != nil && !errors.Is(bunErr, exec.ErrNotFound) {\n\t\ttext.Warning(j.output, \"Bun was found on PATH but could not be queried: %v\\n\", bunErr)\n\t}\n\n\t// Use project's preferred runtime if available\n\tif projectRuntime == \"bun\" && bunRuntime != nil {\n\t\tif j.verbose {\n\t\t\ttext.Info(j.output, \"Found Bun %s (bun.lockb detected)\\n\", bunRuntime.Version)\n\t\t}\n\t\treturn bunRuntime, nil\n\t}\n\tif projectRuntime == \"node\" && nodeRuntime != nil {\n\t\tif j.verbose {\n\t\t\ttext.Info(j.output, \"Found Node.js %s with npm\\n\", nodeRuntime.Version)\n\t\t}\n\t\treturn nodeRuntime, nil\n\t}\n\n\t// Fall back to any available runtime\n\tif nodeRuntime != nil {\n\t\tif j.verbose {\n\t\t\ttext.Info(j.output, \"Found Node.js %s with npm\\n\", nodeRuntime.Version)\n\t\t}\n\t\treturn nodeRuntime, nil\n\t}\n\tif bunRuntime != nil {\n\t\tif j.verbose {\n\t\t\ttext.Info(j.output, \"Found Bun %s\\n\", bunRuntime.Version)\n\t\t}\n\t\treturn bunRuntime, nil\n\t}\n\n\tif errors.Is(nodeErr, ErrNpmMissing) {\n\t\treturn nil, fsterr.RemediationError{\n\t\t\tInner: nodeErr,\n\t\t\tRemediation: `Node.js is installed but npm is missing.\n\nInstall npm (usually bundled with Node.js):\n  - Reinstall Node.js from https://nodejs.org/\n  - Or install npm separately: https://docs.npmjs.com/downloading-and-installing-node-js-and-npm\n\nVerify: npm --version\n\nThen retry the build.`,\n\t\t}\n\t}\n\n\treturn nil, fsterr.RemediationError{\n\t\tInner: ErrNoJSRuntime,\n\t\tRemediation: `A JavaScript runtime is required to build Compute applications.\n\nInstall one of the following:\n\nOption 1 - Node.js:\n  Install from https://nodejs.org/ (LTS version recommended)\n  Or use nvm: https://github.com/nvm-sh/nvm\n  Verify: node --version && npm --version\n\nOption 2 - Bun:\n  curl -fsSL https://bun.sh/install | bash\n  Verify: bun --version\n\nThen retry the build.`,\n\t}\n}\n\n// findAllNodeModules collects every node_modules directory from startDir up to\n// (but not including) the user's home directory. The result is ordered nearest\n// first, which matches the Node.js module resolution order.\nfunc (j *JavaScript) findAllNodeModules(startDir, home string) []string {\n\tvar dirs []string\n\tdir := startDir\n\tfor {\n\t\tcandidate := filepath.Join(dir, \"node_modules\")\n\t\tif info, err := os.Stat(candidate); err == nil && info.IsDir() {\n\t\t\tdirs = append(dirs, candidate)\n\t\t}\n\t\tparent := filepath.Dir(dir)\n\t\tif parent == dir || dir == home {\n\t\t\tbreak\n\t\t}\n\t\tdir = parent\n\t}\n\treturn dirs\n}\n\n// verifyDependencies checks that package.json and node_modules exist.\nfunc (j *JavaScript) verifyDependencies() error {\n\twd, err := os.Getwd()\n\tif err != nil {\n\t\treturn err\n\t}\n\thome, err := os.UserHomeDir()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfound, pkgPath, err := search(\"package.json\", wd, home)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif !found {\n\t\tinitCmd := \"npm init\"\n\t\tinstallCmd := \"npm install @fastly/js-compute\"\n\t\tif j.runtime != nil && j.runtime.PkgMgr == \"bun\" {\n\t\t\tinitCmd = \"bun init\"\n\t\t\tinstallCmd = \"bun add @fastly/js-compute\"\n\t\t}\n\t\treturn fsterr.RemediationError{\n\t\t\tInner: ErrPackageJSONMissing,\n\t\t\tRemediation: fmt.Sprintf(`A package.json file is required for JavaScript Compute projects.\n\nEnsure you're in the correct project directory, or use --dir to specify the project root.\n\nTo initialize a new project:\n  %s\n  %s\n\nThen retry the build.`, initCmd, installCmd),\n\t\t}\n\t}\n\n\tpkgDir := filepath.Dir(pkgPath)\n\tj.nodeModulesDirs = j.findAllNodeModules(pkgDir, home)\n\tif len(j.nodeModulesDirs) == 0 {\n\t\tinstallCmd := \"npm install\"\n\t\tif j.runtime != nil && j.runtime.PkgMgr == \"bun\" {\n\t\t\tinstallCmd = \"bun install\"\n\t\t}\n\t\treturn fsterr.RemediationError{\n\t\t\tInner: ErrNodeModulesMissing,\n\t\t\tRemediation: fmt.Sprintf(`Dependencies have not been installed.\n\nRun: %s\n\nThis will install all dependencies from package.json.\nThen retry the build.`, installCmd),\n\t\t}\n\t}\n\n\tif j.verbose {\n\t\ttext.Info(j.output, \"Found package.json at %s\\n\", pkgPath)\n\t\tfor _, d := range j.nodeModulesDirs {\n\t\t\ttext.Info(j.output, \"Found node_modules at %s\\n\", d)\n\t\t}\n\t}\n\treturn nil\n}\n\n// verifyJsComputeRuntime checks that @fastly/js-compute is installed.\nfunc (j *JavaScript) verifyJsComputeRuntime() error {\n\tfor _, nmDir := range j.nodeModulesDirs {\n\t\truntimePath := filepath.Join(nmDir, \"@fastly\", \"js-compute\")\n\t\tif _, err := os.Stat(runtimePath); err == nil {\n\t\t\tif j.verbose {\n\t\t\t\ttext.Info(j.output, \"Found @fastly/js-compute runtime in %s\\n\", nmDir)\n\t\t\t}\n\t\t\treturn nil\n\t\t}\n\t}\n\tinstallCmd := \"npm install @fastly/js-compute\"\n\tif j.runtime != nil && j.runtime.PkgMgr == \"bun\" {\n\t\tinstallCmd = \"bun add @fastly/js-compute\"\n\t}\n\treturn fsterr.RemediationError{\n\t\tInner: ErrJsComputeMissing,\n\t\tRemediation: fmt.Sprintf(`The Fastly JavaScript Compute runtime is not installed.\n\nRun: %s\n\nThis package is required to compile JavaScript for Fastly Compute.\nThen retry the build.`, installCmd),\n\t}\n}\n\n// verifyToolchain checks that a JavaScript runtime is installed and accessible.\n// Called when using the default build script or a well-known starter kit script\n// (e.g. \"npm run build\").\nfunc (j *JavaScript) verifyToolchain() error {\n\truntime, err := j.detectRuntime()\n\tif err != nil {\n\t\treturn err\n\t}\n\tj.runtime = runtime\n\n\tif err := j.verifyDependencies(); err != nil {\n\t\treturn err\n\t}\n\tif err := j.verifyJsComputeRuntime(); err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\n// getDefaultBuildCommand returns the appropriate build command for the detected runtime.\nfunc (j *JavaScript) getDefaultBuildCommand() string {\n\tif j.runtime != nil && j.runtime.PkgMgr == \"bun\" {\n\t\treturn BunDefaultBuildCommand\n\t}\n\treturn JsDefaultBuildCommand\n}\n"
  },
  {
    "path": "pkg/commands/compute/language_javascript_test.go",
    "content": "package compute\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"runtime\"\n\t\"testing\"\n\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n)\n\n// createFakeRuntime creates a fake executable that outputs the given string.\nfunc createFakeRuntime(t *testing.T, dir, name, output string) {\n\tt.Helper()\n\tvar script string\n\tif runtime.GOOS == \"windows\" {\n\t\tscript = \"@echo off\\r\\necho \" + output\n\t\tname += \".bat\"\n\t} else {\n\t\tscript = \"#!/bin/sh\\necho '\" + output + \"'\"\n\t}\n\tpath := filepath.Join(dir, name)\n\t// G306 (CWE-276): Expect WriteFile permissions to be 0600 or less\n\t// Disabling as executables must be executable.\n\t// #nosec G306\n\terr := os.WriteFile(path, []byte(script), 0o755)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc TestJavaScript_detectRuntime_NoRuntime(t *testing.T) {\n\t// Create a temp directory with no executables\n\ttmpDir := t.TempDir()\n\tt.Setenv(\"PATH\", tmpDir)\n\n\tj := &JavaScript{\n\t\toutput:  &bytes.Buffer{},\n\t\tverbose: false,\n\t}\n\n\t_, err := j.detectRuntime()\n\tif err == nil {\n\t\tt.Fatal(\"expected error when no runtime is found\")\n\t}\n\n\t// Check it's a RemediationError with helpful message\n\tvar re fsterr.RemediationError\n\tif !errors.As(err, &re) {\n\t\tt.Fatalf(\"expected RemediationError, got %T\", err)\n\t}\n\n\tif re.Remediation == \"\" {\n\t\tt.Error(\"expected remediation message\")\n\t}\n}\n\nfunc TestJavaScript_detectRuntime_NodeFound(t *testing.T) {\n\ttmpDir := t.TempDir()\n\tcreateFakeRuntime(t, tmpDir, \"node\", \"v24.13.0\")\n\tcreateFakeRuntime(t, tmpDir, \"npm\", \"11.7.0\")\n\tt.Setenv(\"PATH\", tmpDir)\n\n\tj := &JavaScript{\n\t\toutput:  &bytes.Buffer{},\n\t\tverbose: false,\n\t}\n\n\trt, err := j.detectRuntime()\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\tif rt.Name != \"node\" {\n\t\tt.Errorf(\"expected runtime name 'node', got %q\", rt.Name)\n\t}\n\tif rt.PkgMgr != \"npm\" {\n\t\tt.Errorf(\"expected package manager 'npm', got %q\", rt.PkgMgr)\n\t}\n}\n\nfunc TestJavaScript_detectRuntime_BunFound(t *testing.T) {\n\ttmpDir := t.TempDir()\n\tcreateFakeRuntime(t, tmpDir, \"bun\", \"1.3.7\")\n\tt.Setenv(\"PATH\", tmpDir)\n\n\tj := &JavaScript{\n\t\toutput:  &bytes.Buffer{},\n\t\tverbose: false,\n\t}\n\n\trt, err := j.detectRuntime()\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\tif rt.Name != \"bun\" {\n\t\tt.Errorf(\"expected runtime name 'bun', got %q\", rt.Name)\n\t}\n\tif rt.PkgMgr != \"bun\" {\n\t\tt.Errorf(\"expected package manager 'bun', got %q\", rt.PkgMgr)\n\t}\n}\n\nfunc TestJavaScript_detectRuntime_NodePreferredByDefault(t *testing.T) {\n\ttmpDir := t.TempDir()\n\tcreateFakeRuntime(t, tmpDir, \"bun\", \"1.3.7\")\n\tcreateFakeRuntime(t, tmpDir, \"node\", \"v24.13.0\")\n\tcreateFakeRuntime(t, tmpDir, \"npm\", \"11.7.0\")\n\tt.Setenv(\"PATH\", tmpDir)\n\n\t// Create project dir without bun.lockb (npm project)\n\tprojectDir := t.TempDir()\n\toriginalWd, _ := os.Getwd()\n\tdefer func() { _ = os.Chdir(originalWd) }()\n\tif err := os.Chdir(projectDir); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tj := &JavaScript{\n\t\toutput:  &bytes.Buffer{},\n\t\tverbose: false,\n\t}\n\n\trt, err := j.detectRuntime()\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\t// Node should be preferred by default (no bun.lockb)\n\tif rt.Name != \"node\" {\n\t\tt.Errorf(\"expected runtime name 'node' (default), got %q\", rt.Name)\n\t}\n}\n\nfunc TestJavaScript_detectRuntime_BunPreferredWithLockfile(t *testing.T) {\n\ttmpDir := t.TempDir()\n\tcreateFakeRuntime(t, tmpDir, \"bun\", \"1.3.7\")\n\tcreateFakeRuntime(t, tmpDir, \"node\", \"v24.13.0\")\n\tcreateFakeRuntime(t, tmpDir, \"npm\", \"11.7.0\")\n\tt.Setenv(\"PATH\", tmpDir)\n\n\t// Create project dir with package.json and bun.lockb (bun project)\n\tprojectDir := t.TempDir()\n\t// #nosec G306\n\tif err := os.WriteFile(filepath.Join(projectDir, \"package.json\"), []byte(`{}`), 0o644); err != nil {\n\t\tt.Fatal(err)\n\t}\n\t// #nosec G306\n\tif err := os.WriteFile(filepath.Join(projectDir, \"bun.lockb\"), []byte{}, 0o644); err != nil {\n\t\tt.Fatal(err)\n\t}\n\toriginalWd, _ := os.Getwd()\n\tdefer func() { _ = os.Chdir(originalWd) }()\n\tif err := os.Chdir(projectDir); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tj := &JavaScript{\n\t\toutput:  &bytes.Buffer{},\n\t\tverbose: false,\n\t}\n\n\trt, err := j.detectRuntime()\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\t// Bun should be used when bun.lockb exists alongside package.json\n\tif rt.Name != \"bun\" {\n\t\tt.Errorf(\"expected runtime name 'bun' (bun.lockb detected), got %q\", rt.Name)\n\t}\n}\n\nfunc TestJavaScript_detectRuntime_BunLockfileInParentDir(t *testing.T) {\n\ttmpDir := t.TempDir()\n\tcreateFakeRuntime(t, tmpDir, \"bun\", \"1.3.7\")\n\tcreateFakeRuntime(t, tmpDir, \"node\", \"v24.13.0\")\n\tcreateFakeRuntime(t, tmpDir, \"npm\", \"11.7.0\")\n\tt.Setenv(\"PATH\", tmpDir)\n\n\t// Create project structure: projectDir/subdir with package.json and bun.lockb in projectDir\n\tprojectDir := t.TempDir()\n\tsubDir := filepath.Join(projectDir, \"subdir\")\n\tif err := os.MkdirAll(subDir, 0o755); err != nil {\n\t\tt.Fatal(err)\n\t}\n\t// #nosec G306\n\tif err := os.WriteFile(filepath.Join(projectDir, \"package.json\"), []byte(`{}`), 0o644); err != nil {\n\t\tt.Fatal(err)\n\t}\n\t// #nosec G306\n\tif err := os.WriteFile(filepath.Join(projectDir, \"bun.lockb\"), []byte{}, 0o644); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t// Run from subdir - should detect bun.lockb alongside package.json in parent\n\toriginalWd, _ := os.Getwd()\n\tdefer func() { _ = os.Chdir(originalWd) }()\n\tif err := os.Chdir(subDir); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tj := &JavaScript{\n\t\toutput:  &bytes.Buffer{},\n\t\tverbose: false,\n\t}\n\n\trt, err := j.detectRuntime()\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\t// Bun should be detected from project root (where package.json is)\n\tif rt.Name != \"bun\" {\n\t\tt.Errorf(\"expected runtime name 'bun' (bun.lockb with package.json), got %q\", rt.Name)\n\t}\n}\n\nfunc TestJavaScript_detectRuntime_BunWorkspace(t *testing.T) {\n\ttmpDir := t.TempDir()\n\tcreateFakeRuntime(t, tmpDir, \"bun\", \"1.3.7\")\n\tcreateFakeRuntime(t, tmpDir, \"node\", \"v24.13.0\")\n\tcreateFakeRuntime(t, tmpDir, \"npm\", \"11.7.0\")\n\tt.Setenv(\"PATH\", tmpDir)\n\n\t// Create Bun workspace structure:\n\t// workspace/package.json (workspace root)\n\t// workspace/bun.lockb\n\t// workspace/packages/myapp/package.json (subpackage - we run from here)\n\tworkspaceDir := t.TempDir()\n\tsubpkgDir := filepath.Join(workspaceDir, \"packages\", \"myapp\")\n\tif err := os.MkdirAll(subpkgDir, 0o755); err != nil {\n\t\tt.Fatal(err)\n\t}\n\t// Workspace root package.json\n\t// #nosec G306\n\tif err := os.WriteFile(filepath.Join(workspaceDir, \"package.json\"), []byte(`{\"workspaces\":[\"packages/*\"]}`), 0o644); err != nil {\n\t\tt.Fatal(err)\n\t}\n\t// #nosec G306\n\tif err := os.WriteFile(filepath.Join(workspaceDir, \"bun.lockb\"), []byte{}, 0o644); err != nil {\n\t\tt.Fatal(err)\n\t}\n\t// Subpackage package.json\n\t// #nosec G306\n\tif err := os.WriteFile(filepath.Join(subpkgDir, \"package.json\"), []byte(`{\"name\":\"myapp\"}`), 0o644); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t// Run from subpackage\n\toriginalWd, _ := os.Getwd()\n\tdefer func() { _ = os.Chdir(originalWd) }()\n\tif err := os.Chdir(subpkgDir); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tj := &JavaScript{\n\t\toutput:  &bytes.Buffer{},\n\t\tverbose: false,\n\t}\n\n\trt, err := j.detectRuntime()\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\t// Bun should be detected from workspace root (bun.lockb + package.json)\n\tif rt.Name != \"bun\" {\n\t\tt.Errorf(\"expected runtime name 'bun' (workspace detected), got %q\", rt.Name)\n\t}\n}\n\nfunc TestJavaScript_detectRuntime_IgnoresUnrelatedBunLockfile(t *testing.T) {\n\ttmpDir := t.TempDir()\n\tcreateFakeRuntime(t, tmpDir, \"bun\", \"1.3.7\")\n\tcreateFakeRuntime(t, tmpDir, \"node\", \"v24.13.0\")\n\tcreateFakeRuntime(t, tmpDir, \"npm\", \"11.7.0\")\n\tt.Setenv(\"PATH\", tmpDir)\n\n\t// Create structure: parentDir/bun.lockb (unrelated) and parentDir/project/package.json (npm project)\n\tparentDir := t.TempDir()\n\tprojectDir := filepath.Join(parentDir, \"project\")\n\tif err := os.MkdirAll(projectDir, 0o755); err != nil {\n\t\tt.Fatal(err)\n\t}\n\t// Unrelated bun.lockb in parent (not alongside package.json)\n\t// #nosec G306\n\tif err := os.WriteFile(filepath.Join(parentDir, \"bun.lockb\"), []byte{}, 0o644); err != nil {\n\t\tt.Fatal(err)\n\t}\n\t// Project's package.json (no bun.lockb here)\n\t// #nosec G306\n\tif err := os.WriteFile(filepath.Join(projectDir, \"package.json\"), []byte(`{}`), 0o644); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\toriginalWd, _ := os.Getwd()\n\tdefer func() { _ = os.Chdir(originalWd) }()\n\tif err := os.Chdir(projectDir); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tj := &JavaScript{\n\t\toutput:  &bytes.Buffer{},\n\t\tverbose: false,\n\t}\n\n\trt, err := j.detectRuntime()\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\t// Should use Node because project root has no bun.lockb (parent's is unrelated)\n\tif rt.Name != \"node\" {\n\t\tt.Errorf(\"expected runtime name 'node' (unrelated bun.lockb ignored), got %q\", rt.Name)\n\t}\n}\n\nfunc TestJavaScript_detectRuntime_NodeMissingNpm(t *testing.T) {\n\ttmpDir := t.TempDir()\n\tcreateFakeRuntime(t, tmpDir, \"node\", \"v24.13.0\")\n\t// npm is NOT created\n\tt.Setenv(\"PATH\", tmpDir)\n\n\tj := &JavaScript{\n\t\toutput:  &bytes.Buffer{},\n\t\tverbose: false,\n\t}\n\n\t_, err := j.detectRuntime()\n\tif err == nil {\n\t\tt.Fatal(\"expected error when npm is missing\")\n\t}\n\n\t// Check for specific error message\n\tvar re fsterr.RemediationError\n\tif !errors.As(err, &re) {\n\t\tt.Fatalf(\"expected RemediationError, got %T\", err)\n\t}\n\n\tif !errors.Is(re.Inner, ErrNpmMissing) {\n\t\tt.Errorf(\"expected ErrNpmMissing, got %v\", re.Inner)\n\t}\n}\n\nfunc TestJavaScript_findAllNodeModules(t *testing.T) {\n\t// Create directory structure:\n\t// tmpDir/project/node_modules (parent)\n\t// tmpDir/project/subdir/node_modules (child)\n\ttmpDir := t.TempDir()\n\tprojectDir := filepath.Join(tmpDir, \"project\")\n\tsubDir := filepath.Join(projectDir, \"subdir\")\n\tparentNM := filepath.Join(projectDir, \"node_modules\")\n\tchildNM := filepath.Join(subDir, \"node_modules\")\n\n\tif err := os.MkdirAll(childNM, 0o755); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif err := os.MkdirAll(parentNM, 0o755); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tj := &JavaScript{}\n\n\t// From subDir should find both, nearest first\n\tdirs := j.findAllNodeModules(subDir, tmpDir)\n\tif len(dirs) != 2 {\n\t\tt.Fatalf(\"expected 2 node_modules dirs, got %d: %v\", len(dirs), dirs)\n\t}\n\tif dirs[0] != childNM {\n\t\tt.Errorf(\"expected first dir %q, got %q\", childNM, dirs[0])\n\t}\n\tif dirs[1] != parentNM {\n\t\tt.Errorf(\"expected second dir %q, got %q\", parentNM, dirs[1])\n\t}\n\n\t// From projectDir should find only one\n\tdirs = j.findAllNodeModules(projectDir, tmpDir)\n\tif len(dirs) != 1 {\n\t\tt.Fatalf(\"expected 1 node_modules dir, got %d: %v\", len(dirs), dirs)\n\t}\n\tif dirs[0] != parentNM {\n\t\tt.Errorf(\"expected path %q, got %q\", parentNM, dirs[0])\n\t}\n\n\t// Should not find node_modules above home\n\tdirs = j.findAllNodeModules(tmpDir, tmpDir)\n\tif len(dirs) != 0 {\n\t\tt.Errorf(\"expected no node_modules dirs, got %v\", dirs)\n\t}\n}\n\nfunc TestJavaScript_verifyDependencies_NoPackageJson(t *testing.T) {\n\ttmpDir := t.TempDir()\n\tbinDir := t.TempDir()\n\tcreateFakeRuntime(t, binDir, \"node\", \"v24.13.0\")\n\tcreateFakeRuntime(t, binDir, \"npm\", \"11.7.0\")\n\tt.Setenv(\"PATH\", binDir)\n\n\t// Change to temp dir with no package.json\n\toriginalWd, _ := os.Getwd()\n\tdefer func() { _ = os.Chdir(originalWd) }()\n\tif err := os.Chdir(tmpDir); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tj := &JavaScript{\n\t\toutput:  &bytes.Buffer{},\n\t\tverbose: false,\n\t\truntime: &JSRuntime{Name: \"node\", PkgMgr: \"npm\"},\n\t}\n\n\terr := j.verifyDependencies()\n\tif err == nil {\n\t\tt.Fatal(\"expected error when package.json not found\")\n\t}\n\n\tvar re fsterr.RemediationError\n\tif !errors.As(err, &re) {\n\t\tt.Fatalf(\"expected RemediationError, got %T\", err)\n\t}\n}\n\nfunc TestJavaScript_verifyDependencies_NoNodeModules(t *testing.T) {\n\ttmpDir := t.TempDir()\n\tbinDir := t.TempDir()\n\tcreateFakeRuntime(t, binDir, \"node\", \"v24.13.0\")\n\tcreateFakeRuntime(t, binDir, \"npm\", \"11.7.0\")\n\tt.Setenv(\"PATH\", binDir)\n\n\t// Create package.json but no node_modules\n\t// #nosec G306\n\tif err := os.WriteFile(filepath.Join(tmpDir, \"package.json\"), []byte(`{}`), 0o644); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\toriginalWd, _ := os.Getwd()\n\tdefer func() { _ = os.Chdir(originalWd) }()\n\tif err := os.Chdir(tmpDir); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tj := &JavaScript{\n\t\toutput:  &bytes.Buffer{},\n\t\tverbose: false,\n\t\truntime: &JSRuntime{Name: \"node\", PkgMgr: \"npm\"},\n\t}\n\n\terr := j.verifyDependencies()\n\tif err == nil {\n\t\tt.Fatal(\"expected error when node_modules not found\")\n\t}\n\n\tvar re fsterr.RemediationError\n\tif !errors.As(err, &re) {\n\t\tt.Fatalf(\"expected RemediationError, got %T\", err)\n\t}\n}\n\nfunc TestJavaScript_verifyJsComputeRuntime_NotInstalled(t *testing.T) {\n\ttmpDir := t.TempDir()\n\tnodeModulesDir := filepath.Join(tmpDir, \"node_modules\")\n\tif err := os.MkdirAll(nodeModulesDir, 0o755); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tj := &JavaScript{\n\t\toutput:          &bytes.Buffer{},\n\t\tverbose:         false,\n\t\tnodeModulesDirs: []string{nodeModulesDir},\n\t\truntime:         &JSRuntime{Name: \"node\", PkgMgr: \"npm\"},\n\t}\n\n\terr := j.verifyJsComputeRuntime()\n\tif err == nil {\n\t\tt.Fatal(\"expected error when @fastly/js-compute not found\")\n\t}\n\n\tvar re fsterr.RemediationError\n\tif !errors.As(err, &re) {\n\t\tt.Fatalf(\"expected RemediationError, got %T\", err)\n\t}\n}\n\nfunc TestJavaScript_verifyJsComputeRuntime_Installed(t *testing.T) {\n\ttmpDir := t.TempDir()\n\tnodeModulesDir := filepath.Join(tmpDir, \"node_modules\")\n\truntimeDir := filepath.Join(nodeModulesDir, \"@fastly\", \"js-compute\")\n\tif err := os.MkdirAll(runtimeDir, 0o755); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tj := &JavaScript{\n\t\toutput:          &bytes.Buffer{},\n\t\tverbose:         false,\n\t\tnodeModulesDirs: []string{nodeModulesDir},\n\t\truntime:         &JSRuntime{Name: \"node\", PkgMgr: \"npm\"},\n\t}\n\n\terr := j.verifyJsComputeRuntime()\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n}\n\nfunc TestJavaScript_verifyJsComputeRuntime_InParentNodeModules(t *testing.T) {\n\t// Monorepo: @fastly/js-compute is hoisted to root node_modules\n\ttmpDir := t.TempDir()\n\trootNM := filepath.Join(tmpDir, \"node_modules\")\n\tchildNM := filepath.Join(tmpDir, \"app\", \"node_modules\")\n\truntimeDir := filepath.Join(rootNM, \"@fastly\", \"js-compute\")\n\tif err := os.MkdirAll(runtimeDir, 0o755); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif err := os.MkdirAll(childNM, 0o755); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tj := &JavaScript{\n\t\toutput:          &bytes.Buffer{},\n\t\tverbose:         false,\n\t\tnodeModulesDirs: []string{childNM, rootNM},\n\t\truntime:         &JSRuntime{Name: \"node\", PkgMgr: \"npm\"},\n\t}\n\n\terr := j.verifyJsComputeRuntime()\n\tif err != nil {\n\t\tt.Fatalf(\"expected to find @fastly/js-compute in parent node_modules: %v\", err)\n\t}\n}\n\nfunc TestJavaScript_isDefaultBuildScript(t *testing.T) {\n\ttests := []struct {\n\t\tbuild string\n\t\twant  bool\n\t}{\n\t\t{\"npm run build\", true},\n\t\t{\"bun run build\", true},\n\t\t{\"  npm run build  \", true},\n\t\t{\"NODE_ENV=production npm run build\", true},\n\t\t{\"PATH=./node_modules/.bin:$PATH npm run build\", true},\n\t\t{\"NODE_ENV=production FOO=bar bun run build\", true},\n\t\t{\"\", false},\n\t\t{\"custom-build-cmd\", false},\n\t\t{\"npm run build && echo done\", false},\n\t\t{\"=value npm run build\", false},\n\t\t{\"./scripts/build npm run build\", false},\n\t}\n\n\tfor _, tt := range tests {\n\t\tj := &JavaScript{build: tt.build}\n\t\tif got := j.isDefaultBuildScript(); got != tt.want {\n\t\t\tt.Errorf(\"isDefaultBuildScript() with build=%q: got %v, want %v\", tt.build, got, tt.want)\n\t\t}\n\t}\n}\n\nfunc TestJavaScript_getDefaultBuildCommand_Node(t *testing.T) {\n\tj := &JavaScript{\n\t\truntime: &JSRuntime{Name: \"node\", PkgMgr: \"npm\"},\n\t}\n\n\tcmd := j.getDefaultBuildCommand()\n\tif cmd != JsDefaultBuildCommand {\n\t\tt.Errorf(\"expected default command, got %q\", cmd)\n\t}\n}\n\nfunc TestJavaScript_getDefaultBuildCommand_Bun(t *testing.T) {\n\tj := &JavaScript{\n\t\truntime: &JSRuntime{Name: \"bun\", PkgMgr: \"bun\"},\n\t}\n\n\tcmd := j.getDefaultBuildCommand()\n\tif cmd == JsDefaultBuildCommand {\n\t\tt.Errorf(\"expected bun command, got npm command %q\", cmd)\n\t}\n\tif !bytes.Contains([]byte(cmd), []byte(\"bunx\")) {\n\t\tt.Errorf(\"expected command to contain 'bunx', got %q\", cmd)\n\t}\n}\n"
  },
  {
    "path": "pkg/commands/compute/language_other.go",
    "content": "package compute\n\nimport (\n\t\"io\"\n\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// NewOther constructs a new unsupported language instance.\nfunc NewOther(\n\tc *BuildCommand,\n\tin io.Reader,\n\tmanifestFilename string,\n\tout io.Writer,\n\tspinner text.Spinner,\n) *Other {\n\treturn &Other{\n\t\tShell: Shell{},\n\n\t\tautoYes:               c.Globals.Flags.AutoYes,\n\t\tbuild:                 c.Globals.Manifest.File.Scripts.Build,\n\t\tdefaultBuild:          false, // there is no default build for 'other'\n\t\tenv:                   c.Globals.Manifest.File.Scripts.EnvVars,\n\t\terrlog:                c.Globals.ErrLog,\n\t\tinput:                 in,\n\t\tmanifestFilename:      manifestFilename,\n\t\tmetadataFilterEnvVars: c.MetadataFilterEnvVars,\n\t\tnonInteractive:        c.Globals.Flags.NonInteractive,\n\t\toutput:                out,\n\t\tpostBuild:             c.Globals.Manifest.File.Scripts.PostBuild,\n\t\tspinner:               spinner,\n\t\ttimeout:               c.Flags.Timeout,\n\t\tverbose:               c.Globals.Verbose(),\n\t}\n}\n\n// Other implements a Toolchain for languages without official support.\ntype Other struct {\n\tShell\n\n\t// autoYes is the --auto-yes flag.\n\tautoYes bool\n\t// build is a shell command defined in fastly.toml using [scripts.build].\n\tbuild string\n\t// defaultBuild indicates if the default build script was used.\n\tdefaultBuild bool\n\t// env is environment variables to be set.\n\tenv []string\n\t// errlog is an abstraction for recording errors to disk.\n\terrlog fsterr.LogInterface\n\t// input is the user's terminal stdin stream\n\tinput io.Reader\n\t// manifestFilename is the name of the manifest file.\n\tmanifestFilename string\n\t// metadataFilterEnvVars is a comma-separated list of user defined env vars.\n\tmetadataFilterEnvVars string\n\t// nonInteractive is the --non-interactive flag.\n\tnonInteractive bool\n\t// output is the users terminal stdout stream\n\toutput io.Writer\n\t// postBuild is a custom script executed after the build but before the Wasm\n\t// binary is added to the .tar.gz archive.\n\tpostBuild string\n\t// spinner is a terminal progress status indicator.\n\tspinner text.Spinner\n\t// timeout is the build execution threshold.\n\ttimeout int\n\t// verbose indicates if the user set --verbose\n\tverbose bool\n}\n\n// DefaultBuildScript indicates if a custom build script was used.\nfunc (o Other) DefaultBuildScript() bool {\n\treturn o.defaultBuild\n}\n\n// Dependencies returns all dependencies used by the project.\nfunc (o Other) Dependencies() map[string]string {\n\tdeps := make(map[string]string)\n\treturn deps\n}\n\n// Build implements the Toolchain interface and attempts to compile the package\n// source to a Wasm binary.\nfunc (o Other) Build() error {\n\tbt := BuildToolchain{\n\t\tautoYes:               o.autoYes,\n\t\tbuildFn:               o.Shell.Build,\n\t\tbuildScript:           o.build,\n\t\tenv:                   o.env,\n\t\terrlog:                o.errlog,\n\t\tin:                    o.input,\n\t\tmanifestFilename:      o.manifestFilename,\n\t\tmetadataFilterEnvVars: o.metadataFilterEnvVars,\n\t\tnonInteractive:        o.nonInteractive,\n\t\tout:                   o.output,\n\t\tpostBuild:             o.postBuild,\n\t\tspinner:               o.spinner,\n\t\ttimeout:               o.timeout,\n\t\tverbose:               o.verbose,\n\t}\n\treturn bt.Build()\n}\n"
  },
  {
    "path": "pkg/commands/compute/language_rust.go",
    "content": "package compute\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"regexp\"\n\t\"strings\"\n\n\t\"github.com/Masterminds/semver/v3\"\n\ttoml \"github.com/pelletier/go-toml\"\n\n\t\"github.com/fastly/cli/pkg/config\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/filesystem\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// RustDefaultBuildCommand is a build command compiled into the CLI binary so it\n// can be used as a fallback for customer's who have an existing Compute project and\n// are simply upgrading their CLI version and might not be familiar with the\n// changes in the 4.0.0 release with regards to how build logic has moved to the\n// fastly.toml manifest.\n//\n// NOTE: In the 5.x CLI releases we persisted the default to the fastly.toml\n// We no longer do that. In 6.x we use the default and just inform the user.\n// This makes the experience less confusing as users didn't expect file changes.\nconst RustDefaultBuildCommand = \"cargo build --bin %s --release --target %s --color always\"\n\n// RustDefaultWasmWasiTarget is the expected Rust WasmWasi build target.\nconst RustDefaultWasmWasiTarget = \"wasm32-wasip1\"\n\n// OldRustDefaultWasmWasiTarget was the expected Rust WasmWasi build target before version 11 of the CLI.\nconst OldRustDefaultWasmWasiTarget = \"wasm32-wasi\"\n\n// RustManifest is the manifest file for defining project configuration.\nconst RustManifest = \"Cargo.toml\"\n\n// RustDefaultPackageName is the expected binary create/package name to be built.\nconst RustDefaultPackageName = \"fastly-compute-project\"\n\n// RustSourceDirectory represents the source code directory.\nconst RustSourceDirectory = \"src\"\n\n// NewRust constructs a new Rust toolchain.\nfunc NewRust(\n\tc *BuildCommand,\n\tin io.Reader,\n\tmanifestFilename string,\n\tout io.Writer,\n\tspinner text.Spinner,\n) *Rust {\n\treturn &Rust{\n\t\tShell: Shell{},\n\n\t\tautoYes:               c.Globals.Flags.AutoYes,\n\t\tbuild:                 c.Globals.Manifest.File.Scripts.Build,\n\t\tconfig:                c.Globals.Config.Language.Rust,\n\t\tenv:                   c.Globals.Manifest.File.Scripts.EnvVars,\n\t\terrlog:                c.Globals.ErrLog,\n\t\tinput:                 in,\n\t\tmanifestFilename:      manifestFilename,\n\t\tmetadataFilterEnvVars: c.MetadataFilterEnvVars,\n\t\tnonInteractive:        c.Globals.Flags.NonInteractive,\n\t\toutput:                out,\n\t\tpostBuild:             c.Globals.Manifest.File.Scripts.PostBuild,\n\t\tspinner:               spinner,\n\t\ttimeout:               c.Flags.Timeout,\n\t\tverbose:               c.Globals.Verbose(),\n\t}\n}\n\n// Rust implements a Toolchain for the Rust language.\ntype Rust struct {\n\tShell\n\n\t// autoYes is the --auto-yes flag.\n\tautoYes bool\n\t// build is a shell command defined in fastly.toml using [scripts.build].\n\tbuild string\n\t// config is the Rust specific application configuration.\n\tconfig config.Rust\n\t// defaultBuild indicates if the default build script was used.\n\tdefaultBuild bool\n\t// env is environment variables to be set.\n\tenv []string\n\t// errlog is an abstraction for recording errors to disk.\n\terrlog fsterr.LogInterface\n\t// input is the user's terminal stdin stream\n\tinput io.Reader\n\t// manifestFilename is the name of the manifest file.\n\tmanifestFilename string\n\t// metadataFilterEnvVars is a comma-separated list of user defined env vars.\n\tmetadataFilterEnvVars string\n\t// nonInteractive is the --non-interactive flag.\n\tnonInteractive bool\n\t// output is the users terminal stdout stream\n\toutput io.Writer\n\t// packageName is the resolved package name from the project Cargo.toml\n\tpackageName string\n\t// postBuild is a custom script executed after the build but before the Wasm\n\t// binary is added to the .tar.gz archive.\n\tpostBuild string\n\t// projectRoot is the root directory where the Cargo.toml is located.\n\tprojectRoot string\n\t// spinner is a terminal progress status indicator.\n\tspinner text.Spinner\n\t// timeout is the build execution threshold.\n\ttimeout int\n\t// verbose indicates if the user set --verbose\n\tverbose bool\n}\n\n// DefaultBuildScript indicates if a custom build script was used.\nfunc (r *Rust) DefaultBuildScript() bool {\n\treturn r.defaultBuild\n}\n\n// CargoLockFilePackage represents a package within a Rust lockfile.\ntype CargoLockFilePackage struct {\n\tName    string `toml:\"name\"`\n\tVersion string `toml:\"version\"`\n}\n\n// CargoLockFile represents a Rust lockfile.\ntype CargoLockFile struct {\n\tPackages []CargoLockFilePackage `toml:\"package\"`\n}\n\n// Dependencies returns all dependencies used by the project.\nfunc (r *Rust) Dependencies() map[string]string {\n\tdeps := make(map[string]string)\n\n\tvar clf CargoLockFile\n\tif data, err := os.ReadFile(\"Cargo.lock\"); err == nil {\n\t\tif err := toml.Unmarshal(data, &clf); err == nil {\n\t\t\tfor _, v := range clf.Packages {\n\t\t\t\tdeps[v.Name] = v.Version\n\t\t\t}\n\t\t}\n\t}\n\n\treturn deps\n}\n\n// Build compiles the user's source code into a Wasm binary.\nfunc (r *Rust) Build() error {\n\tif r.build == \"\" {\n\t\tr.build = fmt.Sprintf(RustDefaultBuildCommand, RustDefaultPackageName, RustDefaultWasmWasiTarget)\n\t\tr.defaultBuild = true\n\t}\n\n\terr := r.modifyCargoPackageName(r.defaultBuild)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif r.defaultBuild && r.verbose {\n\t\ttext.Info(r.output, \"No [scripts.build] found in %s. The following default build command for Rust will be used: `%s`\\n\\n\", r.manifestFilename, r.build)\n\t}\n\n\tversion, err := r.toolchainConstraint()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif version != nil {\n\t\terr := r.checkCargoConfigFileName(version)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\twasmWasiTarget := r.config.WasmWasiTarget\n\tif wasmWasiTarget != RustDefaultWasmWasiTarget {\n\t\treturn fmt.Errorf(\"the default build in .fastly/config.toml should produce a %s binary, but was instead set to produce a %s binary\", RustDefaultWasmWasiTarget, wasmWasiTarget)\n\t}\n\n\tbt := BuildToolchain{\n\t\tautoYes:                   r.autoYes,\n\t\tbuildFn:                   r.Shell.Build,\n\t\tbuildScript:               r.build,\n\t\tenv:                       r.env,\n\t\terrlog:                    r.errlog,\n\t\tin:                        r.input,\n\t\tinternalPostBuildCallback: r.ProcessLocation,\n\t\tmanifestFilename:          r.manifestFilename,\n\t\tmetadataFilterEnvVars:     r.metadataFilterEnvVars,\n\t\tnonInteractive:            r.nonInteractive,\n\t\tout:                       r.output,\n\t\tpostBuild:                 r.postBuild,\n\t\tspinner:                   r.spinner,\n\t\ttimeout:                   r.timeout,\n\t\tverbose:                   r.verbose,\n\t}\n\n\treturn bt.Build()\n}\n\n// RustToolchainManifest models a [toolchain] from a rust-toolchain.toml manifest.\ntype RustToolchainManifest struct {\n\tToolchain RustToolchain `toml:\"toolchain\"`\n}\n\n// RustToolchain models the rust-toolchain targets.\ntype RustToolchain struct {\n\tTargets []string `toml:\"targets\"`\n}\n\n// modifyCargoPackageName validates whether the --bin flag matches the\n// Cargo.toml package name. If it doesn't match, update the default build script\n// to match.\nfunc (r *Rust) modifyCargoPackageName(defaultBuild bool) error {\n\ts := \"cargo locate-project --quiet\"\n\targs := strings.Split(s, \" \")\n\n\tvar stdout, stderr bytes.Buffer\n\n\t// gosec flagged this:\n\t// G204 (CWE-78): Subprocess launched with variable\n\t// Disabling as we control this command.\n\t// #nosec\n\t// nosemgrep\n\tcmd := exec.Command(args[0], args[1:]...)\n\tcmd.Stdout = &stdout\n\tcmd.Stderr = &stderr\n\n\terr := cmd.Run()\n\tif err != nil {\n\t\tif stderr.Len() > 0 {\n\t\t\terr = fmt.Errorf(\"%w: %s\", err, stderr.String())\n\t\t}\n\t\treturn fmt.Errorf(\"failed to execute command '%s': %w\", s, err)\n\t}\n\n\tif r.verbose {\n\t\ttext.Output(r.output, \"Command output for '%s': %s\", s, stdout.String())\n\t}\n\n\tvar cp *CargoLocateProject\n\terr = json.Unmarshal(stdout.Bytes(), &cp)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to unmarshal manifest project root metadata: %w\", err)\n\t}\n\n\tr.projectRoot = cp.Root\n\n\tvar m CargoManifest\n\tif err := m.Read(cp.Root); err != nil {\n\t\treturn fmt.Errorf(\"error reading %s manifest: %w\", RustManifest, err)\n\t}\n\n\tswitch {\n\tcase m.Package.Name != \"\":\n\t\t// If using standard project structure.\n\t\t// Cargo.toml won't be a Workspace, so it will contain a package name.\n\t\tr.packageName = m.Package.Name\n\tcase len(m.Workspace.Members) > 0 && defaultBuild:\n\t\t// If user has a Cargo Workspace AND no custom script.\n\t\t// We need to identify which Workspace package is their application.\n\t\t// Then extract the package name from its Cargo.toml manifest.\n\t\t// We do this by checking for a rust-toolchain.toml containing the proper target.\n\t\t//\n\t\t// NOTE: This logic will need to change in the future.\n\t\t// Specifically, when we support linking multiple Wasm binaries.\n\t\tfor _, m := range m.Workspace.Members {\n\t\t\tvar rtm RustToolchainManifest\n\t\t\trustToolchainFile := \"rust-toolchain.toml\"\n\t\t\tdata, err := os.ReadFile(filepath.Join(m, rustToolchainFile)) // #nosec G304 (CWE-22)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terr = toml.Unmarshal(data, &rtm)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"failed to unmarshal '%s' data: %w\", rustToolchainFile, err)\n\t\t\t}\n\t\t\tif len(rtm.Toolchain.Targets) > 0 {\n\t\t\t\tif rtm.Toolchain.Targets[0] == RustDefaultWasmWasiTarget {\n\t\t\t\t\tvar cm CargoManifest\n\t\t\t\t\terr := cm.Read(filepath.Join(m, \"Cargo.toml\"))\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn err\n\t\t\t\t\t}\n\t\t\t\t\tr.packageName = cm.Package.Name\n\t\t\t\t} else {\n\t\t\t\t\treturn fmt.Errorf(\"please consult https://www.fastly.com/documentation/guides/compute/#install-language-tooling to configure your toolchain correctly\")\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\tcase len(m.Workspace.Members) > 0 && !defaultBuild:\n\t\t// If user has a Cargo Workspace AND a custom script.\n\t\t// Trust their custom script aligns with the relevant Workspace package name.\n\t\t// i.e. we parse the package name specified in their custom script.\n\t\tparts := strings.Split(r.build, \" \")\n\t\tfor i, p := range parts {\n\t\t\tif p == \"--bin\" {\n\t\t\t\tr.packageName = parts[i+1]\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\t// Ensure the default build script matches the Cargo.toml package name.\n\tif defaultBuild && r.packageName != \"\" && r.packageName != RustDefaultPackageName {\n\t\tr.build = fmt.Sprintf(RustDefaultBuildCommand, r.packageName, RustDefaultWasmWasiTarget)\n\t}\n\n\treturn nil\n}\n\n// toolchainConstraint generates an error if the toolchain constraint is not met.\nfunc (r *Rust) toolchainConstraint() (*semver.Version, error) {\n\tif r.verbose {\n\t\ttext.Info(r.output, \"The Fastly CLI requires a Rust version '%s'.\\n\\n\", r.config.ToolchainConstraint)\n\t}\n\n\tversionCommand := \"cargo version --quiet\"\n\targs := strings.Split(versionCommand, \" \")\n\n\t// gosec flagged this:\n\t// G204 (CWE-78): Subprocess launched with function call as argument or cmd arguments\n\t// Disabling as we trust the source of the variable.\n\t// #nosec\n\t// nosemgrep\n\tcmd := exec.Command(args[0], args[1:]...)\n\tstdout, err := cmd.Output()\n\toutput := string(stdout)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tversionPattern := regexp.MustCompile(`cargo (?P<version>\\d[^\\s]+)`)\n\tmatch := versionPattern.FindStringSubmatch(output)\n\tif len(match) < 2 { // We expect a pattern with one capture group.\n\t\treturn nil, fmt.Errorf(\"unable to obtain a version number from the 'cargo' command\")\n\t}\n\tversion := match[1]\n\n\tv, err := semver.NewVersion(version)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"the version string '%s' reported by the 'cargo' command is not a valid version number\", version)\n\t}\n\n\tc, err := semver.NewConstraint(r.config.ToolchainConstraint)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"the 'toolchain_constraint' value '%s' (from the config.toml file) is not a valid version constraint\", r.config.ToolchainConstraint)\n\t}\n\n\t// Even though most users shouldn't be using Rust prereleases, it is\n\t// useful for Fastly to be able to test with Rust prereleases, so we\n\t// shouldn't outright prohibit them.\n\tc.IncludePrerelease = true\n\n\tvalid, errs := c.Validate(v)\n\tif !valid {\n\t\terr = nil\n\t\tfor _, e := range errs {\n\t\t\t// if an 'upper bound' constraint was\n\t\t\t// violated, generate an error message\n\t\t\t// specific to that situation\n\t\t\tif strings.Contains(e.Error(), \"is greater than\") {\n\t\t\t\terr = fmt.Errorf(\"version '%s' of Rust has not been validated for use with Fastly Compute\", v)\n\t\t\t}\n\t\t\t// if an 'exact version' constraint was\n\t\t\t// violated, generate an error message\n\t\t\t// specific to that situation\n\t\t\tif strings.Contains(e.Error(), \"is equal to\") {\n\t\t\t\terr = fmt.Errorf(\"version '%s' of Rust is not compatible with Fastly Compute\", v)\n\t\t\t}\n\t\t}\n\t\tif err == nil {\n\t\t\terr = fmt.Errorf(\"the Rust version requirement was not satisfied: '%w'\", errors.Join(errs...))\n\t\t}\n\t\treturn nil, fsterr.RemediationError{\n\t\t\tInner:       err,\n\t\t\tRemediation: \"Consult the Rust guide for Compute at https://www.fastly.com/documentation/guides/compute/rust/ for more information.\",\n\t\t}\n\t}\n\n\treturn v, nil\n}\n\nfunc (r *Rust) checkCargoConfigFileName(rustVersion *semver.Version) error {\n\tdir, err := os.Getwd()\n\tif err != nil {\n\t\tr.errlog.Add(err)\n\t\treturn fmt.Errorf(\"getting current working directory: %w\", err)\n\t}\n\n\tif !filesystem.FileExists(filepath.Join(dir, \".cargo\", \"config\")) {\n\t\treturn nil\n\t}\n\n\tfilenameMsg := \"\\nThe Cargo configuration file name is .cargo/config\"\n\n\tc, _ := semver.NewConstraint(\">=1.78.0\")\n\n\tif c.Check(rustVersion) {\n\t\ttext.Warning(r.output, filenameMsg)\n\t\treturn fmt.Errorf(\"the build cannot proceed with Rust version '%s' as the file must be named .cargo/config.toml\", rustVersion)\n\t}\n\n\ttext.Warning(r.output, filenameMsg+\". The file should be renamed to .cargo/config.toml to be compatible with Rust 1.78.0 or later\\n\\n\")\n\treturn nil\n}\n\n// ProcessLocation ensures the generated Rust Wasm binary is moved to the\n// required location for packaging.\nfunc (r *Rust) ProcessLocation() error {\n\tdir, err := os.Getwd()\n\tif err != nil {\n\t\tr.errlog.Add(err)\n\t\treturn fmt.Errorf(\"getting current working directory: %w\", err)\n\t}\n\n\tvar metadata CargoMetadata\n\tif err := metadata.Read(r.errlog); err != nil {\n\t\tr.errlog.Add(err)\n\t\treturn fmt.Errorf(\"error reading cargo metadata: %w\", err)\n\t}\n\n\tsrc := filepath.Join(metadata.TargetDirectory, r.config.WasmWasiTarget, \"release\", fmt.Sprintf(\"%s.wasm\", r.packageName))\n\tdst := filepath.Join(dir, \"bin\", \"main.wasm\")\n\n\terr = filesystem.CopyFile(src, dst)\n\tif err != nil {\n\t\t// check for the binary in the 'old' location before\n\t\t// the compilation target name was changed\n\t\tsrc := filepath.Join(metadata.TargetDirectory, OldRustDefaultWasmWasiTarget, \"release\", fmt.Sprintf(\"%s.wasm\", r.packageName))\n\t\tif filesystem.FileExists(src) {\n\t\t\treturn fmt.Errorf(\"this project is configured to produce a '%s' target, but the Fastly CLI requires the '%s' target.\\nTo reconfigure your project, follow the instructions at https://www.fastly.com/documentation/guides/compute/rust/#using-fastly-cli-1100-or-higher\", OldRustDefaultWasmWasiTarget, r.config.WasmWasiTarget)\n\t\t}\n\n\t\tr.errlog.Add(err)\n\t\treturn fmt.Errorf(\"failed to copy wasm binary: %w\", err)\n\t}\n\treturn nil\n}\n\n// CargoLocateProject represents the metadata for where to find the project's\n// Cargo.toml manifest file.\ntype CargoLocateProject struct {\n\tRoot string `json:\"root\"`\n}\n\n// CargoManifest models the package configuration properties of a Rust Cargo\n// manifest which we are interested in and are read from the Cargo.toml manifest\n// file within the $PWD of the package.\ntype CargoManifest struct {\n\tPackage   CargoPackage   `toml:\"package\"`\n\tWorkspace CargoWorkspace `toml:\"workspace\"`\n}\n\n// Read the contents of the Cargo.toml manifest from filename.\nfunc (m *CargoManifest) Read(path string) error {\n\t// gosec flagged this:\n\t// G304 (CWE-22): Potential file inclusion via variable.\n\t// Disabling as we need to load the Cargo.toml from the user's file system.\n\t// This file is decoded into a predefined struct, any unrecognised fields are dropped.\n\t// #nosec\n\tdata, err := os.ReadFile(path)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn toml.Unmarshal(data, m)\n}\n\n// CargoWorkspace models the [workspace] config inside Cargo.toml.\ntype CargoWorkspace struct {\n\tMembers []string `toml:\"members\" json:\"members\"`\n}\n\n// CargoPackage models the package configuration properties of a Rust Cargo\n// package which we are interested in and is embedded within CargoManifest and\n// CargoLock.\ntype CargoPackage struct {\n\tName    string `toml:\"name\" json:\"name\"`\n\tVersion string `toml:\"version\" json:\"version\"`\n}\n\n// CargoMetadata models information about the workspace members and resolved\n// dependencies of the current package via `cargo metadata` command output.\ntype CargoMetadata struct {\n\tPackage         []CargoMetadataPackage `json:\"packages\"`\n\tTargetDirectory string                 `json:\"target_directory\"`\n}\n\n// Read the contents of the Cargo.lock file from filename.\nfunc (m *CargoMetadata) Read(errlog fsterr.LogInterface) error {\n\tcmd := exec.Command(\"cargo\", \"metadata\", \"--quiet\", \"--format-version\", \"1\")\n\tstdoutStderr, err := cmd.CombinedOutput()\n\tif err != nil {\n\t\tif len(stdoutStderr) > 0 {\n\t\t\terr = fmt.Errorf(\"%s\", strings.TrimSpace(string(stdoutStderr)))\n\t\t}\n\t\terrlog.Add(err)\n\t\treturn err\n\t}\n\tr := bytes.NewReader(stdoutStderr)\n\tif err := json.NewDecoder(r).Decode(&m); err != nil {\n\t\terrlog.Add(err)\n\t\treturn err\n\t}\n\treturn nil\n}\n\n// CargoMetadataPackage models the package structure returned when executing\n// the command `cargo metadata`.\ntype CargoMetadataPackage struct {\n\tName         string                 `toml:\"name\" json:\"name\"`\n\tVersion      string                 `toml:\"version\" json:\"version\"`\n\tDependencies []CargoMetadataPackage `toml:\"dependencies\" json:\"dependencies\"`\n}\n"
  },
  {
    "path": "pkg/commands/compute/language_toolchain.go",
    "content": "package compute\n\nimport (\n\t\"bytes\"\n\t\"encoding/binary\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"strconv\"\n\t\"strings\"\n\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\tfstexec \"github.com/fastly/cli/pkg/exec\"\n\t\"github.com/fastly/cli/pkg/manifest\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\nconst (\n\t// https://webassembly.github.io/spec/core/binary/modules.html#binary-module\n\twasmBytes = 4\n\n\t// Defining as a constant avoids gosec G304 issue with command execution.\n\tbinWasmPath = \"./bin/main.wasm\"\n)\n\n// DefaultBuildErrorRemediation is the message returned to a user when there is\n// a build error.\nvar DefaultBuildErrorRemediation = func() string {\n\treturn fmt.Sprintf(`%s:\n\n- Re-run the fastly command with the --verbose flag to see more information.\n- Is the required language toolchain (node/npm, rust/cargo etc) installed correctly?\n- Is the required version (if any) of the language toolchain installed/activated?\n- Were the required dependencies (package.json, Cargo.toml etc) installed?\n- Did the build script (see fastly.toml [scripts.build]) produce a ./bin/main.wasm binary file?\n- Was there a configured [scripts.post_build] step that needs to be double-checked?\n\nFor more information on fastly.toml configuration settings, refer to https://www.fastly.com/documentation/reference/compute/fastly-toml`,\n\t\ttext.BoldYellow(\"Here are some steps you can follow to debug the issue\"))\n}()\n\n// Toolchain abstracts a Compute source language toolchain.\ntype Toolchain interface {\n\t// Build compiles the user's source code into a Wasm binary.\n\tBuild() error\n\t// DefaultBuildScript indicates if a default build script was used.\n\tDefaultBuildScript() bool\n\t// Dependencies returns all dependencies used by the project.\n\tDependencies() map[string]string\n}\n\n// BuildToolchain enables a language toolchain to compile their build script.\ntype BuildToolchain struct {\n\t// autoYes is the --auto-yes flag.\n\tautoYes bool\n\t// buildFn constructs a `sh -c` command from the buildScript.\n\tbuildFn func(string) (string, []string)\n\t// buildScript is the [scripts.build] within the fastly.toml manifest.\n\tbuildScript string\n\t// env is environment variables to be set.\n\tenv []string\n\t// errlog is an abstraction for recording errors to disk.\n\terrlog fsterr.LogInterface\n\t// in is the user's terminal stdin stream\n\tin io.Reader\n\t// internalPostBuildCallback is run after the build but before post build.\n\tinternalPostBuildCallback func() error\n\t// manifestFilename is the name of the manifest file.\n\tmanifestFilename string\n\t// metadataFilterEnvVars is a comma-separated list of user defined env vars.\n\tmetadataFilterEnvVars string\n\t// nonInteractive is the --non-interactive flag.\n\tnonInteractive bool\n\t// out is the users terminal stdout stream\n\tout io.Writer\n\t// postBuild is a custom script executed after the build but before the Wasm\n\t// binary is added to the .tar.gz archive.\n\tpostBuild string\n\t// spinner is a terminal progress status indicator.\n\tspinner text.Spinner\n\t// timeout is the build execution threshold.\n\ttimeout int\n\t// verbose indicates if the user set --verbose\n\tverbose bool\n}\n\n// Build compiles the user's source code into a Wasm binary.\nfunc (bt BuildToolchain) Build() error {\n\t// Make sure to delete any pre-existing binary otherwise prior metadata will\n\t// continue to be persisted.\n\tif _, err := os.Stat(binWasmPath); err == nil {\n\t\tos.Remove(binWasmPath)\n\t}\n\n\tcmd, args := bt.buildFn(bt.buildScript)\n\n\tif bt.verbose {\n\t\tbuildScript := fmt.Sprintf(\"%s %s\", cmd, strings.Join(args, \" \"))\n\t\ttext.Description(bt.out, \"Build script to execute\", FilterSecretsFromString(buildScript))\n\n\t\t// IMPORTANT: We filter secrets the best we can before printing env vars.\n\t\t// We use two separate processes to do this.\n\t\t// First is filtering based on known environment variables.\n\t\t// Second is filtering based on a generalised regex pattern.\n\t\tif len(bt.env) > 0 {\n\t\t\tExtendStaticSecretEnvVars(bt.metadataFilterEnvVars)\n\t\t\ts := strings.Join(bt.env, \" \")\n\t\t\ttext.Description(bt.out, \"Build environment variables set\", FilterSecretsFromString(s))\n\t\t}\n\t}\n\n\tvar err error\n\tmsg := \"Running [scripts.build]\"\n\n\t// If we're in verbose mode, the build output is shown.\n\t// So in that case we don't want to have a spinner as it'll interweave output.\n\t// In non-verbose mode we have a spinner running while the build is happening.\n\tif !bt.verbose {\n\t\terr = bt.spinner.Start()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tbt.spinner.Message(msg + \"...\")\n\t}\n\n\terr = bt.execCommand(cmd, args, msg)\n\tif err != nil {\n\t\t// In verbose mode we'll have the failure status AFTER the error output.\n\t\t// But we can't just call StopFailMessage() without first starting the spinner.\n\t\tif bt.verbose {\n\t\t\ttext.Break(bt.out)\n\t\t\tspinErr := bt.spinner.Start()\n\t\t\tif spinErr != nil {\n\t\t\t\treturn fmt.Errorf(text.SpinnerErrWrapper, spinErr, err)\n\t\t\t}\n\t\t\tbt.spinner.Message(msg + \"...\")\n\t\t\tbt.spinner.StopFailMessage(msg)\n\t\t\tspinErr = bt.spinner.StopFail()\n\t\t\tif spinErr != nil {\n\t\t\t\treturn fmt.Errorf(text.SpinnerErrWrapper, spinErr, err)\n\t\t\t}\n\t\t}\n\t\t// WARNING: Don't try to add 'StopFailMessage/StopFail' calls here.\n\t\t// If we're in non-verbose mode, then the spinner is BEFORE the error output.\n\t\t// Also, in non-verbose mode stopping the spinner is handled internally.\n\t\t// See the call to StopFailMessage() inside fstexec.Streaming.Exec().\n\t\treturn bt.handleError(err)\n\t}\n\n\t// In verbose mode we'll have the failure status AFTER the error output.\n\t// But we can't just call StopMessage() without first starting the spinner.\n\tif bt.verbose {\n\t\terr = bt.spinner.Start()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tbt.spinner.Message(msg + \"...\")\n\t\ttext.Break(bt.out)\n\t}\n\n\tbt.spinner.StopMessage(msg)\n\terr = bt.spinner.Stop()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// NOTE: internalPostBuildCallback is only used by Rust currently.\n\t// It's not a step that would be configured by a user in their fastly.toml\n\t// It enables Rust to move the compiled binary to a different location.\n\t// This has to happen BEFORE the postBuild step.\n\tif bt.internalPostBuildCallback != nil {\n\t\terr := bt.internalPostBuildCallback()\n\t\tif err != nil {\n\t\t\treturn bt.handleError(err)\n\t\t}\n\t}\n\n\t// IMPORTANT: The stat check MUST come after the internalPostBuildCallback.\n\t// This is because for Rust it needs to move the binary first.\n\t_, err = os.Stat(binWasmPath)\n\tif err != nil {\n\t\treturn bt.handleError(err)\n\t}\n\t// NOTE: The logic for checking the Wasm binary is 'valid' is not exhaustive.\n\tif err := bt.validateWasm(); err != nil {\n\t\treturn err\n\t}\n\n\tif bt.postBuild != \"\" {\n\t\tif !bt.autoYes && !bt.nonInteractive {\n\t\t\tmanifestFilename := bt.manifestFilename\n\t\t\tif manifestFilename == \"\" {\n\t\t\t\tmanifestFilename = manifest.Filename\n\t\t\t}\n\t\t\tmsg := fmt.Sprintf(CustomPostScriptMessage, \"build\", manifestFilename)\n\t\t\terr := bt.promptForPostBuildContinue(msg, bt.postBuild, bt.out, bt.in)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\n\t\t// If we're in verbose mode, the build output is shown.\n\t\t// So in that case we don't want to have a spinner as it'll interweave output.\n\t\t// In non-verbose mode we have a spinner running while the build is happening.\n\t\tif !bt.verbose {\n\t\t\terr = bt.spinner.Start()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tmsg = \"Running [scripts.post_build]...\"\n\t\t\tbt.spinner.Message(msg)\n\t\t}\n\n\t\tcmd, args := bt.buildFn(bt.postBuild)\n\t\terr := bt.execCommand(cmd, args, msg)\n\t\tif err != nil {\n\t\t\t// In verbose mode we'll have the failure status AFTER the error output.\n\t\t\t// But we can't just call StopFailMessage() without first starting the spinner.\n\t\t\tif bt.verbose {\n\t\t\t\ttext.Break(bt.out)\n\t\t\t\tspinErr := bt.spinner.Start()\n\t\t\t\tif spinErr != nil {\n\t\t\t\t\treturn fmt.Errorf(text.SpinnerErrWrapper, spinErr, err)\n\t\t\t\t}\n\t\t\t\tbt.spinner.Message(msg + \"...\")\n\t\t\t\tbt.spinner.StopFailMessage(msg)\n\t\t\t\tspinErr = bt.spinner.StopFail()\n\t\t\t\tif spinErr != nil {\n\t\t\t\t\treturn fmt.Errorf(text.SpinnerErrWrapper, spinErr, err)\n\t\t\t\t}\n\t\t\t}\n\t\t\t// WARNING: Don't try to add 'StopFailMessage/StopFail' calls here.\n\t\t\t// It is handled internally by fstexec.Streaming.Exec().\n\t\t\treturn bt.handleError(err)\n\t\t}\n\n\t\t// In verbose mode we'll have the failure status AFTER the error output.\n\t\t// But we can't just call StopMessage() without first starting the spinner.\n\t\tif bt.verbose {\n\t\t\terr = bt.spinner.Start()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tbt.spinner.Message(msg + \"...\")\n\t\t\ttext.Break(bt.out)\n\t\t}\n\n\t\tbt.spinner.StopMessage(msg)\n\t\terr = bt.spinner.Stop()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// The encoding of a module starts with a preamble containing a 4-byte magic\n// number (the string '\\0asm') and a version field.\n//\n// Reference:\n// https://webassembly.github.io/spec/core/binary/modules.html#binary-module\nfunc (bt BuildToolchain) validateWasm() error {\n\tf, err := os.Open(binWasmPath)\n\tif err != nil {\n\t\treturn bt.handleError(err)\n\t}\n\tdefer f.Close()\n\n\t// Parse the magic number\n\tmagic := make([]byte, wasmBytes)\n\t_, err = f.Read(magic)\n\tif err != nil {\n\t\treturn bt.handleError(err)\n\t}\n\texpectedMagic := []byte{0x00, 0x61, 0x73, 0x6d}\n\tif !bytes.Equal(magic, expectedMagic) {\n\t\treturn bt.handleError(fmt.Errorf(\"unexpected magic: %#v\", magic))\n\t}\n\tif bt.verbose {\n\t\ttext.Break(bt.out)\n\t\ttext.Description(bt.out, \"Wasm module 'magic'\", fmt.Sprintf(\"%#v\", magic))\n\t}\n\n\t// Parse the version\n\tvar version uint32\n\tif err := binary.Read(f, binary.LittleEndian, &version); err != nil {\n\t\treturn bt.handleError(err)\n\t}\n\tif bt.verbose {\n\t\ttext.Description(bt.out, \"Wasm module 'version'\", strconv.FormatUint(uint64(version), 10))\n\t}\n\treturn nil\n}\n\nfunc (bt BuildToolchain) handleError(err error) error {\n\treturn fsterr.RemediationError{\n\t\tInner:       err,\n\t\tRemediation: DefaultBuildErrorRemediation,\n\t}\n}\n\n// execCommand opens a sub shell to execute the language build script.\n//\n// NOTE: We pass the spinner and associated message to handle error cases.\n// This avoids an issue where the spinner is still running when an error occurs.\n// When the error occurs the command output is displayed.\n// This causes the spinner message to be displayed twice with different status.\n// By passing in the spinner and message we can short-circuit the spinner.\nfunc (bt BuildToolchain) execCommand(cmd string, args []string, spinMessage string) error {\n\treturn fstexec.Command(fstexec.CommandOpts{\n\t\tArgs:           args,\n\t\tCommand:        cmd,\n\t\tEnv:            bt.env,\n\t\tErrLog:         bt.errlog,\n\t\tOutput:         bt.out,\n\t\tSpinner:        bt.spinner,\n\t\tSpinnerMessage: spinMessage,\n\t\tTimeout:        bt.timeout,\n\t\tVerbose:        bt.verbose,\n\t})\n}\n\n// promptForPostBuildContinue ensures the user is happy to continue with the build\n// when there is a post_build in the fastly.toml manifest file.\nfunc (bt BuildToolchain) promptForPostBuildContinue(msg, script string, out io.Writer, in io.Reader) error {\n\ttext.Info(out, \"%s:\\n\", msg)\n\ttext.Indent(out, 4, \"%s\", script)\n\n\tlabel := \"\\nDo you want to run this now? [y/N] \"\n\tanswer, err := text.AskYesNo(out, label, in)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif !answer {\n\t\treturn fsterr.ErrPostBuildStopped\n\t}\n\ttext.Break(out)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/compute/metadata.go",
    "content": "package compute\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/config\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// MetadataCommand controls what metadata is collected for a Wasm binary.\ntype MetadataCommand struct {\n\targparser.Base\n\n\tdisable        bool\n\tdisableBuild   bool\n\tdisableMachine bool\n\tdisablePackage bool\n\tdisableScript  bool\n\tenable         bool\n\tenableBuild    bool\n\tenableMachine  bool\n\tenablePackage  bool\n\tenableScript   bool\n}\n\n// NewMetadataCommand returns a new command registered in the parent.\nfunc NewMetadataCommand(parent argparser.Registerer, g *global.Data) *MetadataCommand {\n\tvar c MetadataCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(\"metadata\", \"Control what metadata is collected\")\n\tc.CmdClause.Flag(\"disable\", \"Disable all metadata\").BoolVar(&c.disable)\n\tc.CmdClause.Flag(\"disable-build\", \"Disable metadata for information regarding the time taken for builds and compilation processes\").BoolVar(&c.disableBuild)\n\tc.CmdClause.Flag(\"disable-machine\", \"Disable metadata for general, non-identifying system specifications (CPU, RAM, operating system)\").BoolVar(&c.disableMachine)\n\tc.CmdClause.Flag(\"disable-package\", \"Disable metadata for packages and libraries utilized in your source code\").BoolVar(&c.disablePackage)\n\tc.CmdClause.Flag(\"disable-script\", \"Disable metadata for script info from the fastly.toml manifest (i.e. [scripts] section).\").BoolVar(&c.disableScript)\n\tc.CmdClause.Flag(\"enable\", \"Enable all metadata\").BoolVar(&c.enable)\n\tc.CmdClause.Flag(\"enable-build\", \"Enable metadata for information regarding the time taken for builds and compilation processes\").BoolVar(&c.enableBuild)\n\tc.CmdClause.Flag(\"enable-machine\", \"Enable metadata for general, non-identifying system specifications (CPU, RAM, operating system)\").BoolVar(&c.enableMachine)\n\tc.CmdClause.Flag(\"enable-package\", \"Enable metadata for packages and libraries utilized in your source code\").BoolVar(&c.enablePackage)\n\tc.CmdClause.Flag(\"enable-script\", \"Enable metadata for script info from the fastly.toml manifest (i.e. [scripts] section).\").BoolVar(&c.enableScript)\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *MetadataCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.disable && c.enable {\n\t\treturn fsterr.ErrInvalidEnableDisableFlagCombo\n\t}\n\n\tvar modified bool\n\n\t// Global enable/disable\n\tif c.enable {\n\t\tc.Globals.Config.WasmMetadata = toggleAll(\"enable\")\n\t\tmodified = true\n\t}\n\tif c.disable {\n\t\tc.Globals.Config.WasmMetadata = toggleAll(\"disable\")\n\t\tmodified = true\n\t}\n\n\t// Specific enablement\n\tif c.enableBuild {\n\t\tc.Globals.Config.WasmMetadata.BuildInfo = \"enable\"\n\t\tmodified = true\n\t}\n\tif c.enableMachine {\n\t\tc.Globals.Config.WasmMetadata.MachineInfo = \"enable\"\n\t\tmodified = true\n\t}\n\tif c.enablePackage {\n\t\tc.Globals.Config.WasmMetadata.PackageInfo = \"enable\"\n\t\tmodified = true\n\t}\n\tif c.enableScript {\n\t\tc.Globals.Config.WasmMetadata.ScriptInfo = \"enable\"\n\t\tmodified = true\n\t}\n\n\t// Specific disablement\n\tif c.disableBuild {\n\t\tc.Globals.Config.WasmMetadata.BuildInfo = \"disable\"\n\t\tmodified = true\n\t}\n\tif c.disableMachine {\n\t\tc.Globals.Config.WasmMetadata.MachineInfo = \"disable\"\n\t\tmodified = true\n\t}\n\tif c.disablePackage {\n\t\tc.Globals.Config.WasmMetadata.PackageInfo = \"disable\"\n\t\tmodified = true\n\t}\n\tif c.disableScript {\n\t\tc.Globals.Config.WasmMetadata.ScriptInfo = \"disable\"\n\t\tmodified = true\n\t}\n\n\tif modified {\n\t\tif c.disable && (c.enableBuild || c.enableMachine || c.enablePackage || c.enableScript) {\n\t\t\ttext.Info(out, \"We will disable all metadata except for the specified `--enable-*` flags\")\n\t\t\ttext.Break(out)\n\t\t}\n\t\tif c.enable && (c.disableBuild || c.disableMachine || c.disablePackage || c.disableScript) {\n\t\t\ttext.Info(out, \"We will enable all metadata except for the specified `--disable-*` flags\")\n\t\t\ttext.Break(out)\n\t\t}\n\t\terr := c.Globals.Config.Write(c.Globals.ConfigPath)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to persist metadata choices to disk: %w\", err)\n\t\t}\n\t\ttext.Success(out, \"configuration updated\")\n\t\ttext.Break(out)\n\t}\n\n\ttext.Output(out, \"Build Information: %s\", c.Globals.Config.WasmMetadata.BuildInfo)\n\ttext.Output(out, \"Machine Information: %s\", c.Globals.Config.WasmMetadata.MachineInfo)\n\ttext.Output(out, \"Package Information: %s\", c.Globals.Config.WasmMetadata.PackageInfo)\n\ttext.Output(out, \"Script Information: %s\", c.Globals.Config.WasmMetadata.ScriptInfo)\n\treturn nil\n}\n\nfunc toggleAll(state string) config.WasmMetadata {\n\tvar t config.WasmMetadata\n\tt.BuildInfo = state\n\tt.MachineInfo = state\n\tt.PackageInfo = state\n\tt.ScriptInfo = state\n\treturn t\n}\n"
  },
  {
    "path": "pkg/commands/compute/metadata_test.go",
    "content": "package compute_test\n\nimport (\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n\n\ttoml \"github.com/pelletier/go-toml\"\n\n\troot \"github.com/fastly/cli/pkg/commands/compute\"\n\t\"github.com/fastly/cli/pkg/config\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/revision\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n\t\"github.com/fastly/cli/pkg/threadsafe\"\n)\n\nfunc TestMetadata(t *testing.T) {\n\t// We read the static/embedded config so we can get the latest config\n\t// version and so we don't accidentally switch to the UseStatic() version.\n\tvar staticConfig config.File\n\terr := toml.Unmarshal(config.Static, &staticConfig)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs: \"--enable\",\n\t\t\tConfigFile: &config.File{\n\t\t\t\tConfigVersion: staticConfig.ConfigVersion,\n\t\t\t\tCLI: config.CLI{\n\t\t\t\t\tVersion: revision.SemVer(revision.AppVersion),\n\t\t\t\t},\n\t\t\t},\n\t\t\tEnv: &testutil.EnvConfig{\n\t\t\t\tOpts: &testutil.EnvOpts{\n\t\t\t\t\tCopy: []testutil.FileIO{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tSrc: filepath.Join(\"testdata\", \"metadata\", \"config.toml\"),\n\t\t\t\t\t\t\tDst: \"config.toml\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tEditScenario: func(scenario *testutil.CLIScenario, rootdir string) {\n\t\t\t\t\tscenario.ConfigPath = filepath.Join(rootdir, \"config.toml\")\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: \"SUCCESS: configuration updated\",\n\t\t\tValidator: func(t *testing.T, _ *testutil.CLIScenario, opts *global.Data, _ *threadsafe.Buffer) {\n\t\t\t\tdata, err := os.ReadFile(opts.ConfigPath)\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Error(err)\n\t\t\t\t}\n\n\t\t\t\tvar testFile config.File\n\t\t\t\tunmarshalErr := toml.Unmarshal(data, &testFile)\n\t\t\t\tif unmarshalErr != nil {\n\t\t\t\t\tt.Error(unmarshalErr)\n\t\t\t\t}\n\n\t\t\t\ttestutil.AssertString(t, \"enable\", testFile.WasmMetadata.BuildInfo)\n\t\t\t\ttestutil.AssertString(t, \"enable\", testFile.WasmMetadata.MachineInfo)\n\t\t\t\ttestutil.AssertString(t, \"enable\", testFile.WasmMetadata.PackageInfo)\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tArgs: \"--disable\",\n\t\t\tEnv: &testutil.EnvConfig{\n\t\t\t\tOpts: &testutil.EnvOpts{\n\t\t\t\t\tCopy: []testutil.FileIO{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tSrc: filepath.Join(\"testdata\", \"metadata\", \"config.toml\"),\n\t\t\t\t\t\t\tDst: \"config.toml\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tEditScenario: func(scenario *testutil.CLIScenario, rootdir string) {\n\t\t\t\t\tscenario.ConfigPath = filepath.Join(rootdir, \"config.toml\")\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: \"SUCCESS: configuration updated\",\n\t\t\tValidator: func(t *testing.T, _ *testutil.CLIScenario, opts *global.Data, _ *threadsafe.Buffer) {\n\t\t\t\tdata, err := os.ReadFile(opts.ConfigPath)\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Error(err)\n\t\t\t\t}\n\n\t\t\t\tvar testFile config.File\n\t\t\t\tunmarshalErr := toml.Unmarshal(data, &testFile)\n\t\t\t\tif unmarshalErr != nil {\n\t\t\t\t\tt.Error(unmarshalErr)\n\t\t\t\t}\n\n\t\t\t\ttestutil.AssertString(t, \"disable\", testFile.WasmMetadata.BuildInfo)\n\t\t\t\ttestutil.AssertString(t, \"disable\", testFile.WasmMetadata.MachineInfo)\n\t\t\t\ttestutil.AssertString(t, \"disable\", testFile.WasmMetadata.PackageInfo)\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tArgs: \"--enable --disable-build\",\n\t\t\tEnv: &testutil.EnvConfig{\n\t\t\t\tOpts: &testutil.EnvOpts{\n\t\t\t\t\tCopy: []testutil.FileIO{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tSrc: filepath.Join(\"testdata\", \"metadata\", \"config.toml\"),\n\t\t\t\t\t\t\tDst: \"config.toml\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tEditScenario: func(scenario *testutil.CLIScenario, rootdir string) {\n\t\t\t\t\tscenario.ConfigPath = filepath.Join(rootdir, \"config.toml\")\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutputs: []string{\n\t\t\t\t\"INFO: We will enable all metadata except for the specified `--disable-*` flags\",\n\t\t\t\t\"SUCCESS: configuration updated\",\n\t\t\t},\n\t\t\tValidator: func(t *testing.T, _ *testutil.CLIScenario, opts *global.Data, _ *threadsafe.Buffer) {\n\t\t\t\tdata, err := os.ReadFile(opts.ConfigPath)\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Error(err)\n\t\t\t\t}\n\n\t\t\t\tvar testFile config.File\n\t\t\t\tunmarshalErr := toml.Unmarshal(data, &testFile)\n\t\t\t\tif unmarshalErr != nil {\n\t\t\t\t\tt.Error(unmarshalErr)\n\t\t\t\t}\n\n\t\t\t\ttestutil.AssertString(t, \"disable\", testFile.WasmMetadata.BuildInfo)\n\t\t\t\ttestutil.AssertString(t, \"enable\", testFile.WasmMetadata.MachineInfo)\n\t\t\t\ttestutil.AssertString(t, \"enable\", testFile.WasmMetadata.PackageInfo)\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tArgs: \"--disable --enable-machine\",\n\t\t\tEnv: &testutil.EnvConfig{\n\t\t\t\tOpts: &testutil.EnvOpts{\n\t\t\t\t\tCopy: []testutil.FileIO{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tSrc: filepath.Join(\"testdata\", \"metadata\", \"config.toml\"),\n\t\t\t\t\t\t\tDst: \"config.toml\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tEditScenario: func(scenario *testutil.CLIScenario, rootdir string) {\n\t\t\t\t\tscenario.ConfigPath = filepath.Join(rootdir, \"config.toml\")\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutputs: []string{\n\t\t\t\t\"INFO: We will disable all metadata except for the specified `--enable-*` flags\",\n\t\t\t\t\"SUCCESS: configuration updated\",\n\t\t\t},\n\t\t\tValidator: func(t *testing.T, _ *testutil.CLIScenario, opts *global.Data, _ *threadsafe.Buffer) {\n\t\t\t\tdata, err := os.ReadFile(opts.ConfigPath)\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Error(err)\n\t\t\t\t}\n\n\t\t\t\tvar testFile config.File\n\t\t\t\tunmarshalErr := toml.Unmarshal(data, &testFile)\n\t\t\t\tif unmarshalErr != nil {\n\t\t\t\t\tt.Error(unmarshalErr)\n\t\t\t\t}\n\n\t\t\t\ttestutil.AssertString(t, \"disable\", testFile.WasmMetadata.BuildInfo)\n\t\t\t\ttestutil.AssertString(t, \"enable\", testFile.WasmMetadata.MachineInfo)\n\t\t\t\ttestutil.AssertString(t, \"disable\", testFile.WasmMetadata.PackageInfo)\n\t\t\t},\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, \"metadata\"}, scenarios)\n}\n"
  },
  {
    "path": "pkg/commands/compute/pack.go",
    "content": "package compute\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"path/filepath\"\n\n\t\"github.com/kennygrant/sanitize\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/filesystem\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/manifest\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// PackCommand takes a .wasm and builds the required tar/gzip package ready to be uploaded.\ntype PackCommand struct {\n\targparser.Base\n\twasmBinary string\n}\n\n// NewPackCommand returns a usable command registered under the parent.\nfunc NewPackCommand(parent argparser.Registerer, g *global.Data) *PackCommand {\n\tvar c PackCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(\"pack\", \"Package a pre-compiled Wasm binary for a Fastly Compute service\")\n\tc.CmdClause.Flag(\"wasm-binary\", \"Path to a pre-compiled Wasm binary\").Short('w').Required().StringVar(&c.wasmBinary)\n\n\treturn &c\n}\n\n// Exec implements the command interface.\n//\n// NOTE: The bin/manifest is placed in a 'package' folder within the tar.gz.\nfunc (c *PackCommand) Exec(_ io.Reader, out io.Writer) (err error) {\n\tspinner, err := text.NewSpinner(out)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfilename := sanitize.BaseName(c.Globals.Manifest.File.Name)\n\tif filename == \"\" {\n\t\tfilename = \"package\"\n\t}\n\n\tdefer func(errLog fsterr.LogInterface) {\n\t\t_ = os.RemoveAll(fmt.Sprintf(\"pkg/%s\", filename))\n\t\tif err != nil {\n\t\t\terrLog.Add(err)\n\t\t}\n\t}(c.Globals.ErrLog)\n\n\tif err = c.Globals.Manifest.File.ReadError(); err != nil {\n\t\treturn err\n\t}\n\n\tbin := fmt.Sprintf(\"pkg/%s/bin/main.wasm\", filename)\n\tbindir := filepath.Dir(bin)\n\n\terr = filesystem.MakeDirectoryIfNotExists(bindir)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Wasm directory (relative)\": bindir,\n\t\t})\n\t\treturn err\n\t}\n\n\tsrc, err := filepath.Abs(c.wasmBinary)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Path (absolute)\": src,\n\t\t})\n\t\treturn err\n\t}\n\n\tdst, err := filepath.Abs(bin)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Wasm destination (relative)\": bin,\n\t\t})\n\t\treturn err\n\t}\n\n\terr = spinner.Process(\"Copying wasm binary\", func(_ *text.SpinnerWrapper) error {\n\t\tif err := filesystem.CopyFile(src, dst); err != nil {\n\t\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\t\"Path (absolute)\":             src,\n\t\t\t\t\"Wasm destination (absolute)\": dst,\n\t\t\t})\n\t\t\treturn fmt.Errorf(\"error copying wasm binary to '%s': %w\", dst, err)\n\t\t}\n\n\t\tif !filesystem.FileExists(bin) {\n\t\t\treturn fsterr.RemediationError{\n\t\t\t\tInner:       fmt.Errorf(\"no wasm binary found\"),\n\t\t\t\tRemediation: \"Run `fastly compute pack --path </path/to/wasm/binary>` to copy your wasm binary to the required location\",\n\t\t\t}\n\t\t}\n\n\t\tsrc = manifest.Filename\n\t\tdst = fmt.Sprintf(\"pkg/%s/%s\", filename, manifest.Filename)\n\t\tif err := filesystem.CopyFile(src, dst); err != nil {\n\t\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\t\"Manifest (destination)\": dst,\n\t\t\t\t\"Manifest (source)\":      src,\n\t\t\t})\n\t\t\treturn fmt.Errorf(\"error copying manifest to '%s': %w\", dst, err)\n\t\t}\n\n\t\t{\n\t\t\tdir := fmt.Sprintf(\"pkg/%s\", filename)\n\t\t\tdst := fmt.Sprintf(\"%s.tar.gz\", dir)\n\t\t\tif err = createTarGz(dir, dst); err != nil {\n\t\t\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\t\t\"Path (absolute)\":             dir,\n\t\t\t\t\t\"Wasm destination (absolute)\": dst,\n\t\t\t\t})\n\t\t\t\treturn fmt.Errorf(\"error copying wasm binary to '%s': %w\", dst, err)\n\t\t\t}\n\n\t\t\tif !filesystem.FileExists(bin) {\n\t\t\t\treturn fsterr.RemediationError{\n\t\t\t\t\tInner:       fmt.Errorf(\"no wasm binary found\"),\n\t\t\t\t\tRemediation: \"Run `fastly compute pack --path </path/to/wasm/binary>` to copy your wasm binary to the required location\",\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn nil\n\t\t}\n\t})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\terr = spinner.Process(\"Copying manifest\", func(_ *text.SpinnerWrapper) error {\n\t\tsrc = manifest.Filename\n\t\tdst = fmt.Sprintf(\"pkg/package/%s\", manifest.Filename)\n\t\tif err := filesystem.CopyFile(src, dst); err != nil {\n\t\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\t\"Manifest (destination)\": dst,\n\t\t\t\t\"Manifest (source)\":      src,\n\t\t\t})\n\t\t\treturn fmt.Errorf(\"error copying manifest to '%s': %w\", dst, err)\n\t\t}\n\t\treturn nil\n\t})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn spinner.Process(fmt.Sprintf(\"Creating %s.tar.gz file\", filename), func(_ *text.SpinnerWrapper) error {\n\t\tdir := \"pkg/package\"\n\t\tdst := fmt.Sprintf(\"%s.tar.gz\", dir)\n\t\tif err = createTarGz(dir, dst); err != nil {\n\t\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\t\"Tar source\":      dir,\n\t\t\t\t\"Tar destination\": dst,\n\t\t\t})\n\t\t\treturn err\n\t\t}\n\t\treturn nil\n\t})\n}\n"
  },
  {
    "path": "pkg/commands/compute/pack_test.go",
    "content": "package compute_test\n\nimport (\n\t\"bytes\"\n\t\"io\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n\n\t\"github.com/fastly/cli/pkg/app\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/manifest\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n)\n\nfunc TestPack(t *testing.T) {\n\targs := testutil.SplitArgs\n\tfor _, testcase := range []struct {\n\t\tname          string\n\t\targs          []string\n\t\tmanifest      string\n\t\twantError     string\n\t\twantOutput    []string\n\t\texpectedFiles [][]string\n\t}{\n\t\t{\n\t\t\tname: \"success\",\n\t\t\targs: args(\"compute pack --wasm-binary ./main.wasm\"),\n\t\t\tmanifest: `\n\t\t\tmanifest_version = 2\n\t\t\tname = \"mypackagename\"`,\n\t\t\twantOutput: []string{\n\t\t\t\t\"Copying wasm binary\",\n\t\t\t\t\"Copying manifest\",\n\t\t\t\t\"Creating mypackagename.tar.gz file\",\n\t\t\t},\n\t\t\texpectedFiles: [][]string{\n\t\t\t\t{\"pkg\", \"mypackagename.tar.gz\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:      \"no wasm binary path flag\",\n\t\t\targs:      args(\"compute pack\"),\n\t\t\tmanifest:  `name = \"precompiled\"`,\n\t\t\twantError: \"error parsing arguments: required flag --wasm-binary not provided\",\n\t\t},\n\t\t{\n\t\t\tname: \"no wasm binary path flag value\",\n\t\t\targs: args(\"compute pack --wasm-binary \"),\n\t\t\tmanifest: `\n\t\t\tmanifest_version = 2\n\t\t\tname = \"precompiled\"`,\n\t\t\twantError: \"error copying wasm binary\",\n\t\t},\n\t} {\n\t\tt.Run(testcase.name, func(t *testing.T) {\n\t\t\t// We're going to chdir to a test environment,\n\t\t\t// so save the PWD to return to, afterwards.\n\t\t\tpwd, err := os.Getwd()\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\t// Create test environment\n\t\t\trootdir := testutil.NewEnv(testutil.EnvOpts{\n\t\t\t\tT: t,\n\t\t\t\tCopy: []testutil.FileIO{\n\t\t\t\t\t{Src: filepath.Join(\"testdata\", \"pack\", \"main.wasm\"), Dst: \"main.wasm\"},\n\t\t\t\t},\n\t\t\t\tWrite: []testutil.FileIO{\n\t\t\t\t\t{Src: testcase.manifest, Dst: manifest.Filename},\n\t\t\t\t},\n\t\t\t})\n\t\t\tdefer os.RemoveAll(rootdir)\n\n\t\t\t// Before running the test, chdir into the build environment.\n\t\t\t// When we're done, chdir back to our original location.\n\t\t\tif err := os.Chdir(rootdir); err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tdefer func() {\n\t\t\t\t_ = os.Chdir(pwd)\n\t\t\t}()\n\n\t\t\tvar stdout bytes.Buffer\n\t\t\tapp.Init = func(_ []string, _ io.Reader) (*global.Data, error) {\n\t\t\t\treturn testutil.MockGlobalData(testcase.args, &stdout), nil\n\t\t\t}\n\t\t\terr = app.Run(testcase.args, nil)\n\n\t\t\tt.Log(stdout.String())\n\n\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\tfor _, s := range testcase.wantOutput {\n\t\t\t\ttestutil.AssertStringContains(t, stdout.String(), s)\n\t\t\t}\n\n\t\t\tfor _, files := range testcase.expectedFiles {\n\t\t\t\tfpath := filepath.Join(rootdir, filepath.Join(files...))\n\t\t\t\t_, err = os.Stat(fpath)\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"the specified file is not in the expected location: %v\", err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/commands/compute/publish.go",
    "content": "package compute\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// PublishCommand produces and deploys an artifact from files on the local disk.\ntype PublishCommand struct {\n\targparser.Base\n\tbuild  *BuildCommand\n\tdeploy *DeployCommand\n\n\t// Build fields\n\tdir                   argparser.OptionalString\n\tincludeSrc            argparser.OptionalBool\n\tlang                  argparser.OptionalString\n\tmetadataDisable       argparser.OptionalBool\n\tmetadataFilterEnvVars argparser.OptionalString\n\tmetadataShow          argparser.OptionalBool\n\tpackageName           argparser.OptionalString\n\ttimeout               argparser.OptionalInt\n\n\t// Deploy fields\n\tcomment            argparser.OptionalString\n\tdomain             argparser.OptionalString\n\tenv                argparser.OptionalString\n\tnoDefaultDomain    argparser.OptionalBool\n\tpkg                argparser.OptionalString\n\tserviceName        argparser.OptionalServiceNameID\n\tserviceVersion     argparser.OptionalServiceVersion\n\tstatusCheckCode    int\n\tstatusCheckOff     bool\n\tstatusCheckPath    string\n\tstatusCheckTimeout int\n\n\t// Publish private fields\n\tprojectDir string\n}\n\n// NewPublishCommand returns a usable command registered under the parent.\nfunc NewPublishCommand(parent argparser.Registerer, g *global.Data, build *BuildCommand, deploy *DeployCommand) *PublishCommand {\n\tvar c PublishCommand\n\tc.Globals = g\n\tc.build = build\n\tc.deploy = deploy\n\tc.CmdClause = parent.Command(\"publish\", \"Build and deploy a Compute package to a Fastly service\")\n\n\tc.CmdClause.Flag(\"comment\", \"Human-readable comment\").Action(c.comment.Set).StringVar(&c.comment.Value)\n\tc.CmdClause.Flag(\"dir\", \"Project directory to build (default: current directory)\").Short('C').Action(c.dir.Set).StringVar(&c.dir.Value)\n\tc.CmdClause.Flag(\"domain\", \"The name of the domain associated to the package\").Action(c.domain.Set).StringVar(&c.domain.Value)\n\tc.CmdClause.Flag(\"env\", \"The manifest environment config to use (e.g. 'stage' will attempt to read 'fastly.stage.toml')\").Action(c.env.Set).StringVar(&c.env.Value)\n\tc.CmdClause.Flag(\"include-source\", \"Include source code in built package\").Action(c.includeSrc.Set).BoolVar(&c.includeSrc.Value)\n\tc.CmdClause.Flag(\"no-default-domain\", \"Skip default domain creation\").Action(c.noDefaultDomain.Set).BoolVar(&c.noDefaultDomain.Value)\n\tc.CmdClause.Flag(\"language\", \"Language type\").Action(c.lang.Set).StringVar(&c.lang.Value)\n\tc.CmdClause.Flag(\"metadata-disable\", \"Disable Wasm binary metadata annotations\").Action(c.metadataDisable.Set).BoolVar(&c.metadataDisable.Value)\n\tc.CmdClause.Flag(\"metadata-filter-envvars\", \"Redact specified environment variables from [scripts.env_vars] using comma-separated list\").Action(c.metadataFilterEnvVars.Set).StringVar(&c.metadataFilterEnvVars.Value)\n\tc.CmdClause.Flag(\"metadata-show\", \"Inspect the Wasm binary metadata\").Action(c.metadataShow.Set).BoolVar(&c.metadataShow.Value)\n\tc.CmdClause.Flag(\"package\", \"Path to a package tar.gz\").Short('p').Action(c.pkg.Set).StringVar(&c.pkg.Value)\n\tc.CmdClause.Flag(\"package-name\", \"Package name\").Action(c.packageName.Set).StringVar(&c.packageName.Value)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &c.Globals.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\tc.CmdClause.Flag(\"status-check-code\", \"Set the expected status response for the service availability check to the root path\").IntVar(&c.statusCheckCode)\n\tc.CmdClause.Flag(\"status-check-off\", \"Disable the service availability check\").BoolVar(&c.statusCheckOff)\n\tc.CmdClause.Flag(\"status-check-path\", \"Specify the URL path for the service availability check\").Default(\"/\").StringVar(&c.statusCheckPath)\n\tc.CmdClause.Flag(\"status-check-timeout\", \"Set a timeout (in seconds) for the service availability check\").Default(\"120\").IntVar(&c.statusCheckTimeout)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tAction:      c.serviceVersion.Set,\n\t})\n\tc.CmdClause.Flag(\"timeout\", \"Timeout, in seconds, for the build compilation step\").Action(c.timeout.Set).IntVar(&c.timeout.Value)\n\n\treturn &c\n}\n\n// Exec implements the command interface.\n//\n// NOTE: unlike other non-aggregate commands that initialize a new\n// text.Progress type for displaying progress information to the user, we don't\n// use that in this command because the nested commands overlap the output in\n// non-deterministic ways. It's best to leave those nested commands to handle\n// the progress indicator.\nfunc (c *PublishCommand) Exec(in io.Reader, out io.Writer) (err error) {\n\twd, err := os.Getwd()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to get current working directory: %w\", err)\n\t}\n\tdefer func() {\n\t\t_ = os.Chdir(wd)\n\t}()\n\n\tc.projectDir, err = ChangeProjectDirectory(c.dir.Value)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif c.projectDir != \"\" {\n\t\tif c.Globals.Verbose() {\n\t\t\ttext.Info(out, ProjectDirMsg, c.projectDir)\n\t\t}\n\t}\n\n\terr = c.Build(in, out)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\ttext.Break(out)\n\n\terr = c.Deploy(in, out)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\treturn nil\n}\n\n// Build constructs and executes the build logic.\nfunc (c *PublishCommand) Build(in io.Reader, out io.Writer) error {\n\t// Reset the fields on the BuildCommand based on PublishCommand values.\n\tif c.dir.WasSet {\n\t\tc.build.Flags.Dir = c.dir.Value\n\t}\n\tif c.env.WasSet {\n\t\tc.build.Flags.Env = c.env.Value\n\t}\n\tif c.includeSrc.WasSet {\n\t\tc.build.Flags.IncludeSrc = c.includeSrc.Value\n\t}\n\tif c.lang.WasSet {\n\t\tc.build.Flags.Lang = c.lang.Value\n\t}\n\tif c.packageName.WasSet {\n\t\tc.build.Flags.PackageName = c.packageName.Value\n\t}\n\tif c.timeout.WasSet {\n\t\tc.build.Flags.Timeout = c.timeout.Value\n\t}\n\tif c.metadataDisable.WasSet {\n\t\tc.build.MetadataDisable = c.metadataDisable.Value\n\t}\n\tif c.metadataFilterEnvVars.WasSet {\n\t\tc.build.MetadataFilterEnvVars = c.metadataFilterEnvVars.Value\n\t}\n\tif c.metadataShow.WasSet {\n\t\tc.build.MetadataShow = c.metadataShow.Value\n\t}\n\tif c.projectDir != \"\" {\n\t\tc.build.SkipChangeDir = true // we've already changed directory\n\t}\n\treturn c.build.Exec(in, out)\n}\n\n// Deploy constructs and executes the deploy logic.\nfunc (c *PublishCommand) Deploy(in io.Reader, out io.Writer) error {\n\t// Reset the fields on the DeployCommand based on PublishCommand values.\n\tif c.dir.WasSet {\n\t\tc.deploy.Dir = c.dir.Value\n\t}\n\tif c.pkg.WasSet {\n\t\tc.deploy.PackagePath = c.pkg.Value\n\t}\n\tif c.serviceName.WasSet {\n\t\tc.deploy.ServiceName = c.serviceName // deploy's field is an argparser.OptionalServiceNameID\n\t}\n\tif c.serviceVersion.WasSet {\n\t\tc.deploy.ServiceVersion = c.serviceVersion // deploy's field is an argparser.OptionalServiceVersion\n\t}\n\tif c.domain.WasSet {\n\t\tc.deploy.Domain = c.domain.Value\n\t}\n\tif c.env.WasSet {\n\t\tc.deploy.Env = c.env.Value\n\t}\n\tif c.noDefaultDomain.WasSet {\n\t\tc.deploy.NoDefaultDomain = c.noDefaultDomain\n\t}\n\tif c.comment.WasSet {\n\t\tc.deploy.Comment = c.comment\n\t}\n\tif c.statusCheckCode > 0 {\n\t\tc.deploy.StatusCheckCode = c.statusCheckCode\n\t}\n\tif c.statusCheckOff {\n\t\tc.deploy.StatusCheckOff = c.statusCheckOff\n\t}\n\tif c.statusCheckTimeout > 0 {\n\t\tc.deploy.StatusCheckTimeout = c.statusCheckTimeout\n\t}\n\tc.deploy.StatusCheckPath = c.statusCheckPath\n\tif c.projectDir != \"\" {\n\t\tc.build.SkipChangeDir = true // we've already changed directory\n\t}\n\treturn c.deploy.Exec(in, out)\n}\n"
  },
  {
    "path": "pkg/commands/compute/pushpin.conf.template",
    "content": "[global]\ninclude={libdir}/internal.conf\n\n# directory to save runtime files\nrundir=%[1]s\n\n# prefix for zmq ipc specs\nipc_prefix=\n\n# port offset for zmq tcp specs and http control server\nport_offset=0\n\n# TTL (seconds) for connection stats\nstats_connection_ttl=120\n\n# whether to send individual connection stats\nstats_connection_send=true\n\n\n[runner]\n# services to start\nservices=connmgr,proxy,handler\n\n# plain HTTP port to listen on for client connections\nhttp_port=%[4]d\n\n# list of HTTPS ports to listen on for client connections (you must have certs set)\n#https_ports=443\n\n# list of unix socket paths to listen on for client connections\n#local_ports={rundir}/{ipc_prefix}server\n\n# directory to save log files\nlogdir=%[2]s\n\n# logging level. 2 = info, >2 = verbose\nlog_level=2\n\n# client full request header must fit in this buffer\nclient_buffer_size=8192\n\n# maximum number of client connections\nclient_maxconn=50000\n\n# whether connections can use compression\nallow_compression=false\n\n# paths\n# mongrel2_bin=mongrel2\n# m2sh_bin=m2sh\n# zurl_bin=zurl\n\n\n[proxy]\n# routes config file (path relative to location of this file)\nroutesfile=%[3]s\n\n# enable debug mode to get informative error responses\ndebug=false\n\n# whether to use automatic CORS and JSON-P wrapping\nauto_cross_origin=false\n\n# whether to accept x-forwarded-proto\naccept_x_forwarded_protocol=false\n\n# whether to assert x-forwarded-proto\nset_x_forwarded_protocol=proto-only\n\n# how to treat x-forwarded-for. example: \"truncate:0,append\"\nx_forwarded_for=\n\n# how to treat x-forwarded-for if grip-signed\nx_forwarded_for_trusted=\n\n# the following headers must be marked in order to qualify as orig\norig_headers_need_mark=\n\n# whether to accept Pushpin-Route header\naccept_pushpin_route=true\n\n# value to append to the CDN-Loop header\ncdn_loop=\n\n# include client IP address in logs\nlog_from=false\n\n# include client user agent in logs\nlog_user_agent=false\n\n# for signing proxied requests\nsig_iss=viceroy\n\n# for signing proxied requests. use \"base64:\" prefix for binary key\nsig_key=viceroy_dev_signing_key_dont_use_in_production\n\n# use this to allow grip to be forwarded upstream (e.g. to fanout.io)\nupstream_key=\n\n# for the sockjs iframe transport\nsockjs_url=http://cdn.jsdelivr.net/sockjs/0.3.4/sockjs.min.js\n\n# updates check has three modes:\n#   report: check for new pushpin version and report anonymous usage info to\n#           the pushpin developers\n#   check:  check for new pushpin version only, don't report anything\n#   off:    don't do any reporting or checking\n# pushpin will output a log message when a new version is available. report\n# mode helps the pushpin project build credibility, so please enable it if you\n# enjoy this software :)\nupdates_check=report\n\n# use this field to identify your organization in updates requests. if left\n# blank, updates requests will be anonymous\norganization_name=\n\n\n[handler]\n# ipc permissions (octal)\n#ipc_file_mode=777\n\n# bind PULL for receiving publish commands\npush_in_spec=tcp://127.0.0.1:%[6]d\n\n# list of bind SUB for receiving published messages\npush_in_sub_specs=tcp://127.0.0.1:%[7]d\n\n# whether the above SUB socket should connect instead of bind\npush_in_sub_connect=false\n\n# addr/port to listen on for receiving publish commands via HTTP\npush_in_http_addr=0.0.0.0\npush_in_http_port=%[5]d\n\n# maximum headers and body size in bytes when receiving publish commands via HTTP\npush_in_http_max_headers_size=8192\npush_in_http_max_body_size=65536\n\n# bind PUB for sending stats (metrics, subscription info, etc)\nstats_spec=ipc://{rundir}/{ipc_prefix}stats\n\n# bind REP for responding to commands\ncommand_spec=tcp://127.0.0.1:%[8]d\n\n# max messages per second\nmessage_rate=2500\n\n# max rate-limited messages\nmessage_hwm=25000\n\n# set to report blocks counts in stats (content size / block size)\n#message_block_size=\n\n# max time (milliseconds) for out-of-order messages to wait\nmessage_wait=5000\n\n# time (seconds) to cache message ids\nid_cache_ttl=60\n\n# retry/recover sessions soon after the first subscription to a channel\nupdate_on_first_subscription=true\n\n# max subscriptions per connection\nconnection_subscription_max=20\n\n# time (seconds) to linger response mode subscriptions\nsubscription_linger=60\n\n# TTL (seconds) for subscription stats\nstats_subscription_ttl=60\n\n# interval (seconds) to send report stats\nstats_report_interval=10\n\n# stats output format\nstats_format=tnetstring\n"
  },
  {
    "path": "pkg/commands/compute/root.go",
    "content": "package compute\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"compute\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manage Compute packages\")\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/compute/secrets.go",
    "content": "package compute\n\nimport (\n\t\"bytes\"\n\t\"regexp\"\n\t\"strings\"\n)\n\n// StaticSecretEnvVars is a static list of env vars containing secrets.\n//\n// NOTE: Env Vars pulled from https://github.com/Puliczek/awesome-list-of-secrets-in-environment-variables\n//\n// The reason for not listing more environment variables is because we have a\n// generalised pattern `SecretGeneralisedEnvVarPattern` that catches the\n// majority of formats used.\nvar StaticSecretEnvVars = []string{\n\t\"AZURE_CLIENT_ID\",\n\t\"CI_JOB_JWT\",\n\t\"CI_JOB_JWT_V2\",\n\t\"FACEBOOK_APP_ID\",\n\t\"MSI_ENDPOINT\",\n\t\"OKTA_AUTHN_GROUPID\",\n\t\"OKTA_OAUTH2_CLIENTID\",\n}\n\n// SecretGeneralisedEnvVarPattern attempts to capture a secret assigned in an environment\n// variable where the key follows a common pattern.\n//\n// Example:\n// https://regex101.com/r/mf9Ymb/1\nvar SecretGeneralisedEnvVarPattern = regexp.MustCompile(`(?i)\\b[^\\s]+_(?:API|CLIENTSECRET|CREDENTIALS|KEY|PASSWORD|SECRET|TOKEN)(?:[^=]+)?=(?:\\s+)?\"?([^\\s\"]+)`) // #nosec G101 (CWE-798)\n\n// AWSIDPattern is the pattern for an AWS ID.\nvar AWSIDPattern = regexp.MustCompile(`\\b((?:AKIA|ABIA|ACCA|ASIA)[0-9A-Z]{16})\\b`)\n\n// AWSSecretPattern is the pattern for an AWS Secret.\nvar AWSSecretPattern = regexp.MustCompile(`[^A-Za-z0-9+\\/]{0,1}([A-Za-z0-9+\\/]{40})[^A-Za-z0-9+\\/]{0,1}`)\n\n// GitHubOAuthTokenPattern is the pattern for a GitHub OAuth token.\nvar GitHubOAuthTokenPattern = regexp.MustCompile(`\\b((?:ghp|gho|ghu|ghs|ghr|github_pat)_[a-zA-Z0-9_]{36,255})\\b`)\n\n// GitHubOldOAuthTokenPattern is the pattern for an older GitHub OAuth token format.\nvar GitHubOldOAuthTokenPattern = regexp.MustCompile(`(?i)(?:github|gh|pat|token)[^\\.].{0,40}[ =:'\"]+([a-f0-9]{40})\\b`)\n\n// GitHubOAuth2ClientIDPattern is the pattern for a GitHub OAuth2 ClientID.\nvar GitHubOAuth2ClientIDPattern = regexp.MustCompile(`(?i)(?:github)(?:.|[\\n\\r]){0,40}\\b([a-f0-9]{20})\\b`)\n\n// GitHubOAuth2ClientSecretPattern is the pattern for a GitHub OAuth2 ClientID.\nvar GitHubOAuth2ClientSecretPattern = regexp.MustCompile(`(?i)(?:github)(?:.|[\\n\\r]){0,40}\\b([a-f0-9]{40})\\b`)\n\n// GitHubAppIDPattern is the pattern for a GitHub App ID.\nvar GitHubAppIDPattern = regexp.MustCompile(`(?i)(?:github)(?:.|[\\n\\r]){0,40}\\b([0-9]{6})\\b`)\n\n// GitHubAppKeyPattern is the pattern for a GitHub App Key.\nvar GitHubAppKeyPattern = regexp.MustCompile(`(?i)(?:github)(?:.|[\\n\\r]){0,40}(-----BEGIN RSA PRIVATE KEY-----\\s[A-Za-z0-9+\\/\\s]*\\s-----END RSA PRIVATE KEY-----)`)\n\n// SecretPatterns is a collection of secret identifying patterns.\n//\n// NOTE: Patterns pulled from https://github.com/trufflesecurity/trufflehog\nvar SecretPatterns = []*regexp.Regexp{\n\tAWSIDPattern,\n\tAWSSecretPattern,\n\tGitHubOAuthTokenPattern,\n\tGitHubOldOAuthTokenPattern,\n\tGitHubOAuth2ClientIDPattern,\n\tGitHubOAuth2ClientSecretPattern,\n\tGitHubAppIDPattern,\n\tGitHubAppKeyPattern,\n}\n\n// ExtendStaticSecretEnvVars mutates `StaticSecretEnvVars` to include user\n// specified environment variables. The `filter` argument is comma-separated.\nfunc ExtendStaticSecretEnvVars(filter string) {\n\tcustomFilters := strings.Split(filter, \",\")\n\tfor _, v := range customFilters {\n\t\tif v == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tvar found bool\n\t\tfor _, f := range StaticSecretEnvVars {\n\t\t\tif f == v {\n\t\t\t\tfound = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif !found {\n\t\t\tStaticSecretEnvVars = append(StaticSecretEnvVars, v)\n\t\t}\n\t}\n}\n\n// FilterSecretsFromSlice returns the input slice modified such that any value\n// assigned to an environment variable (identified as containing a secret) is\n// redacted. Additionally, any 'value' identified as being a secret will also be\n// redacted.\n//\n// NOTE: `data` is expected to contain \"KEY=VALUE\" formatted strings.\nfunc FilterSecretsFromSlice(data []string) []string {\n\tcopyOfData := make([]string, len(data))\n\tcopy(copyOfData, data)\n\n\tfor i, keypair := range copyOfData {\n\t\tk, v, found := strings.Cut(keypair, \"=\")\n\t\tif !found {\n\t\t\treturn copyOfData\n\t\t}\n\t\tfor _, f := range StaticSecretEnvVars {\n\t\t\tif k == f {\n\t\t\t\tcopyOfData[i] = k + \"=REDACTED\"\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif strings.Contains(copyOfData[i], \"REDACTED\") {\n\t\t\tcontinue\n\t\t}\n\t\tfor _, matches := range SecretGeneralisedEnvVarPattern.FindAllStringSubmatch(keypair, -1) {\n\t\t\tif len(matches) == 2 {\n\t\t\t\to := matches[0]\n\t\t\t\tn := strings.ReplaceAll(matches[0], matches[1], \"REDACTED\")\n\t\t\t\tcopyOfData[i] = strings.ReplaceAll(keypair, o, n)\n\t\t\t}\n\t\t}\n\t\tif strings.Contains(copyOfData[i], \"REDACTED\") {\n\t\t\tcontinue\n\t\t}\n\t\tfor _, pattern := range SecretPatterns {\n\t\t\tn := pattern.ReplaceAllString(v, \"REDACTED\")\n\t\t\tcopyOfData[i] = k + \"=\" + n\n\t\t\tif n == \"REDACTED\" {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\treturn copyOfData\n}\n\n// FilterSecretsFromString returns the input string modified such that any value\n// assigned to an environment variable (identified as containing a secret) is\n// redacted. Additionally, any 'value' identified as being a secret will also be\n// redacted.\n//\n// Example:\n// https://go.dev/play/p/jhCcC4SlsHA\n//\n// NOTE: The input data should be simple (i.e. not a complex json object).\n// Otherwise the `SecretGeneralisedEnvVarPattern` will unlikely match all cases.\nfunc FilterSecretsFromString(data string) string {\n\tstaticSecretEnvVarsPattern := regexp.MustCompile(`(?i)\\b(?:` + strings.Join(StaticSecretEnvVars, \"|\") + `)(?:\\s+)?=(?:\\s+)?\"?([^\\s\"]+)`)\n\tfor _, matches := range staticSecretEnvVarsPattern.FindAllStringSubmatch(data, -1) {\n\t\tif len(matches) == 2 {\n\t\t\to := matches[0]\n\t\t\tn := strings.ReplaceAll(matches[0], matches[1], \"REDACTED\")\n\t\t\tdata = strings.ReplaceAll(data, o, n)\n\t\t}\n\t}\n\tfor _, matches := range SecretGeneralisedEnvVarPattern.FindAllStringSubmatch(data, -1) {\n\t\tif len(matches) == 2 {\n\t\t\to := matches[0]\n\t\t\tn := strings.ReplaceAll(matches[0], matches[1], \"REDACTED\")\n\t\t\tdata = strings.ReplaceAll(data, o, n)\n\t\t}\n\t}\n\tfor _, pattern := range SecretPatterns {\n\t\tdata = pattern.ReplaceAllString(data, \"REDACTED\")\n\t}\n\treturn data\n}\n\n// FilterSecretsFromBytes returns the input string modified such that any value\n// assigned to an environment variable (identified as containing a secret) is\n// redacted. Additionally, any 'value' identified as being a secret will also be\n// redacted.\n//\n// Example:\n// https://go.dev/play/p/jhCcC4SlsHA\n//\n// NOTE: The input data should be simple (i.e. not a complex json object).\n// Otherwise the `SecretGeneralisedEnvVarPattern` will unlikely match all cases.\nfunc FilterSecretsFromBytes(data []byte) []byte {\n\tcopyOfData := make([]byte, len(data))\n\tcopy(copyOfData, data)\n\n\tstaticSecretEnvVarsPattern := regexp.MustCompile(`(?i)\\b(?:` + strings.Join(StaticSecretEnvVars, \"|\") + `)(?:\\s+)?=(?:\\s+)?\"?([^\\s\"]+)`)\n\tfor _, matches := range staticSecretEnvVarsPattern.FindAllSubmatch(copyOfData, -1) {\n\t\tif len(matches) == 2 {\n\t\t\to := matches[0]\n\t\t\tn := bytes.ReplaceAll(matches[0], matches[1], []byte(\"REDACTED\"))\n\t\t\tcopyOfData = bytes.ReplaceAll(copyOfData, o, n)\n\t\t}\n\t}\n\tfor _, matches := range SecretGeneralisedEnvVarPattern.FindAllSubmatch(copyOfData, -1) {\n\t\tif len(matches) == 2 {\n\t\t\to := matches[0]\n\t\t\tn := bytes.ReplaceAll(matches[0], matches[1], []byte(\"REDACTED\"))\n\t\t\tcopyOfData = bytes.ReplaceAll(copyOfData, o, n)\n\t\t}\n\t}\n\tfor _, pattern := range SecretPatterns {\n\t\tcopyOfData = pattern.ReplaceAll(copyOfData, []byte(\"REDACTED\"))\n\t}\n\n\treturn copyOfData\n}\n"
  },
  {
    "path": "pkg/commands/compute/serve.go",
    "content": "package compute\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"context\"\n\t\"crypto/rand\"\n\t_ \"embed\"\n\t\"encoding/binary\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"io/fs\"\n\t\"log\"\n\t\"net\"\n\t\"net/url\"\n\t\"os\"\n\t\"os/exec\"\n\t\"os/signal\"\n\t\"path/filepath\"\n\t\"runtime\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"syscall\"\n\t\"time\"\n\n\t\"github.com/bep/debounce\"\n\t\"github.com/blang/semver\"\n\t\"github.com/fatih/color\"\n\t\"github.com/fsnotify/fsnotify\"\n\t\"github.com/mitchellh/go-ps\"\n\tignore \"github.com/sabhiram/go-gitignore\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/check\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\tfstexec \"github.com/fastly/cli/pkg/exec\"\n\t\"github.com/fastly/cli/pkg/filesystem\"\n\t\"github.com/fastly/cli/pkg/github\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/manifest\"\n\tfstruntime \"github.com/fastly/cli/pkg/runtime\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\nvar viceroyError = fsterr.RemediationError{\n\tInner:       fmt.Errorf(\"a Viceroy version was not found\"),\n\tRemediation: fsterr.BugRemediation,\n}\n\n// ServeCommand produces and runs an artifact from files on the local disk.\ntype ServeCommand struct {\n\targparser.Base\n\tbuild *BuildCommand\n\n\t// Build fields\n\tdir                   argparser.OptionalString\n\tincludeSrc            argparser.OptionalBool\n\tlang                  argparser.OptionalString\n\tmetadataDisable       argparser.OptionalBool\n\tmetadataFilterEnvVars argparser.OptionalString\n\tmetadataShow          argparser.OptionalBool\n\tpackageName           argparser.OptionalString\n\ttimeout               argparser.OptionalInt\n\n\t// Serve public fields (public for testing purposes)\n\tForceCheckViceroyLatest bool\n\tViceroyBinExtraArgs     string\n\tViceroyBinPath          string\n\tViceroyVersioner        github.AssetVersioner\n\n\t// Serve private fields\n\taddr                 string\n\tdebug                bool\n\tenablePushpin        bool\n\tpushpinRunnerBinPath string\n\tpushpinProxyPort     string\n\tpushpinPublishPort   string\n\tenv                  argparser.OptionalString\n\tfile                 argparser.OptionalString\n\tprofileGuest         bool\n\tprofileGuestDir      argparser.OptionalString\n\tprojectDir           string\n\tskipBuild            bool\n\twatch                bool\n\twatchDir             argparser.OptionalString\n}\n\n// NewServeCommand returns a usable command registered under the parent.\nfunc NewServeCommand(parent argparser.Registerer, g *global.Data, build *BuildCommand) *ServeCommand {\n\tvar c ServeCommand\n\tc.build = build\n\tc.Globals = g\n\tc.ViceroyVersioner = g.Versioners.Viceroy\n\tc.CmdClause = parent.Command(\"serve\", \"Build and run a Compute package locally\")\n\n\tc.CmdClause.Flag(\"addr\", \"The IPv4 address and port to listen on\").Default(\"127.0.0.1:7676\").StringVar(&c.addr)\n\tc.CmdClause.Flag(\"debug\", \"Run the server in Debug Adapter mode\").Hidden().BoolVar(&c.debug)\n\tc.CmdClause.Flag(\"dir\", \"Project directory to build (default: current directory)\").Short('C').Action(c.dir.Set).StringVar(&c.dir.Value)\n\tc.CmdClause.Flag(\"env\", \"The manifest environment config to use (e.g. 'stage' will attempt to read 'fastly.stage.toml')\").Action(c.env.Set).StringVar(&c.env.Value)\n\tc.CmdClause.Flag(\"file\", \"The Wasm file to run (causes build process to be skipped)\").Action(c.file.Set).StringVar(&c.file.Value)\n\tc.CmdClause.Flag(\"include-source\", \"Include source code in built package\").Action(c.includeSrc.Set).BoolVar(&c.includeSrc.Value)\n\tc.CmdClause.Flag(\"language\", \"Language type\").Action(c.lang.Set).StringVar(&c.lang.Value)\n\tc.CmdClause.Flag(\"metadata-disable\", \"Disable Wasm binary metadata annotations\").Action(c.metadataDisable.Set).BoolVar(&c.metadataDisable.Value)\n\tc.CmdClause.Flag(\"metadata-filter-envvars\", \"Redact specified environment variables from [scripts.env_vars] using comma-separated list\").Action(c.metadataFilterEnvVars.Set).StringVar(&c.metadataFilterEnvVars.Value)\n\tc.CmdClause.Flag(\"metadata-show\", \"Inspect the Wasm binary metadata\").Action(c.metadataShow.Set).BoolVar(&c.metadataShow.Value)\n\tc.CmdClause.Flag(\"package-name\", \"Package name\").Action(c.packageName.Set).StringVar(&c.packageName.Value)\n\tc.CmdClause.Flag(\"experimental-enable-pushpin\", \"Enable experimental Pushpin support for local testing of Fanout\").BoolVar(&c.enablePushpin)\n\tc.CmdClause.Flag(\"pushpin-path\", \"The path to a user installed version of the Pushpin runner binary\").StringVar(&c.pushpinRunnerBinPath)\n\tc.CmdClause.Flag(\"pushpin-proxy-port\", \"The port to run the Pushpin runner on. Overrides 'local_server.pushpin.proxy_port' from 'fastly.toml', and if not specified there, defaults to 7677.\").StringVar(&c.pushpinProxyPort)\n\tc.CmdClause.Flag(\"pushpin-publish-port\", \"The port to run the Pushpin publish handler on. Overrides 'local_server.pushpin.publish_port' from 'fastly.toml', and if not specified there, defaults to 5561.\").StringVar(&c.pushpinPublishPort)\n\tc.CmdClause.Flag(\"profile-guest\", \"Profile the Wasm guest under Viceroy (requires Viceroy 0.9.1 or higher). View profiles at https://profiler.firefox.com/.\").BoolVar(&c.profileGuest)\n\tc.CmdClause.Flag(\"profile-guest-dir\", \"The directory where the per-request profiles are saved to. Defaults to guest-profiles.\").Action(c.profileGuestDir.Set).StringVar(&c.profileGuestDir.Value)\n\tc.CmdClause.Flag(\"skip-build\", \"Skip the build step\").BoolVar(&c.skipBuild)\n\tc.CmdClause.Flag(\"timeout\", \"Timeout, in seconds, for the build compilation step\").Action(c.timeout.Set).IntVar(&c.timeout.Value)\n\tc.CmdClause.Flag(\"viceroy-args\", \"Additional arguments to pass to the Viceroy binary, separated by space\").StringVar(&c.ViceroyBinExtraArgs)\n\tc.CmdClause.Flag(\"viceroy-check\", \"Force the CLI to check for a newer version of the Viceroy binary\").BoolVar(&c.ForceCheckViceroyLatest)\n\tc.CmdClause.Flag(\"viceroy-path\", \"The path to a user installed version of the Viceroy binary\").StringVar(&c.ViceroyBinPath)\n\tc.CmdClause.Flag(\"watch\", \"Watch for file changes, then rebuild project and restart local server\").BoolVar(&c.watch)\n\tc.CmdClause.Flag(\"watch-dir\", \"The directory to watch files from (can be relative or absolute). Defaults to current directory.\").Action(c.watchDir.Set).StringVar(&c.watchDir.Value)\n\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *ServeCommand) Exec(in io.Reader, out io.Writer) (err error) {\n\tif c.skipBuild && c.watch {\n\t\treturn fsterr.ErrIncompatibleServeFlags\n\t}\n\n\tif runtime.GOARCH == \"386\" {\n\t\treturn fsterr.RemediationError{\n\t\t\tInner:       errors.New(\"this command doesn't support the '386' architecture\"),\n\t\t\tRemediation: \"Although the Fastly CLI supports '386', the `compute serve` command requires https://github.com/fastly/Viceroy which does not.\",\n\t\t}\n\t}\n\n\tmanifestFilename := EnvironmentManifest(c.env.Value)\n\tif c.env.Value != \"\" {\n\t\tif c.Globals.Verbose() {\n\t\t\ttext.Info(out, EnvManifestMsg, manifestFilename, manifest.Filename)\n\t\t}\n\t}\n\twd, err := os.Getwd()\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn fmt.Errorf(\"failed to get current working directory: %w\", err)\n\t}\n\tdefer func() {\n\t\t_ = os.Chdir(wd)\n\t}()\n\tmanifestPath := filepath.Join(wd, manifestFilename)\n\n\tc.projectDir, err = ChangeProjectDirectory(c.dir.Value)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif c.projectDir != \"\" {\n\t\tif c.Globals.Verbose() {\n\t\t\ttext.Info(out, ProjectDirMsg, c.projectDir)\n\t\t}\n\t\tmanifestPath = filepath.Join(c.projectDir, manifestFilename)\n\t}\n\n\twasmBinaryToRun := binWasmPath\n\tif c.file.WasSet {\n\t\twasmBinaryToRun = c.file.Value\n\t}\n\n\t// We skip the build if explicitly told to with --skip-build but also when the\n\t// user sets --file to specify their own wasm binary to pass to Viceroy. This\n\t// is typically for users who compile a Wasm binary using an unsupported\n\t// programming language for the Fastly Compute platform.\n\tif !c.skipBuild && !c.file.WasSet {\n\t\terr = c.Build(in, out)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ttext.Break(out)\n\t}\n\n\tc.setBackendsWithDefaultOverrideHostIfMissing(out)\n\n\tspinner, err := text.NewSpinner(out)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// NOTE: We read again the manifest to catch a skip-build scenario.\n\t//\n\t// For example, a user runs `compute build` then `compute serve --skip-build`.\n\t// In that scenario our in-memory manifest could be invalid as the user might\n\t// have also called `compute serve --skip-build --env <...> --dir <...>`.\n\t//\n\t// If the user doesn't set --skip-build then `compute serve` will call\n\t// `compute build` and the logic there will update the manifest in-memory data\n\t// with the relevant project directory and environment manifest content.\n\tif c.skipBuild || c.file.WasSet {\n\t\terr := c.Globals.Manifest.File.Read(manifestPath)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to parse manifest '%s': %w\", manifestPath, err)\n\t\t}\n\t\tc.ViceroyVersioner.SetRequestedVersion(c.Globals.Manifest.File.LocalServer.ViceroyVersion)\n\t\tif c.Globals.Verbose() {\n\t\t\tif c.skipBuild || c.file.WasSet {\n\t\t\t\ttext.Break(out)\n\t\t\t}\n\t\t\ttext.Info(out, \"Fastly manifest set to: %s\\n\\n\", manifestPath)\n\t\t}\n\t}\n\n\tbin, err := c.GetViceroy(spinner, out, manifestPath)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tenablePushpin := c.enablePushpin ||\n\t\t(c.Globals.Manifest.File.LocalServer.Pushpin != nil &&\n\t\t\tc.Globals.Manifest.File.LocalServer.Pushpin.EnablePushpin != nil &&\n\t\t\t*c.Globals.Manifest.File.LocalServer.Pushpin.EnablePushpin)\n\tvar pushpinCtx pushpinContext\n\tif enablePushpin {\n\t\t// The function checks for nil, so the semgrep warning is falsely triggered\n\t\t// nosemgrep: trailofbits.go.invalid-usage-of-modified-variable.invalid-usage-of-modified-variable\n\t\tpushpinCtx, err = c.startPushpin(spinner, out)\n\t\tif err != nil {\n\t\t\tpushpinCtx.Close()\n\t\t\treturn err\n\t\t}\n\t\tdefer pushpinCtx.Close()\n\t}\n\n\terr = spinner.Start()\n\tif err != nil {\n\t\treturn err\n\t}\n\tmsg := \"Running local server\"\n\tspinner.Message(msg + \"...\")\n\n\tspinner.StopMessage(msg)\n\terr = spinner.Stop()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif c.Globals.Verbose() {\n\t\ttext.Break(out)\n\t}\n\n\tvar restart bool\n\tfor {\n\t\terr = local(localOpts{\n\t\t\taddr:             c.addr,\n\t\t\tbin:              bin,\n\t\t\tdebug:            c.debug,\n\t\t\terrLog:           c.Globals.ErrLog,\n\t\t\textraArgs:        c.ViceroyBinExtraArgs,\n\t\t\tmanifestPath:     manifestPath,\n\t\t\tout:              out,\n\t\t\tprofileGuest:     c.profileGuest,\n\t\t\tprofileGuestDir:  c.profileGuestDir,\n\t\t\tpushpinProxyPort: pushpinCtx.proxyPort,\n\t\t\trestarted:        restart,\n\t\t\tverbose:          c.Globals.Verbose(),\n\t\t\twasmBinPath:      wasmBinaryToRun,\n\t\t\twatch:            c.watch,\n\t\t\twatchDir:         c.watchDir,\n\t\t})\n\t\tif err != nil {\n\t\t\tif err != fsterr.ErrViceroyRestart {\n\t\t\t\tif err == fsterr.ErrSignalInterrupt || err == fsterr.ErrSignalKilled {\n\t\t\t\t\ttext.Info(out, \"\\nLocal server stopped\")\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\t// Before restarting Viceroy we should rebuild.\n\t\t\ttext.Break(out)\n\t\t\terr = c.Build(in, out)\n\t\t\tif err != nil {\n\t\t\t\t// NOTE: build errors at this point are going to be user related, so we\n\t\t\t\t// should display the error but keep watching the files so we can\n\t\t\t\t// rebuild successfully once the user has fixed the issues.\n\t\t\t\tfsterr.Deduce(err).Print(color.Error)\n\t\t\t}\n\t\t\trestart = true\n\t\t}\n\t}\n}\n\n// Build constructs and executes the build logic.\nfunc (c *ServeCommand) Build(in io.Reader, out io.Writer) error {\n\t// Reset the fields on the BuildCommand based on ServeCommand values.\n\tif c.dir.WasSet {\n\t\tc.build.Flags.Dir = c.dir.Value\n\t}\n\tif c.env.WasSet {\n\t\tc.build.Flags.Env = c.env.Value\n\t}\n\tif c.includeSrc.WasSet {\n\t\tc.build.Flags.IncludeSrc = c.includeSrc.Value\n\t}\n\tif c.lang.WasSet {\n\t\tc.build.Flags.Lang = c.lang.Value\n\t}\n\tif c.packageName.WasSet {\n\t\tc.build.Flags.PackageName = c.packageName.Value\n\t}\n\tif c.timeout.WasSet {\n\t\tc.build.Flags.Timeout = c.timeout.Value\n\t}\n\tif c.metadataDisable.WasSet {\n\t\tc.build.MetadataDisable = c.metadataDisable.Value\n\t}\n\tif c.metadataFilterEnvVars.WasSet {\n\t\tc.build.MetadataFilterEnvVars = c.metadataFilterEnvVars.Value\n\t}\n\tif c.metadataShow.WasSet {\n\t\tc.build.MetadataShow = c.metadataShow.Value\n\t}\n\tif c.projectDir != \"\" {\n\t\tc.build.SkipChangeDir = true // we've already changed directory\n\t}\n\treturn c.build.Exec(in, out)\n}\n\n// setBackendsWithDefaultOverrideHostIfMissing sets an override_host for any\n// local_server.backends that is missing that property. The value will only be\n// set if the URL defined uses a hostname (e.g. http://127.0.0.1/ won't) so we\n// can set the override_host to match the hostname.\nfunc (c *ServeCommand) setBackendsWithDefaultOverrideHostIfMissing(out io.Writer) {\n\tfor k, backend := range c.Globals.Manifest.File.LocalServer.Backends {\n\t\tif backend.OverrideHost == \"\" {\n\t\t\tif u, err := url.Parse(backend.URL); err == nil {\n\t\t\t\tsegs := strings.Split(u.Host, \":\") // avoid parsing IP with port\n\t\t\t\tif ip := net.ParseIP(segs[0]); ip == nil {\n\t\t\t\t\tif c.Globals.Verbose() {\n\t\t\t\t\t\ttext.Info(out, \"[local_server.backends.%s] (%s) is configured without an `override_host`. We will use %s as a default to help avoid any unexpected errors. See https://www.fastly.com/documentation/reference/compute/fastly-toml#local-server for more details.\", k, backend.URL, u.Host)\n\t\t\t\t\t}\n\t\t\t\t\tbackend.OverrideHost = u.Host\n\t\t\t\t\tc.Globals.Manifest.File.LocalServer.Backends[k] = backend\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\n// GetViceroy returns the path to the installed binary.\n//\n// If Viceroy is installed we either update it or pin it to the version defined\n// in the fastly.toml [viceroy.viceroy_version]. Otherwise, if not installed, we\n// install it in the same directory as the application configuration data.\n//\n// In the case of a network failure we fallback to the latest installed version of the\n// Viceroy binary as long as one is installed and has the correct permissions.\nfunc (c *ServeCommand) GetViceroy(spinner text.Spinner, out io.Writer, manifestPath string) (bin string, err error) {\n\tif c.ViceroyBinPath != \"\" {\n\t\tif c.Globals.Verbose() {\n\t\t\ttext.Info(out, \"Using user provided install of Viceroy via --viceroy-path flag: %s\\n\\n\", c.ViceroyBinPath)\n\t\t}\n\t\treturn filepath.Abs(c.ViceroyBinPath)\n\t}\n\n\t// Allows a user to use a version of Viceroy that is installed in the $PATH.\n\tif usePath := os.Getenv(\"FASTLY_VICEROY_USE_PATH\"); checkViceroyEnvVar(usePath) {\n\t\tpath, err := exec.LookPath(\"viceroy\")\n\t\tif err != nil {\n\t\t\treturn \"\", fmt.Errorf(\"failed to lookup viceroy binary in user $PATH (user has set $FASTLY_VICEROY_USE_PATH): %w\", err)\n\t\t}\n\t\tif c.Globals.Verbose() {\n\t\t\ttext.Info(out, \"Using user provided install of Viceroy via $PATH lookup: %s\\n\\n\", path)\n\t\t}\n\t\treturn filepath.Abs(path)\n\t}\n\n\tbin = filepath.Join(github.InstallDir, c.ViceroyVersioner.BinaryName())\n\n\t// NOTE: When checking if Viceroy is installed we don't use\n\t// exec.LookPath(\"viceroy\") because PATH is unreliable across OS platforms,\n\t// but also we actually install Viceroy in the same location as the\n\t// application configuration, which means it wouldn't be found looking up by\n\t// the PATH env var. We could pass the path for the application configuration\n\t// into exec.LookPath() but it's simpler to just execute the binary.\n\t//\n\t// gosec flagged this:\n\t// G204 (CWE-78): Subprocess launched with variable\n\t// Disabling as the variables come from trusted sources.\n\t/* #nosec */\n\t// nosemgrep\n\tcommand := exec.Command(bin, \"--version\")\n\n\tvar installedVersion string\n\n\tstdoutStderr, err := command.CombinedOutput()\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t} else {\n\t\t// Check the version output has the expected format: `viceroy 0.1.0`\n\t\tinstalledVersion = strings.TrimSpace(string(stdoutStderr))\n\t\tsegs := strings.Split(installedVersion, \" \")\n\t\tif len(segs) < 2 {\n\t\t\treturn bin, viceroyError\n\t\t}\n\t\tinstalledVersion = segs[1]\n\t}\n\n\t// If the user hasn't explicitly set a Viceroy version, then we'll use\n\t// whatever the latest version is.\n\tversionToInstall := \"latest\"\n\tif v := c.ViceroyVersioner.RequestedVersion(); v != \"\" {\n\t\tversionToInstall = v\n\n\t\tif _, err := semver.Parse(versionToInstall); err != nil {\n\t\t\treturn bin, fsterr.RemediationError{\n\t\t\t\tInner:       fmt.Errorf(\"failed to parse configured version as a semver: %w\", err),\n\t\t\t\tRemediation: fmt.Sprintf(\"Ensure the %s `viceroy_version` value '%s' (under the [local_server] section) is a valid semver (https://semver.org/), e.g. `0.1.0`)\", manifestPath, versionToInstall),\n\t\t\t}\n\t\t}\n\t}\n\n\terr = c.InstallViceroy(installedVersion, versionToInstall, manifestPath, bin, spinner)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn bin, err\n\t}\n\n\terr = github.SetBinPerms(bin)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn bin, err\n\t}\n\treturn bin, nil\n}\n\n// checkViceroyEnvVar indicates if the CLI should use a Viceroy binary exposed\n// on the user's $PATH.\nfunc checkViceroyEnvVar(value string) bool {\n\tswitch strings.ToUpper(value) {\n\tcase \"1\", \"TRUE\":\n\t\treturn true\n\t}\n\treturn false\n}\n\n// InstallViceroy downloads the binary from GitHub.\n//\n// The logic flow is as follows:\n//\n// 1. Check if version to install is \"latest\"\n// 2. If so, check the latest release matches the installed version.\n// 3. If not latest, check the installed version matches the expected version.\nfunc (c *ServeCommand) InstallViceroy(\n\tinstalledVersion, versionToInstall, manifestPath, bin string,\n\tspinner text.Spinner,\n) error {\n\tvar (\n\t\terr         error\n\t\tmsg, tmpBin string\n\t)\n\n\tswitch {\n\tcase installedVersion == \"\": // Viceroy not installed\n\t\tif c.Globals.Verbose() {\n\t\t\ttext.Info(c.Globals.Output, \"Viceroy is not already installed, so we will install the %s version.\\n\\n\", versionToInstall)\n\t\t}\n\t\terr = spinner.Start()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tmsg = fmt.Sprintf(\"Fetching Viceroy release: %s\", versionToInstall)\n\t\tspinner.Message(msg + \"...\")\n\n\t\tif versionToInstall == \"latest\" {\n\t\t\ttmpBin, err = c.ViceroyVersioner.DownloadLatest()\n\t\t} else {\n\t\t\ttmpBin, err = c.ViceroyVersioner.DownloadVersion(versionToInstall)\n\t\t}\n\tcase versionToInstall != \"latest\":\n\t\tif installedVersion == versionToInstall {\n\t\t\tif c.Globals.Verbose() {\n\t\t\t\ttext.Info(c.Globals.Output, \"Viceroy is already installed, and the installed version matches the required version (%s) in the %s file.\\n\\n\", versionToInstall, manifestPath)\n\t\t\t}\n\t\t\treturn nil\n\t\t}\n\t\tif c.Globals.Verbose() {\n\t\t\ttext.Info(c.Globals.Output, \"Viceroy is already installed, but the installed version (%s) doesn't match the required version (%s) specified in the %s file.\\n\\n\", installedVersion, versionToInstall, manifestPath)\n\t\t}\n\n\t\terr = spinner.Start()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tmsg = fmt.Sprintf(\"Fetching Viceroy release: %s\", versionToInstall)\n\t\tspinner.Message(msg + \"...\")\n\n\t\ttmpBin, err = c.ViceroyVersioner.DownloadVersion(versionToInstall)\n\tcase versionToInstall == \"latest\":\n\t\t// Viceroy is already installed, so we check if the installed version matches the latest.\n\t\t// But we'll skip that check if the TTL for the Viceroy LastChecked hasn't expired.\n\n\t\tstale := check.Stale(c.Globals.Config.Viceroy.LastChecked, c.Globals.Config.Viceroy.TTL)\n\t\tif !stale && !c.ForceCheckViceroyLatest {\n\t\t\tif c.Globals.Verbose() {\n\t\t\t\ttext.Info(c.Globals.Output, \"Viceroy is installed but the CLI config (`fastly config`) shows the TTL, checking for a newer version, hasn't expired. To force a refresh, re-run the command with the `--viceroy-check` flag.\\n\\n\")\n\t\t\t}\n\t\t\treturn nil\n\t\t}\n\n\t\t// IMPORTANT: We declare separately so to shadow `err` from parent scope.\n\t\tvar latestVersion string\n\n\t\t// NOTE: We won't stop the user because although we can't request the latest\n\t\t// version of the tool, the user may have a local version already installed.\n\t\terr = spinner.Process(\"Checking latest Viceroy release\", func(_ *text.SpinnerWrapper) error {\n\t\t\tlatestVersion, err = c.ViceroyVersioner.LatestVersion()\n\t\t\tif err != nil {\n\t\t\t\treturn fsterr.RemediationError{\n\t\t\t\t\tInner:       fmt.Errorf(\"error fetching latest version: %w\", err),\n\t\t\t\t\tRemediation: fsterr.NetworkRemediation,\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn nil\n\t\t})\n\t\tif err != nil {\n\t\t\treturn nil // short-circuit the rest of this function\n\t\t}\n\n\t\tviceroyConfig := c.Globals.Config.Viceroy\n\t\tviceroyConfig.LatestVersion = latestVersion\n\t\tviceroyConfig.LastChecked = time.Now().Format(time.RFC3339)\n\n\t\t// Before attempting to write the config data back to disk we need to\n\t\t// ensure we reassign the modified struct which is a copy (not reference).\n\t\tc.Globals.Config.Viceroy = viceroyConfig\n\n\t\terr = c.Globals.Config.Write(c.Globals.ConfigPath)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif c.Globals.Verbose() {\n\t\t\ttext.Info(c.Globals.Output, \"\\nThe CLI config (`fastly config`) has been updated with the latest Viceroy version: %s\\n\\n\", latestVersion)\n\t\t}\n\n\t\tif installedVersion != \"\" && installedVersion == latestVersion {\n\t\t\treturn nil\n\t\t}\n\n\t\terr = spinner.Start()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tmsg = fmt.Sprintf(\"Fetching Viceroy release: %s\", versionToInstall)\n\t\tspinner.Message(msg + \"...\")\n\n\t\ttmpBin, err = c.ViceroyVersioner.DownloadLatest()\n\t}\n\n\t// NOTE: The above `switch` needs to shadow the function-level `err` variable.\n\tif err != nil {\n\t\terr = fmt.Errorf(\"error downloading Viceroy release: %w\", err)\n\t\tspinner.StopFailMessage(msg)\n\t\tspinErr := spinner.StopFail()\n\t\tif spinErr != nil {\n\t\t\treturn fmt.Errorf(text.SpinnerErrWrapper, spinErr, err)\n\t\t}\n\t\treturn err\n\t}\n\tdefer os.RemoveAll(tmpBin)\n\n\tif err := os.Rename(tmpBin, bin); err != nil {\n\t\terr = fmt.Errorf(\"failed to rename/move file: %w\", err)\n\t\tif copyErr := filesystem.CopyFile(tmpBin, bin); copyErr != nil {\n\t\t\terr = fmt.Errorf(\"failed to copy file: %w (original error: %w)\", copyErr, err)\n\t\t\tspinner.StopFailMessage(msg)\n\t\t\tspinErr := spinner.StopFail()\n\t\t\tif spinErr != nil {\n\t\t\t\treturn fmt.Errorf(text.SpinnerErrWrapper, spinErr, err)\n\t\t\t}\n\t\t\treturn err\n\t\t}\n\t}\n\n\tspinner.StopMessage(msg)\n\treturn spinner.Stop()\n}\n\n// GetPushpinProxyPort returns the port to run the Pushpin proxy.\n//\n// The default value is 7677.\n// It can be overridden by providing the --pushpin-proxy-port command-line parameter.\n// If it is not found then `local_server.pushpin.proxy_port` in fastly.toml is also checked.\nfunc (c *ServeCommand) GetPushpinProxyPort(out io.Writer) (uint16, error) {\n\tpushpinProxyPortStr := c.pushpinProxyPort\n\tvar pushpinProxyPort uint16\n\tif pushpinProxyPortStr != \"\" {\n\t\tpushpinProxyPortInt, err := strconv.ParseUint(pushpinProxyPortStr, 10, 16)\n\t\tif err != nil {\n\t\t\treturn 0, fmt.Errorf(\"can't parse --pushpin-proxy-port value as a number: %s\", pushpinProxyPortStr)\n\t\t}\n\t\tif pushpinProxyPortInt < 1 || pushpinProxyPortInt > 65535 {\n\t\t\treturn 0, fmt.Errorf(\"--pushpin-proxy-port must be a number between 1 and 65535 (got: %d)\", pushpinProxyPortInt)\n\t\t}\n\t\tpushpinProxyPort = uint16(pushpinProxyPortInt)\n\t\tif c.Globals.Verbose() {\n\t\t\ttext.Info(out, \"Using Pushpin proxy port from --pushpin-proxy-port flag: %d\", pushpinProxyPort)\n\t\t}\n\t\treturn pushpinProxyPort, nil\n\t}\n\n\tif c.Globals.Manifest.File.LocalServer.Pushpin != nil &&\n\t\tc.Globals.Manifest.File.LocalServer.Pushpin.PushpinProxyPort != nil {\n\t\tpushpinProxyPort = *c.Globals.Manifest.File.LocalServer.Pushpin.PushpinProxyPort\n\t\tif pushpinProxyPort != 0 {\n\t\t\tif c.Globals.Verbose() {\n\t\t\t\ttext.Info(out, \"Using Pushpin proxy port via `local_server.pushpin.proxy_port` setting: %d\", pushpinProxyPort)\n\t\t\t}\n\t\t\treturn pushpinProxyPort, nil\n\t\t}\n\t}\n\n\tpushpinProxyPort = 7677\n\tif c.Globals.Verbose() {\n\t\ttext.Info(out, \"Using default Pushpin proxy port %d\", pushpinProxyPort)\n\t}\n\treturn pushpinProxyPort, nil\n}\n\n// GetPushpinPublishPort returns the port to run the Pushpin publishing handler.\n// The design of Pushpin opens four ports starting with this port, though the publishing\n// handler itself runs on the specified port.\n//\n// The default value is 5561.\n// It can be overridden by providing the --pushpin-publish-port command-line parameter.\n// If it is not found then `local_server.pushpin.publish_port` in fastly.toml is also checked.\nfunc (c *ServeCommand) GetPushpinPublishPort(out io.Writer) (uint16, error) {\n\tpushpinPublishPortStr := c.pushpinPublishPort\n\tvar pushpinPublishPort uint16\n\tif pushpinPublishPortStr != \"\" {\n\t\tpushpinPublishPortInt, err := strconv.ParseUint(pushpinPublishPortStr, 10, 16)\n\t\tif err != nil {\n\t\t\treturn 0, fmt.Errorf(\"can't parse --pushpin-publish-port value as a number: %s\", pushpinPublishPortStr)\n\t\t}\n\t\tif pushpinPublishPortInt < 1 || pushpinPublishPortInt > 65535 {\n\t\t\treturn 0, fmt.Errorf(\"--pushpin-publish-port must be a number between 1 and 65535 (got: %d)\", pushpinPublishPortInt)\n\t\t}\n\t\tpushpinPublishPort = uint16(pushpinPublishPortInt)\n\t\tif c.Globals.Verbose() {\n\t\t\ttext.Info(out, \"Using Pushpin publish handler port from --pushpin-publish-port flag: %d\", pushpinPublishPort)\n\t\t}\n\t\treturn pushpinPublishPort, nil\n\t}\n\n\tif c.Globals.Manifest.File.LocalServer.Pushpin != nil &&\n\t\tc.Globals.Manifest.File.LocalServer.Pushpin.PushpinPublishPort != nil {\n\t\tpushpinPublishPort = *c.Globals.Manifest.File.LocalServer.Pushpin.PushpinPublishPort\n\t\tif pushpinPublishPort != 0 {\n\t\t\tif c.Globals.Verbose() {\n\t\t\t\ttext.Info(out, \"Using Pushpin publish handler port via `local_server.pushpin.publish_port` setting: %d\", pushpinPublishPort)\n\t\t\t}\n\t\t\treturn pushpinPublishPort, nil\n\t\t}\n\t}\n\n\tpushpinPublishPort = 5561\n\tif c.Globals.Verbose() {\n\t\ttext.Info(out, \"Using default Pushpin publish handler port %d\", pushpinPublishPort)\n\t}\n\treturn pushpinPublishPort, nil\n}\n\n// GetPushpinRunner returns the path to the installed Pushpin binary.\n//\n// This value comes from searching the system path for `pushpin`\n// It can be overridden by providing the --pushpin-path command-line parameter.\n// If it is not found then `local_server.pushpin.pushpin_path` in fastly.toml is also checked.\nfunc (c *ServeCommand) GetPushpinRunner(out io.Writer) (bin string, err error) {\n\tpushpinRunnerBinPath := c.pushpinRunnerBinPath\n\tif pushpinRunnerBinPath != \"\" {\n\t\tif c.Globals.Verbose() {\n\t\t\ttext.Info(out, \"Using user provided install of Pushpin runner via --pushpin-path flag: %s\", pushpinRunnerBinPath)\n\t\t}\n\t\treturn filepath.Abs(pushpinRunnerBinPath)\n\t}\n\n\tif c.Globals.Manifest.File.LocalServer.Pushpin != nil &&\n\t\tc.Globals.Manifest.File.LocalServer.Pushpin.PushpinPath != nil {\n\t\tpushpinRunnerBinPath = *c.Globals.Manifest.File.LocalServer.Pushpin.PushpinPath\n\t\tif pushpinRunnerBinPath != \"\" {\n\t\t\tif c.Globals.Verbose() {\n\t\t\t\ttext.Info(out, \"Using user provided install of Pushpin runner via `local_server.pushpin.pushpin_path` setting: %s\", pushpinRunnerBinPath)\n\t\t\t}\n\t\t\treturn filepath.Abs(pushpinRunnerBinPath)\n\t\t}\n\t}\n\n\tif c.Globals.Verbose() {\n\t\ttext.Info(out, \"No --pushpin-path provided, attempting to find 'pushpin' in your PATH...\")\n\t}\n\tpushpinRunnerBinPath, err = exec.LookPath(\"pushpin\")\n\tif err != nil {\n\t\treturn \"\", fsterr.RemediationError{\n\t\t\tInner:       fmt.Errorf(\"failed to find 'pushpin' in your $PATH\"),\n\t\t\tRemediation: \"Pushpin support was enabled via --enable-experimental-pushpin, but the 'pushpin' binary could not be found in your $PATH. Please install Pushpin (see: https://pushpin.org/docs/install/) or provide a path to the binary using the --pushpin-path flag.\",\n\t\t}\n\t}\n\n\tif c.Globals.Verbose() {\n\t\ttext.Info(out, \"Found Pushpin runner via $PATH lookup: %s\", pushpinRunnerBinPath)\n\t}\n\treturn filepath.Abs(pushpinRunnerBinPath)\n}\n\n// BuildPushpinRoutes builds a slice of strings based on the backends\n// defined in the manifest's backend section.\nfunc (c *ServeCommand) BuildPushpinRoutes() []string {\n\tvar routes []string\n\tfor name, backend := range c.Globals.Manifest.File.LocalServer.Backends {\n\n\t\t// The target should be a URL\n\t\tu, err := url.Parse(backend.URL)\n\t\tif err != nil {\n\t\t\t// This is unlikely as we parse it elsewhere, but good to be safe.\n\t\t\t// We'll just skip this backend if the URL is invalid.\n\t\t\tcontinue\n\t\t}\n\n\t\t// Route Rule:\n\t\t// 1. `id=<backend_name>`: Match requests whose Pushpin-Route header equals the backend name.\n\t\trules := fmt.Sprintf(\"id=%s\", name)\n\n\t\t// 2. A backend may have a path component. If it does, then it will be prepended during forwarding.\n\t\tforwardPrefix := strings.TrimSuffix(u.Path, \"/\")\n\t\tif forwardPrefix != \"\" {\n\t\t\trules += fmt.Sprintf(\",replace_beg=%s\", forwardPrefix)\n\t\t}\n\n\t\t// Target:\n\t\ttarget := normalizeHost(u)\n\t\t// 1. `over_http`: Enable WebSocket-over-HTTP\n\t\ttarget += \",over_http\"\n\t\t// 2. `ssl`: If backend is https\n\t\tif u.Scheme == \"https\" {\n\t\t\ttarget += \",ssl\"\n\t\t}\n\t\t// 3. `host`: If the backend has an override_host.\n\t\tif backend.OverrideHost != \"\" {\n\t\t\ttarget += fmt.Sprintf(\",host=%s\", backend.OverrideHost)\n\t\t}\n\n\t\t// The final route format\n\t\trouteArg := fmt.Sprintf(\"%s %s\", rules, target)\n\t\troutes = append(routes, routeArg)\n\t}\n\n\treturn routes\n}\n\nfunc normalizeHost(u *url.URL) string {\n\thost := u.Host\n\n\t// If Host already has a port, SplitHostPort succeeds\n\t// This an attempt at future-proofing as it handles IPv6\n\tif _, _, err := net.SplitHostPort(host); err == nil {\n\t\treturn host\n\t}\n\n\tswitch u.Scheme {\n\tcase \"https\":\n\t\treturn net.JoinHostPort(host, \"443\")\n\tcase \"http\":\n\t\treturn net.JoinHostPort(host, \"80\")\n\tdefault:\n\t\t// Unknown scheme, leave untouched\n\t\treturn host\n\t}\n}\n\nfunc formatPushpinLog(line string) (string, string) {\n\tlevel := \"INFO\"\n\tmsg := line\n\n\tif strings.HasPrefix(line, \"[ERR]\") || strings.HasPrefix(line, \"[WARN]\") ||\n\t\tstrings.HasPrefix(line, \"[INFO]\") || strings.HasPrefix(line, \"[DEBUG]\") {\n\t\tparts := strings.SplitN(line, \" \", 4)\n\t\tif len(parts) == 4 {\n\t\t\tlevel = strings.Trim(parts[0], \"[]\")\n\t\t\tif level == \"ERR\" {\n\t\t\t\tlevel = \"ERROR\"\n\t\t\t}\n\t\t\tmsg = parts[3]\n\t\t}\n\t}\n\n\t// Return as-is if it doesn't match pattern\n\treturn level, \"[Pushpin] \" + msg\n}\n\n// pushpinContext contains information about the instance of Pushpin that is\n// executed when enabled.\ntype pushpinContext struct {\n\tinstanceID       uint32\n\tconfFilePath     string\n\tpushpinRunnerBin string\n\tpushpinRunDir    string\n\tpushpinLogDir    string\n\troutesFilePath   string\n\tproxyPort        uint16\n\tpublishPort      uint16\n\tcleanup          func()\n}\n\n// Close ends Pushpin if it's running by calling the registered cleanup function.\nfunc (c *pushpinContext) Close() {\n\tif c.cleanup != nil {\n\t\tc.cleanup()\n\t}\n}\n\n// pushpinConfTemplate is a template used by buildPushpinConf.\n//\n//go:embed pushpin.conf.template\nvar pushpinConfTemplate string\n\n// buildPushpinConf builds a temporary pushpin.conf file that contains everything that covers our needs.\nfunc (c *pushpinContext) buildPushpinConf() string {\n\tpullPort := c.publishPort + 1\n\tsubPort := c.publishPort + 2\n\trepPort := c.publishPort + 3\n\treturn fmt.Sprintf(\n\t\tpushpinConfTemplate,\n\t\tc.pushpinRunDir,\n\t\tc.pushpinLogDir,\n\t\tc.routesFilePath,\n\t\tc.proxyPort,\n\t\tc.publishPort,\n\t\tpullPort,\n\t\tsubPort,\n\t\trepPort,\n\t)\n}\n\n// startPushpin starts Pushpin based on the configuration provided by the\n// command line and/or fastly.toml. The cleanup function on the returned pushpinContext\n// needs to eventually be called by the caller to shut down Pushpin.\nfunc (c *ServeCommand) startPushpin(spinner text.Spinner, out io.Writer) (pushpinContext, error) {\n\ttext.Info(out, \"Enabling experimental Pushpin support for local testing of Fanout.\")\n\n\tpushpinCtx := pushpinContext{}\n\n\t// Generate a non-zero instance ID to represent this Pushpin instance and build temporary\n\t// files\n\tfor {\n\t\tp := make([]byte, 4)\n\t\t_, _ = rand.Read(p)\n\t\tpushpinCtx.instanceID = binary.BigEndian.Uint32(p)\n\t\tif pushpinCtx.instanceID != 0 {\n\t\t\tbreak\n\t\t}\n\t}\n\n\tvar err error\n\tpushpinCtx.proxyPort, err = c.GetPushpinProxyPort(out)\n\tif err != nil {\n\t\treturn pushpinCtx, err\n\t}\n\tpushpinCtx.publishPort, err = c.GetPushpinPublishPort(out)\n\tif err != nil {\n\t\treturn pushpinCtx, err\n\t}\n\tpushpinCtx.pushpinRunnerBin, err = c.GetPushpinRunner(out)\n\tif err != nil {\n\t\treturn pushpinCtx, err\n\t}\n\n\tpwd, _ := os.Getwd()\n\tpushpinCtx.pushpinLogDir = filepath.Join(pwd, \"pushpin-logs\")\n\n\tpushpinCtx.pushpinRunDir = filepath.Join(\n\t\tos.TempDir(),\n\t\tfmt.Sprintf(\"pushpin-%08x\", pushpinCtx.instanceID),\n\t)\n\tpushpinCtx.confFilePath = filepath.Join(\n\t\tos.TempDir(),\n\t\tfmt.Sprintf(\"pushpin-%08x.conf\", pushpinCtx.instanceID),\n\t)\n\tpushpinCtx.routesFilePath = filepath.Join(\n\t\tos.TempDir(),\n\t\tfmt.Sprintf(\"pushpin-routes-%08x\", pushpinCtx.instanceID),\n\t)\n\n\ttext.Break(out)\n\n\terr = spinner.Start()\n\tif err != nil {\n\t\treturn pushpinCtx, err\n\t}\n\tmsg := \"Starting Pushpin\"\n\tspinner.Message(msg + \"...\")\n\n\tspinner.StopMessage(msg)\n\terr = spinner.Stop()\n\tif err != nil {\n\t\treturn pushpinCtx, err\n\t}\n\n\tpushpinConfContents := pushpinCtx.buildPushpinConf()\n\terr = os.WriteFile(pushpinCtx.confFilePath, []byte(pushpinConfContents), 0o600)\n\tif err != nil {\n\t\treturn pushpinCtx, fmt.Errorf(\"error writing config file %s: %w\", pushpinCtx.confFilePath, err)\n\t}\n\n\tpushpinRoutesContents := strings.Join(c.BuildPushpinRoutes(), \"\\n\") + \"\\n\"\n\terr = os.WriteFile(pushpinCtx.routesFilePath, []byte(pushpinRoutesContents), 0o600)\n\tif err != nil {\n\t\treturn pushpinCtx, fmt.Errorf(\"error writing routes file %s: %w\", pushpinCtx.routesFilePath, err)\n\t}\n\n\t// Pushpin is configured with the following.\n\t// - A conf file that sets up the parameters of the instance. In our case, we:\n\t//   - set the runtime temporary files directory\n\t//   - set the log output directory\n\t//   - enable \"pushpin-route\" header for routing\n\t//   - set the message size (64k) to match Fanout\n\t//   - set the publishing addr and port\n\t//   - path to the routes file to use\n\t// - A routes file that sets up the routes. In our case, we:\n\t//   - wires up a backend name (id) to the server host\n\t//   - if the backend sets an override host, then we set that\n\t//   - if the backend enables HTTPS, then we enable that\n\t//   - if the backend has a path prefix, then we set that up\n\t//   - enables WebSocket-over-HTTP\n\t// The runtime temporary directory, as well as the conf file and routes file\n\t// are set up and torn down along with fastly compute serve.\n\n\targs := []string{\n\t\tfmt.Sprintf(\"--config=%s\", pushpinCtx.confFilePath),\n\t\t\"--verbose\",\n\t}\n\n\t// Set up a context that can be canceled (prevent zombie Pushpin process)\n\tvar pushpinCmd *exec.Cmd\n\tctx, cancel := context.WithCancel(context.Background())\n\n\tvar once sync.Once\n\tpushpinCtx.cleanup = func() {\n\t\tonce.Do(func() {\n\t\t\tif pushpinCmd != nil && pushpinCmd.Process != nil {\n\t\t\t\tif c.Globals.Verbose() {\n\t\t\t\t\ttext.Output(out, \"shutting down Pushpin\")\n\t\t\t\t}\n\t\t\t\tkillProcessTree(pushpinCmd.Process.Pid)\n\t\t\t}\n\t\t\tif c.Globals.Verbose() {\n\t\t\t\ttext.Output(out, \"removing %s\", pushpinCtx.pushpinRunDir)\n\t\t\t}\n\t\t\t_ = os.RemoveAll(pushpinCtx.pushpinRunDir)\n\t\t\tif c.Globals.Verbose() {\n\t\t\t\ttext.Output(out, \"deleting %s\", pushpinCtx.confFilePath)\n\t\t\t}\n\t\t\t_ = os.Remove(pushpinCtx.confFilePath)\n\t\t\tif c.Globals.Verbose() {\n\t\t\t\ttext.Output(out, \"deleting %s\", pushpinCtx.routesFilePath)\n\t\t\t}\n\t\t\t_ = os.Remove(pushpinCtx.routesFilePath)\n\t\t\tcancel()\n\t\t})\n\t}\n\n\t// Also allow other forms of termination to perform cleanups\n\tsigCh := make(chan os.Signal, 1)\n\tsignal.Notify(sigCh, os.Interrupt, syscall.SIGTERM, syscall.SIGHUP, syscall.SIGQUIT)\n\tgo func() {\n\t\t<-sigCh\n\t\tpushpinCtx.Close()\n\t}()\n\n\t// gosec flagged this:\n\t// G204: Subprocess launched with a potential tainted input or cmd arguments\n\t// Disabling as we control this command.\n\t// #nosec\n\t// nosemgrep\n\tpushpinCmd = exec.CommandContext(ctx, pushpinCtx.pushpinRunnerBin, args...)\n\tpushpinCmd.Stderr = out\n\tstdout, err := pushpinCmd.StdoutPipe()\n\tif err != nil {\n\t\treturn pushpinCtx, fmt.Errorf(\"failed to capture Pushpin stdout: %w\", err)\n\t}\n\n\t// Start Pushpin\n\tif c.Globals.Verbose() {\n\t\ttext.Output(out, \"%s: %s\", text.BoldYellow(\"Pushpin command\"), strings.Join(pushpinCmd.Args, \" \"))\n\t\ttext.Output(out, \"%s: %d\", text.BoldYellow(\"Pushpin proxy port\"), pushpinCtx.proxyPort)\n\t\ttext.Output(out, \"%s: %d\", text.BoldYellow(\"Pushpin publisher port\"), pushpinCtx.publishPort)\n\t\ttext.Output(out, \"%s: %d - %d\", text.BoldYellow(\"Pushpin other reserved ports\"), pushpinCtx.publishPort+1, pushpinCtx.publishPort+3)\n\t\ttext.Output(out, \"%s: %s\", text.BoldYellow(\"Pushpin temporary runtime directory\"), pushpinCtx.pushpinRunDir)\n\t\ttext.Output(out, \"%s: %s\", text.BoldYellow(\"Pushpin conf file\"), pushpinCtx.confFilePath)\n\t\ttext.Output(out, \"%s: %s\", text.BoldYellow(\"Pushpin routes file\"), pushpinCtx.routesFilePath)\n\t}\n\tif err := pushpinCmd.Start(); err != nil {\n\t\treturn pushpinCtx, fmt.Errorf(\"failed to start Pushpin runner: %w\", err)\n\t}\n\n\t// Monitor output from Pushpin\n\t// 1. convert output and log it\n\t// 2. wait for a timeout after a startup message\n\tstartupError := make(chan error, 1)\n\tgo func() {\n\t\tscanner := bufio.NewScanner(stdout)\n\t\tfor scanner.Scan() {\n\t\t\tline := scanner.Text()\n\t\t\t// Successful if timeout passes after seeing 'started'\n\t\t\tif strings.HasSuffix(line, \"started\") {\n\t\t\t\tgo func() {\n\t\t\t\t\ttime.Sleep(1000 * time.Millisecond)\n\t\t\t\t\tstartupError <- nil\n\t\t\t\t}()\n\t\t\t}\n\n\t\t\tlevel, msg := formatPushpinLog(line)\n\t\t\tif level != \"DEBUG\" || c.Globals.Verbose() {\n\t\t\t\ttext.Output(out, \"%s  %s %s\", time.Now().UTC().Format(\"2006-01-02T15:04:05.000000Z\"), level, msg)\n\t\t\t}\n\t\t}\n\n\t\tif err := scanner.Err(); err != nil {\n\t\t\tstartupError <- fmt.Errorf(\"error reading Pushpin output: %w\", err)\n\t\t} else {\n\t\t\tstartupError <- fmt.Errorf(\"process Pushpin terminated\")\n\t\t}\n\t}()\n\n\t// Startup error\n\terr = <-startupError\n\tif err != nil {\n\t\treturn pushpinCtx, fsterr.RemediationError{\n\t\t\tInner:       err,\n\t\t\tRemediation: fmt.Sprintf(\"Check that your disk isn't full and that a process isn't already running on ports %d or %d - %d.\", pushpinCtx.proxyPort, pushpinCtx.publishPort, pushpinCtx.publishPort+3),\n\t\t}\n\t}\n\n\ttext.Success(out, \"Pushpin started.\")\n\ttext.Break(out)\n\n\treturn pushpinCtx, nil\n}\n\n// localOpts represents the inputs for `local()`.\ntype localOpts struct {\n\taddr             string\n\tbin              string\n\tdebug            bool\n\terrLog           fsterr.LogInterface\n\textraArgs        string\n\tmanifestPath     string\n\tout              io.Writer\n\tprofileGuest     bool\n\tprofileGuestDir  argparser.OptionalString\n\tpushpinProxyPort uint16\n\trestarted        bool\n\tverbose          bool\n\twasmBinPath      string\n\twatch            bool\n\twatchDir         argparser.OptionalString\n}\n\n// local spawns a subprocess that runs the compiled binary.\nfunc local(opts localOpts) error {\n\t// NOTE: Viceroy no longer displays errors unless in verbose mode.\n\t// This can cause confusion for customers: https://github.com/fastly/cli/issues/913\n\t// So regardless of CLI --verbose flag we'll always set verbose for Viceroy.\n\targs := []string{\"-v\", \"-C\", opts.manifestPath, \"--addr\", opts.addr, opts.wasmBinPath}\n\n\tif opts.debug {\n\t\targs = append(args, \"--debug\")\n\t}\n\n\tif opts.profileGuest {\n\t\tdirectory := \"guest-profiles\"\n\t\tif opts.profileGuestDir.WasSet {\n\t\t\tdirectory = opts.profileGuestDir.Value\n\t\t}\n\t\targs = append(args, \"--profile=guest,\"+directory)\n\t\tif opts.verbose {\n\t\t\ttext.Info(opts.out, \"Saving per-request profiles to %s.\", directory)\n\t\t}\n\t}\n\n\tif opts.pushpinProxyPort != 0 {\n\t\targs = append(args, fmt.Sprintf(\"--local-pushpin-proxy-port=%d\", opts.pushpinProxyPort))\n\t}\n\n\tif opts.extraArgs != \"\" {\n\t\textraArgs := strings.Split(opts.extraArgs, \" \")\n\t\targs = append(args, extraArgs...)\n\t}\n\n\tif opts.verbose {\n\t\tif opts.restarted {\n\t\t\ttext.Break(opts.out)\n\t\t}\n\t\ttext.Output(opts.out, \"%s: %s\", text.BoldYellow(\"Manifest\"), opts.manifestPath)\n\t\ttext.Output(opts.out, \"%s: %s\", text.BoldYellow(\"Wasm binary\"), opts.wasmBinPath)\n\t\ttext.Output(opts.out, \"%s: %s\", text.BoldYellow(\"Viceroy command\"), strings.Join(args, \" \"))\n\t\ttext.Output(opts.out, \"%s: %s\", text.BoldYellow(\"Viceroy binary\"), opts.bin)\n\n\t\t// gosec flagged this:\n\t\t// G204 (CWE-78): Subprocess launched with function call as argument or cmd arguments\n\t\t// Disabling as we trust the source of the variable.\n\t\t// #nosec\n\t\t// nosemgrep: go.lang.security.audit.dangerous-exec-command.dangerous-exec-command\n\t\tc := exec.Command(opts.bin, \"--version\")\n\t\tif output, err := c.Output(); err == nil {\n\t\t\ttext.Output(opts.out, \"%s: %s\", text.BoldYellow(\"Viceroy version\"), string(output))\n\t\t}\n\t\ttext.Info(opts.out, \"Listening on http://%s\", opts.addr)\n\t\tif opts.watch {\n\t\t\ttext.Break(opts.out)\n\t\t}\n\t}\n\n\ts := &fstexec.Streaming{\n\t\tArgs:        args,\n\t\tCommand:     opts.bin,\n\t\tEnv:         os.Environ(),\n\t\tForceOutput: true,\n\t\tOutput:      opts.out,\n\t\tSignalCh:    make(chan os.Signal, 1),\n\t}\n\ts.MonitorSignals()\n\n\tfailure := make(chan error)\n\trestart := make(chan bool)\n\tif opts.watch {\n\t\troot := \".\"\n\t\tif opts.watchDir.WasSet {\n\t\t\troot = opts.watchDir.Value\n\t\t}\n\n\t\tif opts.verbose {\n\t\t\ttext.Info(opts.out, \"Watching files for changes (using --watch-dir=%s). To ignore certain files, define patterns within a .fastlyignore config file (uses .fastlyignore from --watch-dir).\\n\\n\", root)\n\t\t}\n\n\t\tgi := ignoreFiles(opts.watchDir)\n\t\tgo watchFiles(root, gi, opts.verbose, s, opts.out, restart, failure)\n\t}\n\n\t// NOTE: The viceroy executable can be stopped by one of three mechanisms.\n\t//\n\t// 1. File modification\n\t// 2. Explicit signal (SIGINT, SIGTERM etc).\n\t// 3. Irrecoverable error (i.e. error watching files).\n\t//\n\t// In the case of a signal (e.g. user presses Ctrl-c) the listener logic\n\t// inside of (*fstexec.Streaming).MonitorSignals() will call\n\t// (*fstexec.Streaming).Signal(signal os.Signal) to kill the process.\n\t//\n\t// In the case of a file modification the viceroy executable needs to first\n\t// be killed (handled by the watchFiles() function) and then we can stop the\n\t// signal listeners (handled below by sending a message to argparser.SignalCh).\n\t//\n\t// If we don't tell the signal listening channel to close, then the restart\n\t// of the viceroy executable will cause the user to end up with N number of\n\t// listeners. This will result in a \"os: process already finished\" error when\n\t// we do finally come to stop the `serve` command (e.g. user presses Ctrl-c).\n\t// How big an issue this is depends on how many file modifications a user\n\t// makes, because having lots of signal listeners could exhaust resources.\n\t//\n\t// When there is an error setting up the watching of files, if we error we\n\t// need to signal the error using a channel as watching files happens\n\t// asynchronously in a goroutine. We also need to be able to signal the\n\t// viceroy process to be killed, and we do that using `s.Signal(os.Kill)` from\n\t// within the relevant error handling blocks in `watchFiles`, where upon the\n\t// below `select` statement will pull the error message from the `failure`\n\t// channel and return it to the user. If we fail to kill the Viceroy process\n\t// then we still want to pull an error from the `failure` channel and so we\n\t// have a separate `select` statement to check for any initial errors prior to\n\t// the Viceroy executable starting and an error occurring in `watchFiles`.\n\tselect {\n\tcase asyncErr := <-failure:\n\t\ts.SignalCh <- syscall.SIGTERM\n\t\treturn asyncErr\n\tcase <-time.After(1 * time.Second):\n\t\t// no-op: allow logic to flow to starting up Viceroy executable.\n\t}\n\n\tif err := s.Exec(); err != nil {\n\t\terrPrefix := \"signal: \"\n\t\terrKilled := \"killed\"\n\t\tif fstruntime.Windows {\n\t\t\terrPrefix = \"exit status\"\n\t\t\terrKilled = errPrefix + \" 1\"\n\t\t}\n\n\t\tif !strings.Contains(err.Error(), errPrefix) {\n\t\t\topts.errLog.Add(err)\n\t\t}\n\t\te := strings.TrimSpace(err.Error())\n\t\tif strings.Contains(e, \"interrupt\") {\n\t\t\treturn fsterr.ErrSignalInterrupt\n\t\t}\n\t\tif strings.Contains(e, errKilled) {\n\t\t\tselect {\n\t\t\tcase asyncErr := <-failure:\n\t\t\t\ts.SignalCh <- syscall.SIGTERM\n\t\t\t\treturn asyncErr\n\t\t\tcase <-restart:\n\t\t\t\ts.SignalCh <- syscall.SIGTERM\n\t\t\t\treturn fsterr.ErrViceroyRestart\n\t\t\tcase <-time.After(1 * time.Second):\n\t\t\t\treturn fsterr.ErrSignalKilled\n\t\t\t}\n\t\t}\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\n// watchFiles watches the language source directory and restarts the viceroy\n// executable when changes are detected.\nfunc watchFiles(root string, gi *ignore.GitIgnore, verbose bool, s *fstexec.Streaming, out io.Writer, restart chan<- bool, failure chan<- error) {\n\twatcher, err := fsnotify.NewWatcher()\n\tif err != nil {\n\t\tsignalErr := s.Signal(os.Kill)\n\t\tif signalErr != nil {\n\t\t\tfailure <- fmt.Errorf(\"failed to stop Viceroy executable while trying to create a fsnotify.Watcher: %w: %w\", signalErr, err)\n\t\t\treturn\n\t\t}\n\t\tfailure <- fmt.Errorf(\"failed to create a fsnotify.Watcher: %w\", err)\n\t\treturn\n\t}\n\tdefer watcher.Close()\n\n\tdone := make(chan bool)\n\tdebounced := debounce.New(1 * time.Second)\n\teventHandler := func(modifiedFile string, _ fsnotify.Op) {\n\t\t// NOTE: We avoid describing the file operation (e.g. created, modified,\n\t\t// deleted, renamed etc) rather than checking the fsnotify.Op iota/enum type\n\t\t// because the output can be confusing depending on the application used to\n\t\t// edit a file.\n\t\t//\n\t\t// For example, modifying a file in Vim might cause the file to be\n\t\t// temporarily copied/renamed and this can cause the watcher to report an\n\t\t// existing file has been 'created' or 'renamed' when from a user's\n\t\t// perspective the file already exists and was only modified.\n\t\ttext.Break(out)\n\t\ttext.Output(out, \"%s Restarting local server (%s)\", text.BoldGreen(\"✓\"), modifiedFile)\n\n\t\t// NOTE: We force closing the watcher by pushing true into a done channel.\n\t\t// We do this because if we didn't, then we'd get an error after one\n\t\t// restart of the viceroy executable: \"os: process already finished\".\n\t\t//\n\t\t// This error happens because the compute.watchFiles() function is\n\t\t// run in a goroutine and so it will keep running with a copy of the\n\t\t// fstexec.Streaming command instance that wraps a process which has\n\t\t// already been terminated.\n\t\tdone <- true\n\n\t\t// NOTE: To be able to force both the current viceroy process signal listener\n\t\t// to close, and to restart the viceroy executable, we need to kill the\n\t\t// process and also send 'true' to a restart channel.\n\t\t//\n\t\t// If we only sent a message to the restart channel, but didn't terminate\n\t\t// the process, then we'd end up in a deadlock because we wouldn't be able\n\t\t// to take a message from the restart channel inside the local() function\n\t\t// because we need to have the process terminate first in order for us to\n\t\t// execute the flushing of channel messages.\n\t\t//\n\t\t// When we stop the signal listener it will internally try to kill the\n\t\t// process and discover it has already been killed and return an error:\n\t\t// `os: process already finished`. This is why we don't do error handling\n\t\t// within (*fstexec.Streaming).MonitorSignalsAsync() as the process could\n\t\t// well be killed already when a user is doing local development with the\n\t\t// --watch flag. The obvious downside to this logic flow is that if the\n\t\t// user is running `compute serve` just to validate the program once, then\n\t\t// there might be an unhandled error when they press Ctrl-c to stop the\n\t\t// serve command from blocking their terminal. That said, this is unlikely\n\t\t// and is a low risk concern.\n\t\terr := s.Signal(os.Kill)\n\t\tif err != nil {\n\t\t\tfailure <- fmt.Errorf(\"failed to stop Viceroy executable while trying to restart the process: %w\", err)\n\t\t\treturn\n\t\t}\n\n\t\trestart <- true\n\t}\n\n\tgo func() {\n\t\tfor {\n\t\t\tselect {\n\t\t\tcase event, ok := <-watcher.Events:\n\t\t\t\tif !ok {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tdebounced(func() {\n\t\t\t\t\teventHandler(event.Name, event.Op)\n\t\t\t\t})\n\t\t\tcase err, ok := <-watcher.Errors:\n\t\t\t\tif !ok {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\ttext.Output(out, \"error event while watching files: %v\", err)\n\t\t\t}\n\t\t}\n\t}()\n\n\tvar buf bytes.Buffer\n\n\t// Walk all directories and files starting from the project's root directory.\n\terr = filepath.WalkDir(root, func(path string, entry fs.DirEntry, err error) error {\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"error configuring watching for file changes: %w\", err)\n\t\t}\n\t\t// If there's no ignore file, we'll default to watching all directories\n\t\t// within the specified top-level directory.\n\t\t//\n\t\t// NOTE: Watching a directory implies watching all files within the root of\n\t\t// the directory. This means we don't need to call Add(path) for each file.\n\t\tif gi == nil && entry.IsDir() {\n\t\t\twatchFile(path, watcher, verbose, &buf)\n\t\t}\n\t\tif gi != nil && !entry.IsDir() && !gi.MatchesPath(path) {\n\t\t\t// If there is an ignore file, we avoid watching directories and instead\n\t\t\t// will only add files that don't match the exclusion patterns defined.\n\t\t\twatchFile(path, watcher, verbose, &buf)\n\t\t}\n\t\treturn nil\n\t})\n\tif err != nil {\n\t\tsignalErr := s.Signal(os.Kill)\n\t\tif signalErr != nil {\n\t\t\tfailure <- fmt.Errorf(\"failed to stop Viceroy executable while trying to walk directory tree for watching files: %w: %w\", signalErr, err)\n\t\t\treturn\n\t\t}\n\t\tfailure <- fmt.Errorf(\"failed to walk directory tree for watching files: %w\", err)\n\t\treturn\n\t}\n\n\tif verbose {\n\t\ttext.Output(out, \"%s\\n\\n\", text.BoldYellow(\"Watching...\"))\n\t\tfmt.Fprintln(out, buf.String()) // IMPORTANT: Avoid text.Output() as it fails to render with large buffer.\n\t\ttext.Break(out)\n\t}\n\n\t<-done\n}\n\n// ignoreFiles returns the specific ignore rules being respected.\n//\n// NOTE: We also ignore the .git directory.\nfunc ignoreFiles(watchDir argparser.OptionalString) *ignore.GitIgnore {\n\tvar patterns []string\n\n\troot := \"\"\n\tif watchDir.WasSet {\n\t\troot = watchDir.Value\n\t\tif !strings.HasPrefix(root, \"/\") {\n\t\t\troot += \"/\"\n\t\t}\n\t}\n\n\tfastlyIgnore := root + \".fastlyignore\"\n\n\t// NOTE: Using a loop to allow for future ignore files to be respected.\n\tfor _, file := range []string{fastlyIgnore} {\n\t\tpatterns = append(patterns, readIgnoreFile(file)...)\n\t}\n\n\tpatterns = append(patterns, \".git/\")\n\n\treturn ignore.CompileIgnoreLines(patterns...)\n}\n\n// readIgnoreFile reads path and splits content into lines.\n//\n// NOTE: If there's an error reading the given path, then we'll return an empty\n// string slice so that the caller can continue to function as expected.\nfunc readIgnoreFile(path string) (lines []string) {\n\t// gosec flagged this:\n\t// G304 (CWE-22): Potential file inclusion via variable\n\t//\n\t// Disabling as the input is either provided by our own package or in the\n\t// case of identifying the user's global git ignore we need to read it from\n\t// their global git configuration.\n\t/* #nosec */\n\tbs, err := os.ReadFile(path)\n\tif err != nil {\n\t\treturn lines\n\t}\n\treturn strings.Split(string(bs), \"\\n\")\n}\n\nfunc watchFile(path string, watcher *fsnotify.Watcher, verbose bool, out io.Writer) {\n\tabsolute, err := filepath.Abs(path)\n\tif err != nil && verbose {\n\t\ttext.Warning(out, \"Unable to convert '%s' to an absolute path\", path)\n\t\treturn\n\t}\n\n\terr = watcher.Add(absolute)\n\tif err != nil {\n\t\ttext.Output(out, \"%s %s\", text.BoldRed(\"✗\"), absolute)\n\t} else if verbose {\n\t\ttext.Output(out, \"%s\", absolute)\n\t}\n}\n\nfunc killProcessTree(pid int) {\n\tprocesses, err := ps.Processes()\n\tif err != nil {\n\t\tlog.Printf(\"failed to list processes: %v\", err)\n\t\treturn\n\t}\n\n\tvar children []int\n\tfor _, p := range processes {\n\t\tif p.PPid() == pid {\n\t\t\tchildren = append(children, p.Pid())\n\t\t}\n\t}\n\n\tfor _, child := range children {\n\t\tkillProcessTree(child)\n\t}\n\n\t_ = killProcess(pid)\n}\n"
  },
  {
    "path": "pkg/commands/compute/serve_test.go",
    "content": "package compute_test\n\nimport (\n\t\"bytes\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/compute\"\n\t\"github.com/fastly/cli/pkg/config\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/github\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/mock\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// TestGetViceroy validates that Viceroy is installed to the appropriate\n// directory.\n//\n// There isn't an executable binary that exists in the test environment, so we\n// expect the spawning of a subprocess (to call `<binary> --version`) to error\n// and subsequently the `installViceroy()` function to be called.\n//\n// The `installViceroy()` function will then think it has downloaded the latest\n// release as we have instructed the mock to provide that behaviour.\n//\n// Subsequently the `os.Rename()` will move the downloaded Viceroy binary,\n// which is just a dummy file created by `testutil.NewEnv`, into the intended\n// destination directory.\nfunc TestGetViceroy(t *testing.T) {\n\twd, err := os.Getwd()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tviceroyBinName := \"foo\"\n\tinstallDirName := \"install\"\n\n\trootdir := testutil.NewEnv(testutil.EnvOpts{\n\t\tT: t,\n\t\tDirs: []string{\n\t\t\tinstallDirName,\n\t\t},\n\t\tWrite: []testutil.FileIO{\n\t\t\t{Src: \"...\", Dst: viceroyBinName},\n\n\t\t\t// NOTE: The reason for creating this file is that in serve.go when it tries\n\t\t\t// to write in-memory data back to disk, although we don't need to validate\n\t\t\t// the contents being written, we don't want the write to fail because no\n\t\t\t// such file existed.\n\t\t\t{Src: \"\", Dst: config.FileName},\n\t\t},\n\t})\n\tinstallDir := filepath.Join(rootdir, installDirName)\n\tbinPath := filepath.Join(rootdir, viceroyBinName)\n\tconfigPath := filepath.Join(rootdir, config.FileName)\n\tdefer os.RemoveAll(rootdir)\n\n\tif err := os.Chdir(rootdir); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer func() {\n\t\t_ = os.Chdir(wd)\n\t}()\n\n\tgithub.InstallDir = installDir\n\n\tvar out bytes.Buffer\n\n\tav := mock.AssetVersioner{\n\t\tAssetVersion:   \"1.2.3\",\n\t\tBinaryFilename: viceroyBinName,\n\t\tDownloadOK:     true,\n\t\tDownloadedFile: binPath,\n\t}\n\n\tvar file config.File\n\n\t// NOTE: We purposefully provide a nonsensical path, which we expect to fail,\n\t// but the function call should fallback to using the stubbed static config\n\t// defined above. We also don't pass stdin, stdout arguments as that\n\t// particular user flow isn't executed in this test case.\n\terr = file.Read(\"example\", strings.NewReader(\"yes\"), &out, fsterr.MockLog{}, false)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tspinner, err := text.NewSpinner(&out)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tmanifestPath := \"fastly.toml\"\n\tserveCommand := &compute.ServeCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: &global.Data{\n\t\t\t\tConfig:     file,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tErrLog:     fsterr.MockLog{},\n\t\t\t},\n\t\t},\n\t\tForceCheckViceroyLatest: false,\n\t\tViceroyBinPath:          \"\",\n\t\tViceroyVersioner:        av,\n\t}\n\t_, err = serveCommand.GetViceroy(spinner, &out, manifestPath)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif !strings.Contains(out.String(), \"Fetching Viceroy release: \") {\n\t\tt.Fatalf(\"expected file to be downloaded successfully\")\n\t}\n\n\tmovedPath := filepath.Join(installDir, viceroyBinName)\n\n\tif _, err := os.Stat(movedPath); err != nil {\n\t\tt.Fatalf(\"binary was not moved to the install directory: %s\", err)\n\t}\n}\n"
  },
  {
    "path": "pkg/commands/compute/serve_unix.go",
    "content": "//go:build !windows\n\npackage compute\n\nimport (\n\t\"syscall\"\n)\n\nfunc killProcess(pid int) error {\n\treturn syscall.Kill(pid, syscall.SIGKILL)\n}\n"
  },
  {
    "path": "pkg/commands/compute/serve_windows.go",
    "content": "//go:build windows\n\npackage compute\n\nimport (\n\t\"os/exec\"\n\t\"strconv\"\n)\n\nfunc killProcess(pid int) error {\n\t// This is safe as the pid is obtained internally\n\t// nolint:gosec\n\treturn exec.Command(\"taskkill\", \"/F\", \"/T\", \"/PID\", strconv.Itoa(pid)).Run()\n}\n"
  },
  {
    "path": "pkg/commands/compute/setup/backend.go",
    "content": "package setup\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"strconv\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/api\"\n\t\"github.com/fastly/cli/pkg/commands/service/backend\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/manifest\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// Backends represents the service state related to backends defined within the\n// fastly.toml [setup] configuration.\n//\n// NOTE: It implements the setup.Interface interface.\ntype Backends struct {\n\t// Public\n\tAPIClient      api.Interface\n\tAcceptDefaults bool\n\tNonInteractive bool\n\tSpinner        text.Spinner\n\tServiceID      string\n\tServiceVersion int\n\tSetup          map[string]*manifest.SetupBackend\n\tStdin          io.Reader\n\tStdout         io.Writer\n\n\t// Private\n\trequired []Backend\n}\n\n// Backend represents the configuration parameters for creating a backend via\n// the API client.\ntype Backend struct {\n\tAddress         string\n\tName            string\n\tOverrideHost    string\n\tPort            int\n\tSSLCertHostname string\n\tSSLSNIHostname  string\n}\n\n// Configure prompts the user for specific values related to the service resource.\nfunc (b *Backends) Configure() error {\n\tif b.Predefined() {\n\t\treturn b.checkPredefined()\n\t}\n\treturn b.promptForBackend()\n}\n\n// Create calls the relevant API to create the service resource(s).\nfunc (b *Backends) Create() error {\n\tif b.Spinner == nil {\n\t\treturn errors.RemediationError{\n\t\t\tInner:       fmt.Errorf(\"internal logic error: no spinner configured for setup.Backends\"),\n\t\t\tRemediation: errors.BugRemediation,\n\t\t}\n\t}\n\n\tfor _, bk := range b.required {\n\t\t// Avoids range-loop variable issue (i.e. var is reused across iterations).\n\t\tbk := bk\n\n\t\tmsg := fmt.Sprintf(\"Creating backend '%s' (host: %s, port: %d)\", bk.Name, bk.Address, bk.Port)\n\n\t\tif !b.isOriginless() {\n\t\t\terr := b.Spinner.Start()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tb.Spinner.Message(msg + \"...\")\n\t\t}\n\n\t\topts := &fastly.CreateBackendInput{\n\t\t\tServiceID:      b.ServiceID,\n\t\t\tServiceVersion: b.ServiceVersion,\n\t\t\tName:           &bk.Name,\n\t\t\tAddress:        &bk.Address,\n\t\t\tPort:           &bk.Port,\n\t\t}\n\n\t\tif bk.OverrideHost != \"\" {\n\t\t\topts.OverrideHost = &bk.OverrideHost\n\t\t}\n\t\tif bk.SSLCertHostname != \"\" {\n\t\t\topts.SSLCertHostname = &bk.SSLCertHostname\n\t\t}\n\t\tif bk.SSLSNIHostname != \"\" {\n\t\t\topts.SSLSNIHostname = &bk.SSLSNIHostname\n\t\t}\n\n\t\t_, err := b.APIClient.CreateBackend(context.TODO(), opts)\n\t\tif err != nil {\n\t\t\tif !b.isOriginless() {\n\t\t\t\terr = fmt.Errorf(\"error creating backend: %w\", err)\n\t\t\t\tb.Spinner.StopFailMessage(msg)\n\t\t\t\tspinErr := b.Spinner.StopFail()\n\t\t\t\tif spinErr != nil {\n\t\t\t\t\treturn fmt.Errorf(text.SpinnerErrWrapper, spinErr, err)\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn fmt.Errorf(\"error configuring the service: %w\", err)\n\t\t}\n\n\t\tif !b.isOriginless() {\n\t\t\tb.Spinner.StopMessage(msg)\n\t\t\terr = b.Spinner.Stop()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// Predefined indicates if the service resource has been specified within the\n// fastly.toml file using a [setup] configuration block.\nfunc (b *Backends) Predefined() bool {\n\treturn len(b.Setup) > 0\n}\n\n// isOriginless indicates if the required backend is originless.\nfunc (b *Backends) isOriginless() bool {\n\treturn len(b.required) == 1 && b.required[0].Name == \"originless\" && b.required[0].Address == \"127.0.0.1\"\n}\n\n// checkPredefined identifies specific backends that are required but missing\n// from the user's service (based on the [setup.backends] configuration).\nfunc (b *Backends) checkPredefined() error {\n\tvar i int\n\tfor name, settings := range b.Setup {\n\t\tif !b.AcceptDefaults && !b.NonInteractive {\n\t\t\tif i > 0 {\n\t\t\t\ttext.Break(b.Stdout)\n\t\t\t}\n\t\t\ti++\n\t\t\ttext.Output(b.Stdout, \"Configure a backend called '%s'\", name)\n\t\t\tif settings.Description != \"\" {\n\t\t\t\ttext.Output(b.Stdout, settings.Description)\n\t\t\t}\n\t\t\ttext.Break(b.Stdout)\n\t\t}\n\n\t\tvar (\n\t\t\taddr string\n\t\t\terr  error\n\t\t)\n\n\t\tdefaultAddress := \"127.0.0.1\"\n\t\tif settings.Address != \"\" {\n\t\t\tdefaultAddress = settings.Address\n\t\t}\n\n\t\tprompt := text.Prompt(fmt.Sprintf(\"Hostname or IP address: [%s] \", defaultAddress))\n\n\t\tif !b.AcceptDefaults && !b.NonInteractive {\n\t\t\taddr, err = text.Input(b.Stdout, prompt, b.Stdin, b.validateAddress)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"error reading prompt input: %w\", err)\n\t\t\t}\n\t\t}\n\t\tif addr == \"\" {\n\t\t\taddr = defaultAddress\n\t\t}\n\n\t\tport := int(443)\n\t\tif settings.Port > 0 {\n\t\t\tport = settings.Port\n\t\t}\n\t\tif !b.AcceptDefaults && !b.NonInteractive {\n\t\t\tinput, err := text.Input(b.Stdout, text.Prompt(fmt.Sprintf(\"Port: [%d] \", port)), b.Stdin)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"error reading prompt input: %w\", err)\n\t\t\t}\n\t\t\tif input != \"\" {\n\t\t\t\tif i, err := strconv.Atoi(input); err != nil {\n\t\t\t\t\ttext.Warning(b.Stdout, fmt.Sprintf(\"error converting prompt input, using default port number (%d)\\n\\n\", port))\n\t\t\t\t} else {\n\t\t\t\t\tport = i\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\toverrideHost, sslSNIHostname, sslCertHostname := backend.SetBackendHostDefaults(addr)\n\t\tb.required = append(b.required, Backend{\n\t\t\tAddress:         addr,\n\t\t\tName:            name,\n\t\t\tOverrideHost:    overrideHost,\n\t\t\tPort:            port,\n\t\t\tSSLCertHostname: sslCertHostname,\n\t\t\tSSLSNIHostname:  sslSNIHostname,\n\t\t})\n\t}\n\n\treturn nil\n}\n\n// promptForBackend issues a prompt requesting one or more Backends that will\n// be created within the user's service.\nfunc (b *Backends) promptForBackend() error {\n\tif b.AcceptDefaults || b.NonInteractive {\n\t\tb.required = append(b.required, b.createOriginlessBackend())\n\t\treturn nil\n\t}\n\n\tvar i int\n\tfor {\n\t\tif i > 0 {\n\t\t\ttext.Break(b.Stdout)\n\t\t}\n\t\ti++\n\n\t\taddr, err := text.Input(b.Stdout, text.Prompt(\"Backend (hostname or IP address, or leave blank to stop adding backends): \"), b.Stdin, b.validateAddress)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"error reading prompt input %w\", err)\n\t\t}\n\n\t\t// This block short-circuits the endless prompt loop\n\t\tif addr == \"\" {\n\t\t\tif len(b.required) == 0 {\n\t\t\t\tb.required = append(b.required, b.createOriginlessBackend())\n\t\t\t}\n\t\t\treturn nil\n\t\t}\n\n\t\tport := int(443)\n\t\tinput, err := text.Input(b.Stdout, text.Prompt(fmt.Sprintf(\"Backend port number: [%d] \", port)), b.Stdin)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"error reading prompt input: %w\", err)\n\t\t}\n\t\tif input != \"\" {\n\t\t\tif portnumber, err := strconv.Atoi(input); err != nil {\n\t\t\t\ttext.Warning(b.Stdout, fmt.Sprintf(\"error converting prompt input, using default port number (%d)\\n\\n\", port))\n\t\t\t} else {\n\t\t\t\tport = portnumber\n\t\t\t}\n\t\t}\n\n\t\tdefaultName := fmt.Sprintf(\"backend_%d\", i)\n\t\tname, err := text.Input(b.Stdout, text.Prompt(fmt.Sprintf(\"Backend name: [%s] \", defaultName)), b.Stdin)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"error reading prompt input %w\", err)\n\t\t}\n\t\tif name == \"\" {\n\t\t\tname = defaultName\n\t\t}\n\n\t\toverrideHost, sslSNIHostname, sslCertHostname := backend.SetBackendHostDefaults(addr)\n\t\tb.required = append(b.required, Backend{\n\t\t\tAddress:         addr,\n\t\t\tName:            name,\n\t\t\tOverrideHost:    overrideHost,\n\t\t\tPort:            port,\n\t\t\tSSLCertHostname: sslCertHostname,\n\t\t\tSSLSNIHostname:  sslSNIHostname,\n\t\t})\n\t}\n}\n\n// createOriginlessBackend returns a Backend instance configured to the\n// localhost settings expected of an 'originless' backend.\nfunc (b *Backends) createOriginlessBackend() Backend {\n\tvar bk Backend\n\tbk.Name = \"originless\"\n\tbk.Address = \"127.0.0.1\"\n\tbk.Port = int(80)\n\treturn bk\n}\n\n// validateAddress checks the user entered address is a valid hostname or IP.\nfunc (b *Backends) validateAddress(input string) error {\n\tvar isHost bool\n\tif _, err := net.LookupHost(input); err == nil {\n\t\tisHost = true\n\t}\n\tvar isAddr bool\n\tif _, err := net.LookupAddr(input); err == nil {\n\t\tisAddr = true\n\t}\n\tisEmpty := input == \"\"\n\tif !isEmpty && !isHost && !isAddr {\n\t\treturn fmt.Errorf(`must be a valid hostname, IPv4, or IPv6 address`)\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/compute/setup/config_store.go",
    "content": "package setup\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/api\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/manifest\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// ConfigStores represents the service state related to config stores defined\n// within the fastly.toml [setup] configuration.\n//\n// NOTE: It implements the setup.Interface interface.\ntype ConfigStores struct {\n\t// Public\n\tAPIClient      api.Interface\n\tAcceptDefaults bool\n\tNonInteractive bool\n\tSpinner        text.Spinner\n\tServiceID      string\n\tServiceVersion int\n\tSetup          map[string]*manifest.SetupConfigStore\n\tStdin          io.Reader\n\tStdout         io.Writer\n\n\t// Private\n\trequired []ConfigStore\n}\n\n// ConfigStore represents the configuration parameters for creating a config\n// store via the API client.\ntype ConfigStore struct {\n\tName              string\n\tItems             []ConfigStoreItem\n\tLinkExistingStore bool\n\tExistingStoreID   string\n}\n\n// ConfigStoreItem represents the configuration parameters for creating config\n// store items via the API client.\ntype ConfigStoreItem struct {\n\tKey   string\n\tValue string\n}\n\n// Configure prompts the user for specific values related to the service resource.\nfunc (o *ConfigStores) Configure() error {\n\texistingStores, err := o.APIClient.ListConfigStores(context.TODO(), &fastly.ListConfigStoresInput{})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfor name, settings := range o.Setup {\n\t\tvar (\n\t\t\texistingStoreID   string\n\t\t\tlinkExistingStore bool\n\t\t)\n\n\t\tfor _, store := range existingStores {\n\t\t\tif store.Name == name {\n\t\t\t\tif o.AcceptDefaults || o.NonInteractive {\n\t\t\t\t\tlinkExistingStore = true\n\t\t\t\t\texistingStoreID = store.StoreID\n\t\t\t\t} else {\n\t\t\t\t\ttext.Warning(o.Stdout, \"\\nA Config Store called '%s' already exists. If you use this store, then this implies that any keys defined in your setup configuration will either be newly created or will update an existing one. To avoid updating an existing key, then stop the command now and edit the setup configuration before re-running the deployment process\\n\\n\", name)\n\t\t\t\t\tprompt := text.Prompt(\"Use a different store name (or leave empty to use the existing store): \")\n\t\t\t\t\tvalue, err := text.Input(o.Stdout, prompt, o.Stdin)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn fmt.Errorf(\"error reading prompt input: %w\", err)\n\t\t\t\t\t}\n\t\t\t\t\tif value == \"\" {\n\t\t\t\t\t\tlinkExistingStore = true\n\t\t\t\t\t\texistingStoreID = store.StoreID\n\t\t\t\t\t} else {\n\t\t\t\t\t\tname = value\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif !o.AcceptDefaults && !o.NonInteractive {\n\t\t\ttext.Output(o.Stdout, \"\\nConfiguring config store '%s'\", name)\n\t\t\tif settings.Description != \"\" {\n\t\t\t\ttext.Output(o.Stdout, settings.Description)\n\t\t\t}\n\t\t}\n\n\t\tvar items []ConfigStoreItem\n\n\t\tfor key, item := range settings.Items {\n\t\t\tdv := \"example\"\n\t\t\tif item.Value != \"\" {\n\t\t\t\tdv = item.Value\n\t\t\t}\n\t\t\tprompt := text.Prompt(fmt.Sprintf(\"Value: [%s] \", dv))\n\n\t\t\tvar (\n\t\t\t\tvalue string\n\t\t\t\terr   error\n\t\t\t)\n\n\t\t\tif !o.AcceptDefaults && !o.NonInteractive {\n\t\t\t\ttext.Output(o.Stdout, \"\\nCreate a config store key called '%s'\", key)\n\t\t\t\tif item.Description != \"\" {\n\t\t\t\t\ttext.Output(o.Stdout, item.Description)\n\t\t\t\t}\n\t\t\t\ttext.Break(o.Stdout)\n\n\t\t\t\tvalue, err = text.Input(o.Stdout, prompt, o.Stdin)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn fmt.Errorf(\"error reading prompt input: %w\", err)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif value == \"\" {\n\t\t\t\tvalue = dv\n\t\t\t}\n\n\t\t\titems = append(items, ConfigStoreItem{\n\t\t\t\tKey:   key,\n\t\t\t\tValue: value,\n\t\t\t})\n\t\t}\n\n\t\to.required = append(o.required, ConfigStore{\n\t\t\tName:              name,\n\t\t\tItems:             items,\n\t\t\tLinkExistingStore: linkExistingStore,\n\t\t\tExistingStoreID:   existingStoreID,\n\t\t})\n\t}\n\n\treturn nil\n}\n\n// Create calls the relevant API to create the service resource(s).\nfunc (o *ConfigStores) Create() error {\n\tif o.Spinner == nil {\n\t\treturn errors.RemediationError{\n\t\t\tInner:       fmt.Errorf(\"internal logic error: no spinner configured for setup.ConfigStores\"),\n\t\t\tRemediation: errors.BugRemediation,\n\t\t}\n\t}\n\n\tfor _, configStore := range o.required {\n\t\tvar (\n\t\t\terr error\n\t\t\tcs  *fastly.ConfigStore\n\t\t)\n\n\t\tif configStore.LinkExistingStore {\n\t\t\terr = o.Spinner.Process(fmt.Sprintf(\"Retrieving existing Config Store '%s'\", configStore.Name), func(_ *text.SpinnerWrapper) error {\n\t\t\t\tcs, err = o.APIClient.GetConfigStore(context.TODO(), &fastly.GetConfigStoreInput{\n\t\t\t\t\tStoreID: configStore.ExistingStoreID,\n\t\t\t\t})\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn fmt.Errorf(\"failed to get existing store '%s': %w\", configStore.Name, err)\n\t\t\t\t}\n\t\t\t\treturn nil\n\t\t\t})\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t} else {\n\t\t\terr = o.Spinner.Process(fmt.Sprintf(\"Creating config store '%s'\", configStore.Name), func(_ *text.SpinnerWrapper) error {\n\t\t\t\tcs, err = o.APIClient.CreateConfigStore(context.TODO(), &fastly.CreateConfigStoreInput{\n\t\t\t\t\tName: configStore.Name,\n\t\t\t\t})\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn fmt.Errorf(\"error creating config store: %w\", err)\n\t\t\t\t}\n\t\t\t\treturn nil\n\t\t\t})\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\n\t\tif len(configStore.Items) > 0 {\n\t\t\tfor _, item := range configStore.Items {\n\t\t\t\terr = o.Spinner.Process(fmt.Sprintf(\"Creating config store item '%s'\", item.Key), func(_ *text.SpinnerWrapper) error {\n\t\t\t\t\t_, err = o.APIClient.UpdateConfigStoreItem(context.TODO(), &fastly.UpdateConfigStoreItemInput{\n\t\t\t\t\t\tUpsert:  true, // Use upsert to avoid conflicts when reusing a starter kit.\n\t\t\t\t\t\tStoreID: cs.StoreID,\n\t\t\t\t\t\tKey:     item.Key,\n\t\t\t\t\t\tValue:   item.Value,\n\t\t\t\t\t})\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn fmt.Errorf(\"error creating config store item: %w\", err)\n\t\t\t\t\t}\n\t\t\t\t\treturn nil\n\t\t\t\t})\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// IMPORTANT: We need to link the config store to the Compute Service.\n\t\terr = o.Spinner.Process(fmt.Sprintf(\"Creating resource link between service and config store '%s'...\", cs.Name), func(_ *text.SpinnerWrapper) error {\n\t\t\t_, err = o.APIClient.CreateResource(context.TODO(), &fastly.CreateResourceInput{\n\t\t\t\tServiceID:      o.ServiceID,\n\t\t\t\tServiceVersion: o.ServiceVersion,\n\t\t\t\tName:           fastly.ToPointer(cs.Name),\n\t\t\t\tResourceID:     fastly.ToPointer(cs.StoreID),\n\t\t\t})\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"error creating resource link between the service '%s' and the config store '%s': %w\", o.ServiceID, configStore.Name, err)\n\t\t\t}\n\t\t\treturn nil\n\t\t})\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// Predefined indicates if the service resource has been specified within the\n// fastly.toml file using a [setup] configuration block.\nfunc (o *ConfigStores) Predefined() bool {\n\treturn len(o.Setup) > 0\n}\n"
  },
  {
    "path": "pkg/commands/compute/setup/doc.go",
    "content": "// Package setup contains logic for managing the creation of resources that are\n// defined within the fastly.toml manifest file.\npackage setup\n"
  },
  {
    "path": "pkg/commands/compute/setup/domain.go",
    "content": "package setup\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"regexp\"\n\t\"strings\"\n\n\tpetname \"github.com/dustinkirkland/golang-petname\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/api\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\nconst defaultTopLevelDomain = \"edgecompute.app\"\n\nvar domainNameRegEx = regexp.MustCompile(`(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\\.)+[a-z0-9][a-z0-9-]{0,61}[a-z0-9]`)\n\n// Domains represents the service state related to domains.\n//\n// NOTE: It implements the setup.Interface interface.\ntype Domains struct {\n\t// Public\n\tAPIClient       api.Interface\n\tAcceptDefaults  bool\n\tNoDefaultDomain bool\n\tNonInteractive  bool\n\tPackageDomain   string\n\tSpinner         text.Spinner\n\tRetryLimit      int\n\tServiceID       string\n\tServiceVersion  int\n\tStdin           io.Reader\n\tStdout          io.Writer\n\tVerbose         bool\n\n\t// Private\n\tavailable []*fastly.Domain\n\tmissing   bool\n\trequired  []Domain\n}\n\n// Domain represents the configuration parameters for creating a domain via the\n// API client.\ntype Domain struct {\n\tName string\n}\n\n// Configure prompts the user for specific values related to the service resource.\n//\n// NOTE: If --domain flag is used we'll use that as the domain to create.\nfunc (d *Domains) Configure() error {\n\t// Don't generate a domain if --no-default-domain is set and no domain is provided\n\tif d.NoDefaultDomain && d.PackageDomain == \"\" {\n\t\td.missing = false\n\t\treturn nil\n\t}\n\n\t// PackageDomain is the --domain flag value.\n\tif d.PackageDomain != \"\" {\n\t\td.required = append(d.required, Domain{\n\t\t\tName: d.PackageDomain,\n\t\t})\n\t\treturn nil\n\t}\n\n\tdefaultDomain := generateDomainName()\n\n\tvar (\n\t\tdomain string\n\t\terr    error\n\t)\n\tif !d.AcceptDefaults && !d.NonInteractive {\n\t\ttext.Break(d.Stdout)\n\t\tdomain, err = text.Input(d.Stdout, text.Prompt(fmt.Sprintf(\"Domain: [%s] \", defaultDomain)), d.Stdin, d.validateDomain)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"error reading input %w\", err)\n\t\t}\n\t\ttext.Break(d.Stdout)\n\t}\n\n\tif domain == \"\" {\n\t\tdomain = defaultDomain\n\t}\n\td.required = append(d.required, Domain{\n\t\tName: domain,\n\t})\n\n\treturn nil\n}\n\n// Create calls the relevant API to create the service resource(s).\nfunc (d *Domains) Create() error {\n\tif d.Spinner == nil {\n\t\treturn errors.RemediationError{\n\t\t\tInner:       fmt.Errorf(\"internal logic error: no spinner configured for setup.Domains\"),\n\t\t\tRemediation: errors.BugRemediation,\n\t\t}\n\t}\n\n\tfor _, domain := range d.required {\n\t\tif err := d.createDomain(domain.Name, 1); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// Missing indicates if there are missing resources that need to be created.\nfunc (d *Domains) Missing() bool {\n\treturn d.missing || len(d.required) > 0\n}\n\n// Predefined indicates if the service resource has been specified within the\n// fastly.toml file using a [setup] configuration block.\n//\n// NOTE: Domains are not configurable via the fastly.toml [setup] and so this\n// becomes a no-op function that returned a canned response.\nfunc (d *Domains) Predefined() bool {\n\treturn false\n}\n\n// Validate checks if the service has the required resources.\n// For a domain resource, we simply check there is at least one domain.\n//\n// NOTE: It should set an internal `missing` field (boolean) accordingly so that\n// the Missing() method can report the state of the resource.\nfunc (d *Domains) Validate() error {\n\tavailable, err := d.APIClient.ListDomains(context.TODO(), &fastly.ListDomainsInput{\n\t\tServiceID:      d.ServiceID,\n\t\tServiceVersion: d.ServiceVersion,\n\t})\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error fetching service domains: %w\", err)\n\t}\n\td.available = available\n\tif len(d.available) == 0 {\n\t\t// Only mark as missing if we're not intentionally skipping domain creation\n\t\tif !d.NoDefaultDomain || d.PackageDomain != \"\" {\n\t\t\td.missing = true\n\t\t}\n\t}\n\treturn nil\n}\n\n// validateDomain checks the user entered domain is valid.\n//\n// NOTE: An empty value is allowed so that a default domain can be utilised.\nfunc (d *Domains) validateDomain(input string) error {\n\tif input == \"\" {\n\t\treturn nil\n\t}\n\tif !domainNameRegEx.MatchString(input) {\n\t\treturn fmt.Errorf(\"must be valid domain name\")\n\t}\n\treturn nil\n}\n\nfunc (d *Domains) createDomain(name string, attempt int) error {\n\tif !d.AcceptDefaults && !d.NonInteractive {\n\t\ttext.Break(d.Stdout)\n\t}\n\n\terr := d.Spinner.Start()\n\tif err != nil {\n\t\treturn err\n\t}\n\tmsg := fmt.Sprintf(\"Creating domain '%s'\", name)\n\td.Spinner.Message(msg + \"...\")\n\n\t_, err = d.APIClient.CreateDomain(context.TODO(), &fastly.CreateDomainInput{\n\t\tServiceID:      d.ServiceID,\n\t\tServiceVersion: d.ServiceVersion,\n\t\tName:           &name,\n\t})\n\tif err != nil {\n\t\terr = fmt.Errorf(\"error creating domain: %w\", err)\n\n\t\t// We have to stop the ticker so we can now prompt the user.\n\t\td.Spinner.StopFailMessage(msg)\n\t\tspinErr := d.Spinner.StopFail()\n\t\tif spinErr != nil {\n\t\t\treturn fmt.Errorf(text.SpinnerErrWrapper, spinErr, err)\n\t\t}\n\n\t\tif attempt > d.RetryLimit {\n\t\t\treturn fmt.Errorf(\"too many attempts\")\n\t\t}\n\n\t\tif e, ok := err.(*fastly.HTTPError); ok {\n\t\t\tif e.StatusCode == http.StatusBadRequest {\n\t\t\t\tfor _, he := range e.Errors {\n\t\t\t\t\t// NOTE: In case the domain is already used by another customer.\n\t\t\t\t\t// We'll give the user one additional chance to correct the domain.\n\t\t\t\t\tif strings.Contains(he.Detail, \"by another customer\") {\n\t\t\t\t\t\tvar domain string\n\t\t\t\t\t\tdefaultDomain := generateDomainName()\n\t\t\t\t\t\tif !d.AcceptDefaults && !d.NonInteractive {\n\t\t\t\t\t\t\ttext.Break(d.Stdout)\n\t\t\t\t\t\t\tdomain, err = text.Input(d.Stdout, text.Prompt(fmt.Sprintf(\"Domain already taken, please choose another (attempt %d of %d): [%s] \", attempt, d.RetryLimit, defaultDomain)), d.Stdin, d.validateDomain)\n\t\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\t\treturn fmt.Errorf(\"error reading input %w\", err)\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\ttext.Break(d.Stdout)\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif domain == \"\" {\n\t\t\t\t\t\t\tdomain = defaultDomain\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn d.createDomain(domain, attempt+1)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn err\n\t}\n\n\td.Spinner.StopMessage(msg)\n\treturn d.Spinner.Stop()\n}\n\nfunc generateDomainName() string {\n\t// IMPORTANT: go1.20 deprecates rand.Seed\n\t// The global random number generator (RNG) is now automatically seeded.\n\t// If not seeded, the same domain name is repeated on each run.\n\t// If reverting CLI compilation to using <go1.20 then add the following line:\n\t// rand.Seed(time.Now().UnixNano())\n\treturn fmt.Sprintf(\"%s.%s\", petname.Generate(3, \"-\"), defaultTopLevelDomain)\n}\n"
  },
  {
    "path": "pkg/commands/compute/setup/interface.go",
    "content": "package setup\n\n// Interface represents the behaviour of a [setup] resource.\ntype Interface interface {\n\t// Configure prompts the user for specific values related to the service resource.\n\tConfigure() error\n\n\t// Create calls the relevant API to create the service resource(s).\n\tCreate() error\n\n\t// Missing indicates if there are missing resources that need to be\n\t// configured and/or created.\n\tMissing() bool\n\n\t// Predefined indicates if the service resource has been specified within the\n\t// fastly.toml file using a [setup] configuration block.\n\tPredefined() bool\n\n\t// Validate checks if the service has the required resources.\n\tValidate() error\n}\n"
  },
  {
    "path": "pkg/commands/compute/setup/kv_store.go",
    "content": "package setup\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"path/filepath\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/api\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/manifest\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// KVStores represents the service state related to KV Stores defined\n// within the fastly.toml [setup] configuration.\n//\n// NOTE: It implements the setup.Interface interface.\ntype KVStores struct {\n\t// Public\n\tAPIClient      api.Interface\n\tAcceptDefaults bool\n\tNonInteractive bool\n\tSpinner        text.Spinner\n\tServiceID      string\n\tServiceVersion int\n\tSetup          map[string]*manifest.SetupKVStore\n\tStdin          io.Reader\n\tStdout         io.Writer\n\n\t// Private\n\trequired []KVStore\n}\n\n// KVStore represents the configuration parameters for creating a KV Store via\n// the API client.\ntype KVStore struct {\n\tName              string\n\tItems             []KVStoreItem\n\tLinkExistingStore bool\n\tExistingStoreID   string\n}\n\n// KVStoreItem represents the configuration parameters for creating KV Store\n// items via the API client.\ntype KVStoreItem struct {\n\tKey   string\n\tValue string\n\tBody  fastly.LengthReader\n}\n\n// Configure prompts the user for specific values related to the service resource.\nfunc (o *KVStores) Configure() error {\n\tvar (\n\t\tcursor         string\n\t\texistingStores []fastly.KVStore\n\t)\n\n\tfor {\n\t\tkvs, err := o.APIClient.ListKVStores(context.TODO(), &fastly.ListKVStoresInput{\n\t\t\tCursor: cursor,\n\t\t})\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif kvs != nil {\n\t\t\texistingStores = append(existingStores, kvs.Data...)\n\t\t\tif cur, ok := kvs.Meta[\"next_cursor\"]; ok && cur != \"\" && cur != cursor {\n\t\t\t\tcursor = cur\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tbreak\n\t\t}\n\t}\n\n\tfor name, settings := range o.Setup {\n\t\tvar (\n\t\t\texistingStoreID   string\n\t\t\tlinkExistingStore bool\n\t\t)\n\n\t\tfor _, store := range existingStores {\n\t\t\tif store.Name == name {\n\t\t\t\tif o.AcceptDefaults || o.NonInteractive {\n\t\t\t\t\tlinkExistingStore = true\n\t\t\t\t\texistingStoreID = store.StoreID\n\t\t\t\t} else {\n\t\t\t\t\ttext.Warning(o.Stdout, \"\\nA KV Store called '%s' already exists\\n\\n\", name)\n\t\t\t\t\tprompt := text.Prompt(\"Use a different store name (or leave empty to use the existing store): \")\n\t\t\t\t\tvalue, err := text.Input(o.Stdout, prompt, o.Stdin)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn fmt.Errorf(\"error reading prompt input: %w\", err)\n\t\t\t\t\t}\n\t\t\t\t\tif value == \"\" {\n\t\t\t\t\t\tlinkExistingStore = true\n\t\t\t\t\t\texistingStoreID = store.StoreID\n\t\t\t\t\t} else {\n\t\t\t\t\t\tname = value\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif !o.AcceptDefaults && !o.NonInteractive {\n\t\t\ttext.Output(o.Stdout, \"\\nConfiguring KV Store '%s'\", name)\n\t\t\tif settings.Description != \"\" {\n\t\t\t\ttext.Output(o.Stdout, settings.Description)\n\t\t\t}\n\t\t}\n\n\t\tvar items []KVStoreItem\n\n\t\t// Handle top-level file field for bulk loading\n\t\tif settings.File != \"\" {\n\t\t\tif len(settings.Items) > 0 {\n\t\t\t\treturn errors.RemediationError{\n\t\t\t\t\tInner:       fmt.Errorf(\"invalid config: both 'file' and 'items' were set\"),\n\t\t\t\t\tRemediation: fmt.Sprintf(\"Edit the [setup.kv_stores.%s] configuration to use either 'file' or 'items', not both\", name),\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tfileItems, err := loadKVStoreFile(settings.File)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"failed to load KV Store file '%s': %w\", settings.File, err)\n\t\t\t}\n\t\t\titems = fileItems\n\t\t}\n\n\t\tfor key, item := range settings.Items {\n\t\t\tif item.Value != \"\" && item.File != \"\" {\n\t\t\t\treturn errors.RemediationError{\n\t\t\t\t\tInner:       fmt.Errorf(\"invalid config: both 'value' and 'file' were set\"),\n\t\t\t\t\tRemediation: fmt.Sprintf(\"Edit the [setup.kv_stores.%s.items.%s] configuration to use either 'value' or 'file', not both\", name, key),\n\t\t\t\t}\n\t\t\t}\n\t\t\tpromptMessage := \"Value\"\n\t\t\tdv := \"example\"\n\t\t\tif item.Value != \"\" {\n\t\t\t\tdv = item.Value\n\t\t\t}\n\t\t\tif item.File != \"\" {\n\t\t\t\tpromptMessage = \"File\"\n\t\t\t\tdv = item.File\n\t\t\t}\n\t\t\tprompt := text.Prompt(fmt.Sprintf(\"%s: [%s] \", promptMessage, dv))\n\n\t\t\tvar (\n\t\t\t\tvalue string\n\t\t\t\terr   error\n\t\t\t)\n\n\t\t\tif !o.AcceptDefaults && !o.NonInteractive {\n\t\t\t\ttext.Output(o.Stdout, \"\\nCreate a KV Store key called '%s'\", key)\n\t\t\t\tif item.Description != \"\" {\n\t\t\t\t\ttext.Output(o.Stdout, item.Description)\n\t\t\t\t}\n\t\t\t\ttext.Break(o.Stdout)\n\n\t\t\t\tvalue, err = text.Input(o.Stdout, prompt, o.Stdin)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn fmt.Errorf(\"error reading prompt input: %w\", err)\n\t\t\t\t}\n\t\t\t}\n\t\t\tif value == \"\" {\n\t\t\t\tvalue = dv\n\t\t\t}\n\n\t\t\tvar f *os.File\n\t\t\tif item.File != \"\" {\n\t\t\t\tabs, err := filepath.Abs(item.File)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn fmt.Errorf(\"failed to construct absolute path for '%s': %w\", item.File, err)\n\t\t\t\t}\n\t\t\t\t// G304 (CWE-22): Potential file inclusion via variable\n\t\t\t\t// Disabling as we trust the source of the variable.\n\t\t\t\t// #nosec\n\t\t\t\tf, err = os.Open(abs)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn fmt.Errorf(\"failed to open file '%s': %w\", abs, err)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tkvsi := KVStoreItem{\n\t\t\t\tKey: key,\n\t\t\t}\n\t\t\tif item.File != \"\" && f != nil {\n\t\t\t\tlr, err := fastly.FileLengthReader(f)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn fmt.Errorf(\"failed to convert file to a LengthReader: %w\", err)\n\t\t\t\t}\n\t\t\t\tkvsi.Body = lr\n\t\t\t} else {\n\t\t\t\tkvsi.Value = value\n\t\t\t}\n\t\t\titems = append(items, kvsi)\n\t\t}\n\n\t\to.required = append(o.required, KVStore{\n\t\t\tName:              name,\n\t\t\tItems:             items,\n\t\t\tLinkExistingStore: linkExistingStore,\n\t\t\tExistingStoreID:   existingStoreID,\n\t\t})\n\t}\n\n\treturn nil\n}\n\n// Create calls the relevant API to create the service resource(s).\nfunc (o *KVStores) Create() error {\n\tif o.Spinner == nil {\n\t\treturn errors.RemediationError{\n\t\t\tInner:       fmt.Errorf(\"internal logic error: no spinner configured for setup.KVStores\"),\n\t\t\tRemediation: errors.BugRemediation,\n\t\t}\n\t}\n\n\tfor _, kvStore := range o.required {\n\t\tvar (\n\t\t\terr   error\n\t\t\tstore *fastly.KVStore\n\t\t)\n\n\t\tif kvStore.LinkExistingStore {\n\t\t\terr = o.Spinner.Process(fmt.Sprintf(\"Retrieving existing KV Store '%s'\", kvStore.Name), func(_ *text.SpinnerWrapper) error {\n\t\t\t\tstore, err = o.APIClient.GetKVStore(context.TODO(), &fastly.GetKVStoreInput{\n\t\t\t\t\tStoreID: kvStore.ExistingStoreID,\n\t\t\t\t})\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn fmt.Errorf(\"failed to get existing store '%s': %w\", kvStore.Name, err)\n\t\t\t\t}\n\t\t\t\treturn nil\n\t\t\t})\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t} else {\n\t\t\terr = o.Spinner.Process(fmt.Sprintf(\"Creating KV Store '%s'\", kvStore.Name), func(_ *text.SpinnerWrapper) error {\n\t\t\t\tstore, err = o.APIClient.CreateKVStore(context.TODO(), &fastly.CreateKVStoreInput{\n\t\t\t\t\tName: kvStore.Name,\n\t\t\t\t})\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn fmt.Errorf(\"error creating KV Store: %w\", err)\n\t\t\t\t}\n\t\t\t\treturn nil\n\t\t\t})\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\n\t\tif len(kvStore.Items) > 0 {\n\t\t\tfor _, item := range kvStore.Items {\n\t\t\t\terr = o.Spinner.Process(fmt.Sprintf(\"Creating KV Store key '%s'...\", item.Key), func(_ *text.SpinnerWrapper) error {\n\t\t\t\t\tinput := &fastly.InsertKVStoreKeyInput{\n\t\t\t\t\t\tStoreID: store.StoreID,\n\t\t\t\t\t\tKey:     item.Key,\n\t\t\t\t\t}\n\t\t\t\t\tif item.Body != nil {\n\t\t\t\t\t\tinput.Body = item.Body\n\t\t\t\t\t} else {\n\t\t\t\t\t\tinput.Value = item.Value\n\t\t\t\t\t}\n\t\t\t\t\terr = o.APIClient.InsertKVStoreKey(context.TODO(), input)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn fmt.Errorf(\"error creating KV Store key: %w\", err)\n\t\t\t\t\t}\n\t\t\t\t\treturn nil\n\t\t\t\t})\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// IMPORTANT: We need to link the KV Store to the Compute Service.\n\t\terr = o.Spinner.Process(fmt.Sprintf(\"Creating resource link between service and KV Store '%s'...\", kvStore.Name), func(_ *text.SpinnerWrapper) error {\n\t\t\t_, err = o.APIClient.CreateResource(context.TODO(), &fastly.CreateResourceInput{\n\t\t\t\tServiceID:      o.ServiceID,\n\t\t\t\tServiceVersion: o.ServiceVersion,\n\t\t\t\tName:           fastly.ToPointer(store.Name),\n\t\t\t\tResourceID:     fastly.ToPointer(store.StoreID),\n\t\t\t})\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"error creating resource link between the service '%s' and the KV Store '%s': %w\", o.ServiceID, store.Name, err)\n\t\t\t}\n\t\t\treturn nil\n\t\t})\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// Predefined indicates if the service resource has been specified within the\n// fastly.toml file using a [setup] configuration block.\nfunc (o *KVStores) Predefined() bool {\n\treturn len(o.Setup) > 0\n}\n\n// loadKVStoreFile loads KV Store items from a JSON file.\nfunc loadKVStoreFile(filePath string) ([]KVStoreItem, error) {\n\tabs, err := filepath.Abs(filePath)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to construct absolute path for '%s': %w\", filePath, err)\n\t}\n\n\t// G304 (CWE-22): Potential file inclusion via variable\n\t// Disabling as we trust the source of the variable.\n\t// #nosec\n\tdata, err := os.ReadFile(abs)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to read file '%s': %w\", abs, err)\n\t}\n\n\tvar jsonData map[string]any\n\tif err := json.Unmarshal(data, &jsonData); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to parse JSON file: %w\", err)\n\t}\n\n\tvar items []KVStoreItem\n\tfor key, val := range jsonData {\n\t\t// Handle string values directly\n\t\tif strVal, ok := val.(string); ok {\n\t\t\titems = append(items, KVStoreItem{\n\t\t\t\tKey:   key,\n\t\t\t\tValue: strVal,\n\t\t\t})\n\t\t} else {\n\t\t\t// For non-string values, marshal back to JSON string\n\t\t\tjsonVal, err := json.Marshal(val)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"failed to marshal value for key '%s': %w\", key, err)\n\t\t\t}\n\t\t\titems = append(items, KVStoreItem{\n\t\t\t\tKey:   key,\n\t\t\t\tValue: string(jsonVal),\n\t\t\t})\n\t\t}\n\t}\n\n\treturn items, nil\n}\n"
  },
  {
    "path": "pkg/commands/compute/setup/kv_store_test.go",
    "content": "package setup\n\nimport (\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n)\n\nfunc TestLoadKVStoreFile(t *testing.T) {\n\t// Create a temporary JSON file for testing\n\ttmpDir := t.TempDir()\n\tjsonFile := filepath.Join(tmpDir, \"test.json\")\n\n\tjsonContent := `{\n\t\t\"key1\": \"value1\",\n\t\t\"key2\": \"value2\",\n\t\t\"key3\": {\"nested\": \"object\"}\n\t}`\n\n\tif err := os.WriteFile(jsonFile, []byte(jsonContent), 0o600); err != nil {\n\t\tt.Fatalf(\"failed to create test file: %v\", err)\n\t}\n\n\ttests := []struct {\n\t\tname      string\n\t\tfile      string\n\t\twantErr   bool\n\t\twantCount int\n\t}{\n\t\t{\n\t\t\tname:      \"valid json file\",\n\t\t\tfile:      jsonFile,\n\t\t\twantErr:   false,\n\t\t\twantCount: 3,\n\t\t},\n\t\t{\n\t\t\tname:    \"non-existent file\",\n\t\t\tfile:    \"/nonexistent/path/file.json\",\n\t\t\twantErr: true,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\titems, err := loadKVStoreFile(tt.file)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"loadKVStoreFile() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif !tt.wantErr && len(items) != tt.wantCount {\n\t\t\t\tt.Errorf(\"loadKVStoreFile() got %d items, want %d\", len(items), tt.wantCount)\n\t\t\t}\n\n\t\t\t// Verify that nested objects are marshaled to JSON strings\n\t\t\tif !tt.wantErr {\n\t\t\t\tfoundNested := false\n\t\t\t\tfor _, item := range items {\n\t\t\t\t\tif item.Key == \"key3\" {\n\t\t\t\t\t\tfoundNested = true\n\t\t\t\t\t\tif item.Value != `{\"nested\":\"object\"}` {\n\t\t\t\t\t\t\tt.Errorf(\"nested object not properly marshaled: got %q\", item.Value)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif !foundNested {\n\t\t\t\t\tt.Error(\"expected to find key3 with nested object\")\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/commands/compute/setup/loggers.go",
    "content": "package setup\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/manifest\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// Loggers represents the service state related to log entries defined within\n// the fastly.toml [setup] configuration.\n//\n// NOTE: It implements the setup.Interface interface.\ntype Loggers struct {\n\tSetup  map[string]*manifest.SetupLogger\n\tStdout io.Writer\n}\n\n// Logger represents the configuration parameters for creating a dictionary\n// via the API client.\ntype Logger struct {\n\tProvider string\n}\n\n// Configure prompts the user for specific values related to the service resource.\nfunc (l *Loggers) Configure() error {\n\ttext.Info(l.Stdout, \"The package code requires the following log endpoints to be created.\\n\\n\")\n\n\tfor name, settings := range l.Setup {\n\t\ttext.Output(l.Stdout, \"%s %s\", text.Bold(\"Name:\"), name)\n\t\tif settings.Provider != \"\" {\n\t\t\ttext.Output(l.Stdout, \"%s %s\", text.Bold(\"Provider:\"), settings.Provider)\n\t\t}\n\t\ttext.Break(l.Stdout)\n\t}\n\n\ttext.Description(\n\t\tl.Stdout,\n\t\t\"Refer to the help documentation for each provider (if no provider shown, then select your own)\",\n\t\t\"fastly logging <provider> create --help\",\n\t)\n\n\treturn nil\n}\n\n// Predefined indicates if the service resource has been specified within the\n// fastly.toml file using a [setup] configuration block.\nfunc (l *Loggers) Predefined() bool {\n\treturn len(l.Setup) > 0\n}\n"
  },
  {
    "path": "pkg/commands/compute/setup/secret_store.go",
    "content": "package setup\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/api\"\n\tfsterrors \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/manifest\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// SecretStores represents the service state related to secret stores defined\n// within the fastly.toml [setup] configuration.\n//\n// NOTE: It implements the setup.Interface interface.\ntype SecretStores struct {\n\t// Public\n\tAPIClient      api.Interface\n\tAcceptDefaults bool\n\tNonInteractive bool\n\tSpinner        text.Spinner\n\tServiceID      string\n\tServiceVersion int\n\tSetup          map[string]*manifest.SetupSecretStore\n\tStdin          io.Reader\n\tStdout         io.Writer\n\n\t// Private\n\trequired []SecretStore\n}\n\n// SecretStore represents the configuration parameters for creating a\n// secret store via the API client.\ntype SecretStore struct {\n\tName              string\n\tEntries           []SecretStoreEntry\n\tLinkExistingStore bool\n\tExistingStoreID   string\n}\n\n// SecretStoreEntry represents the configuration parameters for creating\n// secret store items via the API client.\ntype SecretStoreEntry struct {\n\tName   string\n\tSecret string\n}\n\n// Predefined indicates if the service resource has been specified within the\n// fastly.toml file using a [setup] configuration block.\nfunc (s *SecretStores) Predefined() bool {\n\treturn len(s.Setup) > 0\n}\n\n// Configure prompts the user for specific values related to the service resource.\nfunc (s *SecretStores) Configure() error {\n\tvar (\n\t\tcursor         string\n\t\texistingStores []fastly.SecretStore\n\t)\n\n\tfor {\n\t\to, err := s.APIClient.ListSecretStores(context.TODO(), &fastly.ListSecretStoresInput{\n\t\t\tCursor: cursor,\n\t\t})\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif o != nil {\n\t\t\texistingStores = append(existingStores, o.Data...)\n\t\t\tif o.Meta.NextCursor != \"\" {\n\t\t\t\tcursor = o.Meta.NextCursor\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tbreak\n\t\t}\n\t}\n\n\tfor name, settings := range s.Setup {\n\t\tvar (\n\t\t\texistingStoreID   string\n\t\t\tlinkExistingStore bool\n\t\t)\n\n\t\tfor _, store := range existingStores {\n\t\t\tif store.Name == name {\n\t\t\t\tif s.AcceptDefaults || s.NonInteractive {\n\t\t\t\t\tlinkExistingStore = true\n\t\t\t\t\texistingStoreID = store.StoreID\n\t\t\t\t} else {\n\t\t\t\t\ttext.Warning(s.Stdout, \"\\nA Secret Store called '%s' already exists\\n\\n\", name)\n\t\t\t\t\tprompt := text.Prompt(\"Use a different store name (or leave empty to use the existing store): \")\n\t\t\t\t\tvalue, err := text.Input(s.Stdout, prompt, s.Stdin)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn fmt.Errorf(\"error reading prompt input: %w\", err)\n\t\t\t\t\t}\n\t\t\t\t\tif value == \"\" {\n\t\t\t\t\t\tlinkExistingStore = true\n\t\t\t\t\t\texistingStoreID = store.StoreID\n\t\t\t\t\t} else {\n\t\t\t\t\t\tname = value\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif !s.AcceptDefaults && !s.NonInteractive {\n\t\t\ttext.Output(s.Stdout, \"\\nConfiguring Secret Store '%s'\", name)\n\t\t\tif settings.Description != \"\" {\n\t\t\t\ttext.Output(s.Stdout, settings.Description)\n\t\t\t}\n\t\t}\n\n\t\tstore := SecretStore{\n\t\t\tName:              name,\n\t\t\tEntries:           make([]SecretStoreEntry, 0, len(settings.Entries)),\n\t\t\tLinkExistingStore: linkExistingStore,\n\t\t\tExistingStoreID:   existingStoreID,\n\t\t}\n\n\t\tfor key, entry := range settings.Entries {\n\t\t\tvar (\n\t\t\t\tvalue string\n\t\t\t\terr   error\n\t\t\t)\n\n\t\t\tif !s.AcceptDefaults && !s.NonInteractive {\n\t\t\t\ttext.Output(s.Stdout, \"\\nCreate a Secret Store entry called '%s'\", key)\n\t\t\t\tif entry.Description != \"\" {\n\t\t\t\t\ttext.Output(s.Stdout, entry.Description)\n\t\t\t\t}\n\t\t\t\ttext.Break(s.Stdout)\n\n\t\t\t\tprompt := text.Prompt(\"Value: \")\n\t\t\t\tvalue, err = text.InputSecure(s.Stdout, prompt, s.Stdin)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn fmt.Errorf(\"error reading prompt input: %w\", err)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif value == \"\" {\n\t\t\t\treturn errors.New(\"value cannot be blank\")\n\t\t\t}\n\n\t\t\tstore.Entries = append(store.Entries, SecretStoreEntry{\n\t\t\t\tName:   key,\n\t\t\t\tSecret: value,\n\t\t\t})\n\t\t}\n\n\t\ts.required = append(s.required, store)\n\t}\n\n\treturn nil\n}\n\n// Create calls the relevant API to create the service resource(s).\nfunc (s *SecretStores) Create() error {\n\tif s.Spinner == nil {\n\t\treturn fsterrors.RemediationError{\n\t\t\tInner:       fmt.Errorf(\"internal logic error: no spinner configured for setup.SecretStores\"),\n\t\t\tRemediation: fsterrors.BugRemediation,\n\t\t}\n\t}\n\n\tfor _, secretStore := range s.required {\n\t\tvar (\n\t\t\terr   error\n\t\t\tstore *fastly.SecretStore\n\t\t)\n\n\t\tif secretStore.LinkExistingStore {\n\t\t\terr = s.Spinner.Process(fmt.Sprintf(\"Retrieving existing Secret Store '%s'\", secretStore.Name), func(_ *text.SpinnerWrapper) error {\n\t\t\t\tstore, err = s.APIClient.GetSecretStore(context.TODO(), &fastly.GetSecretStoreInput{\n\t\t\t\t\tStoreID: secretStore.ExistingStoreID,\n\t\t\t\t})\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn fmt.Errorf(\"failed to get existing store '%s': %w\", secretStore.Name, err)\n\t\t\t\t}\n\t\t\t\treturn nil\n\t\t\t})\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t} else {\n\t\t\terr = s.Spinner.Process(fmt.Sprintf(\"Creating Secret Store '%s'\", secretStore.Name), func(_ *text.SpinnerWrapper) error {\n\t\t\t\tstore, err = s.APIClient.CreateSecretStore(context.TODO(), &fastly.CreateSecretStoreInput{\n\t\t\t\t\tName: secretStore.Name,\n\t\t\t\t})\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn fmt.Errorf(\"error creating Secret Store %q: %w\", secretStore.Name, err)\n\t\t\t\t}\n\t\t\t\treturn nil\n\t\t\t})\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\n\t\tfor _, entry := range secretStore.Entries {\n\t\t\terr = s.Spinner.Process(fmt.Sprintf(\"Creating Secret Store entry '%s'...\", entry.Name), func(_ *text.SpinnerWrapper) error {\n\t\t\t\t_, err = s.APIClient.CreateSecret(context.TODO(), &fastly.CreateSecretInput{\n\t\t\t\t\tStoreID: store.StoreID,\n\t\t\t\t\tName:    entry.Name,\n\t\t\t\t\tSecret:  []byte(entry.Secret),\n\t\t\t\t})\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn fmt.Errorf(\"error creating Secret Store entry %q: %w\", entry.Name, err)\n\t\t\t\t}\n\t\t\t\treturn nil\n\t\t\t})\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\n\t\terr = s.Spinner.Process(fmt.Sprintf(\"Creating resource link between service and Secret Store '%s'...\", store.Name), func(_ *text.SpinnerWrapper) error {\n\t\t\t// We need to link the secret store to the C@E Service, otherwise the service\n\t\t\t// will not have access to the store.\n\t\t\t_, err = s.APIClient.CreateResource(context.TODO(), &fastly.CreateResourceInput{\n\t\t\t\tServiceID:      s.ServiceID,\n\t\t\t\tServiceVersion: s.ServiceVersion,\n\t\t\t\tName:           fastly.ToPointer(store.Name),\n\t\t\t\tResourceID:     fastly.ToPointer(store.StoreID),\n\t\t\t})\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"error creating resource link between the service %q and the Secret Store %q: %w\", s.ServiceID, store.Name, err)\n\t\t\t}\n\t\t\treturn nil\n\t\t})\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/compute/testdata/build/cpp/main.cpp",
    "content": "#include <stdio.h>\n\nint main() {\n    printf(\"Hello from C++ test!\\n\");\n    return 0;\n}\n"
  },
  {
    "path": "pkg/commands/compute/testdata/build/go/go.mod",
    "content": "module cli-go-sdk\n\ngo 1.18\n\nrequire github.com/fastly/compute-sdk-go v0.1.1\n"
  },
  {
    "path": "pkg/commands/compute/testdata/build/go/main.go",
    "content": "package main\n\nimport \"fmt\"\n\nfunc main() {\n\tfmt.Println(\"hello\")\n}\n"
  },
  {
    "path": "pkg/commands/compute/testdata/build/javascript/package.json",
    "content": "{\n  \"name\": \"compute-starter-kit-javascript-default\",\n  \"version\": \"0.1.0\",\n  \"main\": \"src/index.js\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/fastly/compute-starter-kit-js-proto.git\"\n  },\n  \"author\": \"oss@fastly.com\",\n  \"license\": \"MIT\",\n  \"bugs\": {\n    \"url\": \"https://github.com/fastly/compute-starter-kit-js-proto/issues\"\n  },\n  \"homepage\": \"https://www.fastly.com/documentation/solutions/starters/compute-starter-kit-javascript-default\",\n  \"devDependencies\": {\n    \"core-js\": \"^3.15.2\",\n    \"webpack\": \"^5.10.0\",\n    \"webpack-cli\": \"^4.2.0\"\n  },\n  \"dependencies\": {\n    \"@fastly/js-compute\": \"^0.1.0\"\n  },\n  \"scripts\": {\n    \"prebuild\": \"webpack\",\n    \"build\": \"js-compute-runtime --skip-pkg bin/index.js bin/main.wasm\",\n    \"deploy\": \"npm run build && fastly compute deploy\"\n  }\n}\n"
  },
  {
    "path": "pkg/commands/compute/testdata/build/javascript/src/index.js",
    "content": "// The entry point for your application.\n//\n// Use this fetch event listener to define your main request handling logic. It could be\n// used to route based on the request properties (such as method or path), send\n// the request to a backend, make completely new requests, and/or generate\n// synthetic responses.\naddEventListener('fetch', async function handleRequest(event) {\n\n  // NOTE: By default, console messages are sent to stdout (and stderr for `console.error`).\n  // To send them to a logging endpoint instead, use `console.setEndpoint:\n  // console.setEndpoint(\"my-logging-endpoint\");\n\n  // Get the client request from the event\n  let req = event.request;\n\n  // Make any desired changes to the client request.\n  req.headers.set(\"Host\", \"example.com\");\n\n  // We can filter requests that have unexpected methods.\n  const VALID_METHODS = [\"GET\"];\n  if (!VALID_METHODS.includes(req.method)) {\n    let response = new Response(\"This method is not allowed\", {\n      status: 405\n    });\n    // Send the response back to the client.\n    event.respondWith(response);\n    return;\n  }\n\n  let method = req.method;\n  let url = new URL(event.request.url);\n\n  // If request is a `GET` to the `/` path, send a default response.\n  if (method == \"GET\" && url.pathname == \"/\") {\n    let headers = new Headers();\n    headers.set('Content-Type', 'text/html; charset=utf-8');\n    let response = new Response(\"<iframe src='https://fastly.com/documentation/help/compute-welcome' style='border:0; position: absolute; top: 0; left: 0; width: 100%; height: 100%'></iframe>\\n\", {\n      status: 200,\n      headers\n    });\n    // Send the response back to the client.\n    event.respondWith(response);\n    return;\n  }\n\n  // Catch all other requests and return a 404.\n  let response = new Response(\"The page you requested could not be found\", {\n    status: 404\n  });\n  // Send the response back to the client.\n  event.respondWith(response);\n});\n"
  },
  {
    "path": "pkg/commands/compute/testdata/build/rust/Cargo.lock",
    "content": "# This file is automatically @generated by Cargo.\n# It is not intended for manual editing.\n[[package]]\nname = \"anyhow\"\nversion = \"1.0.38\"\nsource = \"registry+https://github.com/rust-lang/crates.io-index\"\nchecksum = \"afddf7f520a80dbf76e6f50a35bca42a2331ef227a28b3b6dc5c2e2338d114b1\"\n\n[[package]]\nname = \"autocfg\"\nversion = \"1.0.1\"\nsource = \"registry+https://github.com/rust-lang/crates.io-index\"\nchecksum = \"cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a\"\n\n[[package]]\nname = \"bitflags\"\nversion = \"1.2.1\"\nsource = \"registry+https://github.com/rust-lang/crates.io-index\"\nchecksum = \"cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693\"\n\n[[package]]\nname = \"bytes\"\nversion = \"0.5.6\"\nsource = \"registry+https://github.com/rust-lang/crates.io-index\"\nchecksum = \"0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38\"\n\n[[package]]\nname = \"bytes\"\nversion = \"1.0.1\"\nsource = \"registry+https://github.com/rust-lang/crates.io-index\"\nchecksum = \"b700ce4376041dcd0a327fd0097c41095743c4c8af8887265942faf1100bd040\"\n\n[[package]]\nname = \"cfg-if\"\nversion = \"1.0.0\"\nsource = \"registry+https://github.com/rust-lang/crates.io-index\"\nchecksum = \"baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd\"\n\n[[package]]\nname = \"chrono\"\nversion = \"0.4.19\"\nsource = \"registry+https://github.com/rust-lang/crates.io-index\"\nchecksum = \"670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73\"\ndependencies = [\n \"num-integer\",\n \"num-traits\",\n \"serde\",\n]\n\n[[package]]\nname = \"fastly-compute-project\"\nversion = \"0.1.0\"\ndependencies = [\n \"fastly\",\n]\n\n[[package]]\nname = \"fastly\"\nversion = \"0.6.0\"\nsource = \"registry+https://github.com/rust-lang/crates.io-index\"\nchecksum = \"b32333410ceb0499e2c98af856a47464231e44e78cbfc4a5c66592c1dd8bd07a\"\ndependencies = [\n \"anyhow\",\n \"bytes 0.5.6\",\n \"chrono\",\n \"fastly-macros\",\n \"fastly-shared\",\n \"fastly-sys\",\n \"http\",\n \"lazy_static\",\n \"log\",\n \"mime\",\n \"serde\",\n \"serde_json\",\n \"serde_urlencoded\",\n \"thiserror\",\n \"url\",\n]\n\n[[package]]\nname = \"fastly-macros\"\nversion = \"0.4.0\"\nsource = \"registry+https://github.com/rust-lang/crates.io-index\"\nchecksum = \"9805cc40e0ce43131c09107bf833af402ab102ed2e0bdd1a7f4d9b8789138229\"\ndependencies = [\n \"proc-macro2\",\n \"quote\",\n \"syn\",\n]\n\n[[package]]\nname = \"fastly-shared\"\nversion = \"0.6.0\"\nsource = \"registry+https://github.com/rust-lang/crates.io-index\"\nchecksum = \"a31e6b7bc3eae372a3538281a7c38af84bdc15298cfa71badb7eb9f5cb487ae8\"\ndependencies = [\n \"bitflags\",\n \"http\",\n \"thiserror\",\n]\n\n[[package]]\nname = \"fastly-sys\"\nversion = \"0.3.7\"\nsource = \"registry+https://github.com/rust-lang/crates.io-index\"\nchecksum = \"6b95e92b98ef5ea2cef58bde3f3ca41c4fa478172ac66e2d491923765f2b8690\"\ndependencies = [\n \"fastly-shared\",\n]\n\n[[package]]\nname = \"fnv\"\nversion = \"1.0.7\"\nsource = \"registry+https://github.com/rust-lang/crates.io-index\"\nchecksum = \"3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1\"\n\n[[package]]\nname = \"form_urlencoded\"\nversion = \"1.0.0\"\nsource = \"registry+https://github.com/rust-lang/crates.io-index\"\nchecksum = \"ece68d15c92e84fa4f19d3780f1294e5ca82a78a6d515f1efaabcc144688be00\"\ndependencies = [\n \"matches\",\n \"percent-encoding\",\n]\n\n[[package]]\nname = \"http\"\nversion = \"0.2.3\"\nsource = \"registry+https://github.com/rust-lang/crates.io-index\"\nchecksum = \"7245cd7449cc792608c3c8a9eaf69bd4eabbabf802713748fd739c98b82f0747\"\ndependencies = [\n \"bytes 1.0.1\",\n \"fnv\",\n \"itoa\",\n]\n\n[[package]]\nname = \"idna\"\nversion = \"0.2.0\"\nsource = \"registry+https://github.com/rust-lang/crates.io-index\"\nchecksum = \"02e2673c30ee86b5b96a9cb52ad15718aa1f966f5ab9ad54a8b95d5ca33120a9\"\ndependencies = [\n \"matches\",\n \"unicode-bidi\",\n \"unicode-normalization\",\n]\n\n[[package]]\nname = \"itoa\"\nversion = \"0.4.7\"\nsource = \"registry+https://github.com/rust-lang/crates.io-index\"\nchecksum = \"dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736\"\n\n[[package]]\nname = \"lazy_static\"\nversion = \"1.4.0\"\nsource = \"registry+https://github.com/rust-lang/crates.io-index\"\nchecksum = \"e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646\"\n\n[[package]]\nname = \"log\"\nversion = \"0.4.14\"\nsource = \"registry+https://github.com/rust-lang/crates.io-index\"\nchecksum = \"51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710\"\ndependencies = [\n \"cfg-if\",\n]\n\n[[package]]\nname = \"matches\"\nversion = \"0.1.8\"\nsource = \"registry+https://github.com/rust-lang/crates.io-index\"\nchecksum = \"7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08\"\n\n[[package]]\nname = \"mime\"\nversion = \"0.3.16\"\nsource = \"registry+https://github.com/rust-lang/crates.io-index\"\nchecksum = \"2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d\"\n\n[[package]]\nname = \"num-integer\"\nversion = \"0.1.44\"\nsource = \"registry+https://github.com/rust-lang/crates.io-index\"\nchecksum = \"d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db\"\ndependencies = [\n \"autocfg\",\n \"num-traits\",\n]\n\n[[package]]\nname = \"num-traits\"\nversion = \"0.2.14\"\nsource = \"registry+https://github.com/rust-lang/crates.io-index\"\nchecksum = \"9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290\"\ndependencies = [\n \"autocfg\",\n]\n\n[[package]]\nname = \"percent-encoding\"\nversion = \"2.1.0\"\nsource = \"registry+https://github.com/rust-lang/crates.io-index\"\nchecksum = \"d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e\"\n\n[[package]]\nname = \"proc-macro2\"\nversion = \"1.0.24\"\nsource = \"registry+https://github.com/rust-lang/crates.io-index\"\nchecksum = \"1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71\"\ndependencies = [\n \"unicode-xid\",\n]\n\n[[package]]\nname = \"quote\"\nversion = \"1.0.8\"\nsource = \"registry+https://github.com/rust-lang/crates.io-index\"\nchecksum = \"991431c3519a3f36861882da93630ce66b52918dcf1b8e2fd66b397fc96f28df\"\ndependencies = [\n \"proc-macro2\",\n]\n\n[[package]]\nname = \"ryu\"\nversion = \"1.0.5\"\nsource = \"registry+https://github.com/rust-lang/crates.io-index\"\nchecksum = \"71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e\"\n\n[[package]]\nname = \"serde\"\nversion = \"1.0.123\"\nsource = \"registry+https://github.com/rust-lang/crates.io-index\"\nchecksum = \"92d5161132722baa40d802cc70b15262b98258453e85e5d1d365c757c73869ae\"\ndependencies = [\n \"serde_derive\",\n]\n\n[[package]]\nname = \"serde_derive\"\nversion = \"1.0.123\"\nsource = \"registry+https://github.com/rust-lang/crates.io-index\"\nchecksum = \"9391c295d64fc0abb2c556bad848f33cb8296276b1ad2677d1ae1ace4f258f31\"\ndependencies = [\n \"proc-macro2\",\n \"quote\",\n \"syn\",\n]\n\n[[package]]\nname = \"serde_json\"\nversion = \"1.0.61\"\nsource = \"registry+https://github.com/rust-lang/crates.io-index\"\nchecksum = \"4fceb2595057b6891a4ee808f70054bd2d12f0e97f1cbb78689b59f676df325a\"\ndependencies = [\n \"itoa\",\n \"ryu\",\n \"serde\",\n]\n\n[[package]]\nname = \"serde_urlencoded\"\nversion = \"0.7.0\"\nsource = \"registry+https://github.com/rust-lang/crates.io-index\"\nchecksum = \"edfa57a7f8d9c1d260a549e7224100f6c43d43f9103e06dd8b4095a9b2b43ce9\"\ndependencies = [\n \"form_urlencoded\",\n \"itoa\",\n \"ryu\",\n \"serde\",\n]\n\n[[package]]\nname = \"syn\"\nversion = \"1.0.60\"\nsource = \"registry+https://github.com/rust-lang/crates.io-index\"\nchecksum = \"c700597eca8a5a762beb35753ef6b94df201c81cca676604f547495a0d7f0081\"\ndependencies = [\n \"proc-macro2\",\n \"quote\",\n \"unicode-xid\",\n]\n\n[[package]]\nname = \"thiserror\"\nversion = \"1.0.23\"\nsource = \"registry+https://github.com/rust-lang/crates.io-index\"\nchecksum = \"76cc616c6abf8c8928e2fdcc0dbfab37175edd8fb49a4641066ad1364fdab146\"\ndependencies = [\n \"thiserror-impl\",\n]\n\n[[package]]\nname = \"thiserror-impl\"\nversion = \"1.0.23\"\nsource = \"registry+https://github.com/rust-lang/crates.io-index\"\nchecksum = \"9be73a2caec27583d0046ef3796c3794f868a5bc813db689eed00c7631275cd1\"\ndependencies = [\n \"proc-macro2\",\n \"quote\",\n \"syn\",\n]\n\n[[package]]\nname = \"tinyvec\"\nversion = \"1.1.1\"\nsource = \"registry+https://github.com/rust-lang/crates.io-index\"\nchecksum = \"317cca572a0e89c3ce0ca1f1bdc9369547fe318a683418e42ac8f59d14701023\"\ndependencies = [\n \"tinyvec_macros\",\n]\n\n[[package]]\nname = \"tinyvec_macros\"\nversion = \"0.1.0\"\nsource = \"registry+https://github.com/rust-lang/crates.io-index\"\nchecksum = \"cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c\"\n\n[[package]]\nname = \"unicode-bidi\"\nversion = \"0.3.4\"\nsource = \"registry+https://github.com/rust-lang/crates.io-index\"\nchecksum = \"49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5\"\ndependencies = [\n \"matches\",\n]\n\n[[package]]\nname = \"unicode-normalization\"\nversion = \"0.1.16\"\nsource = \"registry+https://github.com/rust-lang/crates.io-index\"\nchecksum = \"a13e63ab62dbe32aeee58d1c5408d35c36c392bba5d9d3142287219721afe606\"\ndependencies = [\n \"tinyvec\",\n]\n\n[[package]]\nname = \"unicode-xid\"\nversion = \"0.2.1\"\nsource = \"registry+https://github.com/rust-lang/crates.io-index\"\nchecksum = \"f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564\"\n\n[[package]]\nname = \"url\"\nversion = \"2.2.0\"\nsource = \"registry+https://github.com/rust-lang/crates.io-index\"\nchecksum = \"5909f2b0817350449ed73e8bcd81c8c3c8d9a7a5d8acba4b27db277f1868976e\"\ndependencies = [\n \"form_urlencoded\",\n \"idna\",\n \"matches\",\n \"percent-encoding\",\n]\n"
  },
  {
    "path": "pkg/commands/compute/testdata/build/rust/Cargo.toml",
    "content": "[package]\nname = \"fastly-compute-project\"\nversion = \"0.1.0\"\nauthors = [\"phamann <patrick@fastly.com>\"]\nedition = \"2018\"\n\n[profile.release]\ndebug = true\n\n[dependencies]\nfastly = \"^0.6.0\"\n"
  },
  {
    "path": "pkg/commands/compute/testdata/build/rust/fastly.toml",
    "content": "manifest_version = \"0.2.0\"\nname = \"Default Rust template\"\ndescription = \"Default package template for Rust based edge compute projects.\"\nauthors = [\"phamann <patrick@fastly.com>\"]\nlanguage = \"rust\"\n"
  },
  {
    "path": "pkg/commands/compute/testdata/build/rust/src/main.rs",
    "content": "fn main() {\n    println!(\"Hello world!\")\n}"
  },
  {
    "path": "pkg/commands/compute/testdata/init/fastly-invalid-missing-version.toml",
    "content": "name = \"Default Rust template\"\ndescription = \"Default package template for Rust based edge compute projects.\"\nauthors = [\"phamann <patrick@fastly.com>\"]\nlanguage = \"rust\"\n"
  },
  {
    "path": "pkg/commands/compute/testdata/init/fastly-invalid-section-version.toml",
    "content": "name = \"Default Rust template\"\ndescription = \"Default package template for Rust based edge compute projects.\"\n[manifest_version]\nauthors = [\"phamann <patrick@fastly.com>\"]\nlanguage = \"rust\"\n"
  },
  {
    "path": "pkg/commands/compute/testdata/init/fastly-invalid-unrecognised.toml",
    "content": "manifest_version = \"abc\" # not a number\nname = \"Default Rust template\"\ndescription = \"Default package template for Rust based edge compute projects.\"\nauthors = [\"phamann <patrick@fastly.com>\"]\nlanguage = \"rust\"\n"
  },
  {
    "path": "pkg/commands/compute/testdata/init/fastly-invalid-version-exceeded.toml",
    "content": "manifest_version = \"99.0.0\" # latest supported manifest_version is less than 99\nname = \"Default Rust template\"\ndescription = \"Default package template for Rust based edge compute projects.\"\nauthors = [\"phamann <patrick@fastly.com>\"]\nlanguage = \"rust\"\n"
  },
  {
    "path": "pkg/commands/compute/testdata/init/fastly-missing-spec-url.toml",
    "content": "manifest_version = 2\nname = \"Default Rust template\"\ndescription = \"Default package template for Rust based edge compute projects.\"\nauthors = [\"phamann <patrick@fastly.com>\"]\nlanguage = \"rust\"\n"
  },
  {
    "path": "pkg/commands/compute/testdata/init/fastly-valid-integer.toml",
    "content": "manifest_version = 2\nname = \"Default Rust template\"\ndescription = \"Default package template for Rust based edge compute projects.\"\nauthors = [\"phamann <patrick@fastly.com>\"]\nlanguage = \"rust\"\n"
  },
  {
    "path": "pkg/commands/compute/testdata/init/fastly-valid-semver.toml",
    "content": "manifest_version = \"0.99.0\" # minor and patch versions are ignored and zero major is bumped to latest\nname = \"Default Rust template\"\ndescription = \"Default package template for Rust based edge compute projects.\"\nauthors = [\"phamann <patrick@fastly.com>\"]\nlanguage = \"rust\"\n"
  },
  {
    "path": "pkg/commands/compute/testdata/init/fastly-viceroy-update.toml",
    "content": "# This file describes a Fastly Compute package. To learn more visit:\n# https://www.fastly.com/documentation/reference/compute/fastly-toml\n\nauthors = [\"phamann <patrick@fastly.com>\"]\ndescription = \"Default package template for Rust based edge compute projects.\"\nlanguage = \"rust\"\nmanifest_version = 2\nname = \"Default Rust template\"\n\n[local_server]\n\n  [local_server.backends]\n\n    [local_server.backends.backend_a]\n      url = \"https://example.com/\"\n      override_host = \"otherexample.com\"\n\n    [local_server.backends.foo]\n      url = \"https://foo.com/\"\n\n    [local_server.backends.bar]\n      url = \"https://bar.com/\"\n\n  [local_server.dictionaries]\n\n    [local_server.dictionaries.strings]\n      file = \"strings.json\"\n      format = \"json\"\n\n    [local_server.dictionaries.toml]\n      format = \"inline-toml\"\n\n    [local_server.dictionaries.toml.contents]\n      foo = \"bar\"\n      baz = \"\"\"\nqux\"\"\"\n\n  [local_server.kv_stores]\n    store_one = [{key = \"first\", data = \"This is some data\"}, {key = \"second\", path = \"strings.json\"}]\n\n    [[local_server.kv_stores.store_two]]\n      key = \"first\"\n      data = \"This is some data\"\n\n    [[local_server.kv_stores.store_two]]\n      key = \"second\"\n      file = \"strings.json\"\n\n  [local_server.secret_stores]\n    store_one = [{key = \"first\", data = \"This is some secret data\"}, {key = \"second\", file = \"/path/to/secret.json\"}]\n\n    [[local_server.secret_stores.store_two]]\n      key = \"first\"\n      data = \"This is also some secret data\"\n\n    [[local_server.secret_stores.store_two]]\n      key = \"second\"\n      file = \"/path/to/other/secret.json\"\n"
  },
  {
    "path": "pkg/commands/compute/testdata/kv_store_example.json",
    "content": "{\n  \"key1\": \"value1\",\n  \"key2\": \"value2\",\n  \"key3\": \"value3\"\n}\n"
  },
  {
    "path": "pkg/commands/compute/testdata/metadata/config.toml",
    "content": "[wasm-metadata]\nbuild_info = \"disable\"\nmachine_info = \"disable\"\npackage_info = \"disable\"\n"
  },
  {
    "path": "pkg/commands/compute/testdata/pack/main.wasm",
    "content": ""
  },
  {
    "path": "pkg/commands/compute/update.go",
    "content": "package compute\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"path/filepath\"\n\n\t\"github.com/kennygrant/sanitize\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/manifest\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// UpdateCommand calls the Fastly API to update packages.\ntype UpdateCommand struct {\n\targparser.Base\n\tpath           string\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n\tautoClone      argparser.OptionalAutoClone\n}\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"update\", \"Update a package on a Fastly Compute service version\")\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.autoClone.Set,\n\t\tDst:    &c.autoClone.Value,\n\t})\n\tc.CmdClause.Flag(\"package\", \"Path to a package tar.gz\").Short('p').StringVar(&c.path)\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *UpdateCommand) Exec(_ io.Reader, out io.Writer) (err error) {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tAutoCloneFlag:      c.autoClone,\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fsterr.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tpackagePath := c.path\n\tif packagePath == \"\" {\n\t\tprojectName, source := c.Globals.Manifest.Name()\n\t\tif source == manifest.SourceUndefined {\n\t\t\treturn fsterr.RemediationError{\n\t\t\t\tInner:       fmt.Errorf(\"failed to read project name: %w\", fsterr.ErrReadingManifest),\n\t\t\t\tRemediation: \"Run `fastly compute build` to produce a Compute package, alternatively use the --package flag to reference a package outside of the current project.\",\n\t\t\t}\n\t\t}\n\t\tpackagePath = filepath.Join(\"pkg\", fmt.Sprintf(\"%s.tar.gz\", sanitize.BaseName(projectName)))\n\t}\n\n\tspinner, err := text.NewSpinner(out)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tdefer func() {\n\t\tif err != nil {\n\t\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\t\"Service ID\":      serviceID,\n\t\t\t\t\"Service Version\": serviceVersion.Number,\n\t\t\t})\n\t\t}\n\t}()\n\n\tserviceVersionNumber := fastly.ToValue(serviceVersion.Number)\n\n\terr = spinner.Process(\"Uploading package\", func(_ *text.SpinnerWrapper) error {\n\t\t_, err = c.Globals.APIClient.UpdatePackage(context.TODO(), &fastly.UpdatePackageInput{\n\t\t\tServiceID:      serviceID,\n\t\t\tServiceVersion: serviceVersionNumber,\n\t\t\tPackagePath:    fastly.ToPointer(packagePath),\n\t\t})\n\t\tif err != nil {\n\t\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\t\"Service ID\":      serviceID,\n\t\t\t\t\"Service Version\": serviceVersionNumber,\n\t\t\t})\n\t\t\treturn fsterr.RemediationError{\n\t\t\t\tInner:       fmt.Errorf(\"error uploading package: %w\", err),\n\t\t\t\tRemediation: \"Run `fastly compute build` to produce a Compute package, alternatively use the --package flag to reference a package outside of the current project.\",\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"\\nUpdated package (service %s, version %v)\", serviceID, serviceVersionNumber)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/compute/update_test.go",
    "content": "package compute_test\n\nimport (\n\t\"fmt\"\n\t\"path/filepath\"\n\t\"testing\"\n\n\troot \"github.com/fastly/cli/pkg/commands/compute\"\n\t\"github.com/fastly/cli/pkg/mock\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n)\n\nfunc TestUpdate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName: \"package API error\",\n\t\t\tArgs: \"-s 123 --version 1 --package pkg/package.tar.gz --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:    testutil.GetVersion,\n\t\t\t\tCloneVersionFn:  testutil.CloneVersionResult(4),\n\t\t\t\tUpdatePackageFn: updatePackageError,\n\t\t\t},\n\t\t\tEnv: &testutil.EnvConfig{\n\t\t\t\tOpts: &testutil.EnvOpts{\n\t\t\t\t\tCopy: []testutil.FileIO{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tSrc: filepath.Join(\"testdata\", \"deploy\", \"pkg\", \"package.tar.gz\"),\n\t\t\t\t\t\t\tDst: filepath.Join(\"pkg\", \"package.tar.gz\"),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: fmt.Sprintf(\"error uploading package: %s\", testutil.Err.Error()),\n\t\t\tWantOutputs: []string{\n\t\t\t\t\"Uploading package\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tName: \"success\",\n\t\t\tArgs: \"-s 123 --version 2 --package pkg/package.tar.gz --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:    testutil.GetVersion,\n\t\t\t\tCloneVersionFn:  testutil.CloneVersionResult(4),\n\t\t\t\tUpdatePackageFn: updatePackageOk,\n\t\t\t},\n\t\t\tEnv: &testutil.EnvConfig{\n\t\t\t\tOpts: &testutil.EnvOpts{\n\t\t\t\t\tCopy: []testutil.FileIO{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tSrc: filepath.Join(\"testdata\", \"deploy\", \"pkg\", \"package.tar.gz\"),\n\t\t\t\t\t\t\tDst: filepath.Join(\"pkg\", \"package.tar.gz\"),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutputs: []string{\n\t\t\t\t\"Uploading package\",\n\t\t\t\t\"Updated package (service 123, version 4)\",\n\t\t\t},\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, \"update\"}, scenarios)\n}\n"
  },
  {
    "path": "pkg/commands/compute/validate.go",
    "content": "package compute\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"path/filepath\"\n\n\t\"github.com/kennygrant/sanitize\"\n\t\"github.com/mholt/archives\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/manifest\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// NewValidateCommand returns a usable command registered under the parent.\nfunc NewValidateCommand(parent argparser.Registerer, g *global.Data) *ValidateCommand {\n\tvar c ValidateCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(\"validate\", \"Validate a Compute package\")\n\tc.CmdClause.Flag(\"package\", \"Path to a package tar.gz\").Short('p').StringVar(&c.path)\n\tc.CmdClause.Flag(\"env\", \"The manifest environment config to validate (e.g. 'stage' will attempt to read 'fastly.stage.toml' inside the package)\").StringVar(&c.env)\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *ValidateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tpackagePath := c.path\n\tif packagePath == \"\" {\n\t\tprojectName, source := c.Globals.Manifest.Name()\n\t\tif source == manifest.SourceUndefined {\n\t\t\treturn fsterr.RemediationError{\n\t\t\t\tInner:       fmt.Errorf(\"failed to read project name: %w\", fsterr.ErrReadingManifest),\n\t\t\t\tRemediation: \"Run `fastly compute build` to produce a Compute package, alternatively use the --package flag to reference a package outside of the current project.\",\n\t\t\t}\n\t\t}\n\t\tpackagePath = filepath.Join(\"pkg\", fmt.Sprintf(\"%s.tar.gz\", sanitize.BaseName(projectName)))\n\t}\n\n\tp, err := filepath.Abs(packagePath)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Path\": c.path,\n\t\t})\n\t\treturn fmt.Errorf(\"error reading file path: %w\", err)\n\t}\n\n\tif c.env != \"\" {\n\t\tmanifestFilename := fmt.Sprintf(\"fastly.%s.toml\", c.env)\n\t\tif c.Globals.Verbose() {\n\t\t\ttext.Info(out, \"Using the '%s' environment manifest (it will be packaged up as %s)\\n\\n\", manifestFilename, manifest.Filename)\n\t\t}\n\t}\n\n\tif err := validatePackageContent(p); err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Path\": c.path,\n\t\t})\n\t\treturn fsterr.RemediationError{\n\t\t\tInner:       fmt.Errorf(\"failed to validate package: %w\", err),\n\t\t\tRemediation: \"Run `fastly compute build` to produce a Compute package, alternatively use the --package flag to reference a package outside of the current project.\",\n\t\t}\n\t}\n\n\ttext.Success(out, \"Validated package %s\", p)\n\treturn nil\n}\n\n// ValidateCommand validates a package archive.\ntype ValidateCommand struct {\n\targparser.Base\n\tenv  string\n\tpath string\n}\n\n// validatePackageContent is a utility function to determine whether a package\n// is valid. It walks through the package files checking the filename against a\n// list of required files. If one of the files doesn't exist it returns an error.\n//\n// NOTE: This function is also called by the `deploy` command.\nfunc validatePackageContent(pkgPath string) error {\n\t// False positive https://github.com/semgrep/semgrep/issues/8593\n\t// nosemgrep: trailofbits.go.iterate-over-empty-map.iterate-over-empty-map\n\tfiles := map[string]bool{\n\t\tmanifest.Filename: false,\n\t\t\"main.wasm\":       false,\n\t}\n\n\tif err := packageFiles(pkgPath, func(f archives.FileInfo) error {\n\t\tfor k := range files {\n\t\t\tif filepath.Base(f.NameInArchive) == k {\n\t\t\t\tfiles[k] = true\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t}); err != nil {\n\t\treturn err\n\t}\n\n\tfor k, found := range files {\n\t\tif !found {\n\t\t\treturn fmt.Errorf(\"error validating package: package must contain a %s file\", k)\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// packageFiles is a utility function to iterate over the package content.\n// It attempts to unarchive and read a tar.gz file from a specific path,\n// calling fn on each file in the archive.\nfunc packageFiles(path string, fn func(archives.FileInfo) error) error {\n\tfile, err := os.Open(filepath.Clean(path))\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error reading package: %w\", err)\n\t}\n\tdefer file.Close() // #nosec G307\n\n\tformat, stream, err := archives.Identify(context.Background(), path, file)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error identifying archive format: %w\", err)\n\t}\n\n\tif ex, ok := format.(archives.Extractor); ok {\n\t\treturn ex.Extract(context.Background(), stream, func(_ context.Context, f archives.FileInfo) error {\n\t\t\t// Skip directories\n\t\t\tif f.IsDir() {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\treturn fn(f)\n\t\t})\n\t}\n\n\treturn fmt.Errorf(\"format does not support extraction\")\n}\n"
  },
  {
    "path": "pkg/commands/compute/validate_test.go",
    "content": "package compute_test\n\nimport (\n\t\"path/filepath\"\n\t\"testing\"\n\n\troot \"github.com/fastly/cli/pkg/commands/compute\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n)\n\nfunc TestValidate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName: \"success\",\n\t\t\tArgs: \"--package pkg/package.tar.gz\",\n\t\t\tEnv: &testutil.EnvConfig{\n\t\t\t\tOpts: &testutil.EnvOpts{\n\t\t\t\t\tCopy: []testutil.FileIO{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tSrc: filepath.Join(\"testdata\", \"deploy\", \"pkg\", \"package.tar.gz\"),\n\t\t\t\t\t\t\tDst: filepath.Join(\"pkg\", \"package.tar.gz\"),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError:  \"\",\n\t\t\tWantOutput: \"Validated package\",\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, \"validate\"}, scenarios)\n}\n"
  },
  {
    "path": "pkg/commands/config/config_test.go",
    "content": "package config_test\n\nimport (\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n\n\troot \"github.com/fastly/cli/pkg/commands/config\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n)\n\nfunc TestConfig(t *testing.T) {\n\tvar data []byte\n\n\t// Read the test config.toml data\n\tpath, err := filepath.Abs(filepath.Join(\"./\", \"testdata\", \"config.toml\"))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdata, err = os.ReadFile(path)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName: \"validate config file content is displayed\",\n\t\t\tEnv: &testutil.EnvConfig{\n\t\t\t\tOpts: &testutil.EnvOpts{\n\t\t\t\t\tWrite: []testutil.FileIO{\n\t\t\t\t\t\t{Src: string(data), Dst: \"config.toml\"},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tEditScenario: func(scenario *testutil.CLIScenario, rootdir string) {\n\t\t\t\t\tscenario.ConfigPath = filepath.Join(rootdir, \"config.toml\")\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: string(data),\n\t\t},\n\t\t{\n\t\t\tName: \"validate config location is displayed\",\n\t\t\tArgs: \"--location\",\n\t\t\tEnv: &testutil.EnvConfig{\n\t\t\t\tOpts: &testutil.EnvOpts{\n\t\t\t\t\tWrite: []testutil.FileIO{\n\t\t\t\t\t\t{Src: string(data), Dst: \"config.toml\"},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tEditScenario: func(scenario *testutil.CLIScenario, rootdir string) {\n\t\t\t\t\tscenario.ConfigPath = filepath.Join(rootdir, \"config.toml\")\n\t\t\t\t\tscenario.WantOutput = scenario.ConfigPath\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName}, scenarios)\n}\n"
  },
  {
    "path": "pkg/commands/config/doc.go",
    "content": "// Package config contains commands to inspect the CLI configuration.\npackage config\n"
  },
  {
    "path": "pkg/commands/config/root.go",
    "content": "package config\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/config\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\n\tlocation bool\n\treset    bool\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"config\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Display the Fastly CLI configuration\")\n\tc.CmdClause.Flag(\"location\", \"Print the location of the CLI configuration file\").Short('l').BoolVar(&c.location)\n\tc.CmdClause.Flag(\"reset\", \"Reset the config to a version compatible with the current CLI version\").Short('r').BoolVar(&c.reset)\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, out io.Writer) (err error) {\n\tif c.reset {\n\t\tif err := c.Globals.Config.UseStatic(config.FilePath); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tif c.location {\n\t\tif c.Globals.Flags.Verbose {\n\t\t\ttext.Break(out)\n\t\t}\n\t\tfmt.Fprintln(out, c.Globals.ConfigPath)\n\t\treturn nil\n\t}\n\n\tdata, err := os.ReadFile(c.Globals.ConfigPath)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\tfmt.Fprintln(out, string(data))\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/config/testdata/config.toml",
    "content": "config_version = 2\n\n[fastly]\n  api_endpoint = \"https://api.fastly.com\"\n"
  },
  {
    "path": "pkg/commands/configstore/configstore_test.go",
    "content": "package configstore_test\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\troot \"github.com/fastly/cli/pkg/commands/configstore\"\n\tfstfmt \"github.com/fastly/cli/pkg/fmt\"\n\t\"github.com/fastly/cli/pkg/mock\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n)\n\nfunc TestCreateStoreCommand(t *testing.T) {\n\tconst (\n\t\tstoreName = \"test123\"\n\t\tstoreID   = \"store-id-123\"\n\t)\n\tnow := time.Now()\n\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: fmt.Sprintf(\"--name %s\", storeName),\n\t\t\tAPI: &mock.API{\n\t\t\t\tCreateConfigStoreFn: func(_ context.Context, _ *fastly.CreateConfigStoreInput) (*fastly.ConfigStore, error) {\n\t\t\t\t\treturn nil, errors.New(\"invalid request\")\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"invalid request\",\n\t\t},\n\t\t{\n\t\t\tArgs: fmt.Sprintf(\"--name %s\", storeName),\n\t\t\tAPI: &mock.API{\n\t\t\t\tCreateConfigStoreFn: func(_ context.Context, i *fastly.CreateConfigStoreInput) (*fastly.ConfigStore, error) {\n\t\t\t\t\treturn &fastly.ConfigStore{\n\t\t\t\t\t\tStoreID: storeID,\n\t\t\t\t\t\tName:    i.Name,\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Created Config Store '%s' (%s)\", storeName, storeID),\n\t\t},\n\t\t{\n\t\t\tArgs: fmt.Sprintf(\"--name %s --json\", storeName),\n\t\t\tAPI: &mock.API{\n\t\t\t\tCreateConfigStoreFn: func(_ context.Context, i *fastly.CreateConfigStoreInput) (*fastly.ConfigStore, error) {\n\t\t\t\t\treturn &fastly.ConfigStore{\n\t\t\t\t\t\tStoreID:   storeID,\n\t\t\t\t\t\tName:      i.Name,\n\t\t\t\t\t\tCreatedAt: &now,\n\t\t\t\t\t\tUpdatedAt: &now,\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(&fastly.ConfigStore{\n\t\t\t\tStoreID:   storeID,\n\t\t\t\tName:      storeName,\n\t\t\t\tCreatedAt: &now,\n\t\t\t\tUpdatedAt: &now,\n\t\t\t}),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, \"create\"}, scenarios)\n}\n\nfunc TestDeleteStoreCommand(t *testing.T) {\n\tconst storeID = \"test123\"\n\terrStoreNotFound := errors.New(\"store not found\")\n\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tWantError: \"error parsing arguments: required flag --store-id not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--store-id DOES-NOT-EXIST\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tDeleteConfigStoreFn: func(_ context.Context, i *fastly.DeleteConfigStoreInput) error {\n\t\t\t\t\tif i.StoreID != storeID {\n\t\t\t\t\t\treturn errStoreNotFound\n\t\t\t\t\t}\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: errStoreNotFound.Error(),\n\t\t},\n\t\t{\n\t\t\tArgs: fmt.Sprintf(\"--store-id %s\", storeID),\n\t\t\tAPI: &mock.API{\n\t\t\t\tDeleteConfigStoreFn: func(_ context.Context, i *fastly.DeleteConfigStoreInput) error {\n\t\t\t\t\tif i.StoreID != storeID {\n\t\t\t\t\t\treturn errStoreNotFound\n\t\t\t\t\t}\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Deleted Config Store '%s'\\n\", storeID),\n\t\t},\n\t\t{\n\t\t\tArgs: fmt.Sprintf(\"--store-id %s --json\", storeID),\n\t\t\tAPI: &mock.API{\n\t\t\t\tDeleteConfigStoreFn: func(_ context.Context, i *fastly.DeleteConfigStoreInput) error {\n\t\t\t\t\tif i.StoreID != storeID {\n\t\t\t\t\t\treturn errStoreNotFound\n\t\t\t\t\t}\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.JSON(`{\"id\": %q, \"deleted\": true}`, storeID),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, \"delete\"}, scenarios)\n}\n\nfunc TestGetStoreCommand(t *testing.T) {\n\tconst (\n\t\tstoreName = \"test123\"\n\t\tstoreID   = \"store-id-123\"\n\t)\n\n\tnow := time.Now()\n\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tWantError: \"error parsing arguments: required flag --store-id not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: fmt.Sprintf(\"--store-id %s\", storeID),\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetConfigStoreFn: func(_ context.Context, _ *fastly.GetConfigStoreInput) (*fastly.ConfigStore, error) {\n\t\t\t\t\treturn nil, errors.New(\"invalid request\")\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"invalid request\",\n\t\t},\n\t\t{\n\t\t\tArgs: fmt.Sprintf(\"--store-id %s\", storeID),\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetConfigStoreFn: func(_ context.Context, i *fastly.GetConfigStoreInput) (*fastly.ConfigStore, error) {\n\t\t\t\t\treturn &fastly.ConfigStore{\n\t\t\t\t\t\tStoreID:   i.StoreID,\n\t\t\t\t\t\tName:      storeName,\n\t\t\t\t\t\tCreatedAt: &now,\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fmtStore(\n\t\t\t\t&fastly.ConfigStore{\n\t\t\t\t\tStoreID:   storeID,\n\t\t\t\t\tName:      storeName,\n\t\t\t\t\tCreatedAt: &now,\n\t\t\t\t},\n\t\t\t\tnil,\n\t\t\t),\n\t\t},\n\t\t{\n\t\t\tArgs: fmt.Sprintf(\"--store-id %s --metadata\", storeID),\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetConfigStoreFn: func(_ context.Context, i *fastly.GetConfigStoreInput) (*fastly.ConfigStore, error) {\n\t\t\t\t\treturn &fastly.ConfigStore{\n\t\t\t\t\t\tStoreID:   i.StoreID,\n\t\t\t\t\t\tName:      storeName,\n\t\t\t\t\t\tCreatedAt: &now,\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t\tGetConfigStoreMetadataFn: func(_ context.Context, _ *fastly.GetConfigStoreMetadataInput) (*fastly.ConfigStoreMetadata, error) {\n\t\t\t\t\treturn &fastly.ConfigStoreMetadata{\n\t\t\t\t\t\tItemCount: 42,\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fmtStore(\n\t\t\t\t&fastly.ConfigStore{\n\t\t\t\t\tStoreID:   storeID,\n\t\t\t\t\tName:      storeName,\n\t\t\t\t\tCreatedAt: &now,\n\t\t\t\t},\n\t\t\t\t&fastly.ConfigStoreMetadata{\n\t\t\t\t\tItemCount: 42,\n\t\t\t\t},\n\t\t\t),\n\t\t},\n\t\t{\n\t\t\tArgs: fmt.Sprintf(\"--store-id %s --json\", storeID),\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetConfigStoreFn: func(_ context.Context, i *fastly.GetConfigStoreInput) (*fastly.ConfigStore, error) {\n\t\t\t\t\treturn &fastly.ConfigStore{\n\t\t\t\t\t\tStoreID:   i.StoreID,\n\t\t\t\t\t\tName:      storeName,\n\t\t\t\t\t\tCreatedAt: &now,\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(&fastly.ConfigStore{\n\t\t\t\tStoreID:   storeID,\n\t\t\t\tName:      storeName,\n\t\t\t\tCreatedAt: &now,\n\t\t\t}),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, \"get\"}, scenarios)\n}\n\nfunc TestListStoresCommand(t *testing.T) {\n\tconst (\n\t\tstoreName = \"test123\"\n\t\tstoreID   = \"store-id-123\"\n\t)\n\n\tnow := time.Now()\n\n\tstores := []*fastly.ConfigStore{\n\t\t{StoreID: storeID, Name: storeName, CreatedAt: &now},\n\t\t{StoreID: storeID + \"+1\", Name: storeName + \"+1\", CreatedAt: &now},\n\t}\n\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tAPI: &mock.API{\n\t\t\t\tListConfigStoresFn: func(_ context.Context, _ *fastly.ListConfigStoresInput) ([]*fastly.ConfigStore, error) {\n\t\t\t\t\treturn nil, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fmtStores(nil),\n\t\t},\n\t\t{\n\t\t\tAPI: &mock.API{\n\t\t\t\tListConfigStoresFn: func(_ context.Context, _ *fastly.ListConfigStoresInput) ([]*fastly.ConfigStore, error) {\n\t\t\t\t\treturn nil, errors.New(\"unknown error\")\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"unknown error\",\n\t\t},\n\t\t{\n\t\t\tAPI: &mock.API{\n\t\t\t\tListConfigStoresFn: func(_ context.Context, _ *fastly.ListConfigStoresInput) ([]*fastly.ConfigStore, error) {\n\t\t\t\t\treturn stores, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fmtStores(stores),\n\t\t},\n\t\t{\n\t\t\tArgs: \"--json\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tListConfigStoresFn: func(_ context.Context, _ *fastly.ListConfigStoresInput) ([]*fastly.ConfigStore, error) {\n\t\t\t\t\treturn stores, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(stores),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, \"list\"}, scenarios)\n}\n\nfunc TestListStoreServicesCommand(t *testing.T) {\n\tconst (\n\t\tstoreName = \"test123\"\n\t\tstoreID   = \"store-id-123\"\n\t)\n\n\tservices := []*fastly.Service{\n\t\t{ServiceID: fastly.ToPointer(\"abc1\"), Name: fastly.ToPointer(\"test1\"), Type: fastly.ToPointer(\"wasm\")},\n\t\t{ServiceID: fastly.ToPointer(\"abc2\"), Name: fastly.ToPointer(\"test2\"), Type: fastly.ToPointer(\"vcl\")},\n\t}\n\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs: fmt.Sprintf(\"--store-id %s\", storeID),\n\t\t\tAPI: &mock.API{\n\t\t\t\tListConfigStoreServicesFn: func(_ context.Context, _ *fastly.ListConfigStoreServicesInput) ([]*fastly.Service, error) {\n\t\t\t\t\treturn nil, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fmtServices(nil),\n\t\t},\n\t\t{\n\t\t\tArgs: fmt.Sprintf(\"--store-id %s\", storeID),\n\t\t\tAPI: &mock.API{\n\t\t\t\tListConfigStoreServicesFn: func(_ context.Context, _ *fastly.ListConfigStoreServicesInput) ([]*fastly.Service, error) {\n\t\t\t\t\treturn nil, errors.New(\"unknown error\")\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"unknown error\",\n\t\t},\n\t\t{\n\t\t\tArgs: fmt.Sprintf(\"--store-id %s\", storeID),\n\t\t\tAPI: &mock.API{\n\t\t\t\tListConfigStoreServicesFn: func(_ context.Context, _ *fastly.ListConfigStoreServicesInput) ([]*fastly.Service, error) {\n\t\t\t\t\treturn services, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fmtServices(services),\n\t\t},\n\t\t{\n\t\t\tArgs: fmt.Sprintf(\"--store-id %s --json\", storeID),\n\t\t\tAPI: &mock.API{\n\t\t\t\tListConfigStoreServicesFn: func(_ context.Context, _ *fastly.ListConfigStoreServicesInput) ([]*fastly.Service, error) {\n\t\t\t\t\treturn services, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(services),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, \"list-services\"}, scenarios)\n}\n\nfunc TestUpdateStoreCommand(t *testing.T) {\n\tconst (\n\t\tstoreID   = \"store-id-123\"\n\t\tstoreName = \"test123\"\n\t)\n\tnow := time.Now()\n\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      fmt.Sprintf(\"--store-id %s\", storeID),\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: fmt.Sprintf(\"--store-id %s --name %s\", storeID, storeName),\n\t\t\tAPI: &mock.API{\n\t\t\t\tUpdateConfigStoreFn: func(_ context.Context, _ *fastly.UpdateConfigStoreInput) (*fastly.ConfigStore, error) {\n\t\t\t\t\treturn nil, errors.New(\"invalid request\")\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"invalid request\",\n\t\t},\n\t\t{\n\t\t\tArgs: fmt.Sprintf(\"--store-id %s --name %s\", storeID, storeName),\n\t\t\tAPI: &mock.API{\n\t\t\t\tUpdateConfigStoreFn: func(_ context.Context, i *fastly.UpdateConfigStoreInput) (*fastly.ConfigStore, error) {\n\t\t\t\t\treturn &fastly.ConfigStore{\n\t\t\t\t\t\tStoreID:   storeID,\n\t\t\t\t\t\tName:      i.Name,\n\t\t\t\t\t\tCreatedAt: &now,\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Updated Config Store '%s' (%s)\", storeName, storeID),\n\t\t},\n\t\t{\n\t\t\tArgs: fmt.Sprintf(\"--store-id %s --name %s --json\", storeID, storeName),\n\t\t\tAPI: &mock.API{\n\t\t\t\tUpdateConfigStoreFn: func(_ context.Context, i *fastly.UpdateConfigStoreInput) (*fastly.ConfigStore, error) {\n\t\t\t\t\treturn &fastly.ConfigStore{\n\t\t\t\t\t\tStoreID:   storeID,\n\t\t\t\t\t\tName:      i.Name,\n\t\t\t\t\t\tCreatedAt: &now,\n\t\t\t\t\t\tUpdatedAt: &now,\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(&fastly.ConfigStore{\n\t\t\t\tStoreID:   storeID,\n\t\t\t\tName:      storeName,\n\t\t\t\tCreatedAt: &now,\n\t\t\t\tUpdatedAt: &now,\n\t\t\t}),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, \"update\"}, scenarios)\n}\n"
  },
  {
    "path": "pkg/commands/configstore/create.go",
    "content": "package configstore\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\n\tc.CmdClause = parent.Command(\"create\", \"Create a new config store\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        \"name\",\n\t\tShort:       'n',\n\t\tDescription: \"Store name\",\n\t\tDst:         &c.input.Name,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\n\treturn &c\n}\n\n// CreateCommand calls the Fastly API to create an appropriate resource.\ntype CreateCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\tinput fastly.CreateConfigStoreInput\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *CreateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\to, err := c.Globals.APIClient.CreateConfigStore(context.TODO(), &c.input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Created Config Store '%s' (%s)\", o.Name, o.StoreID)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/configstore/delete.go",
    "content": "package configstore\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\n\tc.CmdClause = parent.Command(\"delete\", \"Delete a config store\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StoreIDFlag(&c.input.StoreID)) // --store-id\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\n\treturn &c\n}\n\n// DeleteCommand calls the Fastly API to delete an appropriate resource.\ntype DeleteCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\tinput fastly.DeleteConfigStoreInput\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DeleteCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\terr := c.Globals.APIClient.DeleteConfigStore(context.TODO(), &c.input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif c.JSONOutput.Enabled {\n\t\to := struct {\n\t\t\tID      string `json:\"id\"`\n\t\t\tDeleted bool   `json:\"deleted\"`\n\t\t}{\n\t\t\tc.input.StoreID,\n\t\t\ttrue,\n\t\t}\n\t\t_, err := c.WriteJSON(out, o)\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Deleted Config Store '%s'\", c.input.StoreID)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/configstore/describe.go",
    "content": "package configstore\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// NewDescribeCommand returns a usable command registered under the parent.\nfunc NewDescribeCommand(parent argparser.Registerer, g *global.Data) *DescribeCommand {\n\tc := DescribeCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\n\tc.CmdClause = parent.Command(\"describe\", \"Retrieve a single config store\").Alias(\"get\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StoreIDFlag(&c.input.StoreID)) // --store-id\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\tc.RegisterFlagBool(argparser.BoolFlagOpts{\n\t\tName:        \"metadata\",\n\t\tShort:       'm',\n\t\tDescription: \"Include config store metadata\",\n\t\tDst:         &c.metadata,\n\t})\n\n\treturn &c\n}\n\n// DescribeCommand calls the Fastly API to describe an appropriate resource.\ntype DescribeCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\tinput    fastly.GetConfigStoreInput\n\tmetadata bool\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DescribeCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tcs, err := c.Globals.APIClient.GetConfigStore(context.TODO(), &c.input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tvar csm *fastly.ConfigStoreMetadata\n\tif c.metadata {\n\t\tcsm, err = c.Globals.APIClient.GetConfigStoreMetadata(context.TODO(), &fastly.GetConfigStoreMetadataInput{\n\t\t\tStoreID: c.input.StoreID,\n\t\t})\n\t\tif err != nil {\n\t\t\tc.Globals.ErrLog.Add(err)\n\t\t\treturn err\n\t\t}\n\t}\n\n\tif c.JSONOutput.Enabled {\n\t\t// Create an ad-hoc structure for JSON representation of the config store\n\t\t// and its metadata.\n\t\tdata := struct {\n\t\t\t*fastly.ConfigStore\n\t\t\tMetadata *fastly.ConfigStoreMetadata `json:\"metadata,omitempty\"`\n\t\t}{\n\t\t\tConfigStore: cs,\n\t\t\tMetadata:    csm,\n\t\t}\n\n\t\tif ok, err := c.WriteJSON(out, data); ok {\n\t\t\treturn err\n\t\t}\n\t}\n\n\ttext.PrintConfigStore(out, cs, csm)\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/configstore/doc.go",
    "content": "// Package configstore contains commands to inspect and manipulate Fastly edge\n// config stores.\n//\n// https://www.fastly.com/documentation/reference/api/services/resources/config-store\npackage configstore\n"
  },
  {
    "path": "pkg/commands/configstore/helper_test.go",
    "content": "package configstore_test\n\nimport (\n\t\"bytes\"\n\n\t\"github.com/fastly/cli/pkg/text\"\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n)\n\nfunc fmtStore(cs *fastly.ConfigStore, csm *fastly.ConfigStoreMetadata) string {\n\tvar b bytes.Buffer\n\ttext.PrintConfigStore(&b, cs, csm)\n\treturn b.String()\n}\n\nfunc fmtStores(s []*fastly.ConfigStore) string {\n\tvar b bytes.Buffer\n\ttext.PrintConfigStoresTbl(&b, s)\n\treturn b.String()\n}\n\nfunc fmtServices(s []*fastly.Service) string {\n\tvar b bytes.Buffer\n\ttext.PrintConfigStoreServicesTbl(&b, s)\n\treturn b.String()\n}\n"
  },
  {
    "path": "pkg/commands/configstore/list.go",
    "content": "package configstore\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\n\tc.CmdClause = parent.Command(\"list\", \"List config stores\")\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\n\treturn &c\n}\n\n// ListCommand calls the Fastly API to list appropriate resources.\ntype ListCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *ListCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\to, err := c.Globals.APIClient.ListConfigStores(context.TODO(), &fastly.ListConfigStoresInput{})\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\ttext.PrintConfigStoresTbl(out, o)\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/configstore/list_services.go",
    "content": "package configstore\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// NewListServicesCommand returns a usable command registered under the parent.\nfunc NewListServicesCommand(parent argparser.Registerer, g *global.Data) *ListServicesCommand {\n\tc := ListServicesCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\n\tc.CmdClause = parent.Command(\"list-services\", \"List config store's services\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StoreIDFlag(&c.input.StoreID)) // --store-id\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\n\treturn &c\n}\n\n// ListServicesCommand calls the Fastly API to list appropriate resources.\ntype ListServicesCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\tinput fastly.ListConfigStoreServicesInput\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *ListServicesCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\to, err := c.Globals.APIClient.ListConfigStoreServices(context.TODO(), &c.input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\ttext.PrintConfigStoreServicesTbl(out, o)\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/configstore/root.go",
    "content": "package configstore\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"config-store\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tc := RootCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\n\tc.CmdClause = parent.Command(CommandName, \"Manipulate Fastly Config Stores\")\n\n\treturn &c\n}\n\n// RootCommand is the parent command for all 'store' subcommands.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/configstore/update.go",
    "content": "package configstore\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\n\tc.CmdClause = parent.Command(\"update\", \"Update a config store\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        \"name\",\n\t\tShort:       'n',\n\t\tDescription: \"New name for the config store\",\n\t\tDst:         &c.input.Name,\n\t\tRequired:    true,\n\t})\n\tc.RegisterFlag(argparser.StoreIDFlag(&c.input.StoreID)) // --store-id\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\n\treturn &c\n}\n\n// UpdateCommand calls the Fastly API to update an appropriate resource.\ntype UpdateCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\tinput fastly.UpdateConfigStoreInput\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *UpdateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\to, err := c.Globals.APIClient.UpdateConfigStore(context.TODO(), &c.input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Updated Config Store '%s' (%s)\", o.Name, o.StoreID)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/configstoreentry/configstoreentry_test.go",
    "content": "package configstoreentry_test\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\troot \"github.com/fastly/cli/pkg/commands/configstoreentry\"\n\tfstfmt \"github.com/fastly/cli/pkg/fmt\"\n\t\"github.com/fastly/cli/pkg/mock\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\nfunc TestCreateEntryCommand(t *testing.T) {\n\tconst (\n\t\tstoreID   = \"store-id-123\"\n\t\titemKey   = \"key\"\n\t\titemValue = \"the-value\"\n\t)\n\tnow := time.Now()\n\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"--key a-key --value a-value\",\n\t\t\tWantError: \"error parsing arguments: required flag --store-id not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: fmt.Sprintf(\"--store-id %s --key %s --value %s\", storeID, itemKey, itemValue),\n\t\t\tAPI: &mock.API{\n\t\t\t\tCreateConfigStoreItemFn: func(_ context.Context, _ *fastly.CreateConfigStoreItemInput) (*fastly.ConfigStoreItem, error) {\n\t\t\t\t\treturn nil, errors.New(\"invalid request\")\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"invalid request\",\n\t\t},\n\t\t{\n\t\t\tArgs: fmt.Sprintf(\"--store-id %s --key %s --value %s\", storeID, itemKey, itemValue),\n\t\t\tAPI: &mock.API{\n\t\t\t\tCreateConfigStoreItemFn: func(_ context.Context, i *fastly.CreateConfigStoreItemInput) (*fastly.ConfigStoreItem, error) {\n\t\t\t\t\treturn &fastly.ConfigStoreItem{\n\t\t\t\t\t\tStoreID: i.StoreID,\n\t\t\t\t\t\tKey:     i.Key,\n\t\t\t\t\t\tValue:   i.Value,\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Created key '%s' in Config Store '%s'\", itemKey, storeID),\n\t\t},\n\t\t{\n\t\t\tArgs: fmt.Sprintf(\"--store-id %s --key %s --value %s --json\", storeID, itemKey, itemValue),\n\t\t\tAPI: &mock.API{\n\t\t\t\tCreateConfigStoreItemFn: func(_ context.Context, i *fastly.CreateConfigStoreItemInput) (*fastly.ConfigStoreItem, error) {\n\t\t\t\t\treturn &fastly.ConfigStoreItem{\n\t\t\t\t\t\tStoreID:   i.StoreID,\n\t\t\t\t\t\tKey:       i.Key,\n\t\t\t\t\t\tValue:     i.Value,\n\t\t\t\t\t\tCreatedAt: &now,\n\t\t\t\t\t\tUpdatedAt: &now,\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(&fastly.ConfigStoreItem{\n\t\t\t\tStoreID:   storeID,\n\t\t\t\tKey:       itemKey,\n\t\t\t\tValue:     itemValue,\n\t\t\t\tCreatedAt: &now,\n\t\t\t\tUpdatedAt: &now,\n\t\t\t}),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, \"create\"}, scenarios)\n}\n\nfunc TestDeleteEntryCommand(t *testing.T) {\n\tconst (\n\t\tstoreID = \"store-id-123\"\n\t\titemKey = \"key\"\n\t)\n\n\tnow := time.Now()\n\n\ttestItems := make([]*fastly.ConfigStoreItem, 3)\n\tfor i := range testItems {\n\t\ttestItems[i] = &fastly.ConfigStoreItem{\n\t\t\tStoreID:   storeID,\n\t\t\tKey:       fmt.Sprintf(\"key-%02d\", i),\n\t\t\tValue:     fmt.Sprintf(\"value %02d\", i),\n\t\t\tCreatedAt: &now,\n\t\t\tUpdatedAt: &now,\n\t\t}\n\t}\n\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"--key a-key\",\n\t\t\tWantError: \"error parsing arguments: required flag --store-id not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs:      \"--store-id \" + storeID,\n\t\t\tWantError: \"invalid command, neither --all or --key provided\",\n\t\t},\n\t\t{\n\t\t\tArgs:      \"--json --all --store-id \" + storeID,\n\t\t\tWantError: \"invalid flag combination, --all and --json\",\n\t\t},\n\t\t{\n\t\t\tArgs:      \"--key a-key --all --store-id \" + storeID,\n\t\t\tWantError: \"invalid flag combination, --all and --key\",\n\t\t},\n\t\t{\n\t\t\tArgs: fmt.Sprintf(\"--store-id %s --key %s\", storeID, itemKey),\n\t\t\tAPI: &mock.API{\n\t\t\t\tDeleteConfigStoreItemFn: func(_ context.Context, _ *fastly.DeleteConfigStoreItemInput) error {\n\t\t\t\t\treturn errors.New(\"invalid request\")\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"invalid request\",\n\t\t},\n\t\t{\n\t\t\tArgs: fmt.Sprintf(\"--store-id %s --key %s\", storeID, itemKey),\n\t\t\tAPI: &mock.API{\n\t\t\t\tDeleteConfigStoreItemFn: func(_ context.Context, _ *fastly.DeleteConfigStoreItemInput) error {\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Deleted key '%s' from Config Store '%s'\", itemKey, storeID),\n\t\t},\n\t\t{\n\t\t\tArgs: fmt.Sprintf(\"--store-id %s --key %s --json\", storeID, itemKey),\n\t\t\tAPI: &mock.API{\n\t\t\t\tDeleteConfigStoreItemFn: func(_ context.Context, _ *fastly.DeleteConfigStoreItemInput) error {\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(struct {\n\t\t\t\tStoreID string `json:\"store_id\"`\n\t\t\t\tKey     string `json:\"key\"`\n\t\t\t\tDeleted bool   `json:\"deleted\"`\n\t\t\t}{\n\t\t\t\tstoreID,\n\t\t\t\titemKey,\n\t\t\t\ttrue,\n\t\t\t}),\n\t\t},\n\t\t{\n\t\t\tArgs: fmt.Sprintf(\"--store-id %s --all --auto-yes\", storeID),\n\t\t\tAPI: &mock.API{\n\t\t\t\tListConfigStoreItemsFn: func(_ context.Context, _ *fastly.ListConfigStoreItemsInput) ([]*fastly.ConfigStoreItem, error) {\n\t\t\t\t\treturn testItems, nil\n\t\t\t\t},\n\t\t\t\tDeleteConfigStoreItemFn: func(_ context.Context, _ *fastly.DeleteConfigStoreItemInput) error {\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fmt.Sprintf(`Deleting key: key-00\nDeleting key: key-01\nDeleting key: key-02\n\nSUCCESS: Deleted all keys from Config Store '%s'\n`, storeID),\n\t\t},\n\t\t{\n\t\t\tArgs: fmt.Sprintf(\"--store-id %s --all --auto-yes\", storeID),\n\t\t\tAPI: &mock.API{\n\t\t\t\tListConfigStoreItemsFn: func(_ context.Context, _ *fastly.ListConfigStoreItemsInput) ([]*fastly.ConfigStoreItem, error) {\n\t\t\t\t\treturn testItems, nil\n\t\t\t\t},\n\t\t\t\tDeleteConfigStoreItemFn: func(_ context.Context, _ *fastly.DeleteConfigStoreItemInput) error {\n\t\t\t\t\treturn errors.New(\"whoops\")\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"failed to delete keys: key-00, key-01, key-02\",\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, \"delete\"}, scenarios)\n}\n\nfunc TestDescribeEntryCommand(t *testing.T) {\n\tconst (\n\t\tstoreID = \"store-id-123\"\n\t\titemKey = \"key\"\n\t)\n\tnow := time.Now()\n\n\ttestItem := &fastly.ConfigStoreItem{\n\t\tStoreID:   storeID,\n\t\tKey:       itemKey,\n\t\tValue:     \"a value\",\n\t\tCreatedAt: &now,\n\t\tUpdatedAt: &now,\n\t}\n\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"--key a-key\",\n\t\t\tWantError: \"error parsing arguments: required flag --store-id not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: fmt.Sprintf(\"--store-id %s --key %s\", storeID, itemKey),\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetConfigStoreItemFn: func(_ context.Context, _ *fastly.GetConfigStoreItemInput) (*fastly.ConfigStoreItem, error) {\n\t\t\t\t\treturn nil, errors.New(\"invalid request\")\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"invalid request\",\n\t\t},\n\t\t{\n\t\t\tArgs: fmt.Sprintf(\"--store-id %s --key %s\", storeID, itemKey),\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetConfigStoreItemFn: func(_ context.Context, i *fastly.GetConfigStoreItemInput) (*fastly.ConfigStoreItem, error) {\n\t\t\t\t\treturn &fastly.ConfigStoreItem{\n\t\t\t\t\t\tStoreID:   i.StoreID,\n\t\t\t\t\t\tKey:       i.Key,\n\t\t\t\t\t\tValue:     \"a value\",\n\t\t\t\t\t\tCreatedAt: &now,\n\t\t\t\t\t\tUpdatedAt: &now,\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: printConfigStoreItem(testItem),\n\t\t},\n\t\t{\n\t\t\tArgs: fmt.Sprintf(\"--store-id %s --key %s --json\", storeID, itemKey),\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetConfigStoreItemFn: func(_ context.Context, i *fastly.GetConfigStoreItemInput) (*fastly.ConfigStoreItem, error) {\n\t\t\t\t\treturn &fastly.ConfigStoreItem{\n\t\t\t\t\t\tStoreID:   i.StoreID,\n\t\t\t\t\t\tKey:       i.Key,\n\t\t\t\t\t\tValue:     \"a value\",\n\t\t\t\t\t\tCreatedAt: &now,\n\t\t\t\t\t\tUpdatedAt: &now,\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(testItem),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, \"describe\"}, scenarios)\n}\n\nfunc TestListEntriesCommand(t *testing.T) {\n\tconst storeID = \"store-id-123\"\n\n\tnow := time.Now()\n\n\ttestItems := make([]*fastly.ConfigStoreItem, 3)\n\tfor i := range testItems {\n\t\ttestItems[i] = &fastly.ConfigStoreItem{\n\t\t\tStoreID:   storeID,\n\t\t\tKey:       fmt.Sprintf(\"key-%02d\", i),\n\t\t\tValue:     fmt.Sprintf(\"value %02d\", i),\n\t\t\tCreatedAt: &now,\n\t\t\tUpdatedAt: &now,\n\t\t}\n\t}\n\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tWantError: \"error parsing arguments: required flag --store-id not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: fmt.Sprintf(\"--store-id %s\", storeID),\n\t\t\tAPI: &mock.API{\n\t\t\t\tListConfigStoreItemsFn: func(_ context.Context, _ *fastly.ListConfigStoreItemsInput) ([]*fastly.ConfigStoreItem, error) {\n\t\t\t\t\treturn nil, errors.New(\"invalid request\")\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"invalid request\",\n\t\t},\n\t\t{\n\t\t\tArgs: fmt.Sprintf(\"--store-id %s\", storeID),\n\t\t\tAPI: &mock.API{\n\t\t\t\tListConfigStoreItemsFn: func(_ context.Context, _ *fastly.ListConfigStoreItemsInput) ([]*fastly.ConfigStoreItem, error) {\n\t\t\t\t\treturn testItems, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: printConfigStoreItemsTbl(testItems),\n\t\t},\n\t\t{\n\t\t\tArgs: fmt.Sprintf(\"--store-id %s --json\", storeID),\n\t\t\tAPI: &mock.API{\n\t\t\t\tListConfigStoreItemsFn: func(_ context.Context, _ *fastly.ListConfigStoreItemsInput) ([]*fastly.ConfigStoreItem, error) {\n\t\t\t\t\treturn testItems, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(testItems),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, \"list\"}, scenarios)\n}\n\nfunc TestUpdateEntryCommand(t *testing.T) {\n\tconst (\n\t\tstoreID   = \"store-id-123\"\n\t\titemKey   = \"key\"\n\t\titemValue = \"the-value\"\n\t)\n\tnow := time.Now()\n\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"--key a-key --value a-value\",\n\t\t\tWantError: \"error parsing arguments: required flag --store-id not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: fmt.Sprintf(\"--store-id %s --key %s --value %s\", storeID, itemKey, itemValue),\n\t\t\tAPI: &mock.API{\n\t\t\t\tUpdateConfigStoreItemFn: func(_ context.Context, _ *fastly.UpdateConfigStoreItemInput) (*fastly.ConfigStoreItem, error) {\n\t\t\t\t\treturn nil, errors.New(\"invalid request\")\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"invalid request\",\n\t\t},\n\t\t{\n\t\t\tArgs: fmt.Sprintf(\"--store-id %s --key %s --value %s\", storeID, itemKey, itemValue),\n\t\t\tAPI: &mock.API{\n\t\t\t\tUpdateConfigStoreItemFn: func(_ context.Context, i *fastly.UpdateConfigStoreItemInput) (*fastly.ConfigStoreItem, error) {\n\t\t\t\t\treturn &fastly.ConfigStoreItem{\n\t\t\t\t\t\tStoreID: i.StoreID,\n\t\t\t\t\t\tKey:     i.Key,\n\t\t\t\t\t\tValue:   i.Value,\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Updated config store item %s in store %s\", itemKey, storeID),\n\t\t},\n\t\t{\n\t\t\tArgs: fmt.Sprintf(\"--store-id %s --key %s --value %s --json\", storeID, itemKey, itemValue+\"updated\"),\n\t\t\tAPI: &mock.API{\n\t\t\t\tUpdateConfigStoreItemFn: func(_ context.Context, i *fastly.UpdateConfigStoreItemInput) (*fastly.ConfigStoreItem, error) {\n\t\t\t\t\treturn &fastly.ConfigStoreItem{\n\t\t\t\t\t\tStoreID:   i.StoreID,\n\t\t\t\t\t\tKey:       i.Key,\n\t\t\t\t\t\tValue:     i.Value,\n\t\t\t\t\t\tCreatedAt: &now,\n\t\t\t\t\t\tUpdatedAt: &now,\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(&fastly.ConfigStoreItem{\n\t\t\t\tStoreID:   storeID,\n\t\t\t\tKey:       itemKey,\n\t\t\t\tValue:     itemValue + \"updated\",\n\t\t\t\tCreatedAt: &now,\n\t\t\t\tUpdatedAt: &now,\n\t\t\t}),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, \"update\"}, scenarios)\n}\n\nfunc printConfigStoreItem(i *fastly.ConfigStoreItem) string {\n\tvar b bytes.Buffer\n\ttext.PrintConfigStoreItem(&b, \"\", i)\n\treturn b.String()\n}\n\nfunc printConfigStoreItemsTbl(i []*fastly.ConfigStoreItem) string {\n\tvar b bytes.Buffer\n\ttext.PrintConfigStoreItemsTbl(&b, i)\n\treturn b.String()\n}\n"
  },
  {
    "path": "pkg/commands/configstoreentry/create.go",
    "content": "package configstoreentry\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\n\tc.CmdClause = parent.Command(\"create\", \"Create a new config store item\").Alias(\"insert\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        \"key\",\n\t\tShort:       'k',\n\t\tDescription: \"Item name\",\n\t\tDst:         &c.input.Key,\n\t\tRequired:    true,\n\t})\n\tc.RegisterFlag(argparser.StoreIDFlag(&c.input.StoreID)) // --store-id\n\n\t// One of these must be set.\n\tc.RegisterFlagBool(argparser.BoolFlagOpts{\n\t\tName:        \"stdin\",\n\t\tDescription: \"Read item value from STDIN. If set, --value will be ignored\",\n\t\tDst:         &c.stdin,\n\t\tRequired:    false,\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        \"value\",\n\t\tDescription: \"Item value. Required unless --stdin is set\",\n\t\tDst:         &c.input.Value,\n\t\tRequired:    false,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\n\treturn &c\n}\n\n// CreateCommand calls the Fastly API to create an appropriate resource.\ntype CreateCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\tinput fastly.CreateConfigStoreItemInput\n\tstdin bool\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *CreateCommand) Exec(in io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tif c.stdin {\n\t\t// Determine if 'in' has data available.\n\t\tif in == nil || text.IsTTY(in) {\n\t\t\treturn errNoSTDINData\n\t\t}\n\n\t\t// Must read one past limit, since LimitReader returns EOF\n\t\t// once it reads its limited number of bytes.\n\t\tvalue, err := io.ReadAll(io.LimitReader(in, maxValueLen+1))\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tc.input.Value = string(value)\n\t} else if c.input.Value == \"\" {\n\t\treturn errNoValue\n\t}\n\n\tif len(c.input.Key) > maxKeyLen {\n\t\treturn errMaxKeyLen\n\t}\n\tif len(c.input.Value) > maxValueLen {\n\t\treturn errMaxValueLen\n\t}\n\n\to, err := c.Globals.APIClient.CreateConfigStoreItem(context.TODO(), &c.input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Created key '%s' in Config Store '%s'\", o.Key, o.StoreID)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/configstoreentry/delete.go",
    "content": "package configstoreentry\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"strings\"\n\t\"sync\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// deleteKeysConcurrencyLimit is used to limit the concurrency when deleting ALL keys.\n// This is effectively the 'thread pool' size.\nconst deleteKeysConcurrencyLimit int = 100\n\n// batchLimit is used to split the list of items into batches.\n// The batch size of 100 aligns with the KV Store pagination default limit.\nconst batchLimit int = 100\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\n\tc.CmdClause = parent.Command(\"delete\", \"Delete a config store item\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StoreIDFlag(&c.input.StoreID)) // --store-id\n\n\t// Optional.\n\tc.CmdClause.Flag(\"all\", \"Delete all entries within the store\").Short('a').BoolVar(&c.deleteAll)\n\tc.CmdClause.Flag(\"batch-size\", \"Key batch processing size (ignored when set without the --all flag)\").Short('b').Action(c.batchSize.Set).IntVar(&c.batchSize.Value)\n\tc.CmdClause.Flag(\"concurrency\", \"Control thread pool size (ignored when set without the --all flag)\").Short('c').Action(c.concurrency.Set).IntVar(&c.concurrency.Value)\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        \"key\",\n\t\tShort:       'k',\n\t\tDescription: \"Item name\",\n\t\tDst:         &c.input.Key,\n\t})\n\n\treturn &c\n}\n\n// DeleteCommand calls the Fastly API to delete an appropriate resource.\ntype DeleteCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tbatchSize   argparser.OptionalInt\n\tconcurrency argparser.OptionalInt\n\tdeleteAll   bool\n\tinput       fastly.DeleteConfigStoreItemInput\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DeleteCommand) Exec(in io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\t// TODO: Support --json for bulk deletions.\n\tif c.deleteAll && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidDeleteAllJSONKeyCombo\n\t}\n\tif c.deleteAll && c.input.Key != \"\" {\n\t\treturn fsterr.ErrInvalidDeleteAllKeyCombo\n\t}\n\tif !c.deleteAll && c.input.Key == \"\" {\n\t\treturn fsterr.ErrMissingDeleteAllKeyCombo\n\t}\n\n\tif c.deleteAll {\n\t\tif !c.Globals.Flags.AutoYes && !c.Globals.Flags.NonInteractive {\n\t\t\ttext.Warning(out, \"This will delete ALL entries from your store!\\n\\n\")\n\t\t\tcont, err := text.AskYesNo(out, \"Are you sure you want to continue? [y/N]: \", in)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif !cont {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\ttext.Break(out)\n\t\t}\n\t\treturn c.deleteAllKeys(out)\n\t}\n\n\terr := c.Globals.APIClient.DeleteConfigStoreItem(context.TODO(), &c.input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif c.JSONOutput.Enabled {\n\t\to := struct {\n\t\t\tStoreID string `json:\"store_id\"`\n\t\t\tKey     string `json:\"key\"`\n\t\t\tDeleted bool   `json:\"deleted\"`\n\t\t}{\n\t\t\tc.input.StoreID,\n\t\t\tc.input.Key,\n\t\t\ttrue,\n\t\t}\n\t\t_, err := c.WriteJSON(out, o)\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Deleted key '%s' from Config Store '%s'\", c.input.Key, c.input.StoreID)\n\treturn nil\n}\n\nfunc (c *DeleteCommand) deleteAllKeys(out io.Writer) error {\n\t// NOTE: The Config Store returns ALL items (there is no pagination).\n\titems, err := c.Globals.APIClient.ListConfigStoreItems(context.TODO(), &fastly.ListConfigStoreItemsInput{\n\t\tStoreID: c.input.StoreID,\n\t})\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to acquire list of Config Store items: %w\", err)\n\t}\n\n\tvar (\n\t\tmu sync.Mutex\n\t\twg sync.WaitGroup\n\t)\n\tpoolSize := deleteKeysConcurrencyLimit\n\tif c.concurrency.WasSet {\n\t\tpoolSize = c.concurrency.Value\n\t}\n\tsemaphore := make(chan struct{}, poolSize)\n\n\ttotal := len(items)\n\tfailedKeys := []string{}\n\n\tbatchSize := batchLimit\n\tif c.batchSize.WasSet {\n\t\tbatchSize = c.batchSize.Value\n\t}\n\n\t// With KV Store we have pagination support and so that natively provides us a\n\t// predefined 'batch' size. Because we don't have pagination with the Config\n\t// Store it means we'll define our own batch size which the user can override.\n\tfor i := 0; i < total; i += batchSize {\n\t\tend := i + batchSize\n\t\tif end > total {\n\t\t\tend = total\n\t\t}\n\t\tseg := items[i:end]\n\n\t\twg.Add(1)\n\t\tgo func(items []*fastly.ConfigStoreItem) {\n\t\t\tsemaphore <- struct{}{}\n\t\t\tdefer func() { <-semaphore }()\n\t\t\tdefer wg.Done()\n\n\t\t\tfor _, item := range items {\n\t\t\t\ttext.Output(out, \"Deleting key: %s\", item.Key)\n\t\t\t\terr := c.Globals.APIClient.DeleteConfigStoreItem(context.TODO(), &fastly.DeleteConfigStoreItemInput{StoreID: c.input.StoreID, Key: item.Key})\n\t\t\t\tif err != nil {\n\t\t\t\t\tc.Globals.ErrLog.Add(fmt.Errorf(\"failed to delete key '%s': %s\", item.Key, err))\n\t\t\t\t\tmu.Lock()\n\t\t\t\t\tfailedKeys = append(failedKeys, item.Key)\n\t\t\t\t\tmu.Unlock()\n\t\t\t\t}\n\t\t\t}\n\t\t}(seg)\n\t}\n\n\twg.Wait()\n\tclose(semaphore)\n\n\tif len(failedKeys) > 0 {\n\t\treturn fmt.Errorf(\"failed to delete keys: %s\", strings.Join(failedKeys, \", \"))\n\t}\n\n\ttext.Success(out, \"\\nDeleted all keys from Config Store '%s'\", c.input.StoreID)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/configstoreentry/describe.go",
    "content": "package configstoreentry\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// NewDescribeCommand returns a usable command registered under the parent.\nfunc NewDescribeCommand(parent argparser.Registerer, g *global.Data) *DescribeCommand {\n\tc := DescribeCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\n\tc.CmdClause = parent.Command(\"describe\", \"Retrieve a single config store item\").Alias(\"get\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        \"key\",\n\t\tShort:       'k',\n\t\tDescription: \"Item name\",\n\t\tDst:         &c.input.Key,\n\t\tRequired:    true,\n\t})\n\tc.RegisterFlag(argparser.StoreIDFlag(&c.input.StoreID)) // --store-id\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\n\treturn &c\n}\n\n// DescribeCommand calls the Fastly API to describe an appropriate resource.\ntype DescribeCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\tinput fastly.GetConfigStoreItemInput\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DescribeCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\to, err := c.Globals.APIClient.GetConfigStoreItem(context.TODO(), &c.input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\ttext.PrintConfigStoreItem(out, \"\", o)\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/configstoreentry/doc.go",
    "content": "// Package configstoreentry contains commands to inspect and manipulate Fastly\n// edge config store items.\n//\n// https://www.fastly.com/documentation/reference/api/services/resources/config-store-item\npackage configstoreentry\n"
  },
  {
    "path": "pkg/commands/configstoreentry/errors.go",
    "content": "package configstoreentry\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n)\n\nconst (\n\tmaxKeyLen = 256\n\t// maxValueLen is the maximum length of Config Store entry's value. It's set to 64k,\n\t// even though customers may have a smaller limit. The API will reject requests if the\n\t// value is larger than the customer's limit.\n\tmaxValueLen = 2 << 15\n)\n\nvar errNoSTDINData = fsterr.RemediationError{\n\tInner:       errors.New(\"unable to read from STDIN\"),\n\tRemediation: \"Provide data to STDIN, or use --value to specify item value\",\n}\n\nvar errNoValue = fsterr.RemediationError{\n\tInner:       errors.New(\"no value provided\"),\n\tRemediation: \"Use --value or --stdin to specify item value\",\n}\n\nvar errMaxKeyLen = fsterr.RemediationError{\n\tInner:       errors.New(\"key max length\"),\n\tRemediation: fmt.Sprintf(\"Key must be less than or equal to %d bytes\", maxKeyLen),\n}\n\nvar errMaxValueLen = fsterr.RemediationError{\n\tInner:       errors.New(\"value max length\"),\n\tRemediation: fmt.Sprintf(\"Value must be less than or equal to %d bytes\", maxValueLen),\n}\n"
  },
  {
    "path": "pkg/commands/configstoreentry/list.go",
    "content": "package configstoreentry\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\n\tc.CmdClause = parent.Command(\"list\", \"List config store items\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StoreIDFlag(&c.input.StoreID)) // --store-id\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\n\treturn &c\n}\n\n// ListCommand calls the Fastly API to list appropriate resources.\ntype ListCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\tinput fastly.ListConfigStoreItemsInput\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *ListCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\to, err := c.Globals.APIClient.ListConfigStoreItems(context.TODO(), &c.input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\ttext.PrintConfigStoreItemsTbl(out, o)\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/configstoreentry/root.go",
    "content": "package configstoreentry\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"config-store-entry\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tc := RootCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\n\tc.CmdClause = parent.Command(CommandName, \"Manipulate Fastly Config Store items\")\n\n\treturn &c\n}\n\n// RootCommand is the parent command for all subcommands.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/configstoreentry/update.go",
    "content": "package configstoreentry\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\n\tc.CmdClause = parent.Command(\"update\", \"Update a config store item\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        \"key\",\n\t\tShort:       'k',\n\t\tDescription: \"Item name\",\n\t\tDst:         &c.input.Key,\n\t\tRequired:    true,\n\t})\n\tc.RegisterFlag(argparser.StoreIDFlag(&c.input.StoreID)) // --store-id\n\n\t// One of these must be set.\n\tc.RegisterFlagBool(argparser.BoolFlagOpts{\n\t\tName:        \"stdin\",\n\t\tDescription: \"Read item value from STDIN. If set, --value will be ignored\",\n\t\tDst:         &c.stdin,\n\t\tRequired:    false,\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        \"value\",\n\t\tDescription: \"Item value. Required unless --stdin is set\",\n\t\tDst:         &c.input.Value,\n\t\tRequired:    false,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\tc.RegisterFlagBool(argparser.BoolFlagOpts{\n\t\tName:        \"upsert\",\n\t\tShort:       'u',\n\t\tDescription: \"If true, insert or update an entry in a config store. Otherwise, only update\",\n\t\tDst:         &c.input.Upsert,\n\t})\n\n\treturn &c\n}\n\n// UpdateCommand calls the Fastly API to update an appropriate resource.\ntype UpdateCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\tinput fastly.UpdateConfigStoreItemInput\n\tstdin bool\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *UpdateCommand) Exec(in io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tif c.stdin {\n\t\t// Determine if 'in' has data available.\n\t\tif in == nil || text.IsTTY(in) {\n\t\t\treturn errNoSTDINData\n\t\t}\n\n\t\t// Must read one past limit, since LimitReader returns EOF\n\t\t// once it reads its limited number of bytes.\n\t\tvalue, err := io.ReadAll(io.LimitReader(in, maxValueLen+1))\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tc.input.Value = string(value)\n\t} else if c.input.Value == \"\" {\n\t\treturn errNoValue\n\t}\n\n\tif len(c.input.Key) > maxKeyLen {\n\t\treturn errMaxKeyLen\n\t}\n\tif len(c.input.Value) > maxValueLen {\n\t\treturn errMaxValueLen\n\t}\n\n\to, err := c.Globals.APIClient.UpdateConfigStoreItem(context.TODO(), &c.input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\tvar action string\n\tif c.input.Upsert {\n\t\t// The Fastly API does not provide a way to determine if\n\t\t// an item was created or updated when using 'upsert' operation.\n\t\taction = \"Created or updated\"\n\t} else {\n\t\taction = \"Updated\"\n\t}\n\n\ttext.Success(out, \"%s config store item %s in store %s\", action, o.Key, o.StoreID)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/dashboard/create.go",
    "content": "package dashboard\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, globals *global.Data) *CreateCommand {\n\tvar c CreateCommand\n\tc.CmdClause = parent.Command(\"create\", \"Create a custom dashboard\").Alias(\"add\")\n\tc.Globals = globals\n\n\t// Required flags\n\tc.CmdClause.Flag(\"name\", \"A human-readable name for the dashboard\").Short('n').Required().StringVar(&c.name) // --name\n\n\t// Optional flags\n\tc.RegisterFlagBool(c.JSONFlag())                                                                                                  // --json\n\tc.CmdClause.Flag(\"description\", \"A short description of the dashboard\").Action(c.description.Set).StringVar(&c.description.Value) // --description\n\n\treturn &c\n}\n\n// CreateCommand calls the Fastly API to create an appropriate resource.\ntype CreateCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tname        string\n\tdescription argparser.OptionalString\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *CreateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tinput := c.constructInput()\n\tdashboard, err := c.Globals.APIClient.CreateObservabilityCustomDashboard(context.TODO(), input)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, dashboard); ok {\n\t\treturn err\n\t}\n\n\ttext.Success(out, `Created Custom Dashboard \"%s\" (id: %s)`, dashboard.Name, dashboard.ID)\n\treturn nil\n}\n\n// constructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *CreateCommand) constructInput() *fastly.CreateObservabilityCustomDashboardInput {\n\tinput := fastly.CreateObservabilityCustomDashboardInput{\n\t\tName:  c.name,\n\t\tItems: []fastly.DashboardItem{},\n\t}\n\n\tif c.description.WasSet {\n\t\tinput.Description = &c.description.Value\n\t}\n\n\treturn &input\n}\n"
  },
  {
    "path": "pkg/commands/dashboard/dashboard_test.go",
    "content": "package dashboard_test\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\troot \"github.com/fastly/cli/pkg/commands/dashboard\"\n\t\"github.com/fastly/cli/pkg/mock\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n)\n\nconst (\n\tuserID = \"test-user\"\n)\n\nfunc TestCreate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName: \"validate CreateObservabilityCustomDashboard API error\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tCreateObservabilityCustomDashboardFn: func(_ context.Context, _ *fastly.CreateObservabilityCustomDashboardInput) (*fastly.ObservabilityCustomDashboard, error) {\n\t\t\t\t\treturn nil, testutil.Err\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:      \"--name Testing\",\n\t\t\tWantError: testutil.Err.Error(),\n\t\t},\n\t\t{\n\t\t\tName: \"validate missing --name flag\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tCreateObservabilityCustomDashboardFn: func(_ context.Context, _ *fastly.CreateObservabilityCustomDashboardInput) (*fastly.ObservabilityCustomDashboard, error) {\n\t\t\t\t\treturn nil, testutil.Err\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:      \"\",\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --description flag\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tCreateObservabilityCustomDashboardFn: func(_ context.Context, i *fastly.CreateObservabilityCustomDashboardInput) (*fastly.ObservabilityCustomDashboard, error) {\n\t\t\t\t\treturn &fastly.ObservabilityCustomDashboard{\n\t\t\t\t\t\tID:   \"beepboop\",\n\t\t\t\t\t\tName: i.Name,\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:       \"--name Testing\",\n\t\t\tWantOutput: `Created Custom Dashboard \"Testing\" (id: beepboop)`,\n\t\t},\n\t\t{\n\t\t\tName: \"validate CreateObservabilityCustomDashboard API success\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tCreateObservabilityCustomDashboardFn: func(_ context.Context, i *fastly.CreateObservabilityCustomDashboardInput) (*fastly.ObservabilityCustomDashboard, error) {\n\t\t\t\t\treturn &fastly.ObservabilityCustomDashboard{\n\t\t\t\t\t\tID:          \"beepboop\",\n\t\t\t\t\t\tName:        i.Name,\n\t\t\t\t\t\tDescription: *i.Description,\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:       \"--name Testing --description foo\",\n\t\t\tWantOutput: `Created Custom Dashboard \"Testing\" (id: beepboop)`,\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, \"create\"}, scenarios)\n}\n\nfunc TestDelete(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --id flag\",\n\t\t\tWantError: \"error parsing arguments: required flag --id not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate DeleteObservabilityCustomDashboard API error\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tDeleteObservabilityCustomDashboardFn: func(_ context.Context, _ *fastly.DeleteObservabilityCustomDashboardInput) error {\n\t\t\t\t\treturn testutil.Err\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:      \"--id beepboop\",\n\t\t\tWantError: testutil.Err.Error(),\n\t\t},\n\t\t{\n\t\t\tName: \"validate DeleteObservabilityCustomDashboard API success\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tDeleteObservabilityCustomDashboardFn: func(_ context.Context, _ *fastly.DeleteObservabilityCustomDashboardInput) error {\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:       \"--id beepboop\",\n\t\t\tWantOutput: \"Deleted Custom Dashboard beepboop\",\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, \"delete\"}, scenarios)\n}\n\nfunc TestDescribe(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --id flag\",\n\t\t\tWantError: \"error parsing arguments: required flag --id not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate GetObservabilityCustomDashboard API error\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetObservabilityCustomDashboardFn: func(_ context.Context, _ *fastly.GetObservabilityCustomDashboardInput) (*fastly.ObservabilityCustomDashboard, error) {\n\t\t\t\t\treturn nil, testutil.Err\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:      \"--id beepboop\",\n\t\t\tWantError: testutil.Err.Error(),\n\t\t},\n\t\t{\n\t\t\tName: \"validate GetObservabilityCustomDashboard API success\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetObservabilityCustomDashboardFn: getObservabilityCustomDashboard,\n\t\t\t},\n\t\t\tArgs:       \"--id beepboop\",\n\t\t\tWantOutput: \"Name: Testing\\nDescription: This is a test dashboard\\nItems:\\nMeta:\\n    Created at: 2021-06-15 23:00:00 +0000 UTC\\n    Updated at: 2021-06-15 23:00:00 +0000 UTC\\n    Created by: test-user\\n    Updated by: test-user\\n\",\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, \"describe\"}, scenarios)\n}\n\nfunc TestList(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName: \"validate ListObservabilityCustomDashboards API error\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tListObservabilityCustomDashboardsFn: func(_ context.Context, _ *fastly.ListObservabilityCustomDashboardsInput) (*fastly.ListDashboardsResponse, error) {\n\t\t\t\t\treturn nil, testutil.Err\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: testutil.Err.Error(),\n\t\t},\n\t\t{\n\t\t\tName: \"validate ListObservabilityCustomDashboards API success\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tListObservabilityCustomDashboardsFn: listObservabilityCustomDashboards,\n\t\t\t},\n\t\t\tWantOutput: \"DASHBOARD ID  NAME       DESCRIPTION  # ITEMS\\nbeepboop      Testing 1  This is #1   0\\nbleepblorp    Testing 2  This is #2   0\\n\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate --verbose flag\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tListObservabilityCustomDashboardsFn: listObservabilityCustomDashboards,\n\t\t\t},\n\t\t\tArgs:       \"--verbose\",\n\t\t\tWantOutput: \"Fastly API endpoint: https://api.fastly.com\\nFastly API token provided via config file (auth: user)\\n\\nName: Testing 1\\nDescription: This is #1\\nItems:\\nMeta:\\n    Created at: 2021-06-15 23:00:00 +0000 UTC\\n    Updated at: 2021-06-15 23:00:00 +0000 UTC\\n    Created by: test-user\\n    Updated by: test-user\\n\\nName: Testing 2\\nDescription: This is #2\\nItems:\\nMeta:\\n    Created at: 2021-06-15 23:00:00 +0000 UTC\\n    Updated at: 2021-06-15 23:00:00 +0000 UTC\\n    Created by: test-user\\n    Updated by: test-user\\n\\n\",\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, \"list\"}, scenarios)\n}\n\nfunc TestUpdate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --id flag\",\n\t\t\tWantError: \"error parsing arguments: required flag --id not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate UpdateObservabilityCustomDashboard API error\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tUpdateObservabilityCustomDashboardFn: func(_ context.Context, _ *fastly.UpdateObservabilityCustomDashboardInput) (*fastly.ObservabilityCustomDashboard, error) {\n\t\t\t\t\treturn nil, testutil.Err\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:      \"--id beepboop\",\n\t\t\tWantError: testutil.Err.Error(),\n\t\t},\n\t\t{\n\t\t\tName: \"validate UpdateObservabilityCustomDashboard API success\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tUpdateObservabilityCustomDashboardFn: func(_ context.Context, i *fastly.UpdateObservabilityCustomDashboardInput) (*fastly.ObservabilityCustomDashboard, error) {\n\t\t\t\t\treturn &fastly.ObservabilityCustomDashboard{\n\t\t\t\t\t\tID:          *i.ID,\n\t\t\t\t\t\tName:        *i.Name,\n\t\t\t\t\t\tDescription: *i.Description,\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:       \"--id beepboop --name Foo --description Bleepblorp\",\n\t\t\tWantOutput: \"SUCCESS: Updated Custom Dashboard \\\"Foo\\\" (id: beepboop)\\n\",\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, \"update\"}, scenarios)\n}\n\nfunc getObservabilityCustomDashboard(_ context.Context, i *fastly.GetObservabilityCustomDashboardInput) (*fastly.ObservabilityCustomDashboard, error) {\n\tt := testutil.Date\n\n\treturn &fastly.ObservabilityCustomDashboard{\n\t\tCreatedAt:   t,\n\t\tCreatedBy:   userID,\n\t\tDescription: \"This is a test dashboard\",\n\t\tID:          *i.ID,\n\t\tItems:       []fastly.DashboardItem{},\n\t\tName:        \"Testing\",\n\t\tUpdatedAt:   t,\n\t\tUpdatedBy:   userID,\n\t}, nil\n}\n\nfunc listObservabilityCustomDashboards(_ context.Context, _ *fastly.ListObservabilityCustomDashboardsInput) (*fastly.ListDashboardsResponse, error) {\n\tt := testutil.Date\n\tvs := &fastly.ListDashboardsResponse{\n\t\tData: []fastly.ObservabilityCustomDashboard{{\n\t\t\tCreatedAt:   t,\n\t\t\tCreatedBy:   userID,\n\t\t\tDescription: \"This is #1\",\n\t\t\tID:          \"beepboop\",\n\t\t\tItems:       []fastly.DashboardItem{},\n\t\t\tName:        \"Testing 1\",\n\t\t\tUpdatedAt:   t,\n\t\t\tUpdatedBy:   userID,\n\t\t}, {\n\t\t\tCreatedAt:   t,\n\t\t\tCreatedBy:   userID,\n\t\t\tDescription: \"This is #2\",\n\t\t\tID:          \"bleepblorp\",\n\t\t\tItems:       []fastly.DashboardItem{},\n\t\t\tName:        \"Testing 2\",\n\t\t\tUpdatedAt:   t,\n\t\t\tUpdatedBy:   userID,\n\t\t}},\n\t\tMeta: fastly.DashboardMeta{},\n\t}\n\n\treturn vs, nil\n}\n"
  },
  {
    "path": "pkg/commands/dashboard/delete.go",
    "content": "package dashboard\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, globals *global.Data) *DeleteCommand {\n\tvar c DeleteCommand\n\tc.CmdClause = parent.Command(\"delete\", \"Delete a custom dashboard\").Alias(\"remove\")\n\tc.Globals = globals\n\n\t// Required flags\n\tc.CmdClause.Flag(\"id\", \"ID of the Dashboard to delete\").Required().StringVar(&c.dashboardID)\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\n\treturn &c\n}\n\n// DeleteCommand calls the Fastly API to delete an appropriate resource.\ntype DeleteCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tdashboardID string\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DeleteCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tinput := c.constructInput()\n\terr := c.Globals.APIClient.DeleteObservabilityCustomDashboard(context.TODO(), input)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif c.JSONOutput.Enabled {\n\t\to := struct {\n\t\t\tID      string `json:\"dashboard_id\"`\n\t\t\tDeleted bool   `json:\"deleted\"`\n\t\t}{\n\t\t\tc.dashboardID,\n\t\t\ttrue,\n\t\t}\n\t\t_, err := c.WriteJSON(out, o)\n\t\treturn err\n\t}\n\n\ttext.Success(out, `Deleted Custom Dashboard %s`, fastly.ToValue(input.ID))\n\treturn nil\n}\n\n// constructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *DeleteCommand) constructInput() *fastly.DeleteObservabilityCustomDashboardInput {\n\tvar input fastly.DeleteObservabilityCustomDashboardInput\n\n\tinput.ID = &c.dashboardID\n\n\treturn &input\n}\n"
  },
  {
    "path": "pkg/commands/dashboard/describe.go",
    "content": "package dashboard\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/dashboard/printer\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// NewDescribeCommand returns a usable command registered under the parent.\nfunc NewDescribeCommand(parent argparser.Registerer, globals *global.Data) *DescribeCommand {\n\tvar c DescribeCommand\n\tc.CmdClause = parent.Command(\"describe\", \"Show detailed information about a custom dashboard\").Alias(\"get\")\n\tc.Globals = globals\n\n\t// Required flags\n\tc.CmdClause.Flag(\"id\", \"ID of the Dashboard to describe\").Required().StringVar(&c.dashboardID)\n\n\t// Optional flags\n\tc.RegisterFlagBool(c.JSONFlag())\n\treturn &c\n}\n\n// DescribeCommand calls the Fastly API to describe an appropriate resource.\ntype DescribeCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tdashboardID string\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DescribeCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tinput := c.constructInput()\n\tdashboard, err := c.Globals.APIClient.GetObservabilityCustomDashboard(context.TODO(), input)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, dashboard); ok {\n\t\treturn err\n\t}\n\n\tprinter.PrintDashboard(out, 0, dashboard)\n\treturn nil\n}\n\n// constructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *DescribeCommand) constructInput() *fastly.GetObservabilityCustomDashboardInput {\n\tvar input fastly.GetObservabilityCustomDashboardInput\n\n\tinput.ID = &c.dashboardID\n\n\treturn &input\n}\n"
  },
  {
    "path": "pkg/commands/dashboard/doc.go",
    "content": "// Package dashboard contains commands to manage custom Observability Dashboards.\npackage dashboard\n"
  },
  {
    "path": "pkg/commands/dashboard/item/common.go",
    "content": "package item\n\nvar (\n\tsourceTypes        = []string{\"stats.domain\", \"stats.edge\", \"stats.origin\"}\n\tvisualizationTypes = []string{\"chart\"}\n\tplotTypes          = []string{\"bar\", \"donut\", \"line\", \"single-metric\"}\n\tcalculationMethods = []string{\"avg\", \"sum\", \"min\", \"max\", \"latest\", \"p95\"}\n\tformats            = []string{\"number\", \"bytes\", \"percent\", \"requests\", \"responses\", \"seconds\", \"milliseconds\", \"ratio\", \"bitrate\"}\n)\n"
  },
  {
    "path": "pkg/commands/dashboard/item/create.go",
    "content": "package item\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/dashboard/printer\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, globals *global.Data) *CreateCommand {\n\tvar c CreateCommand\n\tc.CmdClause = parent.Command(\"create\", \"Create a custom dashboard item\").Alias(\"add\")\n\tc.Globals = globals\n\n\t// Required flags\n\tc.CmdClause.Flag(\"dashboard-id\", \"ID of the Dashboard to contain the item\").Required().StringVar(&c.dashboardID)\n\tc.CmdClause.Flag(\"title\", \"A human-readable title for the dashboard item\").Required().StringVar(&c.title)\n\tc.CmdClause.Flag(\"subtitle\", \"A human-readable subtitle for the dashboard item. Often a description of the visualization\").Required().StringVar(&c.subtitle)\n\tc.CmdClause.Flag(\"source-type\", \"The source of the data to display\").Required().HintOptions(sourceTypes...).EnumVar(&c.sourceType, sourceTypes...)\n\tc.CmdClause.Flag(\"metric\", \"The metrics to visualize. Valid options depend on the selected data source. Set flag multiple times to include multiple metrics\").Required().StringsVar(&c.metrics)\n\tc.CmdClause.Flag(\"plot-type\", \"The type of chart to display\").Required().HintOptions(plotTypes...).EnumVar(&c.plotType, plotTypes...)\n\n\t// Optional flags\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\tc.CmdClause.Flag(\"visualization-type\", `The type of visualization to display. Currently, only \"chart\" is supported`).Default(\"chart\").HintOptions(visualizationTypes...).EnumVar(&c.vizType, visualizationTypes...)\n\tc.CmdClause.Flag(\"calculation-method\", \"The aggregation function to apply to the dataset\").Action(c.calculationMethod.Set).HintOptions(calculationMethods...).EnumVar(&c.calculationMethod.Value, calculationMethods...) // --calculation-method\n\tc.CmdClause.Flag(\"format\", \"The units to use to format the data\").Action(c.format.Set).HintOptions(formats...).EnumVar(&c.format.Value, formats...)                                                                      // --format\n\tc.CmdClause.Flag(\"span\", `The number of columns for the dashboard item to span. Dashboards are rendered on a 12-column grid on \"desktop\" screen sizes`).Default(\"4\").Uint8Var(&c.span)\n\n\treturn &c\n}\n\n// CreateCommand calls the Fastly API to create an appropriate resource.\ntype CreateCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// required\n\tdashboardID string\n\ttitle       string\n\tsubtitle    string\n\tsourceType  string\n\tmetrics     []string\n\tplotType    string\n\n\t// optional\n\tvizType           string\n\tcalculationMethod argparser.OptionalString\n\tformat            argparser.OptionalString\n\tspan              uint8\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *CreateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\td, err := c.Globals.APIClient.GetObservabilityCustomDashboard(context.TODO(), &fastly.GetObservabilityCustomDashboardInput{ID: &c.dashboardID})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tinput := c.constructInput(d)\n\td, err = c.Globals.APIClient.UpdateObservabilityCustomDashboard(context.TODO(), input)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, d); ok {\n\t\treturn err\n\t}\n\n\ttext.Success(out, `Added item to Custom Dashboard \"%s\" (id: %s)`, d.Name, d.ID)\n\t// Summary isn't useful for a single dashboard, so print verbose by default\n\tprinter.PrintDashboard(out, 0, d)\n\treturn nil\n}\n\n// constructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *CreateCommand) constructInput(d *fastly.ObservabilityCustomDashboard) *fastly.UpdateObservabilityCustomDashboardInput {\n\tinput := fastly.UpdateObservabilityCustomDashboardInput{\n\t\tID:          &d.ID,\n\t\tName:        &d.Name,\n\t\tDescription: &d.Description,\n\t\tItems:       &d.Items,\n\t}\n\titem := fastly.DashboardItem{\n\t\tTitle:    c.title,\n\t\tSubtitle: c.subtitle,\n\t\tSpan:     c.span,\n\t\tDataSource: fastly.DashboardDataSource{\n\t\t\tType: fastly.DashboardSourceType(c.sourceType),\n\t\t\tConfig: fastly.DashboardSourceConfig{\n\t\t\t\tMetrics: c.metrics,\n\t\t\t},\n\t\t},\n\t\tVisualization: fastly.DashboardVisualization{\n\t\t\tType: fastly.VisualizationType(c.vizType),\n\t\t\tConfig: fastly.VisualizationConfig{\n\t\t\t\tPlotType: fastly.PlotType(c.plotType),\n\t\t\t},\n\t\t},\n\t}\n\tif c.calculationMethod.WasSet {\n\t\titem.Visualization.Config.CalculationMethod = fastly.ToPointer(fastly.CalculationMethod(c.calculationMethod.Value))\n\t}\n\tif c.format.WasSet {\n\t\titem.Visualization.Config.Format = fastly.ToPointer(fastly.VisualizationFormat(c.format.Value))\n\t}\n\n\t*input.Items = append(*input.Items, item)\n\n\treturn &input\n}\n"
  },
  {
    "path": "pkg/commands/dashboard/item/delete.go",
    "content": "package item\n\nimport (\n\t\"context\"\n\t\"io\"\n\t\"slices\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, globals *global.Data) *DeleteCommand {\n\tvar c DeleteCommand\n\tc.CmdClause = parent.Command(\"delete\", \"Delete a custom dashboard item\").Alias(\"add\")\n\tc.Globals = globals\n\n\t// Required flags\n\tc.CmdClause.Flag(\"dashboard-id\", \"ID of the Dashboard containing the item\").Required().StringVar(&c.dashboardID) // --dashboard-id\n\tc.CmdClause.Flag(\"item-id\", \"ID of the Item to be deleted\").Required().StringVar(&c.itemID)                      // --item-id\n\n\t// Optional flags\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\n\treturn &c\n}\n\n// DeleteCommand calls the Fastly API to delete an appropriate resource.\ntype DeleteCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// required\n\tdashboardID string\n\titemID      string\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DeleteCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\td, err := c.Globals.APIClient.GetObservabilityCustomDashboard(context.TODO(), &fastly.GetObservabilityCustomDashboardInput{ID: &c.dashboardID})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tsuccess := false\n\tnumItems := len(d.Items)\n\n\tif slices.ContainsFunc(d.Items, func(di fastly.DashboardItem) bool {\n\t\treturn di.ID == c.itemID\n\t}) {\n\t\tinput := c.constructInput(d)\n\t\td, err = c.Globals.APIClient.UpdateObservabilityCustomDashboard(context.TODO(), input)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tsuccess = true\n\t}\n\n\tif c.JSONOutput.Enabled {\n\t\to := struct {\n\t\t\tID       string                               `json:\"item_id\"`\n\t\t\tDeleted  bool                                 `json:\"deleted\"`\n\t\t\tNewState *fastly.ObservabilityCustomDashboard `json:\"dashboard_state\"`\n\t\t}{\n\t\t\tc.itemID,\n\t\t\tsuccess,\n\t\t\td,\n\t\t}\n\t\t_, err := c.WriteJSON(out, o)\n\t\treturn err\n\t}\n\n\tif success {\n\t\ttext.Success(out, `Removed %d dashboard item(s) from Custom Dashboard \"%s\" (dashboardID: %s)`, (numItems - (len(d.Items))), d.Name, d.ID)\n\t} else {\n\t\ttext.Warning(out, \"dashboard (%s) has no item with ID (%s)\", d.ID, c.itemID)\n\t}\n\treturn nil\n}\n\n// constructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *DeleteCommand) constructInput(d *fastly.ObservabilityCustomDashboard) *fastly.UpdateObservabilityCustomDashboardInput {\n\tinput := fastly.UpdateObservabilityCustomDashboardInput{\n\t\tID:          &d.ID,\n\t\tName:        &d.Name,\n\t\tDescription: &d.Description,\n\t}\n\n\titems := slices.DeleteFunc(d.Items, func(di fastly.DashboardItem) bool {\n\t\treturn di.ID == c.itemID\n\t})\n\tinput.Items = &items\n\n\treturn &input\n}\n"
  },
  {
    "path": "pkg/commands/dashboard/item/describe.go",
    "content": "package item\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/dashboard/printer\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// NewDescribeCommand returns a usable command registered under the parent.\nfunc NewDescribeCommand(parent argparser.Registerer, globals *global.Data) *DescribeCommand {\n\tvar c DescribeCommand\n\tc.CmdClause = parent.Command(\"describe\", \"Describe a custom dashboard item\").Alias(\"add\")\n\tc.Globals = globals\n\n\t// Required flags\n\tc.CmdClause.Flag(\"dashboard-id\", \"ID of the Dashboard containing the item\").Required().StringVar(&c.dashboardID) // --dashboard-id\n\tc.CmdClause.Flag(\"item-id\", \"ID of the Item to be described\").Required().StringVar(&c.itemID)                    // --item-id\n\n\t// Optional flags\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\n\treturn &c\n}\n\n// DescribeCommand calls the Fastly API to describe an appropriate resource.\ntype DescribeCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// required\n\tdashboardID string\n\titemID      string\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DescribeCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tinput := c.constructInput()\n\n\td, err := c.Globals.APIClient.GetObservabilityCustomDashboard(context.TODO(), input)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tdi, err := getItemFromDashboard(context.TODO(), d, c.itemID)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif c.JSONOutput.Enabled {\n\t\t_, err := c.WriteJSON(out, di)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t} else {\n\t\tprinter.PrintItem(out, 0, di)\n\t}\n\n\treturn nil\n}\n\n// constructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *DescribeCommand) constructInput() *fastly.GetObservabilityCustomDashboardInput {\n\treturn &fastly.GetObservabilityCustomDashboardInput{ID: &c.dashboardID}\n}\n\nfunc getItemFromDashboard(_ context.Context, d *fastly.ObservabilityCustomDashboard, itemID string) (*fastly.DashboardItem, error) {\n\tfor _, di := range d.Items {\n\t\tif di.ID == itemID {\n\t\t\treturn &di, nil\n\t\t}\n\t}\n\treturn nil, fmt.Errorf(\"could not find item with ID (%s) in Dashboard (%s)\", itemID, d.ID)\n}\n"
  },
  {
    "path": "pkg/commands/dashboard/item/doc.go",
    "content": "// Package item contains commands to inspect and manipulate the contents of\n// a Custom Observability Dashboard.\npackage item\n"
  },
  {
    "path": "pkg/commands/dashboard/item/item_test.go",
    "content": "package item_test\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\troot \"github.com/fastly/cli/pkg/commands/dashboard\"\n\tsub \"github.com/fastly/cli/pkg/commands/dashboard/item\"\n\t\"github.com/fastly/cli/pkg/mock\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n)\n\nvar (\n\ttestDate             = testutil.Date\n\tuserID               = \"test-user\"\n\tdashboardID          = \"beepboop\"\n\titemID               = \"bleepblorp\"\n\tdashboardName        = \"Foo\"\n\tdashboardDescription = \"Testing...\"\n\ttitle                = \"Title\"\n\tsubtitle             = \"Subtitle\"\n\tsourceType           = \"stats.edge\"\n\tmetrics              = \"requests\"\n\tplotType             = \"line\"\n\tvizType              = \"chart\"\n\tcalculationMethod    = \"latest\"\n\tformat               = \"requests\"\n\tspan                 = 8\n\tdefaultItem          = fastly.DashboardItem{\n\t\tDataSource: fastly.DashboardDataSource{\n\t\t\tConfig: fastly.DashboardSourceConfig{\n\t\t\t\tMetrics: []string{metrics},\n\t\t\t},\n\t\t\tType: fastly.DashboardSourceType(sourceType),\n\t\t},\n\t\tID:       itemID,\n\t\tSpan:     uint8(span),\n\t\tSubtitle: subtitle,\n\t\tTitle:    title,\n\t\tVisualization: fastly.DashboardVisualization{\n\t\t\tConfig: fastly.VisualizationConfig{\n\t\t\t\tCalculationMethod: fastly.ToPointer(fastly.CalculationMethod(calculationMethod)),\n\t\t\t\tFormat:            fastly.ToPointer(fastly.VisualizationFormat(format)),\n\t\t\t\tPlotType:          fastly.PlotType(plotType),\n\t\t\t},\n\t\t\tType: fastly.VisualizationType(vizType),\n\t\t},\n\t}\n\tdefaultDashboard = func() fastly.ObservabilityCustomDashboard {\n\t\treturn fastly.ObservabilityCustomDashboard{\n\t\t\tCreatedAt:   testDate,\n\t\t\tCreatedBy:   userID,\n\t\t\tDescription: dashboardDescription,\n\t\t\tID:          dashboardID,\n\t\t\tItems:       []fastly.DashboardItem{defaultItem},\n\t\t\tName:        dashboardName,\n\t\t\tUpdatedAt:   testDate,\n\t\t\tUpdatedBy:   userID,\n\t\t}\n\t}\n)\n\nfunc TestCreate(t *testing.T) {\n\tallRequiredFlags := fmt.Sprintf(\"--dashboard-id %s --title %s --subtitle %s --source-type %s --metric %s --plot-type %s\", dashboardID, title, subtitle, sourceType, metrics, plotType)\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --dashboard-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--title %s --subtitle %s --source-type %s --metric %s --plot-type %s\", title, subtitle, sourceType, metrics, plotType),\n\t\t\tWantError: \"error parsing arguments: required flag --dashboard-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --title flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--dashboard-id %s --subtitle %s --source-type %s --metric %s --plot-type %s\", dashboardID, subtitle, sourceType, metrics, plotType),\n\t\t\tWantError: \"error parsing arguments: required flag --title not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --subtitle flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--dashboard-id %s --title %s --source-type %s --metric %s --plot-type %s\", dashboardID, title, sourceType, metrics, plotType),\n\t\t\tWantError: \"error parsing arguments: required flag --subtitle not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --source-type flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--dashboard-id %s --title %s --subtitle %s --metric %s --plot-type %s\", dashboardID, title, subtitle, metrics, plotType),\n\t\t\tWantError: \"error parsing arguments: required flag --source-type not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --metric flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--dashboard-id %s --title %s --subtitle %s --source-type %s --plot-type %s\", dashboardID, title, subtitle, sourceType, plotType),\n\t\t\tWantError: \"error parsing arguments: required flag --metric not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --plot-type flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--dashboard-id %s --title %s --subtitle %s --source-type %s --metric %s\", dashboardID, title, subtitle, sourceType, metrics),\n\t\t\tWantError: \"error parsing arguments: required flag --plot-type not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate multiple --metric flag\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetObservabilityCustomDashboardFn:    getDashboardOK,\n\t\t\t\tUpdateObservabilityCustomDashboardFn: updateDashboardOK,\n\t\t\t},\n\t\t\tArgs:       allRequiredFlags + \" --metric responses\",\n\t\t\tWantOutput: \"Metrics: requests, responses\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate all required flags\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetObservabilityCustomDashboardFn:    getDashboardOK,\n\t\t\t\tUpdateObservabilityCustomDashboardFn: updateDashboardOK,\n\t\t\t},\n\t\t\tArgs:       allRequiredFlags,\n\t\t\tWantOutput: `Added item to Custom Dashboard \"Foo\"`,\n\t\t},\n\t\t{\n\t\t\tName: \"validate all optional flags\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetObservabilityCustomDashboardFn:    getDashboardOK,\n\t\t\t\tUpdateObservabilityCustomDashboardFn: updateDashboardOK,\n\t\t\t},\n\t\t\tArgs:       fmt.Sprintf(\"%s --visualization-type %s --calculation-method %s --format %s --span %d\", allRequiredFlags, vizType, calculationMethod, format, span),\n\t\t\tWantOutput: `Added item to Custom Dashboard \"Foo\"`,\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"create\"}, scenarios)\n}\n\nfunc TestDelete(t *testing.T) {\n\tallRequiredFlags := fmt.Sprintf(\"--dashboard-id %s --item-id %s\", dashboardID, itemID)\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --dashboard-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--item-id %s\", itemID),\n\t\t\tWantError: \"error parsing arguments: required flag --dashboard-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --item-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--dashboard-id %s\", dashboardID),\n\t\t\tWantError: \"error parsing arguments: required flag --item-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate all required flags\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetObservabilityCustomDashboardFn:    getDashboardOK,\n\t\t\t\tUpdateObservabilityCustomDashboardFn: updateDashboardEmpty,\n\t\t\t},\n\t\t\tArgs:       allRequiredFlags,\n\t\t\tWantOutput: `Removed 1 dashboard item(s) from Custom Dashboard \"Foo\"`,\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"delete\"}, scenarios)\n}\n\nfunc TestDescribe(t *testing.T) {\n\tallRequiredFlags := fmt.Sprintf(\"--dashboard-id %s --item-id %s\", dashboardID, itemID)\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --dashboard-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--item-id %s\", itemID),\n\t\t\tWantError: \"error parsing arguments: required flag --dashboard-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --item-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--dashboard-id %s\", dashboardID),\n\t\t\tWantError: \"error parsing arguments: required flag --item-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate all required flags\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetObservabilityCustomDashboardFn:    getDashboardOK,\n\t\t\t\tUpdateObservabilityCustomDashboardFn: updateDashboardEmpty,\n\t\t\t},\n\t\t\tArgs:       allRequiredFlags,\n\t\t\tWantOutput: \"ID: bleepblorp\\nTitle: Title\\nSubtitle: Subtitle\\nSpan: 8\\nData Source:\\n    Type: stats.edge\\n    Metrics: requests\\nVisualization:\\n    Type: chart\\n    Plot Type: line\\n    Calculation Method: latest\\n    Format: requests\\n\",\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"describe\"}, scenarios)\n}\n\nfunc TestUpdate(t *testing.T) {\n\tallRequiredFlags := fmt.Sprintf(\"--dashboard-id %s --item-id %s --json\", dashboardID, itemID)\n\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --dashboard-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--item-id %s\", itemID),\n\t\t\tWantError: \"error parsing arguments: required flag --dashboard-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --item-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--dashboard-id %s\", dashboardID),\n\t\t\tWantError: \"error parsing arguments: required flag --item-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate all required flags\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetObservabilityCustomDashboardFn:    getDashboardOK,\n\t\t\t\tUpdateObservabilityCustomDashboardFn: updateDashboardOK,\n\t\t\t},\n\t\t\tArgs: allRequiredFlags,\n\t\t\tWantOutputs: []string{\n\t\t\t\t`\"name\":`, \"Foo\",\n\t\t\t\t`\"description\":`, \"Testing...\",\n\t\t\t\t`\"items\":`,\n\t\t\t\t`\"id\":`, \"bleepblorp\",\n\t\t\t\t`\"title\":`, \"Title\",\n\t\t\t\t`\"subtitle\":`, \"Subtitle\",\n\t\t\t\t`\"span\":`, \"8\",\n\t\t\t\t`\"data_source\":`,\n\t\t\t\t`\"type\":`, \"stats.edge\",\n\t\t\t\t`\"metrics\":`, \"requests\",\n\t\t\t\t`\"visualization\":`,\n\t\t\t\t`\"type\":`, \"chart\",\n\t\t\t\t`\"plot_type\":`, \"line\",\n\t\t\t\t`\"calculation_method\":`, \"latest\",\n\t\t\t\t`\"format\":`, \"requests\",\n\t\t\t\t`\"created_at\":`, \"2021-06-15T23:00:00Z\",\n\t\t\t\t`\"updated_at\":`, \"2021-06-15T23:00:00Z\",\n\t\t\t\t`\"created_by\":`, \"test-user\",\n\t\t\t\t`\"updated_by\":`, \"test-user\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --title flag\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetObservabilityCustomDashboardFn:    getDashboardOK,\n\t\t\t\tUpdateObservabilityCustomDashboardFn: updateDashboardOK,\n\t\t\t},\n\t\t\tArgs:       fmt.Sprintf(\"%s --title %s\", allRequiredFlags, \"NewTitle\"),\n\t\t\tWantOutput: `\"title\": \"NewTitle\"`,\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --subtitle flag\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetObservabilityCustomDashboardFn:    getDashboardOK,\n\t\t\t\tUpdateObservabilityCustomDashboardFn: updateDashboardOK,\n\t\t\t},\n\t\t\tArgs:       fmt.Sprintf(\"%s --subtitle %s\", allRequiredFlags, \"NewSubtitle\"),\n\t\t\tWantOutput: `\"subtitle\": \"NewSubtitle\"`,\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --span flag\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetObservabilityCustomDashboardFn:    getDashboardOK,\n\t\t\t\tUpdateObservabilityCustomDashboardFn: updateDashboardOK,\n\t\t\t},\n\t\t\tArgs:       fmt.Sprintf(\"%s --span %d\", allRequiredFlags, 12),\n\t\t\tWantOutput: `\"span\": 12`,\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --source-type flag\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetObservabilityCustomDashboardFn:    getDashboardOK,\n\t\t\t\tUpdateObservabilityCustomDashboardFn: updateDashboardOK,\n\t\t\t},\n\t\t\tArgs:       fmt.Sprintf(\"%s --source-type %s\", allRequiredFlags, \"stats.domain\"),\n\t\t\tWantOutput: `\"type\": \"stats.domain\"`,\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --metric flag\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetObservabilityCustomDashboardFn:    getDashboardOK,\n\t\t\t\tUpdateObservabilityCustomDashboardFn: updateDashboardOK,\n\t\t\t},\n\t\t\tArgs:        fmt.Sprintf(\"%s --metric %s\", allRequiredFlags, \"status_4xx\"),\n\t\t\tWantOutputs: []string{\"metrics\", \"status_4xx\"},\n\t\t},\n\t\t{\n\t\t\tName: \"validate multiple --metric flag\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetObservabilityCustomDashboardFn:    getDashboardOK,\n\t\t\t\tUpdateObservabilityCustomDashboardFn: updateDashboardOK,\n\t\t\t},\n\t\t\tArgs: fmt.Sprintf(\"%s --metric %s --metric %s --metric %s\", allRequiredFlags, \"status_2xx\", \"status_4xx\", \"status_5xx\"),\n\t\t\tWantOutputs: []string{\n\t\t\t\t\"metrics\",\n\t\t\t\t\"status_2xx\",\n\t\t\t\t\"status_4xx\",\n\t\t\t\t\"status_5xx\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --calculation-method flag\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetObservabilityCustomDashboardFn:    getDashboardOK,\n\t\t\t\tUpdateObservabilityCustomDashboardFn: updateDashboardOK,\n\t\t\t},\n\t\t\tArgs:       fmt.Sprintf(\"%s --calculation-method %s\", allRequiredFlags, \"avg\"),\n\t\t\tWantOutput: `\"calculation_method\": \"avg\"`,\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --format flag\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetObservabilityCustomDashboardFn:    getDashboardOK,\n\t\t\t\tUpdateObservabilityCustomDashboardFn: updateDashboardOK,\n\t\t\t},\n\t\t\tArgs:       fmt.Sprintf(\"%s --format %s\", allRequiredFlags, \"ratio\"),\n\t\t\tWantOutput: `\"format\": \"ratio\"`,\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --plot-type flag\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetObservabilityCustomDashboardFn:    getDashboardOK,\n\t\t\t\tUpdateObservabilityCustomDashboardFn: updateDashboardOK,\n\t\t\t},\n\t\t\tArgs:       fmt.Sprintf(\"%s --plot-type %s\", allRequiredFlags, \"single-metric\"),\n\t\t\tWantOutput: `\"plot_type\": \"single-metric\"`,\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"update\"}, scenarios)\n}\n\nfunc getDashboardOK(_ context.Context, _ *fastly.GetObservabilityCustomDashboardInput) (*fastly.ObservabilityCustomDashboard, error) {\n\td := defaultDashboard()\n\treturn &d, nil\n}\n\nfunc updateDashboardOK(_ context.Context, i *fastly.UpdateObservabilityCustomDashboardInput) (*fastly.ObservabilityCustomDashboard, error) {\n\td := defaultDashboard()\n\td.Items = *i.Items\n\treturn &d, nil\n}\n\nfunc updateDashboardEmpty(_ context.Context, _ *fastly.UpdateObservabilityCustomDashboardInput) (*fastly.ObservabilityCustomDashboard, error) {\n\td := defaultDashboard()\n\td.Items = []fastly.DashboardItem{}\n\treturn &d, nil\n}\n"
  },
  {
    "path": "pkg/commands/dashboard/item/root.go",
    "content": "package item\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"item\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, globals *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = globals\n\tc.CmdClause = parent.Command(CommandName, \"Manipulate Fastly Custom Dashboard Items\")\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/dashboard/item/update.go",
    "content": "package item\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"slices\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, globals *global.Data) *UpdateCommand {\n\tvar c UpdateCommand\n\tc.CmdClause = parent.Command(\"update\", \"Update a custom dashboard item\").Alias(\"add\")\n\tc.Globals = globals\n\n\t// Required flags\n\tc.CmdClause.Flag(\"dashboard-id\", \"ID of the Dashboard containing the item\").Required().StringVar(&c.dashboardID) // --dashboard-id\n\tc.CmdClause.Flag(\"item-id\", \"ID of the Item to be updated\").Required().StringVar(&c.itemID)                      // --item-id\n\n\t// Optional flags\n\tc.RegisterFlagBool(c.JSONFlag())                                                                                                                                                                                               // --json\n\tc.CmdClause.Flag(\"title\", \"A human-readable title for the dashboard item\").Action(c.title.Set).StringVar(&c.title.Value)                                                                                                       // --title\n\tc.CmdClause.Flag(\"subtitle\", \"A human-readable subtitle for the dashboard item. Often a description of the visualization\").Action(c.subtitle.Set).StringVar(&c.subtitle.Value)                                                 // --subtitle\n\tc.CmdClause.Flag(\"span\", `The number of columns for the dashboard item to span. Dashboards are rendered on a 12-column grid on \"desktop\" screen sizes`).Action(c.span.Set).IntVar(&c.span.Value)                               // --span\n\tc.CmdClause.Flag(\"source-type\", \"The source of the data to display\").Action(c.sourceType.Set).HintOptions(sourceTypes...).EnumVar(&c.sourceType.Value, sourceTypes...)                                                         // --source-type\n\tc.CmdClause.Flag(\"metric\", \"The metrics to visualize. Valid options depend on the selected data source. Set flag multiple times to include multiple metrics\").Action(c.metrics.Set).StringsVar(&c.metrics.Value)               // --metrics\n\tc.CmdClause.Flag(\"visualization-type\", `The type of visualization to display. Currently, only \"chart\" is supported`).Action(c.vizType.Set).HintOptions(visualizationTypes...).EnumVar(&c.vizType.Value, visualizationTypes...) // --visualization-type\n\tc.CmdClause.Flag(\"calculation-method\", \"The aggregation function to apply to the dataset\").Action(c.calculationMethod.Set).HintOptions(calculationMethods...).EnumVar(&c.calculationMethod.Value, calculationMethods...)       // --calculation-method\n\tc.CmdClause.Flag(\"format\", \"The units to use to format the data\").Action(c.format.Set).HintOptions(formats...).EnumVar(&c.format.Value, formats...)                                                                            // --format\n\tc.CmdClause.Flag(\"plot-type\", \"The type of chart to display\").Action(c.plotType.Set).HintOptions(plotTypes...).EnumVar(&c.plotType.Value, plotTypes...)                                                                        // --plot-type\n\n\treturn &c\n}\n\n// UpdateCommand calls the Fastly API to update an appropriate resource.\ntype UpdateCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// required\n\tdashboardID string\n\titemID      string\n\n\t// optional\n\ttitle             argparser.OptionalString\n\tsubtitle          argparser.OptionalString\n\tspan              argparser.OptionalInt\n\tsourceType        argparser.OptionalString\n\tmetrics           argparser.OptionalStringSlice\n\tplotType          argparser.OptionalString\n\tvizType           argparser.OptionalString\n\tcalculationMethod argparser.OptionalString\n\tformat            argparser.OptionalString\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *UpdateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\td, err := c.Globals.APIClient.GetObservabilityCustomDashboard(context.TODO(), &fastly.GetObservabilityCustomDashboardInput{ID: &c.dashboardID})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tinput, err := c.constructInput(d)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\td, err = c.Globals.APIClient.UpdateObservabilityCustomDashboard(context.TODO(), input)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, d); ok {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\n// constructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *UpdateCommand) constructInput(d *fastly.ObservabilityCustomDashboard) (*fastly.UpdateObservabilityCustomDashboardInput, error) {\n\tvar input fastly.UpdateObservabilityCustomDashboardInput\n\n\tinput.ID = &d.ID\n\tinput.Items = &d.Items\n\tidx := slices.IndexFunc(*input.Items, func(di fastly.DashboardItem) bool {\n\t\treturn di.ID == c.itemID\n\t})\n\tif idx < 0 {\n\t\treturn nil, fmt.Errorf(\"dashboard (%s) does not contain item with ID %s\", d.ID, c.itemID)\n\t}\n\titem := &(*input.Items)[idx]\n\n\tif c.title.WasSet {\n\t\titem.Title = c.title.Value\n\t}\n\tif c.subtitle.WasSet {\n\t\titem.Subtitle = c.subtitle.Value\n\t}\n\tif c.span.WasSet {\n\t\tif span := c.span.Value; span <= 255 && span >= 0 {\n\t\t\titem.Span = uint8(span)\n\t\t} else {\n\t\t\treturn nil, fmt.Errorf(\"invalid span value %d\", span)\n\t\t}\n\t}\n\tif c.sourceType.WasSet {\n\t\titem.DataSource.Type = fastly.DashboardSourceType(c.sourceType.Value)\n\t}\n\tif c.metrics.WasSet {\n\t\titem.DataSource.Config.Metrics = c.metrics.Value\n\t}\n\tif c.vizType.WasSet {\n\t\titem.Visualization.Type = fastly.VisualizationType(c.vizType.Value)\n\t}\n\tif c.plotType.WasSet {\n\t\titem.Visualization.Config.PlotType = fastly.PlotType(c.plotType.Value)\n\t}\n\tif c.calculationMethod.WasSet {\n\t\titem.Visualization.Config.CalculationMethod = fastly.ToPointer(fastly.CalculationMethod(c.calculationMethod.Value))\n\t}\n\tif c.format.WasSet {\n\t\titem.Visualization.Config.Format = fastly.ToPointer(fastly.VisualizationFormat(c.format.Value))\n\t}\n\n\treturn &input, nil\n}\n"
  },
  {
    "path": "pkg/commands/dashboard/list.go",
    "content": "package dashboard\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/dashboard/printer\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, globals *global.Data) *ListCommand {\n\tvar c ListCommand\n\tc.CmdClause = parent.Command(\"list\", \"List custom dashboards\")\n\tc.Globals = globals\n\n\t// Optional Flags\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\tc.CmdClause.Flag(\"cursor\", \"Pagination cursor (Use 'next_cursor' value from list output)\").Action(c.cursor.Set).StringVar(&c.cursor.Value)\n\tc.CmdClause.Flag(\"limit\", \"Maximum number of items to list\").Action(c.limit.Set).IntVar(&c.limit.Value)\n\tc.CmdClause.Flag(\"order\", \"Sort by one of the following [asc, desc]\").Action(c.order.Set).StringVar(&c.order.Value)\n\tc.CmdClause.Flag(\"sort\", \"Sort by one of the following [name, created_at, updated_at]\").Action(c.sort.Set).StringVar(&c.sort.Value)\n\n\treturn &c\n}\n\n// ListCommand calls the Fastly API to list appropriate resources.\ntype ListCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tcursor argparser.OptionalString\n\tlimit  argparser.OptionalInt\n\tsort   argparser.OptionalString\n\torder  argparser.OptionalString\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *ListCommand) Exec(in io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tinput, err := c.constructInput()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tvar dashboards []fastly.ObservabilityCustomDashboard\n\tloadAllPages := c.JSONOutput.Enabled || c.Globals.Flags.NonInteractive || c.Globals.Flags.AutoYes\n\n\tfor {\n\t\to, err := c.Globals.APIClient.ListObservabilityCustomDashboards(context.TODO(), input)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif o != nil {\n\t\t\tdashboards = append(dashboards, o.Data...)\n\n\t\t\tif loadAllPages {\n\t\t\t\tif o.Meta.NextCursor != \"\" {\n\t\t\t\t\tinput.Cursor = &o.Meta.NextCursor\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\tif c.Globals.Verbose() {\n\t\t\t\tprinter.PrintVerbose(out, dashboards)\n\t\t\t} else {\n\t\t\t\tprinter.PrintSummary(out, dashboards)\n\t\t\t}\n\n\t\t\tif o.Meta.NextCursor != \"\" && text.IsTTY(out) {\n\t\t\t\ttext.Break(out)\n\t\t\t\tprintNextPage, err := text.AskYesNo(out, \"Print next page [y/N]: \", in)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tif printNextPage {\n\t\t\t\t\tdashboards = []fastly.ObservabilityCustomDashboard{}\n\t\t\t\t\tinput.Cursor = &o.Meta.NextCursor\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn nil\n\t}\n\n\tif ok, err := c.WriteJSON(out, dashboards); ok {\n\t\t// No pagination prompt w/ JSON output.\n\t\treturn err\n\t}\n\n\t// Only print output here if we've not already printed JSON.\n\tif c.Globals.Verbose() {\n\t\tprinter.PrintVerbose(out, dashboards)\n\t} else {\n\t\tprinter.PrintSummary(out, dashboards)\n\t}\n\n\treturn nil\n}\n\n// constructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *ListCommand) constructInput() (*fastly.ListObservabilityCustomDashboardsInput, error) {\n\tvar input fastly.ListObservabilityCustomDashboardsInput\n\n\tif c.cursor.WasSet {\n\t\tinput.Cursor = &c.cursor.Value\n\t}\n\tif c.limit.WasSet {\n\t\tinput.Limit = &c.limit.Value\n\t}\n\tvar sign string\n\tvar err error\n\tif c.order.WasSet {\n\t\tsign, err = argparser.ConvertOrderFromStringFlag(c.order.Value, \"order\")\n\t\tif err != nil {\n\t\t\tc.Globals.ErrLog.Add(err)\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tif c.sort.WasSet {\n\t\tstr := sign + c.sort.Value\n\t\tinput.Sort = &str\n\t}\n\n\treturn &input, nil\n}\n"
  },
  {
    "path": "pkg/commands/dashboard/printer/print.go",
    "content": "// Package printer contains functions used by both dashboard and dashboard/item packages\npackage printer\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"strings\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// PrintSummary displays the information returned from the API in a summarised\n// format.\nfunc PrintSummary(out io.Writer, ds []fastly.ObservabilityCustomDashboard) {\n\tt := text.NewTable(out)\n\tt.AddHeader(\"DASHBOARD ID\", \"NAME\", \"DESCRIPTION\", \"# ITEMS\")\n\tfor _, d := range ds {\n\t\tt.AddLine(\n\t\t\td.ID,\n\t\t\td.Name,\n\t\t\td.Description,\n\t\t\tlen(d.Items),\n\t\t)\n\t}\n\tt.Print()\n}\n\n// PrintVerbose displays the information returned from the API in a verbose\n// format.\nfunc PrintVerbose(out io.Writer, ds []fastly.ObservabilityCustomDashboard) {\n\tfor _, d := range ds {\n\t\tPrintDashboard(out, 0, &d)\n\t\tfmt.Fprintf(out, \"\\n\")\n\t}\n}\n\n// PrintDashboard displays the Dashboard returned from the API in a human-\n// readable format.\nfunc PrintDashboard(out io.Writer, indent uint, dashboard *fastly.ObservabilityCustomDashboard) {\n\tindentStep := uint(4)\n\tlevel := indent\n\ttext.Indent(out, level, \"Name: %s\", dashboard.Name)\n\ttext.Indent(out, level, \"Description: %s\", dashboard.Description)\n\ttext.Indent(out, level, \"Items:\")\n\n\tlevel += indentStep\n\tfor i, di := range dashboard.Items {\n\t\ttext.Indent(out, level, \"[%d]:\", i)\n\t\tlevel += indentStep\n\t\tPrintItem(out, level, &di)\n\t\tlevel -= indentStep\n\t}\n\tlevel -= indentStep\n\n\ttext.Indent(out, level, \"Meta:\")\n\tlevel += indentStep\n\ttext.Indent(out, level, \"Created at: %s\", dashboard.CreatedAt)\n\ttext.Indent(out, level, \"Updated at: %s\", dashboard.UpdatedAt)\n\ttext.Indent(out, level, \"Created by: %s\", dashboard.CreatedBy)\n\ttext.Indent(out, level, \"Updated by: %s\", dashboard.UpdatedBy)\n}\n\n// PrintItem displays a single DashboardItem in a human-readable format.\nfunc PrintItem(out io.Writer, indent uint, item *fastly.DashboardItem) {\n\tindentStep := uint(4)\n\tlevel := indent\n\tif item != nil {\n\t\ttext.Indent(out, level, \"ID: %s\", item.ID)\n\t\ttext.Indent(out, level, \"Title: %s\", item.Title)\n\t\ttext.Indent(out, level, \"Subtitle: %s\", item.Subtitle)\n\t\ttext.Indent(out, level, \"Span: %d\", item.Span)\n\n\t\ttext.Indent(out, level, \"Data Source:\")\n\t\tlevel += indentStep\n\t\ttext.Indent(out, level, \"Type: %s\", item.DataSource.Type)\n\t\ttext.Indent(out, level, \"Metrics: %s\", strings.Join(item.DataSource.Config.Metrics, \", \"))\n\t\tlevel -= indentStep\n\n\t\ttext.Indent(out, level, \"Visualization:\")\n\t\tlevel += indentStep\n\t\ttext.Indent(out, level, \"Type: %s\", item.Visualization.Type)\n\t\ttext.Indent(out, level, \"Plot Type: %s\", item.Visualization.Config.PlotType)\n\t\tif item.Visualization.Config.CalculationMethod != nil {\n\t\t\ttext.Indent(out, level, \"Calculation Method: %s\", *item.Visualization.Config.CalculationMethod)\n\t\t}\n\t\tif item.Visualization.Config.Format != nil {\n\t\t\ttext.Indent(out, level, \"Format: %s\", *item.Visualization.Config.Format)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "pkg/commands/dashboard/root.go",
    "content": "package dashboard\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"dashboard\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, globals *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = globals\n\tc.CmdClause = parent.Command(CommandName, \"Manipulate Fastly Custom Dashboards\")\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/dashboard/update.go",
    "content": "package dashboard\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, globals *global.Data) *UpdateCommand {\n\tvar c UpdateCommand\n\tc.CmdClause = parent.Command(\"update\", \"Update a custom dashboard\")\n\tc.Globals = globals\n\n\t// Required flags\n\tc.CmdClause.Flag(\"id\", \"ID of the Dashboard to update\").Required().StringVar(&c.dashboardID)\n\n\t// Optional flags\n\tc.RegisterFlagBool(c.JSONFlag())                                                                                                  // --json\n\tc.CmdClause.Flag(\"name\", \"A human-readable name for the dashboard\").Short('n').Action(c.name.Set).StringVar(&c.name.Value)        // --name\n\tc.CmdClause.Flag(\"description\", \"A short description of the dashboard\").Action(c.description.Set).StringVar(&c.description.Value) // --description\n\n\treturn &c\n}\n\n// UpdateCommand calls the Fastly API to update an appropriate resource.\ntype UpdateCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tdashboardID string\n\tname        argparser.OptionalString\n\tdescription argparser.OptionalString\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *UpdateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tinput := c.constructInput()\n\tdashboard, err := c.Globals.APIClient.UpdateObservabilityCustomDashboard(context.TODO(), input)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, dashboard); ok {\n\t\treturn err\n\t}\n\n\ttext.Success(out, `Updated Custom Dashboard \"%s\" (id: %s)`, dashboard.Name, dashboard.ID)\n\treturn nil\n}\n\n// constructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *UpdateCommand) constructInput() *fastly.UpdateObservabilityCustomDashboardInput {\n\tvar input fastly.UpdateObservabilityCustomDashboardInput\n\n\tinput.ID = &c.dashboardID\n\n\tif c.name.WasSet {\n\t\tinput.Name = &c.name.Value\n\t}\n\tif c.description.WasSet {\n\t\tinput.Description = &c.description.Value\n\t}\n\n\treturn &input\n}\n"
  },
  {
    "path": "pkg/commands/doc.go",
    "content": "// Package commands contains functions for managing exposed CLI commands.\npackage commands\n"
  },
  {
    "path": "pkg/commands/domain/common.go",
    "content": "package domain\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly/domainmanagement/v1/domains\"\n\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// printSummary displays the information returned from the API in a summarised\n// format.\nfunc printSummary(out io.Writer, data []domains.Data) {\n\tt := text.NewTable(out)\n\tt.AddHeader(\"FQDN\", \"DOMAIN ID\", \"SERVICE ID\", \"CREATED AT\", \"UPDATED AT\", \"DESCRIPTION\")\n\tfor _, d := range data {\n\t\tvar sid string\n\t\tif d.ServiceID != nil {\n\t\t\tsid = *d.ServiceID\n\t\t}\n\t\tt.AddLine(d.FQDN, d.DomainID, sid, d.CreatedAt, d.UpdatedAt, d.Description)\n\t}\n\tt.Print()\n}\n\n// printSummary displays the information returned from the API in a verbose\n// format.\nfunc printVerbose(out io.Writer, data []domains.Data) {\n\tfor _, d := range data {\n\t\tfmt.Fprintf(out, \"FQDN: %s\\n\", d.FQDN)\n\t\tfmt.Fprintf(out, \"Domain ID: %s\\n\", d.DomainID)\n\t\tif d.ServiceID != nil {\n\t\t\tfmt.Fprintf(out, \"Service ID: %s\\n\", *d.ServiceID)\n\t\t}\n\t\tfmt.Fprintf(out, \"Created at: %s\\n\", d.CreatedAt)\n\t\tfmt.Fprintf(out, \"Updated at: %s\\n\", d.UpdatedAt)\n\t\tfmt.Fprintf(out, \"Description: %s\\n\", d.Description)\n\t\tfmt.Fprintf(out, \"\\n\")\n\t}\n}\n"
  },
  {
    "path": "pkg/commands/domain/create.go",
    "content": "package domain\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/domainmanagement/v1/domains\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// CreateCommand calls the Fastly API to create domains.\ntype CreateCommand struct {\n\targparser.Base\n\n\t// Required.\n\tfqdn      string\n\tserviceID string\n\n\t// Optional.\n\tdescription argparser.OptionalString\n}\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"create\", \"Create a domain\").Alias(\"add\")\n\n\t// Optional.\n\tc.CmdClause.Flag(\"description\", \"The description for the domain\").Action(c.description.Set).StringVar(&c.description.Value)\n\tc.CmdClause.Flag(\"fqdn\", \"The fully qualified domain name\").Required().StringVar(&c.fqdn)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: \"The service_id associated with your domain\",\n\t\tDst:         &c.serviceID,\n\t\tShort:       's',\n\t})\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *CreateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tinput := &domains.CreateInput{\n\t\tFQDN: &c.fqdn,\n\t}\n\tif c.serviceID != \"\" {\n\t\tinput.ServiceID = &c.serviceID\n\t}\n\n\tif c.description.WasSet {\n\t\tinput.Description = &c.description.Value\n\t}\n\n\tfc, ok := c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\td, err := domains.Create(context.TODO(), fc, input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"FQDN\":       c.fqdn,\n\t\t\t\"Service ID\": c.serviceID,\n\t\t})\n\t\treturn err\n\t}\n\n\tserviceOutput := \"\"\n\tif d.ServiceID != nil {\n\t\tserviceOutput = fmt.Sprintf(\", service-id: %s\", *d.ServiceID)\n\t}\n\n\ttext.Success(out, \"Created domain '%s' (domain-id: %s%s)\", d.FQDN, d.DomainID, serviceOutput)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/domain/delete.go",
    "content": "package domain\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/domainmanagement/v1/domains\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DeleteCommand calls the Fastly API to delete domains.\ntype DeleteCommand struct {\n\targparser.Base\n\tdomainID string\n}\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"delete\", \"Delete a domain\").Alias(\"remove\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"domain-id\", \"The Domain Identifier (UUID)\").Required().StringVar(&c.domainID)\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DeleteCommand) Exec(_ io.Reader, out io.Writer) error {\n\tfc, ok := c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tinput := &domains.DeleteInput{\n\t\tDomainID: &c.domainID,\n\t}\n\n\terr := domains.Delete(context.TODO(), fc, input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Domain ID\": c.domainID,\n\t\t})\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Deleted domain (domain-id: %s)\", c.domainID)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/domain/describe.go",
    "content": "package domain\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/domainmanagement/v1/domains\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// DescribeCommand calls the Fastly API to describe a domain.\ntype DescribeCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\tdomainID string\n}\n\n// NewDescribeCommand returns a usable command registered under the parent.\nfunc NewDescribeCommand(parent argparser.Registerer, g *global.Data) *DescribeCommand {\n\tc := DescribeCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"describe\", \"Show detailed information about a domain\").Alias(\"get\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"domain-id\", \"The Domain Identifier (UUID)\").Required().StringVar(&c.domainID)\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DescribeCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tfc, ok := c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tinput := &domains.GetInput{\n\t\tDomainID: &c.domainID,\n\t}\n\n\td, err := domains.Get(context.TODO(), fc, input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Domain ID\": c.domainID,\n\t\t})\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, d); ok {\n\t\treturn err\n\t}\n\n\tif d != nil {\n\t\tcl := []domains.Data{*d}\n\t\tif c.Globals.Verbose() {\n\t\t\tprintVerbose(out, cl)\n\t\t} else {\n\t\t\tprintSummary(out, cl)\n\t\t}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/domain/doc.go",
    "content": "// Package domainv1 contains commands to inspect and manipulate Fastly domains.\npackage domain\n"
  },
  {
    "path": "pkg/commands/domain/domain_test.go",
    "content": "package domain_test\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly/domainmanagement/v1/domains\"\n\n\troot \"github.com/fastly/cli/pkg/commands/domain\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n)\n\nfunc TestDomainCreate(t *testing.T) {\n\tfqdn := \"www.example.com\"\n\tsid := \"123\"\n\tdid := \"domain-id\"\n\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"\",\n\t\t\tWantError: \"error parsing arguments: required flag --fqdn not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: fmt.Sprintf(\"--fqdn %s --service-id %s\", fqdn, sid),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(domains.Data{\n\t\t\t\t\t\t\tDomainID:  did,\n\t\t\t\t\t\t\tFQDN:      fqdn,\n\t\t\t\t\t\t\tServiceID: &sid,\n\t\t\t\t\t\t}))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fmt.Sprintf(\"SUCCESS: Created domain '%s' (domain-id: %s, service-id: %s)\", fqdn, did, sid),\n\t\t},\n\t\t{\n\t\t\tArgs: fmt.Sprintf(\"--fqdn %s\", fqdn),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(domains.Data{\n\t\t\t\t\t\t\tDomainID: did,\n\t\t\t\t\t\t\tFQDN:     fqdn,\n\t\t\t\t\t\t}))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fmt.Sprintf(\"SUCCESS: Created domain '%s' (domain-id: %s)\", fqdn, did),\n\t\t},\n\t\t{\n\t\t\tArgs: fmt.Sprintf(\"--fqdn %s\", fqdn),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusBadRequest,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusBadRequest),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(`\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t  \"errors\":[\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t  \"title\":\"Invalid value for fqdn\",\n\t\t\t\t\t\t\t\t  \"detail\":\"fqdn has already been taken\"\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t  ]\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t`))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"400 - Bad Request\",\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, \"create\"}, scenarios)\n}\n\nfunc TestDomainList(t *testing.T) {\n\tfqdn := \"www.example.com\"\n\tsid := \"123\"\n\tdid := \"domain-id\"\n\tdescription := \"domain description\"\n\n\tresp := testutil.GenJSON(domains.Collection{\n\t\tData: []domains.Data{\n\t\t\t{\n\t\t\t\tDomainID:    did,\n\t\t\t\tFQDN:        fqdn,\n\t\t\t\tServiceID:   &sid,\n\t\t\t\tDescription: description,\n\t\t\t},\n\t\t},\n\t})\n\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"--verbose --json\",\n\t\t\tWantError: \"invalid flag combination, --verbose and --json\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--json\",\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(resp)),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: string(resp),\n\t\t},\n\t\t{\n\t\t\tArgs: \"\",\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusBadRequest,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusBadRequest),\n\t\t\t\t\t\tBody:       io.NopCloser(strings.NewReader(`{\"error\": \"whoops\"}`)),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"400 - Bad Request\",\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, \"list\"}, scenarios)\n}\n\nfunc TestDomainDescribe(t *testing.T) {\n\tfqdn := \"www.example.com\"\n\tsid := \"123\"\n\tdid := \"domain-id\"\n\tdescription := \"domain description\"\n\n\tresp := testutil.GenJSON(domains.Data{\n\t\tDomainID:    did,\n\t\tFQDN:        fqdn,\n\t\tServiceID:   &sid,\n\t\tDescription: description,\n\t})\n\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"\",\n\t\t\tWantError: \"error parsing arguments: required flag --domain-id not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: fmt.Sprintf(\"--domain-id %s --json\", did),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(resp)),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: string(resp),\n\t\t},\n\t\t{\n\t\t\tArgs: fmt.Sprintf(\"--domain-id %s --json\", did),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusBadRequest,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusBadRequest),\n\t\t\t\t\t\tBody:       io.NopCloser(strings.NewReader(`{\"error\": \"whoops\"}`)),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"400 - Bad Request\",\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, \"describe\"}, scenarios)\n}\n\nfunc TestDomainUpdate(t *testing.T) {\n\tfqdn := \"www.example.com\"\n\tsid := \"123\"\n\tdid := \"domain-id\"\n\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"\",\n\t\t\tWantError: \"error parsing arguments: required flag --domain-id not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: fmt.Sprintf(\"--domain-id %s --service-id %s\", did, sid),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(domains.Data{\n\t\t\t\t\t\t\tDomainID:  did,\n\t\t\t\t\t\t\tFQDN:      fqdn,\n\t\t\t\t\t\t\tServiceID: &sid,\n\t\t\t\t\t\t}))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fmt.Sprintf(\"SUCCESS: Updated domain '%s' (domain-id: %s, service-id: %s)\", fqdn, did, sid),\n\t\t},\n\t\t{\n\t\t\tArgs: fmt.Sprintf(\"--domain-id %s\", did),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(domains.Data{\n\t\t\t\t\t\t\tDomainID: did,\n\t\t\t\t\t\t\tFQDN:     fqdn,\n\t\t\t\t\t\t}))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fmt.Sprintf(\"SUCCESS: Updated domain '%s' (domain-id: %s)\", fqdn, did),\n\t\t},\n\t\t{\n\t\t\tArgs: fmt.Sprintf(\"--domain-id %s\", did),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusBadRequest,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusBadRequest),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(`\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t  \"errors\":[\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t  \"title\":\"Invalid value for domain-id\",\n\t\t\t\t\t\t\t\t  \"detail\":\"whoops\"\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t  ]\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t`))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"400 - Bad Request\",\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, \"update\"}, scenarios)\n}\n\nfunc TestDomainDelete(t *testing.T) {\n\tdid := \"domain-id\"\n\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"\",\n\t\t\tWantError: \"error parsing arguments: required flag --domain-id not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: fmt.Sprintf(\"--domain-id %s\", did),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusNoContent,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusNoContent),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fmt.Sprintf(\"SUCCESS: Deleted domain (domain-id: %s)\", did),\n\t\t},\n\t\t{\n\t\t\tArgs: fmt.Sprintf(\"--domain-id %s\", did),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusBadRequest,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusBadRequest),\n\t\t\t\t\t\tBody:       io.NopCloser(strings.NewReader(`{\"error\": \"whoops\"}`)),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"400 - Bad Request\",\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, \"delete\"}, scenarios)\n}\n"
  },
  {
    "path": "pkg/commands/domain/list.go",
    "content": "package domain\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/domainmanagement/v1/domains\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// ListCommand calls the Fastly API to list domains.\ntype ListCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tcursor    argparser.OptionalString\n\tfqdn      argparser.OptionalString\n\tlimit     argparser.OptionalInt\n\tserviceID argparser.OptionalString\n\tsort      argparser.OptionalString\n}\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"list\", \"List domains\")\n\n\t// Optional.\n\tc.CmdClause.Flag(\"cursor\", \"Cursor value from the next_cursor field of a previous response, used to retrieve the next page\").Action(c.cursor.Set).StringVar(&c.cursor.Value)\n\tc.CmdClause.Flag(\"fqdn\", \"Filters results by the FQDN using a fuzzy/partial match\").Action(c.fqdn.Set).StringVar(&c.fqdn.Value)\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\tc.CmdClause.Flag(\"limit\", \"Limit how many results are returned\").Action(c.limit.Set).IntVar(&c.limit.Value)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceID.Set,\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: \"Filter results based on a service_id\",\n\t\tDst:         &c.serviceID.Value,\n\t\tShort:       's',\n\t})\n\tc.CmdClause.Flag(\"sort\", \"The order in which to list the results\").Action(c.sort.Set).StringVar(&c.sort.Value)\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *ListCommand) Exec(in io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tinput := &domains.ListInput{}\n\n\tif c.serviceID.WasSet {\n\t\tinput.ServiceID = &c.serviceID.Value\n\t}\n\tif c.cursor.WasSet {\n\t\tinput.Cursor = &c.cursor.Value\n\t}\n\tif c.fqdn.WasSet {\n\t\tinput.FQDN = &c.fqdn.Value\n\t}\n\tif c.limit.WasSet {\n\t\tinput.Limit = &c.limit.Value\n\t}\n\tif c.sort.WasSet {\n\t\tinput.Sort = &c.sort.Value\n\t}\n\n\tfc, ok := c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tfor {\n\t\tcl, err := domains.List(context.TODO(), fc, input)\n\t\tif err != nil {\n\t\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\t\"Cursor\":     c.cursor.Value,\n\t\t\t\t\"FQDN\":       c.fqdn.Value,\n\t\t\t\t\"Limit\":      c.limit.Value,\n\t\t\t\t\"Service ID\": c.serviceID.Value,\n\t\t\t\t\"Sort\":       c.sort.Value,\n\t\t\t})\n\t\t\treturn err\n\t\t}\n\n\t\tif ok, err := c.WriteJSON(out, cl); ok {\n\t\t\t// No pagination prompt w/ JSON output.\n\t\t\treturn err\n\t\t}\n\n\t\tif c.Globals.Verbose() {\n\t\t\tprintVerbose(out, cl.Data)\n\t\t} else {\n\t\t\tprintSummary(out, cl.Data)\n\t\t}\n\n\t\tif cl != nil && cl.Meta.NextCursor != \"\" {\n\t\t\t// Check if 'out' is interactive before prompting.\n\t\t\tif !c.Globals.Flags.NonInteractive && !c.Globals.Flags.AutoYes && text.IsTTY(out) {\n\t\t\t\tprintNext, err := text.AskYesNo(out, \"Print next page [y/N]: \", in)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tif printNext {\n\t\t\t\t\tinput.Cursor = &cl.Meta.NextCursor\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn nil\n\t}\n}\n"
  },
  {
    "path": "pkg/commands/domain/root.go",
    "content": "package domain\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"domain\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manipulate Fastly domains\")\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/domain/update.go",
    "content": "package domain\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/domainmanagement/v1/domains\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// UpdateCommand calls the Fastly API to update domains.\ntype UpdateCommand struct {\n\targparser.Base\n\tdomainID    string\n\tserviceID   string\n\tdescription argparser.OptionalString\n}\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"update\", \"Update a domain\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"domain-id\", \"The Domain Identifier (UUID)\").Required().StringVar(&c.domainID)\n\n\t// Optional\n\tc.CmdClause.Flag(\"description\", \"The description for the domain\").Action(c.description.Set).StringVar(&c.description.Value)\n\tc.CmdClause.Flag(\"service-id\", \"The service_id associated with your domain (omit to unset)\").StringVar(&c.serviceID)\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *UpdateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tinput := &domains.UpdateInput{\n\t\tDomainID: &c.domainID,\n\t}\n\tif c.serviceID != \"\" {\n\t\tinput.ServiceID = &c.serviceID\n\t}\n\n\tif c.description.WasSet {\n\t\tinput.Description = &c.description.Value\n\t}\n\n\tfc, ok := c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\td, err := domains.Update(context.TODO(), fc, input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Domain ID\":  c.domainID,\n\t\t\t\"Service ID\": c.serviceID,\n\t\t})\n\t\treturn err\n\t}\n\n\tserviceOutput := \"\"\n\tif d.ServiceID != nil {\n\t\tserviceOutput = fmt.Sprintf(\", service-id: %s\", *d.ServiceID)\n\t}\n\n\ttext.Success(out, \"Updated domain '%s' (domain-id: %s%s)\", d.FQDN, d.DomainID, serviceOutput)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/install/doc.go",
    "content": "// Package install contains functions for installing a specific CLI version.\npackage install\n"
  },
  {
    "path": "pkg/commands/install/root.go",
    "content": "package install\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"path/filepath\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/filesystem\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\n\tversionToInstall string\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"install\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Install the specified version of the CLI\")\n\tc.CmdClause.Arg(\"version\", \"CLI release version to install (e.g. 10.8.0)\").Required().StringVar(&c.versionToInstall)\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, out io.Writer) error {\n\tspinner, err := text.NewSpinner(out)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tvar downloadedBin string\n\terr = spinner.Process(fmt.Sprintf(\"Fetching release %s\", c.versionToInstall), func(_ *text.SpinnerWrapper) error {\n\t\tdownloadedBin, err = c.Globals.Versioners.CLI.DownloadVersion(c.versionToInstall)\n\t\tif err != nil {\n\t\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\t\"CLI version to install\": c.versionToInstall,\n\t\t\t})\n\t\t\treturn fmt.Errorf(\"error downloading release version %s: %w\", c.versionToInstall, err)\n\t\t}\n\t\treturn nil\n\t})\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer os.RemoveAll(downloadedBin)\n\n\tvar currentBin string\n\terr = spinner.Process(\"Replacing binary\", func(_ *text.SpinnerWrapper) error {\n\t\texecPath, err := os.Executable()\n\t\tif err != nil {\n\t\t\tc.Globals.ErrLog.Add(err)\n\t\t\treturn fmt.Errorf(\"error determining executable path: %w\", err)\n\t\t}\n\n\t\tcurrentBin, err = filepath.Abs(execPath)\n\t\tif err != nil {\n\t\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\t\"Executable path\": execPath,\n\t\t\t})\n\t\t\treturn fmt.Errorf(\"error determining absolute target path: %w\", err)\n\t\t}\n\n\t\t// Windows does not permit replacing a running executable, however it will\n\t\t// permit it if you first move the original executable. So we first move the\n\t\t// running executable to a new location, then we move the executable that we\n\t\t// downloaded to the same location as the original.\n\t\t// I've also tested this approach on nix systems and it works fine.\n\t\t//\n\t\t// Reference:\n\t\t// https://github.com/golang/go/issues/21997#issuecomment-331744930\n\n\t\tbackup := currentBin + \".bak\"\n\t\tif err := os.Rename(currentBin, backup); err != nil {\n\t\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\t\"Executable (source)\":      downloadedBin,\n\t\t\t\t\"Executable (destination)\": currentBin,\n\t\t\t})\n\t\t\treturn fmt.Errorf(\"error moving the current executable: %w\", err)\n\t\t}\n\n\t\tif err = os.Remove(backup); err != nil {\n\t\t\tc.Globals.ErrLog.Add(err)\n\t\t}\n\n\t\t// Move the downloaded binary to the same location as the current executable.\n\t\tif err := os.Rename(downloadedBin, currentBin); err != nil {\n\t\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\t\"Executable (source)\":      downloadedBin,\n\t\t\t\t\"Executable (destination)\": currentBin,\n\t\t\t})\n\t\t\trenameErr := err\n\n\t\t\t// Failing that we'll try to io.Copy downloaded binary to the current binary.\n\t\t\tif err := filesystem.CopyFile(downloadedBin, currentBin); err != nil {\n\t\t\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\t\t\"Executable (source)\":      downloadedBin,\n\t\t\t\t\t\"Executable (destination)\": currentBin,\n\t\t\t\t})\n\t\t\t\treturn fmt.Errorf(\"error 'copying' latest binary in place: %w (following an error 'moving': %w)\", err, renameErr)\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"\\nInstalled version %s.\", c.versionToInstall)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ip/doc.go",
    "content": "// Package ip contains commands to inspect and manipulate Fastly IPs.\npackage ip\n"
  },
  {
    "path": "pkg/commands/ip/ip_test.go",
    "content": "package ip_test\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\troot \"github.com/fastly/cli/pkg/commands/ip\"\n\t\"github.com/fastly/cli/pkg/mock\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n)\n\nfunc TestAllIPs(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName: \"validate listing IP addresses\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tAllIPsFn: func(_ context.Context) (v4, v6 fastly.IPAddrs, err error) {\n\t\t\t\t\treturn []string{\n\t\t\t\t\t\t\t\"00.123.45.6/78\",\n\t\t\t\t\t\t}, []string{\n\t\t\t\t\t\t\t\"0a12:3b45::/67\",\n\t\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: \"\\nIPv4\\n\\t00.123.45.6/78\\n\\nIPv6\\n\\t0a12:3b45::/67\\n\",\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName}, scenarios)\n}\n"
  },
  {
    "path": "pkg/commands/ip/root.go",
    "content": "package ip\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"ip-list\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"List Fastly's public IPs\")\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, out io.Writer) error {\n\tipv4, ipv6, err := c.Globals.APIClient.AllIPs(context.TODO())\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\t// TODO: Implement --json support.\n\n\ttext.Break(out)\n\tfmt.Fprintf(out, \"%s\\n\", text.Bold(\"IPv4\"))\n\tfor _, ip := range ipv4 {\n\t\tfmt.Fprintf(out, \"\\t%s\\n\", ip)\n\t}\n\tfmt.Fprintf(out, \"\\n%s\\n\", text.Bold(\"IPv6\"))\n\tfor _, ip := range ipv6 {\n\t\tfmt.Fprintf(out, \"\\t%s\\n\", ip)\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/kvstore/create.go",
    "content": "package kvstore\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// CreateCommand calls the Fastly API to create a kv store.\ntype CreateCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tInput fastly.CreateKVStoreInput\n}\n\n// locations is a list of supported regional location options.\nvar locations = []string{\"US\", \"EU\", \"ASIA\", \"AUS\"}\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\n\tc.CmdClause = parent.Command(\"create\", \"Create a KV Store\")\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\tc.CmdClause.Flag(\"location\", \"Regional location of KV Store\").Short('l').HintOptions(locations...).EnumVar(&c.Input.Location, locations...)\n\tc.CmdClause.Flag(\"name\", \"Name of KV Store\").Short('n').Required().StringVar(&c.Input.Name)\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *CreateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\to, err := c.Globals.APIClient.CreateKVStore(context.TODO(), &c.Input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Created KV Store '%s' (%s)\", o.Name, o.StoreID)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/kvstore/delete.go",
    "content": "package kvstore\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"strconv\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/kvstoreentry\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DeleteCommand calls the Fastly API to delete a kv store.\ntype DeleteCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tdeleteAll bool\n\tmaxErrors int\n\tpoolSize  int\n\tInput     fastly.DeleteKVStoreInput\n}\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"delete\", \"Delete a KV Store\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"store-id\", \"Store ID\").Short('s').Required().StringVar(&c.Input.StoreID)\n\n\t// Optional.\n\tc.CmdClause.Flag(\"all\", \"Delete all entries within the store\").Short('a').BoolVar(&c.deleteAll)\n\tc.CmdClause.Flag(\"concurrency\", \"The thread pool size (ignored when set without the --all flag)\").Default(strconv.Itoa(kvstoreentry.DeleteKeysPoolSize)).Short('r').IntVar(&c.poolSize)\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\tc.CmdClause.Flag(\"max-errors\", \"The number of errors to accept before stopping (ignored when set without the --all flag)\").Default(strconv.Itoa(kvstoreentry.DeleteKeysMaxErrors)).Short('m').IntVar(&c.maxErrors)\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DeleteCommand) Exec(in io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tif c.deleteAll {\n\t\tif !c.Globals.Flags.AutoYes && !c.Globals.Flags.NonInteractive {\n\t\t\ttext.Warning(out, \"This will delete ALL entries from your store!\\n\\n\")\n\t\t\tcont, err := text.AskYesNo(out, \"Are you sure you want to continue? [y/N]: \", in)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif !cont {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\ttext.Break(out)\n\t\t}\n\t\tdc := kvstoreentry.DeleteCommand{\n\t\t\tBase: argparser.Base{\n\t\t\t\tGlobals: c.Globals,\n\t\t\t},\n\t\t\tDeleteAll: c.deleteAll,\n\t\t\tMaxErrors: c.maxErrors,\n\t\t\tPoolSize:  c.poolSize,\n\t\t\tStoreID:   c.Input.StoreID,\n\t\t}\n\t\tif err := dc.DeleteAllKeys(out); err != nil {\n\t\t\treturn err\n\t\t}\n\t\ttext.Break(out)\n\t}\n\n\terr := c.Globals.APIClient.DeleteKVStore(context.TODO(), &c.Input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn fmt.Errorf(\"failed to delete KV store: %w\", err)\n\t}\n\n\tif c.JSONOutput.Enabled {\n\t\to := struct {\n\t\t\tID      string `json:\"id\"`\n\t\t\tDeleted bool   `json:\"deleted\"`\n\t\t}{\n\t\t\tc.Input.StoreID,\n\t\t\ttrue,\n\t\t}\n\t\t_, err := c.WriteJSON(out, o)\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Deleted KV Store '%s'\", c.Input.StoreID)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/kvstore/describe.go",
    "content": "package kvstore\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DescribeCommand calls the Fastly API to fetch the value of a key from a kv store.\ntype DescribeCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tInput fastly.GetKVStoreInput\n}\n\n// NewDescribeCommand returns a usable command registered under the parent.\nfunc NewDescribeCommand(parent argparser.Registerer, g *global.Data) *DescribeCommand {\n\tc := DescribeCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"describe\", \"Describe a KV Store\").Alias(\"get\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"store-id\", \"Store ID\").Short('s').Required().StringVar(&c.Input.StoreID)\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DescribeCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\to, err := c.Globals.APIClient.GetKVStore(context.TODO(), &c.Input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\ttext.PrintKVStore(out, \"\", o)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/kvstore/doc.go",
    "content": "// Package kvstore contains commands to inspect and manipulate Fastly edge\n// kv stores.\npackage kvstore\n"
  },
  {
    "path": "pkg/commands/kvstore/kvstore_test.go",
    "content": "package kvstore_test\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\troot \"github.com/fastly/cli/pkg/commands/kvstore\"\n\tfstfmt \"github.com/fastly/cli/pkg/fmt\"\n\t\"github.com/fastly/cli/pkg/mock\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\nfunc TestCreateStoreCommand(t *testing.T) {\n\tconst (\n\t\tstoreName     = \"test123\"\n\t\tstoreLocation = \"EU\"\n\t\tstoreID       = \"store-id-123\"\n\t)\n\tnow := time.Now()\n\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: fmt.Sprintf(\"--name %s\", storeName),\n\t\t\tAPI: &mock.API{\n\t\t\t\tCreateKVStoreFn: func(_ context.Context, _ *fastly.CreateKVStoreInput) (*fastly.KVStore, error) {\n\t\t\t\t\treturn nil, errors.New(\"invalid request\")\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"invalid request\",\n\t\t},\n\t\t{\n\t\t\tArgs: fmt.Sprintf(\"--name %s\", storeName),\n\t\t\tAPI: &mock.API{\n\t\t\t\tCreateKVStoreFn: func(_ context.Context, i *fastly.CreateKVStoreInput) (*fastly.KVStore, error) {\n\t\t\t\t\treturn &fastly.KVStore{\n\t\t\t\t\t\tStoreID: storeID,\n\t\t\t\t\t\tName:    i.Name,\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Created KV Store '%s' (%s)\", storeName, storeID),\n\t\t},\n\t\t{\n\t\t\tArgs: fmt.Sprintf(\"--name %s --json\", storeName),\n\t\t\tAPI: &mock.API{\n\t\t\t\tCreateKVStoreFn: func(_ context.Context, i *fastly.CreateKVStoreInput) (*fastly.KVStore, error) {\n\t\t\t\t\treturn &fastly.KVStore{\n\t\t\t\t\t\tStoreID:   storeID,\n\t\t\t\t\t\tName:      i.Name,\n\t\t\t\t\t\tCreatedAt: &now,\n\t\t\t\t\t\tUpdatedAt: &now,\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(&fastly.KVStore{\n\t\t\t\tStoreID:   storeID,\n\t\t\t\tName:      storeName,\n\t\t\t\tCreatedAt: &now,\n\t\t\t\tUpdatedAt: &now,\n\t\t\t}),\n\t\t},\n\t\t{\n\t\t\t// NOTE: The following tests only validate support for the --location flag.\n\t\t\t// Location/region indicators are not exposed for us to validate.\n\t\t\tArgs: fmt.Sprintf(\"--name %s --location %s\", storeName, storeLocation),\n\t\t\tAPI: &mock.API{\n\t\t\t\tCreateKVStoreFn: func(_ context.Context, i *fastly.CreateKVStoreInput) (*fastly.KVStore, error) {\n\t\t\t\t\treturn &fastly.KVStore{\n\t\t\t\t\t\tStoreID: storeID,\n\t\t\t\t\t\tName:    i.Name,\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Created KV Store '%s' (%s)\", storeName, storeID),\n\t\t},\n\t\t{\n\t\t\t// NOTE: The following tests only validate support for the --location flag.\n\t\t\t// Location/region indicators are not exposed for us to validate.\n\t\t\tArgs: fmt.Sprintf(\"--name %s --location %s --json\", storeName, storeLocation),\n\t\t\tAPI: &mock.API{\n\t\t\t\tCreateKVStoreFn: func(_ context.Context, i *fastly.CreateKVStoreInput) (*fastly.KVStore, error) {\n\t\t\t\t\treturn &fastly.KVStore{\n\t\t\t\t\t\tStoreID:   storeID,\n\t\t\t\t\t\tName:      i.Name,\n\t\t\t\t\t\tCreatedAt: &now,\n\t\t\t\t\t\tUpdatedAt: &now,\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(&fastly.KVStore{\n\t\t\t\tStoreID:   storeID,\n\t\t\t\tName:      storeName,\n\t\t\t\tCreatedAt: &now,\n\t\t\t\tUpdatedAt: &now,\n\t\t\t}),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, \"create\"}, scenarios)\n}\n\nfunc TestDeleteStoreCommand(t *testing.T) {\n\tconst storeID = \"test123\"\n\terrStoreNotFound := errors.New(\"store not found\")\n\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tWantError: \"error parsing arguments: required flag --store-id not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--store-id DOES-NOT-EXIST\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tDeleteKVStoreFn: func(_ context.Context, i *fastly.DeleteKVStoreInput) error {\n\t\t\t\t\tif i.StoreID != storeID {\n\t\t\t\t\t\treturn errStoreNotFound\n\t\t\t\t\t}\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: errStoreNotFound.Error(),\n\t\t},\n\t\t{\n\t\t\tArgs: fmt.Sprintf(\"--store-id %s\", storeID),\n\t\t\tAPI: &mock.API{\n\t\t\t\tDeleteKVStoreFn: func(_ context.Context, i *fastly.DeleteKVStoreInput) error {\n\t\t\t\t\tif i.StoreID != storeID {\n\t\t\t\t\t\treturn errStoreNotFound\n\t\t\t\t\t}\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Deleted KV Store '%s'\\n\", storeID),\n\t\t},\n\t\t{\n\t\t\tArgs: fmt.Sprintf(\"--store-id %s --json\", storeID),\n\t\t\tAPI: &mock.API{\n\t\t\t\tDeleteKVStoreFn: func(_ context.Context, i *fastly.DeleteKVStoreInput) error {\n\t\t\t\t\tif i.StoreID != storeID {\n\t\t\t\t\t\treturn errStoreNotFound\n\t\t\t\t\t}\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.JSON(`{\"id\": %q, \"deleted\": true}`, storeID),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, \"delete\"}, scenarios)\n}\n\nfunc TestGetStoreCommand(t *testing.T) {\n\tconst (\n\t\tstoreName = \"test123\"\n\t\tstoreID   = \"store-id-123\"\n\t)\n\n\tnow := time.Now()\n\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tWantError: \"error parsing arguments: required flag --store-id not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: fmt.Sprintf(\"--store-id %s\", storeID),\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetKVStoreFn: func(_ context.Context, _ *fastly.GetKVStoreInput) (*fastly.KVStore, error) {\n\t\t\t\t\treturn nil, errors.New(\"invalid request\")\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"invalid request\",\n\t\t},\n\t\t{\n\t\t\tArgs: fmt.Sprintf(\"--store-id %s\", storeID),\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetKVStoreFn: func(_ context.Context, i *fastly.GetKVStoreInput) (*fastly.KVStore, error) {\n\t\t\t\t\treturn &fastly.KVStore{\n\t\t\t\t\t\tStoreID:   i.StoreID,\n\t\t\t\t\t\tName:      storeName,\n\t\t\t\t\t\tCreatedAt: &now,\n\t\t\t\t\t\tUpdatedAt: &now,\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fmtStore(\n\t\t\t\t&fastly.KVStore{\n\t\t\t\t\tStoreID:   storeID,\n\t\t\t\t\tName:      storeName,\n\t\t\t\t\tCreatedAt: &now,\n\t\t\t\t\tUpdatedAt: &now,\n\t\t\t\t},\n\t\t\t),\n\t\t},\n\t\t{\n\t\t\tArgs: fmt.Sprintf(\"--store-id %s --json\", storeID),\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetKVStoreFn: func(_ context.Context, i *fastly.GetKVStoreInput) (*fastly.KVStore, error) {\n\t\t\t\t\treturn &fastly.KVStore{\n\t\t\t\t\t\tStoreID:   i.StoreID,\n\t\t\t\t\t\tName:      storeName,\n\t\t\t\t\t\tCreatedAt: &now,\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(&fastly.KVStore{\n\t\t\t\tStoreID:   storeID,\n\t\t\t\tName:      storeName,\n\t\t\t\tCreatedAt: &now,\n\t\t\t}),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, \"get\"}, scenarios)\n}\n\nfunc TestListStoresCommand(t *testing.T) {\n\tconst (\n\t\tstoreName = \"test123\"\n\t\tstoreID   = \"store-id-123\"\n\t)\n\n\tnow := time.Now()\n\n\tstores := &fastly.ListKVStoresResponse{\n\t\tData: []fastly.KVStore{\n\t\t\t{StoreID: storeID, Name: storeName, CreatedAt: &now, UpdatedAt: &now},\n\t\t\t{StoreID: storeID + \"+1\", Name: storeName + \"+1\", CreatedAt: &now, UpdatedAt: &now},\n\t\t},\n\t}\n\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tAPI: &mock.API{\n\t\t\t\tListKVStoresFn: func(_ context.Context, _ *fastly.ListKVStoresInput) (*fastly.ListKVStoresResponse, error) {\n\t\t\t\t\treturn nil, nil\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tAPI: &mock.API{\n\t\t\t\tListKVStoresFn: func(_ context.Context, _ *fastly.ListKVStoresInput) (*fastly.ListKVStoresResponse, error) {\n\t\t\t\t\treturn nil, errors.New(\"unknown error\")\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"unknown error\",\n\t\t},\n\t\t{\n\t\t\tAPI: &mock.API{\n\t\t\t\tListKVStoresFn: func(_ context.Context, _ *fastly.ListKVStoresInput) (*fastly.ListKVStoresResponse, error) {\n\t\t\t\t\treturn stores, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fmtStores(stores),\n\t\t},\n\t\t{\n\t\t\tArgs: \"--json\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tListKVStoresFn: func(_ context.Context, _ *fastly.ListKVStoresInput) (*fastly.ListKVStoresResponse, error) {\n\t\t\t\t\treturn stores, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(stores),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, \"list\"}, scenarios)\n}\n\nfunc fmtStore(ks *fastly.KVStore) string {\n\tvar b bytes.Buffer\n\ttext.PrintKVStore(&b, \"\", ks)\n\treturn b.String()\n}\n\nfunc fmtStores(ks *fastly.ListKVStoresResponse) string {\n\tvar b bytes.Buffer\n\tfor _, o := range ks.Data {\n\t\t// avoid gosec loop aliasing check :/\n\t\to := o\n\t\ttext.PrintKVStore(&b, \"\", &o)\n\t}\n\treturn b.String()\n}\n"
  },
  {
    "path": "pkg/commands/kvstore/list.go",
    "content": "package kvstore\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// ListCommand calls the Fastly API to list the available kv stores.\ntype ListCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n}\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"list\", \"List KV Stores\")\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *ListCommand) Exec(in io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tvar cursor string\n\n\tfor {\n\t\to, err := c.Globals.APIClient.ListKVStores(context.TODO(), &fastly.ListKVStoresInput{\n\t\t\tCursor: cursor,\n\t\t})\n\t\tif err != nil {\n\t\t\tc.Globals.ErrLog.Add(err)\n\t\t\treturn err\n\t\t}\n\n\t\tif ok, err := c.WriteJSON(out, o); ok {\n\t\t\t// No pagination prompt w/ JSON output.\n\t\t\t// FIXME: This should be fixed here and for Secrets Store.\n\t\t\treturn err\n\t\t}\n\n\t\tif o != nil {\n\t\t\tfor _, o := range o.Data {\n\t\t\t\t// avoid gosec loop aliasing check :/\n\t\t\t\to := o\n\t\t\t\ttext.PrintKVStore(out, \"\", &o)\n\t\t\t}\n\t\t\tif cur, ok := o.Meta[\"next_cursor\"]; ok && cur != \"\" && cur != cursor {\n\t\t\t\tif c.Globals.Flags.NonInteractive || c.Globals.Flags.AutoYes || !text.IsTTY(out) {\n\t\t\t\t\t// If non-interactive or auto-yes, then load all data.\n\t\t\t\t\tcursor = cur\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\ttext.Break(out)\n\t\t\t\tprintNext, err := text.AskYesNo(out, \"Print next page [y/N]: \", in)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tif printNext {\n\t\t\t\t\ttext.Break(out)\n\t\t\t\t\tcursor = cur\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn nil\n\t}\n}\n"
  },
  {
    "path": "pkg/commands/kvstore/root.go",
    "content": "package kvstore\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"kv-store\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manipulate Fastly KV Stores\").Alias(\"object-store\")\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/kvstoreentry/create.go",
    "content": "package kvstoreentry\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"io/fs\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"sync/atomic\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/api\"\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/runtime\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"create\", \"Insert a key-value pair\").Alias(\"insert\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"store-id\", \"Store ID\").Short('s').Required().StringVar(&c.Input.StoreID)\n\n\t// Optional.\n\tc.CmdClause.Flag(\"add\", \"Limit the operation to adding a new item. If an existing item with the specified key exists, the operation will fail (default: false)\").BoolVar(&c.add)\n\tc.CmdClause.Flag(\"append\", \"If an item with the specified key exists, the value provided in the operation is appended to the existing value instead of replacing it (default: false)\").BoolVar(&c.append)\n\tc.CmdClause.Flag(\"background-fetch\", \"If set to true, the new value for the item will not be immediately visible to other users of the KV store; they will receive the existing (stale) value while the platform updates cached copies. Setting this to true ensures that other users of the KV store will receive responses to 'get' operations for this item quickly, although they will be slightly out of date (default: false)\").BoolVar(&c.backFetch)\n\tc.CmdClause.Flag(\"dir\", \"Path to a directory containing individual files where the filename is the key and the file contents is the value\").StringVar(&c.dirPath)\n\tc.CmdClause.Flag(\"dir-allow-hidden\", \"Allow hidden files (e.g. dot files) to be included (skipped by default)\").BoolVar(&c.dirAllowHidden)\n\tc.CmdClause.Flag(\"dir-concurrency\", \"Limit the number of concurrent network resources allocated\").Default(\"50\").IntVar(&c.dirConcurrency)\n\tc.CmdClause.Flag(\"file\", `Path to a file containing individual JSON objects (e.g., {\"key\":\"...\",\"value\":\"base64_encoded_value\"}) separated by new-line delimiter`).StringVar(&c.filePath)\n\tc.CmdClause.Flag(\"if-generation-match\", \"Value which must match the current generation marker in an item for an update operation to proceed\").StringVar(&c.ifGenMatch)\n\tc.CmdClause.Flag(\"metadata\", \"An arbitrary data field which can contain up to 2000 bytes of data\").StringVar(&c.metadata)\n\tc.CmdClause.Flag(\"prepend\", \"If an item with the specified key exists, the value provided in the operation is prepended to the existing value instead of replacing it (Default: false)\").BoolVar(&c.prepend)\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\tc.CmdClause.Flag(\"key\", \"Key name\").Short('k').StringVar(&c.Input.Key)\n\tc.CmdClause.Flag(\"stdin\", \"Read new-line separated JSON stream via STDIN\").BoolVar(&c.stdin)\n\tc.CmdClause.Flag(\"value\", \"Value\").StringVar(&c.Input.Value)\n\n\treturn &c\n}\n\n// CreateCommand calls the Fastly API to insert a key into a kv store.\ntype CreateCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tadd            bool\n\tappend         bool\n\tbackFetch      bool\n\tdirAllowHidden bool\n\tdirConcurrency int\n\tdirPath        string\n\tfilePath       string\n\tifGenMatch     string\n\tmetadata       string\n\tprepend        bool\n\tstdin          bool\n\n\tInput fastly.InsertKVStoreKeyInput\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *CreateCommand) Exec(in io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tif err := c.CheckFlags(); err != nil {\n\t\treturn err\n\t}\n\n\tif c.stdin {\n\t\treturn c.ProcessStdin(in, out)\n\t}\n\n\tif c.filePath != \"\" {\n\t\treturn c.ProcessFile(out)\n\t}\n\n\tif c.dirPath != \"\" {\n\t\treturn c.ProcessDir(in, out)\n\t}\n\n\tif c.Input.Key == \"\" || c.Input.Value == \"\" {\n\t\treturn fsterr.ErrInvalidKVCombo\n\t}\n\n\t// Append optional params.\n\tc.Input.Add = c.add\n\tc.Input.Append = c.append\n\tc.Input.BackgroundFetch = c.backFetch\n\t// Parse generation match if provided.\n\tif c.ifGenMatch != \"\" {\n\t\tinputGeneration, err := strconv.ParseUint(c.ifGenMatch, 10, 64)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"invalid generation value: %s\", c.ifGenMatch)\n\t\t}\n\t\tc.Input.IfGenerationMatch = inputGeneration\n\t}\n\n\tif c.metadata != \"\" {\n\t\tc.Input.Metadata = &c.metadata\n\t}\n\tc.Input.Prepend = c.prepend\n\n\terr := c.Globals.APIClient.InsertKVStoreKey(context.TODO(), &c.Input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif c.JSONOutput.Enabled {\n\t\to := struct {\n\t\t\tID  string `json:\"id\"`\n\t\t\tKey string `json:\"key\"`\n\t\t}{\n\t\t\tc.Input.StoreID,\n\t\t\tc.Input.Key,\n\t\t}\n\t\t_, err := c.WriteJSON(out, o)\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Created key '%s' in KV Store '%s'\", c.Input.Key, c.Input.StoreID)\n\treturn nil\n}\n\n// CheckFlags ensures only one of the three specified flags are provided.\nfunc (c *CreateCommand) CheckFlags() error {\n\tflagCount := 0\n\tif c.stdin {\n\t\tflagCount++\n\t}\n\tif c.filePath != \"\" {\n\t\tflagCount++\n\t}\n\tif c.dirPath != \"\" {\n\t\tflagCount++\n\t}\n\tif flagCount > 1 {\n\t\treturn fsterr.ErrInvalidStdinFileDirCombo\n\t}\n\treturn nil\n}\n\n// ProcessStdin streams STDIN to the batch API endpoint.\nfunc (c *CreateCommand) ProcessStdin(in io.Reader, out io.Writer) error {\n\t// Determine if 'in' has data available.\n\tif in == nil || text.IsTTY(in) {\n\t\treturn fsterr.ErrNoSTDINData\n\t}\n\tif c.Globals.Verbose() {\n\t\tin = io.TeeReader(in, out)\n\t}\n\treturn c.CallBatchEndpoint(in, out)\n}\n\n// ProcessFile streams a JSON file content to the batch API endpoint.\nfunc (c *CreateCommand) ProcessFile(out io.Writer) error {\n\tf, err := os.Open(c.filePath)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\tdefer func() {\n\t\t_ = f.Close()\n\t}()\n\n\tvar in io.Reader = f\n\tif c.Globals.Verbose() {\n\t\tin = io.TeeReader(f, out)\n\t}\n\treturn c.CallBatchEndpoint(in, out)\n}\n\n// ProcessDir concurrently reads files from the given directory structure and\n// uploads each file to the set-value-for-key endpoint where the filename is the\n// key and the file content is the value.\n//\n// NOTE: Unlike ProcessStdin/ProcessFile content doesn't need to be base64.\nfunc (c *CreateCommand) ProcessDir(in io.Reader, out io.Writer) error {\n\tif runtime.Windows {\n\t\tcont, err := c.PromptWindowsUser(in, out)\n\t\tif err != nil {\n\t\t\tc.Globals.ErrLog.Add(err)\n\t\t\treturn err\n\t\t}\n\t\tif !cont {\n\t\t\treturn nil\n\t\t}\n\t\ttext.Break(out)\n\t}\n\n\tpath, err := filepath.Abs(c.dirPath)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tallFiles, err := os.ReadDir(path)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfilteredFiles := make([]fs.DirEntry, 0)\n\tfor _, file := range allFiles {\n\t\t// Skip directories/symlinks OR any hidden files unless the user allows them.\n\t\tif !file.Type().IsRegular() || (file.Type().IsRegular() && isHiddenFile(file.Name()) && !c.dirAllowHidden) {\n\t\t\tcontinue\n\t\t}\n\t\tfilteredFiles = append(filteredFiles, file)\n\t}\n\n\tspinner, err := text.NewSpinner(out)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\terr = spinner.Start()\n\tif err != nil {\n\t\treturn err\n\t}\n\tfilesTotal := len(filteredFiles)\n\tmsg := \"%s %d of %d files\"\n\tspinner.Message(fmt.Sprintf(msg, \"Processing\", 0, filesTotal) + \"...\")\n\n\tprocessed := make(chan struct{}, c.dirConcurrency)\n\tsem := make(chan struct{}, c.dirConcurrency)\n\tfilesVerboseOutput := make(chan string, filesTotal)\n\n\tvar (\n\t\tprocessingErrors []ProcessErr\n\t\tfilesProcessed   uint64\n\t\t// NOTE: mu protects access to the 'processingErrors' shared resource.\n\t\t// We create multiple goroutines (one for each file) and each one has the\n\t\t// potential to mutate the slice by appending new errors to it.\n\t\tmu sync.Mutex\n\t\twg sync.WaitGroup\n\t)\n\n\tgo func() {\n\t\tfor range processed {\n\t\t\tatomic.AddUint64(&filesProcessed, 1)\n\t\t\tspinner.Message(fmt.Sprintf(msg, \"Processing\", filesProcessed, filesTotal) + \"...\")\n\t\t}\n\t}()\n\n\tfor _, file := range filteredFiles {\n\t\twg.Add(1)\n\n\t\tgo func(file fs.DirEntry) {\n\t\t\t// Restrict resource allocation if concurrency limit is exceeded.\n\t\t\tsem <- struct{}{}\n\t\t\tdefer func() {\n\t\t\t\tprocessed <- struct{}{}\n\t\t\t\t<-sem\n\t\t\t}()\n\t\t\tdefer wg.Done()\n\n\t\t\tfilename := file.Name()\n\t\t\tfilePath := filepath.Join(path, filename)\n\n\t\t\tif c.Globals.Verbose() {\n\t\t\t\tfilesVerboseOutput <- filename\n\t\t\t}\n\n\t\t\t// G304 (CWE-22): Potential file inclusion via variable\n\t\t\t// #nosec\n\t\t\tf, err := os.Open(filePath)\n\t\t\tif err != nil {\n\t\t\t\tmu.Lock()\n\t\t\t\tprocessingErrors = append(processingErrors, ProcessErr{\n\t\t\t\t\tFile: filePath,\n\t\t\t\t\tErr:  err,\n\t\t\t\t})\n\t\t\t\tmu.Unlock()\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tlr, err := fastly.FileLengthReader(f)\n\t\t\tif err != nil {\n\t\t\t\tmu.Lock()\n\t\t\t\tprocessingErrors = append(processingErrors, ProcessErr{\n\t\t\t\t\tFile: filePath,\n\t\t\t\t\tErr:  err,\n\t\t\t\t})\n\t\t\t\tmu.Unlock()\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\topts := insertKeyOptions{\n\t\t\t\tclient: c.Globals.APIClient,\n\t\t\t\tid:     c.Input.StoreID,\n\t\t\t\tkey:    filename,\n\t\t\t\tfile:   lr,\n\t\t\t}\n\n\t\t\terr = insertKey(opts)\n\t\t\tif err != nil {\n\t\t\t\t// In case the network connection is lost due to exhaustion of\n\t\t\t\t// resources, then try one more time to make the request.\n\t\t\t\t//\n\t\t\t\t// NOTE: you can't type assert the error as it's not exported.\n\t\t\t\t// https://github.com/golang/go/issues/54173\n\t\t\t\tif strings.Contains(err.Error(), \"net/http: cannot rewind body after connection loss\") {\n\t\t\t\t\terr = insertKey(opts)\n\t\t\t\t\tif err == nil {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tmu.Lock()\n\t\t\t\tprocessingErrors = append(processingErrors, ProcessErr{\n\t\t\t\t\tFile: filePath,\n\t\t\t\t\tErr:  err,\n\t\t\t\t})\n\t\t\t\tmu.Unlock()\n\t\t\t\treturn\n\t\t\t}\n\t\t}(file)\n\t}\n\n\twg.Wait()\n\n\tspinner.StopMessage(fmt.Sprintf(msg, \"Processed\", atomic.LoadUint64(&filesProcessed)-uint64(len(processingErrors)), filesTotal))\n\terr = spinner.Stop()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif c.Globals.Verbose() {\n\t\tclose(filesVerboseOutput)\n\t\ttext.Break(out)\n\t\tfor filename := range filesVerboseOutput {\n\t\t\tfmt.Println(filename)\n\t\t}\n\t}\n\n\tif len(processingErrors) == 0 {\n\t\ttext.Success(out, \"\\nInserted %d keys into KV Store\", len(filteredFiles))\n\t\treturn nil\n\t}\n\n\ttext.Break(out)\n\tfor _, err := range processingErrors {\n\t\tfmt.Printf(\"File: %s\\nError: %s\\n\\n\", err.File, err.Err.Error())\n\t}\n\n\treturn errors.New(\"failed to process all the provided files (see error log above ⬆️)\")\n}\n\n// PromptWindowsUser ensures a user understands that we only filter files whose\n// name is prefixed with a dot and not any other kind of 'hidden' attribute that\n// can be set by the Windows platform.\nfunc (c *CreateCommand) PromptWindowsUser(in io.Reader, out io.Writer) (bool, error) {\n\tif !c.Globals.Flags.AutoYes && !c.Globals.Flags.NonInteractive {\n\t\tlabel := `The Fastly CLI will skip dotfiles (filenames prefixed with a period character, example: '.ignore') but this does not include files set with a \"hidden\" attribute). Are you sure you want to continue? [y/N] `\n\t\tresult, err := text.AskYesNo(out, label, in)\n\t\tif err != nil {\n\t\t\treturn false, err\n\t\t}\n\t\treturn result, nil\n\t}\n\treturn true, nil\n}\n\n// CallBatchEndpoint calls the batch API endpoint.\nfunc (c *CreateCommand) CallBatchEndpoint(in io.Reader, out io.Writer) error {\n\ttype result struct {\n\t\tSuccess bool                  `json:\"success\"`\n\t\tErrors  []*fastly.ErrorObject `json:\"errors,omitempty\"`\n\t}\n\n\tif err := c.Globals.APIClient.BatchModifyKVStoreKey(context.TODO(), &fastly.BatchModifyKVStoreKeyInput{\n\t\tStoreID: c.Input.StoreID,\n\t\tBody:    in,\n\t}); err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\n\t\tr := result{Success: false}\n\n\t\the, ok := err.(*fastly.HTTPError)\n\t\tif ok {\n\t\t\tr.Errors = append(r.Errors, he.Errors...)\n\t\t}\n\n\t\tif c.JSONOutput.Enabled {\n\t\t\t_, err := c.WriteJSON(out, r)\n\t\t\treturn err\n\t\t}\n\n\t\t// If we were able to convert the error into a fastly.HTTPError, then\n\t\t// display those errors to the user, otherwise we'll display the original\n\t\t// error type.\n\t\tif ok {\n\t\t\tfor i, e := range he.Errors {\n\t\t\t\ttext.Output(out, \"Error %d\", i)\n\t\t\t\ttext.Output(out, \"Title: %s\", e.Title)\n\t\t\t\ttext.Output(out, \"Code: %s\", e.Code)\n\t\t\t\ttext.Output(out, \"Detail: %s\", e.Detail)\n\t\t\t\ttext.Break(out)\n\t\t\t}\n\t\t\treturn he\n\t\t}\n\t\treturn err\n\t}\n\n\tif c.JSONOutput.Enabled {\n\t\t_, err := c.WriteJSON(out, result{Success: true})\n\t\treturn err\n\t}\n\n\tif c.Globals.Verbose() {\n\t\ttext.Break(out)\n\t}\n\ttext.Success(out, \"Inserted keys into KV Store\")\n\treturn nil\n}\n\nfunc insertKey(opts insertKeyOptions) error {\n\treturn opts.client.InsertKVStoreKey(context.TODO(), &fastly.InsertKVStoreKeyInput{\n\t\tBody:    opts.file,\n\t\tStoreID: opts.id,\n\t\tKey:     opts.key,\n\t})\n}\n\ntype insertKeyOptions struct {\n\tclient api.Interface\n\tid     string\n\tkey    string\n\tfile   fastly.LengthReader\n}\n\n// ProcessErr represents an error related to processing individual files.\ntype ProcessErr struct {\n\tFile string\n\tErr  error\n}\n"
  },
  {
    "path": "pkg/commands/kvstoreentry/delete.go",
    "content": "package kvstoreentry\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"strconv\"\n\t\"sync\"\n\t\"sync/atomic\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DeleteKeysPoolSize is the goroutine/thread-pool size.\n// Each pool will take a 'key' from a channel and issue a DELETE request.\nconst DeleteKeysPoolSize int = 100\n\n// DeleteKeysMaxErrors is the maximum number of errors we'll allow before\n// stopping the goroutines from executing.\nconst DeleteKeysMaxErrors int = 100\n\n// DeleteCommand calls the Fastly API to delete a kv store.\ntype DeleteCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\tkey argparser.OptionalString\n\n\t// NOTE: Public fields can be set via `kv-store delete`.\n\tDeleteAll         bool\n\tForce             bool\n\tIfGenerationMatch string\n\tMaxErrors         int\n\tPoolSize          int\n\tStoreID           string\n}\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"delete\", \"Delete a key\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"store-id\", \"Store ID\").Short('s').Required().StringVar(&c.StoreID)\n\n\t// Optional.\n\tc.CmdClause.Flag(\"all\", \"Delete all entries within the store\").Short('a').BoolVar(&c.DeleteAll)\n\tc.CmdClause.Flag(\"concurrency\", \"The thread pool size (ignored when set without the --all flag)\").Default(strconv.Itoa(DeleteKeysPoolSize)).Short('r').IntVar(&c.PoolSize)\n\tc.CmdClause.Flag(\"force\", \"Return a successful result from a 'delete' operation even if the specified key was not found\").BoolVar(&c.Force)\n\tc.CmdClause.Flag(\"if-generation-match\", \"Value which must match the current generation marker in an item for a delete operation to proceed\").StringVar(&c.IfGenerationMatch)\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\tc.CmdClause.Flag(\"key\", \"Key name\").Short('k').Action(c.key.Set).StringVar(&c.key.Value)\n\tc.CmdClause.Flag(\"max-errors\", \"The number of errors to accept before stopping (ignored when set without the --all flag)\").Default(strconv.Itoa(DeleteKeysMaxErrors)).Short('m').IntVar(&c.MaxErrors)\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DeleteCommand) Exec(in io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\t// TODO: Support --json for bulk deletions.\n\tif c.DeleteAll && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidDeleteAllJSONKeyCombo\n\t}\n\tif c.DeleteAll && c.key.WasSet {\n\t\treturn fsterr.ErrInvalidDeleteAllKeyCombo\n\t}\n\tif !c.DeleteAll && !c.key.WasSet {\n\t\treturn fsterr.ErrMissingDeleteAllKeyCombo\n\t}\n\n\tif c.DeleteAll {\n\t\tif !c.Globals.Flags.AutoYes && !c.Globals.Flags.NonInteractive {\n\t\t\ttext.Warning(out, \"This will delete ALL entries from your store!\\n\\n\")\n\t\t\tcont, err := text.AskYesNo(out, \"Are you sure you want to continue? [y/N]: \", in)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif !cont {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\ttext.Break(out)\n\t\t}\n\t\treturn c.DeleteAllKeys(out)\n\t}\n\n\tinput := fastly.DeleteKVStoreKeyInput{\n\t\tStoreID: c.StoreID,\n\t\tForce:   c.Force,\n\t\tKey:     c.key.Value,\n\t}\n\n\t// Validate generation value if provided.\n\tif c.IfGenerationMatch != \"\" {\n\t\t_, err := strconv.ParseUint(c.IfGenerationMatch, 10, 64)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"invalid generation value: %s\", c.IfGenerationMatch)\n\t\t}\n\t\tinput.IfGenerationMatch, _ = strconv.ParseUint(c.IfGenerationMatch, 10, 64)\n\t}\n\n\terr := c.Globals.APIClient.DeleteKVStoreKey(context.TODO(), &input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif c.JSONOutput.Enabled {\n\t\to := struct {\n\t\t\tKey     string `json:\"key\"`\n\t\t\tID      string `json:\"store_id\"`\n\t\t\tDeleted bool   `json:\"deleted\"`\n\t\t}{\n\t\t\tc.key.Value,\n\t\t\tc.StoreID,\n\t\t\ttrue,\n\t\t}\n\t\t_, err := c.WriteJSON(out, o)\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Deleted key '%s' from KV Store '%s'\", c.key.Value, c.StoreID)\n\treturn nil\n}\n\n// DeleteAllKeys deletes all keys within the specified KV Store.\n// NOTE: It's a public method as it can be called via `kv-store delete --all`.\nfunc (c *DeleteCommand) DeleteAllKeys(out io.Writer) error {\n\tspinnerMessage := \"Deleting keys\"\n\tvar spinner text.Spinner\n\n\tvar err error\n\tspinner, err = text.NewSpinner(out)\n\tif err != nil {\n\t\treturn err\n\t}\n\terr = spinner.Start()\n\tif err != nil {\n\t\treturn err\n\t}\n\tspinner.Message(spinnerMessage + \"...\")\n\n\tp := c.Globals.APIClient.NewListKVStoreKeysPaginator(context.TODO(), &fastly.ListKVStoreKeysInput{\n\t\tStoreID: c.StoreID,\n\t})\n\n\terrorsCh := make(chan string, c.MaxErrors)\n\tkeysCh := make(chan string, 1000) // number correlates to pagination page size\n\n\tvar (\n\t\tdeleteCount atomic.Uint64\n\t\tfailedKeys  []string\n\t\twg          sync.WaitGroup\n\t)\n\n\t// We have two separate execution flows happening at once:\n\t//\n\t// 1. Pushing keys from pagination data into a key channel.\n\t// 2. Pulling keys from key channel and issuing API DELETE call.\n\t//\n\t// We have a limit on the number of errors. Once that limit is reached we'll\n\t// stop the 2. set of goroutines.\n\n\twg.Add(1)\n\tgo func() {\n\t\tdefer wg.Done()\n\t\tdefer close(keysCh)\n\t\tfor p.Next() {\n\t\t\tfor _, key := range p.Keys() {\n\t\t\t\tkeysCh <- key\n\t\t\t}\n\t\t}\n\t}()\n\n\tfor range c.PoolSize {\n\t\twg.Add(1)\n\t\tgo func() {\n\t\t\tdefer wg.Done()\n\t\t\tfor key := range keysCh {\n\t\t\t\terr := c.Globals.APIClient.DeleteKVStoreKey(context.TODO(), &fastly.DeleteKVStoreKeyInput{StoreID: c.StoreID, Key: key})\n\t\t\t\tif err != nil {\n\t\t\t\t\tselect {\n\t\t\t\t\tcase errorsCh <- key:\n\t\t\t\t\tdefault:\n\t\t\t\t\t\treturn // channel is blocked\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tspinner.Message(spinnerMessage + \"...\" + strconv.FormatUint(deleteCount.Add(1), 10))\n\t\t\t}\n\t\t}()\n\t}\n\n\twg.Wait()\n\n\tclose(errorsCh)\n\tfor err := range errorsCh {\n\t\tfailedKeys = append(failedKeys, err)\n\t}\n\n\tspinnerMessage = \"Deleted keys: \" + strconv.FormatUint(deleteCount.Load(), 10)\n\n\tif len(failedKeys) > 0 {\n\t\tspinner.StopFailMessage(spinnerMessage)\n\t\terr := spinner.StopFail()\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to stop spinner: %w\", err)\n\t\t}\n\t\treturn fmt.Errorf(\"failed to delete %d keys\", len(failedKeys))\n\t}\n\n\tspinner.StopMessage(spinnerMessage)\n\tif err := spinner.Stop(); err != nil {\n\t\treturn fmt.Errorf(\"failed to stop spinner: %w\", err)\n\t}\n\n\ttext.Success(out, \"\\nDeleted all keys from KV Store '%s'\", c.StoreID)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/kvstoreentry/describe.go",
    "content": "package kvstoreentry\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// DescribeCommand calls the Fastly API to fetch the value of a key from a kv store.\ntype DescribeCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tInput fastly.GetKVStoreItemInput\n}\n\n// NewDescribeCommand returns a usable command registered under the parent.\nfunc NewDescribeCommand(parent argparser.Registerer, g *global.Data) *DescribeCommand {\n\tc := DescribeCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t\t// This argument suppresses the 'Fastly API' output from the global verbose command.\n\t\t\tSuppressVerbose: true,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"describe\", \"Get the associated attributes of a key\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"key\", \"Key name\").Short('k').Required().StringVar(&c.Input.Key)\n\tc.CmdClause.Flag(\"store-id\", \"Store ID\").Short('s').Required().StringVar(&c.Input.StoreID)\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DescribeCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tif c.Globals.Flags.Verbose {\n\t\t// We won't be supporting a --verbose flag here as there wouldn't be any additional output to provide.\n\t\treturn fmt.Errorf(\"the 'describe' command does not support the --verbose flag\")\n\t}\n\n\titem, err := c.Globals.APIClient.GetKVStoreItem(context.TODO(), &c.Input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif c.JSONOutput.Enabled {\n\t\to := map[string]interface{}{\n\t\t\t\"key\":        c.Input.Key,\n\t\t\t\"generation\": fmt.Sprintf(\"%d\", item.Generation),\n\t\t\t\"metadata\":   item.Metadata,\n\t\t}\n\t\tif ok, err := c.WriteJSON(out, o); ok {\n\t\t\treturn err\n\t\t}\n\t\treturn nil\n\t}\n\n\t// IMPORTANT: Don't use `text` package as binary data can be messed up.\n\t// Print the key attributes.\n\tfmt.Fprintf(out, \"Key: %s\\n\", c.Input.Key)\n\tfmt.Fprintf(out, \"Generation: %d\\n\", item.Generation)\n\tfmt.Fprintf(out, \"Metadata: %s\\n\", item.Metadata)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/kvstoreentry/doc.go",
    "content": "// Package kvstoreentry contains commands to inspect and manipulate Fastly edge\n// kv stores keys.\npackage kvstoreentry\n"
  },
  {
    "path": "pkg/commands/kvstoreentry/get.go",
    "content": "package kvstoreentry\n\nimport (\n\t\"context\"\n\t\"encoding/base64\"\n\t\"fmt\"\n\t\"io\"\n\t\"strconv\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// GetCommand calls the Fastly API to fetch the value of a key from a kv store.\ntype GetCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tInput      fastly.GetKVStoreItemInput\n\tGeneration string\n}\n\n// NewGetCommand returns a usable command registered under the parent.\nfunc NewGetCommand(parent argparser.Registerer, g *global.Data) *GetCommand {\n\tc := GetCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t\t// This argument suppresses the 'Fastly API' output from the global verbose command.\n\t\t\tSuppressVerbose: true,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"get\", \"Get the value associated with a key\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"key\", \"Key name\").Short('k').Required().StringVar(&c.Input.Key)\n\tc.CmdClause.Flag(\"store-id\", \"Store ID\").Short('s').Required().StringVar(&c.Input.StoreID)\n\n\t// Optional.\n\tc.CmdClause.Flag(\"if-generation-match\", \"Compares if the provided generation marker matches that of the object\").StringVar(&c.Generation)\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *GetCommand) Exec(_ io.Reader, out io.Writer) error {\n\t// As the 'describe' command provides the object attributes,\n\t// we won't be supporting a --verbose flag here.\n\tif c.Globals.Flags.Verbose {\n\t\treturn fmt.Errorf(\"the 'get' command does not support the --verbose flag\")\n\t}\n\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\t// Validate generation value before making API call\n\tvar inputGeneration uint64\n\tif c.Generation != \"\" {\n\t\tvar err error\n\t\tinputGeneration, err = strconv.ParseUint(c.Generation, 10, 64)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"invalid generation value: %s\", c.Generation)\n\t\t}\n\t}\n\n\tresult, err := c.Globals.APIClient.GetKVStoreItem(context.TODO(), &c.Input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\t// Check if the generation marker matches the API result\n\tif c.Generation != \"\" {\n\t\tif inputGeneration != result.Generation {\n\t\t\treturn fmt.Errorf(\"generation value does not match: expected %d, got %d\", result.Generation, inputGeneration)\n\t\t}\n\t}\n\n\t// Ensure we close the value reader.\n\tif result.Value != nil {\n\t\tdefer result.Value.Close()\n\t}\n\n\t// Read the value from ReadCloser.\n\tvar value string\n\tif result.Value != nil {\n\t\tvalueBytes, err := io.ReadAll(result.Value)\n\t\tif err != nil {\n\t\t\tc.Globals.ErrLog.Add(err)\n\t\t\treturn err\n\t\t}\n\t\tvalue = string(valueBytes)\n\t}\n\n\tif c.JSONOutput.Enabled {\n\t\t// We are encoding the value of the item here to ensure safe\n\t\t// output for binary content along with other outputs.\n\t\tencodedValue := base64.StdEncoding.EncodeToString([]byte(value))\n\t\ttext.Output(out, `{\"%s\": \"%s\"}`, c.Input.Key, encodedValue)\n\t\treturn nil\n\t}\n\n\t// IMPORTANT: Don't use `text` package as binary data can be messed up.\n\tfmt.Fprint(out, value)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/kvstoreentry/hidden.go",
    "content": "package kvstoreentry\n\nfunc isHiddenFile(filename string) bool {\n\treturn filename[0] == '.'\n}\n"
  },
  {
    "path": "pkg/commands/kvstoreentry/kvstoreentry_test.go",
    "content": "package kvstoreentry_test\n\nimport (\n\t\"context\"\n\t\"encoding/base64\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\troot \"github.com/fastly/cli/pkg/commands/kvstoreentry\"\n\tfstfmt \"github.com/fastly/cli/pkg/fmt\"\n\t\"github.com/fastly/cli/pkg/mock\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n)\n\nfunc TestCreateCommand(t *testing.T) {\n\tconst (\n\t\tstoreID   = \"store-id-123\"\n\t\titemKey   = \"foo\"\n\t\titemValue = \"the-value\"\n\t)\n\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"--key a-key --value a-value\",\n\t\t\tWantError: \"error parsing arguments: required flag --store-id not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: fmt.Sprintf(\"--store-id %s --key %s --value %s\", storeID, itemKey, itemValue),\n\t\t\tAPI: &mock.API{\n\t\t\t\tInsertKVStoreKeyFn: func(_ context.Context, _ *fastly.InsertKVStoreKeyInput) error {\n\t\t\t\t\treturn errors.New(\"invalid request\")\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"invalid request\",\n\t\t},\n\t\t{\n\t\t\tArgs: fmt.Sprintf(\"--store-id %s --key %s --value %s\", storeID, itemKey, itemValue),\n\t\t\tAPI: &mock.API{\n\t\t\t\tInsertKVStoreKeyFn: func(_ context.Context, _ *fastly.InsertKVStoreKeyInput) error {\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Created key '%s' in KV Store '%s'\", itemKey, storeID),\n\t\t},\n\t\t{\n\t\t\tArgs: fmt.Sprintf(\"--store-id %s --key %s --value %s --json\", storeID, itemKey, itemValue),\n\t\t\tAPI: &mock.API{\n\t\t\t\tInsertKVStoreKeyFn: func(_ context.Context, _ *fastly.InsertKVStoreKeyInput) error {\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.JSON(`{\"id\": %q, \"key\": %q}`, storeID, itemKey),\n\t\t},\n\t\t{\n\t\t\tName: \"validate --add flag\",\n\t\t\tArgs: fmt.Sprintf(\"--store-id %s --key %s --value %s --add\", storeID, itemKey, itemValue),\n\t\t\tAPI: &mock.API{\n\t\t\t\tInsertKVStoreKeyFn: func(_ context.Context, i *fastly.InsertKVStoreKeyInput) error {\n\t\t\t\t\tif !i.Add {\n\t\t\t\t\t\treturn errors.New(\"expected Add flag to be true\")\n\t\t\t\t\t}\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Created key '%s' in KV Store '%s'\", itemKey, storeID),\n\t\t},\n\t\t{\n\t\t\tName: \"validate --append flag\",\n\t\t\tArgs: fmt.Sprintf(\"--store-id %s --key %s --value %s --append\", storeID, itemKey, itemValue),\n\t\t\tAPI: &mock.API{\n\t\t\t\tInsertKVStoreKeyFn: func(_ context.Context, i *fastly.InsertKVStoreKeyInput) error {\n\t\t\t\t\tif !i.Append {\n\t\t\t\t\t\treturn errors.New(\"expected Append flag to be true\")\n\t\t\t\t\t}\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Created key '%s' in KV Store '%s'\", itemKey, storeID),\n\t\t},\n\t\t{\n\t\t\tName: \"validate --prepend flag\",\n\t\t\tArgs: fmt.Sprintf(\"--store-id %s --key %s --value %s --prepend\", storeID, itemKey, itemValue),\n\t\t\tAPI: &mock.API{\n\t\t\t\tInsertKVStoreKeyFn: func(_ context.Context, i *fastly.InsertKVStoreKeyInput) error {\n\t\t\t\t\tif !i.Prepend {\n\t\t\t\t\t\treturn errors.New(\"expected Prepend flag to be true\")\n\t\t\t\t\t}\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Created key '%s' in KV Store '%s'\", itemKey, storeID),\n\t\t},\n\t\t{\n\t\t\tName: \"validate --background-fetch flag\",\n\t\t\tArgs: fmt.Sprintf(\"--store-id %s --key %s --value %s --background-fetch\", storeID, itemKey, itemValue),\n\t\t\tAPI: &mock.API{\n\t\t\t\tInsertKVStoreKeyFn: func(_ context.Context, i *fastly.InsertKVStoreKeyInput) error {\n\t\t\t\t\tif !i.BackgroundFetch {\n\t\t\t\t\t\treturn errors.New(\"expected BackgroundFetch flag to be true\")\n\t\t\t\t\t}\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Created key '%s' in KV Store '%s'\", itemKey, storeID),\n\t\t},\n\t\t{\n\t\t\tName: \"validate --if-generation-match flag\",\n\t\t\tArgs: fmt.Sprintf(\"--store-id %s --key %s --value %s --if-generation-match 42\", storeID, itemKey, itemValue),\n\t\t\tAPI: &mock.API{\n\t\t\t\tInsertKVStoreKeyFn: func(_ context.Context, i *fastly.InsertKVStoreKeyInput) error {\n\t\t\t\t\tif i.IfGenerationMatch != 42 {\n\t\t\t\t\t\treturn fmt.Errorf(\"expected IfGenerationMatch to be 42, got %d\", i.IfGenerationMatch)\n\t\t\t\t\t}\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Created key '%s' in KV Store '%s'\", itemKey, storeID),\n\t\t},\n\t\t{\n\t\t\tName:      \"validate --if-generation-match flag with invalid value\",\n\t\t\tArgs:      fmt.Sprintf(\"--store-id %s --key %s --value %s --if-generation-match invalid\", storeID, itemKey, itemValue),\n\t\t\tWantError: \"invalid generation value: invalid\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate --metadata flag\",\n\t\t\tArgs: fmt.Sprintf(\"--store-id %s --key %s --value %s --metadata %s\", storeID, itemKey, itemValue, \"test-metadata\"),\n\t\t\tAPI: &mock.API{\n\t\t\t\tInsertKVStoreKeyFn: func(_ context.Context, i *fastly.InsertKVStoreKeyInput) error {\n\t\t\t\t\tif i.Metadata == nil || *i.Metadata != \"test-metadata\" {\n\t\t\t\t\t\treturn errors.New(\"expected Metadata to be 'test-metadata'\")\n\t\t\t\t\t}\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Created key '%s' in KV Store '%s'\", itemKey, storeID),\n\t\t},\n\t\t{\n\t\t\tArgs:  fmt.Sprintf(\"--store-id %s --stdin\", storeID),\n\t\t\tStdin: []string{`{\"key\":\"example\",\"value\":\"VkFMVUU=\"}`},\n\t\t\tAPI: &mock.API{\n\t\t\t\tBatchModifyKVStoreKeyFn: func(_ context.Context, _ *fastly.BatchModifyKVStoreKeyInput) error {\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: \"SUCCESS: Inserted keys into KV Store\\n\",\n\t\t},\n\t\t{\n\t\t\tArgs: fmt.Sprintf(\"--store-id %s --file %s\", storeID, filepath.Join(\"testdata\", \"data.json\")),\n\t\t\tAPI: &mock.API{\n\t\t\t\tBatchModifyKVStoreKeyFn: func(_ context.Context, _ *fastly.BatchModifyKVStoreKeyInput) error {\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: \"SUCCESS: Inserted keys into KV Store\\n\",\n\t\t},\n\t\t{\n\t\t\tArgs:  fmt.Sprintf(\"--store-id %s --dir %s\", storeID, filepath.Join(\"testdata\", \"example\")),\n\t\t\tStdin: []string{\"y\"},\n\t\t\tAPI: &mock.API{\n\t\t\t\tInsertKVStoreKeyFn: func(_ context.Context, i *fastly.InsertKVStoreKeyInput) error {\n\t\t\t\t\tif i.Key == \"foo.txt\" {\n\t\t\t\t\t\treturn nil\n\t\t\t\t\t}\n\t\t\t\t\treturn errors.New(\"invalid request\")\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: \"SUCCESS: Inserted 1 keys into KV Store\",\n\t\t},\n\t\t{\n\t\t\tArgs:  fmt.Sprintf(\"--store-id %s --dir %s --dir-allow-hidden\", storeID, filepath.Join(\"testdata\", \"example\")),\n\t\t\tStdin: []string{\"y\"},\n\t\t\tAPI: &mock.API{\n\t\t\t\tInsertKVStoreKeyFn: func(_ context.Context, i *fastly.InsertKVStoreKeyInput) error {\n\t\t\t\t\tif i.Key == \"foo.txt\" || i.Key == \".hiddenfile\" {\n\t\t\t\t\t\treturn nil\n\t\t\t\t\t}\n\t\t\t\t\treturn errors.New(\"invalid request\")\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: \"SUCCESS: Inserted 2 keys into KV Store\",\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, \"create\"}, scenarios)\n}\n\nfunc TestDeleteCommand(t *testing.T) {\n\tconst (\n\t\tstoreID = \"store-id-123\"\n\t\titemKey = \"foo\"\n\t)\n\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"--key a-key\",\n\t\t\tWantError: \"error parsing arguments: required flag --store-id not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs:      \"--store-id \" + storeID,\n\t\t\tWantError: \"invalid command, neither --all or --key provided\",\n\t\t},\n\t\t{\n\t\t\tArgs:      \"--json --all --store-id \" + storeID,\n\t\t\tWantError: \"invalid flag combination, --all and --json\",\n\t\t},\n\t\t{\n\t\t\tArgs:      \"--key a-key --all --store-id \" + storeID,\n\t\t\tWantError: \"invalid flag combination, --all and --key\",\n\t\t},\n\t\t{\n\t\t\tArgs: fmt.Sprintf(\"--store-id %s --key %s\", storeID, itemKey),\n\t\t\tAPI: &mock.API{\n\t\t\t\tDeleteKVStoreKeyFn: func(_ context.Context, _ *fastly.DeleteKVStoreKeyInput) error {\n\t\t\t\t\treturn errors.New(\"invalid request\")\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"invalid request\",\n\t\t},\n\t\t{\n\t\t\tArgs: fmt.Sprintf(\"--store-id %s --key %s\", storeID, itemKey),\n\t\t\tAPI: &mock.API{\n\t\t\t\tDeleteKVStoreKeyFn: func(_ context.Context, _ *fastly.DeleteKVStoreKeyInput) error {\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Deleted key '%s' from KV Store '%s'\", itemKey, storeID),\n\t\t},\n\t\t{\n\t\t\tArgs: fmt.Sprintf(\"--store-id %s --key %s --json\", storeID, itemKey),\n\t\t\tAPI: &mock.API{\n\t\t\t\tDeleteKVStoreKeyFn: func(_ context.Context, _ *fastly.DeleteKVStoreKeyInput) error {\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.JSON(`{\"key\": \"%s\", \"store_id\": \"%s\", \"deleted\": true}`, itemKey, storeID),\n\t\t},\n\t\t{\n\t\t\tName: \"validate --force flag with any key\",\n\t\t\tArgs: fmt.Sprintf(\"--store-id %s --key %s --force\", storeID, \"myFakeKey\"),\n\t\t\tAPI: &mock.API{\n\t\t\t\tDeleteKVStoreKeyFn: func(_ context.Context, _ *fastly.DeleteKVStoreKeyInput) error {\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Deleted key '%s' from KV Store '%s'\", \"myFakeKey\", storeID),\n\t\t},\n\t\t{\n\t\t\tName: \"validate --if-generation-match with matching generation\",\n\t\t\tArgs: fmt.Sprintf(\"--store-id %s --key %s --if-generation-match 123\", storeID, itemKey),\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetKVStoreItemFn: func(_ context.Context, _ *fastly.GetKVStoreItemInput) (fastly.GetKVStoreItemOutput, error) {\n\t\t\t\t\treturn fastly.GetKVStoreItemOutput{Generation: 123}, nil\n\t\t\t\t},\n\t\t\t\tDeleteKVStoreKeyFn: func(_ context.Context, _ *fastly.DeleteKVStoreKeyInput) error {\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Deleted key '%s' from KV Store '%s'\", itemKey, storeID),\n\t\t},\n\t\t{\n\t\t\tName: \"validate --if-generation-match with invalid generation value\",\n\t\t\tArgs: fmt.Sprintf(\"--store-id %s --key %s --if-generation-match invalid\", storeID, itemKey),\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetKVStoreItemFn: func(_ context.Context, _ *fastly.GetKVStoreItemInput) (fastly.GetKVStoreItemOutput, error) {\n\t\t\t\t\treturn fastly.GetKVStoreItemOutput{Generation: 123}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: `invalid generation value: invalid`,\n\t\t},\n\t\t{\n\t\t\tArgs: fmt.Sprintf(\"--store-id %s --all --auto-yes\", storeID),\n\t\t\tAPI: &mock.API{\n\t\t\t\tNewListKVStoreKeysPaginatorFn: func(_ context.Context, _ *fastly.ListKVStoreKeysInput) fastly.PaginatorKVStoreEntries {\n\t\t\t\t\treturn &mockKVStoresEntriesPaginator{\n\t\t\t\t\t\tnext: true,\n\t\t\t\t\t\tkeys: []string{\"foo\", \"bar\", \"baz\"},\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\tDeleteKVStoreKeyFn: func(_ context.Context, _ *fastly.DeleteKVStoreKeyInput) error {\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: \"Deleting keys...\",\n\t\t},\n\t\t{\n\t\t\tArgs: fmt.Sprintf(\"--store-id %s --all --auto-yes\", storeID),\n\t\t\tAPI: &mock.API{\n\t\t\t\tNewListKVStoreKeysPaginatorFn: func(_ context.Context, _ *fastly.ListKVStoreKeysInput) fastly.PaginatorKVStoreEntries {\n\t\t\t\t\treturn &mockKVStoresEntriesPaginator{\n\t\t\t\t\t\tnext: true,\n\t\t\t\t\t\tkeys: []string{\"foo\", \"bar\", \"baz\"},\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\tDeleteKVStoreKeyFn: func(_ context.Context, _ *fastly.DeleteKVStoreKeyInput) error {\n\t\t\t\t\treturn errors.New(\"whoops\")\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"failed to delete 3 keys\",\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, \"delete\"}, scenarios)\n}\n\nfunc TestGetCommand(t *testing.T) {\n\tconst (\n\t\tstoreID   = \"store-id-123\"\n\t\titemKey   = \"foo\"\n\t\titemValue = \"a value\"\n\t)\n\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --store-id flag\",\n\t\t\tArgs:      \"--key a-key\",\n\t\t\tWantError: \"error parsing arguments: required flag --store-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --key flag\",\n\t\t\tArgs:      \"--store-id \" + storeID,\n\t\t\tWantError: \"error parsing arguments: required flag --key not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API error handling\",\n\t\t\tArgs: fmt.Sprintf(\"--store-id %s --key %s\", storeID, itemKey),\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetKVStoreItemFn: func(_ context.Context, _ *fastly.GetKVStoreItemInput) (fastly.GetKVStoreItemOutput, error) {\n\t\t\t\t\treturn fastly.GetKVStoreItemOutput{}, errors.New(\"invalid request\")\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"invalid request\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate successful get operation\",\n\t\t\tArgs: fmt.Sprintf(\"--store-id %s --key %s\", storeID, itemKey),\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetKVStoreItemFn: func(_ context.Context, _ *fastly.GetKVStoreItemInput) (fastly.GetKVStoreItemOutput, error) {\n\t\t\t\t\treturn fastly.GetKVStoreItemOutput{\n\t\t\t\t\t\tGeneration: 123,\n\t\t\t\t\t\tValue:      io.NopCloser(strings.NewReader(itemValue)),\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: itemValue,\n\t\t},\n\t\t{\n\t\t\tName: \"validate --json flag output\",\n\t\t\tArgs: fmt.Sprintf(\"--store-id %s --key %s --json\", storeID, itemKey),\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetKVStoreItemFn: func(_ context.Context, _ *fastly.GetKVStoreItemInput) (fastly.GetKVStoreItemOutput, error) {\n\t\t\t\t\treturn fastly.GetKVStoreItemOutput{\n\t\t\t\t\t\tGeneration: 123,\n\t\t\t\t\t\tValue:      io.NopCloser(strings.NewReader(itemValue)),\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fmt.Sprintf(`{\"%s\": \"%s\"}`, itemKey, base64.StdEncoding.EncodeToString([]byte(itemValue))) + \"\\n\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate --verbose flag error\",\n\t\t\tArgs:      fmt.Sprintf(\"--store-id %s --key %s --verbose\", storeID, itemKey),\n\t\t\tWantError: \"the 'get' command does not support the --verbose flag\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate --if-generation-match with matching generation\",\n\t\t\tArgs: fmt.Sprintf(\"--store-id %s --key %s --if-generation-match 123\", storeID, itemKey),\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetKVStoreItemFn: func(_ context.Context, _ *fastly.GetKVStoreItemInput) (fastly.GetKVStoreItemOutput, error) {\n\t\t\t\t\treturn fastly.GetKVStoreItemOutput{\n\t\t\t\t\t\tGeneration: 123,\n\t\t\t\t\t\tValue:      io.NopCloser(strings.NewReader(itemValue)),\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: itemValue,\n\t\t},\n\t\t{\n\t\t\tName: \"validate --if-generation-match with non-matching generation\",\n\t\t\tArgs: fmt.Sprintf(\"--store-id %s --key %s --if-generation-match 123\", storeID, itemKey),\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetKVStoreItemFn: func(_ context.Context, _ *fastly.GetKVStoreItemInput) (fastly.GetKVStoreItemOutput, error) {\n\t\t\t\t\treturn fastly.GetKVStoreItemOutput{\n\t\t\t\t\t\tGeneration: 456,\n\t\t\t\t\t\tValue:      io.NopCloser(strings.NewReader(itemValue)),\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"generation value does not match: expected 456, got 123\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate --if-generation-match with invalid generation value\",\n\t\t\tArgs: fmt.Sprintf(\"--store-id %s --key %s --if-generation-match invalid\", storeID, itemKey),\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetKVStoreItemFn: func(_ context.Context, _ *fastly.GetKVStoreItemInput) (fastly.GetKVStoreItemOutput, error) {\n\t\t\t\t\treturn fastly.GetKVStoreItemOutput{\n\t\t\t\t\t\tGeneration: 123,\n\t\t\t\t\t\tValue:      io.NopCloser(strings.NewReader(itemValue)),\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"invalid generation value: invalid\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate handling of nil value reader\",\n\t\t\tArgs: fmt.Sprintf(\"--store-id %s --key %s\", storeID, itemKey),\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetKVStoreItemFn: func(_ context.Context, _ *fastly.GetKVStoreItemInput) (fastly.GetKVStoreItemOutput, error) {\n\t\t\t\t\treturn fastly.GetKVStoreItemOutput{\n\t\t\t\t\t\tGeneration: 123,\n\t\t\t\t\t\tValue:      nil,\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: \"\",\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, \"get\"}, scenarios)\n}\n\nfunc TestDescribeCommand(t *testing.T) {\n\tconst (\n\t\tstoreID      = \"store-id-123\"\n\t\titemKey      = \"foo\"\n\t\titemMetadata = \"test-metadata\"\n\t)\n\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --store-id flag\",\n\t\t\tArgs:      \"--key a-key\",\n\t\t\tWantError: \"error parsing arguments: required flag --store-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --key flag\",\n\t\t\tArgs:      \"--store-id \" + storeID,\n\t\t\tWantError: \"error parsing arguments: required flag --key not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API error handling\",\n\t\t\tArgs: fmt.Sprintf(\"--store-id %s --key %s\", storeID, itemKey),\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetKVStoreItemFn: func(_ context.Context, _ *fastly.GetKVStoreItemInput) (fastly.GetKVStoreItemOutput, error) {\n\t\t\t\t\treturn fastly.GetKVStoreItemOutput{}, errors.New(\"invalid request\")\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"invalid request\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate successful describe operation\",\n\t\t\tArgs: fmt.Sprintf(\"--store-id %s --key %s\", storeID, itemKey),\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetKVStoreItemFn: func(_ context.Context, _ *fastly.GetKVStoreItemInput) (fastly.GetKVStoreItemOutput, error) {\n\t\t\t\t\treturn fastly.GetKVStoreItemOutput{\n\t\t\t\t\t\tGeneration: 123,\n\t\t\t\t\t\tMetadata:   itemMetadata,\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fmt.Sprintf(\"Key: %s\\nGeneration: %d\\nMetadata: %s\\n\", itemKey, 123, itemMetadata),\n\t\t},\n\t\t{\n\t\t\tName: \"validate --json flag output\",\n\t\t\tArgs: fmt.Sprintf(\"--store-id %s --key %s --json\", storeID, itemKey),\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetKVStoreItemFn: func(_ context.Context, _ *fastly.GetKVStoreItemInput) (fastly.GetKVStoreItemOutput, error) {\n\t\t\t\t\treturn fastly.GetKVStoreItemOutput{\n\t\t\t\t\t\tGeneration: 123,\n\t\t\t\t\t\tMetadata:   itemMetadata,\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fmt.Sprintf(\"{\\n  \\\"generation\\\": \\\"%d\\\",\\n  \\\"key\\\": \\\"%s\\\",\\n  \\\"metadata\\\": \\\"%s\\\"\\n}\\n\", 123, itemKey, itemMetadata),\n\t\t},\n\t\t{\n\t\t\tName:      \"validate --verbose flag error\",\n\t\t\tArgs:      fmt.Sprintf(\"--store-id %s --key %s --verbose\", storeID, itemKey),\n\t\t\tWantError: \"the 'describe' command does not support the --verbose flag\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate handling of empty metadata\",\n\t\t\tArgs: fmt.Sprintf(\"--store-id %s --key %s\", storeID, itemKey),\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetKVStoreItemFn: func(_ context.Context, _ *fastly.GetKVStoreItemInput) (fastly.GetKVStoreItemOutput, error) {\n\t\t\t\t\treturn fastly.GetKVStoreItemOutput{\n\t\t\t\t\t\tGeneration: 456,\n\t\t\t\t\t\tMetadata:   \"\",\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fmt.Sprintf(\"Key: %s\\nGeneration: %d\\nMetadata: \\n\", itemKey, 456),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, \"describe\"}, scenarios)\n}\n\nfunc TestListCommand(t *testing.T) {\n\tconst storeID = \"store-id-123\"\n\n\ttestItems := make([]string, 3)\n\tfor i := range testItems {\n\t\ttestItems[i] = fmt.Sprintf(\"key-%02d\", i)\n\t}\n\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tWantError: \"error parsing arguments: required flag --store-id not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: fmt.Sprintf(\"--store-id %s\", storeID),\n\t\t\tAPI: &mock.API{\n\t\t\t\tListKVStoreKeysFn: func(_ context.Context, _ *fastly.ListKVStoreKeysInput) (*fastly.ListKVStoreKeysResponse, error) {\n\t\t\t\t\treturn nil, errors.New(\"invalid request\")\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"invalid request\",\n\t\t},\n\t\t{\n\t\t\tArgs: fmt.Sprintf(\"--store-id %s\", storeID),\n\t\t\tAPI: &mock.API{\n\t\t\t\tListKVStoreKeysFn: func(_ context.Context, _ *fastly.ListKVStoreKeysInput) (*fastly.ListKVStoreKeysResponse, error) {\n\t\t\t\t\treturn &fastly.ListKVStoreKeysResponse{Data: testItems}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: strings.Join(testItems, \"\\n\") + \"\\n\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate --prefix param\",\n\t\t\tArgs: fmt.Sprintf(\"--store-id %s --prefix=foo\", storeID),\n\t\t\tAPI: &mock.API{\n\t\t\t\tListKVStoreKeysFn: func(_ context.Context, _ *fastly.ListKVStoreKeysInput) (*fastly.ListKVStoreKeysResponse, error) {\n\t\t\t\t\treturn &fastly.ListKVStoreKeysResponse{Data: []string{\"foo-key1\", \"foo-key2\"}}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: \"✓ Getting data\\nfoo-key1\\nfoo-key2\\n\",\n\t\t},\n\t\t{\n\t\t\tArgs: fmt.Sprintf(\"--store-id %s --json\", storeID),\n\t\t\tAPI: &mock.API{\n\t\t\t\tListKVStoreKeysFn: func(_ context.Context, _ *fastly.ListKVStoreKeysInput) (*fastly.ListKVStoreKeysResponse, error) {\n\t\t\t\t\treturn &fastly.ListKVStoreKeysResponse{Data: testItems}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(testItems),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, \"list\"}, scenarios)\n}\n\ntype mockKVStoresEntriesPaginator struct {\n\tnext bool\n\tkeys []string\n\terr  error\n}\n\nfunc (m *mockKVStoresEntriesPaginator) Next() bool {\n\tret := m.next\n\tif m.next {\n\t\tm.next = false // allow one instance of true before stopping\n\t}\n\treturn ret\n}\n\nfunc (m *mockKVStoresEntriesPaginator) Keys() []string {\n\treturn m.keys\n}\n\nfunc (m *mockKVStoresEntriesPaginator) Err() error {\n\treturn m.err\n}\n"
  },
  {
    "path": "pkg/commands/kvstoreentry/list.go",
    "content": "package kvstoreentry\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// ListCommand calls the Fastly API to list the keys for a given kv store.\ntype ListCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tconsistency string\n\tprefix      string\n\tInput       fastly.ListKVStoreKeysInput\n}\n\n// ConsistencyOptions is a list of allowed consistency values.\nvar ConsistencyOptions = []string{\n\t\"eventual\",\n\t\"strong\",\n}\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\n\tc.CmdClause = parent.Command(\"list\", \"List keys\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"store-id\", \"Store ID\").Short('s').Required().StringVar(&c.Input.StoreID)\n\n\t// Optional.\n\tc.CmdClause.Flag(\"consistency\", \"Determines accuracy of results. i.e. 'eventual' uses caching to improve performance\").Default(\"strong\").HintOptions(ConsistencyOptions...).EnumVar(&c.consistency, ConsistencyOptions...)\n\tc.CmdClause.Flag(\"prefix\", \"Restrict results to items whose keys match this prefix\").StringVar(&c.prefix)\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *ListCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tvar (\n\t\tcursor string\n\t\tkeys   []string\n\t\tok     bool\n\t)\n\n\tc.Input.Cursor = cursor\n\n\tspinner, err := text.NewSpinner(out)\n\tif err != nil {\n\t\treturn err\n\t}\n\tmsg := \"Getting data\"\n\n\t// A spinner produces output and is incompatible with JSON expected output.\n\tif !c.JSONOutput.Enabled {\n\t\terr := spinner.Start()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tspinner.Message(msg + \"... (this can take a few minutes depending on the number of entries)\")\n\t}\n\n\tswitch c.consistency {\n\tcase \"eventual\":\n\t\tc.Input.Consistency = fastly.ConsistencyEventual\n\tcase \"strong\":\n\t\tc.Input.Consistency = fastly.ConsistencyStrong\n\t}\n\n\tif c.prefix != \"\" {\n\t\tc.Input.Prefix = c.prefix\n\t}\n\n\tfor {\n\t\to, err := c.Globals.APIClient.ListKVStoreKeys(context.TODO(), &c.Input)\n\t\tif err != nil {\n\t\t\tc.Globals.ErrLog.Add(err)\n\t\t\tif !c.JSONOutput.Enabled {\n\t\t\t\tspinner.StopFailMessage(msg)\n\t\t\t\tspinErr := spinner.StopFail()\n\t\t\t\tif spinErr != nil {\n\t\t\t\t\treturn fmt.Errorf(text.SpinnerErrWrapper, spinErr, err)\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn err\n\t\t}\n\n\t\tkeys = append(keys, o.Data...)\n\n\t\tc.Input.Cursor, ok = o.Meta[\"next_cursor\"]\n\t\tif !ok {\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif !c.JSONOutput.Enabled {\n\t\tspinner.StopMessage(msg)\n\t\terr := spinner.Stop()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tif keys == nil {\n\t\tif ok, err := c.WriteJSON(out, []string{}); ok {\n\t\t\treturn err\n\t\t}\n\t\ttext.Break(out)\n\t\ttext.Output(out, \"no keys\")\n\t\treturn nil\n\t}\n\n\tif ok, err := c.WriteJSON(out, keys); ok {\n\t\treturn err\n\t}\n\n\tif c.Globals.Flags.Verbose {\n\t\ttext.PrintKVStoreKeys(out, \"\", keys)\n\t\treturn nil\n\t}\n\n\tfor _, k := range keys {\n\t\ttext.Output(out, k)\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/kvstoreentry/root.go",
    "content": "package kvstoreentry\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"kv-store-entry\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manipulate Fastly KV Store keys\")\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/kvstoreentry/testdata/data.json",
    "content": "{\n  \"key\": \"file-example-1\",\n  \"value\": \"VkFMVUU=\"\n}\n{\n  \"key\": \"file-example-2\",\n  \"value\": \"VkFMVUU=\"\n}\n"
  },
  {
    "path": "pkg/commands/kvstoreentry/testdata/example/.hiddenfile",
    "content": "This file is hidden and should not be uploaded by default unless --dir-allow-hidden flag is set.\n"
  },
  {
    "path": "pkg/commands/kvstoreentry/testdata/example/foo.txt",
    "content": "FOO\n"
  },
  {
    "path": "pkg/commands/logtail/doc.go",
    "content": "// Package logtail contains commands to inspect and manipulate Fastly streaming\n// log data.\npackage logtail\n"
  },
  {
    "path": "pkg/commands/logtail/root.go",
    "content": "package logtail\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"os\"\n\t\"os/signal\"\n\t\"sort\"\n\t\"strconv\"\n\t\"strings\"\n\t\"syscall\"\n\t\"time\"\n\n\t\"github.com/tomnomnom/linkheader\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/debug\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\n\tInput       fastly.CreateManagedLoggingInput\n\tbatchCh     chan Batch // send batches to output loop\n\tcfg         cfg\n\tdieCh       chan struct{} // channel to end output/printing\n\tdoneCh      chan struct{} // channel to signal we've reached the end of the run\n\thClient     *http.Client  // TODO: this will go away when GET is in go-fastly\n\tserviceName argparser.OptionalServiceNameID\n\ttoken       string // TODO: this will go away when GET is in go-fastly\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"log-tail\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Tail Compute logs\")\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\tc.CmdClause.Flag(\"from\", \"From time, in Unix seconds\").Int64Var(&c.cfg.from)\n\tc.CmdClause.Flag(\"to\", \"To time, in Unix seconds\").Int64Var(&c.cfg.to)\n\tc.CmdClause.Flag(\"sort-buffer\", \"Duration of sort buffer for received logs\").Default(\"1s\").DurationVar(&c.cfg.sortBuffer)\n\tc.CmdClause.Flag(\"search-padding\", \"Time beyond from/to to consider in searches\").Default(\"2s\").DurationVar(&c.cfg.searchPadding)\n\tc.CmdClause.Flag(\"stream\", \"Output: stdout, stderr, both (default)\").StringVar(&c.cfg.stream)\n\tc.CmdClause.Flag(\"timestamps\", \"Print timestamps with logs\").BoolVar(&c.cfg.printTimestamps)\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, source, flag, err := argparser.ServiceID(c.serviceName, *c.Globals.Manifest, c.Globals.APIClient, c.Globals.ErrLog)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif c.Globals.Verbose() {\n\t\targparser.DisplayServiceID(serviceID, flag, source, out)\n\t}\n\n\tc.Input.ServiceID = serviceID\n\n\tc.Input.Kind = fastly.ManagedLoggingInstanceOutput\n\tendpoint, _ := c.Globals.APIEndpoint()\n\tc.cfg.path = fmt.Sprintf(\"%s/service/%s/log_stream/managed/instance_output\", endpoint, c.Input.ServiceID)\n\n\tc.dieCh = make(chan struct{})\n\tc.batchCh = make(chan Batch)\n\tc.doneCh = make(chan struct{})\n\n\tc.hClient = http.DefaultClient\n\tc.token, _ = c.Globals.Token()\n\n\t// Adjust the from/to times if they are\n\t// defined. We adjust the times based on searchPadding.\n\tc.adjustTimes()\n\n\t// Enable managed logging if not already enabled.\n\tif err := c.enableManagedLogging(out); err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tfailure := make(chan error)\n\tsigs := make(chan os.Signal, 2)\n\tsignal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP)\n\n\t// Start the output loop.\n\tgo c.outputLoop(out)\n\n\t// Start tailing the logs.\n\tgo func() {\n\t\tfailure <- c.tail(out)\n\t}()\n\n\tselect {\n\tcase asyncErr := <-failure:\n\t\tclose(c.dieCh)\n\t\treturn asyncErr\n\tcase <-c.doneCh:\n\t\treturn nil\n\tcase <-sigs:\n\t\tclose(c.dieCh)\n\t}\n\n\treturn nil\n}\n\n// Tail starts the virtual tail process. Tail fetches data from the eventbuffer\n// API. It hands off the requested logs to the outputloop for the actual\n// printing.\nfunc (c *RootCommand) tail(out io.Writer) error {\n\t// Start this with --from and --to if set.\n\tcurWindow := c.cfg.from\n\ttoWindow := c.cfg.to\n\n\t// Start the loop with an initial address to query.\n\tpath, err := makeNewPath(c.cfg.path, curWindow, \"\")\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// lastBatchID keeps the last successfully read Batch.ID in case we need\n\t// re-request on failure.\n\tvar lastBatchID string\n\n\tfor {\n\t\t// Check to see if we already passed the \"to\" requirement.\n\t\tif toWindow != 0 && curWindow > toWindow {\n\t\t\ttext.Info(out, \"Reached window: %v which is newer than the requested 'to': %v\", curWindow, toWindow)\n\t\t\t// We are done, but we still want printing to finish.\n\t\t\tclose(c.doneCh)\n\t\t\tbreak\n\t\t}\n\n\t\treq, err := http.NewRequest(http.MethodGet, path, nil)\n\t\tif err != nil {\n\t\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\thttp.MethodGet: path,\n\t\t\t})\n\t\t\treturn fmt.Errorf(\"unable to create new request: %w\", err)\n\t\t}\n\t\treq.Header.Add(\"Fastly-Key\", c.token)\n\n\t\tresp, err := c.doReq(req)\n\t\tif err != nil {\n\t\t\tc.Globals.ErrLog.Add(err)\n\t\t\treturn fmt.Errorf(\"unable to execute request: %w\", err)\n\t\t}\n\n\t\t// Check that our request was successful. If the server is\n\t\t// having trouble, retry after waiting for some time.\n\t\tif resp.StatusCode != http.StatusOK {\n\t\t\t// If the response was a 404, the from time was\n\t\t\t// not valid, give them an error stating this and exit.\n\t\t\tif resp.StatusCode == http.StatusNotFound &&\n\t\t\t\tc.cfg.from != 0 {\n\t\t\t\treturn fmt.Errorf(\"specified 'from' time %d not found, either too far in the past or future\", c.cfg.from)\n\t\t\t}\n\n\t\t\t// In an effort to clean up the output, do not print on\n\t\t\t// 503's.\n\t\t\tif resp.StatusCode != http.StatusServiceUnavailable {\n\t\t\t\ttext.Warning(out, \"non-200 resp %d\", resp.StatusCode)\n\t\t\t}\n\n\t\t\t// Reuse the connection for the retry, or cleanup in the\n\t\t\t// case of Exit.\n\t\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t\terr := resp.Body.Close()\n\t\t\tif err != nil {\n\t\t\t\tc.Globals.ErrLog.Add(err)\n\t\t\t}\n\n\t\t\t// Try the response again after a 1 second wait.\n\t\t\tif resp.StatusCode/100 == 5 && resp.StatusCode != 501 ||\n\t\t\t\tresp.StatusCode == http.StatusTooManyRequests {\n\t\t\t\ttime.Sleep(1 * time.Second)\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\t// Failing at this point is unrecoverable.\n\t\t\treturn fmt.Errorf(\"unrecoverable error, response code: %d\", resp.StatusCode)\n\t\t}\n\n\t\t// Read and parse response, send batches to the output loop.\n\t\tscanner := bufio.NewScanner(resp.Body)\n\n\t\t// Use a 10MB buffer for the bufio scanner, as we don't know\n\t\t// how big some of the responses will be.\n\t\tconst tmb = 10 << 20\n\t\tbuf := make([]byte, tmb)\n\t\tscanner.Buffer(buf, tmb)\n\n\t\tfor scanner.Scan() {\n\t\t\t// Scan one line at a time, and get only one batch\n\t\t\t// at a time.\n\t\t\tb := scanner.Bytes()\n\t\t\tbatch, err := parseResponseData(b)\n\t\t\tif err != nil {\n\t\t\t\tc.Globals.ErrLog.Add(err)\n\t\t\t\t// We can't parse the response, attempt to\n\t\t\t\t// re-request from the last window & batch.\n\t\t\t\ttext.Warning(out, \"unable to parse response body: %v\", err)\n\t\t\t\tpath, err = makeNewPath(path, curWindow, lastBatchID)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\t// If we got a batch back, there will be an ID.\n\t\t\tif batch.ID != \"\" {\n\t\t\t\t// Record last batchID in case\n\t\t\t\t// anything fails along the way, we\n\t\t\t\t// can re-request.\n\t\t\t\tlastBatchID = batch.ID\n\t\t\t\t// Send batch down batchCh to the output loop.\n\t\t\t\tc.batchCh <- batch\n\t\t\t}\n\t\t}\n\t\terr = resp.Body.Close()\n\t\tif err != nil {\n\t\t\tc.Globals.ErrLog.Add(err)\n\t\t}\n\n\t\tif err := scanner.Err(); err != nil {\n\t\t\tc.Globals.ErrLog.Add(err)\n\t\t\t// ErrUnexpectedEOFs need to be retried, but they\n\t\t\t// produce a lot of noise for the user, so don't log.\n\t\t\tif err != io.ErrUnexpectedEOF {\n\t\t\t\ttext.Warning(out, \"error scanning response body: %v\", err)\n\t\t\t}\n\n\t\t\t// Something happened in the scanner, re-request the\n\t\t\t// current batchID.\n\t\t\tpath, err = makeNewPath(path, curWindow, lastBatchID)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\n\t\t// Get our next time window to request.\n\t\t_, next := getLinks(resp.Header)\n\t\tcurWindow, err = getTimeFromLink(next)\n\t\tif err != nil {\n\t\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\t\"Next link\": next,\n\t\t\t})\n\t\t\ttext.Error(out, \"error generating window from next link\")\n\t\t}\n\n\t\t// We do NOT want to specify a batchID, as this\n\t\t// request was successful.\n\t\tlastBatchID = \"\"\n\t\tpath, err = makeNewPath(path, curWindow, lastBatchID)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\n// adjustTimes adjusts the passed in from and to flags based on the\n// specified padding.\nfunc (c *RootCommand) adjustTimes() {\n\tif c.cfg.from != 0 {\n\t\t// Adjust from based on search padding, we want to\n\t\t// look back further.\n\t\tc.cfg.from -= int64(c.cfg.searchPadding.Seconds())\n\t}\n\n\tif c.cfg.to != 0 {\n\t\t// Adjust to based on search padding, we want look forward more.\n\t\tc.cfg.to += int64(c.cfg.searchPadding.Seconds())\n\t}\n}\n\n// enableManagedLogging enables managed logging in our API.\nfunc (c *RootCommand) enableManagedLogging(out io.Writer) error {\n\t_, err := c.Globals.APIClient.CreateManagedLogging(context.TODO(), &c.Input)\n\tif err != nil && err != fastly.ErrManagedLoggingEnabled {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\ttext.Info(out, \"Managed logging enabled on service %s\\n\\n\", c.Input.ServiceID)\n\treturn nil\n}\n\n// outputLoop processes the logs out of band from the request/response loop.\nfunc (c *RootCommand) outputLoop(out io.Writer) {\n\ttype (\n\t\tbufferedLog struct {\n\t\t\treqID string\n\t\t\tseq   int\n\t\t}\n\n\t\treceive struct {\n\t\t\twhen    time.Time\n\t\t\thighSeq int\n\t\t}\n\n\t\tlogrecv struct {\n\t\t\tlogs     []Log\n\t\t\treceives []receive\n\t\t}\n\t)\n\n\t// Channel for timers to notify they are done buffering.\n\ttdCh := make(chan bufferedLog)\n\n\t// Single map to keep all buffered logs by RequestID as\n\t// well recording when logs were received.\n\tlogmap := make(map[string]logrecv)\n\n\tfor {\n\t\tselect {\n\t\tcase <-c.dieCh:\n\t\t\treturn\n\t\tcase batch := <-c.batchCh: // Got new batch.\n\t\t\t// Range through batch logs, for each\n\t\t\t// RequestID we create a timer based on the\n\t\t\t// highest SequenceNum we got in this batch\n\t\t\t// for that RequestID.  If a timer already\n\t\t\t// exists for the RequestID, we append the new\n\t\t\t// time.Now() and high SequenceNum.  At most\n\t\t\t// there should be one timer per RequestID.\n\t\t\tfor reqid, logs := range splitByReqID(batch.Logs) {\n\t\t\t\t// Required for use in AfterFunc below.\n\t\t\t\treq := reqid\n\n\t\t\t\t// Record highest SequenceNum in this new batch\n\t\t\t\t// for this RequestID\n\t\t\t\thighSeq := highSequence(logs)\n\n\t\t\t\t// Whether we have the RequestID or not, we\n\t\t\t\t// append and sort the logs slice.\n\t\t\t\treqLogs := logmap[req]\n\t\t\t\treqLogs.logs = append(reqLogs.logs, logs...)\n\t\t\t\t// Sort the current batch of logs by their sequence number.\n\t\t\t\tsort.Slice(reqLogs.logs,\n\t\t\t\t\tfunc(i, j int) bool {\n\t\t\t\t\t\treturn reqLogs.logs[i].SequenceNum < reqLogs.logs[j].SequenceNum\n\t\t\t\t\t})\n\n\t\t\t\t// Check to see if we already have a timer running or if the current\n\t\t\t\t// high sequence is higher than the one with the timer.\n\t\t\t\t// The timer will always be running on the head of the slice.\n\t\t\t\t// In either case append to the receives slice.\n\t\t\t\trecv := reqLogs.receives\n\t\t\t\tif len(recv) == 0 || recv[0].highSeq < highSeq {\n\t\t\t\t\t// NOTE: gocritic will warn about appendAssign but we ignore it.\n\t\t\t\t\t// Because if we try to address it the code fails to work at runtime.\n\t\t\t\t\t//nolint:gocritic\n\t\t\t\t\treqLogs.receives = append(recv, receive{\n\t\t\t\t\t\twhen:    time.Now(),\n\t\t\t\t\t\thighSeq: highSeq,\n\t\t\t\t\t})\n\t\t\t\t}\n\n\t\t\t\t// In only the empty case, start a new timer\n\t\t\t\t// since this is the head of the slice.\n\t\t\t\tif len(recv) == 0 {\n\t\t\t\t\ttime.AfterFunc(c.cfg.sortBuffer, func() {\n\t\t\t\t\t\ttdCh <- bufferedLog{\n\t\t\t\t\t\t\treqID: req,\n\t\t\t\t\t\t\tseq:   highSeq,\n\t\t\t\t\t\t}\n\t\t\t\t\t})\n\t\t\t\t}\n\n\t\t\t\t// Set the new log and receive info back to the\n\t\t\t\t// logmap for this RequestID.\n\t\t\t\tlogmap[req] = reqLogs\n\t\t\t}\n\n\t\tcase bufdLogs := <-tdCh: // A timer expired for a particular request.\n\t\t\treqID, seq := bufdLogs.reqID, bufdLogs.seq\n\n\t\t\t// Get the logs for this RequestID and\n\t\t\t// find the index of the sequence in our current logs.\n\t\t\treqLogs := logmap[reqID]\n\t\t\tidx := findIdxBySeq(reqLogs.logs, seq)\n\n\t\t\t// Split off the source of this timer, leave\n\t\t\t// remaining logs to be printed later.\n\t\t\ttoPrint, remainingLogs := reqLogs.logs[:idx], reqLogs.logs[idx:]\n\t\t\treqLogs.logs = remainingLogs\n\t\t\tc.printLogs(out, toPrint)\n\n\t\t\t// Special case if we just printed the entire set of\n\t\t\t// logs, we remove the keys from the maps and finish.\n\t\t\tif len(remainingLogs) == 0 {\n\t\t\t\tdelete(logmap, reqID)\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\t// Drop the front of the batchReqReceives map and start\n\t\t\t// another timer for any remaining recorded sequences.\n\t\t\trecv := reqLogs.receives[1:]\n\t\t\treqLogs.receives = recv\n\n\t\t\t// If anything is left...\n\t\t\tif len(recv) > 0 {\n\t\t\t\t// We create a new timer, we subtract\n\t\t\t\t// off time already served from the\n\t\t\t\t// user defined sortBuffer.\n\t\t\t\ttime.AfterFunc(c.cfg.sortBuffer-time.Since(recv[0].when), func() {\n\t\t\t\t\ttdCh <- bufferedLog{\n\t\t\t\t\t\treqID: reqID,\n\t\t\t\t\t\tseq:   recv[0].highSeq,\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t}\n\n\t\t\t// Set the new log and receive info back to the\n\t\t\t// logmap for this RequestID.\n\t\t\tlogmap[reqID] = reqLogs\n\t\t}\n\t}\n}\n\n// printLogs is a simple printer for Log slices, only printing requested\n// streams.\nfunc (c *RootCommand) printLogs(out io.Writer, logs []Log) {\n\tif len(logs) > 0 {\n\t\tfiltered := filterStream(c.cfg.stream, logs)\n\n\t\tfor _, l := range filtered {\n\t\t\tif c.cfg.printTimestamps {\n\t\t\t\tfmt.Fprint(out, l.RequestStartFromRaw().UTC().Format(time.RFC3339))\n\t\t\t\tfmt.Fprint(out, \" | \")\n\t\t\t}\n\t\t\tfmt.Fprintln(out, l.String())\n\t\t}\n\t}\n}\n\n// doReq runs the http.Request, returning a http.Response or error.\nfunc (c *RootCommand) doReq(req *http.Request) (*http.Response, error) {\n\tctx, cancel := context.WithCancel(context.Background())\n\treq = req.WithContext(ctx)\n\tgo func() {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\tcase <-c.dieCh:\n\t\t\tcancel()\n\t\t}\n\t}()\n\n\tif c.Globals.Flags.Debug {\n\t\tdebug.DumpHTTPRequest(req)\n\t}\n\tresp, err := c.hClient.Do(req)\n\tif c.Globals.Flags.Debug {\n\t\tdebug.DumpHTTPResponse(resp)\n\t}\n\treturn resp, err\n}\n\ntype (\n\t// cfg holds the configuration parameters passed in through\n\t// command line arguments.\n\tcfg struct {\n\t\t// path is the full path to fetch\n\t\tpath string\n\n\t\t// from is how far in the past to start showing logs.\n\t\tfrom int64\n\n\t\t// to is when to get logs until.\n\t\tto int64\n\n\t\t// printTimestamps is whether to print timestamps with logs.\n\t\tprintTimestamps bool\n\n\t\t// sortBuffer is how long to buffer logs from when the cli\n\t\t// receives them to when the cli prints them. It will sort\n\t\t// by RequestID for that buffer period.\n\t\tsortBuffer time.Duration\n\t\t// searchPadding is how much of a window on either side of\n\t\t// from and to to use for searching for the beginning or\n\t\t// through the end timestamps.\n\t\tsearchPadding time.Duration\n\t\t// stream specifies which of stdout or stderr or both the\n\t\t// customer wants to consume.\n\t\t// Undefined == both stderr and stdout.\n\t\tstream string\n\t}\n\n\t// Log defines the message envelope that the Compute platform wraps the\n\t// user messages in.\n\tLog struct {\n\t\t// SequenceNum is the message sequence number used to reorder\n\t\t// messages.\n\t\tSequenceNum int `json:\"sequence_number\"`\n\t\t// RequestTime is the time in microseconds when the request\n\t\t// was received.\n\t\tRequestStart int64 `json:\"request_start_us\"`\n\t\t// Stream is the Compute stream, either stdout or stderr.\n\t\tStream string `json:\"stream\"`\n\t\t// RequestID is a UUID representing individual requests to the\n\t\t// particular Wasm service.\n\t\tRequestID string `json:\"id\"`\n\t\t// Message is the actual message body the user wants printed.\n\t\tMessage string `json:\"message\"`\n\t}\n\n\t// Batch encompasses a batch ID and the logs for this batch.\n\tBatch struct {\n\t\tID   string `json:\"batch_id\"`\n\t\tLogs []Log  `json:\"logs\"`\n\t}\n)\n\n// RequestStartFromRaw return a time.Time object representing the\n// RequestStart data.\nfunc (l *Log) RequestStartFromRaw() time.Time {\n\t// RequestTime comes as unix time in microseconds. Convert to\n\t// nanoseconds, then parse with stdlib.\n\tnano := l.RequestStart * 1000\n\treturn time.Unix(0, nano)\n}\n\n// String is used to print a log for the tail output.\nfunc (l *Log) String() string {\n\t// Trim the RequestID for nicer output, it might be a long UUID.\n\treturn fmt.Sprintf(\"%6s | %8.8s | %s\",\n\t\tl.Stream,\n\t\tl.RequestID,\n\t\tl.Message)\n}\n\n// makeNewPath generates a new request path based on current\n// path, window, and batchID.\nfunc makeNewPath(path string, window int64, batchID string) (string, error) {\n\tbasePath, err := url.Parse(path)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"error generating request URL: %w\", err)\n\t}\n\n\t// Unset anything in the query parameters that might already exist.\n\tbasePath.RawQuery = \"\"\n\n\tq := basePath.Query()\n\tif window != 0 {\n\t\tq.Set(\"from\", strconv.FormatInt(window, 10))\n\t}\n\n\tif batchID != \"\" {\n\t\tq.Set(\"batch_id\", batchID)\n\t}\n\n\tbasePath.RawQuery = q.Encode()\n\treturn basePath.String(), nil\n}\n\n// splitByReqID splits slices of logs based on RequestID.\nfunc splitByReqID(in []Log) map[string][]Log {\n\tout := make(map[string][]Log)\n\tfor _, l := range in {\n\t\tout[l.RequestID] = append(out[l.RequestID], l)\n\t}\n\treturn out\n}\n\n// parseResponseData returns the batch from a response.\nfunc parseResponseData(data []byte) (Batch, error) {\n\tvar batch Batch\n\treader := bytes.NewReader(data)\n\td := json.NewDecoder(reader)\n\n\tif err := d.Decode(&batch); err != nil && err != io.EOF {\n\t\treturn batch, err\n\t}\n\n\treturn batch, nil\n}\n\n// filterStream returns only logs that are requested by the stream flag.\nfunc filterStream(stream string, logs []Log) []Log {\n\t// If unset, do not filter out any logs.\n\tif stream == \"\" {\n\t\treturn logs\n\t}\n\n\tvar out []Log\n\tfor _, l := range logs {\n\t\t// If the stream matches what they wanted, keep it.\n\t\tif stream == l.Stream {\n\t\t\tout = append(out, l)\n\t\t}\n\t}\n\treturn out\n}\n\n// getTimeFromLink splits a link header format, returning\n// the time.\nfunc getTimeFromLink(link string) (int64, error) {\n\ts := strings.SplitN(link, \"=\", 2)[1]\n\treturn strconv.ParseInt(s, 10, 64)\n}\n\n// getLinks returns the prev and next links from a header.\nfunc getLinks(head http.Header) (prev, next string) {\n\tlinks := linkheader.ParseMultiple(head[\"Link\"])\n\tfor _, link := range links {\n\t\tswitch link.Rel {\n\t\tcase \"prev\":\n\t\t\tprev = link.URL\n\t\tcase \"next\":\n\t\t\tnext = link.URL\n\t\t}\n\t}\n\treturn prev, next\n}\n\n// findIdxBySeq returns the slice index after the\n// SequenceNum we are searching for.\nfunc findIdxBySeq(logs []Log, seq int) int {\n\tfor i, v := range logs {\n\t\tif v.SequenceNum > seq {\n\t\t\treturn i\n\t\t}\n\t}\n\treturn len(logs)\n}\n\n// highSequence returns the highest SequenceNum\n// in a slice of logs.\nfunc highSequence(logs []Log) int {\n\tvar maximum int\n\tfor _, l := range logs {\n\t\tif l.SequenceNum > maximum {\n\t\t\tmaximum = l.SequenceNum\n\t\t}\n\t}\n\treturn maximum\n}\n"
  },
  {
    "path": "pkg/commands/logtail/tail_test.go",
    "content": "package logtail\n\nimport (\n\t\"net/http\"\n\t\"os\"\n\t\"reflect\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n)\n\nconst responseFile = \"testdata/response.json\"\n\n// TestAdjustTimes tests that the from and to times are adjusted accordingly\n// based on the searchPadding.\nfunc TestAdjustTimes(t *testing.T) {\n\tdur, _ := time.ParseDuration(\"10s\")\n\tfor i, test := range []struct {\n\t\tin  cfg\n\t\texp cfg\n\t}{\n\t\t{\n\t\t\tin:  cfg{from: 1601480668, to: 1601480768, searchPadding: dur},\n\t\t\texp: cfg{from: 1601480658, to: 1601480778, searchPadding: dur},\n\t\t},\n\t\t{\n\t\t\tin:  cfg{from: 1601480668, to: 1601480768},\n\t\t\texp: cfg{from: 1601480668, to: 1601480768},\n\t\t},\n\t\t{\n\t\t\tin:  cfg{searchPadding: dur},\n\t\t\texp: cfg{searchPadding: dur},\n\t\t},\n\t} {\n\t\tc := RootCommand{cfg: test.in}\n\t\tc.adjustTimes()\n\t\tif equal := reflect.DeepEqual(test.exp, c.cfg); !equal {\n\t\t\tt.Errorf(\"#%d: adjustTimes mismatch: got: %#+v  want: %#+v\", i, c.cfg, test.exp)\n\t\t}\n\t}\n}\n\n// TestSplitByReqID tests that logs are properly grouped and sorted\n// by their RequestID and SequenceNum.\nfunc TestSplitByReqID(t *testing.T) {\n\tfull := []Log{\n\t\t{SequenceNum: 1, RequestID: \"41f82900\"},\n\t\t{SequenceNum: 2, RequestID: \"41f82900\"},\n\t\t{SequenceNum: 3, RequestID: \"2bef4613\"},\n\t\t{SequenceNum: 4, RequestID: \"2bef4613\"},\n\t\t{SequenceNum: 5, RequestID: \"41f82900\"},\n\t\t{SequenceNum: 6, RequestID: \"41f82900\"},\n\t\t{SequenceNum: 6, RequestID: \"2bef4613\"},\n\t\t{SequenceNum: 1, RequestID: \"2bef4613\"},\n\t\t{SequenceNum: 5, RequestID: \"2bef4613\"},\n\t\t{SequenceNum: 2, RequestID: \"2bef4613\"},\n\t\t{SequenceNum: 3, RequestID: \"41f82900\"},\n\t\t{SequenceNum: 4, RequestID: \"41f82900\"},\n\t}\n\n\texpfull := map[string][]Log{\n\t\t\"41f82900\": {\n\t\t\t{SequenceNum: 1, RequestID: \"41f82900\"},\n\t\t\t{SequenceNum: 2, RequestID: \"41f82900\"},\n\t\t\t{SequenceNum: 5, RequestID: \"41f82900\"},\n\t\t\t{SequenceNum: 6, RequestID: \"41f82900\"},\n\t\t\t{SequenceNum: 3, RequestID: \"41f82900\"},\n\t\t\t{SequenceNum: 4, RequestID: \"41f82900\"},\n\t\t},\n\t\t\"2bef4613\": {\n\t\t\t{SequenceNum: 3, RequestID: \"2bef4613\"},\n\t\t\t{SequenceNum: 4, RequestID: \"2bef4613\"},\n\t\t\t{SequenceNum: 6, RequestID: \"2bef4613\"},\n\t\t\t{SequenceNum: 1, RequestID: \"2bef4613\"},\n\t\t\t{SequenceNum: 5, RequestID: \"2bef4613\"},\n\t\t\t{SequenceNum: 2, RequestID: \"2bef4613\"},\n\t\t},\n\t}\n\n\tsingle := []Log{\n\t\t{SequenceNum: 1, RequestID: \"41f82900\"},\n\t}\n\n\texpsingle := map[string][]Log{\n\t\t\"41f82900\": {{SequenceNum: 1, RequestID: \"41f82900\"}},\n\t}\n\n\tfor i, test := range []struct {\n\t\tin   []Log\n\t\twant map[string][]Log\n\t}{\n\t\t{in: full, want: expfull},\n\t\t{in: single, want: expsingle},\n\t\t{in: []Log{}, want: make(map[string][]Log)},\n\t} {\n\t\tgot := splitByReqID(test.in)\n\t\tif diff := cmp.Diff(test.want, got); diff != \"\" {\n\t\t\tt.Errorf(\"#%d: splitByReqID mismatch (-want +got):\\n%s\", i, diff)\n\t\t}\n\t}\n}\n\n// TestParseResponseData validates we're correctly decoding a batch JSON log\n// response into a logs.Batch type.\nfunc TestParseResponseData(t *testing.T) {\n\tdata, err := os.ReadFile(responseFile)\n\tif err != nil {\n\t\tt.Fatalf(\"cannot read from file: %v\", err)\n\t}\n\n\tgot, err := parseResponseData(data)\n\tif err != nil {\n\t\tt.Fatalf(\"error parsing response data: %v\", err)\n\t}\n\n\twant := Batch{\n\t\tID: \"MC0x\",\n\t\tLogs: []Log{\n\t\t\t{SequenceNum: 1, RequestStart: 1601645172164667, Stream: \"stdout\", RequestID: \"44a1eedd-5831-49fe-b094-7435908ba1fb\", Message: \"1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1\"},\n\t\t\t{SequenceNum: 2, RequestStart: 1601645172164667, Stream: \"stdout\", RequestID: \"44a1eedd-5831-49fe-b094-7435908ba1fb\", Message: \"2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2\"},\n\t\t\t{SequenceNum: 3, RequestStart: 1601645172164667, Stream: \"stdout\", RequestID: \"44a1eedd-5831-49fe-b094-7435908ba1fb\", Message: \"3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3\"},\n\t\t\t{SequenceNum: 4, RequestStart: 1601645172164667, Stream: \"stdout\", RequestID: \"44a1eedd-5831-49fe-b094-7435908ba1fb\", Message: \"4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4\"},\n\t\t\t{SequenceNum: 5, RequestStart: 1601645172164667, Stream: \"stderr\", RequestID: \"44a1eedd-5831-49fe-b094-7435908ba1fb\", Message: \"5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5\"},\n\t\t\t{SequenceNum: 6, RequestStart: 1601645172164667, Stream: \"stderr\", RequestID: \"44a1eedd-5831-49fe-b094-7435908ba1fb\", Message: \"6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6\"},\n\t\t\t{SequenceNum: 7, RequestStart: 1601645172164667, Stream: \"stdout\", RequestID: \"44a1eedd-5831-49fe-b094-7435908ba1fb\", Message: \"7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7\"},\n\t\t\t{SequenceNum: 8, RequestStart: 1601645172164667, Stream: \"stdout\", RequestID: \"44a1eedd-5831-49fe-b094-7435908ba1fb\", Message: \"8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8\"},\n\t\t\t{SequenceNum: 9, RequestStart: 1601645172164667, Stream: \"stderr\", RequestID: \"44a1eedd-5831-49fe-b094-7435908ba1fb\", Message: \"9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9\"},\n\t\t\t{SequenceNum: 10, RequestStart: 1601645172164667, Stream: \"stdout\", RequestID: \"44a1eedd-5831-49fe-b094-7435908ba1fb\", Message: \"10 10 10 10 10 10 10 10 10 10 10 10 10 10\"},\n\t\t},\n\t}\n\n\tif diff := cmp.Diff(want, got); diff != \"\" {\n\t\tt.Errorf(\"parseResponseData mismatch (-want +got):\\n%s\", diff)\n\t}\n}\n\n// TestFilterStream tests that a passed in stream will filter out\n// unwanted output.\nfunc TestFilterStream(t *testing.T) {\n\tfor i, test := range []struct {\n\t\tstream string\n\t\tlogs   []Log\n\t\texplen int\n\t}{\n\t\t{\n\t\t\tstream: \"stdout\",\n\t\t\tlogs: []Log{\n\t\t\t\t{Stream: \"stdout\"},\n\t\t\t\t{Stream: \"stdout\"},\n\t\t\t\t{Stream: \"stderr\"},\n\t\t\t\t{Stream: \"stdout\"},\n\t\t\t\t{Stream: \"stdout\"},\n\t\t\t\t{Stream: \"stderr\"},\n\t\t\t\t{Stream: \"stdout\"},\n\t\t\t\t{Stream: \"stderr\"},\n\t\t\t},\n\t\t\texplen: 5,\n\t\t},\n\t\t{\n\t\t\tstream: \"stderr\",\n\t\t\tlogs: []Log{\n\t\t\t\t{Stream: \"stdout\"},\n\t\t\t\t{Stream: \"stdout\"},\n\t\t\t\t{Stream: \"stderr\"},\n\t\t\t\t{Stream: \"stdout\"},\n\t\t\t\t{Stream: \"stdout\"},\n\t\t\t\t{Stream: \"stderr\"},\n\t\t\t\t{Stream: \"stdout\"},\n\t\t\t\t{Stream: \"stderr\"},\n\t\t\t},\n\t\t\texplen: 3,\n\t\t},\n\t\t{\n\t\t\tlogs: []Log{\n\t\t\t\t{Stream: \"stdout\"},\n\t\t\t\t{Stream: \"stdout\"},\n\t\t\t\t{Stream: \"stderr\"},\n\t\t\t\t{Stream: \"stderr\"},\n\t\t\t\t{Stream: \"stdout\"},\n\t\t\t\t{Stream: \"stderr\"},\n\t\t\t\t{Stream: \"stdout\"},\n\t\t\t\t{Stream: \"stderr\"},\n\t\t\t},\n\t\t\texplen: 8,\n\t\t},\n\t} {\n\t\tout := filterStream(test.stream, test.logs)\n\t\tif len(out) != test.explen {\n\t\t\tt.Errorf(\"#%d: exp: %d != got: %d\", i, test.explen, len(out))\n\t\t}\n\t}\n}\n\n// TestGetLinks tests that we can parse next and prev links from a Link HTTP\n// header.\nfunc TestGetLinks(t *testing.T) {\n\trawNexPrev := `</service/sid/log_stream/managed/instance_output%3Ffrom=1601412640>; rel=\"next\", </service/sid/log_stream/managed/instance_output%3Ffrom=1601412620>; rel=\"prev\"`\n\thead := make(http.Header)\n\thead.Set(\"Link\", rawNexPrev)\n\n\tprev, next := getLinks(head)\n\tprevexp := \"/service/sid/log_stream/managed/instance_output%3Ffrom=1601412620\"\n\tif prev != prevexp {\n\t\tt.Errorf(\"prev header exp: %s != got: %s\", prevexp, prev)\n\t}\n\n\tnextexp := \"/service/sid/log_stream/managed/instance_output%3Ffrom=1601412640\"\n\tif next != nextexp {\n\t\tt.Errorf(\"next header exp: %s != got: %s\", nextexp, next)\n\t}\n\n\tpTime, err := getTimeFromLink(prev)\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error parsing prev link: %s\", err)\n\t}\n\texp := int64(1601412620)\n\tif pTime != exp {\n\t\tt.Errorf(\"prev time exp: %v != got: %v\", exp, pTime)\n\t}\n\n\tnTime, err := getTimeFromLink(next)\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error parsing next link: %s\", err)\n\t}\n\texp = int64(1601412640)\n\tif nTime != exp {\n\t\tt.Errorf(\"next time exp: %v != got: %v\", exp, nTime)\n\t}\n}\n\n// TestSplitOnIdx tests both findIdxBySeq() and the split functionality\n// that is used when a timer expires in the outputLoop() function. Indexes\n// and splitting (especially at slice boundaries) are particularly error prone.\nfunc TestSplitOnIdx(t *testing.T) {\n\tfor i, test := range []struct {\n\t\tseq      int\n\t\tlogs     []Log\n\t\texpleft  int\n\t\texpright int\n\t}{\n\t\t{\n\t\t\tseq: 4,\n\t\t\tlogs: []Log{\n\t\t\t\t{SequenceNum: 0},\n\t\t\t\t{SequenceNum: 1},\n\t\t\t\t{SequenceNum: 2},\n\t\t\t\t{SequenceNum: 3},\n\t\t\t\t{SequenceNum: 4},\n\t\t\t\t{SequenceNum: 5},\n\t\t\t\t{SequenceNum: 6},\n\t\t\t\t{SequenceNum: 7},\n\t\t\t},\n\t\t\texpleft:  5,\n\t\t\texpright: 3,\n\t\t},\n\t\t{\n\t\t\tseq: 1,\n\t\t\tlogs: []Log{\n\t\t\t\t{SequenceNum: 0},\n\t\t\t\t{SequenceNum: 1},\n\t\t\t\t{SequenceNum: 2},\n\t\t\t\t{SequenceNum: 3},\n\t\t\t},\n\t\t\texpleft:  2,\n\t\t\texpright: 2,\n\t\t},\n\t\t{\n\t\t\tseq: 4,\n\t\t\tlogs: []Log{\n\t\t\t\t{SequenceNum: 1},\n\t\t\t\t{SequenceNum: 2},\n\t\t\t\t{SequenceNum: 3},\n\t\t\t\t{SequenceNum: 4},\n\t\t\t},\n\t\t\texpleft:  4,\n\t\t\texpright: 0,\n\t\t},\n\t\t{\n\t\t\tseq: 6,\n\t\t\tlogs: []Log{\n\t\t\t\t{SequenceNum: 1},\n\t\t\t\t{SequenceNum: 3},\n\t\t\t\t{SequenceNum: 5},\n\t\t\t},\n\t\t\texpleft:  3,\n\t\t\texpright: 0,\n\t\t},\n\t} {\n\t\tidx := findIdxBySeq(test.logs, test.seq)\n\t\tleft, right := test.logs[:idx], test.logs[idx:]\n\t\tif len(left) != test.expleft {\n\t\t\tt.Errorf(\"#%d: exp: %d != got: %d\", i, test.expleft, len(left))\n\t\t}\n\n\t\tif len(right) != test.expright {\n\t\t\tt.Errorf(\"#%d: exp: %d != got: %d\", i, test.expright, len(right))\n\t\t}\n\t}\n}\n\n// TestHighSequence tests that we correctly get the highest\n// SequenceNum in a slice of logs.\nfunc TestHighSequence(t *testing.T) {\n\tfor i, test := range []struct {\n\t\tlogs []Log\n\t\texp  int\n\t}{\n\t\t{\n\t\t\tlogs: []Log{\n\t\t\t\t{SequenceNum: 0},\n\t\t\t\t{SequenceNum: 1},\n\t\t\t\t{SequenceNum: 2},\n\t\t\t},\n\t\t\texp: 2,\n\t\t},\n\t\t{\n\t\t\tlogs: []Log{\n\t\t\t\t{SequenceNum: 2},\n\t\t\t\t{SequenceNum: 1},\n\t\t\t\t{SequenceNum: 0},\n\t\t\t},\n\t\t\texp: 2,\n\t\t},\n\t\t{\n\t\t\tlogs: []Log{\n\t\t\t\t{SequenceNum: 1},\n\t\t\t\t{SequenceNum: 1},\n\t\t\t},\n\t\t\texp: 1,\n\t\t},\n\t\t{\n\t\t\tlogs: []Log{},\n\t\t\texp:  0,\n\t\t},\n\t} {\n\t\tif got := highSequence(test.logs); got != test.exp {\n\t\t\tt.Errorf(\"#%d: exp: %d != got: %d\", i, test.exp, got)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "pkg/commands/logtail/testdata/response.json",
    "content": "{\n  \"batch_id\": \"MC0x\",\n  \"logs\": [\n    {\n      \"sequence_number\": 1,\n      \"request_start_us\": 1601645172164667,\n      \"stream\": \"stdout\",\n      \"id\": \"44a1eedd-5831-49fe-b094-7435908ba1fb\",\n      \"message\": \"1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1\"\n    },\n    {\n      \"sequence_number\": 2,\n      \"request_start_us\": 1601645172164667,\n      \"stream\": \"stdout\",\n      \"id\": \"44a1eedd-5831-49fe-b094-7435908ba1fb\",\n      \"message\": \"2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2\"\n    },\n    {\n      \"sequence_number\": 3,\n      \"request_start_us\": 1601645172164667,\n      \"stream\": \"stdout\",\n      \"id\": \"44a1eedd-5831-49fe-b094-7435908ba1fb\",\n      \"message\": \"3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3\"\n    },\n    {\n      \"sequence_number\": 4,\n      \"request_start_us\": 1601645172164667,\n      \"stream\": \"stdout\",\n      \"id\": \"44a1eedd-5831-49fe-b094-7435908ba1fb\",\n      \"message\": \"4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4\"\n    },\n    {\n      \"sequence_number\": 5,\n      \"request_start_us\": 1601645172164667,\n      \"stream\": \"stderr\",\n      \"id\": \"44a1eedd-5831-49fe-b094-7435908ba1fb\",\n      \"message\": \"5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5\"\n    },\n    {\n      \"sequence_number\": 6,\n      \"request_start_us\": 1601645172164667,\n      \"stream\": \"stderr\",\n      \"id\": \"44a1eedd-5831-49fe-b094-7435908ba1fb\",\n      \"message\": \"6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6\"\n    },\n    {\n      \"sequence_number\": 7,\n      \"request_start_us\": 1601645172164667,\n      \"stream\": \"stdout\",\n      \"id\": \"44a1eedd-5831-49fe-b094-7435908ba1fb\",\n      \"message\": \"7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7\"\n    },\n    {\n      \"sequence_number\": 8,\n      \"request_start_us\": 1601645172164667,\n      \"stream\": \"stdout\",\n      \"id\": \"44a1eedd-5831-49fe-b094-7435908ba1fb\",\n      \"message\": \"8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8\"\n    },\n    {\n      \"sequence_number\": 9,\n      \"request_start_us\": 1601645172164667,\n      \"stream\": \"stderr\",\n      \"id\": \"44a1eedd-5831-49fe-b094-7435908ba1fb\",\n      \"message\": \"9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9\"\n    },\n    {\n      \"sequence_number\": 10,\n      \"request_start_us\": 1601645172164667,\n      \"stream\": \"stdout\",\n      \"id\": \"44a1eedd-5831-49fe-b094-7435908ba1fb\",\n      \"message\": \"10 10 10 10 10 10 10 10 10 10 10 10 10 10\"\n    }\n  ]\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/countrylist/countrylist_test.go",
    "content": "package countrylist_test\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\t\"testing\"\n\n\troot \"github.com/fastly/cli/pkg/commands/ngwaf\"\n\tsub \"github.com/fastly/cli/pkg/commands/ngwaf/countrylist\"\n\tfstfmt \"github.com/fastly/cli/pkg/fmt\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/lists\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/scope\"\n)\n\nconst (\n\tlistID          = \"someListID\"\n\tlistDescription = \"NGWAFCLIList\"\n\tlistEntries     = \"us\"\n\tlistType        = \"country\"\n\tlistName        = \"listName\"\n)\n\nvar stringlist = lists.List{\n\tListID:      listID,\n\tDescription: listDescription,\n\tEntries:     []string{listEntries},\n\tName:        listName,\n\tType:        listType,\n\tCreatedAt:   testutil.Date,\n\tUpdatedAt:   testutil.Date,\n\tScope: lists.Scope{\n\t\tType: string(scope.ScopeTypeAccount),\n\t},\n}\n\nvar stringlist2 = lists.List{\n\tListID:      listID + \"2\",\n\tDescription: listDescription + \"2\",\n\tEntries:     []string{listEntries},\n\tName:        listName + \"2\",\n\tType:        listType,\n\tCreatedAt:   testutil.Date,\n\tUpdatedAt:   testutil.Date,\n\tScope: lists.Scope{\n\t\tType: string(scope.ScopeTypeAccount),\n\t},\n}\n\nfunc TestCountryListCreate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --entries flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--name %s\", listName),\n\t\t\tWantError: \"error parsing arguments: required flag --entries not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --name flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--entries %s\", listEntries),\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate internal server error\",\n\t\t\tArgs: fmt.Sprintf(\"--entries %s --name %s\", listEntries, listName),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusInternalServerError,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusInternalServerError),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"500 - Internal Server Error\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--entries %s --name %s\", listEntries, listName),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader((testutil.GenJSON(stringlist)))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Created Account Country List '%s' (list id: %s)\", listName, listID),\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--entries %s --name %s --json\", listEntries, listName),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(stringlist))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(stringlist),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"create\"}, scenarios)\n}\n\nfunc TestCountryListDelete(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --list-id flag\",\n\t\t\tArgs:      \"\",\n\t\t\tWantError: \"error parsing arguments: required flag --list-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate bad request\",\n\t\t\tArgs: \"--list-id bar\",\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusBadRequest,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusBadRequest),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(`\n\t\t\t\t\t\t\t{\n    \t\t\t\t\t\t\t\"title\": \"invalid List ID\",\n    \t\t\t\t\t\t\t\"status\": 400\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t`))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"400 - Bad Request\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--list-id %s\", listID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusNoContent,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusNoContent),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Deleted Account Country List (list id: %s)\", listID),\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--list-id %s --json\", listID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusNoContent,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusNoContent),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.JSON(`{\"id\": %q, \"deleted\": true}`, listID),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"delete\"}, scenarios)\n}\n\nfunc TestCountryListGet(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --list-id flag\",\n\t\t\tArgs:      \"\",\n\t\t\tWantError: \"error parsing arguments: required flag --list-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate bad request\",\n\t\t\tArgs: \"--list-id baz\",\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusBadRequest,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusBadRequest),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(`\n\t\t\t\t\t\t\t{\n    \t\t\t\t\t\t\t\"title\": \"invalid List ID\",\n    \t\t\t\t\t\t\t\"status\": 400\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t`))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"400 - Bad Request\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--list-id %s\", listID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader((testutil.GenJSON(stringlist)))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: listString,\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--list-id %s --json\", listID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader((testutil.GenJSON(stringlist)))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(stringlist),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"get\"}, scenarios)\n}\n\nfunc TestCountryListList(t *testing.T) {\n\tlistsObject := lists.Lists{\n\t\tData: []lists.List{\n\t\t\tstringlist,\n\t\t\tstringlist2,\n\t\t},\n\t\tMeta: lists.MetaLists{},\n\t}\n\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName: \"validate internal server error\",\n\t\t\tArgs: \"\",\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusInternalServerError,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusInternalServerError),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"500 - Internal Server Error\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success (zero workspaces)\",\n\t\t\tArgs: \"\",\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(lists.Lists{\n\t\t\t\t\t\t\tData: []lists.List{},\n\t\t\t\t\t\t\tMeta: lists.MetaLists{},\n\t\t\t\t\t\t}))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: zeroListString,\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: \"\",\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(listsObject))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: listListsString,\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: \"--json\",\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(listsObject))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(listsObject),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"list\"}, scenarios)\n}\n\nfunc TestCountryListUpdate(t *testing.T) {\n\tupdatelist := lists.List{\n\t\tListID:      listID,\n\t\tDescription: listDescription + \"2\",\n\t\tEntries:     []string{listEntries + \"2\"},\n\t\tName:        listName,\n\t\tType:        listType,\n\t\tCreatedAt:   testutil.Date,\n\t\tUpdatedAt:   testutil.Date,\n\t\tScope: lists.Scope{\n\t\t\tType: string(scope.ScopeTypeAccount),\n\t\t},\n\t}\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --list-id flag\",\n\t\t\tArgs:      \"\",\n\t\t\tWantError: \"error parsing arguments: required flag --list-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--list-id %s --description %s --entries %s\", listID, listDescription+\"2\", listEntries+\"2\"),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(updatelist))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Updated Account Country List '%s' (list id: %s)\", listName, listID),\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--list-id %s --description %s --entries %s --json\", listID, listDescription+\"2\", listEntries+\"2\"),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(updatelist))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(updatelist),\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"update\"}, scenarios)\n}\n\nvar listListsString = strings.TrimSpace(`\nID           Name       Description    Type     Scope    Entries  Updated At                     Created At\nsomeListID   listName   NGWAFCLIList   country  account  us       2021-06-15 23:00:00 +0000 UTC  2021-06-15 23:00:00 +0000 UTC\nsomeListID2  listName2  NGWAFCLIList2  country  account  us       2021-06-15 23:00:00 +0000 UTC  2021-06-15 23:00:00 +0000 UTC\n`) + \"\\n\"\n\nvar zeroListString = strings.TrimSpace(`\nID  Name  Description  Type  Scope  Entries  Updated At  Created At\n`) + \"\\n\"\n\nvar listString = strings.TrimSpace(`\nID: someListID\nName: listName\nDescription: NGWAFCLIList\nType: country\nEntries: us\nScope: account\nUpdated (UTC): 2021-06-15 23:00\n`)\n"
  },
  {
    "path": "pkg/commands/ngwaf/countrylist/create.go",
    "content": "package countrylist\n\nimport (\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/scope\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/ngwaf/ngwaflist\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// CreateCommand calls the Fastly API to create account-level country lists.\ntype CreateCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tentries string\n\tname    string\n\n\t// Optional.\n\tdescription argparser.OptionalString\n}\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"create\", \"Create an account-level country list\").Alias(\"add\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"entries\", \"Entries for the list. Can either be a comma separated list or a path to a file.\").Required().StringVar(&c.entries)\n\tc.CmdClause.Flag(\"name\", \"User submitted display name of a list.\").Required().StringVar(&c.name)\n\n\t// Optional.\n\tc.CmdClause.Flag(\"description\", \"User submitted description of the list.\").Action(c.description.Set).StringVar(&c.description.Value)\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *CreateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\tinput := ngwaflist.ListCreateInput{\n\t\tCommandScope: scope.ScopeTypeAccount,\n\t\tDescription:  c.description,\n\t\tEntries:      c.entries,\n\t\tName:         c.name,\n\t\tType:         \"country\",\n\t\tWorkspaceID:  nil,\n\t}\n\n\tvar ok bool\n\tinput.FC, ok = c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tdata, err := ngwaflist.ListCreate(input)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, data); ok {\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Created Account Country List '%s' (list id: %s)\", data.Name, data.ListID)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/countrylist/delete.go",
    "content": "package countrylist\n\nimport (\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/scope\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/ngwaf/ngwaflist\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DeleteCommand calls the Fastly API to delete an account-level country list.\ntype DeleteCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tlistID string\n}\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\n\tc.CmdClause = parent.Command(\"delete\", \"Delete an account country list\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"list-id\", \"List ID\").Required().StringVar(&c.listID)\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DeleteCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tinput := ngwaflist.ListDeleteInput{\n\t\tCommandScope: scope.ScopeTypeAccount,\n\t\tListID:       c.listID,\n\t\tWorkspaceID:  nil,\n\t}\n\n\tvar ok bool\n\tinput.FC, ok = c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\terr := ngwaflist.ListDelete(input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif c.JSONOutput.Enabled {\n\t\to := struct {\n\t\t\tID      string `json:\"id\"`\n\t\t\tDeleted bool   `json:\"deleted\"`\n\t\t}{\n\t\t\tc.listID,\n\t\t\ttrue,\n\t\t}\n\t\t_, err := c.WriteJSON(out, o)\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Deleted Account Country List (list id: %s)\", c.listID)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/countrylist/doc.go",
    "content": "// Package countrylist contains commands to inspect and manipulate NGWAF account-level country lists.\npackage countrylist\n"
  },
  {
    "path": "pkg/commands/ngwaf/countrylist/get.go",
    "content": "package countrylist\n\nimport (\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/ngwaf/ngwaflist\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/scope\"\n)\n\n// GetCommand calls the Fastly API to get an account-level country list.\ntype GetCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tlistID string\n}\n\n// NewGetCommand returns a usable command registered under the parent.\nfunc NewGetCommand(parent argparser.Registerer, g *global.Data) *GetCommand {\n\tc := GetCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\n\tc.CmdClause = parent.Command(\"get\", \"Get an account-level country list\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"list-id\", \"List ID\").Required().StringVar(&c.listID)\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *GetCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tinput := ngwaflist.ListGetInput{\n\t\tCommandScope: scope.ScopeTypeAccount,\n\t\tListID:       c.listID,\n\t\tWorkspaceID:  nil,\n\t}\n\n\tvar ok bool\n\tinput.FC, ok = c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tlist, err := ngwaflist.ListGet(input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, list); ok {\n\t\treturn err\n\t}\n\n\ttext.PrintList(out, list)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/countrylist/list.go",
    "content": "package countrylist\n\nimport (\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/ngwaf/ngwaflist\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/scope\"\n)\n\n// ListCommand calls the Fastly API to list all country lists for your API token.\n\ntype ListCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n}\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\n\tc.CmdClause = parent.Command(\"list\", \"List all country lists for your account\")\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *ListCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tinput := ngwaflist.ListListInput{\n\t\tCommandScope: scope.ScopeTypeAccount,\n\t\tType:         \"country\",\n\t\tWorkspaceID:  nil,\n\t}\n\n\tvar ok bool\n\tinput.FC, ok = c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tlists, err := ngwaflist.ListList(input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, *lists); ok {\n\t\treturn err\n\t}\n\n\ttext.PrintListTbl(out, lists.Data)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/countrylist/root.go",
    "content": "package countrylist\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"country-list\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manage NGWAF Account Country Lists\")\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/countrylist/update.go",
    "content": "package countrylist\n\nimport (\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/scope\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/ngwaf/ngwaflist\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// UpdateCommand calls the Fastly API to update an account country list.\ntype UpdateCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tlistID string\n\n\t// Optional.\n\tdescription argparser.OptionalString\n\tentries     argparser.OptionalString\n}\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"update\", \"Update an account-level country list\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"list-id\", \"List ID\").Required().StringVar(&c.listID)\n\n\t// Optional.\n\tc.CmdClause.Flag(\"description\", \"User submitted description of the list.\").Action(c.description.Set).StringVar(&c.description.Value)\n\tc.CmdClause.Flag(\"entries\", \"Entries for the list. Can either be a comma separated list or a path to a file.\").Action(c.entries.Set).StringVar(&c.entries.Value)\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *UpdateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\tinput := ngwaflist.ListUpdateInput{\n\t\tCommandScope: scope.ScopeTypeAccount,\n\t\tDescription:  c.description,\n\t\tEntries:      c.entries,\n\t\tListID:       c.listID,\n\t\tWorkspaceID:  nil,\n\t}\n\n\tvar ok bool\n\tinput.FC, ok = c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tdata, err := ngwaflist.ListUpdate(input)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, data); ok {\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Updated Account Country List '%s' (list id: %s)\", data.Name, data.ListID)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/customsignal/create.go",
    "content": "package customsignal\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/scope\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/signals\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// CreateCommand calls the Fastly API to create account-level custom signals.\ntype CreateCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tname string\n\n\t// Optional.\n\tdescription argparser.OptionalString\n}\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"create\", \"Create an account-level custom signal\").Alias(\"add\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"User submitted display name of a custom signal. Is immutable and must be between 3 and 25 characters\").Required().StringVar(&c.name)\n\n\t// Optional.\n\tc.CmdClause.Flag(\"description\", \"User submitted description of a custom signal.\").Action(c.description.Set).StringVar(&c.description.Value)\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *CreateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\tvar err error\n\tinput := &signals.CreateInput{\n\t\tName: &c.name,\n\t\tScope: &scope.Scope{\n\t\t\tType:      scope.ScopeTypeAccount,\n\t\t\tAppliesTo: []string{\"*\"},\n\t\t},\n\t}\n\tif c.description.WasSet {\n\t\tinput.Description = &c.description.Value\n\t}\n\n\tfc, ok := c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tdata, err := signals.Create(context.TODO(), fc, input)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, data); ok {\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Created account-level custom signal '%s' (signal-id: %s)\", data.Name, data.SignalID)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/customsignal/customsignal_test.go",
    "content": "package customsignal_test\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\t\"testing\"\n\n\troot \"github.com/fastly/cli/pkg/commands/ngwaf\"\n\tsub \"github.com/fastly/cli/pkg/commands/ngwaf/customsignal\"\n\tfstfmt \"github.com/fastly/cli/pkg/fmt\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/scope\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/signals\"\n)\n\nconst (\n\tcustomSignalDescription = \"NGWAFCLICustomSignal\"\n\tcustomSignalID          = \"someID\"\n\tcustomSignalName        = \"CLICustomSignalName\"\n)\n\nvar customSignal = signals.Signal{\n\tCreatedAt:   testutil.Date,\n\tDescription: customSignalDescription,\n\tName:        customSignalName,\n\tSignalID:    customSignalID,\n\tScope: signals.Scope{\n\t\tType:      string(scope.ScopeTypeAccount),\n\t\tAppliesTo: []string{\"*\"},\n\t},\n}\n\nfunc TestCustomSignalCreate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --name flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--description %s\", customSignalDescription),\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate internal server error\",\n\t\t\tArgs: fmt.Sprintf(\"--description %s --name %s\", customSignalDescription, customSignalName),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusInternalServerError,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusInternalServerError),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"500 - Internal Server Error\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--description %s --name %s\", customSignalDescription, customSignalName),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader((testutil.GenJSON(customSignal)))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Created account-level custom signal '%s' (signal-id: %s)\", customSignalName, customSignalID),\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--description %s --name %s --json\", customSignalDescription, customSignalName),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(customSignal))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(customSignal),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"create\"}, scenarios)\n}\n\nfunc TestCustomSignalDelete(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --signal-id flag\",\n\t\t\tArgs:      \"\",\n\t\t\tWantError: \"error parsing arguments: required flag --signal-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate bad request\",\n\t\t\tArgs: \"--signal-id bar\",\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusBadRequest,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusBadRequest),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(`\n\t\t\t\t\t\t\t{\n    \t\t\t\t\t\t\t\"title\": \"invalid signal ID\",\n    \t\t\t\t\t\t\t\"status\": 400\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t`))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"400 - Bad Request\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--signal-id %s\", customSignalID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusNoContent,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusNoContent),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Deleted account-level custom signal (id: %s)\", customSignalID),\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--signal-id %s --json\", customSignalID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusNoContent,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusNoContent),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.JSON(`{\"id\": %q, \"deleted\": true}`, customSignalID),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"delete\"}, scenarios)\n}\n\nfunc TestCustomSignalGet(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --signal-id flag\",\n\t\t\tArgs:      \"\",\n\t\t\tWantError: \"error parsing arguments: required flag --signal-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate bad request\",\n\t\t\tArgs: \"--signal-id baz\",\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusBadRequest,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusBadRequest),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(`\n\t\t\t\t\t\t\t{\n    \t\t\t\t\t\t\t\"title\": \"invalid Custom Signal ID\",\n    \t\t\t\t\t\t\t\"status\": 400\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t`))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"400 - Bad Request\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--signal-id %s\", customSignalID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader((testutil.GenJSON(customSignal)))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: customSignalString,\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--signal-id %s --json\", customSignalID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader((testutil.GenJSON(customSignal)))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(customSignal),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"get\"}, scenarios)\n}\n\nfunc TestCustomSignalList(t *testing.T) {\n\tcustomSignalsObject := signals.Signals{\n\t\tData: []signals.Signal{\n\t\t\t{\n\t\t\t\tCreatedAt:   testutil.Date,\n\t\t\t\tDescription: customSignalDescription,\n\t\t\t\tName:        customSignalName,\n\t\t\t\tSignalID:    customSignalID,\n\t\t\t\tScope: signals.Scope{\n\t\t\t\t\tType:      string(scope.ScopeTypeAccount),\n\t\t\t\t\tAppliesTo: []string{\"*\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tCreatedAt:   testutil.Date,\n\t\t\t\tDescription: customSignalDescription,\n\t\t\t\tName:        customSignalName + \"2\",\n\t\t\t\tSignalID:    customSignalID + \"2\",\n\t\t\t\tScope: signals.Scope{\n\t\t\t\t\tType:      string(scope.ScopeTypeAccount),\n\t\t\t\t\tAppliesTo: []string{\"*\"},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tMeta: signals.MetaSignals{},\n\t}\n\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName: \"validate internal server error\",\n\t\t\tArgs: \"\",\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusInternalServerError,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusInternalServerError),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"500 - Internal Server Error\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success (zero account-level custom signals)\",\n\t\t\tArgs: \"\",\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(signals.Signals{\n\t\t\t\t\t\t\tData: []signals.Signal{},\n\t\t\t\t\t\t\tMeta: signals.MetaSignals{},\n\t\t\t\t\t\t}))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: zeroListCustomSignalsString,\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: \"\",\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(customSignalsObject))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: listCustomSignalsString,\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: \"--json\",\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(customSignalsObject))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(customSignalsObject),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"list\"}, scenarios)\n}\n\nfunc TestCustomSignalUpdate(t *testing.T) {\n\tcustomSignalObject := signals.Signal{\n\t\tCreatedAt:   testutil.Date,\n\t\tDescription: customSignalDescription,\n\t\tName:        customSignalName,\n\t\tSignalID:    customSignalID,\n\t}\n\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --signal-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--description %s\", customSignalDescription),\n\t\t\tWantError: \"error parsing arguments: required flag --signal-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --description flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--signal-id %s\", customSignalID),\n\t\t\tWantError: \"error parsing arguments: required flag --description not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--signal-id %s --description %s\", customSignalID, customSignalDescription),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(customSignalObject))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Updated account-level signal '%s' (signal-id: %s)\", customSignalName, customSignalID),\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--signal-id %s --description %s --json\", customSignalID, customSignalDescription),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(customSignal))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(customSignal),\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"update\"}, scenarios)\n}\n\nvar listCustomSignalsString = strings.TrimSpace(`\nID       Name                  Description           Scope    Updated At                     Created At\nsomeID   CLICustomSignalName   NGWAFCLICustomSignal  account  0001-01-01 00:00:00 +0000 UTC  2021-06-15 23:00:00 +0000 UTC\nsomeID2  CLICustomSignalName2  NGWAFCLICustomSignal  account  0001-01-01 00:00:00 +0000 UTC  2021-06-15 23:00:00 +0000 UTC\n`) + \"\\n\"\n\nvar zeroListCustomSignalsString = strings.TrimSpace(`\nID  Name  Description  Scope  Updated At  Created At\n`) + \"\\n\"\n\nvar customSignalString = strings.TrimSpace(`\nID: someID\nName: CLICustomSignalName\nDescription: NGWAFCLICustomSignal\nScope: account\nUpdated (UTC): 0001-01-01 00:00\nCreated (UTC): 2021-06-15 23:00\n`)\n"
  },
  {
    "path": "pkg/commands/ngwaf/customsignal/delete.go",
    "content": "package customsignal\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/scope\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/signals\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DeleteCommand calls the Fastly API to delete an account-level custom signal.\ntype DeleteCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tsignalID string\n}\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\n\tc.CmdClause = parent.Command(\"delete\", \"Delete an account-level custom signal\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"signal-id\", \"Custom Signal ID\").Required().StringVar(&c.signalID)\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DeleteCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tfc, ok := c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\terr := signals.Delete(context.TODO(), fc, &signals.DeleteInput{\n\t\tSignalID: &c.signalID,\n\t\tScope: &scope.Scope{\n\t\t\tType:      scope.ScopeTypeAccount,\n\t\t\tAppliesTo: []string{\"*\"},\n\t\t},\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif c.JSONOutput.Enabled {\n\t\to := struct {\n\t\t\tID      string `json:\"id\"`\n\t\t\tDeleted bool   `json:\"deleted\"`\n\t\t}{\n\t\t\tc.signalID,\n\t\t\ttrue,\n\t\t}\n\t\t_, err := c.WriteJSON(out, o)\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Deleted account-level custom signal (id: %s)\", c.signalID)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/customsignal/doc.go",
    "content": "// Package customsignal contains commands to inspect and manipulate NGWAF account-level custom signals.\npackage customsignal\n"
  },
  {
    "path": "pkg/commands/ngwaf/customsignal/get.go",
    "content": "package customsignal\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/scope\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/signals\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// GetCommand calls the Fastly API to get an account-level custom signal.\ntype GetCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tsignalID string\n}\n\n// NewGetCommand returns a usable command registered under the parent.\nfunc NewGetCommand(parent argparser.Registerer, g *global.Data) *GetCommand {\n\tc := GetCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\n\tc.CmdClause = parent.Command(\"get\", \"Get a custom signal\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"signal-id\", \"Custom Signal ID\").Required().StringVar(&c.signalID)\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *GetCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tfc, ok := c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tdata, err := signals.Get(context.TODO(), fc, &signals.GetInput{\n\t\tSignalID: &c.signalID,\n\t\tScope: &scope.Scope{\n\t\t\tType:      scope.ScopeTypeAccount,\n\t\t\tAppliesTo: []string{\"*\"},\n\t\t},\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, data); ok {\n\t\treturn err\n\t}\n\n\ttext.PrintCustomSignal(out, data)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/customsignal/list.go",
    "content": "package customsignal\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/scope\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/signals\"\n)\n\n// ListCommand calls the Fastly API to list all account-level custom signals for your API token.\ntype ListCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n}\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\n\tc.CmdClause = parent.Command(\"list\", \"List all account-level custom signals\")\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *ListCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tfc, ok := c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tsignals, err := signals.List(context.TODO(), fc, &signals.ListInput{\n\t\tScope: &scope.Scope{\n\t\t\tType:      scope.ScopeTypeAccount,\n\t\t\tAppliesTo: []string{\"*\"},\n\t\t},\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, signals); ok {\n\t\treturn err\n\t}\n\n\ttext.PrintCustomSignalTbl(out, signals.Data)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/customsignal/root.go",
    "content": "package customsignal\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"customsignal\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manage NGWAF Account-Level Custom Signals\")\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/customsignal/update.go",
    "content": "package customsignal\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/scope\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/signals\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// UpdateCommand calls the Fastly API to update account-level custom signals.\ntype UpdateCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tsignalID    string\n\tdescription string\n}\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"update\", \"Update a workspace\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"signal-id\", \"Custom Signal ID\").Required().StringVar(&c.signalID)\n\tc.CmdClause.Flag(\"description\", \"User submitted description of a custom signal.\").Required().StringVar(&c.description)\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *UpdateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\tvar err error\n\tinput := &signals.UpdateInput{\n\t\tSignalID:    &c.signalID,\n\t\tDescription: &c.description,\n\t\tScope: &scope.Scope{\n\t\t\tType:      scope.ScopeTypeAccount,\n\t\t\tAppliesTo: []string{\"*\"},\n\t\t},\n\t}\n\n\tfc, ok := c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tdata, err := signals.Update(context.TODO(), fc, input)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, data); ok {\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Updated account-level signal '%s' (signal-id: %s)\", data.Name, data.SignalID)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/doc.go",
    "content": "// Package ngwaf contains commands to inspect and manipulate NGWAF objects.\npackage ngwaf\n"
  },
  {
    "path": "pkg/commands/ngwaf/iplist/create.go",
    "content": "package iplist\n\nimport (\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/scope\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/ngwaf/ngwaflist\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// CreateCommand calls the Fastly API to create account-level ip lists.\ntype CreateCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tentries string\n\tname    string\n\n\t// Optional.\n\tdescription argparser.OptionalString\n}\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"create\", \"Create an account-level ip list\").Alias(\"add\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"entries\", \"Entries for the list. Can either be a comma separated list or a path to a file.\").Required().StringVar(&c.entries)\n\tc.CmdClause.Flag(\"name\", \"User submitted display name of a list.\").Required().StringVar(&c.name)\n\n\t// Optional.\n\tc.CmdClause.Flag(\"description\", \"User submitted description of the list.\").Action(c.description.Set).StringVar(&c.description.Value)\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *CreateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\tinput := ngwaflist.ListCreateInput{\n\t\tCommandScope: scope.ScopeTypeAccount,\n\t\tDescription:  c.description,\n\t\tEntries:      c.entries,\n\t\tName:         c.name,\n\t\tType:         \"ip\",\n\t\tWorkspaceID:  nil,\n\t}\n\n\tvar ok bool\n\tinput.FC, ok = c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tdata, err := ngwaflist.ListCreate(input)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, data); ok {\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Created Account IP List '%s' (list id: %s)\", data.Name, data.ListID)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/iplist/delete.go",
    "content": "package iplist\n\nimport (\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/scope\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/ngwaf/ngwaflist\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DeleteCommand calls the Fastly API to delete an account-level ip list.\ntype DeleteCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tlistID string\n}\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\n\tc.CmdClause = parent.Command(\"delete\", \"Delete an account ip list\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"list-id\", \"List ID\").Required().StringVar(&c.listID)\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DeleteCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tinput := ngwaflist.ListDeleteInput{\n\t\tCommandScope: scope.ScopeTypeAccount,\n\t\tListID:       c.listID,\n\t\tWorkspaceID:  nil,\n\t}\n\n\tvar ok bool\n\tinput.FC, ok = c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\terr := ngwaflist.ListDelete(input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif c.JSONOutput.Enabled {\n\t\to := struct {\n\t\t\tID      string `json:\"id\"`\n\t\t\tDeleted bool   `json:\"deleted\"`\n\t\t}{\n\t\t\tc.listID,\n\t\t\ttrue,\n\t\t}\n\t\t_, err := c.WriteJSON(out, o)\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Deleted Account IP List (list id: %s)\", c.listID)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/iplist/doc.go",
    "content": "// Package iplist contains commands to inspect and manipulate NGWAF account-level ip lists.\npackage iplist\n"
  },
  {
    "path": "pkg/commands/ngwaf/iplist/get.go",
    "content": "package iplist\n\nimport (\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/ngwaf/ngwaflist\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/scope\"\n)\n\n// GetCommand calls the Fastly API to get an account-level ip list.\ntype GetCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tlistID string\n}\n\n// NewGetCommand returns a usable command registered under the parent.\nfunc NewGetCommand(parent argparser.Registerer, g *global.Data) *GetCommand {\n\tc := GetCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\n\tc.CmdClause = parent.Command(\"get\", \"Get an account-level ip list\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"list-id\", \"List ID\").Required().StringVar(&c.listID)\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *GetCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tinput := ngwaflist.ListGetInput{\n\t\tCommandScope: scope.ScopeTypeAccount,\n\t\tListID:       c.listID,\n\t\tWorkspaceID:  nil,\n\t}\n\n\tvar ok bool\n\tinput.FC, ok = c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tlist, err := ngwaflist.ListGet(input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, list); ok {\n\t\treturn err\n\t}\n\n\ttext.PrintList(out, list)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/iplist/iplist_test.go",
    "content": "package iplist_test\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\t\"testing\"\n\n\troot \"github.com/fastly/cli/pkg/commands/ngwaf\"\n\tsub \"github.com/fastly/cli/pkg/commands/ngwaf/iplist\"\n\tfstfmt \"github.com/fastly/cli/pkg/fmt\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/lists\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/scope\"\n)\n\nconst (\n\tlistID          = \"someListID\"\n\tlistDescription = \"NGWAFCLIList\"\n\tlistEntries     = \"1.0.0.0\"\n\tlistType        = \"ip\"\n\tlistName        = \"listName\"\n)\n\nvar stringlist = lists.List{\n\tListID:      listID,\n\tDescription: listDescription,\n\tEntries:     []string{listEntries},\n\tName:        listName,\n\tType:        listType,\n\tCreatedAt:   testutil.Date,\n\tUpdatedAt:   testutil.Date,\n\tScope: lists.Scope{\n\t\tType: string(scope.ScopeTypeAccount),\n\t},\n}\n\nvar stringlist2 = lists.List{\n\tListID:      listID + \"2\",\n\tDescription: listDescription + \"2\",\n\tEntries:     []string{listEntries},\n\tName:        listName + \"2\",\n\tType:        listType,\n\tCreatedAt:   testutil.Date,\n\tUpdatedAt:   testutil.Date,\n\tScope: lists.Scope{\n\t\tType: string(scope.ScopeTypeAccount),\n\t},\n}\n\nfunc TestIPListCreate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --entries flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--name %s\", listName),\n\t\t\tWantError: \"error parsing arguments: required flag --entries not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --name flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--entries %s\", listEntries),\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate internal server error\",\n\t\t\tArgs: fmt.Sprintf(\"--entries %s --name %s\", listEntries, listName),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusInternalServerError,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusInternalServerError),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"500 - Internal Server Error\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--entries %s --name %s\", listEntries, listName),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader((testutil.GenJSON(stringlist)))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Created Account IP List '%s' (list id: %s)\", listName, listID),\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--entries %s --name %s --json\", listEntries, listName),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(stringlist))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(stringlist),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"create\"}, scenarios)\n}\n\nfunc TestIPListDelete(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --list-id flag\",\n\t\t\tArgs:      \"\",\n\t\t\tWantError: \"error parsing arguments: required flag --list-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate bad request\",\n\t\t\tArgs: \"--list-id bar\",\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusBadRequest,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusBadRequest),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(`\n\t\t\t\t\t\t\t{\n    \t\t\t\t\t\t\t\"title\": \"invalid List ID\",\n    \t\t\t\t\t\t\t\"status\": 400\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t`))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"400 - Bad Request\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--list-id %s\", listID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusNoContent,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusNoContent),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Deleted Account IP List (list id: %s)\", listID),\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--list-id %s --json\", listID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusNoContent,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusNoContent),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.JSON(`{\"id\": %q, \"deleted\": true}`, listID),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"delete\"}, scenarios)\n}\n\nfunc TestIPListGet(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --list-id flag\",\n\t\t\tArgs:      \"\",\n\t\t\tWantError: \"error parsing arguments: required flag --list-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate bad request\",\n\t\t\tArgs: \"--list-id baz\",\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusBadRequest,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusBadRequest),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(`\n\t\t\t\t\t\t\t{\n    \t\t\t\t\t\t\t\"title\": \"invalid List ID\",\n    \t\t\t\t\t\t\t\"status\": 400\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t`))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"400 - Bad Request\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--list-id %s\", listID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader((testutil.GenJSON(stringlist)))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: listString,\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--list-id %s --json\", listID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader((testutil.GenJSON(stringlist)))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(stringlist),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"get\"}, scenarios)\n}\n\nfunc TestIPListList(t *testing.T) {\n\tlistsObject := lists.Lists{\n\t\tData: []lists.List{\n\t\t\tstringlist,\n\t\t\tstringlist2,\n\t\t},\n\t\tMeta: lists.MetaLists{},\n\t}\n\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName: \"validate internal server error\",\n\t\t\tArgs: \"\",\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusInternalServerError,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusInternalServerError),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"500 - Internal Server Error\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success (zero workspaces)\",\n\t\t\tArgs: \"\",\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(lists.Lists{\n\t\t\t\t\t\t\tData: []lists.List{},\n\t\t\t\t\t\t\tMeta: lists.MetaLists{},\n\t\t\t\t\t\t}))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: zeroListString,\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: \"\",\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(listsObject))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: listListsString,\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: \"--json\",\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(listsObject))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(listsObject),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"list\"}, scenarios)\n}\n\nfunc TestIPListUpdate(t *testing.T) {\n\tupdatelist := lists.List{\n\t\tListID:      listID,\n\t\tDescription: listDescription + \"2\",\n\t\tEntries:     []string{listEntries + \"2\"},\n\t\tName:        listName,\n\t\tType:        listType,\n\t\tCreatedAt:   testutil.Date,\n\t\tUpdatedAt:   testutil.Date,\n\t\tScope: lists.Scope{\n\t\t\tType: string(scope.ScopeTypeAccount),\n\t\t},\n\t}\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --list-id flag\",\n\t\t\tArgs:      \"\",\n\t\t\tWantError: \"error parsing arguments: required flag --list-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--list-id %s --description %s --entries %s\", listID, listDescription+\"2\", listEntries+\"2\"),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(updatelist))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Updated Account IP List '%s' (list id: %s)\", listName, listID),\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--list-id %s --description %s --entries %s --json\", listID, listDescription+\"2\", listEntries+\"2\"),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(updatelist))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(updatelist),\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"update\"}, scenarios)\n}\n\nvar listListsString = strings.TrimSpace(`\nID           Name       Description    Type  Scope    Entries  Updated At                     Created At\nsomeListID   listName   NGWAFCLIList   ip    account  1.0.0.0  2021-06-15 23:00:00 +0000 UTC  2021-06-15 23:00:00 +0000 UTC\nsomeListID2  listName2  NGWAFCLIList2  ip    account  1.0.0.0  2021-06-15 23:00:00 +0000 UTC  2021-06-15 23:00:00 +0000 UTC\n`) + \"\\n\"\n\nvar zeroListString = strings.TrimSpace(`\nID  Name  Description  Type  Scope  Entries  Updated At  Created At\n`) + \"\\n\"\n\nvar listString = strings.TrimSpace(`\nID: someListID\nName: listName\nDescription: NGWAFCLIList\nType: ip\nEntries: 1.0.0.0\nScope: account\nUpdated (UTC): 2021-06-15 23:00\n`)\n"
  },
  {
    "path": "pkg/commands/ngwaf/iplist/list.go",
    "content": "package iplist\n\nimport (\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/ngwaf/ngwaflist\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/scope\"\n)\n\n// ListCommand calls the Fastly API to list all ip lists for your API token.\n\ntype ListCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n}\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\n\tc.CmdClause = parent.Command(\"list\", \"List all ip lists for your account\")\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *ListCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tinput := ngwaflist.ListListInput{\n\t\tCommandScope: scope.ScopeTypeAccount,\n\t\tType:         \"ip\",\n\t\tWorkspaceID:  nil,\n\t}\n\n\tvar ok bool\n\tinput.FC, ok = c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tlists, err := ngwaflist.ListList(input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, *lists); ok {\n\t\treturn err\n\t}\n\n\ttext.PrintListTbl(out, lists.Data)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/iplist/root.go",
    "content": "package iplist\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"ip-list\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manage NGWAF Account IP Lists\")\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/iplist/update.go",
    "content": "package iplist\n\nimport (\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/scope\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/ngwaf/ngwaflist\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// UpdateCommand calls the Fastly API to update an account ip list.\ntype UpdateCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tlistID string\n\n\t// Optional.\n\tdescription argparser.OptionalString\n\tentries     argparser.OptionalString\n}\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"update\", \"Update an account-level ip list\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"list-id\", \"List ID\").Required().StringVar(&c.listID)\n\n\t// Optional.\n\tc.CmdClause.Flag(\"description\", \"User submitted description of the list.\").Action(c.description.Set).StringVar(&c.description.Value)\n\tc.CmdClause.Flag(\"entries\", \"Entries for the list. Can either be a comma separated list or a path to a file.\").Action(c.entries.Set).StringVar(&c.entries.Value)\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *UpdateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\tinput := ngwaflist.ListUpdateInput{\n\t\tCommandScope: scope.ScopeTypeAccount,\n\t\tDescription:  c.description,\n\t\tEntries:      c.entries,\n\t\tListID:       c.listID,\n\t\tWorkspaceID:  nil,\n\t}\n\n\tvar ok bool\n\tinput.FC, ok = c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tdata, err := ngwaflist.ListUpdate(input)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, data); ok {\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Updated Account IP List '%s' (list id: %s)\", data.Name, data.ListID)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/ngwaflist/api.go",
    "content": "package ngwaflist\n\nimport (\n\t\"context\"\n\t\"strings\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/ngwaf\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/lists\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/scope\"\n)\n\ntype ListCreateInput struct {\n\tCommandScope scope.Type\n\tDescription  argparser.OptionalString\n\tEntries      string\n\tName         string\n\tType         string\n\tWorkspaceID  *argparser.OptionalWorkspaceID\n\tFC           *fastly.Client\n}\n\nfunc ListCreate(argsInput ListCreateInput) (*lists.List, error) {\n\tinput := lists.CreateInput{\n\t\tEntries: fastly.ToPointer(strings.Split(argparser.Content(argsInput.Entries), \",\")),\n\t\tName:    &argsInput.Name,\n\t\tType:    &argsInput.Type,\n\t}\n\tif argsInput.Description.WasSet {\n\t\tinput.Description = &argsInput.Description.Value\n\t}\n\tinputWorkspaceID := \"\"\n\tif argsInput.CommandScope == scope.ScopeTypeWorkspace {\n\t\tif err := argsInput.WorkspaceID.Parse(); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tinputWorkspaceID = argsInput.WorkspaceID.Value\n\t}\n\n\tvar err error\n\tinput.Scope, err = generateScope(argsInput.CommandScope, inputWorkspaceID)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn lists.Create(context.TODO(), argsInput.FC, &input)\n}\n\ntype ListDeleteInput struct {\n\tCommandScope scope.Type\n\tListID       string\n\tWorkspaceID  *argparser.OptionalWorkspaceID\n\tFC           *fastly.Client\n}\n\nfunc ListDelete(argsInput ListDeleteInput) error {\n\tinput := lists.DeleteInput{\n\t\tListID: &argsInput.ListID,\n\t}\n\tinputWorkspaceID := \"\"\n\tif argsInput.CommandScope == scope.ScopeTypeWorkspace {\n\t\tif err := argsInput.WorkspaceID.Parse(); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tinputWorkspaceID = argsInput.WorkspaceID.Value\n\t}\n\tvar err error\n\tinput.Scope, err = generateScope(argsInput.CommandScope, inputWorkspaceID)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn lists.Delete(context.TODO(), argsInput.FC, &input)\n}\n\ntype ListGetInput struct {\n\tCommandScope scope.Type\n\tListID       string\n\tWorkspaceID  *argparser.OptionalWorkspaceID\n\tFC           *fastly.Client\n}\n\nfunc ListGet(argsInput ListGetInput) (*lists.List, error) {\n\tinput := lists.GetInput{\n\t\tListID: &argsInput.ListID,\n\t}\n\tinputWorkspaceID := \"\"\n\tif argsInput.CommandScope == scope.ScopeTypeWorkspace {\n\t\tif err := argsInput.WorkspaceID.Parse(); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tinputWorkspaceID = argsInput.WorkspaceID.Value\n\t}\n\tvar err error\n\tinput.Scope, err = generateScope(argsInput.CommandScope, inputWorkspaceID)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn lists.Get(context.TODO(), argsInput.FC, &input)\n}\n\ntype ListListInput struct {\n\tCommandScope scope.Type\n\tListID       string\n\tType         string\n\tWorkspaceID  *argparser.OptionalWorkspaceID\n\tFC           *fastly.Client\n}\n\nfunc ListList(argsInput ListListInput) (*lists.Lists, error) {\n\tinput := lists.ListInput{}\n\tinputWorkspaceID := \"\"\n\tif argsInput.CommandScope == scope.ScopeTypeWorkspace {\n\t\tif err := argsInput.WorkspaceID.Parse(); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tinputWorkspaceID = argsInput.WorkspaceID.Value\n\t}\n\tvar err error\n\tinput.Scope, err = generateScope(argsInput.CommandScope, inputWorkspaceID)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tdata, err := lists.ListLists(context.TODO(), argsInput.FC, &input)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tlistFilteredByType := []lists.List{}\n\n\tfor _, list := range data.Data {\n\t\tif list.Type == argsInput.Type {\n\t\t\tlistFilteredByType = append(listFilteredByType, list)\n\t\t}\n\t}\n\tdata.Data = listFilteredByType\n\n\treturn data, nil\n}\n\ntype ListUpdateInput struct {\n\tCommandScope scope.Type\n\tDescription  argparser.OptionalString\n\tEntries      argparser.OptionalString\n\tListID       string\n\tWorkspaceID  *argparser.OptionalWorkspaceID\n\tFC           *fastly.Client\n}\n\nfunc ListUpdate(argsInput ListUpdateInput) (*lists.List, error) {\n\tinput := lists.UpdateInput{\n\t\tListID: &argsInput.ListID,\n\t}\n\tif argsInput.Description.WasSet {\n\t\tinput.Description = &argsInput.Description.Value\n\t}\n\tif argsInput.Entries.WasSet {\n\t\tinput.Entries = fastly.ToPointer(strings.Split(argparser.Content(argsInput.Entries.Value), \",\"))\n\t}\n\tinputWorkspaceID := \"\"\n\tif argsInput.CommandScope == scope.ScopeTypeWorkspace {\n\t\tif err := argsInput.WorkspaceID.Parse(); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tinputWorkspaceID = argsInput.WorkspaceID.Value\n\t}\n\tvar err error\n\tinput.Scope, err = generateScope(argsInput.CommandScope, inputWorkspaceID)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn lists.Update(context.TODO(), argsInput.FC, &input)\n}\n\nfunc generateScope(inputScope scope.Type, workspaceID string) (*scope.Scope, error) {\n\tif inputScope == scope.ScopeTypeAccount {\n\t\treturn &scope.Scope{\n\t\t\tType:      scope.ScopeTypeAccount,\n\t\t\tAppliesTo: ngwaf.DefaultAccountScope,\n\t\t}, nil\n\t}\n\tif inputScope == scope.ScopeTypeWorkspace {\n\t\treturn &scope.Scope{\n\t\t\tType:      scope.ScopeTypeWorkspace,\n\t\t\tAppliesTo: []string{workspaceID},\n\t\t}, nil\n\t}\n\treturn nil, fsterr.ErrInvalidNGWAFScopeType\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/ngwaflist/doc.go",
    "content": "// Package list contains generic api calls to inspect and manipulate NGWAF account lists.\npackage ngwaflist\n"
  },
  {
    "path": "pkg/commands/ngwaf/root.go",
    "content": "package ngwaf\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/scope\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"ngwaf\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manage NGWAF\")\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n\nvar ScopeTypes = []string{string(scope.ScopeTypeAccount), string(scope.ScopeTypeWorkspace)}\n\nvar DefaultAccountScope = []string{\"*\"}\n"
  },
  {
    "path": "pkg/commands/ngwaf/rule/create.go",
    "content": "package rule\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"path/filepath\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/rules\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/scope\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// CreateCommand calls the Fastly API to create account-level rules.\ntype CreateCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tpath string\n}\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"create\", \"Create an account-level rule\").Alias(\"add\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"path\", \"Path to a json file that contains the rule schema.\").Required().StringVar(&c.path)\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *CreateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\tvar err error\n\trule := &rules.Rule{}\n\tif c.path != \"\" {\n\t\tpath, err := filepath.Abs(c.path)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"error parsing path '%s': %q\", c.path, err)\n\t\t}\n\n\t\tjsonFile, err := os.Open(path)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"error reading path '%s': %q\", c.path, err)\n\t\t}\n\t\tdefer jsonFile.Close()\n\n\t\tbyteValue, err := io.ReadAll(jsonFile)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to read json file: %v\", err)\n\t\t}\n\n\t\tif err := json.Unmarshal(byteValue, rule); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to unmarshal json data: %v\", err)\n\t\t}\n\t}\n\n\tinput := &rules.CreateInput{\n\t\tActions:            []*rules.CreateAction{},\n\t\tConditions:         []*rules.CreateCondition{},\n\t\tDescription:        &rule.Description,\n\t\tGroupConditions:    []*rules.CreateGroupCondition{},\n\t\tMultivalConditions: []*rules.CreateMultivalCondition{},\n\t\tEnabled:            &rule.Enabled,\n\t\tType:               &rule.Type,\n\t\tGroupOperator:      &rule.GroupOperator,\n\t\tRequestLogging:     &rule.RequestLogging,\n\t\tScope: &scope.Scope{\n\t\t\tType:      scope.ScopeTypeAccount,\n\t\t\tAppliesTo: []string{\"*\"},\n\t\t},\n\t}\n\n\tfor _, action := range rule.Actions {\n\t\tinput.Actions = append(input.Actions, &rules.CreateAction{\n\t\t\tAllowInteractive: action.AllowInteractive,\n\t\t\tDeceptionType:    &action.DeceptionType,\n\t\t\tRedirectURL:      &action.RedirectURL,\n\t\t\tResponseCode:     &action.ResponseCode,\n\t\t\tSignal:           &action.Signal,\n\t\t\tType:             &action.Type,\n\t\t})\n\t}\n\n\tif rule.RateLimit != nil {\n\t\tinput.RateLimit = &rules.CreateRateLimit{\n\t\t\tClientIdentifiers: []*rules.CreateClientIdentifier{},\n\t\t\tDuration:          &rule.RateLimit.Duration,\n\t\t\tInterval:          &rule.RateLimit.Interval,\n\t\t\tSignal:            &rule.RateLimit.Signal,\n\t\t\tThreshold:         &rule.RateLimit.Threshold,\n\t\t}\n\n\t\tfor _, rateLimit := range rule.RateLimit.ClientIdentifiers {\n\t\t\tinput.RateLimit.ClientIdentifiers = append(input.RateLimit.ClientIdentifiers, &rules.CreateClientIdentifier{\n\t\t\t\tKey:  &rateLimit.Key,\n\t\t\t\tName: &rateLimit.Name,\n\t\t\t\tType: &rateLimit.Type,\n\t\t\t})\n\t\t}\n\t}\n\n\tfor _, jsonCondition := range rule.Conditions {\n\t\tswitch jsonCondition.Type {\n\t\tcase \"single\":\n\t\t\tif sc, ok := jsonCondition.Fields.(rules.SingleCondition); ok {\n\t\t\t\tinput.Conditions = append(input.Conditions, &rules.CreateCondition{\n\t\t\t\t\tField:    &sc.Field,\n\t\t\t\t\tOperator: &sc.Operator,\n\t\t\t\t\tValue:    &sc.Value,\n\t\t\t\t})\n\t\t\t} else {\n\t\t\t\treturn fmt.Errorf(\"expected SingleCondition, got %T\", jsonCondition.Fields)\n\t\t\t}\n\t\tcase \"group\":\n\t\t\tif gc, ok := jsonCondition.Fields.(rules.GroupCondition); ok {\n\t\t\t\tparsedGroupCondition := &rules.CreateGroupCondition{\n\t\t\t\t\tGroupOperator: &gc.GroupOperator,\n\t\t\t\t\tConditions:    []*rules.CreateCondition{},\n\t\t\t\t}\n\t\t\t\tfor _, groupCondition := range gc.Conditions {\n\t\t\t\t\tswitch groupCondition.Type {\n\t\t\t\t\tcase \"single\":\n\t\t\t\t\t\tif gsc, ok := groupCondition.Fields.(rules.Condition); ok {\n\t\t\t\t\t\t\tparsedGroupCondition.Conditions = append(parsedGroupCondition.Conditions, &rules.CreateCondition{\n\t\t\t\t\t\t\t\tField:    &gsc.Field,\n\t\t\t\t\t\t\t\tOperator: &gsc.Operator,\n\t\t\t\t\t\t\t\tValue:    &gsc.Value,\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\treturn fmt.Errorf(\"expected Condition, got %T\", groupCondition.Fields)\n\t\t\t\t\t\t}\n\t\t\t\t\tcase \"multival\":\n\t\t\t\t\t\tif gmvc, ok := groupCondition.Fields.(rules.MultivalCondition); ok {\n\t\t\t\t\t\t\tcreateMultivalCondition := &rules.CreateMultivalCondition{\n\t\t\t\t\t\t\t\tField:         &gmvc.Field,\n\t\t\t\t\t\t\t\tOperator:      &gmvc.Operator,\n\t\t\t\t\t\t\t\tGroupOperator: &gmvc.GroupOperator,\n\t\t\t\t\t\t\t\tConditions:    []*rules.CreateConditionMult{},\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tfor _, groupMultivalSingleCondition := range gmvc.Conditions {\n\t\t\t\t\t\t\t\tcreateMultivalCondition.Conditions = append(createMultivalCondition.Conditions, &rules.CreateConditionMult{\n\t\t\t\t\t\t\t\t\tField:    &groupMultivalSingleCondition.Field,\n\t\t\t\t\t\t\t\t\tOperator: &groupMultivalSingleCondition.Operator,\n\t\t\t\t\t\t\t\t\tValue:    &groupMultivalSingleCondition.Value,\n\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tparsedGroupCondition.MultivalConditions = append(parsedGroupCondition.MultivalConditions, createMultivalCondition)\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\treturn fmt.Errorf(\"expected MultivalCondition, got %T\", groupCondition.Fields)\n\t\t\t\t\t\t}\n\n\t\t\t\t\tdefault:\n\t\t\t\t\t\treturn fmt.Errorf(\"unknown condition type: %s\", groupCondition.Type)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tinput.GroupConditions = append(input.GroupConditions, parsedGroupCondition)\n\t\t\t} else {\n\t\t\t\treturn fmt.Errorf(\"expected GroupCondition, got %T\", jsonCondition.Fields)\n\t\t\t}\n\t\tcase \"multival\":\n\t\t\tif mvc, ok := jsonCondition.Fields.(rules.CreateMultivalCondition); ok {\n\t\t\t\tparsedMultiValCondition := &rules.CreateMultivalCondition{\n\t\t\t\t\tField:         mvc.Field,\n\t\t\t\t\tGroupOperator: mvc.GroupOperator,\n\t\t\t\t\tOperator:      mvc.Operator,\n\t\t\t\t\tConditions:    []*rules.CreateConditionMult{},\n\t\t\t\t}\n\t\t\t\tfor _, multiSingleCondition := range mvc.Conditions {\n\t\t\t\t\tparsedMultiValCondition.Conditions = append(parsedMultiValCondition.Conditions, &rules.CreateConditionMult{\n\t\t\t\t\t\tField:    multiSingleCondition.Field,\n\t\t\t\t\t\tOperator: multiSingleCondition.Operator,\n\t\t\t\t\t\tValue:    multiSingleCondition.Value,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t\tinput.MultivalConditions = append(input.MultivalConditions, parsedMultiValCondition)\n\t\t\t} else {\n\t\t\t\treturn fmt.Errorf(\"expected MultivalCondition, got %T\", jsonCondition.Fields)\n\t\t\t}\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"unknown condition type: %s\", jsonCondition.Type)\n\t\t}\n\t}\n\n\tfc, ok := c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tdata, err := rules.Create(context.TODO(), fc, input)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, data); ok {\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Created account-level rule with ID %s\", data.RuleID)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/rule/delete.go",
    "content": "package rule\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/rules\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/scope\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DeleteCommand calls the Fastly API to delete an account-level rule.\ntype DeleteCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\truleID string\n}\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\n\tc.CmdClause = parent.Command(\"delete\", \"Delete an account-level rule\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"rule-id\", \"Rule ID\").Required().StringVar(&c.ruleID)\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DeleteCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tfc, ok := c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\terr := rules.Delete(context.TODO(), fc, &rules.DeleteInput{\n\t\tRuleID: &c.ruleID,\n\t\tScope: &scope.Scope{\n\t\t\tType:      scope.ScopeTypeAccount,\n\t\t\tAppliesTo: []string{\"*\"},\n\t\t},\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif c.JSONOutput.Enabled {\n\t\to := struct {\n\t\t\tID      string `json:\"id\"`\n\t\t\tDeleted bool   `json:\"deleted\"`\n\t\t}{\n\t\t\tc.ruleID,\n\t\t\ttrue,\n\t\t}\n\t\t_, err := c.WriteJSON(out, o)\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Deleted account-level rule with id: %s\", c.ruleID)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/rule/doc.go",
    "content": "// Package rule contains commands to inspect and manipulate NGWAF account-level rules.\npackage rule\n"
  },
  {
    "path": "pkg/commands/ngwaf/rule/get.go",
    "content": "package rule\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/rules\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/scope\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// GetCommand calls the Fastly API to get an account-level rule.\ntype GetCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\truleID string\n}\n\n// NewGetCommand returns a usable command registered under the parent.\nfunc NewGetCommand(parent argparser.Registerer, g *global.Data) *GetCommand {\n\tc := GetCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\n\tc.CmdClause = parent.Command(\"get\", \"Get an account-level rule\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"rule-id\", \"Rule ID\").Required().StringVar(&c.ruleID)\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *GetCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tfc, ok := c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tdata, err := rules.Get(context.TODO(), fc, &rules.GetInput{\n\t\tRuleID: &c.ruleID,\n\t\tScope: &scope.Scope{\n\t\t\tType:      scope.ScopeTypeAccount,\n\t\t\tAppliesTo: []string{\"*\"},\n\t\t},\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, data); ok {\n\t\treturn err\n\t}\n\n\ttext.PrintRule(out, data)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/rule/list.go",
    "content": "package rule\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\t\"strconv\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/rules\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/scope\"\n)\n\n// ListCommand calls the Fastly API to list all account-level rules for your API token.\ntype ListCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Optional.\n\taction  argparser.OptionalString\n\tenabled argparser.OptionalString\n}\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\n\tc.CmdClause = parent.Command(\"list\", \"List all account-level rules\")\n\n\t// Optional.\n\tc.CmdClause.Flag(\"action\", \"Filter rules based on action.\").Action(c.action.Set).StringVar(&c.action.Value)\n\tc.CmdClause.Flag(\"enabled\", \"Filter rules based on whether the rule is enabled.\").Action(c.enabled.Set).StringVar(&c.enabled.Value)\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *ListCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tfc, ok := c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tinput := &rules.ListInput{\n\t\tScope: &scope.Scope{\n\t\t\tType:      scope.ScopeTypeAccount,\n\t\t\tAppliesTo: []string{\"*\"},\n\t\t},\n\t}\n\n\tif c.action.WasSet {\n\t\tinput.Action = &c.action.Value\n\t}\n\n\tif c.enabled.WasSet {\n\t\tenabled, _ := strconv.ParseBool(c.enabled.Value)\n\t\tinput.Enabled = &enabled\n\t}\n\n\trules, err := rules.List(context.TODO(), fc, input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, rules); ok {\n\t\treturn err\n\t}\n\n\ttext.PrintRuleTbl(out, rules.Data)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/rule/root.go",
    "content": "package rule\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"rule\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manage NGWAF Account-Level Rules\")\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/rule/rule_test.go",
    "content": "package rule_test\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\t\"testing\"\n\n\troot \"github.com/fastly/cli/pkg/commands/ngwaf\"\n\tsub \"github.com/fastly/cli/pkg/commands/ngwaf/rule\"\n\tfstfmt \"github.com/fastly/cli/pkg/fmt\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/rules\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/scope\"\n)\n\nconst (\n\tcomplexRulePath = \"testdata/test_complex_rule.json\"\n\tcomplexRuleID   = \"someComplexID\"\n\truleDescription = \"Utility requests\"\n\truleEnabled     = true\n\truleAction      = \"allow\"\n\truleID          = \"someID\"\n\trulePath        = \"testdata/test_rule.json\"\n\truleType        = \"request\"\n)\n\nvar rule = rules.Rule{\n\tCreatedAt:   testutil.Date,\n\tDescription: ruleDescription,\n\tEnabled:     ruleEnabled,\n\tRuleID:      ruleID,\n\tActions: []rules.Action{\n\t\t{\n\t\t\tType: ruleAction,\n\t\t},\n\t},\n\tType: ruleType,\n\tScope: rules.Scope{\n\t\tType:      string(scope.ScopeTypeAccount),\n\t\tAppliesTo: []string{\"*\"},\n\t},\n}\n\nvar complexRule = rules.Rule{\n\tCreatedAt:   testutil.Date,\n\tDescription: ruleDescription,\n\tEnabled:     ruleEnabled,\n\tRuleID:      complexRuleID,\n\tActions: []rules.Action{\n\t\t{\n\t\t\tType: ruleAction,\n\t\t},\n\t},\n\tType: ruleType,\n\tScope: rules.Scope{\n\t\tType:      string(scope.ScopeTypeAccount),\n\t\tAppliesTo: []string{\"*\"},\n\t},\n}\n\nfunc TestRuleCreate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --path flag\",\n\t\t\tArgs:      \"\",\n\t\t\tWantError: \"error parsing arguments: required flag --path not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate internal server error\",\n\t\t\tArgs: fmt.Sprintf(\"--path %s\", rulePath),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusInternalServerError,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusInternalServerError),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"500 - Internal Server Error\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--path %s\", rulePath),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader((testutil.GenJSON(rule)))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Created account-level rule with ID %s\", ruleID),\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success with complex rule\",\n\t\t\tArgs: fmt.Sprintf(\"--path %s\", complexRulePath),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader((testutil.GenJSON(complexRule)))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Created account-level rule with ID %s\", complexRuleID),\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--path %s --json\", rulePath),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(rule))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(rule),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"create\"}, scenarios)\n}\n\nfunc TestRuleDelete(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --rule-id flag\",\n\t\t\tArgs:      \"\",\n\t\t\tWantError: \"error parsing arguments: required flag --rule-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate bad request\",\n\t\t\tArgs: \"--rule-id bar\",\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusBadRequest,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusBadRequest),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(`\n\t\t\t\t\t\t\t{\n    \t\t\t\t\t\t\t\"title\": \"invalid rule ID\",\n    \t\t\t\t\t\t\t\"status\": 400\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t`))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"400 - Bad Request\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--rule-id %s\", ruleID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusNoContent,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusNoContent),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Deleted account-level rule with id: %s\", ruleID),\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--rule-id %s --json\", ruleID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusNoContent,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusNoContent),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.JSON(`{\"id\": %q, \"deleted\": true}`, ruleID),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"delete\"}, scenarios)\n}\n\nfunc TestRuleGet(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --rule-id flag\",\n\t\t\tArgs:      \"\",\n\t\t\tWantError: \"error parsing arguments: required flag --rule-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate bad request\",\n\t\t\tArgs: \"--rule-id baz\",\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusBadRequest,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusBadRequest),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(`\n\t\t\t\t\t\t\t{\n    \t\t\t\t\t\t\t\"title\": \"invalid Rule ID\",\n    \t\t\t\t\t\t\t\"status\": 400\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t`))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"400 - Bad Request\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--rule-id %s\", ruleID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader((testutil.GenJSON(rule)))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: ruleString,\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--rule-id %s --json\", ruleID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader((testutil.GenJSON(rule)))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(rule),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"get\"}, scenarios)\n}\n\nfunc TestRuleList(t *testing.T) {\n\trulesObject := rules.Rules{\n\t\tData: []rules.Rule{\n\t\t\t{\n\t\t\t\tCreatedAt:   testutil.Date,\n\t\t\t\tDescription: ruleDescription,\n\t\t\t\tEnabled:     ruleEnabled,\n\t\t\t\tRuleID:      ruleID,\n\t\t\t\tActions: []rules.Action{\n\t\t\t\t\t{\n\t\t\t\t\t\tType: ruleAction,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tType: ruleType,\n\t\t\t\tScope: rules.Scope{\n\t\t\t\t\tType:      string(scope.ScopeTypeAccount),\n\t\t\t\t\tAppliesTo: []string{\"*\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tCreatedAt:   testutil.Date,\n\t\t\t\tDescription: ruleDescription + \"2\",\n\t\t\t\tEnabled:     ruleEnabled,\n\t\t\t\tRuleID:      ruleID + \"2\",\n\t\t\t\tActions: []rules.Action{\n\t\t\t\t\t{\n\t\t\t\t\t\tType: ruleAction,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tType: ruleType,\n\t\t\t\tScope: rules.Scope{\n\t\t\t\t\tType:      string(scope.ScopeTypeAccount),\n\t\t\t\t\tAppliesTo: []string{\"*\"},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tMeta: rules.MetaRules{},\n\t}\n\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName: \"validate internal server error\",\n\t\t\tArgs: \"\",\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusInternalServerError,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusInternalServerError),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"500 - Internal Server Error\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success (zero account-level Rules)\",\n\t\t\tArgs: \"\",\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(rules.Rules{\n\t\t\t\t\t\t\tData: []rules.Rule{},\n\t\t\t\t\t\t\tMeta: rules.MetaRules{},\n\t\t\t\t\t\t}))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: zeroListRulesString,\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: \"\",\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(rulesObject))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: listRulesString,\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: \"--json\",\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(rulesObject))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(rulesObject),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"list\"}, scenarios)\n}\n\nfunc TestRuleUpdate(t *testing.T) {\n\truleObject := rules.Rule{\n\t\tCreatedAt:   testutil.Date,\n\t\tDescription: ruleDescription,\n\t\tRuleID:      ruleID,\n\t}\n\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --rule-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--path %s\", rulePath),\n\t\t\tWantError: \"error parsing arguments: required flag --rule-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --path flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--rule-id %s\", ruleID),\n\t\t\tWantError: \"error parsing arguments: required flag --path not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--rule-id %s --path %s\", ruleID, rulePath),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(ruleObject))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Updated account-level rule with id: %s\", ruleID),\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--rule-id %s --path %s --json\", ruleID, rulePath),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(rule))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(rule),\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"update\"}, scenarios)\n}\n\nvar listRulesString = strings.TrimSpace(`\nID       Action  Description        Enabled  Type     Scope    Updated At                     Created At\nsomeID   allow   Utility requests   true     request  account  0001-01-01 00:00:00 +0000 UTC  2021-06-15 23:00:00 +0000 UTC\nsomeID2  allow   Utility requests2  true     request  account  0001-01-01 00:00:00 +0000 UTC  2021-06-15 23:00:00 +0000 UTC\n`) + \"\\n\"\n\nvar zeroListRulesString = strings.TrimSpace(`\nID  Action  Description  Enabled  Type  Scope  Updated At  Created At\n`) + \"\\n\"\n\nvar ruleString = strings.TrimSpace(`\nID: someID\nAction: allow\nDescription: Utility requests\nEnabled: true\nType: request\nScope: account\nUpdated (UTC): 0001-01-01 00:00\nCreated (UTC): 2021-06-15 23:00\n`)\n"
  },
  {
    "path": "pkg/commands/ngwaf/rule/testdata/test_complex_rule.json",
    "content": "{\n    \"type\": \"request\",\n    \"description\": \"complex_test\",\n    \"enabled\": true,\n    \"expires_at\": \"\",\n    \"group_operator\": \"all\",\n    \"conditions\": [\n        {\n            \"type\": \"single\",\n            \"field\": \"ip\",\n            \"operator\": \"equals\",\n            \"value\": \"1.2.3.4\"\n        },\n        {\n            \"type\": \"single\",\n            \"field\": \"country\",\n            \"operator\": \"equals\",\n            \"value\": \"AE\"\n        },\n        {\n            \"type\": \"group\",\n            \"group_operator\": \"all\",\n            \"conditions\": [\n                {\n                    \"type\": \"single\",\n                    \"field\": \"ip\",\n                    \"operator\": \"equals\",\n                    \"value\": \"2.4.5.6\"\n                },\n                {\n                    \"type\": \"single\",\n                    \"field\": \"country\",\n                    \"operator\": \"equals\",\n                    \"value\": \"AD\"\n                }\n            ]\n        },\n        {\n            \"type\": \"group\",\n            \"group_operator\": \"all\",\n            \"conditions\": [\n                {\n                    \"type\": \"single\",\n                    \"field\": \"domain\",\n                    \"operator\": \"equals\",\n                    \"value\": \"test.com\"\n                },\n                {\n                    \"type\": \"single\",\n                    \"field\": \"agent_name\",\n                    \"operator\": \"equals\",\n                    \"value\": \"test\"\n                }\n            ]\n        },\n        {\n            \"type\": \"group\",\n            \"group_operator\": \"all\",\n            \"conditions\": [\n                {\n                    \"type\": \"single\",\n                    \"field\": \"ip\",\n                    \"operator\": \"equals\",\n                    \"value\": \"0.0.0.0\"\n                },\n                {\n                    \"type\": \"multival\",\n                    \"field\": \"request_header\",\n                    \"operator\": \"exists\",\n                    \"group_operator\": \"all\",\n                    \"conditions\": [\n                        {\n                            \"type\": \"single\",\n                            \"field\": \"name\",\n                            \"operator\": \"equals\",\n                            \"value\": \"x-something\"\n                        },\n                        {\n                            \"type\": \"single\",\n                            \"field\": \"value_string\",\n                            \"operator\": \"equals\",\n                            \"value\": \"abc-123\"\n                        }\n                    ]\n                }\n            ]\n        }\n    ],\n    \"actions\": [\n        {\n            \"type\": \"allow\"\n        }\n    ],\n    \"request_logging\": \"none\"\n}"
  },
  {
    "path": "pkg/commands/ngwaf/rule/testdata/test_rule.json",
    "content": "{\n    \"type\": \"request\",\n    \"enabled\": true,\n    \"description\": \"Utility requests\",\n    \"group_operator\": \"all\",\n    \"request_logging\": \"sampled\",\n    \"conditions\": [\n        {\n            \"type\": \"single\",\n            \"field\": \"path\",\n            \"operator\": \"equals\",\n            \"value\": \"/echo.json\"\n        }\n    ],\n    \"actions\": [\n        {\n            \"type\": \"allow\"\n        }\n    ]\n}"
  },
  {
    "path": "pkg/commands/ngwaf/rule/update.go",
    "content": "package rule\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"path/filepath\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/rules\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/scope\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// UpdateCommand calls the Fastly API to update an account-level rule.\ntype UpdateCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tpath   string\n\truleID string\n}\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"update\", \"Update a workspace\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"rule-id\", \"Rule ID\").Required().StringVar(&c.ruleID)\n\tc.CmdClause.Flag(\"path\", \"Path to a json file that contains the rule schema.\").Required().StringVar(&c.path)\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *UpdateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\tvar err error\n\n\trule := &rules.Rule{}\n\tif c.path != \"\" {\n\t\tpath, err := filepath.Abs(c.path)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"error parsing path '%s': %q\", c.path, err)\n\t\t}\n\n\t\tjsonFile, err := os.Open(path)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"error reading path '%s': %q\", c.path, err)\n\t\t}\n\t\tdefer jsonFile.Close()\n\n\t\tbyteValue, err := io.ReadAll(jsonFile)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to read json file: %v\", err)\n\t\t}\n\n\t\tif err := json.Unmarshal(byteValue, rule); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to unmarshal json data: %v\", err)\n\t\t}\n\t}\n\n\tinput := &rules.UpdateInput{\n\t\tRuleID:             &c.ruleID,\n\t\tActions:            []*rules.UpdateAction{},\n\t\tConditions:         []*rules.UpdateCondition{},\n\t\tDescription:        &rule.Description,\n\t\tGroupConditions:    []*rules.UpdateGroupCondition{},\n\t\tMultivalConditions: []*rules.UpdateMultivalCondition{},\n\t\tEnabled:            &rule.Enabled,\n\t\tType:               &rule.Type,\n\t\tGroupOperator:      &rule.GroupOperator,\n\t\tRequestLogging:     &rule.RequestLogging,\n\t\tScope: &scope.Scope{\n\t\t\tType:      scope.ScopeTypeAccount,\n\t\t\tAppliesTo: []string{\"*\"},\n\t\t},\n\t}\n\n\tfor _, action := range rule.Actions {\n\t\tinput.Actions = append(input.Actions, &rules.UpdateAction{\n\t\t\tAllowInteractive: action.AllowInteractive,\n\t\t\tDeceptionType:    &action.DeceptionType,\n\t\t\tRedirectURL:      &action.RedirectURL,\n\t\t\tResponseCode:     &action.ResponseCode,\n\t\t\tSignal:           &action.Signal,\n\t\t\tType:             &action.Type,\n\t\t})\n\t}\n\n\tif rule.RateLimit != nil {\n\t\tinput.RateLimit = &rules.UpdateRateLimit{\n\t\t\tClientIdentifiers: []*rules.UpdateClientIdentifier{},\n\t\t\tDuration:          &rule.RateLimit.Duration,\n\t\t\tInterval:          &rule.RateLimit.Interval,\n\t\t\tSignal:            &rule.RateLimit.Signal,\n\t\t\tThreshold:         &rule.RateLimit.Threshold,\n\t\t}\n\n\t\tfor _, rateLimit := range rule.RateLimit.ClientIdentifiers {\n\t\t\tinput.RateLimit.ClientIdentifiers = append(input.RateLimit.ClientIdentifiers, &rules.UpdateClientIdentifier{\n\t\t\t\tKey:  &rateLimit.Key,\n\t\t\t\tName: &rateLimit.Name,\n\t\t\t\tType: &rateLimit.Type,\n\t\t\t})\n\t\t}\n\t}\n\n\tfor _, jsonCondition := range rule.Conditions {\n\t\tswitch jsonCondition.Type {\n\t\tcase \"single\":\n\t\t\tif sc, ok := jsonCondition.Fields.(rules.SingleCondition); ok {\n\t\t\t\tinput.Conditions = append(input.Conditions, &rules.UpdateCondition{\n\t\t\t\t\tField:    &sc.Field,\n\t\t\t\t\tOperator: &sc.Operator,\n\t\t\t\t\tValue:    &sc.Value,\n\t\t\t\t})\n\t\t\t} else {\n\t\t\t\treturn fmt.Errorf(\"expected SingleCondition, got %T\", jsonCondition.Fields)\n\t\t\t}\n\t\tcase \"group\":\n\t\t\tif gc, ok := jsonCondition.Fields.(rules.GroupCondition); ok {\n\t\t\t\tparsedGroupCondition := &rules.UpdateGroupCondition{\n\t\t\t\t\tGroupOperator: &gc.GroupOperator,\n\t\t\t\t\tConditions:    []*rules.UpdateCondition{},\n\t\t\t\t}\n\t\t\t\tfor _, groupCondition := range gc.Conditions {\n\t\t\t\t\tswitch groupCondition.Type {\n\t\t\t\t\tcase \"single\":\n\t\t\t\t\t\tif gsc, ok := groupCondition.Fields.(rules.Condition); ok {\n\t\t\t\t\t\t\tparsedGroupCondition.Conditions = append(parsedGroupCondition.Conditions, &rules.UpdateCondition{\n\t\t\t\t\t\t\t\tField:    &gsc.Field,\n\t\t\t\t\t\t\t\tOperator: &gsc.Operator,\n\t\t\t\t\t\t\t\tValue:    &gsc.Value,\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\treturn fmt.Errorf(\"expected Condition, got %T\", groupCondition.Fields)\n\t\t\t\t\t\t}\n\t\t\t\t\tcase \"multival\":\n\t\t\t\t\t\tif gmvc, ok := groupCondition.Fields.(rules.MultivalCondition); ok {\n\t\t\t\t\t\t\tupdateMultivalCondition := &rules.UpdateMultivalCondition{\n\t\t\t\t\t\t\t\tField:         &gmvc.Field,\n\t\t\t\t\t\t\t\tOperator:      &gmvc.Operator,\n\t\t\t\t\t\t\t\tGroupOperator: &gmvc.GroupOperator,\n\t\t\t\t\t\t\t\tConditions:    []*rules.UpdateConditionMult{},\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tfor _, groupMultivalSingleCondition := range gmvc.Conditions {\n\t\t\t\t\t\t\t\tupdateMultivalCondition.Conditions = append(updateMultivalCondition.Conditions, &rules.UpdateConditionMult{\n\t\t\t\t\t\t\t\t\tField:    &groupMultivalSingleCondition.Field,\n\t\t\t\t\t\t\t\t\tOperator: &groupMultivalSingleCondition.Operator,\n\t\t\t\t\t\t\t\t\tValue:    &groupMultivalSingleCondition.Value,\n\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tparsedGroupCondition.MultivalConditions = append(parsedGroupCondition.MultivalConditions, updateMultivalCondition)\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\treturn fmt.Errorf(\"expected MultivalCondition, got %T\", groupCondition.Fields)\n\t\t\t\t\t\t}\n\n\t\t\t\t\tdefault:\n\t\t\t\t\t\treturn fmt.Errorf(\"unknown condition type: %s\", groupCondition.Type)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tinput.GroupConditions = append(input.GroupConditions, parsedGroupCondition)\n\t\t\t} else {\n\t\t\t\treturn fmt.Errorf(\"expected GroupCondition, got %T\", jsonCondition.Fields)\n\t\t\t}\n\t\tcase \"multival\":\n\t\t\tif mvc, ok := jsonCondition.Fields.(rules.UpdateMultivalCondition); ok {\n\t\t\t\tparsedMultiValCondition := &rules.UpdateMultivalCondition{\n\t\t\t\t\tField:         mvc.Field,\n\t\t\t\t\tGroupOperator: mvc.GroupOperator,\n\t\t\t\t\tOperator:      mvc.Operator,\n\t\t\t\t\tConditions:    []*rules.UpdateConditionMult{},\n\t\t\t\t}\n\t\t\t\tfor _, multiSingleCondition := range mvc.Conditions {\n\t\t\t\t\tparsedMultiValCondition.Conditions = append(parsedMultiValCondition.Conditions, &rules.UpdateConditionMult{\n\t\t\t\t\t\tField:    multiSingleCondition.Field,\n\t\t\t\t\t\tOperator: multiSingleCondition.Operator,\n\t\t\t\t\t\tValue:    multiSingleCondition.Value,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t\tinput.MultivalConditions = append(input.MultivalConditions, parsedMultiValCondition)\n\t\t\t} else {\n\t\t\t\treturn fmt.Errorf(\"expected MultivalCondition, got %T\", jsonCondition.Fields)\n\t\t\t}\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"unknown condition type: %s\", jsonCondition.Type)\n\t\t}\n\t}\n\n\tfc, ok := c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tdata, err := rules.Update(context.TODO(), fc, input)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, data); ok {\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Updated account-level rule with id: %s\", data.RuleID)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/signallist/create.go",
    "content": "package signallist\n\nimport (\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/scope\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/ngwaf/ngwaflist\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// CreateCommand calls the Fastly API to create account-level signal lists.\ntype CreateCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tentries string\n\tname    string\n\n\t// Optional.\n\tdescription argparser.OptionalString\n}\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"create\", \"Create an account-level signal list\").Alias(\"add\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"entries\", \"Entries for the list. Can either be a comma separated list or a path to a file.\").Required().StringVar(&c.entries)\n\tc.CmdClause.Flag(\"name\", \"User submitted display name of a list.\").Required().StringVar(&c.name)\n\n\t// Optional.\n\tc.CmdClause.Flag(\"description\", \"User submitted description of the list.\").Action(c.description.Set).StringVar(&c.description.Value)\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *CreateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\tinput := ngwaflist.ListCreateInput{\n\t\tCommandScope: scope.ScopeTypeAccount,\n\t\tDescription:  c.description,\n\t\tEntries:      c.entries,\n\t\tName:         c.name,\n\t\tType:         \"signal\",\n\t\tWorkspaceID:  nil,\n\t}\n\n\tvar ok bool\n\tinput.FC, ok = c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tdata, err := ngwaflist.ListCreate(input)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, data); ok {\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Created Account Signal List '%s' (list id: %s)\", data.Name, data.ListID)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/signallist/delete.go",
    "content": "package signallist\n\nimport (\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/scope\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/ngwaf/ngwaflist\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DeleteCommand calls the Fastly API to delete an account-level signal list.\ntype DeleteCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tlistID string\n}\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\n\tc.CmdClause = parent.Command(\"delete\", \"Delete an account signal list\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"list-id\", \"List ID\").Required().StringVar(&c.listID)\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DeleteCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tinput := ngwaflist.ListDeleteInput{\n\t\tCommandScope: scope.ScopeTypeAccount,\n\t\tListID:       c.listID,\n\t\tWorkspaceID:  nil,\n\t}\n\n\tvar ok bool\n\tinput.FC, ok = c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\terr := ngwaflist.ListDelete(input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif c.JSONOutput.Enabled {\n\t\to := struct {\n\t\t\tID      string `json:\"id\"`\n\t\t\tDeleted bool   `json:\"deleted\"`\n\t\t}{\n\t\t\tc.listID,\n\t\t\ttrue,\n\t\t}\n\t\t_, err := c.WriteJSON(out, o)\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Deleted Account Signal List (list id: %s)\", c.listID)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/signallist/doc.go",
    "content": "// Package signallist contains commands to inspect and manipulate NGWAF account-level signal lists.\npackage signallist\n"
  },
  {
    "path": "pkg/commands/ngwaf/signallist/get.go",
    "content": "package signallist\n\nimport (\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/ngwaf/ngwaflist\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/scope\"\n)\n\n// GetCommand calls the Fastly API to get an account-level signal list.\ntype GetCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tlistID string\n}\n\n// NewGetCommand returns a usable command registered under the parent.\nfunc NewGetCommand(parent argparser.Registerer, g *global.Data) *GetCommand {\n\tc := GetCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\n\tc.CmdClause = parent.Command(\"get\", \"Get an account-level signal list\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"list-id\", \"List ID\").Required().StringVar(&c.listID)\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *GetCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tinput := ngwaflist.ListGetInput{\n\t\tCommandScope: scope.ScopeTypeAccount,\n\t\tListID:       c.listID,\n\t\tWorkspaceID:  nil,\n\t}\n\n\tvar ok bool\n\tinput.FC, ok = c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tlist, err := ngwaflist.ListGet(input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, list); ok {\n\t\treturn err\n\t}\n\n\ttext.PrintList(out, list)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/signallist/list.go",
    "content": "package signallist\n\nimport (\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/ngwaf/ngwaflist\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/scope\"\n)\n\n// ListCommand calls the Fastly API to list all signal lists for your API token.\n\ntype ListCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n}\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\n\tc.CmdClause = parent.Command(\"list\", \"List all signal lists for your account\")\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *ListCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tinput := ngwaflist.ListListInput{\n\t\tCommandScope: scope.ScopeTypeAccount,\n\t\tType:         \"signal\",\n\t\tWorkspaceID:  nil,\n\t}\n\n\tvar ok bool\n\tinput.FC, ok = c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tlists, err := ngwaflist.ListList(input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, *lists); ok {\n\t\treturn err\n\t}\n\n\ttext.PrintListTbl(out, lists.Data)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/signallist/root.go",
    "content": "package signallist\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"signal-list\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manage NGWAF Account Signal Lists\")\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/signallist/signallist_test.go",
    "content": "package signallist_test\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\t\"testing\"\n\n\troot \"github.com/fastly/cli/pkg/commands/ngwaf\"\n\tsub \"github.com/fastly/cli/pkg/commands/ngwaf/signallist\"\n\tfstfmt \"github.com/fastly/cli/pkg/fmt\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/lists\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/scope\"\n)\n\nconst (\n\tlistID          = \"someListID\"\n\tlistDescription = \"NGWAFCLIList\"\n\tlistEntries     = \"BHH\"\n\tlistType        = \"signal\"\n\tlistName        = \"listName\"\n)\n\nvar stringlist = lists.List{\n\tListID:      listID,\n\tDescription: listDescription,\n\tEntries:     []string{listEntries},\n\tName:        listName,\n\tType:        listType,\n\tCreatedAt:   testutil.Date,\n\tUpdatedAt:   testutil.Date,\n\tScope: lists.Scope{\n\t\tType: string(scope.ScopeTypeAccount),\n\t},\n}\n\nvar stringlist2 = lists.List{\n\tListID:      listID + \"2\",\n\tDescription: listDescription + \"2\",\n\tEntries:     []string{listEntries},\n\tName:        listName + \"2\",\n\tType:        listType,\n\tCreatedAt:   testutil.Date,\n\tUpdatedAt:   testutil.Date,\n\tScope: lists.Scope{\n\t\tType: string(scope.ScopeTypeAccount),\n\t},\n}\n\nfunc TestSignalListCreate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --entries flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--name %s\", listName),\n\t\t\tWantError: \"error parsing arguments: required flag --entries not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --name flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--entries %s\", listEntries),\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate internal server error\",\n\t\t\tArgs: fmt.Sprintf(\"--entries %s --name %s\", listEntries, listName),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusInternalServerError,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusInternalServerError),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"500 - Internal Server Error\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--entries %s --name %s\", listEntries, listName),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader((testutil.GenJSON(stringlist)))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Created Account Signal List '%s' (list id: %s)\", listName, listID),\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--entries %s --name %s --json\", listEntries, listName),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(stringlist))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(stringlist),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"create\"}, scenarios)\n}\n\nfunc TestSignalListDelete(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --list-id flag\",\n\t\t\tArgs:      \"\",\n\t\t\tWantError: \"error parsing arguments: required flag --list-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate bad request\",\n\t\t\tArgs: \"--list-id bar\",\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusBadRequest,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusBadRequest),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(`\n\t\t\t\t\t\t\t{\n    \t\t\t\t\t\t\t\"title\": \"invalid List ID\",\n    \t\t\t\t\t\t\t\"status\": 400\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t`))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"400 - Bad Request\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--list-id %s\", listID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusNoContent,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusNoContent),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Deleted Account Signal List (list id: %s)\", listID),\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--list-id %s --json\", listID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusNoContent,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusNoContent),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.JSON(`{\"id\": %q, \"deleted\": true}`, listID),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"delete\"}, scenarios)\n}\n\nfunc TestSignalListGet(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --list-id flag\",\n\t\t\tArgs:      \"\",\n\t\t\tWantError: \"error parsing arguments: required flag --list-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate bad request\",\n\t\t\tArgs: \"--list-id baz\",\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusBadRequest,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusBadRequest),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(`\n\t\t\t\t\t\t\t{\n    \t\t\t\t\t\t\t\"title\": \"invalid List ID\",\n    \t\t\t\t\t\t\t\"status\": 400\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t`))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"400 - Bad Request\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--list-id %s\", listID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader((testutil.GenJSON(stringlist)))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: listString,\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--list-id %s --json\", listID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader((testutil.GenJSON(stringlist)))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(stringlist),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"get\"}, scenarios)\n}\n\nfunc TestSignalListList(t *testing.T) {\n\tlistsObject := lists.Lists{\n\t\tData: []lists.List{\n\t\t\tstringlist,\n\t\t\tstringlist2,\n\t\t},\n\t\tMeta: lists.MetaLists{},\n\t}\n\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName: \"validate internal server error\",\n\t\t\tArgs: \"\",\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusInternalServerError,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusInternalServerError),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"500 - Internal Server Error\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success (zero workspaces)\",\n\t\t\tArgs: \"\",\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(lists.Lists{\n\t\t\t\t\t\t\tData: []lists.List{},\n\t\t\t\t\t\t\tMeta: lists.MetaLists{},\n\t\t\t\t\t\t}))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: zeroListString,\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: \"\",\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(listsObject))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: listListsString,\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: \"--json\",\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(listsObject))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(listsObject),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"list\"}, scenarios)\n}\n\nfunc TestSignalListUpdate(t *testing.T) {\n\tupdatelist := lists.List{\n\t\tListID:      listID,\n\t\tDescription: listDescription + \"2\",\n\t\tEntries:     []string{listEntries + \"2\"},\n\t\tName:        listName,\n\t\tType:        listType,\n\t\tCreatedAt:   testutil.Date,\n\t\tUpdatedAt:   testutil.Date,\n\t\tScope: lists.Scope{\n\t\t\tType: string(scope.ScopeTypeAccount),\n\t\t},\n\t}\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --list-id flag\",\n\t\t\tArgs:      \"\",\n\t\t\tWantError: \"error parsing arguments: required flag --list-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--list-id %s --description %s --entries %s\", listID, listDescription+\"2\", listEntries+\"2\"),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(updatelist))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Updated Account Signal List '%s' (list id: %s)\", listName, listID),\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--list-id %s --description %s --entries %s --json\", listID, listDescription+\"2\", listEntries+\"2\"),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(updatelist))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(updatelist),\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"update\"}, scenarios)\n}\n\nvar listListsString = strings.TrimSpace(`\nID           Name       Description    Type    Scope    Entries  Updated At                     Created At\nsomeListID   listName   NGWAFCLIList   signal  account  BHH      2021-06-15 23:00:00 +0000 UTC  2021-06-15 23:00:00 +0000 UTC\nsomeListID2  listName2  NGWAFCLIList2  signal  account  BHH      2021-06-15 23:00:00 +0000 UTC  2021-06-15 23:00:00 +0000 UTC\n`) + \"\\n\"\n\nvar zeroListString = strings.TrimSpace(`\nID  Name  Description  Type  Scope  Entries  Updated At  Created At\n`) + \"\\n\"\n\nvar listString = strings.TrimSpace(`\nID: someListID\nName: listName\nDescription: NGWAFCLIList\nType: signal\nEntries: BHH\nScope: account\nUpdated (UTC): 2021-06-15 23:00\n`)\n"
  },
  {
    "path": "pkg/commands/ngwaf/signallist/update.go",
    "content": "package signallist\n\nimport (\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/scope\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/ngwaf/ngwaflist\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// UpdateCommand calls the Fastly API to update an account signal list.\ntype UpdateCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tlistID string\n\n\t// Optional.\n\tdescription argparser.OptionalString\n\tentries     argparser.OptionalString\n}\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"update\", \"Update an account-level signal list\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"list-id\", \"List ID\").Required().StringVar(&c.listID)\n\n\t// Optional.\n\tc.CmdClause.Flag(\"description\", \"User submitted description of the list.\").Action(c.description.Set).StringVar(&c.description.Value)\n\tc.CmdClause.Flag(\"entries\", \"Entries for the list. Can either be a comma separated list or a path to a file.\").Action(c.entries.Set).StringVar(&c.entries.Value)\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *UpdateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\tinput := ngwaflist.ListUpdateInput{\n\t\tCommandScope: scope.ScopeTypeAccount,\n\t\tDescription:  c.description,\n\t\tEntries:      c.entries,\n\t\tListID:       c.listID,\n\t\tWorkspaceID:  nil,\n\t}\n\n\tvar ok bool\n\tinput.FC, ok = c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tdata, err := ngwaflist.ListUpdate(input)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, data); ok {\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Updated Account Signal List '%s' (list id: %s)\", data.Name, data.ListID)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/stringlist/create.go",
    "content": "package stringlist\n\nimport (\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/scope\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/ngwaf/ngwaflist\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// CreateCommand calls the Fastly API to create account-level string lists.\ntype CreateCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tentries string\n\tname    string\n\n\t// Optional.\n\tdescription argparser.OptionalString\n}\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"create\", \"Create an account-level string list\").Alias(\"add\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"entries\", \"Entries for the list. Can either be a comma separated list or a path to a file.\").Required().StringVar(&c.entries)\n\tc.CmdClause.Flag(\"name\", \"User submitted display name of a list.\").Required().StringVar(&c.name)\n\n\t// Optional.\n\tc.CmdClause.Flag(\"description\", \"User submitted description of the list.\").Action(c.description.Set).StringVar(&c.description.Value)\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *CreateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\tinput := ngwaflist.ListCreateInput{\n\t\tCommandScope: scope.ScopeTypeAccount,\n\t\tDescription:  c.description,\n\t\tEntries:      c.entries,\n\t\tName:         c.name,\n\t\tType:         \"string\",\n\t\tWorkspaceID:  nil,\n\t}\n\n\tvar ok bool\n\tinput.FC, ok = c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tdata, err := ngwaflist.ListCreate(input)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, data); ok {\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Created Account String List '%s' (list id: %s)\", data.Name, data.ListID)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/stringlist/delete.go",
    "content": "package stringlist\n\nimport (\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/scope\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/ngwaf/ngwaflist\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DeleteCommand calls the Fastly API to delete an account-level string list.\ntype DeleteCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tlistID string\n}\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\n\tc.CmdClause = parent.Command(\"delete\", \"Delete an account string list\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"list-id\", \"List ID\").Required().StringVar(&c.listID)\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DeleteCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tinput := ngwaflist.ListDeleteInput{\n\t\tCommandScope: scope.ScopeTypeAccount,\n\t\tListID:       c.listID,\n\t\tWorkspaceID:  nil,\n\t}\n\n\tvar ok bool\n\tinput.FC, ok = c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\terr := ngwaflist.ListDelete(input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif c.JSONOutput.Enabled {\n\t\to := struct {\n\t\t\tID      string `json:\"id\"`\n\t\t\tDeleted bool   `json:\"deleted\"`\n\t\t}{\n\t\t\tc.listID,\n\t\t\ttrue,\n\t\t}\n\t\t_, err := c.WriteJSON(out, o)\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Deleted Account String List (list id: %s)\", c.listID)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/stringlist/doc.go",
    "content": "// Package stringlist contains commands to inspect and manipulate NGWAF account-level string lists.\npackage stringlist\n"
  },
  {
    "path": "pkg/commands/ngwaf/stringlist/get.go",
    "content": "package stringlist\n\nimport (\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/ngwaf/ngwaflist\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/scope\"\n)\n\n// GetCommand calls the Fastly API to get an account-level string list.\ntype GetCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tlistID string\n}\n\n// NewGetCommand returns a usable command registered under the parent.\nfunc NewGetCommand(parent argparser.Registerer, g *global.Data) *GetCommand {\n\tc := GetCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\n\tc.CmdClause = parent.Command(\"get\", \"Get an account-level string list\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"list-id\", \"List ID\").Required().StringVar(&c.listID)\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *GetCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tinput := ngwaflist.ListGetInput{\n\t\tCommandScope: scope.ScopeTypeAccount,\n\t\tListID:       c.listID,\n\t\tWorkspaceID:  nil,\n\t}\n\n\tvar ok bool\n\tinput.FC, ok = c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tlist, err := ngwaflist.ListGet(input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, list); ok {\n\t\treturn err\n\t}\n\n\ttext.PrintList(out, list)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/stringlist/list.go",
    "content": "package stringlist\n\nimport (\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/ngwaf/ngwaflist\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/scope\"\n)\n\n// ListCommand calls the Fastly API to list all string lists for your API token.\n\ntype ListCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n}\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\n\tc.CmdClause = parent.Command(\"list\", \"List all string lists for your account\")\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *ListCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tinput := ngwaflist.ListListInput{\n\t\tCommandScope: scope.ScopeTypeAccount,\n\t\tType:         \"string\",\n\t\tWorkspaceID:  nil,\n\t}\n\n\tvar ok bool\n\tinput.FC, ok = c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tlists, err := ngwaflist.ListList(input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, *lists); ok {\n\t\treturn err\n\t}\n\n\ttext.PrintListTbl(out, lists.Data)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/stringlist/root.go",
    "content": "package stringlist\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"string-list\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manage NGWAF Account String Lists\")\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/stringlist/stringlist_test.go",
    "content": "package stringlist_test\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\t\"testing\"\n\n\troot \"github.com/fastly/cli/pkg/commands/ngwaf\"\n\tsub \"github.com/fastly/cli/pkg/commands/ngwaf/stringlist\"\n\tfstfmt \"github.com/fastly/cli/pkg/fmt\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/lists\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/scope\"\n)\n\nconst (\n\tlistID          = \"someListID\"\n\tlistDescription = \"NGWAFCLIList\"\n\tlistEntries     = \"1.0.0.0\"\n\tlistType        = \"string\"\n\tlistName        = \"listName\"\n)\n\nvar stringlist = lists.List{\n\tListID:      listID,\n\tDescription: listDescription,\n\tEntries:     []string{listEntries},\n\tName:        listName,\n\tType:        listType,\n\tCreatedAt:   testutil.Date,\n\tUpdatedAt:   testutil.Date,\n\tScope: lists.Scope{\n\t\tType: string(scope.ScopeTypeAccount),\n\t},\n}\n\nvar stringlist2 = lists.List{\n\tListID:      listID + \"2\",\n\tDescription: listDescription + \"2\",\n\tEntries:     []string{listEntries},\n\tName:        listName + \"2\",\n\tType:        listType,\n\tCreatedAt:   testutil.Date,\n\tUpdatedAt:   testutil.Date,\n\tScope: lists.Scope{\n\t\tType: string(scope.ScopeTypeAccount),\n\t},\n}\n\nfunc TestStringListCreate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --entries flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--name %s\", listName),\n\t\t\tWantError: \"error parsing arguments: required flag --entries not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --name flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--entries %s\", listEntries),\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate internal server error\",\n\t\t\tArgs: fmt.Sprintf(\"--entries %s --name %s\", listEntries, listName),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusInternalServerError,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusInternalServerError),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"500 - Internal Server Error\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--entries %s --name %s\", listEntries, listName),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader((testutil.GenJSON(stringlist)))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Created Account String List '%s' (list id: %s)\", listName, listID),\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--entries %s --name %s --json\", listEntries, listName),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(stringlist))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(stringlist),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"create\"}, scenarios)\n}\n\nfunc TestStringListDelete(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --list-id flag\",\n\t\t\tArgs:      \"\",\n\t\t\tWantError: \"error parsing arguments: required flag --list-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate bad request\",\n\t\t\tArgs: \"--list-id bar\",\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusBadRequest,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusBadRequest),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(`\n\t\t\t\t\t\t\t{\n    \t\t\t\t\t\t\t\"title\": \"invalid List ID\",\n    \t\t\t\t\t\t\t\"status\": 400\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t`))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"400 - Bad Request\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--list-id %s\", listID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusNoContent,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusNoContent),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Deleted Account String List (list id: %s)\", listID),\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--list-id %s --json\", listID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusNoContent,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusNoContent),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.JSON(`{\"id\": %q, \"deleted\": true}`, listID),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"delete\"}, scenarios)\n}\n\nfunc TestStringListGet(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --list-id flag\",\n\t\t\tArgs:      \"\",\n\t\t\tWantError: \"error parsing arguments: required flag --list-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate bad request\",\n\t\t\tArgs: \"--list-id baz\",\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusBadRequest,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusBadRequest),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(`\n\t\t\t\t\t\t\t{\n    \t\t\t\t\t\t\t\"title\": \"invalid List ID\",\n    \t\t\t\t\t\t\t\"status\": 400\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t`))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"400 - Bad Request\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--list-id %s\", listID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader((testutil.GenJSON(stringlist)))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: listString,\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--list-id %s --json\", listID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader((testutil.GenJSON(stringlist)))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(stringlist),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"get\"}, scenarios)\n}\n\nfunc TestStringListList(t *testing.T) {\n\tlistsObject := lists.Lists{\n\t\tData: []lists.List{\n\t\t\tstringlist,\n\t\t\tstringlist2,\n\t\t},\n\t\tMeta: lists.MetaLists{},\n\t}\n\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName: \"validate internal server error\",\n\t\t\tArgs: \"\",\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusInternalServerError,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusInternalServerError),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"500 - Internal Server Error\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success (zero workspaces)\",\n\t\t\tArgs: \"\",\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(lists.Lists{\n\t\t\t\t\t\t\tData: []lists.List{},\n\t\t\t\t\t\t\tMeta: lists.MetaLists{},\n\t\t\t\t\t\t}))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: zeroListString,\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: \"\",\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(listsObject))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: listListsString,\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: \"--json\",\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(listsObject))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(listsObject),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"list\"}, scenarios)\n}\n\nfunc TestStringListUpdate(t *testing.T) {\n\tupdatelist := lists.List{\n\t\tListID:      listID,\n\t\tDescription: listDescription + \"2\",\n\t\tEntries:     []string{listEntries + \"2\"},\n\t\tName:        listName,\n\t\tType:        listType,\n\t\tCreatedAt:   testutil.Date,\n\t\tUpdatedAt:   testutil.Date,\n\t\tScope: lists.Scope{\n\t\t\tType: string(scope.ScopeTypeAccount),\n\t\t},\n\t}\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --list-id flag\",\n\t\t\tArgs:      \"\",\n\t\t\tWantError: \"error parsing arguments: required flag --list-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--list-id %s --description %s --entries %s\", listID, listDescription+\"2\", listEntries+\"2\"),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(updatelist))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Updated Account String List '%s' (list id: %s)\", listName, listID),\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--list-id %s --description %s --entries %s --json\", listID, listDescription+\"2\", listEntries+\"2\"),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(updatelist))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(updatelist),\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"update\"}, scenarios)\n}\n\nvar listListsString = strings.TrimSpace(`\nID           Name       Description    Type    Scope    Entries  Updated At                     Created At\nsomeListID   listName   NGWAFCLIList   string  account  1.0.0.0  2021-06-15 23:00:00 +0000 UTC  2021-06-15 23:00:00 +0000 UTC\nsomeListID2  listName2  NGWAFCLIList2  string  account  1.0.0.0  2021-06-15 23:00:00 +0000 UTC  2021-06-15 23:00:00 +0000 UTC\n`) + \"\\n\"\n\nvar zeroListString = strings.TrimSpace(`\nID  Name  Description  Type  Scope  Entries  Updated At  Created At\n`) + \"\\n\"\n\nvar listString = strings.TrimSpace(`\nID: someListID\nName: listName\nDescription: NGWAFCLIList\nType: string\nEntries: 1.0.0.0\nScope: account\nUpdated (UTC): 2021-06-15 23:00\n`)\n"
  },
  {
    "path": "pkg/commands/ngwaf/stringlist/update.go",
    "content": "package stringlist\n\nimport (\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/scope\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/ngwaf/ngwaflist\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// UpdateCommand calls the Fastly API to update an account string list.\ntype UpdateCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tlistID string\n\n\t// Optional.\n\tdescription argparser.OptionalString\n\tentries     argparser.OptionalString\n}\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"update\", \"Update an account-level string list\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"list-id\", \"List ID\").Required().StringVar(&c.listID)\n\n\t// Optional.\n\tc.CmdClause.Flag(\"description\", \"User submitted description of the list.\").Action(c.description.Set).StringVar(&c.description.Value)\n\tc.CmdClause.Flag(\"entries\", \"Entries for the list. Can either be a comma separated list or a path to a file.\").Action(c.entries.Set).StringVar(&c.entries.Value)\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *UpdateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\tinput := ngwaflist.ListUpdateInput{\n\t\tCommandScope: scope.ScopeTypeAccount,\n\t\tDescription:  c.description,\n\t\tEntries:      c.entries,\n\t\tListID:       c.listID,\n\t\tWorkspaceID:  nil,\n\t}\n\n\tvar ok bool\n\tinput.FC, ok = c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tdata, err := ngwaflist.ListUpdate(input)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, data); ok {\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Updated Account String List '%s' (list id: %s)\", data.Name, data.ListID)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/wildcardlist/create.go",
    "content": "package wildcardlist\n\nimport (\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/scope\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/ngwaf/ngwaflist\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// CreateCommand calls the Fastly API to create account-level wildcard lists.\ntype CreateCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tentries string\n\tname    string\n\n\t// Optional.\n\tdescription argparser.OptionalString\n}\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"create\", \"Create an account-level wildcard list\").Alias(\"add\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"entries\", \"Entries for the list. Can either be a comma separated list or a path to a file.\").Required().StringVar(&c.entries)\n\tc.CmdClause.Flag(\"name\", \"User submitted display name of a list.\").Required().StringVar(&c.name)\n\n\t// Optional.\n\tc.CmdClause.Flag(\"description\", \"User submitted description of the list.\").Action(c.description.Set).StringVar(&c.description.Value)\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *CreateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\tinput := ngwaflist.ListCreateInput{\n\t\tCommandScope: scope.ScopeTypeAccount,\n\t\tDescription:  c.description,\n\t\tEntries:      c.entries,\n\t\tName:         c.name,\n\t\tType:         \"wildcard\",\n\t\tWorkspaceID:  nil,\n\t}\n\n\tvar ok bool\n\tinput.FC, ok = c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tdata, err := ngwaflist.ListCreate(input)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, data); ok {\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Created Account Wildcard List '%s' (list id: %s)\", data.Name, data.ListID)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/wildcardlist/delete.go",
    "content": "package wildcardlist\n\nimport (\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/scope\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/ngwaf/ngwaflist\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DeleteCommand calls the Fastly API to delete an account-level wildcardip list.\ntype DeleteCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tlistID string\n}\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\n\tc.CmdClause = parent.Command(\"delete\", \"Delete an account wildcard list\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"list-id\", \"List ID\").Required().StringVar(&c.listID)\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DeleteCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tinput := ngwaflist.ListDeleteInput{\n\t\tCommandScope: scope.ScopeTypeAccount,\n\t\tListID:       c.listID,\n\t\tWorkspaceID:  nil,\n\t}\n\n\tvar ok bool\n\tinput.FC, ok = c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\terr := ngwaflist.ListDelete(input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif c.JSONOutput.Enabled {\n\t\to := struct {\n\t\t\tID      string `json:\"id\"`\n\t\t\tDeleted bool   `json:\"deleted\"`\n\t\t}{\n\t\t\tc.listID,\n\t\t\ttrue,\n\t\t}\n\t\t_, err := c.WriteJSON(out, o)\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Deleted Account Wildcard List (list id: %s)\", c.listID)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/wildcardlist/doc.go",
    "content": "// Package wildcardlist contains commands to inspect and manipulate NGWAF account-level wildcard lists.\npackage wildcardlist\n"
  },
  {
    "path": "pkg/commands/ngwaf/wildcardlist/get.go",
    "content": "package wildcardlist\n\nimport (\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/ngwaf/ngwaflist\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/scope\"\n)\n\n// GetCommand calls the Fastly API to get an account-level wildcard list.\ntype GetCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tlistID string\n}\n\n// NewGetCommand returns a usable command registered under the parent.\nfunc NewGetCommand(parent argparser.Registerer, g *global.Data) *GetCommand {\n\tc := GetCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\n\tc.CmdClause = parent.Command(\"get\", \"Get an account-level wildcard list\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"list-id\", \"List ID\").Required().StringVar(&c.listID)\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *GetCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tinput := ngwaflist.ListGetInput{\n\t\tCommandScope: scope.ScopeTypeAccount,\n\t\tListID:       c.listID,\n\t\tWorkspaceID:  nil,\n\t}\n\n\tvar ok bool\n\tinput.FC, ok = c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tlist, err := ngwaflist.ListGet(input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, list); ok {\n\t\treturn err\n\t}\n\n\ttext.PrintList(out, list)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/wildcardlist/list.go",
    "content": "package wildcardlist\n\nimport (\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/ngwaf/ngwaflist\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/scope\"\n)\n\n// ListCommand calls the Fastly API to list all wildcard lists for your API token.\n\ntype ListCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n}\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\n\tc.CmdClause = parent.Command(\"list\", \"List all wildcard lists for your account\")\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *ListCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tinput := ngwaflist.ListListInput{\n\t\tCommandScope: scope.ScopeTypeAccount,\n\t\tType:         \"wildcard\",\n\t\tWorkspaceID:  nil,\n\t}\n\n\tvar ok bool\n\tinput.FC, ok = c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tlists, err := ngwaflist.ListList(input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, *lists); ok {\n\t\treturn err\n\t}\n\n\ttext.PrintListTbl(out, lists.Data)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/wildcardlist/root.go",
    "content": "package wildcardlist\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"wildcard-list\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manage NGWAF Account Wildcard Lists\")\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/wildcardlist/update.go",
    "content": "package wildcardlist\n\nimport (\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/scope\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/ngwaf/ngwaflist\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// UpdateCommand calls the Fastly API to update an account wildcard list.\ntype UpdateCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tlistID string\n\n\t// Optional.\n\tdescription argparser.OptionalString\n\tentries     argparser.OptionalString\n}\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"update\", \"Update an account-level wildcard list\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"list-id\", \"List ID\").Required().StringVar(&c.listID)\n\n\t// Optional.\n\tc.CmdClause.Flag(\"description\", \"User submitted description of the list.\").Action(c.description.Set).StringVar(&c.description.Value)\n\tc.CmdClause.Flag(\"entries\", \"Entries for the list. Can either be a comma separated list or a path to a file.\").Action(c.entries.Set).StringVar(&c.entries.Value)\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *UpdateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\tinput := ngwaflist.ListUpdateInput{\n\t\tCommandScope: scope.ScopeTypeAccount,\n\t\tDescription:  c.description,\n\t\tEntries:      c.entries,\n\t\tListID:       c.listID,\n\t\tWorkspaceID:  nil,\n\t}\n\n\tvar ok bool\n\tinput.FC, ok = c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tdata, err := ngwaflist.ListUpdate(input)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, data); ok {\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Updated Account Wildcard List '%s' (list id: %s)\", data.Name, data.ListID)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/wildcardlist/wildcardlist_test.go",
    "content": "package wildcardlist_test\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\t\"testing\"\n\n\troot \"github.com/fastly/cli/pkg/commands/ngwaf\"\n\tsub \"github.com/fastly/cli/pkg/commands/ngwaf/wildcardlist\"\n\tfstfmt \"github.com/fastly/cli/pkg/fmt\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/lists\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/scope\"\n)\n\nconst (\n\tlistID          = \"someListID\"\n\tlistDescription = \"NGWAFCLIList\"\n\tlistEntries     = \"1.0.0.0\"\n\tlistType        = \"wildcard\"\n\tlistName        = \"listName\"\n)\n\nvar stringlist = lists.List{\n\tListID:      listID,\n\tDescription: listDescription,\n\tEntries:     []string{listEntries},\n\tName:        listName,\n\tType:        listType,\n\tCreatedAt:   testutil.Date,\n\tUpdatedAt:   testutil.Date,\n\tScope: lists.Scope{\n\t\tType: string(scope.ScopeTypeAccount),\n\t},\n}\n\nvar stringlist2 = lists.List{\n\tListID:      listID + \"2\",\n\tDescription: listDescription + \"2\",\n\tEntries:     []string{listEntries},\n\tName:        listName + \"2\",\n\tType:        listType,\n\tCreatedAt:   testutil.Date,\n\tUpdatedAt:   testutil.Date,\n\tScope: lists.Scope{\n\t\tType: string(scope.ScopeTypeAccount),\n\t},\n}\n\nfunc TestWildcardListCreate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --entries flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--name %s\", listName),\n\t\t\tWantError: \"error parsing arguments: required flag --entries not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --name flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--entries %s\", listEntries),\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate internal server error\",\n\t\t\tArgs: fmt.Sprintf(\"--entries %s --name %s\", listEntries, listName),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusInternalServerError,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusInternalServerError),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"500 - Internal Server Error\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--entries %s --name %s\", listEntries, listName),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader((testutil.GenJSON(stringlist)))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Created Account Wildcard List '%s' (list id: %s)\", listName, listID),\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--entries %s --name %s --json\", listEntries, listName),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(stringlist))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(stringlist),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"create\"}, scenarios)\n}\n\nfunc TestWildcardListDelete(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --list-id flag\",\n\t\t\tArgs:      \"\",\n\t\t\tWantError: \"error parsing arguments: required flag --list-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate bad request\",\n\t\t\tArgs: \"--list-id bar\",\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusBadRequest,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusBadRequest),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(`\n\t\t\t\t\t\t\t{\n    \t\t\t\t\t\t\t\"title\": \"invalid List ID\",\n    \t\t\t\t\t\t\t\"status\": 400\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t`))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"400 - Bad Request\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--list-id %s\", listID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusNoContent,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusNoContent),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Deleted Account Wildcard List (list id: %s)\", listID),\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--list-id %s --json\", listID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusNoContent,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusNoContent),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.JSON(`{\"id\": %q, \"deleted\": true}`, listID),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"delete\"}, scenarios)\n}\n\nfunc TestWildcardListGet(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --list-id flag\",\n\t\t\tArgs:      \"\",\n\t\t\tWantError: \"error parsing arguments: required flag --list-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate bad request\",\n\t\t\tArgs: \"--list-id baz\",\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusBadRequest,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusBadRequest),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(`\n\t\t\t\t\t\t\t{\n    \t\t\t\t\t\t\t\"title\": \"invalid List ID\",\n    \t\t\t\t\t\t\t\"status\": 400\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t`))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"400 - Bad Request\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--list-id %s\", listID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader((testutil.GenJSON(stringlist)))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: listString,\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--list-id %s --json\", listID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader((testutil.GenJSON(stringlist)))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(stringlist),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"get\"}, scenarios)\n}\n\nfunc TestWildcardListList(t *testing.T) {\n\tlistsObject := lists.Lists{\n\t\tData: []lists.List{\n\t\t\tstringlist,\n\t\t\tstringlist2,\n\t\t},\n\t\tMeta: lists.MetaLists{},\n\t}\n\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName: \"validate internal server error\",\n\t\t\tArgs: \"\",\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusInternalServerError,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusInternalServerError),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"500 - Internal Server Error\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success (zero workspaces)\",\n\t\t\tArgs: \"\",\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(lists.Lists{\n\t\t\t\t\t\t\tData: []lists.List{},\n\t\t\t\t\t\t\tMeta: lists.MetaLists{},\n\t\t\t\t\t\t}))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: zeroListString,\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: \"\",\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(listsObject))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: listListsString,\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: \"--json\",\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(listsObject))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(listsObject),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"list\"}, scenarios)\n}\n\nfunc TestWildcardListUpdate(t *testing.T) {\n\tupdatelist := lists.List{\n\t\tListID:      listID,\n\t\tDescription: listDescription + \"2\",\n\t\tEntries:     []string{listEntries + \"2\"},\n\t\tName:        listName,\n\t\tType:        listType,\n\t\tCreatedAt:   testutil.Date,\n\t\tUpdatedAt:   testutil.Date,\n\t\tScope: lists.Scope{\n\t\t\tType: string(scope.ScopeTypeAccount),\n\t\t},\n\t}\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --list-id flag\",\n\t\t\tArgs:      \"\",\n\t\t\tWantError: \"error parsing arguments: required flag --list-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--list-id %s --description %s --entries %s\", listID, listDescription+\"2\", listEntries+\"2\"),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(updatelist))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Updated Account Wildcard List '%s' (list id: %s)\", listName, listID),\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--list-id %s --description %s --entries %s --json\", listID, listDescription+\"2\", listEntries+\"2\"),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(updatelist))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(updatelist),\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"update\"}, scenarios)\n}\n\nvar listListsString = strings.TrimSpace(`\nID           Name       Description    Type      Scope    Entries  Updated At                     Created At\nsomeListID   listName   NGWAFCLIList   wildcard  account  1.0.0.0  2021-06-15 23:00:00 +0000 UTC  2021-06-15 23:00:00 +0000 UTC\nsomeListID2  listName2  NGWAFCLIList2  wildcard  account  1.0.0.0  2021-06-15 23:00:00 +0000 UTC  2021-06-15 23:00:00 +0000 UTC\n`) + \"\\n\"\n\nvar zeroListString = strings.TrimSpace(`\nID  Name  Description  Type  Scope  Entries  Updated At  Created At\n`) + \"\\n\"\n\nvar listString = strings.TrimSpace(`\nID: someListID\nName: listName\nDescription: NGWAFCLIList\nType: wildcard\nEntries: 1.0.0.0\nScope: account\nUpdated (UTC): 2021-06-15 23:00\n`)\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/alert/datadog/create.go",
    "content": "package datadog\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/ngwaf/workspace/alert\"\n\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/workspaces/alerts/datadog\"\n)\n\n// CreateCommand calls the Fastly API to create Datadog alerts.\ntype CreateCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tWorkspaceID argparser.OptionalWorkspaceID\n\tKey         string\n\tSite        string\n\n\t// Optional.\n\tDescription argparser.OptionalString\n}\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"create\", \"Create a Datadog alert\").Alias(\"add\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagNGWAFWorkspaceID,\n\t\tDescription: argparser.FlagNGWAFWorkspaceIDDesc,\n\t\tDst:         &c.WorkspaceID.Value,\n\t\tAction:      c.WorkspaceID.Set,\n\t})\n\tc.CmdClause.Flag(\"key\", \"Datadog integration key.\").Required().StringVar(&c.Key)\n\tc.CmdClause.Flag(\"site\", \"Datadog site.\").Required().StringVar(&c.Site)\n\n\t// Optional.\n\tc.CmdClause.Flag(\"description\", \"An optional description for the alert.\").Action(c.Description.Set).StringVar(&c.Description.Value)\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *CreateCommand) Exec(_ io.Reader, out io.Writer) error {\n\t// Call Parse() to ensure that we check if workspaceID\n\t// is set or to throw the appropriate error.\n\tif err := c.WorkspaceID.Parse(); err != nil {\n\t\treturn err\n\t}\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tinput := &datadog.CreateInput{\n\t\tWorkspaceID: &c.WorkspaceID.Value,\n\t\tConfig: &datadog.CreateConfig{\n\t\t\tKey:  &c.Key,\n\t\t\tSite: &c.Site,\n\t\t},\n\t\t// Set 'Events' to the only possible value, 'flag'\n\t\tEvents: alert.GetDefaultEvents(),\n\t}\n\tif c.Description.WasSet {\n\t\tinput.Description = &c.Description.Value\n\t}\n\n\tfc, ok := c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tdata, err := datadog.Create(context.TODO(), fc, input)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, data); ok {\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Created a '%s' alert '%s' (workspace-id: %s)\", data.Type, data.ID, c.WorkspaceID.Value)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/alert/datadog/datadog_test.go",
    "content": "package datadog_test\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\t\"testing\"\n\n\troot \"github.com/fastly/cli/pkg/commands/ngwaf\"\n\tworkspaceroot \"github.com/fastly/cli/pkg/commands/ngwaf/workspace\"\n\talertroot \"github.com/fastly/cli/pkg/commands/ngwaf/workspace/alert\"\n\tsub \"github.com/fastly/cli/pkg/commands/ngwaf/workspace/alert/datadog\"\n\tfstfmt \"github.com/fastly/cli/pkg/fmt\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/workspaces/alerts/datadog\"\n)\n\nconst (\n\talertID     = \"7890abcdef12345678901234\"\n\tworkspaceID = \"nBw2ENWfOY1M2dpSwK1l5R\"\n\tdescription = \"TestDatadogAlert\"\n)\n\nvar (\n\tkey          = \"a1b2c3d4e5f67890abcdef1234567890\"\n\tsite         = \"datadoghq.com\"\n\tdatadogAlert = datadog.Alert{\n\t\tID:          alertID,\n\t\tType:        \"datadog\",\n\t\tDescription: description,\n\t\tCreatedAt:   \"2025-11-25T16:40:12Z\",\n\t\tCreatedBy:   \"test@example.com\",\n\t\tConfig: datadog.ResponseConfig{\n\t\t\tKey:  &key,\n\t\t\tSite: &site,\n\t\t},\n\t}\n)\n\nfunc TestDatadogAlertCreate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --workspace-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--key %s --site %s\", key, site),\n\t\t\tWantError: \"error reading workspace ID: no workspace ID found\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --key flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--workspace-id %s --site %s\", workspaceID, site),\n\t\t\tWantError: \"error parsing arguments: required flag --key not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --site flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--workspace-id %s --key %s\", workspaceID, key),\n\t\t\tWantError: \"error parsing arguments: required flag --site not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --key %s --site %s\", workspaceID, key, site),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusCreated,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusCreated),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader((testutil.GenJSON(datadogAlert)))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Created a '%s' alert '%s' (workspace-id: %s)\", datadogAlert.Type, datadogAlert.ID, workspaceID),\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success with description\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --key %s --site %s --description %s\", workspaceID, key, site, description),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusCreated,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusCreated),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader((testutil.GenJSON(datadogAlert)))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Created a '%s' alert '%s' (workspace-id: %s)\", datadogAlert.Type, datadogAlert.ID, workspaceID),\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --key %s --site %s --json\", workspaceID, key, site),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusCreated,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusCreated),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader((testutil.GenJSON(datadogAlert)))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(datadogAlert),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, workspaceroot.CommandName, alertroot.CommandName, sub.CommandName, \"create\"}, scenarios)\n}\n\nfunc TestDatadogAlertList(t *testing.T) {\n\talertsObject := datadog.Alerts{\n\t\tData: []datadog.Alert{\n\t\t\t{\n\t\t\t\tID:          \"1a2b3c4d5e6f7890abcdef12\",\n\t\t\t\tType:        \"datadog\",\n\t\t\t\tDescription: \"First Datadog alert\",\n\t\t\t\tCreatedAt:   \"2025-11-25T16:40:12Z\",\n\t\t\t\tCreatedBy:   \"test@example.com\",\n\t\t\t\tConfig: datadog.ResponseConfig{\n\t\t\t\t\tKey:  &key,\n\t\t\t\t\tSite: &site,\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tID:          \"2b3c4d5e6f7890abcdef1234\",\n\t\t\t\tType:        \"datadog\",\n\t\t\t\tDescription: \"Second Datadog alert\",\n\t\t\t\tCreatedAt:   \"2025-11-25T16:40:12Z\",\n\t\t\t\tCreatedBy:   \"test@example.com\",\n\t\t\t\tConfig: datadog.ResponseConfig{\n\t\t\t\t\tKey:  &key,\n\t\t\t\t\tSite: &site,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tMeta: datadog.MetaAlerts{\n\t\t\tTotal: 2,\n\t\t},\n\t}\n\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --workspace-id flag\",\n\t\t\tArgs:      \"\",\n\t\t\tWantError: \"error reading workspace ID: no workspace ID found\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate internal server error\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s\", workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusInternalServerError,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusInternalServerError),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"500 - Internal Server Error\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success (zero alerts)\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s\", workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(datadog.Alerts{\n\t\t\t\t\t\t\tData: []datadog.Alert{},\n\t\t\t\t\t\t}))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: zeroListString,\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s\", workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(alertsObject))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: listString,\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --json\", workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(alertsObject))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(alertsObject),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, workspaceroot.CommandName, alertroot.CommandName, sub.CommandName, \"list\"}, scenarios)\n}\n\nfunc TestDatadogAlertGet(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --workspace-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--alert-id %s\", alertID),\n\t\t\tWantError: \"error reading workspace ID: no workspace ID found\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --alert-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--workspace-id %s\", workspaceID),\n\t\t\tWantError: \"error parsing arguments: required flag --alert-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate not found\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --alert-id invalid\", workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusNotFound,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusNotFound),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(`\n\t\t\t\t\t\t\t{\n    \t\t\t\t\t\t\t\"title\": \"This resource does not exist\",\n    \t\t\t\t\t\t\t\"status\": 404\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t`))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"404 - Not Found\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --alert-id %s\", workspaceID, alertID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader((testutil.GenJSON(datadogAlert)))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: alertString,\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --alert-id %s --json\", workspaceID, alertID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader((testutil.GenJSON(datadogAlert)))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(datadogAlert),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, workspaceroot.CommandName, alertroot.CommandName, sub.CommandName, \"get\"}, scenarios)\n}\n\nfunc TestDatadogAlertUpdate(t *testing.T) {\n\tupdatedKey := \"updated-key-9876543210\"\n\tupdatedSite := \"datadoghq.eu\"\n\tupdatedDescription := \"Updated description\"\n\tupdatedAlert := datadog.Alert{\n\t\tID:          alertID,\n\t\tType:        \"datadog\",\n\t\tDescription: updatedDescription,\n\t\tCreatedAt:   \"2025-11-25T16:40:12Z\",\n\t\tCreatedBy:   \"test@example.com\",\n\t\tConfig: datadog.ResponseConfig{\n\t\t\tKey:  &updatedKey,\n\t\t\tSite: &updatedSite,\n\t\t},\n\t}\n\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --workspace-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--alert-id %s --key %s --site %s\", alertID, key, site),\n\t\t\tWantError: \"error reading workspace ID: no workspace ID found\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --alert-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--workspace-id %s --key %s --site %s\", workspaceID, key, site),\n\t\t\tWantError: \"error parsing arguments: required flag --alert-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate not found\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --alert-id invalid --key updated-key-9876543210 --site datadoghq.eu\", workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusNotFound,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusNotFound),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(`\n\t\t\t\t\t\t\t{\n    \t\t\t\t\t\t\t\"title\": \"This resource does not exist\",\n    \t\t\t\t\t\t\t\"status\": 404\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t`))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"404 - Not Found\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success with key and site\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --alert-id %s --key updated-key-9876543210 --site datadoghq.eu\", workspaceID, alertID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MultiResponseRoundTripper{\n\t\t\t\t\tResponses: []*http.Response{\n\t\t\t\t\t\t// First response for GET (fetching current alert)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader((testutil.GenJSON(datadogAlert)))),\n\t\t\t\t\t\t},\n\t\t\t\t\t\t// Second response for PATCH (updating alert)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader((testutil.GenJSON(updatedAlert)))),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Updated '%s' alert '%s' (workspace-id: %s)\", updatedAlert.Type, updatedAlert.ID, workspaceID),\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --alert-id %s --key updated-key-9876543210 --site datadoghq.eu --json\", workspaceID, alertID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MultiResponseRoundTripper{\n\t\t\t\t\tResponses: []*http.Response{\n\t\t\t\t\t\t// First response for GET (fetching current alert)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader((testutil.GenJSON(datadogAlert)))),\n\t\t\t\t\t\t},\n\t\t\t\t\t\t// Second response for PATCH (updating alert)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader((testutil.GenJSON(updatedAlert)))),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(updatedAlert),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, workspaceroot.CommandName, alertroot.CommandName, sub.CommandName, \"update\"}, scenarios)\n}\n\nfunc TestDatadogAlertDelete(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --workspace-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--alert-id %s\", alertID),\n\t\t\tWantError: \"error reading workspace ID: no workspace ID found\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --alert-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--workspace-id %s\", workspaceID),\n\t\t\tWantError: \"error parsing arguments: required flag --alert-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate not found\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --alert-id invalid\", workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusNotFound,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusNotFound),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(`\n\t\t\t\t\t\t\t{\n    \t\t\t\t\t\t\t\"title\": \"This resource does not exist\",\n    \t\t\t\t\t\t\t\"status\": 404\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t`))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"404 - Not Found\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --alert-id %s\", workspaceID, alertID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusNoContent,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusNoContent),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Deleted alert '%s' (workspace-id: %s)\", alertID, workspaceID),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, workspaceroot.CommandName, alertroot.CommandName, sub.CommandName, \"delete\"}, scenarios)\n}\n\nvar alertString = strings.TrimSpace(`\nID: 7890abcdef12345678901234\nType: datadog\nDescription: TestDatadogAlert\nCreated At: 2025-11-25T16:40:12Z\nCreated By: test@example.com\nConfig:\n  Key: <redacted>\n  Site: datadoghq.com\n`)\n\nvar listString = strings.TrimSpace(`\nID                        Type     Description           Created At            Created By        Config\n1a2b3c4d5e6f7890abcdef12  datadog  First Datadog alert   2025-11-25T16:40:12Z  test@example.com  Site: datadoghq.com, Key: <redacted>\n2b3c4d5e6f7890abcdef1234  datadog  Second Datadog alert  2025-11-25T16:40:12Z  test@example.com  Site: datadoghq.com, Key: <redacted>\n`) + \"\\n\"\n\nvar zeroListString = strings.TrimSpace(`\nID  Type  Description  Created At  Created By  Config\n`) + \"\\n\"\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/alert/datadog/delete.go",
    "content": "package datadog\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/workspaces/alerts/datadog\"\n)\n\n// DeleteCommand calls the Fastly API to delete Datadog alerts.\ntype DeleteCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tAlertID     string\n\tWorkspaceID argparser.OptionalWorkspaceID\n}\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"delete\", \"Delete a Datadog alert\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagNGWAFWorkspaceID,\n\t\tDescription: argparser.FlagNGWAFWorkspaceIDDesc,\n\t\tDst:         &c.WorkspaceID.Value,\n\t\tAction:      c.WorkspaceID.Set,\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagNGWAFAlertID,\n\t\tDescription: argparser.FlagNGWAFAlertIDDesc,\n\t\tDst:         &c.AlertID,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DeleteCommand) Exec(_ io.Reader, out io.Writer) error {\n\t// Call Parse() to ensure that we check if workspaceID\n\t// is set or to throw the appropriate error.\n\tif err := c.WorkspaceID.Parse(); err != nil {\n\t\treturn err\n\t}\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\tfc, ok := c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\terr := datadog.Delete(context.TODO(), fc, &datadog.DeleteInput{\n\t\tWorkspaceID: &c.WorkspaceID.Value,\n\t\tAlertID:     &c.AlertID,\n\t})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif c.JSONOutput.Enabled {\n\t\to := struct {\n\t\t\tID      string `json:\"id\"`\n\t\t\tDeleted bool   `json:\"deleted\"`\n\t\t}{\n\t\t\tc.AlertID,\n\t\t\ttrue,\n\t\t}\n\t\t_, err := c.WriteJSON(out, o)\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Deleted alert '%s' (workspace-id: %s)\", c.AlertID, c.WorkspaceID.Value)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/alert/datadog/doc.go",
    "content": "// Package datadog contains commands to inspect and manipulate NGWAF Datadog alerts.\npackage datadog\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/alert/datadog/get.go",
    "content": "package datadog\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/workspaces/alerts/datadog\"\n)\n\n// GetCommand calls the Fastly API to get Datadog alerts.\ntype GetCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tAlertID     string\n\tWorkspaceID argparser.OptionalWorkspaceID\n}\n\n// NewGetCommand returns a usable command registered under the parent.\nfunc NewGetCommand(parent argparser.Registerer, g *global.Data) *GetCommand {\n\tc := GetCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"get\", \"Get a Datadog alert\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagNGWAFWorkspaceID,\n\t\tDescription: argparser.FlagNGWAFWorkspaceIDDesc,\n\t\tDst:         &c.WorkspaceID.Value,\n\t\tAction:      c.WorkspaceID.Set,\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagNGWAFAlertID,\n\t\tDescription: argparser.FlagNGWAFAlertIDDesc,\n\t\tDst:         &c.AlertID,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *GetCommand) Exec(_ io.Reader, out io.Writer) error {\n\t// Call Parse() to ensure that we check if workspaceID\n\t// is set or to throw the appropriate error.\n\tif err := c.WorkspaceID.Parse(); err != nil {\n\t\treturn err\n\t}\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\tinput := &datadog.GetInput{\n\t\tAlertID:     &c.AlertID,\n\t\tWorkspaceID: &c.WorkspaceID.Value,\n\t}\n\n\tfc, ok := c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tdata, err := datadog.Get(context.TODO(), fc, input)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, data); ok {\n\t\treturn err\n\t}\n\n\ttext.PrintAlert(out, data)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/alert/datadog/list.go",
    "content": "package datadog\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/workspaces/alerts/datadog\"\n)\n\n// ListCommand calls the Fastly API to list Datadog alerts.\ntype ListCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tWorkspaceID argparser.OptionalWorkspaceID\n}\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"list\", \"List Datadog alerts\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagNGWAFWorkspaceID,\n\t\tDescription: argparser.FlagNGWAFWorkspaceIDDesc,\n\t\tDst:         &c.WorkspaceID.Value,\n\t\tAction:      c.WorkspaceID.Set,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *ListCommand) Exec(_ io.Reader, out io.Writer) error {\n\t// Call Parse() to ensure that we check if workspaceID\n\t// is set or to throw the appropriate error.\n\tif err := c.WorkspaceID.Parse(); err != nil {\n\t\treturn err\n\t}\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\tinput := &datadog.ListInput{\n\t\tWorkspaceID: &c.WorkspaceID.Value,\n\t}\n\n\tfc, ok := c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tdata, err := datadog.List(context.TODO(), fc, input)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, data); ok {\n\t\treturn err\n\t}\n\n\ttext.PrintAlertTbl(out, data.Data)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/alert/datadog/root.go",
    "content": "package datadog\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"datadog\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manage Datadog workspace alerts\")\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n\n// ConfigFlags contains Datadog specific configuration flags.\ntype ConfigFlags struct {\n\tKey  string\n\tSite string\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/alert/datadog/update.go",
    "content": "package datadog\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/ngwaf/workspace/alert\"\n\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/workspaces/alerts/datadog\"\n)\n\n// UpdateCommand calls the Fastly API to update Datadog alerts.\ntype UpdateCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tAlertID     string\n\tWorkspaceID argparser.OptionalWorkspaceID\n\tKey         string\n\tSite        string\n}\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"update\", \"Update a Datadog alert\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagNGWAFWorkspaceID,\n\t\tDescription: argparser.FlagNGWAFWorkspaceIDDesc,\n\t\tDst:         &c.WorkspaceID.Value,\n\t\tAction:      c.WorkspaceID.Set,\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagNGWAFAlertID,\n\t\tDescription: argparser.FlagNGWAFAlertIDDesc,\n\t\tDst:         &c.AlertID,\n\t\tRequired:    true,\n\t})\n\tc.CmdClause.Flag(\"key\", \"Datadog integration key.\").Required().StringVar(&c.Key)\n\tc.CmdClause.Flag(\"site\", \"Datadog site.\").Required().StringVar(&c.Site)\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *UpdateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\t// Call Parse() to ensure that we check if workspaceID\n\t// is set or to throw the appropriate error.\n\tif err := c.WorkspaceID.Parse(); err != nil {\n\t\treturn err\n\t}\n\tinput := &datadog.UpdateInput{\n\t\tAlertID:     &c.AlertID,\n\t\tWorkspaceID: &c.WorkspaceID.Value,\n\t\tConfig: &datadog.UpdateConfig{\n\t\t\tKey:  &c.Key,\n\t\t\tSite: &c.Site,\n\t\t},\n\t\t// Set 'Events' to the only possible value, 'flag'\n\t\tEvents: alert.GetDefaultEvents(),\n\t}\n\n\tfc, ok := c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tdata, err := datadog.Update(context.TODO(), fc, input)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, data); ok {\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Updated '%s' alert '%s' (workspace-id: %s)\", data.Type, data.ID, c.WorkspaceID.Value)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/alert/doc.go",
    "content": "// Package alert contains commands to inspect and manipulate NGWAF alerts.\npackage alert\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/alert/jira/create.go",
    "content": "package jira\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/ngwaf/workspace/alert\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/workspaces/alerts/jira\"\n)\n\n// CreateCommand calls the Fastly API to create Jira alerts.\ntype CreateCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tWorkspaceID argparser.OptionalWorkspaceID\n\tHost        string\n\tKey         string\n\tProject     string\n\tUsername    string\n\n\t// Optional.\n\tDescription argparser.OptionalString\n\tIssueType   string\n}\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"create\", \"Create a Jira alert\").Alias(\"add\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagNGWAFWorkspaceID,\n\t\tDescription: argparser.FlagNGWAFWorkspaceIDDesc,\n\t\tDst:         &c.WorkspaceID.Value,\n\t\tAction:      c.WorkspaceID.Set,\n\t})\n\tc.CmdClause.Flag(\"host\", \"Host name of the Jira instance.\").Required().StringVar(&c.Host)\n\tc.CmdClause.Flag(\"key\", \"Jira API key.\").Required().StringVar(&c.Key)\n\tc.CmdClause.Flag(\"project\", \"Specifies the Jira project where the issue will be created.\").Required().StringVar(&c.Project)\n\tc.CmdClause.Flag(\"username\", \"Jira username of the user who created the ticket.\").Required().StringVar(&c.Username)\n\n\t// Optional.\n\tc.CmdClause.Flag(\"description\", \"An optional description for the alert.\").Action(c.Description.Set).StringVar(&c.Description.Value)\n\tc.CmdClause.Flag(\"issue-type\", \"An optional Jira issue type associated with the ticket. (Default Task)\").StringVar(&c.IssueType)\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *CreateCommand) Exec(_ io.Reader, out io.Writer) error {\n\t// Call Parse() to ensure that we check if workspaceID\n\t// is set or to throw the appropriate error.\n\tif err := c.WorkspaceID.Parse(); err != nil {\n\t\treturn err\n\t}\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tinput := &jira.CreateInput{\n\t\tWorkspaceID: &c.WorkspaceID.Value,\n\t\tConfig: &jira.CreateConfig{\n\t\t\tHost:     &c.Host,\n\t\t\tKey:      &c.Key,\n\t\t\tProject:  &c.Project,\n\t\t\tUsername: &c.Username,\n\t\t},\n\t\t// Set 'Events' to the only possible value, 'flag'\n\t\tEvents: alert.GetDefaultEvents(),\n\t}\n\tif c.IssueType != \"\" {\n\t\tinput.Config.IssueType = &c.IssueType\n\t}\n\tif c.Description.WasSet {\n\t\tinput.Description = &c.Description.Value\n\t}\n\n\tfc, ok := c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tdata, err := jira.Create(context.TODO(), fc, input)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, data); ok {\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Created a '%s' alert '%s' (workspace-id: %s)\", data.Type, data.ID, c.WorkspaceID.Value)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/alert/jira/delete.go",
    "content": "package jira\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/workspaces/alerts/jira\"\n)\n\n// DeleteCommand calls the Fastly API to delete Jira alerts.\ntype DeleteCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tAlertID     string\n\tWorkspaceID argparser.OptionalWorkspaceID\n}\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"delete\", \"Delete a Jira alert\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagNGWAFWorkspaceID,\n\t\tDescription: argparser.FlagNGWAFWorkspaceIDDesc,\n\t\tDst:         &c.WorkspaceID.Value,\n\t\tAction:      c.WorkspaceID.Set,\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagNGWAFAlertID,\n\t\tDescription: argparser.FlagNGWAFAlertIDDesc,\n\t\tDst:         &c.AlertID,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DeleteCommand) Exec(_ io.Reader, out io.Writer) error {\n\t// Call Parse() to ensure that we check if workspaceID\n\t// is set or to throw the appropriate error.\n\tif err := c.WorkspaceID.Parse(); err != nil {\n\t\treturn err\n\t}\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\tfc, ok := c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\terr := jira.Delete(context.TODO(), fc, &jira.DeleteInput{\n\t\tWorkspaceID: &c.WorkspaceID.Value,\n\t\tAlertID:     &c.AlertID,\n\t})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif c.JSONOutput.Enabled {\n\t\to := struct {\n\t\t\tID      string `json:\"id\"`\n\t\t\tDeleted bool   `json:\"deleted\"`\n\t\t}{\n\t\t\tc.AlertID,\n\t\t\ttrue,\n\t\t}\n\t\t_, err := c.WriteJSON(out, o)\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Deleted alert '%s' (workspace-id: %s)\", c.AlertID, c.WorkspaceID.Value)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/alert/jira/doc.go",
    "content": "// Package jira contains commands to inspect and manipulate NGWAF Jira alerts.\npackage jira\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/alert/jira/get.go",
    "content": "package jira\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/workspaces/alerts/jira\"\n)\n\n// GetCommand calls the Fastly API to get Jira alerts.\ntype GetCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tAlertID     string\n\tWorkspaceID argparser.OptionalWorkspaceID\n}\n\n// NewGetCommand returns a usable command registered under the parent.\nfunc NewGetCommand(parent argparser.Registerer, g *global.Data) *GetCommand {\n\tc := GetCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"get\", \"Get a Jira alert\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagNGWAFWorkspaceID,\n\t\tDescription: argparser.FlagNGWAFWorkspaceIDDesc,\n\t\tDst:         &c.WorkspaceID.Value,\n\t\tAction:      c.WorkspaceID.Set,\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagNGWAFAlertID,\n\t\tDescription: argparser.FlagNGWAFAlertIDDesc,\n\t\tDst:         &c.AlertID,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *GetCommand) Exec(_ io.Reader, out io.Writer) error {\n\t// Call Parse() to ensure that we check if workspaceID\n\t// is set or to throw the appropriate error.\n\tif err := c.WorkspaceID.Parse(); err != nil {\n\t\treturn err\n\t}\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\tinput := &jira.GetInput{\n\t\tAlertID:     &c.AlertID,\n\t\tWorkspaceID: &c.WorkspaceID.Value,\n\t}\n\n\tfc, ok := c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tdata, err := jira.Get(context.TODO(), fc, input)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, data); ok {\n\t\treturn err\n\t}\n\n\ttext.PrintAlert(out, data)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/alert/jira/jira_test.go",
    "content": "package jira_test\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\t\"testing\"\n\n\troot \"github.com/fastly/cli/pkg/commands/ngwaf\"\n\tworkspaceroot \"github.com/fastly/cli/pkg/commands/ngwaf/workspace\"\n\talertroot \"github.com/fastly/cli/pkg/commands/ngwaf/workspace/alert\"\n\tsub \"github.com/fastly/cli/pkg/commands/ngwaf/workspace/alert/jira\"\n\tfstfmt \"github.com/fastly/cli/pkg/fmt\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/workspaces/alerts/jira\"\n)\n\nconst (\n\talertID     = \"890abcdef1234567890123ab\"\n\tworkspaceID = \"nBw2ENWfOY1M2dpSwK1l5R\"\n\tdescription = \"TestJiraAlert\"\n)\n\nvar (\n\thost      = \"example.atlassian.net\"\n\tkey       = \"jira-api-key-123456\"\n\tproject   = \"PROJ\"\n\tusername  = \"user@example.com\"\n\tissueType = \"Task\"\n\tjiraAlert = jira.Alert{\n\t\tID:          alertID,\n\t\tType:        \"jira\",\n\t\tDescription: description,\n\t\tCreatedAt:   \"2025-11-25T16:40:12Z\",\n\t\tCreatedBy:   \"test@example.com\",\n\t\tConfig: jira.ResponseConfig{\n\t\t\tHost:      &host,\n\t\t\tKey:       &key,\n\t\t\tProject:   &project,\n\t\t\tUsername:  &username,\n\t\t\tIssueType: &issueType,\n\t\t},\n\t}\n)\n\nfunc TestJiraAlertCreate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --workspace-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--host %s --key %s --project %s --username %s\", host, key, project, username),\n\t\t\tWantError: \"error reading workspace ID: no workspace ID found\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --host flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--workspace-id %s --key %s --project %s --username %s\", workspaceID, key, project, username),\n\t\t\tWantError: \"error parsing arguments: required flag --host not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --key flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--workspace-id %s --host %s --project %s --username %s\", workspaceID, host, project, username),\n\t\t\tWantError: \"error parsing arguments: required flag --key not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --project flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--workspace-id %s --host %s --key %s --username %s\", workspaceID, host, key, username),\n\t\t\tWantError: \"error parsing arguments: required flag --project not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --username flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--workspace-id %s --host %s --key %s --project %s\", workspaceID, host, key, project),\n\t\t\tWantError: \"error parsing arguments: required flag --username not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --host %s --key %s --project %s --username %s\", workspaceID, host, key, project, username),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusCreated,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusCreated),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader((testutil.GenJSON(jiraAlert)))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Created a '%s' alert '%s' (workspace-id: %s)\", jiraAlert.Type, jiraAlert.ID, workspaceID),\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success with description\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --host %s --key %s --project %s --username %s --description %s\", workspaceID, host, key, project, username, description),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusCreated,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusCreated),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader((testutil.GenJSON(jiraAlert)))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Created a '%s' alert '%s' (workspace-id: %s)\", jiraAlert.Type, jiraAlert.ID, workspaceID),\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success with issue-type\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --host %s --key %s --project %s --username %s --issue-type %s\", workspaceID, host, key, project, username, issueType),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusCreated,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusCreated),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader((testutil.GenJSON(jiraAlert)))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Created a '%s' alert '%s' (workspace-id: %s)\", jiraAlert.Type, jiraAlert.ID, workspaceID),\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --host %s --key %s --project %s --username %s --json\", workspaceID, host, key, project, username),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusCreated,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusCreated),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader((testutil.GenJSON(jiraAlert)))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(jiraAlert),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, workspaceroot.CommandName, alertroot.CommandName, sub.CommandName, \"create\"}, scenarios)\n}\n\nfunc TestJiraAlertList(t *testing.T) {\n\talertsObject := jira.Alerts{\n\t\tData: []jira.Alert{\n\t\t\t{\n\t\t\t\tID:          \"1a2b3c4d5e6f7890abcdef12\",\n\t\t\t\tType:        \"jira\",\n\t\t\t\tDescription: \"First Jira alert\",\n\t\t\t\tCreatedAt:   \"2025-11-25T16:40:12Z\",\n\t\t\t\tCreatedBy:   \"test@example.com\",\n\t\t\t\tConfig: jira.ResponseConfig{\n\t\t\t\t\tHost:      &host,\n\t\t\t\t\tKey:       &key,\n\t\t\t\t\tProject:   &project,\n\t\t\t\t\tUsername:  &username,\n\t\t\t\t\tIssueType: &issueType,\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tID:          \"2b3c4d5e6f7890abcdef1234\",\n\t\t\t\tType:        \"jira\",\n\t\t\t\tDescription: \"Second Jira alert\",\n\t\t\t\tCreatedAt:   \"2025-11-25T16:40:12Z\",\n\t\t\t\tCreatedBy:   \"test@example.com\",\n\t\t\t\tConfig: jira.ResponseConfig{\n\t\t\t\t\tHost:      &host,\n\t\t\t\t\tKey:       &key,\n\t\t\t\t\tProject:   &project,\n\t\t\t\t\tUsername:  &username,\n\t\t\t\t\tIssueType: &issueType,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tMeta: jira.MetaAlerts{\n\t\t\tTotal: 2,\n\t\t},\n\t}\n\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --workspace-id flag\",\n\t\t\tArgs:      \"\",\n\t\t\tWantError: \"error reading workspace ID: no workspace ID found\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate internal server error\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s\", workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusInternalServerError,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusInternalServerError),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"500 - Internal Server Error\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success (zero alerts)\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s\", workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(jira.Alerts{\n\t\t\t\t\t\t\tData: []jira.Alert{},\n\t\t\t\t\t\t}))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: zeroListString,\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s\", workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(alertsObject))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: listString,\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --json\", workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(alertsObject))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(alertsObject),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, workspaceroot.CommandName, alertroot.CommandName, sub.CommandName, \"list\"}, scenarios)\n}\n\nfunc TestJiraAlertGet(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --workspace-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--alert-id %s\", alertID),\n\t\t\tWantError: \"error reading workspace ID: no workspace ID found\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --alert-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--workspace-id %s\", workspaceID),\n\t\t\tWantError: \"error parsing arguments: required flag --alert-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate not found\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --alert-id invalid\", workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusNotFound,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusNotFound),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(`\n\t\t\t\t\t\t\t{\n    \t\t\t\t\t\t\t\"title\": \"This resource does not exist\",\n    \t\t\t\t\t\t\t\"status\": 404\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t`))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"404 - Not Found\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --alert-id %s\", workspaceID, alertID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader((testutil.GenJSON(jiraAlert)))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: alertString,\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --alert-id %s --json\", workspaceID, alertID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader((testutil.GenJSON(jiraAlert)))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(jiraAlert),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, workspaceroot.CommandName, alertroot.CommandName, sub.CommandName, \"get\"}, scenarios)\n}\n\nfunc TestJiraAlertUpdate(t *testing.T) {\n\tupdatedHost := \"updated.atlassian.net\"\n\tupdatedKey := \"updated-jira-key-456\"\n\tupdatedProject := \"UPDT\"\n\tupdatedUsername := \"updated@example.com\"\n\tupdatedIssueType := \"Bug\"\n\tupdatedAlert := jira.Alert{\n\t\tID:          alertID,\n\t\tType:        \"jira\",\n\t\tDescription: \"Updated description\",\n\t\tCreatedAt:   \"2025-11-25T16:40:12Z\",\n\t\tCreatedBy:   \"test@example.com\",\n\t\tConfig: jira.ResponseConfig{\n\t\t\tHost:      &updatedHost,\n\t\t\tKey:       &updatedKey,\n\t\t\tProject:   &updatedProject,\n\t\t\tUsername:  &updatedUsername,\n\t\t\tIssueType: &updatedIssueType,\n\t\t},\n\t}\n\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --workspace-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--alert-id %s --host %s --key %s --project %s --username %s\", alertID, host, key, project, username),\n\t\t\tWantError: \"error reading workspace ID: no workspace ID found\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --alert-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--workspace-id %s --host %s --key %s --project %s --username %s\", workspaceID, host, key, project, username),\n\t\t\tWantError: \"error parsing arguments: required flag --alert-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate not found\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --alert-id invalid --host updated.atlassian.net --key updated-jira-key-456 --project UPDT --username updated@example.com\", workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusNotFound,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusNotFound),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(`\n\t\t\t\t\t\t\t{\n    \t\t\t\t\t\t\t\"title\": \"This resource does not exist\",\n    \t\t\t\t\t\t\t\"status\": 404\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t`))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"404 - Not Found\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success with all config fields\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --alert-id %s --host updated.atlassian.net --key updated-jira-key-456 --project UPDT --username updated@example.com --issue-type Bug\", workspaceID, alertID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MultiResponseRoundTripper{\n\t\t\t\t\tResponses: []*http.Response{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(jiraAlert))),\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(updatedAlert))),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Updated '%s' alert '%s' (workspace-id: %s)\", updatedAlert.Type, updatedAlert.ID, workspaceID),\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --alert-id %s --host updated.atlassian.net --key updated-jira-key-456 --project UPDT --username updated@example.com --json\", workspaceID, alertID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MultiResponseRoundTripper{\n\t\t\t\t\tResponses: []*http.Response{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(jiraAlert))),\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(updatedAlert))),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(updatedAlert),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, workspaceroot.CommandName, alertroot.CommandName, sub.CommandName, \"update\"}, scenarios)\n}\n\nfunc TestJiraAlertDelete(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --workspace-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--alert-id %s\", alertID),\n\t\t\tWantError: \"error reading workspace ID: no workspace ID found\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --alert-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--workspace-id %s\", workspaceID),\n\t\t\tWantError: \"error parsing arguments: required flag --alert-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate not found\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --alert-id invalid\", workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusNotFound,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusNotFound),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(`\n\t\t\t\t\t\t\t{\n    \t\t\t\t\t\t\t\"title\": \"This resource does not exist\",\n    \t\t\t\t\t\t\t\"status\": 404\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t`))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"404 - Not Found\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --alert-id %s\", workspaceID, alertID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusNoContent,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusNoContent),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Deleted alert '%s' (workspace-id: %s)\", alertID, workspaceID),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, workspaceroot.CommandName, alertroot.CommandName, sub.CommandName, \"delete\"}, scenarios)\n}\n\nvar alertString = strings.TrimSpace(`\nID: 890abcdef1234567890123ab\nType: jira\nDescription: TestJiraAlert\nCreated At: 2025-11-25T16:40:12Z\nCreated By: test@example.com\nConfig:\n  Host: example.atlassian.net\n  Username: user@example.com\n  Project: PROJ\n  Issue Type: Task\n  Key: <redacted>\n`)\n\nvar listString = strings.TrimSpace(`\nID                        Type  Description        Created At            Created By        Config\n1a2b3c4d5e6f7890abcdef12  jira  First Jira alert   2025-11-25T16:40:12Z  test@example.com  Host: example.atlassian.net, Issue Type: Task, Key: <redacted>, Project: PROJ, Username: user@example.com\n2b3c4d5e6f7890abcdef1234  jira  Second Jira alert  2025-11-25T16:40:12Z  test@example.com  Host: example.atlassian.net, Issue Type: Task, Key: <redacted>, Project: PROJ, Username: user@example.com\n`) + \"\\n\"\n\nvar zeroListString = strings.TrimSpace(`\nID  Type  Description  Created At  Created By  Config\n`) + \"\\n\"\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/alert/jira/list.go",
    "content": "package jira\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/workspaces/alerts/jira\"\n)\n\n// ListCommand calls the Fastly API to list Jira alerts.\ntype ListCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tWorkspaceID argparser.OptionalWorkspaceID\n}\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"list\", \"List Jira alerts\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagNGWAFWorkspaceID,\n\t\tDescription: argparser.FlagNGWAFWorkspaceIDDesc,\n\t\tDst:         &c.WorkspaceID.Value,\n\t\tAction:      c.WorkspaceID.Set,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *ListCommand) Exec(_ io.Reader, out io.Writer) error {\n\t// Call Parse() to ensure that we check if workspaceID\n\t// is set or to throw the appropriate error.\n\tif err := c.WorkspaceID.Parse(); err != nil {\n\t\treturn err\n\t}\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\tinput := &jira.ListInput{\n\t\tWorkspaceID: &c.WorkspaceID.Value,\n\t}\n\n\tfc, ok := c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tdata, err := jira.List(context.TODO(), fc, input)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, data); ok {\n\t\treturn err\n\t}\n\n\ttext.PrintAlertTbl(out, data.Data)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/alert/jira/root.go",
    "content": "package jira\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"jira\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manage Jira workspace alerts\")\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n\n// ConfigFlags contains Jira specific configuration flags.\ntype ConfigFlags struct {\n\tHost     string\n\tKey      string\n\tProject  string\n\tUsername string\n}\n\n// OptConfigFlags contains optional Jira specific configuration flags.\ntype OptConfigFlags struct {\n\tIssueType string\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/alert/jira/update.go",
    "content": "package jira\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/ngwaf/workspace/alert\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/workspaces/alerts/jira\"\n)\n\n// UpdateCommand calls the Fastly API to update Jira alerts.\ntype UpdateCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tAlertID     string\n\tWorkspaceID argparser.OptionalWorkspaceID\n\tHost        string\n\tKey         string\n\tProject     string\n\tUsername    string\n\n\t// Optional\n\tIssueType string\n}\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"update\", \"Update a Jira alert\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagNGWAFWorkspaceID,\n\t\tDescription: argparser.FlagNGWAFWorkspaceIDDesc,\n\t\tDst:         &c.WorkspaceID.Value,\n\t\tAction:      c.WorkspaceID.Set,\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagNGWAFAlertID,\n\t\tDescription: argparser.FlagNGWAFAlertIDDesc,\n\t\tDst:         &c.AlertID,\n\t\tRequired:    true,\n\t})\n\tc.CmdClause.Flag(\"host\", \"Host name of the Jira instance.\").Required().StringVar(&c.Host)\n\tc.CmdClause.Flag(\"key\", \"Jira API key.\").Required().StringVar(&c.Key)\n\tc.CmdClause.Flag(\"project\", \"Specifies the Jira project where the issue will be created.\").Required().StringVar(&c.Project)\n\tc.CmdClause.Flag(\"username\", \"Jira username of the user who created the ticket.\").Required().StringVar(&c.Username)\n\n\t// Optional.\n\tc.CmdClause.Flag(\"issue-type\", \"An optional Jira issue type associated with the ticket. (Default Task)\").StringVar(&c.IssueType)\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *UpdateCommand) Exec(_ io.Reader, out io.Writer) error {\n\t// Call Parse() to ensure that we check if workspaceID\n\t// is set or to throw the appropriate error.\n\tif err := c.WorkspaceID.Parse(); err != nil {\n\t\treturn err\n\t}\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tinput := &jira.UpdateInput{\n\t\tAlertID:     &c.AlertID,\n\t\tWorkspaceID: &c.WorkspaceID.Value,\n\t\tConfig: &jira.UpdateConfig{\n\t\t\tHost:     &c.Host,\n\t\t\tKey:      &c.Key,\n\t\t\tProject:  &c.Project,\n\t\t\tUsername: &c.Username,\n\t\t},\n\t\t// Set 'Events' to the only possible value, 'flag'\n\t\tEvents: alert.GetDefaultEvents(),\n\t}\n\tif c.IssueType != \"\" {\n\t\tinput.Config.IssueType = &c.IssueType\n\t}\n\n\tfc, ok := c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tdata, err := jira.Update(context.TODO(), fc, input)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, data); ok {\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Updated '%s' alert '%s' (workspace-id: %s)\", data.Type, data.ID, c.WorkspaceID.Value)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/alert/mailinglist/create.go",
    "content": "package mailinglist\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/ngwaf/workspace/alert\"\n\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/workspaces/alerts/mailinglist\"\n)\n\n// CreateCommand calls the Fastly API to create Mailing List alerts.\ntype CreateCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tWorkspaceID argparser.OptionalWorkspaceID\n\tAddress     string\n\n\t// Optional.\n\tDescription argparser.OptionalString\n}\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"create\", \"Create a Mailing List alert\").Alias(\"add\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagNGWAFWorkspaceID,\n\t\tDescription: argparser.FlagNGWAFWorkspaceIDDesc,\n\t\tDst:         &c.WorkspaceID.Value,\n\t\tAction:      c.WorkspaceID.Set,\n\t})\n\tc.CmdClause.Flag(\"address\", \"An email address.\").Required().StringVar(&c.Address)\n\n\t// Optional.\n\tc.CmdClause.Flag(\"description\", \"An optional description for the alert.\").Action(c.Description.Set).StringVar(&c.Description.Value)\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *CreateCommand) Exec(_ io.Reader, out io.Writer) error {\n\t// Call Parse() to ensure that we check if workspaceID\n\t// is set or to throw the appropriate error.\n\tif err := c.WorkspaceID.Parse(); err != nil {\n\t\treturn err\n\t}\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tinput := &mailinglist.CreateInput{\n\t\tWorkspaceID: &c.WorkspaceID.Value,\n\t\tConfig: &mailinglist.CreateConfig{\n\t\t\tAddress: &c.Address,\n\t\t},\n\t\t// Set 'Events' to the only possible value, 'flag'\n\t\tEvents: alert.GetDefaultEvents(),\n\t}\n\tif c.Description.WasSet {\n\t\tinput.Description = &c.Description.Value\n\t}\n\n\tfc, ok := c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tdata, err := mailinglist.Create(context.TODO(), fc, input)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, data); ok {\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Created a '%s' alert '%s' (workspace-id: %s)\", data.Type, data.ID, c.WorkspaceID.Value)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/alert/mailinglist/delete.go",
    "content": "package mailinglist\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/workspaces/alerts/mailinglist\"\n)\n\n// DeleteCommand calls the Fastly API to delete Mailing List alerts.\ntype DeleteCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tAlertID     string\n\tWorkspaceID argparser.OptionalWorkspaceID\n}\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"delete\", \"Delete a Mailing List alert\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagNGWAFWorkspaceID,\n\t\tDescription: argparser.FlagNGWAFWorkspaceIDDesc,\n\t\tDst:         &c.WorkspaceID.Value,\n\t\tAction:      c.WorkspaceID.Set,\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagNGWAFAlertID,\n\t\tDescription: argparser.FlagNGWAFAlertIDDesc,\n\t\tDst:         &c.AlertID,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DeleteCommand) Exec(_ io.Reader, out io.Writer) error {\n\t// Call Parse() to ensure that we check if workspaceID\n\t// is set or to throw the appropriate error.\n\tif err := c.WorkspaceID.Parse(); err != nil {\n\t\treturn err\n\t}\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\tfc, ok := c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\terr := mailinglist.Delete(context.TODO(), fc, &mailinglist.DeleteInput{\n\t\tWorkspaceID: &c.WorkspaceID.Value,\n\t\tAlertID:     &c.AlertID,\n\t})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif c.JSONOutput.Enabled {\n\t\to := struct {\n\t\t\tID      string `json:\"id\"`\n\t\t\tDeleted bool   `json:\"deleted\"`\n\t\t}{\n\t\t\tc.AlertID,\n\t\t\ttrue,\n\t\t}\n\t\t_, err := c.WriteJSON(out, o)\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Deleted alert '%s' (workspace-id: %s)\", c.AlertID, c.WorkspaceID.Value)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/alert/mailinglist/doc.go",
    "content": "// Package mailinglist contains commands to inspect and manipulate NGWAF Mailing List alerts.\npackage mailinglist\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/alert/mailinglist/get.go",
    "content": "package mailinglist\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/workspaces/alerts/mailinglist\"\n)\n\n// GetCommand calls the Fastly API to get Mailing List alerts.\ntype GetCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tAlertID     string\n\tWorkspaceID argparser.OptionalWorkspaceID\n}\n\n// NewGetCommand returns a usable command registered under the parent.\nfunc NewGetCommand(parent argparser.Registerer, g *global.Data) *GetCommand {\n\tc := GetCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"get\", \"Get a Mailing List alert\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagNGWAFWorkspaceID,\n\t\tDescription: argparser.FlagNGWAFWorkspaceIDDesc,\n\t\tDst:         &c.WorkspaceID.Value,\n\t\tAction:      c.WorkspaceID.Set,\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagNGWAFAlertID,\n\t\tDescription: argparser.FlagNGWAFAlertIDDesc,\n\t\tDst:         &c.AlertID,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *GetCommand) Exec(_ io.Reader, out io.Writer) error {\n\t// Call Parse() to ensure that we check if workspaceID\n\t// is set or to throw the appropriate error.\n\tif err := c.WorkspaceID.Parse(); err != nil {\n\t\treturn err\n\t}\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\tinput := &mailinglist.GetInput{\n\t\tAlertID:     &c.AlertID,\n\t\tWorkspaceID: &c.WorkspaceID.Value,\n\t}\n\n\tfc, ok := c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tdata, err := mailinglist.Get(context.TODO(), fc, input)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, data); ok {\n\t\treturn err\n\t}\n\n\ttext.PrintAlert(out, data)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/alert/mailinglist/list.go",
    "content": "package mailinglist\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/workspaces/alerts/mailinglist\"\n)\n\n// ListCommand calls the Fastly API to list Mailing List alerts.\ntype ListCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tWorkspaceID argparser.OptionalWorkspaceID\n}\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"list\", \"List Mailing List alerts\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagNGWAFWorkspaceID,\n\t\tDescription: argparser.FlagNGWAFWorkspaceIDDesc,\n\t\tDst:         &c.WorkspaceID.Value,\n\t\tAction:      c.WorkspaceID.Set,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *ListCommand) Exec(_ io.Reader, out io.Writer) error {\n\t// Call Parse() to ensure that we check if workspaceID\n\t// is set or to throw the appropriate error.\n\tif err := c.WorkspaceID.Parse(); err != nil {\n\t\treturn err\n\t}\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\tinput := &mailinglist.ListInput{\n\t\tWorkspaceID: &c.WorkspaceID.Value,\n\t}\n\n\tfc, ok := c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tdata, err := mailinglist.List(context.TODO(), fc, input)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, data); ok {\n\t\treturn err\n\t}\n\n\ttext.PrintAlertTbl(out, data.Data)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/alert/mailinglist/mailinglist_test.go",
    "content": "package mailinglist_test\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\t\"testing\"\n\n\troot \"github.com/fastly/cli/pkg/commands/ngwaf\"\n\tworkspaceroot \"github.com/fastly/cli/pkg/commands/ngwaf/workspace\"\n\talertroot \"github.com/fastly/cli/pkg/commands/ngwaf/workspace/alert\"\n\tsub \"github.com/fastly/cli/pkg/commands/ngwaf/workspace/alert/mailinglist\"\n\tfstfmt \"github.com/fastly/cli/pkg/fmt\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/workspaces/alerts/mailinglist\"\n)\n\nconst (\n\talertID     = \"2b3c4d5e6f7890abcdef1234\"\n\tworkspaceID = \"nBw2ENWfOY1M2dpSwK1l5R\"\n\tdescription = \"TestMailingListAlert\"\n)\n\nvar (\n\taddress          = \"alerts@example.com\"\n\talertAddress1    = \"alerts1@example.com\"\n\talertAddress2    = \"alerts2@example.com\"\n\tmailinglistAlert = mailinglist.Alert{\n\t\tID:          alertID,\n\t\tType:        \"mailinglist\",\n\t\tDescription: description,\n\t\tCreatedAt:   \"2025-11-25T16:40:12Z\",\n\t\tCreatedBy:   \"test@example.com\",\n\t\tConfig: mailinglist.ResponseConfig{\n\t\t\tAddress: &address,\n\t\t},\n\t}\n)\n\nfunc TestMailingListAlertCreate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --workspace-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--address %s\", address),\n\t\t\tWantError: \"error reading workspace ID: no workspace ID found\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --address flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--workspace-id %s\", workspaceID),\n\t\t\tWantError: \"error parsing arguments: required flag --address not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --address %s\", workspaceID, address),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusCreated,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusCreated),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader((testutil.GenJSON(mailinglistAlert)))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Created a '%s' alert '%s' (workspace-id: %s)\", mailinglistAlert.Type, mailinglistAlert.ID, workspaceID),\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --address %s --json\", workspaceID, address),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusCreated,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusCreated),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader((testutil.GenJSON(mailinglistAlert)))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(mailinglistAlert),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, workspaceroot.CommandName, alertroot.CommandName, sub.CommandName, \"create\"}, scenarios)\n}\n\nfunc TestMailingListAlertList(t *testing.T) {\n\talertsObject := mailinglist.Alerts{\n\t\tData: []mailinglist.Alert{\n\t\t\t{\n\t\t\t\tID:          \"1a2b3c4d5e6f7890abcdef12\",\n\t\t\t\tType:        \"mailinglist\",\n\t\t\t\tDescription: \"First mailing list alert\",\n\t\t\t\tCreatedAt:   \"2025-11-25T16:40:12Z\",\n\t\t\t\tCreatedBy:   \"test@example.com\",\n\t\t\t\tConfig: mailinglist.ResponseConfig{\n\t\t\t\t\tAddress: &alertAddress1,\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tID:          \"2b3c4d5e6f7890abcdef1234\",\n\t\t\t\tType:        \"mailinglist\",\n\t\t\t\tDescription: \"Second mailing list alert\",\n\t\t\t\tCreatedAt:   \"2025-11-25T16:40:12Z\",\n\t\t\t\tCreatedBy:   \"test@example.com\",\n\t\t\t\tConfig: mailinglist.ResponseConfig{\n\t\t\t\t\tAddress: &alertAddress2,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tMeta: mailinglist.MetaAlerts{\n\t\t\tTotal: 2,\n\t\t},\n\t}\n\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --workspace-id flag\",\n\t\t\tArgs:      \"\",\n\t\t\tWantError: \"error reading workspace ID: no workspace ID found\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate internal server error\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s\", workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusInternalServerError,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusInternalServerError),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"500 - Internal Server Error\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success (zero alerts)\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s\", workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(mailinglist.Alerts{\n\t\t\t\t\t\t\tData: []mailinglist.Alert{},\n\t\t\t\t\t\t}))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: zeroListString,\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s\", workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(alertsObject))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: listString,\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --json\", workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(alertsObject))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(alertsObject),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, workspaceroot.CommandName, alertroot.CommandName, sub.CommandName, \"list\"}, scenarios)\n}\n\nfunc TestMailingListAlertGet(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --workspace-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--alert-id %s\", alertID),\n\t\t\tWantError: \"error reading workspace ID: no workspace ID found\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --alert-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--workspace-id %s\", workspaceID),\n\t\t\tWantError: \"error parsing arguments: required flag --alert-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate not found\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --alert-id invalid\", workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusNotFound,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusNotFound),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(`\n\t\t\t\t\t\t\t{\n    \t\t\t\t\t\t\t\"title\": \"This resource does not exist\",\n    \t\t\t\t\t\t\t\"status\": 404\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t`))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"404 - Not Found\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --alert-id %s\", workspaceID, alertID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader((testutil.GenJSON(mailinglistAlert)))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: alertString,\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --alert-id %s --json\", workspaceID, alertID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader((testutil.GenJSON(mailinglistAlert)))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(mailinglistAlert),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, workspaceroot.CommandName, alertroot.CommandName, sub.CommandName, \"get\"}, scenarios)\n}\n\nfunc TestMailingListAlertUpdate(t *testing.T) {\n\tupdatedAddress := \"updated@example.com\"\n\tupdatedAlert := mailinglist.Alert{\n\t\tID:          alertID,\n\t\tType:        \"mailingList\",\n\t\tDescription: \"Updated description\",\n\t\tCreatedAt:   \"2025-11-25T16:40:12Z\",\n\t\tCreatedBy:   \"test@example.com\",\n\t\tConfig: mailinglist.ResponseConfig{\n\t\t\tAddress: &updatedAddress,\n\t\t},\n\t}\n\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --workspace-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--alert-id %s --address %s\", alertID, address),\n\t\t\tWantError: \"error reading workspace ID: no workspace ID found\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --alert-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--workspace-id %s --address %s\", workspaceID, address),\n\t\t\tWantError: \"error parsing arguments: required flag --alert-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate not found\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --alert-id invalid --address updated@example.com\", workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusNotFound,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusNotFound),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(`\n\t\t\t\t\t\t\t{\n    \t\t\t\t\t\t\t\"title\": \"This resource does not exist\",\n    \t\t\t\t\t\t\t\"status\": 404\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t`))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"404 - Not Found\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success with address\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --alert-id %s --address updated@example.com\", workspaceID, alertID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MultiResponseRoundTripper{\n\t\t\t\t\tResponses: []*http.Response{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tStatusCode: http.StatusOK,\n\n\t\t\t\t\t\t\tStatus: http.StatusText(http.StatusOK),\n\n\t\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(mailinglistAlert))),\n\t\t\t\t\t\t},\n\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tStatusCode: http.StatusOK,\n\n\t\t\t\t\t\t\tStatus: http.StatusText(http.StatusOK),\n\n\t\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(updatedAlert))),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Updated '%s' alert '%s' (workspace-id: %s)\", updatedAlert.Type, updatedAlert.ID, workspaceID),\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --alert-id %s --address updated@example.com --json\", workspaceID, alertID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MultiResponseRoundTripper{\n\t\t\t\t\tResponses: []*http.Response{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tStatusCode: http.StatusOK,\n\n\t\t\t\t\t\t\tStatus: http.StatusText(http.StatusOK),\n\n\t\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(mailinglistAlert))),\n\t\t\t\t\t\t},\n\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tStatusCode: http.StatusOK,\n\n\t\t\t\t\t\t\tStatus: http.StatusText(http.StatusOK),\n\n\t\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(updatedAlert))),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(updatedAlert),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, workspaceroot.CommandName, alertroot.CommandName, sub.CommandName, \"update\"}, scenarios)\n}\n\nfunc TestMailingListAlertDelete(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --workspace-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--alert-id %s\", alertID),\n\t\t\tWantError: \"error reading workspace ID: no workspace ID found\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --alert-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--workspace-id %s\", workspaceID),\n\t\t\tWantError: \"error parsing arguments: required flag --alert-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate not found\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --alert-id invalid\", workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusNotFound,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusNotFound),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(`\n\t\t\t\t\t\t\t{\n    \t\t\t\t\t\t\t\"title\": \"This resource does not exist\",\n    \t\t\t\t\t\t\t\"status\": 404\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t`))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"404 - Not Found\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --alert-id %s\", workspaceID, alertID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusNoContent,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusNoContent),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Deleted alert '%s' (workspace-id: %s)\", alertID, workspaceID),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, workspaceroot.CommandName, alertroot.CommandName, sub.CommandName, \"delete\"}, scenarios)\n}\n\nvar alertString = strings.TrimSpace(`\nID: 2b3c4d5e6f7890abcdef1234\nType: mailinglist\nDescription: TestMailingListAlert\nCreated At: 2025-11-25T16:40:12Z\nCreated By: test@example.com\nConfig:\n  Address: alerts@example.com\n`)\n\nvar listString = strings.TrimSpace(`\nID                        Type         Description                Created At            Created By        Config\n1a2b3c4d5e6f7890abcdef12  mailinglist  First mailing list alert   2025-11-25T16:40:12Z  test@example.com  Address: alerts1@example.com\n2b3c4d5e6f7890abcdef1234  mailinglist  Second mailing list alert  2025-11-25T16:40:12Z  test@example.com  Address: alerts2@example.com\n`) + \"\\n\"\n\nvar zeroListString = strings.TrimSpace(`\nID  Type  Description  Created At  Created By  Config\n`) + \"\\n\"\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/alert/mailinglist/root.go",
    "content": "package mailinglist\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"mailinglist\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manage Mailing List workspace alerts\")\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n\n// AddressConfigFlags contains Address configurations used by mailing lists.\ntype AddressConfigFlags struct {\n\tAddress string\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/alert/mailinglist/update.go",
    "content": "package mailinglist\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/ngwaf/workspace/alert\"\n\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/workspaces/alerts/mailinglist\"\n)\n\n// UpdateCommand calls the Fastly API to update Mailing List alerts.\ntype UpdateCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tAlertID     string\n\tWorkspaceID argparser.OptionalWorkspaceID\n\tAddress     string\n}\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"update\", \"Update a Mailing List alert\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagNGWAFWorkspaceID,\n\t\tDescription: argparser.FlagNGWAFWorkspaceIDDesc,\n\t\tDst:         &c.WorkspaceID.Value,\n\t\tAction:      c.WorkspaceID.Set,\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagNGWAFAlertID,\n\t\tDescription: argparser.FlagNGWAFAlertIDDesc,\n\t\tDst:         &c.AlertID,\n\t\tRequired:    true,\n\t})\n\tc.CmdClause.Flag(\"address\", \"An email address.\").Required().StringVar(&c.Address)\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *UpdateCommand) Exec(_ io.Reader, out io.Writer) error {\n\t// Call Parse() to ensure that we check if workspaceID\n\t// is set or to throw the appropriate error.\n\tif err := c.WorkspaceID.Parse(); err != nil {\n\t\treturn err\n\t}\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tinput := &mailinglist.UpdateInput{\n\t\tAlertID:     &c.AlertID,\n\t\tWorkspaceID: &c.WorkspaceID.Value,\n\t\tConfig: &mailinglist.UpdateConfig{\n\t\t\tAddress: &c.Address,\n\t\t},\n\t\t// Set 'Events' to the only possible value, 'flag'\n\t\tEvents: alert.GetDefaultEvents(),\n\t}\n\n\tfc, ok := c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tdata, err := mailinglist.Update(context.TODO(), fc, input)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, data); ok {\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Updated '%s' alert '%s' (workspace-id: %s)\", data.Type, data.ID, c.WorkspaceID.Value)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/alert/microsoftteams/create.go",
    "content": "package microsoftteams\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/ngwaf/workspace/alert\"\n\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/workspaces/alerts/microsoftteams\"\n)\n\n// CreateCommand calls the Fastly API to create Microsoft Teams alerts.\ntype CreateCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tWorkspaceID argparser.OptionalWorkspaceID\n\tWebhook     string\n\n\t// Optional.\n\tDescription argparser.OptionalString\n}\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"create\", \"Create a Microsoft Teams alert\").Alias(\"add\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagNGWAFWorkspaceID,\n\t\tDescription: argparser.FlagNGWAFWorkspaceIDDesc,\n\t\tDst:         &c.WorkspaceID.Value,\n\t\tAction:      c.WorkspaceID.Set,\n\t})\n\tc.CmdClause.Flag(\"webhook\", \"Microsoft Teams webhook.\").Required().StringVar(&c.Webhook)\n\n\t// Optional.\n\tc.CmdClause.Flag(\"description\", \"An optional description for the alert.\").Action(c.Description.Set).StringVar(&c.Description.Value)\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *CreateCommand) Exec(_ io.Reader, out io.Writer) error {\n\t// Call Parse() to ensure that we check if workspaceID\n\t// is set or to throw the appropriate error.\n\tif err := c.WorkspaceID.Parse(); err != nil {\n\t\treturn err\n\t}\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tinput := &microsoftteams.CreateInput{\n\t\tWorkspaceID: &c.WorkspaceID.Value,\n\t\tConfig: &microsoftteams.CreateConfig{\n\t\t\tWebhook: &c.Webhook,\n\t\t},\n\t\t// Set 'Events' to the only possible value, 'flag'\n\t\tEvents: alert.GetDefaultEvents(),\n\t}\n\tif c.Description.WasSet {\n\t\tinput.Description = &c.Description.Value\n\t}\n\n\tfc, ok := c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tdata, err := microsoftteams.Create(context.TODO(), fc, input)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, data); ok {\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Created a '%s' alert '%s' (workspace-id: %s)\", data.Type, data.ID, c.WorkspaceID.Value)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/alert/microsoftteams/delete.go",
    "content": "package microsoftteams\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/workspaces/alerts/microsoftteams\"\n)\n\n// DeleteCommand calls the Fastly API to delete Microsoft Teams alerts.\ntype DeleteCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tAlertID     string\n\tWorkspaceID argparser.OptionalWorkspaceID\n}\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"delete\", \"Delete a Microsoft Teams alert\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagNGWAFWorkspaceID,\n\t\tDescription: argparser.FlagNGWAFWorkspaceIDDesc,\n\t\tDst:         &c.WorkspaceID.Value,\n\t\tAction:      c.WorkspaceID.Set,\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagNGWAFAlertID,\n\t\tDescription: argparser.FlagNGWAFAlertIDDesc,\n\t\tDst:         &c.AlertID,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DeleteCommand) Exec(_ io.Reader, out io.Writer) error {\n\t// Call Parse() to ensure that we check if workspaceID\n\t// is set or to throw the appropriate error.\n\tif err := c.WorkspaceID.Parse(); err != nil {\n\t\treturn err\n\t}\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\tfc, ok := c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\terr := microsoftteams.Delete(context.TODO(), fc, &microsoftteams.DeleteInput{\n\t\tWorkspaceID: &c.WorkspaceID.Value,\n\t\tAlertID:     &c.AlertID,\n\t})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif c.JSONOutput.Enabled {\n\t\to := struct {\n\t\t\tID      string `json:\"id\"`\n\t\t\tDeleted bool   `json:\"deleted\"`\n\t\t}{\n\t\t\tc.AlertID,\n\t\t\ttrue,\n\t\t}\n\t\t_, err := c.WriteJSON(out, o)\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Deleted alert '%s' (workspace-id: %s)\", c.AlertID, c.WorkspaceID.Value)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/alert/microsoftteams/doc.go",
    "content": "// Package microsoftteams contains commands to inspect and manipulate NGWAF Microsoft Teams alerts.\npackage microsoftteams\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/alert/microsoftteams/get.go",
    "content": "package microsoftteams\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/workspaces/alerts/microsoftteams\"\n)\n\n// GetCommand calls the Fastly API to get Microsoft Teams alerts.\ntype GetCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tAlertID     string\n\tWorkspaceID argparser.OptionalWorkspaceID\n}\n\n// NewGetCommand returns a usable command registered under the parent.\nfunc NewGetCommand(parent argparser.Registerer, g *global.Data) *GetCommand {\n\tc := GetCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"get\", \"Get a Microsoft Teams alert\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagNGWAFWorkspaceID,\n\t\tDescription: argparser.FlagNGWAFWorkspaceIDDesc,\n\t\tDst:         &c.WorkspaceID.Value,\n\t\tAction:      c.WorkspaceID.Set,\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagNGWAFAlertID,\n\t\tDescription: argparser.FlagNGWAFAlertIDDesc,\n\t\tDst:         &c.AlertID,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *GetCommand) Exec(_ io.Reader, out io.Writer) error {\n\t// Call Parse() to ensure that we check if workspaceID\n\t// is set or to throw the appropriate error.\n\tif err := c.WorkspaceID.Parse(); err != nil {\n\t\treturn err\n\t}\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\tinput := &microsoftteams.GetInput{\n\t\tAlertID:     &c.AlertID,\n\t\tWorkspaceID: &c.WorkspaceID.Value,\n\t}\n\n\tfc, ok := c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tdata, err := microsoftteams.Get(context.TODO(), fc, input)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, data); ok {\n\t\treturn err\n\t}\n\n\ttext.PrintAlert(out, data)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/alert/microsoftteams/list.go",
    "content": "package microsoftteams\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/workspaces/alerts/microsoftteams\"\n)\n\n// ListCommand calls the Fastly API to list Microsoft Teams alerts.\ntype ListCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tWorkspaceID argparser.OptionalWorkspaceID\n}\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"list\", \"List Microsoft Teams alerts\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagNGWAFWorkspaceID,\n\t\tDescription: argparser.FlagNGWAFWorkspaceIDDesc,\n\t\tDst:         &c.WorkspaceID.Value,\n\t\tAction:      c.WorkspaceID.Set,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *ListCommand) Exec(_ io.Reader, out io.Writer) error {\n\t// Call Parse() to ensure that we check if workspaceID\n\t// is set or to throw the appropriate error.\n\tif err := c.WorkspaceID.Parse(); err != nil {\n\t\treturn err\n\t}\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\tinput := &microsoftteams.ListInput{\n\t\tWorkspaceID: &c.WorkspaceID.Value,\n\t}\n\n\tfc, ok := c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tdata, err := microsoftteams.List(context.TODO(), fc, input)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, data); ok {\n\t\treturn err\n\t}\n\n\ttext.PrintAlertTbl(out, data.Data)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/alert/microsoftteams/microsoftteams_test.go",
    "content": "package microsoftteams_test\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\t\"testing\"\n\n\troot \"github.com/fastly/cli/pkg/commands/ngwaf\"\n\tworkspaceroot \"github.com/fastly/cli/pkg/commands/ngwaf/workspace\"\n\talertroot \"github.com/fastly/cli/pkg/commands/ngwaf/workspace/alert\"\n\tsub \"github.com/fastly/cli/pkg/commands/ngwaf/workspace/alert/microsoftteams\"\n\tfstfmt \"github.com/fastly/cli/pkg/fmt\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/workspaces/alerts/microsoftteams\"\n)\n\nconst (\n\talertID     = \"3c4d5e6f7890abcdef123456\"\n\tworkspaceID = \"nBw2ENWfOY1M2dpSwK1l5R\"\n\tdescription = \"TestMicrosoftTeamsAlert\"\n)\n\nvar (\n\twebhook    = \"https://outlook.office.com/webhook/example\"\n\tteamsAlert = microsoftteams.Alert{\n\t\tID:          alertID,\n\t\tType:        \"microsoftteams\",\n\t\tDescription: description,\n\t\tCreatedAt:   \"2025-11-25T16:40:12Z\",\n\t\tCreatedBy:   \"test@example.com\",\n\t\tConfig: microsoftteams.ResponseConfig{\n\t\t\tWebhook: &webhook,\n\t\t},\n\t}\n)\n\nfunc TestMicrosoftTeamsAlertCreate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --workspace-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--webhook %s\", webhook),\n\t\t\tWantError: \"error reading workspace ID: no workspace ID found\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --webhook flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--workspace-id %s\", workspaceID),\n\t\t\tWantError: \"error parsing arguments: required flag --webhook not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --webhook %s\", workspaceID, webhook),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusCreated,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusCreated),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader((testutil.GenJSON(teamsAlert)))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Created a '%s' alert '%s' (workspace-id: %s)\", teamsAlert.Type, teamsAlert.ID, workspaceID),\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --webhook %s --json\", workspaceID, webhook),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusCreated,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusCreated),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader((testutil.GenJSON(teamsAlert)))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(teamsAlert),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, workspaceroot.CommandName, alertroot.CommandName, sub.CommandName, \"create\"}, scenarios)\n}\n\nfunc TestMicrosoftTeamsAlertList(t *testing.T) {\n\talertsObject := microsoftteams.Alerts{\n\t\tData: []microsoftteams.Alert{\n\t\t\t{\n\t\t\t\tID:          \"1a2b3c4d5e6f7890abcdef12\",\n\t\t\t\tType:        \"microsoftteams\",\n\t\t\t\tDescription: \"First Microsoft Teams alert\",\n\t\t\t\tCreatedAt:   \"2025-11-25T16:40:12Z\",\n\t\t\t\tCreatedBy:   \"test@example.com\",\n\t\t\t\tConfig: microsoftteams.ResponseConfig{\n\t\t\t\t\tWebhook: &webhook,\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tID:          \"2b3c4d5e6f7890abcdef1234\",\n\t\t\t\tType:        \"microsoftteams\",\n\t\t\t\tDescription: \"Second Microsoft Teams alert\",\n\t\t\t\tCreatedAt:   \"2025-11-25T16:40:12Z\",\n\t\t\t\tCreatedBy:   \"test@example.com\",\n\t\t\t\tConfig: microsoftteams.ResponseConfig{\n\t\t\t\t\tWebhook: &webhook,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tMeta: microsoftteams.MetaAlerts{\n\t\t\tTotal: 2,\n\t\t},\n\t}\n\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --workspace-id flag\",\n\t\t\tArgs:      \"\",\n\t\t\tWantError: \"error reading workspace ID: no workspace ID found\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate internal server error\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s\", workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusInternalServerError,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusInternalServerError),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"500 - Internal Server Error\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success (zero alerts)\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s\", workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(microsoftteams.Alerts{\n\t\t\t\t\t\t\tData: []microsoftteams.Alert{},\n\t\t\t\t\t\t}))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: zeroListString,\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s\", workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(alertsObject))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: listString,\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --json\", workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(alertsObject))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(alertsObject),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, workspaceroot.CommandName, alertroot.CommandName, sub.CommandName, \"list\"}, scenarios)\n}\n\nfunc TestMicrosoftTeamsAlertGet(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --workspace-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--alert-id %s\", alertID),\n\t\t\tWantError: \"error reading workspace ID: no workspace ID found\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --alert-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--workspace-id %s\", workspaceID),\n\t\t\tWantError: \"error parsing arguments: required flag --alert-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate not found\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --alert-id invalid\", workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusNotFound,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusNotFound),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(`\n\t\t\t\t\t\t\t{\n    \t\t\t\t\t\t\t\"title\": \"This resource does not exist\",\n    \t\t\t\t\t\t\t\"status\": 404\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t`))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"404 - Not Found\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --alert-id %s\", workspaceID, alertID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader((testutil.GenJSON(teamsAlert)))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: alertString,\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --alert-id %s --json\", workspaceID, alertID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader((testutil.GenJSON(teamsAlert)))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(teamsAlert),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, workspaceroot.CommandName, alertroot.CommandName, sub.CommandName, \"get\"}, scenarios)\n}\n\nfunc TestMicrosoftTeamsAlertUpdate(t *testing.T) {\n\tupdatedWebhook := \"https://outlook.office.com/webhook/updated\"\n\tupdatedAlert := microsoftteams.Alert{\n\t\tID:          alertID,\n\t\tType:        \"microsoftteams\",\n\t\tDescription: \"Updated description\",\n\t\tCreatedAt:   \"2025-11-25T16:40:12Z\",\n\t\tCreatedBy:   \"test@example.com\",\n\t\tConfig: microsoftteams.ResponseConfig{\n\t\t\tWebhook: &updatedWebhook,\n\t\t},\n\t}\n\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --workspace-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--alert-id %s --webhook %s\", alertID, webhook),\n\t\t\tWantError: \"error reading workspace ID: no workspace ID found\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --alert-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--workspace-id %s --webhook %s\", workspaceID, webhook),\n\t\t\tWantError: \"error parsing arguments: required flag --alert-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate not found\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --alert-id invalid --webhook https://outlook.office.com/webhook/updated\", workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusNotFound,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusNotFound),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(`\n\t\t\t\t\t\t\t{\n    \t\t\t\t\t\t\t\"title\": \"This resource does not exist\",\n    \t\t\t\t\t\t\t\"status\": 404\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t`))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"404 - Not Found\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success with webhook\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --alert-id %s --webhook https://outlook.office.com/webhook/updated\", workspaceID, alertID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MultiResponseRoundTripper{\n\t\t\t\t\tResponses: []*http.Response{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tStatusCode: http.StatusOK,\n\n\t\t\t\t\t\t\tStatus: http.StatusText(http.StatusOK),\n\n\t\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(teamsAlert))),\n\t\t\t\t\t\t},\n\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tStatusCode: http.StatusOK,\n\n\t\t\t\t\t\t\tStatus: http.StatusText(http.StatusOK),\n\n\t\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(updatedAlert))),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Updated '%s' alert '%s' (workspace-id: %s)\", updatedAlert.Type, updatedAlert.ID, workspaceID),\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --alert-id %s --webhook https://outlook.office.com/webhook/updated --json\", workspaceID, alertID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MultiResponseRoundTripper{\n\t\t\t\t\tResponses: []*http.Response{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tStatusCode: http.StatusOK,\n\n\t\t\t\t\t\t\tStatus: http.StatusText(http.StatusOK),\n\n\t\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(teamsAlert))),\n\t\t\t\t\t\t},\n\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tStatusCode: http.StatusOK,\n\n\t\t\t\t\t\t\tStatus: http.StatusText(http.StatusOK),\n\n\t\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(updatedAlert))),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(updatedAlert),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, workspaceroot.CommandName, alertroot.CommandName, sub.CommandName, \"update\"}, scenarios)\n}\n\nfunc TestMicrosoftTeamsAlertDelete(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --workspace-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--alert-id %s\", alertID),\n\t\t\tWantError: \"error reading workspace ID: no workspace ID found\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --alert-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--workspace-id %s\", workspaceID),\n\t\t\tWantError: \"error parsing arguments: required flag --alert-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate not found\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --alert-id invalid\", workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusNotFound,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusNotFound),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(`\n\t\t\t\t\t\t\t{\n    \t\t\t\t\t\t\t\"title\": \"This resource does not exist\",\n    \t\t\t\t\t\t\t\"status\": 404\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t`))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"404 - Not Found\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --alert-id %s\", workspaceID, alertID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusNoContent,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusNoContent),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Deleted alert '%s' (workspace-id: %s)\", alertID, workspaceID),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, workspaceroot.CommandName, alertroot.CommandName, sub.CommandName, \"delete\"}, scenarios)\n}\n\nvar alertString = strings.TrimSpace(`\nID: 3c4d5e6f7890abcdef123456\nType: microsoftteams\nDescription: TestMicrosoftTeamsAlert\nCreated At: 2025-11-25T16:40:12Z\nCreated By: test@example.com\nConfig:\n  Webhook: <redacted>\n`)\n\nvar listString = strings.TrimSpace(`\nID                        Type            Description                   Created At            Created By        Config\n1a2b3c4d5e6f7890abcdef12  microsoftteams  First Microsoft Teams alert   2025-11-25T16:40:12Z  test@example.com  Webhook: <redacted>\n2b3c4d5e6f7890abcdef1234  microsoftteams  Second Microsoft Teams alert  2025-11-25T16:40:12Z  test@example.com  Webhook: <redacted>\n`) + \"\\n\"\n\nvar zeroListString = strings.TrimSpace(`\nID  Type  Description  Created At  Created By  Config\n`) + \"\\n\"\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/alert/microsoftteams/root.go",
    "content": "package microsoftteams\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"microsoftteams\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manage Microsoft Teams workspace alerts\")\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/alert/microsoftteams/update.go",
    "content": "package microsoftteams\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/ngwaf/workspace/alert\"\n\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/workspaces/alerts/microsoftteams\"\n)\n\n// UpdateCommand calls the Fastly API to update Microsoft Teams alerts.\ntype UpdateCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tAlertID     string\n\tWorkspaceID argparser.OptionalWorkspaceID\n\tWebhook     string\n}\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"update\", \"Update a Microsoft Teams alert\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagNGWAFWorkspaceID,\n\t\tDescription: argparser.FlagNGWAFWorkspaceIDDesc,\n\t\tDst:         &c.WorkspaceID.Value,\n\t\tAction:      c.WorkspaceID.Set,\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagNGWAFAlertID,\n\t\tDescription: argparser.FlagNGWAFAlertIDDesc,\n\t\tDst:         &c.AlertID,\n\t\tRequired:    true,\n\t})\n\tc.CmdClause.Flag(\"webhook\", \"Microsoft Teams webhook.\").Required().StringVar(&c.Webhook)\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *UpdateCommand) Exec(_ io.Reader, out io.Writer) error {\n\t// Call Parse() to ensure that we check if workspaceID\n\t// is set or to throw the appropriate error.\n\tif err := c.WorkspaceID.Parse(); err != nil {\n\t\treturn err\n\t}\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tinput := &microsoftteams.UpdateInput{\n\t\tAlertID:     &c.AlertID,\n\t\tWorkspaceID: &c.WorkspaceID.Value,\n\t\tConfig: &microsoftteams.UpdateConfig{\n\t\t\tWebhook: &c.Webhook,\n\t\t},\n\t\t// Set 'Events' to the only possible value, 'flag'\n\t\tEvents: alert.GetDefaultEvents(),\n\t}\n\n\tfc, ok := c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tdata, err := microsoftteams.Update(context.TODO(), fc, input)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, data); ok {\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Updated '%s' alert '%s' (workspace-id: %s)\", data.Type, data.ID, c.WorkspaceID.Value)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/alert/opsgenie/create.go",
    "content": "package opsgenie\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/ngwaf/workspace/alert\"\n\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/workspaces/alerts/opsgenie\"\n)\n\n// CreateCommand calls the Fastly API to create Opsgenie alerts.\ntype CreateCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tWorkspaceID argparser.OptionalWorkspaceID\n\tKey         string\n\n\t// Optional.\n\tDescription argparser.OptionalString\n}\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"create\", \"Create a Opsgenie alert\").Alias(\"add\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagNGWAFWorkspaceID,\n\t\tDescription: argparser.FlagNGWAFWorkspaceIDDesc,\n\t\tDst:         &c.WorkspaceID.Value,\n\t\tAction:      c.WorkspaceID.Set,\n\t})\n\tc.CmdClause.Flag(\"key\", \"Opsgenie integration key.\").Required().StringVar(&c.Key)\n\n\t// Optional.\n\tc.CmdClause.Flag(\"description\", \"An optional description for the alert.\").Action(c.Description.Set).StringVar(&c.Description.Value)\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *CreateCommand) Exec(_ io.Reader, out io.Writer) error {\n\t// Call Parse() to ensure that we check if workspaceID\n\t// is set or to throw the appropriate error.\n\tif err := c.WorkspaceID.Parse(); err != nil {\n\t\treturn err\n\t}\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tinput := &opsgenie.CreateInput{\n\t\tWorkspaceID: &c.WorkspaceID.Value,\n\t\tConfig: &opsgenie.CreateConfig{\n\t\t\tKey: &c.Key,\n\t\t},\n\t\t// Set 'Events' to the only possible value, 'flag'\n\t\tEvents: alert.GetDefaultEvents(),\n\t}\n\tif c.Description.WasSet {\n\t\tinput.Description = &c.Description.Value\n\t}\n\n\tfc, ok := c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tdata, err := opsgenie.Create(context.TODO(), fc, input)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, data); ok {\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Created a '%s' alert '%s' (workspace-id: %s)\", data.Type, data.ID, c.WorkspaceID.Value)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/alert/opsgenie/delete.go",
    "content": "package opsgenie\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/workspaces/alerts/opsgenie\"\n)\n\n// DeleteCommand calls the Fastly API to delete Opsgenie alerts.\ntype DeleteCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tAlertID     string\n\tWorkspaceID argparser.OptionalWorkspaceID\n}\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"delete\", \"Delete a Opsgenie alert\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagNGWAFWorkspaceID,\n\t\tDescription: argparser.FlagNGWAFWorkspaceIDDesc,\n\t\tDst:         &c.WorkspaceID.Value,\n\t\tAction:      c.WorkspaceID.Set,\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagNGWAFAlertID,\n\t\tDescription: argparser.FlagNGWAFAlertIDDesc,\n\t\tDst:         &c.AlertID,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DeleteCommand) Exec(_ io.Reader, out io.Writer) error {\n\t// Call Parse() to ensure that we check if workspaceID\n\t// is set or to throw the appropriate error.\n\tif err := c.WorkspaceID.Parse(); err != nil {\n\t\treturn err\n\t}\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\tfc, ok := c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\terr := opsgenie.Delete(context.TODO(), fc, &opsgenie.DeleteInput{\n\t\tWorkspaceID: &c.WorkspaceID.Value,\n\t\tAlertID:     &c.AlertID,\n\t})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif c.JSONOutput.Enabled {\n\t\to := struct {\n\t\t\tID      string `json:\"id\"`\n\t\t\tDeleted bool   `json:\"deleted\"`\n\t\t}{\n\t\t\tc.AlertID,\n\t\t\ttrue,\n\t\t}\n\t\t_, err := c.WriteJSON(out, o)\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Deleted alert '%s' (workspace-id: %s)\", c.AlertID, c.WorkspaceID.Value)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/alert/opsgenie/doc.go",
    "content": "// Package opsgenie contains commands to inspect and manipulate NGWAF Opsgenie alerts.\npackage opsgenie\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/alert/opsgenie/get.go",
    "content": "package opsgenie\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/workspaces/alerts/opsgenie\"\n)\n\n// GetCommand calls the Fastly API to get Opsgenie alerts.\ntype GetCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tAlertID     string\n\tWorkspaceID argparser.OptionalWorkspaceID\n}\n\n// NewGetCommand returns a usable command registered under the parent.\nfunc NewGetCommand(parent argparser.Registerer, g *global.Data) *GetCommand {\n\tc := GetCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"get\", \"Get a Opsgenie alert\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagNGWAFWorkspaceID,\n\t\tDescription: argparser.FlagNGWAFWorkspaceIDDesc,\n\t\tDst:         &c.WorkspaceID.Value,\n\t\tAction:      c.WorkspaceID.Set,\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagNGWAFAlertID,\n\t\tDescription: argparser.FlagNGWAFAlertIDDesc,\n\t\tDst:         &c.AlertID,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *GetCommand) Exec(_ io.Reader, out io.Writer) error {\n\t// Call Parse() to ensure that we check if workspaceID\n\t// is set or to throw the appropriate error.\n\tif err := c.WorkspaceID.Parse(); err != nil {\n\t\treturn err\n\t}\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\tinput := &opsgenie.GetInput{\n\t\tAlertID:     &c.AlertID,\n\t\tWorkspaceID: &c.WorkspaceID.Value,\n\t}\n\n\tfc, ok := c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tdata, err := opsgenie.Get(context.TODO(), fc, input)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, data); ok {\n\t\treturn err\n\t}\n\n\ttext.PrintAlert(out, data)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/alert/opsgenie/list.go",
    "content": "package opsgenie\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/workspaces/alerts/opsgenie\"\n)\n\n// ListCommand calls the Fastly API to list Opsgenie alerts.\ntype ListCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tWorkspaceID argparser.OptionalWorkspaceID\n}\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"list\", \"List Opsgenie alerts\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagNGWAFWorkspaceID,\n\t\tDescription: argparser.FlagNGWAFWorkspaceIDDesc,\n\t\tDst:         &c.WorkspaceID.Value,\n\t\tAction:      c.WorkspaceID.Set,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *ListCommand) Exec(_ io.Reader, out io.Writer) error {\n\t// Call Parse() to ensure that we check if workspaceID\n\t// is set or to throw the appropriate error.\n\tif err := c.WorkspaceID.Parse(); err != nil {\n\t\treturn err\n\t}\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\tinput := &opsgenie.ListInput{\n\t\tWorkspaceID: &c.WorkspaceID.Value,\n\t}\n\n\tfc, ok := c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tdata, err := opsgenie.List(context.TODO(), fc, input)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, data); ok {\n\t\treturn err\n\t}\n\n\ttext.PrintAlertTbl(out, data.Data)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/alert/opsgenie/opsgenie_test.go",
    "content": "package opsgenie_test\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\t\"testing\"\n\n\troot \"github.com/fastly/cli/pkg/commands/ngwaf\"\n\tworkspaceroot \"github.com/fastly/cli/pkg/commands/ngwaf/workspace\"\n\talertroot \"github.com/fastly/cli/pkg/commands/ngwaf/workspace/alert\"\n\tsub \"github.com/fastly/cli/pkg/commands/ngwaf/workspace/alert/opsgenie\"\n\tfstfmt \"github.com/fastly/cli/pkg/fmt\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/workspaces/alerts/opsgenie\"\n)\n\nconst (\n\talertID     = \"4d5e6f7890abcdef12345678\"\n\tworkspaceID = \"nBw2ENWfOY1M2dpSwK1l5R\"\n\tdescription = \"TestOpsgenieAlert\"\n)\n\nvar (\n\tkey           = \"a1b2c3d4-e5f6-7890-abcd-ef1234567890\"\n\topsgenieAlert = opsgenie.Alert{\n\t\tID:          alertID,\n\t\tType:        \"opsgenie\",\n\t\tDescription: description,\n\t\tCreatedAt:   \"2025-11-25T16:40:12Z\",\n\t\tCreatedBy:   \"test@example.com\",\n\t\tConfig: opsgenie.ResponseConfig{\n\t\t\tKey: &key,\n\t\t},\n\t}\n)\n\nfunc TestOpsgenieAlertCreate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --workspace-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--key %s\", key),\n\t\t\tWantError: \"error reading workspace ID: no workspace ID found\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --key flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--workspace-id %s\", workspaceID),\n\t\t\tWantError: \"error parsing arguments: required flag --key not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --key %s\", workspaceID, key),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusCreated,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusCreated),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader((testutil.GenJSON(opsgenieAlert)))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Created a '%s' alert '%s' (workspace-id: %s)\", opsgenieAlert.Type, opsgenieAlert.ID, workspaceID),\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success with description\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --key %s --description %s\", workspaceID, key, description),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusCreated,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusCreated),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader((testutil.GenJSON(opsgenieAlert)))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Created a '%s' alert '%s' (workspace-id: %s)\", opsgenieAlert.Type, opsgenieAlert.ID, workspaceID),\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --key %s --json\", workspaceID, key),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusCreated,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusCreated),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader((testutil.GenJSON(opsgenieAlert)))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(opsgenieAlert),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, workspaceroot.CommandName, alertroot.CommandName, sub.CommandName, \"create\"}, scenarios)\n}\n\nfunc TestOpsgenieAlertList(t *testing.T) {\n\talertsObject := opsgenie.Alerts{\n\t\tData: []opsgenie.Alert{\n\t\t\t{\n\t\t\t\tID:          \"1a2b3c4d5e6f7890abcdef12\",\n\t\t\t\tType:        \"opsgenie\",\n\t\t\t\tDescription: \"First Opsgenie alert\",\n\t\t\t\tCreatedAt:   \"2025-11-25T16:40:12Z\",\n\t\t\t\tCreatedBy:   \"test@example.com\",\n\t\t\t\tConfig: opsgenie.ResponseConfig{\n\t\t\t\t\tKey: &key,\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tID:          \"2b3c4d5e6f7890abcdef1234\",\n\t\t\t\tType:        \"opsgenie\",\n\t\t\t\tDescription: \"Second Opsgenie alert\",\n\t\t\t\tCreatedAt:   \"2025-11-25T16:40:12Z\",\n\t\t\t\tCreatedBy:   \"test@example.com\",\n\t\t\t\tConfig: opsgenie.ResponseConfig{\n\t\t\t\t\tKey: &key,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tMeta: opsgenie.MetaAlerts{\n\t\t\tTotal: 2,\n\t\t},\n\t}\n\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --workspace-id flag\",\n\t\t\tArgs:      \"\",\n\t\t\tWantError: \"error reading workspace ID: no workspace ID found\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate internal server error\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s\", workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusInternalServerError,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusInternalServerError),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"500 - Internal Server Error\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success (zero alerts)\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s\", workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(opsgenie.Alerts{\n\t\t\t\t\t\t\tData: []opsgenie.Alert{},\n\t\t\t\t\t\t}))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: zeroListString,\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s\", workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(alertsObject))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: listString,\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --json\", workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(alertsObject))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(alertsObject),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, workspaceroot.CommandName, alertroot.CommandName, sub.CommandName, \"list\"}, scenarios)\n}\n\nfunc TestOpsgenieAlertGet(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --workspace-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--alert-id %s\", alertID),\n\t\t\tWantError: \"error reading workspace ID: no workspace ID found\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --alert-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--workspace-id %s\", workspaceID),\n\t\t\tWantError: \"error parsing arguments: required flag --alert-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate not found\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --alert-id invalid\", workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusNotFound,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusNotFound),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(`\n\t\t\t\t\t\t\t{\n    \t\t\t\t\t\t\t\"title\": \"This resource does not exist\",\n    \t\t\t\t\t\t\t\"status\": 404\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t`))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"404 - Not Found\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --alert-id %s\", workspaceID, alertID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader((testutil.GenJSON(opsgenieAlert)))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: alertString,\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --alert-id %s --json\", workspaceID, alertID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader((testutil.GenJSON(opsgenieAlert)))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(opsgenieAlert),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, workspaceroot.CommandName, alertroot.CommandName, sub.CommandName, \"get\"}, scenarios)\n}\n\nfunc TestOpsgenieAlertUpdate(t *testing.T) {\n\tupdatedKey := \"updated-key-1234\"\n\tupdatedAlert := opsgenie.Alert{\n\t\tID:          alertID,\n\t\tType:        \"opsgenie\",\n\t\tDescription: \"Updated description\",\n\t\tCreatedAt:   \"2025-11-25T16:40:12Z\",\n\t\tCreatedBy:   \"test@example.com\",\n\t\tConfig: opsgenie.ResponseConfig{\n\t\t\tKey: &updatedKey,\n\t\t},\n\t}\n\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --workspace-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--alert-id %s --key %s\", alertID, key),\n\t\t\tWantError: \"error reading workspace ID: no workspace ID found\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --alert-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--workspace-id %s --key %s\", workspaceID, key),\n\t\t\tWantError: \"error parsing arguments: required flag --alert-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate not found\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --alert-id invalid --key updated-key-1234\", workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusNotFound,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusNotFound),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(`\n\t\t\t\t\t\t\t{\n    \t\t\t\t\t\t\t\"title\": \"This resource does not exist\",\n    \t\t\t\t\t\t\t\"status\": 404\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t`))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"404 - Not Found\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success with key\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --alert-id %s --key updated-key-1234\", workspaceID, alertID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MultiResponseRoundTripper{\n\t\t\t\t\tResponses: []*http.Response{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tStatusCode: http.StatusOK,\n\n\t\t\t\t\t\t\tStatus: http.StatusText(http.StatusOK),\n\n\t\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(opsgenieAlert))),\n\t\t\t\t\t\t},\n\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tStatusCode: http.StatusOK,\n\n\t\t\t\t\t\t\tStatus: http.StatusText(http.StatusOK),\n\n\t\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(updatedAlert))),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Updated '%s' alert '%s' (workspace-id: %s)\", updatedAlert.Type, updatedAlert.ID, workspaceID),\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --alert-id %s --key updated-key-1234 --json\", workspaceID, alertID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MultiResponseRoundTripper{\n\t\t\t\t\tResponses: []*http.Response{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tStatusCode: http.StatusOK,\n\n\t\t\t\t\t\t\tStatus: http.StatusText(http.StatusOK),\n\n\t\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(opsgenieAlert))),\n\t\t\t\t\t\t},\n\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tStatusCode: http.StatusOK,\n\n\t\t\t\t\t\t\tStatus: http.StatusText(http.StatusOK),\n\n\t\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(updatedAlert))),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(updatedAlert),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, workspaceroot.CommandName, alertroot.CommandName, sub.CommandName, \"update\"}, scenarios)\n}\n\nfunc TestOpsgenieAlertDelete(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --workspace-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--alert-id %s\", alertID),\n\t\t\tWantError: \"error reading workspace ID: no workspace ID found\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --alert-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--workspace-id %s\", workspaceID),\n\t\t\tWantError: \"error parsing arguments: required flag --alert-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate not found\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --alert-id invalid\", workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusNotFound,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusNotFound),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(`\n\t\t\t\t\t\t\t{\n    \t\t\t\t\t\t\t\"title\": \"This resource does not exist\",\n    \t\t\t\t\t\t\t\"status\": 404\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t`))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"404 - Not Found\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --alert-id %s\", workspaceID, alertID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusNoContent,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusNoContent),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Deleted alert '%s' (workspace-id: %s)\", alertID, workspaceID),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, workspaceroot.CommandName, alertroot.CommandName, sub.CommandName, \"delete\"}, scenarios)\n}\n\nvar alertString = strings.TrimSpace(`\nID: 4d5e6f7890abcdef12345678\nType: opsgenie\nDescription: TestOpsgenieAlert\nCreated At: 2025-11-25T16:40:12Z\nCreated By: test@example.com\nConfig:\n  Key: <redacted>\n`)\n\nvar listString = strings.TrimSpace(`\nID                        Type      Description            Created At            Created By        Config\n1a2b3c4d5e6f7890abcdef12  opsgenie  First Opsgenie alert   2025-11-25T16:40:12Z  test@example.com  Key: <redacted>\n2b3c4d5e6f7890abcdef1234  opsgenie  Second Opsgenie alert  2025-11-25T16:40:12Z  test@example.com  Key: <redacted>\n`) + \"\\n\"\n\nvar zeroListString = strings.TrimSpace(`\nID  Type  Description  Created At  Created By  Config\n`) + \"\\n\"\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/alert/opsgenie/root.go",
    "content": "package opsgenie\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"opsgenie\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manage Opsgenie workspace alerts\")\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/alert/opsgenie/update.go",
    "content": "package opsgenie\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/ngwaf/workspace/alert\"\n\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/workspaces/alerts/opsgenie\"\n)\n\n// UpdateCommand calls the Fastly API to update Opsgenie alerts.\ntype UpdateCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tAlertID     string\n\tWorkspaceID argparser.OptionalWorkspaceID\n\tKey         string\n}\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"update\", \"Update a Opsgenie alert\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagNGWAFWorkspaceID,\n\t\tDescription: argparser.FlagNGWAFWorkspaceIDDesc,\n\t\tDst:         &c.WorkspaceID.Value,\n\t\tAction:      c.WorkspaceID.Set,\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagNGWAFAlertID,\n\t\tDescription: argparser.FlagNGWAFAlertIDDesc,\n\t\tDst:         &c.AlertID,\n\t\tRequired:    true,\n\t})\n\tc.CmdClause.Flag(\"key\", \"Opsgenie integration key.\").Required().StringVar(&c.Key)\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *UpdateCommand) Exec(_ io.Reader, out io.Writer) error {\n\t// Call Parse() to ensure that we check if workspaceID\n\t// is set or to throw the appropriate error.\n\tif err := c.WorkspaceID.Parse(); err != nil {\n\t\treturn err\n\t}\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tinput := &opsgenie.UpdateInput{\n\t\tAlertID:     &c.AlertID,\n\t\tWorkspaceID: &c.WorkspaceID.Value,\n\t\tConfig: &opsgenie.UpdateConfig{\n\t\t\tKey: &c.Key,\n\t\t},\n\t\t// Set 'Events' to the only possible value, 'flag'\n\t\tEvents: alert.GetDefaultEvents(),\n\t}\n\n\tfc, ok := c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tdata, err := opsgenie.Update(context.TODO(), fc, input)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, data); ok {\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Updated '%s' alert '%s' (workspace-id: %s)\", data.Type, data.ID, c.WorkspaceID.Value)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/alert/pagerduty/create.go",
    "content": "package pagerduty\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/ngwaf/workspace/alert\"\n\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/workspaces/alerts/pagerduty\"\n)\n\n// CreateCommand calls the Fastly API to create PagerDuty alerts.\ntype CreateCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tWorkspaceID argparser.OptionalWorkspaceID\n\tKey         string\n\n\t// Optional.\n\tDescription argparser.OptionalString\n}\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"create\", \"Create a PagerDuty alert\").Alias(\"add\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagNGWAFWorkspaceID,\n\t\tDescription: argparser.FlagNGWAFWorkspaceIDDesc,\n\t\tDst:         &c.WorkspaceID.Value,\n\t\tAction:      c.WorkspaceID.Set,\n\t})\n\tc.CmdClause.Flag(\"key\", \"PagerDuty integration key.\").Required().StringVar(&c.Key)\n\n\t// Optional.\n\tc.CmdClause.Flag(\"description\", \"An optional description for the alert.\").Action(c.Description.Set).StringVar(&c.Description.Value)\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *CreateCommand) Exec(_ io.Reader, out io.Writer) error {\n\t// Call Parse() to ensure that we check if workspaceID\n\t// is set or to throw the appropriate error.\n\tif err := c.WorkspaceID.Parse(); err != nil {\n\t\treturn err\n\t}\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tinput := &pagerduty.CreateInput{\n\t\tWorkspaceID: &c.WorkspaceID.Value,\n\t\tConfig: &pagerduty.CreateConfig{\n\t\t\tKey: &c.Key,\n\t\t},\n\t\t// Set 'Events' to the only possible value, 'flag'\n\t\tEvents: alert.GetDefaultEvents(),\n\t}\n\tif c.Description.WasSet {\n\t\tinput.Description = &c.Description.Value\n\t}\n\n\tfc, ok := c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tdata, err := pagerduty.Create(context.TODO(), fc, input)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, data); ok {\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Created a '%s' alert '%s' (workspace-id: %s)\", data.Type, data.ID, c.WorkspaceID.Value)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/alert/pagerduty/delete.go",
    "content": "package pagerduty\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/workspaces/alerts/pagerduty\"\n)\n\n// DeleteCommand calls the Fastly API to delete PagerDuty alerts.\ntype DeleteCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tAlertID     string\n\tWorkspaceID argparser.OptionalWorkspaceID\n}\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"delete\", \"Delete a PagerDuty alert\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagNGWAFWorkspaceID,\n\t\tDescription: argparser.FlagNGWAFWorkspaceIDDesc,\n\t\tDst:         &c.WorkspaceID.Value,\n\t\tAction:      c.WorkspaceID.Set,\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagNGWAFAlertID,\n\t\tDescription: argparser.FlagNGWAFAlertIDDesc,\n\t\tDst:         &c.AlertID,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DeleteCommand) Exec(_ io.Reader, out io.Writer) error {\n\t// Call Parse() to ensure that we check if workspaceID\n\t// is set or to throw the appropriate error.\n\tif err := c.WorkspaceID.Parse(); err != nil {\n\t\treturn err\n\t}\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\tfc, ok := c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\terr := pagerduty.Delete(context.TODO(), fc, &pagerduty.DeleteInput{\n\t\tWorkspaceID: &c.WorkspaceID.Value,\n\t\tAlertID:     &c.AlertID,\n\t})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif c.JSONOutput.Enabled {\n\t\to := struct {\n\t\t\tID      string `json:\"id\"`\n\t\t\tDeleted bool   `json:\"deleted\"`\n\t\t}{\n\t\t\tc.AlertID,\n\t\t\ttrue,\n\t\t}\n\t\t_, err := c.WriteJSON(out, o)\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Deleted alert '%s' (workspace-id: %s)\", c.AlertID, c.WorkspaceID.Value)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/alert/pagerduty/doc.go",
    "content": "// Package pagerduty contains commands to inspect and manipulate NGWAF PagerDuty alerts.\npackage pagerduty\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/alert/pagerduty/get.go",
    "content": "package pagerduty\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/workspaces/alerts/pagerduty\"\n)\n\n// GetCommand calls the Fastly API to get PagerDuty alerts.\ntype GetCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tAlertID     string\n\tWorkspaceID argparser.OptionalWorkspaceID\n}\n\n// NewGetCommand returns a usable command registered under the parent.\nfunc NewGetCommand(parent argparser.Registerer, g *global.Data) *GetCommand {\n\tc := GetCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"get\", \"Get a PagerDuty alert\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagNGWAFWorkspaceID,\n\t\tDescription: argparser.FlagNGWAFWorkspaceIDDesc,\n\t\tDst:         &c.WorkspaceID.Value,\n\t\tAction:      c.WorkspaceID.Set,\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagNGWAFAlertID,\n\t\tDescription: argparser.FlagNGWAFAlertIDDesc,\n\t\tDst:         &c.AlertID,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *GetCommand) Exec(_ io.Reader, out io.Writer) error {\n\t// Call Parse() to ensure that we check if workspaceID\n\t// is set or to throw the appropriate error.\n\tif err := c.WorkspaceID.Parse(); err != nil {\n\t\treturn err\n\t}\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\tinput := &pagerduty.GetInput{\n\t\tAlertID:     &c.AlertID,\n\t\tWorkspaceID: &c.WorkspaceID.Value,\n\t}\n\n\tfc, ok := c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tdata, err := pagerduty.Get(context.TODO(), fc, input)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, data); ok {\n\t\treturn err\n\t}\n\n\ttext.PrintAlert(out, data)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/alert/pagerduty/list.go",
    "content": "package pagerduty\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/workspaces/alerts/pagerduty\"\n)\n\n// ListCommand calls the Fastly API to list PagerDuty alerts.\ntype ListCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tWorkspaceID argparser.OptionalWorkspaceID\n}\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"list\", \"List PagerDuty alerts\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagNGWAFWorkspaceID,\n\t\tDescription: argparser.FlagNGWAFWorkspaceIDDesc,\n\t\tDst:         &c.WorkspaceID.Value,\n\t\tAction:      c.WorkspaceID.Set,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *ListCommand) Exec(_ io.Reader, out io.Writer) error {\n\t// Call Parse() to ensure that we check if workspaceID\n\t// is set or to throw the appropriate error.\n\tif err := c.WorkspaceID.Parse(); err != nil {\n\t\treturn err\n\t}\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\tinput := &pagerduty.ListInput{\n\t\tWorkspaceID: &c.WorkspaceID.Value,\n\t}\n\n\tfc, ok := c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tdata, err := pagerduty.List(context.TODO(), fc, input)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, data); ok {\n\t\treturn err\n\t}\n\n\ttext.PrintAlertTbl(out, data.Data)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/alert/pagerduty/pagerduty_test.go",
    "content": "package pagerduty_test\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\t\"testing\"\n\n\troot \"github.com/fastly/cli/pkg/commands/ngwaf\"\n\tworkspaceroot \"github.com/fastly/cli/pkg/commands/ngwaf/workspace\"\n\talertroot \"github.com/fastly/cli/pkg/commands/ngwaf/workspace/alert\"\n\tsub \"github.com/fastly/cli/pkg/commands/ngwaf/workspace/alert/pagerduty\"\n\tfstfmt \"github.com/fastly/cli/pkg/fmt\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/workspaces/alerts/pagerduty\"\n)\n\nconst (\n\talertID     = \"5e6f7890abcdef1234567890\"\n\tworkspaceID = \"nBw2ENWfOY1M2dpSwK1l5R\"\n\tdescription = \"TestPagerDutyAlert\"\n)\n\nvar (\n\tkey            = \"a1b2c3d4e5f67890abcdef1234567890\"\n\tpagerdutyAlert = pagerduty.Alert{\n\t\tID:          alertID,\n\t\tType:        \"pagerduty\",\n\t\tDescription: description,\n\t\tCreatedAt:   \"2025-11-25T16:40:12Z\",\n\t\tCreatedBy:   \"test@example.com\",\n\t\tConfig: pagerduty.ResponseConfig{\n\t\t\tKey: &key,\n\t\t},\n\t}\n)\n\nfunc TestPagerDutyAlertCreate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --workspace-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--key %s\", key),\n\t\t\tWantError: \"error reading workspace ID: no workspace ID found\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --key flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--workspace-id %s\", workspaceID),\n\t\t\tWantError: \"error parsing arguments: required flag --key not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --key %s\", workspaceID, key),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusCreated,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusCreated),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader((testutil.GenJSON(pagerdutyAlert)))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Created a '%s' alert '%s' (workspace-id: %s)\", pagerdutyAlert.Type, pagerdutyAlert.ID, workspaceID),\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --key %s --json\", workspaceID, key),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusCreated,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusCreated),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader((testutil.GenJSON(pagerdutyAlert)))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(pagerdutyAlert),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, workspaceroot.CommandName, alertroot.CommandName, sub.CommandName, \"create\"}, scenarios)\n}\n\nfunc TestPagerDutyAlertList(t *testing.T) {\n\talertsObject := pagerduty.Alerts{\n\t\tData: []pagerduty.Alert{\n\t\t\t{\n\t\t\t\tID:          \"1a2b3c4d5e6f7890abcdef12\",\n\t\t\t\tType:        \"pagerduty\",\n\t\t\t\tDescription: \"First PagerDuty alert\",\n\t\t\t\tCreatedAt:   \"2025-11-25T16:40:12Z\",\n\t\t\t\tCreatedBy:   \"test@example.com\",\n\t\t\t\tConfig: pagerduty.ResponseConfig{\n\t\t\t\t\tKey: &key,\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tID:          \"2b3c4d5e6f7890abcdef1234\",\n\t\t\t\tType:        \"pagerduty\",\n\t\t\t\tDescription: \"Second PagerDuty alert\",\n\t\t\t\tCreatedAt:   \"2025-11-25T16:40:12Z\",\n\t\t\t\tCreatedBy:   \"test@example.com\",\n\t\t\t\tConfig: pagerduty.ResponseConfig{\n\t\t\t\t\tKey: &key,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tMeta: pagerduty.MetaAlerts{\n\t\t\tTotal: 2,\n\t\t},\n\t}\n\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --workspace-id flag\",\n\t\t\tArgs:      \"\",\n\t\t\tWantError: \"error reading workspace ID: no workspace ID found\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate internal server error\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s\", workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusInternalServerError,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusInternalServerError),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"500 - Internal Server Error\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success (zero alerts)\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s\", workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(pagerduty.Alerts{\n\t\t\t\t\t\t\tData: []pagerduty.Alert{},\n\t\t\t\t\t\t}))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: zeroListString,\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s\", workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(alertsObject))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: listString,\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --json\", workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(alertsObject))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(alertsObject),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, workspaceroot.CommandName, alertroot.CommandName, sub.CommandName, \"list\"}, scenarios)\n}\n\nfunc TestPagerDutyAlertGet(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --workspace-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--alert-id %s\", alertID),\n\t\t\tWantError: \"error reading workspace ID: no workspace ID found\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --alert-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--workspace-id %s\", workspaceID),\n\t\t\tWantError: \"error parsing arguments: required flag --alert-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate not found\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --alert-id invalid\", workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusNotFound,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusNotFound),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(`\n\t\t\t\t\t\t\t{\n    \t\t\t\t\t\t\t\"title\": \"This resource does not exist\",\n    \t\t\t\t\t\t\t\"status\": 404\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t`))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"404 - Not Found\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --alert-id %s\", workspaceID, alertID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader((testutil.GenJSON(pagerdutyAlert)))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: alertString,\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --alert-id %s --json\", workspaceID, alertID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader((testutil.GenJSON(pagerdutyAlert)))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(pagerdutyAlert),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, workspaceroot.CommandName, alertroot.CommandName, sub.CommandName, \"get\"}, scenarios)\n}\n\nfunc TestPagerDutyAlertUpdate(t *testing.T) {\n\tupdatedKey := \"updated-key-9876543210\"\n\tupdatedAlert := pagerduty.Alert{\n\t\tID:          alertID,\n\t\tType:        \"pagerduty\",\n\t\tDescription: \"Updated description\",\n\t\tCreatedAt:   \"2025-11-25T16:40:12Z\",\n\t\tCreatedBy:   \"test@example.com\",\n\t\tConfig: pagerduty.ResponseConfig{\n\t\t\tKey: &updatedKey,\n\t\t},\n\t}\n\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --workspace-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--alert-id %s --key %s\", alertID, key),\n\t\t\tWantError: \"error reading workspace ID: no workspace ID found\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --alert-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--workspace-id %s --key %s\", workspaceID, key),\n\t\t\tWantError: \"error parsing arguments: required flag --alert-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate not found\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --alert-id invalid --key updated-key-9876543210\", workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusNotFound,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusNotFound),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(`\n\t\t\t\t\t\t\t{\n    \t\t\t\t\t\t\t\"title\": \"This resource does not exist\",\n    \t\t\t\t\t\t\t\"status\": 404\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t`))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"404 - Not Found\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success with key\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --alert-id %s --key updated-key-9876543210\", workspaceID, alertID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MultiResponseRoundTripper{\n\t\t\t\t\tResponses: []*http.Response{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tStatusCode: http.StatusOK,\n\n\t\t\t\t\t\t\tStatus: http.StatusText(http.StatusOK),\n\n\t\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(pagerdutyAlert))),\n\t\t\t\t\t\t},\n\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tStatusCode: http.StatusOK,\n\n\t\t\t\t\t\t\tStatus: http.StatusText(http.StatusOK),\n\n\t\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(updatedAlert))),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Updated '%s' alert '%s' (workspace-id: %s)\", updatedAlert.Type, updatedAlert.ID, workspaceID),\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --alert-id %s --key updated-key-9876543210 --json\", workspaceID, alertID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MultiResponseRoundTripper{\n\t\t\t\t\tResponses: []*http.Response{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tStatusCode: http.StatusOK,\n\n\t\t\t\t\t\t\tStatus: http.StatusText(http.StatusOK),\n\n\t\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(pagerdutyAlert))),\n\t\t\t\t\t\t},\n\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tStatusCode: http.StatusOK,\n\n\t\t\t\t\t\t\tStatus: http.StatusText(http.StatusOK),\n\n\t\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(updatedAlert))),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(updatedAlert),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, workspaceroot.CommandName, alertroot.CommandName, sub.CommandName, \"update\"}, scenarios)\n}\n\nfunc TestPagerDutyAlertDelete(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --workspace-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--alert-id %s\", alertID),\n\t\t\tWantError: \"error reading workspace ID: no workspace ID found\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --alert-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--workspace-id %s\", workspaceID),\n\t\t\tWantError: \"error parsing arguments: required flag --alert-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate not found\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --alert-id invalid\", workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusNotFound,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusNotFound),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(`\n\t\t\t\t\t\t\t{\n    \t\t\t\t\t\t\t\"title\": \"This resource does not exist\",\n    \t\t\t\t\t\t\t\"status\": 404\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t`))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"404 - Not Found\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --alert-id %s\", workspaceID, alertID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusNoContent,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusNoContent),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Deleted alert '%s' (workspace-id: %s)\", alertID, workspaceID),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, workspaceroot.CommandName, alertroot.CommandName, sub.CommandName, \"delete\"}, scenarios)\n}\n\nvar alertString = strings.TrimSpace(`\nID: 5e6f7890abcdef1234567890\nType: pagerduty\nDescription: TestPagerDutyAlert\nCreated At: 2025-11-25T16:40:12Z\nCreated By: test@example.com\nConfig:\n  Key: <redacted>\n`)\n\nvar listString = strings.TrimSpace(`\nID                        Type       Description             Created At            Created By        Config\n1a2b3c4d5e6f7890abcdef12  pagerduty  First PagerDuty alert   2025-11-25T16:40:12Z  test@example.com  Key: <redacted>\n2b3c4d5e6f7890abcdef1234  pagerduty  Second PagerDuty alert  2025-11-25T16:40:12Z  test@example.com  Key: <redacted>\n`) + \"\\n\"\n\nvar zeroListString = strings.TrimSpace(`\nID  Type  Description  Created At  Created By  Config\n`) + \"\\n\"\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/alert/pagerduty/root.go",
    "content": "package pagerduty\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"pagerduty\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manage PagerDuty workspace alerts\")\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/alert/pagerduty/update.go",
    "content": "package pagerduty\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/ngwaf/workspace/alert\"\n\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/workspaces/alerts/pagerduty\"\n)\n\n// UpdateCommand calls the Fastly API to update PagerDuty alerts.\ntype UpdateCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tAlertID     string\n\tWorkspaceID argparser.OptionalWorkspaceID\n\tKey         string\n}\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"update\", \"Update a PagerDuty alert\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagNGWAFWorkspaceID,\n\t\tDescription: argparser.FlagNGWAFWorkspaceIDDesc,\n\t\tDst:         &c.WorkspaceID.Value,\n\t\tAction:      c.WorkspaceID.Set,\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagNGWAFAlertID,\n\t\tDescription: argparser.FlagNGWAFAlertIDDesc,\n\t\tDst:         &c.AlertID,\n\t\tRequired:    true,\n\t})\n\tc.CmdClause.Flag(\"key\", \"PagerDuty integration key.\").Required().StringVar(&c.Key)\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *UpdateCommand) Exec(_ io.Reader, out io.Writer) error {\n\t// Call Parse() to ensure that we check if workspaceID\n\t// is set or to throw the appropriate error.\n\tif err := c.WorkspaceID.Parse(); err != nil {\n\t\treturn err\n\t}\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tinput := &pagerduty.UpdateInput{\n\t\tAlertID:     &c.AlertID,\n\t\tWorkspaceID: &c.WorkspaceID.Value,\n\t\tConfig: &pagerduty.UpdateConfig{\n\t\t\tKey: &c.Key,\n\t\t},\n\t\t// Set 'Events' to the only possible value, 'flag'\n\t\tEvents: alert.GetDefaultEvents(),\n\t}\n\n\tfc, ok := c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tdata, err := pagerduty.Update(context.TODO(), fc, input)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, data); ok {\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Updated '%s' alert '%s' (workspace-id: %s)\", data.Type, data.ID, c.WorkspaceID.Value)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/alert/root.go",
    "content": "package alert\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"alert\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manage workspace alerts\")\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n\n// GetDefaultEvents returns the hardcoded events value for all alerts.\n// Currently the only supported value is \"flag\".\nfunc GetDefaultEvents() *[]string {\n\tevents := []string{\"flag\"}\n\treturn &events\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/alert/slack/create.go",
    "content": "package slack\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/ngwaf/workspace/alert\"\n\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/workspaces/alerts/slack\"\n)\n\n// CreateCommand calls the Fastly API to create Slack alerts.\ntype CreateCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tWorkspaceID argparser.OptionalWorkspaceID\n\tWebhook     string\n\n\t// Optional.\n\tDescription argparser.OptionalString\n}\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"create\", \"Create a Slack alert\").Alias(\"add\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagNGWAFWorkspaceID,\n\t\tDescription: argparser.FlagNGWAFWorkspaceIDDesc,\n\t\tDst:         &c.WorkspaceID.Value,\n\t\tAction:      c.WorkspaceID.Set,\n\t})\n\tc.CmdClause.Flag(\"webhook\", \"Slack webhook.\").Required().StringVar(&c.Webhook)\n\n\t// Optional.\n\tc.CmdClause.Flag(\"description\", \"An optional description for the alert.\").Action(c.Description.Set).StringVar(&c.Description.Value)\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *CreateCommand) Exec(_ io.Reader, out io.Writer) error {\n\t// Call Parse() to ensure that we check if workspaceID\n\t// is set or to throw the appropriate error.\n\tif err := c.WorkspaceID.Parse(); err != nil {\n\t\treturn err\n\t}\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tinput := &slack.CreateInput{\n\t\tWorkspaceID: &c.WorkspaceID.Value,\n\t\tConfig: &slack.CreateConfig{\n\t\t\tWebhook: &c.Webhook,\n\t\t},\n\t\t// Set 'Events' to the only possible value, 'flag'\n\t\tEvents: alert.GetDefaultEvents(),\n\t}\n\tif c.Description.WasSet {\n\t\tinput.Description = &c.Description.Value\n\t}\n\n\tfc, ok := c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tdata, err := slack.Create(context.TODO(), fc, input)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, data); ok {\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Created a '%s' alert '%s' (workspace-id: %s)\", data.Type, data.ID, c.WorkspaceID.Value)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/alert/slack/delete.go",
    "content": "package slack\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/workspaces/alerts/slack\"\n)\n\n// DeleteCommand calls the Fastly API to delete Slack alerts.\ntype DeleteCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tAlertID     string\n\tWorkspaceID argparser.OptionalWorkspaceID\n}\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"delete\", \"Delete a Slack alert\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagNGWAFWorkspaceID,\n\t\tDescription: argparser.FlagNGWAFWorkspaceIDDesc,\n\t\tDst:         &c.WorkspaceID.Value,\n\t\tAction:      c.WorkspaceID.Set,\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagNGWAFAlertID,\n\t\tDescription: argparser.FlagNGWAFAlertIDDesc,\n\t\tDst:         &c.AlertID,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DeleteCommand) Exec(_ io.Reader, out io.Writer) error {\n\t// Call Parse() to ensure that we check if workspaceID\n\t// is set or to throw the appropriate error.\n\tif err := c.WorkspaceID.Parse(); err != nil {\n\t\treturn err\n\t}\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\tfc, ok := c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\terr := slack.Delete(context.TODO(), fc, &slack.DeleteInput{\n\t\tWorkspaceID: &c.WorkspaceID.Value,\n\t\tAlertID:     &c.AlertID,\n\t})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif c.JSONOutput.Enabled {\n\t\to := struct {\n\t\t\tID      string `json:\"id\"`\n\t\t\tDeleted bool   `json:\"deleted\"`\n\t\t}{\n\t\t\tc.AlertID,\n\t\t\ttrue,\n\t\t}\n\t\t_, err := c.WriteJSON(out, o)\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Deleted alert '%s' (workspace-id: %s)\", c.AlertID, c.WorkspaceID.Value)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/alert/slack/doc.go",
    "content": "// Package slack contains commands to inspect and manipulate NGWAF Slack alerts.\npackage slack\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/alert/slack/get.go",
    "content": "package slack\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/workspaces/alerts/slack\"\n)\n\n// GetCommand calls the Fastly API to get Slack alerts.\ntype GetCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tAlertID     string\n\tWorkspaceID argparser.OptionalWorkspaceID\n}\n\n// NewGetCommand returns a usable command registered under the parent.\nfunc NewGetCommand(parent argparser.Registerer, g *global.Data) *GetCommand {\n\tc := GetCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"get\", \"Get a Slack alert\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagNGWAFWorkspaceID,\n\t\tDescription: argparser.FlagNGWAFWorkspaceIDDesc,\n\t\tDst:         &c.WorkspaceID.Value,\n\t\tAction:      c.WorkspaceID.Set,\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagNGWAFAlertID,\n\t\tDescription: argparser.FlagNGWAFAlertIDDesc,\n\t\tDst:         &c.AlertID,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *GetCommand) Exec(_ io.Reader, out io.Writer) error {\n\t// Call Parse() to ensure that we check if workspaceID\n\t// is set or to throw the appropriate error.\n\tif err := c.WorkspaceID.Parse(); err != nil {\n\t\treturn err\n\t}\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\tinput := &slack.GetInput{\n\t\tAlertID:     &c.AlertID,\n\t\tWorkspaceID: &c.WorkspaceID.Value,\n\t}\n\n\tfc, ok := c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tdata, err := slack.Get(context.TODO(), fc, input)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, data); ok {\n\t\treturn err\n\t}\n\n\ttext.PrintAlert(out, data)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/alert/slack/list.go",
    "content": "package slack\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/workspaces/alerts/slack\"\n)\n\n// ListCommand calls the Fastly API to list Slack alerts.\ntype ListCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tWorkspaceID argparser.OptionalWorkspaceID\n}\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"list\", \"List Slack alerts\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagNGWAFWorkspaceID,\n\t\tDescription: argparser.FlagNGWAFWorkspaceIDDesc,\n\t\tDst:         &c.WorkspaceID.Value,\n\t\tAction:      c.WorkspaceID.Set,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *ListCommand) Exec(_ io.Reader, out io.Writer) error {\n\t// Call Parse() to ensure that we check if workspaceID\n\t// is set or to throw the appropriate error.\n\tif err := c.WorkspaceID.Parse(); err != nil {\n\t\treturn err\n\t}\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\tinput := &slack.ListInput{\n\t\tWorkspaceID: &c.WorkspaceID.Value,\n\t}\n\n\tfc, ok := c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tdata, err := slack.List(context.TODO(), fc, input)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, data); ok {\n\t\treturn err\n\t}\n\n\ttext.PrintAlertTbl(out, data.Data)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/alert/slack/root.go",
    "content": "package slack\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"slack\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manage Slack workspace alerts\")\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/alert/slack/slack_test.go",
    "content": "package slack_test\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\t\"testing\"\n\n\troot \"github.com/fastly/cli/pkg/commands/ngwaf\"\n\tworkspaceroot \"github.com/fastly/cli/pkg/commands/ngwaf/workspace\"\n\talertroot \"github.com/fastly/cli/pkg/commands/ngwaf/workspace/alert\"\n\tsub \"github.com/fastly/cli/pkg/commands/ngwaf/workspace/alert/slack\"\n\tfstfmt \"github.com/fastly/cli/pkg/fmt\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/workspaces/alerts/slack\"\n)\n\nconst (\n\talertID     = \"1a2b3c4d5e6f7890abcdef12\"\n\tworkspaceID = \"nBw2ENWfOY1M2dpSwK1l5R\"\n\tdescription = \"TestSlackAlert\"\n)\n\nvar (\n\twebhook    = \"https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXX\"\n\tslackAlert = slack.Alert{\n\t\tID:          alertID,\n\t\tType:        \"slack\",\n\t\tDescription: description,\n\t\tCreatedAt:   \"2025-11-25T16:40:12Z\",\n\t\tCreatedBy:   \"test@example.com\",\n\t\tConfig: slack.ResponseConfig{\n\t\t\tWebhook: &webhook,\n\t\t},\n\t}\n)\n\nfunc TestSlackAlertCreate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --workspace-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--webhook %s\", webhook),\n\t\t\tWantError: \"error reading workspace ID: no workspace ID found\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --webhook flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--workspace-id %s\", workspaceID),\n\t\t\tWantError: \"error parsing arguments: required flag --webhook not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --webhook %s\", workspaceID, webhook),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusCreated,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusCreated),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader((testutil.GenJSON(slackAlert)))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Created a '%s' alert '%s' (workspace-id: %s)\", slackAlert.Type, slackAlert.ID, workspaceID),\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --webhook %s --json\", workspaceID, webhook),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusCreated,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusCreated),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader((testutil.GenJSON(slackAlert)))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(slackAlert),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, workspaceroot.CommandName, alertroot.CommandName, sub.CommandName, \"create\"}, scenarios)\n}\n\nfunc TestSlackAlertList(t *testing.T) {\n\talertsObject := slack.Alerts{\n\t\tData: []slack.Alert{\n\t\t\t{\n\t\t\t\tID:          \"1a2b3c4d5e6f7890abcdef12\",\n\t\t\t\tType:        \"slack\",\n\t\t\t\tDescription: \"First slack alert\",\n\t\t\t\tCreatedAt:   \"2025-11-25T16:40:12Z\",\n\t\t\t\tCreatedBy:   \"test@example.com\",\n\t\t\t\tConfig: slack.ResponseConfig{\n\t\t\t\t\tWebhook: &webhook,\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tID:          \"2b3c4d5e6f7890abcdef1234\",\n\t\t\t\tType:        \"slack\",\n\t\t\t\tDescription: \"Second slack alert\",\n\t\t\t\tCreatedAt:   \"2025-11-25T16:40:12Z\",\n\t\t\t\tCreatedBy:   \"test@example.com\",\n\t\t\t\tConfig: slack.ResponseConfig{\n\t\t\t\t\tWebhook: &webhook,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tMeta: slack.MetaAlerts{\n\t\t\tTotal: 2,\n\t\t},\n\t}\n\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --workspace-id flag\",\n\t\t\tArgs:      \"\",\n\t\t\tWantError: \"error reading workspace ID: no workspace ID found\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate internal server error\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s\", workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusInternalServerError,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusInternalServerError),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"500 - Internal Server Error\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success (zero alerts)\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s\", workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(slack.Alerts{\n\t\t\t\t\t\t\tData: []slack.Alert{},\n\t\t\t\t\t\t}))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: zeroListString,\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s\", workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(alertsObject))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: listString,\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --json\", workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(alertsObject))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(alertsObject),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, workspaceroot.CommandName, alertroot.CommandName, sub.CommandName, \"list\"}, scenarios)\n}\n\nfunc TestSlackAlertGet(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --workspace-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--alert-id %s\", alertID),\n\t\t\tWantError: \"error reading workspace ID: no workspace ID found\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --alert-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--workspace-id %s\", workspaceID),\n\t\t\tWantError: \"error parsing arguments: required flag --alert-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate not found\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --alert-id invalid\", workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusNotFound,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusNotFound),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(`\n\t\t\t\t\t\t\t{\n    \t\t\t\t\t\t\t\"title\": \"This resource does not exist\",\n    \t\t\t\t\t\t\t\"status\": 404\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t`))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"404 - Not Found\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --alert-id %s\", workspaceID, alertID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader((testutil.GenJSON(slackAlert)))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: alertString,\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --alert-id %s --json\", workspaceID, alertID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader((testutil.GenJSON(slackAlert)))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(slackAlert),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, workspaceroot.CommandName, alertroot.CommandName, sub.CommandName, \"get\"}, scenarios)\n}\n\nfunc TestSlackAlertUpdate(t *testing.T) {\n\tupdatedWebhook := \"https://hooks.slack.com/services/updated\"\n\tupdatedAlert := slack.Alert{\n\t\tID:          alertID,\n\t\tType:        \"slack\",\n\t\tDescription: \"Updated description\",\n\t\tCreatedAt:   \"2025-11-25T16:40:12Z\",\n\t\tCreatedBy:   \"test@example.com\",\n\t\tConfig: slack.ResponseConfig{\n\t\t\tWebhook: &updatedWebhook,\n\t\t},\n\t}\n\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --workspace-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--alert-id %s --webhook %s\", alertID, webhook),\n\t\t\tWantError: \"error reading workspace ID: no workspace ID found\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --alert-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--workspace-id %s --webhook %s\", workspaceID, webhook),\n\t\t\tWantError: \"error parsing arguments: required flag --alert-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate not found\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --alert-id invalid --webhook https://hooks.slack.com/services/updated\", workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusNotFound,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusNotFound),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(`\n\t\t\t\t\t\t\t{\n    \t\t\t\t\t\t\t\"title\": \"This resource does not exist\",\n    \t\t\t\t\t\t\t\"status\": 404\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t`))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"404 - Not Found\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success with webhook\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --alert-id %s --webhook https://hooks.slack.com/services/updated\", workspaceID, alertID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MultiResponseRoundTripper{\n\t\t\t\t\tResponses: []*http.Response{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tStatusCode: http.StatusOK,\n\n\t\t\t\t\t\t\tStatus: http.StatusText(http.StatusOK),\n\n\t\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(slackAlert))),\n\t\t\t\t\t\t},\n\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tStatusCode: http.StatusOK,\n\n\t\t\t\t\t\t\tStatus: http.StatusText(http.StatusOK),\n\n\t\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(updatedAlert))),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Updated '%s' alert '%s' (workspace-id: %s)\", updatedAlert.Type, updatedAlert.ID, workspaceID),\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --alert-id %s --webhook https://hooks.slack.com/services/updated --json\", workspaceID, alertID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MultiResponseRoundTripper{\n\t\t\t\t\tResponses: []*http.Response{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tStatusCode: http.StatusOK,\n\n\t\t\t\t\t\t\tStatus: http.StatusText(http.StatusOK),\n\n\t\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(slackAlert))),\n\t\t\t\t\t\t},\n\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tStatusCode: http.StatusOK,\n\n\t\t\t\t\t\t\tStatus: http.StatusText(http.StatusOK),\n\n\t\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(updatedAlert))),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(updatedAlert),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, workspaceroot.CommandName, alertroot.CommandName, sub.CommandName, \"update\"}, scenarios)\n}\n\nfunc TestSlackAlertDelete(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --workspace-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--alert-id %s\", alertID),\n\t\t\tWantError: \"error reading workspace ID: no workspace ID found\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --alert-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--workspace-id %s\", workspaceID),\n\t\t\tWantError: \"error parsing arguments: required flag --alert-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate not found\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --alert-id invalid\", workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusNotFound,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusNotFound),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(`\n\t\t\t\t\t\t\t{\n    \t\t\t\t\t\t\t\"title\": \"This resource does not exist\",\n    \t\t\t\t\t\t\t\"status\": 404\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t`))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"404 - Not Found\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --alert-id %s\", workspaceID, alertID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusNoContent,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusNoContent),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Deleted alert '%s' (workspace-id: %s)\", alertID, workspaceID),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, workspaceroot.CommandName, alertroot.CommandName, sub.CommandName, \"delete\"}, scenarios)\n}\n\nvar alertString = strings.TrimSpace(`\nID: 1a2b3c4d5e6f7890abcdef12\nType: slack\nDescription: TestSlackAlert\nCreated At: 2025-11-25T16:40:12Z\nCreated By: test@example.com\nConfig:\n  Webhook: <redacted>\n`)\n\nvar listString = strings.TrimSpace(`\nID                        Type   Description         Created At            Created By        Config\n1a2b3c4d5e6f7890abcdef12  slack  First slack alert   2025-11-25T16:40:12Z  test@example.com  Webhook: <redacted>\n2b3c4d5e6f7890abcdef1234  slack  Second slack alert  2025-11-25T16:40:12Z  test@example.com  Webhook: <redacted>\n`) + \"\\n\"\n\nvar zeroListString = strings.TrimSpace(`\nID  Type  Description  Created At  Created By  Config\n`) + \"\\n\"\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/alert/slack/update.go",
    "content": "package slack\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/ngwaf/workspace/alert\"\n\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/workspaces/alerts/slack\"\n)\n\n// UpdateCommand calls the Fastly API to update Slack alerts.\ntype UpdateCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tAlertID     string\n\tWorkspaceID argparser.OptionalWorkspaceID\n\tWebhook     string\n}\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"update\", \"Update a Slack alert\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagNGWAFWorkspaceID,\n\t\tDescription: argparser.FlagNGWAFWorkspaceIDDesc,\n\t\tDst:         &c.WorkspaceID.Value,\n\t\tAction:      c.WorkspaceID.Set,\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagNGWAFAlertID,\n\t\tDescription: argparser.FlagNGWAFAlertIDDesc,\n\t\tDst:         &c.AlertID,\n\t\tRequired:    true,\n\t})\n\tc.CmdClause.Flag(\"webhook\", \"Slack webhook.\").Required().StringVar(&c.Webhook)\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *UpdateCommand) Exec(_ io.Reader, out io.Writer) error {\n\t// Call Parse() to ensure that we check if workspaceID\n\t// is set or to throw the appropriate error.\n\tif err := c.WorkspaceID.Parse(); err != nil {\n\t\treturn err\n\t}\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tinput := &slack.UpdateInput{\n\t\tAlertID:     &c.AlertID,\n\t\tWorkspaceID: &c.WorkspaceID.Value,\n\t\tConfig: &slack.UpdateConfig{\n\t\t\tWebhook: &c.Webhook,\n\t\t},\n\t\t// Set 'Events' to the only possible value, 'flag'\n\t\tEvents: alert.GetDefaultEvents(),\n\t}\n\n\tfc, ok := c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tdata, err := slack.Update(context.TODO(), fc, input)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, data); ok {\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Updated '%s' alert '%s' (workspace-id: %s)\", data.Type, data.ID, c.WorkspaceID.Value)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/alert/webhook/create.go",
    "content": "package webhook\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/ngwaf/workspace/alert\"\n\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/workspaces/alerts/webhook\"\n)\n\n// CreateCommand calls the Fastly API to create Webhook alerts.\ntype CreateCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tWorkspaceID argparser.OptionalWorkspaceID\n\tWebhook     string\n\n\t// Optional.\n\tDescription argparser.OptionalString\n}\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"create\", \"Create a Webhook alert\").Alias(\"add\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagNGWAFWorkspaceID,\n\t\tDescription: argparser.FlagNGWAFWorkspaceIDDesc,\n\t\tDst:         &c.WorkspaceID.Value,\n\t\tAction:      c.WorkspaceID.Set,\n\t})\n\tc.CmdClause.Flag(\"webhook\", \"Webhook webhook.\").Required().StringVar(&c.Webhook)\n\n\t// Optional.\n\tc.CmdClause.Flag(\"description\", \"An optional description for the alert.\").Action(c.Description.Set).StringVar(&c.Description.Value)\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *CreateCommand) Exec(_ io.Reader, out io.Writer) error {\n\t// Call Parse() to ensure that we check if workspaceID\n\t// is set or to throw the appropriate error.\n\tif err := c.WorkspaceID.Parse(); err != nil {\n\t\treturn err\n\t}\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tinput := &webhook.CreateInput{\n\t\tWorkspaceID: &c.WorkspaceID.Value,\n\t\tConfig: &webhook.CreateConfig{\n\t\t\tWebhook: &c.Webhook,\n\t\t},\n\t\t// Set 'Events' to the only possible value, 'flag'\n\t\tEvents: alert.GetDefaultEvents(),\n\t}\n\tif c.Description.WasSet {\n\t\tinput.Description = &c.Description.Value\n\t}\n\n\tfc, ok := c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tdata, err := webhook.Create(context.TODO(), fc, input)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, data); ok {\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Created a '%s' alert '%s' (workspace-id: %s)\", data.Type, data.ID, c.WorkspaceID.Value)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/alert/webhook/delete.go",
    "content": "package webhook\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/workspaces/alerts/webhook\"\n)\n\n// DeleteCommand calls the Fastly API to delete Webhook alerts.\ntype DeleteCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tAlertID     string\n\tWorkspaceID argparser.OptionalWorkspaceID\n}\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"delete\", \"Delete a Webhook alert\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagNGWAFWorkspaceID,\n\t\tDescription: argparser.FlagNGWAFWorkspaceIDDesc,\n\t\tDst:         &c.WorkspaceID.Value,\n\t\tAction:      c.WorkspaceID.Set,\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagNGWAFAlertID,\n\t\tDescription: argparser.FlagNGWAFAlertIDDesc,\n\t\tDst:         &c.AlertID,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DeleteCommand) Exec(_ io.Reader, out io.Writer) error {\n\t// Call Parse() to ensure that we check if workspaceID\n\t// is set or to throw the appropriate error.\n\tif err := c.WorkspaceID.Parse(); err != nil {\n\t\treturn err\n\t}\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\tfc, ok := c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\terr := webhook.Delete(context.TODO(), fc, &webhook.DeleteInput{\n\t\tWorkspaceID: &c.WorkspaceID.Value,\n\t\tAlertID:     &c.AlertID,\n\t})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif c.JSONOutput.Enabled {\n\t\to := struct {\n\t\t\tID      string `json:\"id\"`\n\t\t\tDeleted bool   `json:\"deleted\"`\n\t\t}{\n\t\t\tc.AlertID,\n\t\t\ttrue,\n\t\t}\n\t\t_, err := c.WriteJSON(out, o)\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Deleted alert '%s' (workspace-id: %s)\", c.AlertID, c.WorkspaceID.Value)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/alert/webhook/doc.go",
    "content": "// Package webhook contains commands to inspect and manipulate NGWAF Webhook alerts.\npackage webhook\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/alert/webhook/get-signing-key.go",
    "content": "package webhook\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/workspaces/alerts/webhook\"\n)\n\n// GetSigningKeyCommand calls the Fastly API to get Webhook alerts.\ntype GetSigningKeyCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tAlertID     string\n\tWorkspaceID argparser.OptionalWorkspaceID\n}\n\n// NewGetSigningKeyCommand returns a usable command registered under the parent.\nfunc NewGetSigningKeyCommand(parent argparser.Registerer, g *global.Data) *GetSigningKeyCommand {\n\tc := GetSigningKeyCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"get-signing-key\", \"Retrieves details of a webhook alert signing key\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagNGWAFWorkspaceID,\n\t\tDescription: argparser.FlagNGWAFWorkspaceIDDesc,\n\t\tDst:         &c.WorkspaceID.Value,\n\t\tAction:      c.WorkspaceID.Set,\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagNGWAFAlertID,\n\t\tDescription: argparser.FlagNGWAFAlertIDDesc,\n\t\tDst:         &c.AlertID,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *GetSigningKeyCommand) Exec(_ io.Reader, out io.Writer) error {\n\t// Call Parse() to ensure that we check if workspaceID\n\t// is set or to throw the appropriate error.\n\tif err := c.WorkspaceID.Parse(); err != nil {\n\t\treturn err\n\t}\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\tinput := &webhook.GetKeyInput{\n\t\tAlertID:     &c.AlertID,\n\t\tWorkspaceID: &c.WorkspaceID.Value,\n\t}\n\n\tfc, ok := c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tdata, err := webhook.GetKey(context.TODO(), fc, input)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, data); ok {\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Signing Key: '%s' (Workspace: %s)\", data.SigningKey, c.WorkspaceID.Value)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/alert/webhook/get.go",
    "content": "package webhook\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/workspaces/alerts/webhook\"\n)\n\n// GetCommand calls the Fastly API to get Webhook alerts.\ntype GetCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tAlertID     string\n\tWorkspaceID argparser.OptionalWorkspaceID\n}\n\n// NewGetCommand returns a usable command registered under the parent.\nfunc NewGetCommand(parent argparser.Registerer, g *global.Data) *GetCommand {\n\tc := GetCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"get\", \"Get a Webhook alert\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagNGWAFWorkspaceID,\n\t\tDescription: argparser.FlagNGWAFWorkspaceIDDesc,\n\t\tDst:         &c.WorkspaceID.Value,\n\t\tAction:      c.WorkspaceID.Set,\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagNGWAFAlertID,\n\t\tDescription: argparser.FlagNGWAFAlertIDDesc,\n\t\tDst:         &c.AlertID,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *GetCommand) Exec(_ io.Reader, out io.Writer) error {\n\t// Call Parse() to ensure that we check if workspaceID\n\t// is set or to throw the appropriate error.\n\tif err := c.WorkspaceID.Parse(); err != nil {\n\t\treturn err\n\t}\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\tinput := &webhook.GetInput{\n\t\tAlertID:     &c.AlertID,\n\t\tWorkspaceID: &c.WorkspaceID.Value,\n\t}\n\n\tfc, ok := c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tdata, err := webhook.Get(context.TODO(), fc, input)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, data); ok {\n\t\treturn err\n\t}\n\n\ttext.PrintAlert(out, data)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/alert/webhook/list.go",
    "content": "package webhook\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/workspaces/alerts/webhook\"\n)\n\n// ListCommand calls the Fastly API to list Webhook alerts.\ntype ListCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tWorkspaceID argparser.OptionalWorkspaceID\n}\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"list\", \"List Webhook alerts\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagNGWAFWorkspaceID,\n\t\tDescription: argparser.FlagNGWAFWorkspaceIDDesc,\n\t\tDst:         &c.WorkspaceID.Value,\n\t\tAction:      c.WorkspaceID.Set,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *ListCommand) Exec(_ io.Reader, out io.Writer) error {\n\t// Call Parse() to ensure that we check if workspaceID\n\t// is set or to throw the appropriate error.\n\tif err := c.WorkspaceID.Parse(); err != nil {\n\t\treturn err\n\t}\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\tinput := &webhook.ListInput{\n\t\tWorkspaceID: &c.WorkspaceID.Value,\n\t}\n\n\tfc, ok := c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tdata, err := webhook.List(context.TODO(), fc, input)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, data); ok {\n\t\treturn err\n\t}\n\n\ttext.PrintAlertTbl(out, data.Data)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/alert/webhook/root.go",
    "content": "package webhook\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"webhook\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manage Webhook workspace alerts\")\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/alert/webhook/rotate-signing-key.go",
    "content": "package webhook\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/workspaces/alerts/webhook\"\n)\n\n// RotateSigningKeyCommand calls the Fastly API to get Webhook alerts.\ntype RotateSigningKeyCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tAlertID     string\n\tWorkspaceID argparser.OptionalWorkspaceID\n}\n\n// NewRotateSigningKeyCommand returns a usable command registered under the parent.\nfunc NewRotateSigningKeyCommand(parent argparser.Registerer, g *global.Data) *RotateSigningKeyCommand {\n\tc := RotateSigningKeyCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"rotate-signing-key\", \"Rotate webhook alert signing key\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagNGWAFWorkspaceID,\n\t\tDescription: argparser.FlagNGWAFWorkspaceIDDesc,\n\t\tDst:         &c.WorkspaceID.Value,\n\t\tAction:      c.WorkspaceID.Set,\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagNGWAFAlertID,\n\t\tDescription: argparser.FlagNGWAFAlertIDDesc,\n\t\tDst:         &c.AlertID,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *RotateSigningKeyCommand) Exec(_ io.Reader, out io.Writer) error {\n\t// Call Parse() to ensure that we check if workspaceID\n\t// is set or to throw the appropriate error.\n\tif err := c.WorkspaceID.Parse(); err != nil {\n\t\treturn err\n\t}\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\tinput := &webhook.RotateKeyInput{\n\t\tAlertID:     &c.AlertID,\n\t\tWorkspaceID: &c.WorkspaceID.Value,\n\t}\n\n\tfc, ok := c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tdata, err := webhook.RotateKey(context.TODO(), fc, input)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, data); ok {\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Signing Key: '%s' (Workspace: %s)\", data.SigningKey, c.WorkspaceID.Value)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/alert/webhook/update.go",
    "content": "package webhook\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/ngwaf/workspace/alert\"\n\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/workspaces/alerts/webhook\"\n)\n\n// UpdateCommand calls the Fastly API to update Webhook alerts.\ntype UpdateCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tAlertID     string\n\tWorkspaceID argparser.OptionalWorkspaceID\n\tWebhook     string\n}\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"update\", \"Update a Webhook alert\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagNGWAFWorkspaceID,\n\t\tDescription: argparser.FlagNGWAFWorkspaceIDDesc,\n\t\tDst:         &c.WorkspaceID.Value,\n\t\tAction:      c.WorkspaceID.Set,\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagNGWAFAlertID,\n\t\tDescription: argparser.FlagNGWAFAlertIDDesc,\n\t\tDst:         &c.AlertID,\n\t\tRequired:    true,\n\t})\n\tc.CmdClause.Flag(\"webhook\", \"Webhook webhook.\").Required().StringVar(&c.Webhook)\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *UpdateCommand) Exec(_ io.Reader, out io.Writer) error {\n\t// Call Parse() to ensure that we check if workspaceID\n\t// is set or to throw the appropriate error.\n\tif err := c.WorkspaceID.Parse(); err != nil {\n\t\treturn err\n\t}\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tinput := &webhook.UpdateInput{\n\t\tAlertID:     &c.AlertID,\n\t\tWorkspaceID: &c.WorkspaceID.Value,\n\t\tConfig: &webhook.UpdateConfig{\n\t\t\tWebhook: &c.Webhook,\n\t\t},\n\t\t// Set 'Events' to the only possible value, 'flag'\n\t\tEvents: alert.GetDefaultEvents(),\n\t}\n\n\tfc, ok := c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tdata, err := webhook.Update(context.TODO(), fc, input)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, data); ok {\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Updated '%s' alert '%s' (workspace-id: %s)\", data.Type, data.ID, c.WorkspaceID.Value)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/alert/webhook/webhook_test.go",
    "content": "package webhook_test\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\t\"testing\"\n\n\troot \"github.com/fastly/cli/pkg/commands/ngwaf\"\n\tworkspaceroot \"github.com/fastly/cli/pkg/commands/ngwaf/workspace\"\n\talertroot \"github.com/fastly/cli/pkg/commands/ngwaf/workspace/alert\"\n\tsub \"github.com/fastly/cli/pkg/commands/ngwaf/workspace/alert/webhook\"\n\tfstfmt \"github.com/fastly/cli/pkg/fmt\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/workspaces/alerts/webhook\"\n)\n\nconst (\n\talertID     = \"6f7890abcdef123456789012\"\n\tworkspaceID = \"nBw2ENWfOY1M2dpSwK1l5R\"\n\tdescription = \"TestWebhookAlert\"\n\tsigningKey  = \"a1b2c3d4e5f67890abcdef1234567890\"\n)\n\nvar (\n\twebhookURL   = \"https://example.com/webhook\"\n\twebhookAlert = webhook.Alert{\n\t\tID:          alertID,\n\t\tType:        \"webhook\",\n\t\tDescription: description,\n\t\tCreatedAt:   \"2025-11-25T16:40:12Z\",\n\t\tCreatedBy:   \"test@example.com\",\n\t\tConfig: webhook.ResponseConfig{\n\t\t\tWebhook: &webhookURL,\n\t\t},\n\t}\n)\n\nfunc TestWebhookAlertCreate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --workspace-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--webhook %s\", webhookURL),\n\t\t\tWantError: \"error reading workspace ID: no workspace ID found\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --webhook flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--workspace-id %s\", workspaceID),\n\t\t\tWantError: \"error parsing arguments: required flag --webhook not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --webhook %s\", workspaceID, webhookURL),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusCreated,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusCreated),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader((testutil.GenJSON(webhookAlert)))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Created a '%s' alert '%s' (workspace-id: %s)\", webhookAlert.Type, webhookAlert.ID, workspaceID),\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --webhook %s --json\", workspaceID, webhookURL),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusCreated,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusCreated),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader((testutil.GenJSON(webhookAlert)))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(webhookAlert),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, workspaceroot.CommandName, alertroot.CommandName, sub.CommandName, \"create\"}, scenarios)\n}\n\nfunc TestWebhookAlertList(t *testing.T) {\n\talertsObject := webhook.Alerts{\n\t\tData: []webhook.Alert{\n\t\t\t{\n\t\t\t\tID:          \"1a2b3c4d5e6f7890abcdef12\",\n\t\t\t\tType:        \"webhook\",\n\t\t\t\tDescription: \"First webhook alert\",\n\t\t\t\tCreatedAt:   \"2025-11-25T16:40:12Z\",\n\t\t\t\tCreatedBy:   \"test@example.com\",\n\t\t\t\tConfig: webhook.ResponseConfig{\n\t\t\t\t\tWebhook: &webhookURL,\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tID:          \"2b3c4d5e6f7890abcdef1234\",\n\t\t\t\tType:        \"webhook\",\n\t\t\t\tDescription: \"Second webhook alert\",\n\t\t\t\tCreatedAt:   \"2025-11-25T16:40:12Z\",\n\t\t\t\tCreatedBy:   \"test@example.com\",\n\t\t\t\tConfig: webhook.ResponseConfig{\n\t\t\t\t\tWebhook: &webhookURL,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tMeta: webhook.MetaAlerts{\n\t\t\tTotal: 2,\n\t\t},\n\t}\n\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --workspace-id flag\",\n\t\t\tArgs:      \"\",\n\t\t\tWantError: \"error reading workspace ID: no workspace ID found\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate internal server error\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s\", workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusInternalServerError,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusInternalServerError),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"500 - Internal Server Error\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success (zero alerts)\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s\", workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(webhook.Alerts{\n\t\t\t\t\t\t\tData: []webhook.Alert{},\n\t\t\t\t\t\t}))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: zeroListString,\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s\", workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(alertsObject))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: listString,\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --json\", workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(alertsObject))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(alertsObject),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, workspaceroot.CommandName, alertroot.CommandName, sub.CommandName, \"list\"}, scenarios)\n}\n\nfunc TestWebhookAlertGet(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --workspace-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--alert-id %s\", alertID),\n\t\t\tWantError: \"error reading workspace ID: no workspace ID found\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --alert-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--workspace-id %s\", workspaceID),\n\t\t\tWantError: \"error parsing arguments: required flag --alert-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate not found\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --alert-id invalid\", workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusNotFound,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusNotFound),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(`\n\t\t\t\t\t\t\t{\n    \t\t\t\t\t\t\t\"title\": \"This resource does not exist\",\n    \t\t\t\t\t\t\t\"status\": 404\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t`))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"404 - Not Found\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --alert-id %s\", workspaceID, alertID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader((testutil.GenJSON(webhookAlert)))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: alertString,\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --alert-id %s --json\", workspaceID, alertID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader((testutil.GenJSON(webhookAlert)))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(webhookAlert),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, workspaceroot.CommandName, alertroot.CommandName, sub.CommandName, \"get\"}, scenarios)\n}\n\nfunc TestWebhookAlertUpdate(t *testing.T) {\n\tupdatedWebhookURL := \"https://example.com/webhook/updated\"\n\tupdatedAlert := webhook.Alert{\n\t\tID:          alertID,\n\t\tType:        \"webhook\",\n\t\tDescription: \"Updated description\",\n\t\tCreatedAt:   \"2025-11-25T16:40:12Z\",\n\t\tCreatedBy:   \"test@example.com\",\n\t\tConfig: webhook.ResponseConfig{\n\t\t\tWebhook: &updatedWebhookURL,\n\t\t},\n\t}\n\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --workspace-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--alert-id %s --webhook %s\", alertID, webhookURL),\n\t\t\tWantError: \"error reading workspace ID: no workspace ID found\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --alert-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--workspace-id %s --webhook %s\", workspaceID, webhookURL),\n\t\t\tWantError: \"error parsing arguments: required flag --alert-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate not found\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --alert-id invalid --webhook https://example.com/webhook/updated\", workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusNotFound,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusNotFound),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(`\n\t\t\t\t\t\t\t{\n    \t\t\t\t\t\t\t\"title\": \"This resource does not exist\",\n    \t\t\t\t\t\t\t\"status\": 404\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t`))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"404 - Not Found\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success with webhook\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --alert-id %s --webhook https://example.com/webhook/updated\", workspaceID, alertID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MultiResponseRoundTripper{\n\t\t\t\t\tResponses: []*http.Response{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(webhookAlert))),\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(updatedAlert))),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Updated '%s' alert '%s' (workspace-id: %s)\", updatedAlert.Type, updatedAlert.ID, workspaceID),\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --alert-id %s --webhook https://example.com/webhook/updated --json\", workspaceID, alertID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MultiResponseRoundTripper{\n\t\t\t\t\tResponses: []*http.Response{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(webhookAlert))),\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(updatedAlert))),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(updatedAlert),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, workspaceroot.CommandName, alertroot.CommandName, sub.CommandName, \"update\"}, scenarios)\n}\n\nfunc TestWebhookAlertDelete(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --workspace-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--alert-id %s\", alertID),\n\t\t\tWantError: \"error reading workspace ID: no workspace ID found\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --alert-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--workspace-id %s\", workspaceID),\n\t\t\tWantError: \"error parsing arguments: required flag --alert-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate not found\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --alert-id invalid\", workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusNotFound,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusNotFound),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(`\n\t\t\t\t\t\t\t{\n    \t\t\t\t\t\t\t\"title\": \"This resource does not exist\",\n    \t\t\t\t\t\t\t\"status\": 404\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t`))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"404 - Not Found\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --alert-id %s\", workspaceID, alertID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusNoContent,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusNoContent),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Deleted alert '%s' (workspace-id: %s)\", alertID, workspaceID),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, workspaceroot.CommandName, alertroot.CommandName, sub.CommandName, \"delete\"}, scenarios)\n}\n\nfunc TestWebhookGetSigningKey(t *testing.T) {\n\tsigningKeyResponse := webhook.AlertsKey{\n\t\tSigningKey: signingKey,\n\t}\n\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --workspace-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--alert-id %s\", alertID),\n\t\t\tWantError: \"error reading workspace ID: no workspace ID found\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --alert-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--workspace-id %s\", workspaceID),\n\t\t\tWantError: \"error parsing arguments: required flag --alert-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate not found\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --alert-id invalid\", workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusNotFound,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusNotFound),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(`\n\t\t\t\t\t\t\t{\n    \t\t\t\t\t\t\t\"title\": \"This resource does not exist\",\n    \t\t\t\t\t\t\t\"status\": 404\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t`))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"404 - Not Found\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --alert-id %s\", workspaceID, alertID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader((testutil.GenJSON(signingKeyResponse)))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Signing Key: '%s' (Workspace: %s)\", signingKeyResponse.SigningKey, workspaceID),\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --alert-id %s --json\", workspaceID, alertID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader((testutil.GenJSON(signingKeyResponse)))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(signingKeyResponse),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, workspaceroot.CommandName, alertroot.CommandName, sub.CommandName, \"get-signing-key\"}, scenarios)\n}\n\nfunc TestWebhookRotateSigningKey(t *testing.T) {\n\tnewSigningKey := \"new-signing-key-0987654321\"\n\tsigningKeyResponse := webhook.AlertsKey{\n\t\tSigningKey: newSigningKey,\n\t}\n\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --workspace-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--alert-id %s\", alertID),\n\t\t\tWantError: \"error reading workspace ID: no workspace ID found\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --alert-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--workspace-id %s\", workspaceID),\n\t\t\tWantError: \"error parsing arguments: required flag --alert-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate not found\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --alert-id invalid\", workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusNotFound,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusNotFound),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(`\n\t\t\t\t\t\t\t{\n    \t\t\t\t\t\t\t\"title\": \"This resource does not exist\",\n    \t\t\t\t\t\t\t\"status\": 404\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t`))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"404 - Not Found\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --alert-id %s\", workspaceID, alertID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader((testutil.GenJSON(signingKeyResponse)))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Signing Key: '%s' (Workspace: %s)\", signingKeyResponse.SigningKey, workspaceID),\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --alert-id %s --json\", workspaceID, alertID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader((testutil.GenJSON(signingKeyResponse)))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(signingKeyResponse),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, workspaceroot.CommandName, alertroot.CommandName, sub.CommandName, \"rotate-signing-key\"}, scenarios)\n}\n\nvar alertString = strings.TrimSpace(`\nID: 6f7890abcdef123456789012\nType: webhook\nDescription: TestWebhookAlert\nCreated At: 2025-11-25T16:40:12Z\nCreated By: test@example.com\nConfig:\n  Webhook: <redacted>\n`)\n\nvar listString = strings.TrimSpace(`\nID                        Type     Description           Created At            Created By        Config\n1a2b3c4d5e6f7890abcdef12  webhook  First webhook alert   2025-11-25T16:40:12Z  test@example.com  Webhook: <redacted>\n2b3c4d5e6f7890abcdef1234  webhook  Second webhook alert  2025-11-25T16:40:12Z  test@example.com  Webhook: <redacted>\n`) + \"\\n\"\n\nvar zeroListString = strings.TrimSpace(`\nID  Type  Description  Created At  Created By  Config\n`) + \"\\n\"\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/countrylist/countrylist_test.go",
    "content": "package countrylist_test\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\t\"testing\"\n\n\troot \"github.com/fastly/cli/pkg/commands/ngwaf\"\n\tsub \"github.com/fastly/cli/pkg/commands/ngwaf/workspace\"\n\tsub2 \"github.com/fastly/cli/pkg/commands/ngwaf/workspace/countrylist\"\n\tfstfmt \"github.com/fastly/cli/pkg/fmt\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/lists\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/scope\"\n)\n\nconst (\n\tlistID          = \"someListID\"\n\tlistDescription = \"NGWAFCLIList\"\n\tlistEntries     = \"us\"\n\tlistType        = \"country\"\n\tlistName        = \"listName\"\n\tworkspaceID     = \"someWorkspaceID\"\n)\n\nvar stringlist = lists.List{\n\tListID:      listID,\n\tDescription: listDescription,\n\tEntries:     []string{listEntries},\n\tName:        listName,\n\tType:        listType,\n\tCreatedAt:   testutil.Date,\n\tUpdatedAt:   testutil.Date,\n\tScope: lists.Scope{\n\t\tType: string(scope.ScopeTypeWorkspace),\n\t},\n}\n\nvar stringlist2 = lists.List{\n\tListID:      listID + \"2\",\n\tDescription: listDescription + \"2\",\n\tEntries:     []string{listEntries},\n\tName:        listName + \"2\",\n\tType:        listType,\n\tCreatedAt:   testutil.Date,\n\tUpdatedAt:   testutil.Date,\n\tScope: lists.Scope{\n\t\tType: string(scope.ScopeTypeWorkspace),\n\t},\n}\n\nfunc TestCountryListCreate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --entries flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--name %s --workspace-id %s\", listName, workspaceID),\n\t\t\tWantError: \"error parsing arguments: required flag --entries not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --name flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--entries %s --workspace-id %s\", listEntries, workspaceID),\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate missing --workspace-id flag\",\n\t\t\tArgs: fmt.Sprintf(\"--entries %s --name %s\", listEntries, listName),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusNoContent,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusNoContent),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"error reading workspace ID: no workspace ID found\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate internal server error\",\n\t\t\tArgs: fmt.Sprintf(\"--entries %s --name %s --workspace-id %s\", listEntries, listName, workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusInternalServerError,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusInternalServerError),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"500 - Internal Server Error\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--entries %s --name %s --workspace-id %s\", listEntries, listName, workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader((testutil.GenJSON(stringlist)))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Created Workspace Country List '%s' (list id: %s)\", listName, listID),\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--entries %s --name %s --workspace-id %s --json\", listEntries, listName, workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(stringlist))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(stringlist),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, sub2.CommandName, \"create\"}, scenarios)\n}\n\nfunc TestCountryListDelete(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --list-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--workspace-id %s\", workspaceID),\n\t\t\tWantError: \"error parsing arguments: required flag --list-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate missing --workspace-id flag\",\n\t\t\tArgs: fmt.Sprintf(\"--list-id %s\", listID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusNoContent,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusNoContent),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"error reading workspace ID: no workspace ID found\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate bad request\",\n\t\t\tArgs: \"--list-id bar --workspace-id baz\",\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusBadRequest,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusBadRequest),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(`\n\t\t\t\t\t\t\t{\n    \t\t\t\t\t\t\t\"title\": \"invalid List ID\",\n    \t\t\t\t\t\t\t\"status\": 400\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t`))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"400 - Bad Request\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--list-id %s --workspace-id %s\", listID, workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusNoContent,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusNoContent),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Deleted Workspace Country List (list id: %s)\", listID),\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--list-id %s --workspace-id %s --json\", listID, workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusNoContent,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusNoContent),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.JSON(`{\"id\": %q, \"deleted\": true}`, listID),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, sub2.CommandName, \"delete\"}, scenarios)\n}\n\nfunc TestCountryListGet(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --list-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--workspace-id %s\", workspaceID),\n\t\t\tWantError: \"error parsing arguments: required flag --list-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate missing --workspace-id flag\",\n\t\t\tArgs: fmt.Sprintf(\"--list-id %s\", listID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusNoContent,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusNoContent),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"error reading workspace ID: no workspace ID found\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate bad request\",\n\t\t\tArgs: \"--list-id baz --workspace-id baz\",\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusBadRequest,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusBadRequest),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(`\n\t\t\t\t\t\t\t{\n    \t\t\t\t\t\t\t\"title\": \"invalid List ID\",\n    \t\t\t\t\t\t\t\"status\": 400\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t`))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"400 - Bad Request\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--list-id %s --workspace-id %s\", listID, workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader((testutil.GenJSON(stringlist)))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: listString,\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--list-id %s --workspace-id %s --json\", listID, workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader((testutil.GenJSON(stringlist)))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(stringlist),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, sub2.CommandName, \"get\"}, scenarios)\n}\n\nfunc TestCountryListList(t *testing.T) {\n\tlistsObject := lists.Lists{\n\t\tData: []lists.List{\n\t\t\tstringlist,\n\t\t\tstringlist2,\n\t\t},\n\t\tMeta: lists.MetaLists{},\n\t}\n\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName: \"validate missing --workspace-id flag\",\n\t\t\tArgs: \"\",\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusNoContent,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusNoContent),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"error reading workspace ID: no workspace ID found\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate internal server error\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s\", workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusInternalServerError,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusInternalServerError),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"500 - Internal Server Error\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success (zero workspaces)\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s\", workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(lists.Lists{\n\t\t\t\t\t\t\tData: []lists.List{},\n\t\t\t\t\t\t\tMeta: lists.MetaLists{},\n\t\t\t\t\t\t}))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: zeroListString,\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s\", workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(listsObject))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: listListsString,\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --json\", workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(listsObject))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(listsObject),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, sub2.CommandName, \"list\"}, scenarios)\n}\n\nfunc TestCountryListUpdate(t *testing.T) {\n\tupdatelist := lists.List{\n\t\tListID:      listID,\n\t\tDescription: listDescription + \"2\",\n\t\tEntries:     []string{listEntries + \"2\"},\n\t\tName:        listName,\n\t\tType:        listType,\n\t\tCreatedAt:   testutil.Date,\n\t\tUpdatedAt:   testutil.Date,\n\t\tScope: lists.Scope{\n\t\t\tType: string(scope.ScopeTypeWorkspace),\n\t\t},\n\t}\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --list-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--workspace-id %s\", workspaceID),\n\t\t\tWantError: \"error parsing arguments: required flag --list-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate missing --workspace-id flag\",\n\t\t\tArgs: fmt.Sprintf(\"--list-id %s\", listID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusNoContent,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusNoContent),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"error reading workspace ID: no workspace ID found\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--list-id %s --description %s --entries %s --workspace-id %s\", listID, listDescription+\"2\", listEntries+\"2\", workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(updatelist))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Updated Workspace Country List '%s' (list id: %s)\", listName, listID),\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--list-id %s --description %s --entries %s --workspace-id %s --json\", listID, listDescription+\"2\", listEntries+\"2\", workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(updatelist))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(updatelist),\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, sub2.CommandName, \"update\"}, scenarios)\n}\n\nvar listListsString = strings.TrimSpace(`\nID           Name       Description    Type     Scope      Entries  Updated At                     Created At\nsomeListID   listName   NGWAFCLIList   country  workspace  us       2021-06-15 23:00:00 +0000 UTC  2021-06-15 23:00:00 +0000 UTC\nsomeListID2  listName2  NGWAFCLIList2  country  workspace  us       2021-06-15 23:00:00 +0000 UTC  2021-06-15 23:00:00 +0000 UTC\n`) + \"\\n\"\n\nvar zeroListString = strings.TrimSpace(`\nID  Name  Description  Type  Scope  Entries  Updated At  Created At\n`) + \"\\n\"\n\nvar listString = strings.TrimSpace(`\nID: someListID\nName: listName\nDescription: NGWAFCLIList\nType: country\nEntries: us\nScope: workspace\nUpdated (UTC): 2021-06-15 23:00\n`)\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/countrylist/create.go",
    "content": "package countrylist\n\nimport (\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/scope\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/ngwaf/ngwaflist\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// CreateCommand calls the Fastly API to create workspace-level country lists.\ntype CreateCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tentries     string\n\tname        string\n\tworkspaceID argparser.OptionalWorkspaceID\n\n\t// Optional.\n\tdescription argparser.OptionalString\n}\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"create\", \"Create a workspace-level country list\").Alias(\"add\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"entries\", \"Entries for the list. Can either be a comma separated list or a path to a file.\").Required().StringVar(&c.entries)\n\tc.CmdClause.Flag(\"name\", \"User submitted display name of a list.\").Required().StringVar(&c.name)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagNGWAFWorkspaceID,\n\t\tDescription: argparser.FlagNGWAFWorkspaceIDDesc,\n\t\tDst:         &c.workspaceID.Value,\n\t\tAction:      c.workspaceID.Set,\n\t})\n\n\t// Optional.\n\tc.CmdClause.Flag(\"description\", \"User submitted description of the list.\").Action(c.description.Set).StringVar(&c.description.Value)\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *CreateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\tinput := ngwaflist.ListCreateInput{\n\t\tCommandScope: scope.ScopeTypeWorkspace,\n\t\tDescription:  c.description,\n\t\tEntries:      c.entries,\n\t\tName:         c.name,\n\t\tType:         \"country\",\n\t\tWorkspaceID:  &c.workspaceID,\n\t}\n\n\tvar ok bool\n\tinput.FC, ok = c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tdata, err := ngwaflist.ListCreate(input)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, data); ok {\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Created Workspace Country List '%s' (list id: %s)\", data.Name, data.ListID)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/countrylist/delete.go",
    "content": "package countrylist\n\nimport (\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/scope\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/ngwaf/ngwaflist\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DeleteCommand calls the Fastly API to delete a workspace-level country list.\ntype DeleteCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tlistID      string\n\tworkspaceID argparser.OptionalWorkspaceID\n}\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\n\tc.CmdClause = parent.Command(\"delete\", \"Delete a workspace country list\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"list-id\", \"List ID\").Required().StringVar(&c.listID)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagNGWAFWorkspaceID,\n\t\tDescription: argparser.FlagNGWAFWorkspaceIDDesc,\n\t\tDst:         &c.workspaceID.Value,\n\t\tAction:      c.workspaceID.Set,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DeleteCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tinput := ngwaflist.ListDeleteInput{\n\t\tCommandScope: scope.ScopeTypeWorkspace,\n\t\tListID:       c.listID,\n\t\tWorkspaceID:  &c.workspaceID,\n\t}\n\n\tvar ok bool\n\tinput.FC, ok = c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\terr := ngwaflist.ListDelete(input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif c.JSONOutput.Enabled {\n\t\to := struct {\n\t\t\tID      string `json:\"id\"`\n\t\t\tDeleted bool   `json:\"deleted\"`\n\t\t}{\n\t\t\tc.listID,\n\t\t\ttrue,\n\t\t}\n\t\t_, err := c.WriteJSON(out, o)\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Deleted Workspace Country List (list id: %s)\", c.listID)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/countrylist/doc.go",
    "content": "// Package countrylist contains commands to inspect and manipulate NGWAF workspace-level country lists.\npackage countrylist\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/countrylist/get.go",
    "content": "package countrylist\n\nimport (\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/ngwaf/ngwaflist\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/scope\"\n)\n\n// GetCommand calls the Fastly API to get a workspace-level country list.\ntype GetCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tlistID      string\n\tworkspaceID argparser.OptionalWorkspaceID\n}\n\n// NewGetCommand returns a usable command registered under the parent.\nfunc NewGetCommand(parent argparser.Registerer, g *global.Data) *GetCommand {\n\tc := GetCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\n\tc.CmdClause = parent.Command(\"get\", \"Get a workspace-level country list\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"list-id\", \"List ID\").Required().StringVar(&c.listID)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagNGWAFWorkspaceID,\n\t\tDescription: argparser.FlagNGWAFWorkspaceIDDesc,\n\t\tDst:         &c.workspaceID.Value,\n\t\tAction:      c.workspaceID.Set,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *GetCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tinput := ngwaflist.ListGetInput{\n\t\tCommandScope: scope.ScopeTypeWorkspace,\n\t\tListID:       c.listID,\n\t\tWorkspaceID:  &c.workspaceID,\n\t}\n\n\tvar ok bool\n\tinput.FC, ok = c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tlist, err := ngwaflist.ListGet(input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, list); ok {\n\t\treturn err\n\t}\n\n\ttext.PrintList(out, list)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/countrylist/list.go",
    "content": "package countrylist\n\nimport (\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/ngwaf/ngwaflist\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/scope\"\n)\n\n// ListCommand calls the Fastly API to list all country lists for your workspace.\n\ntype ListCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tworkspaceID argparser.OptionalWorkspaceID\n}\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\n\tc.CmdClause = parent.Command(\"list\", \"List all country lists for your workspace\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagNGWAFWorkspaceID,\n\t\tDescription: argparser.FlagNGWAFWorkspaceIDDesc,\n\t\tDst:         &c.workspaceID.Value,\n\t\tAction:      c.workspaceID.Set,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *ListCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tinput := ngwaflist.ListListInput{\n\t\tCommandScope: scope.ScopeTypeWorkspace,\n\t\tType:         \"country\",\n\t\tWorkspaceID:  &c.workspaceID,\n\t}\n\n\tvar ok bool\n\tinput.FC, ok = c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tlists, err := ngwaflist.ListList(input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, *lists); ok {\n\t\treturn err\n\t}\n\n\ttext.PrintListTbl(out, lists.Data)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/countrylist/root.go",
    "content": "package countrylist\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"country-list\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manage NGWAF Workspace Country Lists\")\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/countrylist/update.go",
    "content": "package countrylist\n\nimport (\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/scope\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/ngwaf/ngwaflist\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// UpdateCommand calls the Fastly API to update an account country list.\ntype UpdateCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tlistID      string\n\tworkspaceID argparser.OptionalWorkspaceID\n\n\t// Optional.\n\tdescription argparser.OptionalString\n\tentries     argparser.OptionalString\n}\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"update\", \"Update an account-level country list\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"list-id\", \"List ID\").Required().StringVar(&c.listID)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagNGWAFWorkspaceID,\n\t\tDescription: argparser.FlagNGWAFWorkspaceIDDesc,\n\t\tDst:         &c.workspaceID.Value,\n\t\tAction:      c.workspaceID.Set,\n\t})\n\n\t// Optional.\n\tc.CmdClause.Flag(\"description\", \"User submitted description of the list.\").Action(c.description.Set).StringVar(&c.description.Value)\n\tc.CmdClause.Flag(\"entries\", \"Entries for the list. Can either be a comma separated list or a path to a file.\").Action(c.entries.Set).StringVar(&c.entries.Value)\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *UpdateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\tinput := ngwaflist.ListUpdateInput{\n\t\tCommandScope: scope.ScopeTypeWorkspace,\n\t\tDescription:  c.description,\n\t\tEntries:      c.entries,\n\t\tListID:       c.listID,\n\t\tWorkspaceID:  &c.workspaceID,\n\t}\n\n\tvar ok bool\n\tinput.FC, ok = c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tdata, err := ngwaflist.ListUpdate(input)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, data); ok {\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Updated Workspace Country List '%s' (list id: %s)\", data.Name, data.ListID)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/create.go",
    "content": "package workspace\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/workspaces\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// CreateCommand calls the Fastly API to create workspaces.\ntype CreateCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tdescription  string\n\tblockingMode string\n\tname         string\n\n\t// Optional.\n\tattackThresholds    argparser.OptionalString\n\tdefaultBlockingCode argparser.OptionalInt\n\tdefaultRedirectURL  argparser.OptionalString\n\tclientIPHeaders     argparser.OptionalString\n\tipAnonymization     argparser.OptionalString\n}\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"create\", \"Create a workspace\").Alias(\"add\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"description\", \"User submitted description of a workspace.\").Required().StringVar(&c.description)\n\tc.CmdClause.Flag(\"blockingMode\", \"User configured mode blocking mode.\").Required().StringVar(&c.blockingMode)\n\tc.CmdClause.Flag(\"name\", \"User submitted display name of a workspace.\").Required().StringVar(&c.name)\n\n\t// Optional.\n\tc.CmdClause.Flag(\"attackThresholds\", \"Attack threshold parameters for system site alerts. Each threshold value is the number of attack signals per IP address that must be detected during the interval before the related IP address is flagged. Input accepted as colon separated string: Immediate:OneMinute:TenMinutes:OneHour\").Action(c.attackThresholds.Set).StringVar(&c.attackThresholds.Value)\n\tc.CmdClause.Flag(\"clientIPHeaders\", \"Specify the request header containing the client IP address. Input accepted as colon separated string.\").Action(c.clientIPHeaders.Set).StringVar(&c.clientIPHeaders.Value)\n\tc.CmdClause.Flag(\"defaultBlockingCode\", \"Default status code that is returned when a request to your web application is blocked.\").Action(c.defaultBlockingCode.Set).IntVar(&c.defaultBlockingCode.Value)\n\tc.CmdClause.Flag(\"defaultRedirectURL\", \"Redirect url to be used if code 301 or 302 is used.\").Action(c.defaultRedirectURL.Set).StringVar(&c.defaultRedirectURL.Value)\n\tc.CmdClause.Flag(\"ipAnonymization\", \"Agents will anonymize IP addresses according to the option selected.\").Action(c.ipAnonymization.Set).StringVar(&c.ipAnonymization.Value)\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *CreateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\tvar err error\n\tinput := &workspaces.CreateInput{\n\t\tDescription: &c.description,\n\t\tMode:        &c.blockingMode,\n\t\tName:        &c.name,\n\t}\n\tif c.attackThresholds.WasSet {\n\t\tinput.AttackSignalThresholds, err = parseCreateAttackSignalThresholds(c.attackThresholds.Value)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tif c.clientIPHeaders.WasSet {\n\t\tinput.ClientIPHeaders = strings.Split(c.clientIPHeaders.Value, \":\")\n\t}\n\tif c.defaultBlockingCode.WasSet {\n\t\tinput.DefaultBlockingResponseCode = &c.defaultBlockingCode.Value\n\t}\n\tif c.defaultRedirectURL.WasSet {\n\t\tinput.DefaultRedirectURL = &c.defaultRedirectURL.Value\n\t}\n\tif c.ipAnonymization.WasSet {\n\t\tinput.IPAnonymization = &c.ipAnonymization.Value\n\t}\n\n\tfc, ok := c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tdata, err := workspaces.Create(context.TODO(), fc, input)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, data); ok {\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Created workspace '%s' (workspace-id: %s)\", data.Name, data.WorkspaceID)\n\treturn nil\n}\n\nfunc parseCreateAttackSignalThresholds(thresholds string) (*workspaces.AttackSignalThresholdsCreateInput, error) {\n\tthresholdsArray := strings.Split(thresholds, \":\")\n\tif len(thresholdsArray) != 4 {\n\t\treturn nil, errors.New(\"wrong number of inputs for Attack Signal Thresholds\")\n\t}\n\timmediate, err := strconv.ParseBool(thresholdsArray[0])\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\toneMinute, err := strconv.Atoi(thresholdsArray[1])\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\ttenMinutes, err := strconv.Atoi(thresholdsArray[2])\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\toneHour, err := strconv.Atoi(thresholdsArray[3])\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &workspaces.AttackSignalThresholdsCreateInput{\n\t\tOneMinute:  &oneMinute,\n\t\tTenMinutes: &tenMinutes,\n\t\tOneHour:    &oneHour,\n\t\tImmediate:  &immediate,\n\t}, nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/customsignal/create.go",
    "content": "package customsignal\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/scope\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/signals\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// CreateCommand calls the Fastly API to create workspace-level custom signals.\ntype CreateCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tname        string\n\tworkspaceID argparser.OptionalWorkspaceID\n\n\t// Optional.\n\tdescription argparser.OptionalString\n}\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"create\", \"Create a workspace-level custom signal\").Alias(\"add\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"User submitted display name of a custom signal. Is immutable and must be between 3 and 25 characters\").Required().StringVar(&c.name)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagNGWAFWorkspaceID,\n\t\tDescription: argparser.FlagNGWAFWorkspaceIDDesc,\n\t\tDst:         &c.workspaceID.Value,\n\t\tAction:      c.workspaceID.Set,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.CmdClause.Flag(\"description\", \"User submitted description of a custom signal.\").Action(c.description.Set).StringVar(&c.description.Value)\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *CreateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\tvar err error\n\tinput := &signals.CreateInput{\n\t\tName: &c.name,\n\t\tScope: &scope.Scope{\n\t\t\tType: scope.ScopeTypeWorkspace,\n\t\t},\n\t}\n\n\tif err := c.workspaceID.Parse(); err != nil {\n\t\treturn err\n\t}\n\tinput.Scope.AppliesTo = []string{c.workspaceID.Value}\n\n\tif c.description.WasSet {\n\t\tinput.Description = &c.description.Value\n\t}\n\n\tfc, ok := c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tdata, err := signals.Create(context.TODO(), fc, input)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, data); ok {\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Created workspace-level custom signal '%s' (signal-id: %s)\", data.Name, data.SignalID)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/customsignal/customsignal_test.go",
    "content": "package customsignal_test\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\t\"testing\"\n\n\troot \"github.com/fastly/cli/pkg/commands/ngwaf\"\n\tsub \"github.com/fastly/cli/pkg/commands/ngwaf/workspace\"\n\tsub2 \"github.com/fastly/cli/pkg/commands/ngwaf/workspace/customsignal\"\n\tfstfmt \"github.com/fastly/cli/pkg/fmt\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/scope\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/signals\"\n)\n\nconst (\n\tcustomSignalDescription = \"NGWAFCLICustomSignal\"\n\tcustomSignalID          = \"someID\"\n\tcustomSignalName        = \"CLICustomSignalName\"\n\tworkspaceID             = \"WorkspaceID\"\n)\n\nvar customSignal = signals.Signal{\n\tCreatedAt:   testutil.Date,\n\tDescription: customSignalDescription,\n\tName:        customSignalName,\n\tSignalID:    customSignalID,\n\tScope: signals.Scope{\n\t\tType:      string(scope.ScopeTypeWorkspace),\n\t\tAppliesTo: []string{workspaceID},\n\t},\n}\n\nfunc TestCustomSignalCreate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --name flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--description %s --workspace-id %s\", customSignalDescription, workspaceID),\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --workspace-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--description %s --name %s\", customSignalDescription, customSignalName),\n\t\t\tWantError: \"error parsing arguments: required flag --workspace-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate internal server error\",\n\t\t\tArgs: fmt.Sprintf(\"--description %s --name %s --workspace-id %s\", customSignalDescription, customSignalName, workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusInternalServerError,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusInternalServerError),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"500 - Internal Server Error\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--description %s --name %s --workspace-id %s\", customSignalDescription, customSignalName, workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader((testutil.GenJSON(customSignal)))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Created workspace-level custom signal '%s' (signal-id: %s)\", customSignalName, customSignalID),\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--description %s --name %s --workspace-id %s --json\", customSignalDescription, customSignalName, workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(customSignal))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(customSignal),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, sub2.CommandName, \"create\"}, scenarios)\n}\n\nfunc TestCustomSignalDelete(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --signal-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--workspace-id %s\", workspaceID),\n\t\t\tWantError: \"error parsing arguments: required flag --signal-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --workspace-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--signal-id %s\", customSignalID),\n\t\t\tWantError: \"error parsing arguments: required flag --workspace-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate bad request\",\n\t\t\tArgs: \"--signal-id bar --workspace-id baz\",\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusBadRequest,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusBadRequest),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(`\n\t\t\t\t\t\t\t{\n    \t\t\t\t\t\t\t\"title\": \"invalid signal ID\",\n    \t\t\t\t\t\t\t\"status\": 400\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t`))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"400 - Bad Request\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--signal-id %s --workspace-id %s\", customSignalID, workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusNoContent,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusNoContent),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Deleted workspace-level custom signal (id: %s)\", customSignalID),\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--signal-id %s --workspace-id %s --json\", customSignalID, workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusNoContent,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusNoContent),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.JSON(`{\"id\": %q, \"deleted\": true}`, customSignalID),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, sub2.CommandName, \"delete\"}, scenarios)\n}\n\nfunc TestCustomSignalGet(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --signal-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--workspace-id %s\", workspaceID),\n\t\t\tWantError: \"error parsing arguments: required flag --signal-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --workspace-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--signal-id %s\", customSignalID),\n\t\t\tWantError: \"error parsing arguments: required flag --workspace-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate bad request\",\n\t\t\tArgs: \"--signal-id baz --workspace-id bar\",\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusBadRequest,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusBadRequest),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(`\n\t\t\t\t\t\t\t{\n    \t\t\t\t\t\t\t\"title\": \"invalid Custom Signal ID\",\n    \t\t\t\t\t\t\t\"status\": 400\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t`))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"400 - Bad Request\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--signal-id %s --workspace-id %s\", customSignalID, workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader((testutil.GenJSON(customSignal)))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: customSignalString,\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--signal-id %s --workspace-id %s --json\", customSignalID, workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader((testutil.GenJSON(customSignal)))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(customSignal),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, sub2.CommandName, \"get\"}, scenarios)\n}\n\nfunc TestCustomSignalList(t *testing.T) {\n\tcustomSignalsObject := signals.Signals{\n\t\tData: []signals.Signal{\n\t\t\t{\n\t\t\t\tCreatedAt:   testutil.Date,\n\t\t\t\tDescription: customSignalDescription,\n\t\t\t\tName:        customSignalName,\n\t\t\t\tSignalID:    customSignalID,\n\t\t\t\tScope: signals.Scope{\n\t\t\t\t\tType:      string(scope.ScopeTypeWorkspace),\n\t\t\t\t\tAppliesTo: []string{workspaceID},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tCreatedAt:   testutil.Date,\n\t\t\t\tDescription: customSignalDescription,\n\t\t\t\tName:        customSignalName + \"2\",\n\t\t\t\tSignalID:    customSignalID + \"2\",\n\t\t\t\tScope: signals.Scope{\n\t\t\t\t\tType:      string(scope.ScopeTypeWorkspace),\n\t\t\t\t\tAppliesTo: []string{workspaceID},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tMeta: signals.MetaSignals{},\n\t}\n\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --workspace-id flag\",\n\t\t\tArgs:      \"\",\n\t\t\tWantError: \"error parsing arguments: required flag --workspace-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate internal server error\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s\", workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusInternalServerError,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusInternalServerError),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"500 - Internal Server Error\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success (zero workspace-level custom signals)\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s\", workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(signals.Signals{\n\t\t\t\t\t\t\tData: []signals.Signal{},\n\t\t\t\t\t\t\tMeta: signals.MetaSignals{},\n\t\t\t\t\t\t}))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: zeroListCustomSignalsString,\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s\", workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(customSignalsObject))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: listCustomSignalsString,\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --json\", workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(customSignalsObject))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(customSignalsObject),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, sub2.CommandName, \"list\"}, scenarios)\n}\n\nfunc TestCustomSignalUpdate(t *testing.T) {\n\tcustomSignalObject := signals.Signal{\n\t\tCreatedAt:   testutil.Date,\n\t\tDescription: customSignalDescription,\n\t\tName:        customSignalName,\n\t\tSignalID:    customSignalID,\n\t}\n\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --signal-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--description %s --workspace-id %s\", customSignalDescription+\"2\", workspaceID),\n\t\t\tWantError: \"error parsing arguments: required flag --signal-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --workspace-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--description %s --signal-id %s\", customSignalDescription+\"2\", customSignalID),\n\t\t\tWantError: \"error parsing arguments: required flag --workspace-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --description flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--workspace-id %s --signal-id %s\", workspaceID, customSignalID),\n\t\t\tWantError: \"error parsing arguments: required flag --description not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--signal-id %s --description %s --workspace-id %s\", customSignalID, customSignalDescription, workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(customSignalObject))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Updated workspace-level signal '%s' (signal-id: %s)\", customSignalName, customSignalID),\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--signal-id %s --description %s --workspace-id %s --json\", customSignalID, customSignalDescription, workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(customSignal))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(customSignal),\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, sub2.CommandName, \"update\"}, scenarios)\n}\n\nvar listCustomSignalsString = strings.TrimSpace(`\nID       Name                  Description           Scope      Updated At                     Created At\nsomeID   CLICustomSignalName   NGWAFCLICustomSignal  workspace  0001-01-01 00:00:00 +0000 UTC  2021-06-15 23:00:00 +0000 UTC\nsomeID2  CLICustomSignalName2  NGWAFCLICustomSignal  workspace  0001-01-01 00:00:00 +0000 UTC  2021-06-15 23:00:00 +0000 UTC\n`) + \"\\n\"\n\nvar zeroListCustomSignalsString = strings.TrimSpace(`\nID  Name  Description  Scope  Updated At  Created At\n`) + \"\\n\"\n\nvar customSignalString = strings.TrimSpace(`\nID: someID\nName: CLICustomSignalName\nDescription: NGWAFCLICustomSignal\nScope: workspace\nUpdated (UTC): 0001-01-01 00:00\nCreated (UTC): 2021-06-15 23:00\n`)\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/customsignal/delete.go",
    "content": "package customsignal\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/scope\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/signals\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DeleteCommand calls the Fastly API to delete a workspace-level custom signal.\ntype DeleteCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tsignalID    string\n\tworkspaceID argparser.OptionalWorkspaceID\n}\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\n\tc.CmdClause = parent.Command(\"delete\", \"Delete a workspace-level custom signal\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"signal-id\", \"Custom Signal ID\").Required().StringVar(&c.signalID)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagNGWAFWorkspaceID,\n\t\tDescription: argparser.FlagNGWAFWorkspaceIDDesc,\n\t\tDst:         &c.workspaceID.Value,\n\t\tAction:      c.workspaceID.Set,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DeleteCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tfc, ok := c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tinput := &signals.DeleteInput{\n\t\tSignalID: &c.signalID,\n\t\tScope: &scope.Scope{\n\t\t\tType: scope.ScopeTypeWorkspace,\n\t\t},\n\t}\n\tif err := c.workspaceID.Parse(); err != nil {\n\t\treturn err\n\t}\n\tinput.Scope.AppliesTo = []string{c.workspaceID.Value}\n\n\terr := signals.Delete(context.TODO(), fc, input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif c.JSONOutput.Enabled {\n\t\to := struct {\n\t\t\tID      string `json:\"id\"`\n\t\t\tDeleted bool   `json:\"deleted\"`\n\t\t}{\n\t\t\tc.signalID,\n\t\t\ttrue,\n\t\t}\n\t\t_, err := c.WriteJSON(out, o)\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Deleted workspace-level custom signal (id: %s)\", c.signalID)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/customsignal/doc.go",
    "content": "// Package customsignal contains commands to inspect and manipulate NGWAF workspace-level custom signals.\npackage customsignal\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/customsignal/get.go",
    "content": "package customsignal\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/scope\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/signals\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// GetCommand calls the Fastly API to get a workspace-level custom signal.\ntype GetCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tsignalID    string\n\tworkspaceID argparser.OptionalWorkspaceID\n}\n\n// NewGetCommand returns a usable command registered under the parent.\nfunc NewGetCommand(parent argparser.Registerer, g *global.Data) *GetCommand {\n\tc := GetCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\n\tc.CmdClause = parent.Command(\"get\", \"Get a custom signal\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"signal-id\", \"Custom Signal ID\").Required().StringVar(&c.signalID)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagNGWAFWorkspaceID,\n\t\tDescription: argparser.FlagNGWAFWorkspaceIDDesc,\n\t\tDst:         &c.workspaceID.Value,\n\t\tAction:      c.workspaceID.Set,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *GetCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tfc, ok := c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tinput := &signals.GetInput{\n\t\tSignalID: &c.signalID,\n\t\tScope: &scope.Scope{\n\t\t\tType: scope.ScopeTypeWorkspace,\n\t\t},\n\t}\n\n\tif err := c.workspaceID.Parse(); err != nil {\n\t\treturn err\n\t}\n\tinput.Scope.AppliesTo = []string{c.workspaceID.Value}\n\n\tdata, err := signals.Get(context.TODO(), fc, input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, data); ok {\n\t\treturn err\n\t}\n\n\ttext.PrintCustomSignal(out, data)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/customsignal/list.go",
    "content": "package customsignal\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/scope\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/signals\"\n)\n\n// ListCommand calls the Fastly API to list all workspace-level custom signals for your API token.\ntype ListCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tworkspaceID argparser.OptionalWorkspaceID\n}\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\n\tc.CmdClause = parent.Command(\"list\", \"List all workspace-level custom signals\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagNGWAFWorkspaceID,\n\t\tDescription: argparser.FlagNGWAFWorkspaceIDDesc,\n\t\tDst:         &c.workspaceID.Value,\n\t\tAction:      c.workspaceID.Set,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *ListCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tfc, ok := c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tinput := &signals.ListInput{\n\t\tScope: &scope.Scope{\n\t\t\tType: scope.ScopeTypeWorkspace,\n\t\t},\n\t}\n\n\tif err := c.workspaceID.Parse(); err != nil {\n\t\treturn err\n\t}\n\tinput.Scope.AppliesTo = []string{c.workspaceID.Value}\n\n\tsignals, err := signals.List(context.TODO(), fc, input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, signals); ok {\n\t\treturn err\n\t}\n\n\ttext.PrintCustomSignalTbl(out, signals.Data)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/customsignal/root.go",
    "content": "package customsignal\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"customsignal\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manage NGWAF Workspace-Level Custom Signals\")\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/customsignal/update.go",
    "content": "package customsignal\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/scope\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/signals\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// UpdateCommand calls the Fastly API to update workspace-level custom signals.\ntype UpdateCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tsignalID    string\n\tdescription string\n\tworkspaceID argparser.OptionalWorkspaceID\n}\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"update\", \"Update a workspace\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"signal-id\", \"Custom Signal ID\").Required().StringVar(&c.signalID)\n\tc.CmdClause.Flag(\"description\", \"User submitted description of a custom signal.\").Required().StringVar(&c.description)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagNGWAFWorkspaceID,\n\t\tDescription: argparser.FlagNGWAFWorkspaceIDDesc,\n\t\tDst:         &c.workspaceID.Value,\n\t\tAction:      c.workspaceID.Set,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *UpdateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\tvar err error\n\tinput := &signals.UpdateInput{\n\t\tSignalID:    &c.signalID,\n\t\tDescription: &c.description,\n\t\tScope: &scope.Scope{\n\t\t\tType: scope.ScopeTypeWorkspace,\n\t\t},\n\t}\n\n\tif err := c.workspaceID.Parse(); err != nil {\n\t\treturn err\n\t}\n\tinput.Scope.AppliesTo = []string{c.workspaceID.Value}\n\n\tfc, ok := c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tdata, err := signals.Update(context.TODO(), fc, input)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, data); ok {\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Updated workspace-level signal '%s' (signal-id: %s)\", data.Name, data.SignalID)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/delete.go",
    "content": "package workspace\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/workspaces\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DeleteCommand calls the Fastly API to delete a workspace.\ntype DeleteCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tworkspaceID string\n}\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\n\tc.CmdClause = parent.Command(\"delete\", \"Delete a workspace\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"workspace-id\", \"Workspace ID\").Required().StringVar(&c.workspaceID)\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DeleteCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tfc, ok := c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\terr := workspaces.Delete(context.TODO(), fc, &workspaces.DeleteInput{\n\t\tWorkspaceID: &c.workspaceID,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif c.JSONOutput.Enabled {\n\t\to := struct {\n\t\t\tID      string `json:\"id\"`\n\t\t\tDeleted bool   `json:\"deleted\"`\n\t\t}{\n\t\t\tc.workspaceID,\n\t\t\ttrue,\n\t\t}\n\t\t_, err := c.WriteJSON(out, o)\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Deleted workspace (id: %s)\", c.workspaceID)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/doc.go",
    "content": "// Package workspace contains commands to inspect and manipulate NGWAF workspaces.\npackage workspace\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/get.go",
    "content": "package workspace\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/workspaces\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// GetCommand calls the Fastly API to get a workspace.\ntype GetCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tworkspaceID string\n}\n\n// NewGetCommand returns a usable command registered under the parent.\nfunc NewGetCommand(parent argparser.Registerer, g *global.Data) *GetCommand {\n\tc := GetCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\n\tc.CmdClause = parent.Command(\"get\", \"Get a workspace\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"workspace-id\", \"Workspace ID\").Required().StringVar(&c.workspaceID)\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *GetCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tfc, ok := c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tdata, err := workspaces.Get(context.TODO(), fc, &workspaces.GetInput{\n\t\tWorkspaceID: &c.workspaceID,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, data); ok {\n\t\treturn err\n\t}\n\n\ttext.PrintWorkspace(out, data)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/iplist/create.go",
    "content": "package iplist\n\nimport (\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/scope\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/ngwaf/ngwaflist\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// CreateCommand calls the Fastly API to create workspace-level ip lists.\ntype CreateCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tentries     string\n\tname        string\n\tworkspaceID argparser.OptionalWorkspaceID\n\n\t// Optional.\n\tdescription argparser.OptionalString\n}\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"create\", \"Create a workspace-level ip list\").Alias(\"add\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"entries\", \"Entries for the list. Can either be a comma separated list or a path to a file.\").Required().StringVar(&c.entries)\n\tc.CmdClause.Flag(\"name\", \"User submitted display name of a list.\").Required().StringVar(&c.name)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagNGWAFWorkspaceID,\n\t\tDescription: argparser.FlagNGWAFWorkspaceIDDesc,\n\t\tDst:         &c.workspaceID.Value,\n\t\tAction:      c.workspaceID.Set,\n\t})\n\n\t// Optional.\n\tc.CmdClause.Flag(\"description\", \"User submitted description of the list.\").Action(c.description.Set).StringVar(&c.description.Value)\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *CreateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\tinput := ngwaflist.ListCreateInput{\n\t\tCommandScope: scope.ScopeTypeWorkspace,\n\t\tDescription:  c.description,\n\t\tEntries:      c.entries,\n\t\tName:         c.name,\n\t\tType:         \"ip\",\n\t\tWorkspaceID:  &c.workspaceID,\n\t}\n\n\tvar ok bool\n\tinput.FC, ok = c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tdata, err := ngwaflist.ListCreate(input)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, data); ok {\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Created Workspace IP List '%s' (list id: %s)\", data.Name, data.ListID)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/iplist/delete.go",
    "content": "package iplist\n\nimport (\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/scope\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/ngwaf/ngwaflist\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DeleteCommand calls the Fastly API to delete an account-level ip list.\ntype DeleteCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tlistID      string\n\tworkspaceID argparser.OptionalWorkspaceID\n}\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\n\tc.CmdClause = parent.Command(\"delete\", \"Delete an account ip list\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"list-id\", \"List ID\").Required().StringVar(&c.listID)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagNGWAFWorkspaceID,\n\t\tDescription: argparser.FlagNGWAFWorkspaceIDDesc,\n\t\tDst:         &c.workspaceID.Value,\n\t\tAction:      c.workspaceID.Set,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DeleteCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tinput := ngwaflist.ListDeleteInput{\n\t\tCommandScope: scope.ScopeTypeWorkspace,\n\t\tListID:       c.listID,\n\t\tWorkspaceID:  &c.workspaceID,\n\t}\n\n\tvar ok bool\n\tinput.FC, ok = c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\terr := ngwaflist.ListDelete(input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif c.JSONOutput.Enabled {\n\t\to := struct {\n\t\t\tID      string `json:\"id\"`\n\t\t\tDeleted bool   `json:\"deleted\"`\n\t\t}{\n\t\t\tc.listID,\n\t\t\ttrue,\n\t\t}\n\t\t_, err := c.WriteJSON(out, o)\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Deleted Workspace IP List (list id: %s)\", c.listID)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/iplist/doc.go",
    "content": "// Package iplist contains commands to inspect and manipulate NGWAF workspace-level ip lists.\npackage iplist\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/iplist/get.go",
    "content": "package iplist\n\nimport (\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/ngwaf/ngwaflist\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/scope\"\n)\n\n// GetCommand calls the Fastly API to get a workspace-level ip list.\ntype GetCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tlistID      string\n\tworkspaceID argparser.OptionalWorkspaceID\n}\n\n// NewGetCommand returns a usable command registered under the parent.\nfunc NewGetCommand(parent argparser.Registerer, g *global.Data) *GetCommand {\n\tc := GetCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\n\tc.CmdClause = parent.Command(\"get\", \"Get a workspace-level ip list\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"list-id\", \"List ID\").Required().StringVar(&c.listID)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagNGWAFWorkspaceID,\n\t\tDescription: argparser.FlagNGWAFWorkspaceIDDesc,\n\t\tDst:         &c.workspaceID.Value,\n\t\tAction:      c.workspaceID.Set,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *GetCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tinput := ngwaflist.ListGetInput{\n\t\tCommandScope: scope.ScopeTypeWorkspace,\n\t\tListID:       c.listID,\n\t\tWorkspaceID:  &c.workspaceID,\n\t}\n\n\tvar ok bool\n\tinput.FC, ok = c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tlist, err := ngwaflist.ListGet(input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, list); ok {\n\t\treturn err\n\t}\n\n\ttext.PrintList(out, list)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/iplist/iplist_test.go",
    "content": "package iplist_test\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\t\"testing\"\n\n\troot \"github.com/fastly/cli/pkg/commands/ngwaf\"\n\tsub \"github.com/fastly/cli/pkg/commands/ngwaf/workspace\"\n\tsub2 \"github.com/fastly/cli/pkg/commands/ngwaf/workspace/iplist\"\n\tfstfmt \"github.com/fastly/cli/pkg/fmt\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/lists\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/scope\"\n)\n\nconst (\n\tlistID          = \"someListID\"\n\tlistDescription = \"NGWAFCLIList\"\n\tlistEntries     = \"1.0.0.0\"\n\tlistType        = \"ip\"\n\tlistName        = \"listName\"\n\tworkspaceID     = \"someWorkspaceID\"\n)\n\nvar stringlist = lists.List{\n\tListID:      listID,\n\tDescription: listDescription,\n\tEntries:     []string{listEntries},\n\tName:        listName,\n\tType:        listType,\n\tCreatedAt:   testutil.Date,\n\tUpdatedAt:   testutil.Date,\n\tScope: lists.Scope{\n\t\tType: string(scope.ScopeTypeWorkspace),\n\t},\n}\n\nvar stringlist2 = lists.List{\n\tListID:      listID + \"2\",\n\tDescription: listDescription + \"2\",\n\tEntries:     []string{listEntries},\n\tName:        listName + \"2\",\n\tType:        listType,\n\tCreatedAt:   testutil.Date,\n\tUpdatedAt:   testutil.Date,\n\tScope: lists.Scope{\n\t\tType: string(scope.ScopeTypeWorkspace),\n\t},\n}\n\nfunc TestIPListCreate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --entries flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--name %s --workspace-id %s\", listName, workspaceID),\n\t\t\tWantError: \"error parsing arguments: required flag --entries not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --name flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--entries %s --workspace-id %s\", listEntries, workspaceID),\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate missing --workspace-id flag\",\n\t\t\tArgs: fmt.Sprintf(\"--entries %s --name %s\", listEntries, listName),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusNoContent,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusNoContent),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"error reading workspace ID: no workspace ID found\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate internal server error\",\n\t\t\tArgs: fmt.Sprintf(\"--entries %s --name %s --workspace-id %s\", listEntries, listName, workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusInternalServerError,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusInternalServerError),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"500 - Internal Server Error\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--entries %s --name %s --workspace-id %s\", listEntries, listName, workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader((testutil.GenJSON(stringlist)))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Created Workspace IP List '%s' (list id: %s)\", listName, listID),\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--entries %s --name %s --workspace-id %s --json\", listEntries, listName, workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(stringlist))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(stringlist),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, sub2.CommandName, \"create\"}, scenarios)\n}\n\nfunc TestIPListDelete(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --list-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--workspace-id %s\", workspaceID),\n\t\t\tWantError: \"error parsing arguments: required flag --list-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate missing --workspace-id flag\",\n\t\t\tArgs: fmt.Sprintf(\"--list-id %s\", listID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusNoContent,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusNoContent),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"error reading workspace ID: no workspace ID found\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate bad request\",\n\t\t\tArgs: \"--list-id bar --workspace-id baz\",\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusBadRequest,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusBadRequest),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(`\n\t\t\t\t\t\t\t{\n    \t\t\t\t\t\t\t\"title\": \"invalid List ID\",\n    \t\t\t\t\t\t\t\"status\": 400\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t`))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"400 - Bad Request\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--list-id %s --workspace-id %s\", listID, workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusNoContent,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusNoContent),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Deleted Workspace IP List (list id: %s)\", listID),\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--list-id %s --workspace-id %s --json\", listID, workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusNoContent,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusNoContent),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.JSON(`{\"id\": %q, \"deleted\": true}`, listID),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, sub2.CommandName, \"delete\"}, scenarios)\n}\n\nfunc TestIPListGet(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --list-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--workspace-id %s\", workspaceID),\n\t\t\tWantError: \"error parsing arguments: required flag --list-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate missing --workspace-id flag\",\n\t\t\tArgs: fmt.Sprintf(\"--list-id %s\", listID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusNoContent,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusNoContent),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"error reading workspace ID: no workspace ID found\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate bad request\",\n\t\t\tArgs: \"--list-id baz --workspace-id baz\",\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusBadRequest,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusBadRequest),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(`\n\t\t\t\t\t\t\t{\n    \t\t\t\t\t\t\t\"title\": \"invalid List ID\",\n    \t\t\t\t\t\t\t\"status\": 400\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t`))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"400 - Bad Request\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--list-id %s --workspace-id %s\", listID, workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader((testutil.GenJSON(stringlist)))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: listString,\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--list-id %s --workspace-id %s --json\", listID, workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader((testutil.GenJSON(stringlist)))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(stringlist),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, sub2.CommandName, \"get\"}, scenarios)\n}\n\nfunc TestIPListList(t *testing.T) {\n\tlistsObject := lists.Lists{\n\t\tData: []lists.List{\n\t\t\tstringlist,\n\t\t\tstringlist2,\n\t\t},\n\t\tMeta: lists.MetaLists{},\n\t}\n\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName: \"validate missing --workspace-id flag\",\n\t\t\tArgs: \"\",\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusNoContent,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusNoContent),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"error reading workspace ID: no workspace ID found\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate internal server error\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s\", workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusInternalServerError,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusInternalServerError),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"500 - Internal Server Error\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success (zero workspaces)\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s\", workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(lists.Lists{\n\t\t\t\t\t\t\tData: []lists.List{},\n\t\t\t\t\t\t\tMeta: lists.MetaLists{},\n\t\t\t\t\t\t}))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: zeroListString,\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s\", workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(listsObject))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: listListsString,\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --json\", workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(listsObject))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(listsObject),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, sub2.CommandName, \"list\"}, scenarios)\n}\n\nfunc TestIPListUpdate(t *testing.T) {\n\tupdatelist := lists.List{\n\t\tListID:      listID,\n\t\tDescription: listDescription + \"2\",\n\t\tEntries:     []string{listEntries + \"2\"},\n\t\tName:        listName,\n\t\tType:        listType,\n\t\tCreatedAt:   testutil.Date,\n\t\tUpdatedAt:   testutil.Date,\n\t\tScope: lists.Scope{\n\t\t\tType: string(scope.ScopeTypeWorkspace),\n\t\t},\n\t}\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --list-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--workspace-id %s\", workspaceID),\n\t\t\tWantError: \"error parsing arguments: required flag --list-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate missing --workspace-id flag\",\n\t\t\tArgs: fmt.Sprintf(\"--list-id %s\", listID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusNoContent,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusNoContent),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"error reading workspace ID: no workspace ID found\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--list-id %s --description %s --entries %s --workspace-id %s\", listID, listDescription+\"2\", listEntries+\"2\", workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(updatelist))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Updated Workspace IP List '%s' (list id: %s)\", listName, listID),\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--list-id %s --description %s --entries %s --workspace-id %s --json\", listID, listDescription+\"2\", listEntries+\"2\", workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(updatelist))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(updatelist),\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, sub2.CommandName, \"update\"}, scenarios)\n}\n\nvar listListsString = strings.TrimSpace(`\nID           Name       Description    Type  Scope      Entries  Updated At                     Created At\nsomeListID   listName   NGWAFCLIList   ip    workspace  1.0.0.0  2021-06-15 23:00:00 +0000 UTC  2021-06-15 23:00:00 +0000 UTC\nsomeListID2  listName2  NGWAFCLIList2  ip    workspace  1.0.0.0  2021-06-15 23:00:00 +0000 UTC  2021-06-15 23:00:00 +0000 UTC\n`) + \"\\n\"\n\nvar zeroListString = strings.TrimSpace(`\nID  Name  Description  Type  Scope  Entries  Updated At  Created At\n`) + \"\\n\"\n\nvar listString = strings.TrimSpace(`\nID: someListID\nName: listName\nDescription: NGWAFCLIList\nType: ip\nEntries: 1.0.0.0\nScope: workspace\nUpdated (UTC): 2021-06-15 23:00\n`)\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/iplist/list.go",
    "content": "package iplist\n\nimport (\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/ngwaf/ngwaflist\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/scope\"\n)\n\n// ListCommand calls the Fastly API to list all ip lists for your workspace.\n\ntype ListCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tworkspaceID argparser.OptionalWorkspaceID\n}\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\n\tc.CmdClause = parent.Command(\"list\", \"List all ip lists for your workspace\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagNGWAFWorkspaceID,\n\t\tDescription: argparser.FlagNGWAFWorkspaceIDDesc,\n\t\tDst:         &c.workspaceID.Value,\n\t\tAction:      c.workspaceID.Set,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *ListCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tinput := ngwaflist.ListListInput{\n\t\tCommandScope: scope.ScopeTypeWorkspace,\n\t\tType:         \"ip\",\n\t\tWorkspaceID:  &c.workspaceID,\n\t}\n\n\tvar ok bool\n\tinput.FC, ok = c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tlists, err := ngwaflist.ListList(input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, *lists); ok {\n\t\treturn err\n\t}\n\n\ttext.PrintListTbl(out, lists.Data)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/iplist/root.go",
    "content": "package iplist\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"ip-list\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manage NGWAF Workspace IP Lists\")\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/iplist/update.go",
    "content": "package iplist\n\nimport (\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/scope\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/ngwaf/ngwaflist\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// UpdateCommand calls the Fastly API to update a workspace ip list.\ntype UpdateCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tlistID      string\n\tworkspaceID argparser.OptionalWorkspaceID\n\n\t// Optional.\n\tdescription argparser.OptionalString\n\tentries     argparser.OptionalString\n}\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"update\", \"Update a workspace-level ip list\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"list-id\", \"List ID\").Required().StringVar(&c.listID)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagNGWAFWorkspaceID,\n\t\tDescription: argparser.FlagNGWAFWorkspaceIDDesc,\n\t\tDst:         &c.workspaceID.Value,\n\t\tAction:      c.workspaceID.Set,\n\t})\n\n\t// Optional.\n\tc.CmdClause.Flag(\"description\", \"User submitted description of the list.\").Action(c.description.Set).StringVar(&c.description.Value)\n\tc.CmdClause.Flag(\"entries\", \"Entries for the list. Can either be a comma separated list or a path to a file.\").Action(c.entries.Set).StringVar(&c.entries.Value)\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *UpdateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\tinput := ngwaflist.ListUpdateInput{\n\t\tCommandScope: scope.ScopeTypeWorkspace,\n\t\tDescription:  c.description,\n\t\tEntries:      c.entries,\n\t\tListID:       c.listID,\n\t\tWorkspaceID:  &c.workspaceID,\n\t}\n\n\tvar ok bool\n\tinput.FC, ok = c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tdata, err := ngwaflist.ListUpdate(input)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, data); ok {\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Updated Workspace IP List '%s' (list id: %s)\", data.Name, data.ListID)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/list.go",
    "content": "package workspace\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/workspaces\"\n)\n\n// ListCommand calls the Fastly API to list all Workspaces for your API token.\ntype ListCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n}\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\n\tc.CmdClause = parent.Command(\"list\", \"List all workspaces\")\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *ListCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tfc, ok := c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tworkspaces, err := workspaces.List(context.TODO(), fc, &workspaces.ListInput{})\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, workspaces); ok {\n\t\treturn err\n\t}\n\n\ttext.PrintWorkspaceTbl(out, workspaces.Data)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/redaction/create.go",
    "content": "package redaction\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/workspaces/redactions\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// CreateCommand calls the Fastly API to create a redaction.\ntype CreateCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tfield         string\n\tredactionType string\n\tworkspaceID   argparser.OptionalWorkspaceID\n}\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"create\", \"Create a redaction\").Alias(\"add\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"field\", \"The name of the field that should be redacted.\").Required().StringVar(&c.field)\n\tc.CmdClause.Flag(\"type\", \"The type of field that is being redacted.\").Required().StringVar(&c.redactionType)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagNGWAFWorkspaceID,\n\t\tDescription: argparser.FlagNGWAFWorkspaceIDDesc,\n\t\tDst:         &c.workspaceID.Value,\n\t\tAction:      c.workspaceID.Set,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *CreateCommand) Exec(_ io.Reader, out io.Writer) error {\n\t// Call Parse() to ensure that we check if workspaceID\n\t// is set or to throw the appropriate error.\n\tif err := c.workspaceID.Parse(); err != nil {\n\t\treturn err\n\t}\n\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tvar err error\n\tinput := &redactions.CreateInput{\n\t\tField:       &c.field,\n\t\tType:        &c.redactionType,\n\t\tWorkspaceID: &c.workspaceID.Value,\n\t}\n\n\tfc, ok := c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tdata, err := redactions.Create(context.TODO(), fc, input)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, data); ok {\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Created redaction '%s' (field: %s, type: %s)\", data.RedactionID, data.Field, data.Type)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/redaction/delete.go",
    "content": "package redaction\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/workspaces/redactions\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DeleteCommand calls the Fastly API to delete a redaction.\ntype DeleteCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tworkspaceID argparser.OptionalWorkspaceID\n\tredactionID string\n}\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\n\tc.CmdClause = parent.Command(\"delete\", \"Delete a redaction\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"redaction-id\", \"Redaction ID\").Required().StringVar(&c.redactionID)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagNGWAFWorkspaceID,\n\t\tDescription: argparser.FlagNGWAFWorkspaceIDDesc,\n\t\tDst:         &c.workspaceID.Value,\n\t\tAction:      c.workspaceID.Set,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DeleteCommand) Exec(_ io.Reader, out io.Writer) error {\n\t// Call Parse() to ensure that we check if workspaceID\n\t// is set or to throw the appropriate error.\n\tif err := c.workspaceID.Parse(); err != nil {\n\t\treturn err\n\t}\n\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tfc, ok := c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\terr := redactions.Delete(context.TODO(), fc, &redactions.DeleteInput{\n\t\tRedactionID: &c.redactionID,\n\t\tWorkspaceID: &c.workspaceID.Value,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif c.JSONOutput.Enabled {\n\t\to := struct {\n\t\t\tID      string `json:\"id\"`\n\t\t\tDeleted bool   `json:\"deleted\"`\n\t\t}{\n\t\t\tc.redactionID,\n\t\t\ttrue,\n\t\t}\n\t\t_, err := c.WriteJSON(out, o)\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Deleted redaction (id: %s)\", c.redactionID)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/redaction/list.go",
    "content": "package redaction\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/workspaces/redactions\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// ListCommand calls the Fastly API to list redactions in a workspace.\ntype ListCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tworkspaceID argparser.OptionalWorkspaceID\n\n\t// Optional.\n\tlimit argparser.OptionalInt\n}\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\n\tc.CmdClause = parent.Command(\"list\", \"List redactions in a workspace\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagNGWAFWorkspaceID,\n\t\tDescription: argparser.FlagNGWAFWorkspaceIDDesc,\n\t\tDst:         &c.workspaceID.Value,\n\t\tAction:      c.workspaceID.Set,\n\t})\n\n\t// Optional.\n\tc.CmdClause.Flag(\"limit\", \"Limit how many results are returned\").Action(c.limit.Set).IntVar(&c.limit.Value)\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *ListCommand) Exec(_ io.Reader, out io.Writer) error {\n\t// Call Parse() to ensure that we check if workspaceID\n\t// is set or to throw the appropriate error.\n\tif err := c.workspaceID.Parse(); err != nil {\n\t\treturn err\n\t}\n\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tfc, ok := c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tinput := &redactions.ListInput{\n\t\tWorkspaceID: &c.workspaceID.Value,\n\t}\n\n\tif c.limit.WasSet {\n\t\tinput.Limit = &c.limit.Value\n\t}\n\n\tdata, err := redactions.List(context.TODO(), fc, input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, data); ok {\n\t\treturn err\n\t}\n\n\ttext.PrintRedactionTbl(out, data.Data)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/redaction/redaction_test.go",
    "content": "package redaction_test\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\t\"testing\"\n\n\troot \"github.com/fastly/cli/pkg/commands/ngwaf\"\n\tworkspace \"github.com/fastly/cli/pkg/commands/ngwaf/workspace\"\n\tsub \"github.com/fastly/cli/pkg/commands/ngwaf/workspace/redaction\"\n\tfstfmt \"github.com/fastly/cli/pkg/fmt\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/workspaces/redactions\"\n)\n\nconst (\n\tredactionField = \"password\"\n\tredactionID    = \"someID\"\n\tredactionType  = \"request\"\n\tworkspaceID    = \"workspaceID\"\n)\n\nvar redaction = redactions.Redaction{\n\tCreatedAt:   testutil.Date,\n\tField:       redactionField,\n\tRedactionID: redactionID,\n\tType:        redactionType,\n}\n\nfunc TestRedactionCreate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --field flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--type %s --workspace-id %s\", redactionType, workspaceID),\n\t\t\tWantError: \"error parsing arguments: required flag --field not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --type flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--field %s --workspace-id %s\", redactionField, workspaceID),\n\t\t\tWantError: \"error parsing arguments: required flag --type not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --workspace-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--field %s --type %s\", redactionField, redactionType),\n\t\t\tWantError: \"error reading workspace ID: no workspace ID found\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate internal server error\",\n\t\t\tArgs: fmt.Sprintf(\"--field %s --type %s --workspace-id %s\", redactionField, redactionType, workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusInternalServerError,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusInternalServerError),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"500 - Internal Server Error\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--field %s --type %s --workspace-id %s\", redactionField, redactionType, workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader((testutil.GenJSON(redaction)))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Created redaction '%s' (field: %s, type: %s)\", redactionID, redactionField, redactionType),\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--field %s --type %s --workspace-id %s --json\", redactionField, redactionType, workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(redaction))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(redaction),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, workspace.CommandName, sub.CommandName, \"create\"}, scenarios)\n}\n\nfunc TestRedactionDelete(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --redaction-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--workspace-id %s\", workspaceID),\n\t\t\tWantError: \"error parsing arguments: required flag --redaction-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --workspace-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--redaction-id %s\", redactionID),\n\t\t\tWantError: \"error reading workspace ID: no workspace ID found\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate bad request\",\n\t\t\tArgs: fmt.Sprintf(\"--redaction-id %s --workspace-id %s\", redactionID, workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusBadRequest,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusBadRequest),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(`\n\t\t\t\t\t\t\t{\n    \t\t\t\t\t\t\t\"title\": \"invalid Redaction ID\",\n    \t\t\t\t\t\t\t\"status\": 400\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t`))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"400 - Bad Request\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--redaction-id %s --workspace-id %s\", redactionID, workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusNoContent,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusNoContent),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Deleted redaction (id: %s)\", redactionID),\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--redaction-id %s --workspace-id %s --json\", redactionID, workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusNoContent,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusNoContent),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.JSON(`{\"id\": %q, \"deleted\": true}`, redactionID),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, workspace.CommandName, sub.CommandName, \"delete\"}, scenarios)\n}\n\nfunc TestRedactionRetrieve(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --redaction-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--workspace-id %s\", workspaceID),\n\t\t\tWantError: \"error parsing arguments: required flag --redaction-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --workspace-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--redaction-id %s\", redactionID),\n\t\t\tWantError: \"error reading workspace ID: no workspace ID found\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate bad request\",\n\t\t\tArgs: fmt.Sprintf(\"--redaction-id %s --workspace-id %s\", redactionID, workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusBadRequest,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusBadRequest),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(`\n\t\t\t\t\t\t\t{\n    \t\t\t\t\t\t\t\"title\": \"invalid Redaction ID\",\n    \t\t\t\t\t\t\t\"status\": 400\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t`))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"400 - Bad Request\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--redaction-id %s --workspace-id %s\", redactionID, workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader((testutil.GenJSON(redaction)))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: redactionString,\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--redaction-id %s --workspace-id %s --json\", redactionID, workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader((testutil.GenJSON(redaction)))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(redaction),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, workspace.CommandName, sub.CommandName, \"retrieve\"}, scenarios)\n}\n\nfunc TestRedactionList(t *testing.T) {\n\tredactionsObject := redactions.Redactions{\n\t\tData: []redactions.Redaction{\n\t\t\t{\n\t\t\t\tCreatedAt:   testutil.Date,\n\t\t\t\tField:       redactionField,\n\t\t\t\tRedactionID: redactionID,\n\t\t\t\tType:        redactionType,\n\t\t\t},\n\t\t\t{\n\t\t\t\tCreatedAt:   testutil.Date,\n\t\t\t\tField:       \"username\",\n\t\t\t\tRedactionID: redactionID + \"2\",\n\t\t\t\tType:        redactionType,\n\t\t\t},\n\t\t},\n\t\tMeta: redactions.MetaRedactions{},\n\t}\n\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --workspace-id flag\",\n\t\t\tArgs:      \"\",\n\t\t\tWantError: \"error reading workspace ID: no workspace ID found\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate internal server error\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s\", workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusInternalServerError,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusInternalServerError),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"500 - Internal Server Error\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success (zero redactions)\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s\", workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(redactions.Redactions{\n\t\t\t\t\t\t\tData: []redactions.Redaction{},\n\t\t\t\t\t\t\tMeta: redactions.MetaRedactions{},\n\t\t\t\t\t\t}))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: zeroListRedactionString,\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s\", workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(redactionsObject))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: listRedactionString,\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --json\", workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(redactionsObject))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(redactionsObject),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, workspace.CommandName, sub.CommandName, \"list\"}, scenarios)\n}\n\nfunc TestRedactionUpdate(t *testing.T) {\n\tredactionsObject := redactions.Redaction{\n\t\tCreatedAt:   testutil.Date,\n\t\tField:       redactionField,\n\t\tRedactionID: redactionID,\n\t\tType:        redactionType,\n\t}\n\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --redaction-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--workspace-id %s\", workspaceID),\n\t\t\tWantError: \"error parsing arguments: required flag --redaction-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --workspace-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--redaction-id %s\", redactionID),\n\t\t\tWantError: \"error reading workspace ID: no workspace ID found\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--redaction-id %s --workspace-id %s --field %s --type %s\", redactionID, workspaceID, redactionField, redactionType),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(redactionsObject))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Updated redaction '%s' (field: %s, type: %s)\", redactionID, redactionField, redactionType),\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--redaction-id %s --workspace-id %s --field %s --type %s --json\", redactionID, workspaceID, redactionField, redactionType),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(redaction))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(redaction),\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, workspace.CommandName, sub.CommandName, \"update\"}, scenarios)\n}\n\nvar listRedactionString = strings.TrimSpace(`\nField     ID       Type     Created At\npassword  someID   request  2021-06-15 23:00:00 +0000 UTC\nusername  someID2  request  2021-06-15 23:00:00 +0000 UTC\n`) + \"\\n\"\n\nvar zeroListRedactionString = strings.TrimSpace(`\nField  ID  Type  Created At\n`) + \"\\n\"\n\nvar redactionString = strings.TrimSpace(`\nField: password\nID: someID\nType: request\nCreated At: 2021-06-15 23:00:00 +0000 UTC\n`)\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/redaction/retrieve.go",
    "content": "package redaction\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/workspaces/redactions\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// GetCommand calls the Fastly API to get a redaction.\ntype GetCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tredactionID string\n\tworkspaceID argparser.OptionalWorkspaceID\n}\n\n// NewGetCommand returns a usable command registered under the parent.\nfunc NewRetrieveCommand(parent argparser.Registerer, g *global.Data) *GetCommand {\n\tc := GetCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\n\tc.CmdClause = parent.Command(\"retrieve\", \"Retrieve a redaction\").Alias(\"get\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"redaction-id\", \"Redaction ID\").Required().StringVar(&c.redactionID)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagNGWAFWorkspaceID,\n\t\tDescription: argparser.FlagNGWAFWorkspaceIDDesc,\n\t\tDst:         &c.workspaceID.Value,\n\t\tAction:      c.workspaceID.Set,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *GetCommand) Exec(_ io.Reader, out io.Writer) error {\n\t// Call Parse() to ensure that we check if workspaceID\n\t// is set or to throw the appropriate error.\n\tif err := c.workspaceID.Parse(); err != nil {\n\t\treturn err\n\t}\n\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tfc, ok := c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tdata, err := redactions.Get(context.TODO(), fc, &redactions.GetInput{\n\t\tRedactionID: &c.redactionID,\n\t\tWorkspaceID: &c.workspaceID.Value,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, data); ok {\n\t\treturn err\n\t}\n\n\ttext.PrintRedaction(out, data)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/redaction/root.go",
    "content": "package redaction\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"redaction\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manage NGWAF Redactions\")\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/redaction/update.go",
    "content": "package redaction\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/workspaces/redactions\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// UpdateCommand calls the Fastly API to update redactions.\ntype UpdateCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tredactionID string\n\tworkspaceID argparser.OptionalWorkspaceID\n\n\t// Optional.\n\tfield         argparser.OptionalString\n\tredactionType argparser.OptionalString\n}\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"update\", \"Update a redaction\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"redaction-id\", \"A base62-encoded representation of a UUID used to uniquely identify a redaction.\").Required().StringVar(&c.redactionID)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagNGWAFWorkspaceID,\n\t\tDescription: argparser.FlagNGWAFWorkspaceIDDesc,\n\t\tDst:         &c.workspaceID.Value,\n\t\tAction:      c.workspaceID.Set,\n\t})\n\n\t// Optional.\n\tc.CmdClause.Flag(\"field\", \"The name of the field that should be redacted.\").Action(c.field.Set).StringVar(&c.field.Value)\n\tc.CmdClause.Flag(\"type\", \"The type of field that is being redacted.\").Action(c.redactionType.Set).StringVar(&c.redactionType.Value)\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *UpdateCommand) Exec(_ io.Reader, out io.Writer) error {\n\t// Call Parse() to ensure that we check if workspaceID\n\t// is set or to throw the appropriate error.\n\tif err := c.workspaceID.Parse(); err != nil {\n\t\treturn err\n\t}\n\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tvar err error\n\tinput := &redactions.UpdateInput{\n\t\tRedactionID: &c.redactionID,\n\t\tWorkspaceID: &c.workspaceID.Value,\n\t}\n\n\tif c.field.WasSet {\n\t\tinput.Field = &c.field.Value\n\t}\n\n\tif c.redactionType.WasSet {\n\t\tinput.Type = &c.redactionType.Value\n\t}\n\n\tfc, ok := c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tdata, err := redactions.Update(context.TODO(), fc, input)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, data); ok {\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Updated redaction '%s' (field: %s, type: %s)\", data.RedactionID, data.Field, data.Type)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/root.go",
    "content": "package workspace\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"workspace\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manage NGWAF Workspaces\")\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/rule/create.go",
    "content": "package rule\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"path/filepath\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/rules\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/scope\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// CreateCommand calls the Fastly API to create workspace-level rules.\ntype CreateCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tpath        string\n\tworkspaceID argparser.OptionalWorkspaceID\n}\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"create\", \"Create a workspace-level rule\").Alias(\"add\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"path\", \"Path to a json file that contains the rule schema.\").Required().StringVar(&c.path)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagNGWAFWorkspaceID,\n\t\tDescription: argparser.FlagNGWAFWorkspaceIDDesc,\n\t\tDst:         &c.workspaceID.Value,\n\t\tAction:      c.workspaceID.Set,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *CreateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\tif err := c.workspaceID.Parse(); err != nil {\n\t\treturn err\n\t}\n\trule := &rules.Rule{}\n\tif c.path != \"\" {\n\t\tpath, err := filepath.Abs(c.path)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"error parsing path '%s': %q\", c.path, err)\n\t\t}\n\n\t\tjsonFile, err := os.Open(path)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"error reading path '%s': %q\", c.path, err)\n\t\t}\n\t\tdefer jsonFile.Close()\n\n\t\tbyteValue, err := io.ReadAll(jsonFile)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to read json file: %v\", err)\n\t\t}\n\n\t\tif err := json.Unmarshal(byteValue, rule); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to unmarshal json data: %v\", err)\n\t\t}\n\t}\n\n\tinput := &rules.CreateInput{\n\t\tActions:            []*rules.CreateAction{},\n\t\tConditions:         []*rules.CreateCondition{},\n\t\tDescription:        &rule.Description,\n\t\tGroupConditions:    []*rules.CreateGroupCondition{},\n\t\tMultivalConditions: []*rules.CreateMultivalCondition{},\n\t\tEnabled:            &rule.Enabled,\n\t\tType:               &rule.Type,\n\t\tGroupOperator:      &rule.GroupOperator,\n\t\tRequestLogging:     &rule.RequestLogging,\n\t\tScope: &scope.Scope{\n\t\t\tType:      scope.ScopeTypeWorkspace,\n\t\t\tAppliesTo: []string{c.workspaceID.Value},\n\t\t},\n\t}\n\n\tfor _, action := range rule.Actions {\n\t\tinput.Actions = append(input.Actions, &rules.CreateAction{\n\t\t\tAllowInteractive: action.AllowInteractive,\n\t\t\tDeceptionType:    &action.DeceptionType,\n\t\t\tRedirectURL:      &action.RedirectURL,\n\t\t\tResponseCode:     &action.ResponseCode,\n\t\t\tSignal:           &action.Signal,\n\t\t\tType:             &action.Type,\n\t\t})\n\t}\n\n\tif rule.RateLimit != nil {\n\t\tinput.RateLimit = &rules.CreateRateLimit{\n\t\t\tClientIdentifiers: []*rules.CreateClientIdentifier{},\n\t\t\tDuration:          &rule.RateLimit.Duration,\n\t\t\tInterval:          &rule.RateLimit.Interval,\n\t\t\tSignal:            &rule.RateLimit.Signal,\n\t\t\tThreshold:         &rule.RateLimit.Threshold,\n\t\t}\n\n\t\tfor _, rateLimit := range rule.RateLimit.ClientIdentifiers {\n\t\t\tinput.RateLimit.ClientIdentifiers = append(input.RateLimit.ClientIdentifiers, &rules.CreateClientIdentifier{\n\t\t\t\tKey:  &rateLimit.Key,\n\t\t\t\tName: &rateLimit.Name,\n\t\t\t\tType: &rateLimit.Type,\n\t\t\t})\n\t\t}\n\t}\n\n\tfor _, jsonCondition := range rule.Conditions {\n\t\tswitch jsonCondition.Type {\n\t\tcase \"single\":\n\t\t\tif sc, ok := jsonCondition.Fields.(rules.SingleCondition); ok {\n\t\t\t\tinput.Conditions = append(input.Conditions, &rules.CreateCondition{\n\t\t\t\t\tField:    &sc.Field,\n\t\t\t\t\tOperator: &sc.Operator,\n\t\t\t\t\tValue:    &sc.Value,\n\t\t\t\t})\n\t\t\t} else {\n\t\t\t\treturn fmt.Errorf(\"expected SingleCondition, got %T\", jsonCondition.Fields)\n\t\t\t}\n\t\tcase \"group\":\n\t\t\tif gc, ok := jsonCondition.Fields.(rules.GroupCondition); ok {\n\t\t\t\tparsedGroupCondition := &rules.CreateGroupCondition{\n\t\t\t\t\tGroupOperator: &gc.GroupOperator,\n\t\t\t\t\tConditions:    []*rules.CreateCondition{},\n\t\t\t\t}\n\t\t\t\tfor _, groupCondition := range gc.Conditions {\n\t\t\t\t\tswitch groupCondition.Type {\n\t\t\t\t\tcase \"single\":\n\t\t\t\t\t\tif gsc, ok := groupCondition.Fields.(rules.Condition); ok {\n\t\t\t\t\t\t\tparsedGroupCondition.Conditions = append(parsedGroupCondition.Conditions, &rules.CreateCondition{\n\t\t\t\t\t\t\t\tField:    &gsc.Field,\n\t\t\t\t\t\t\t\tOperator: &gsc.Operator,\n\t\t\t\t\t\t\t\tValue:    &gsc.Value,\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\treturn fmt.Errorf(\"expected Condition, got %T\", groupCondition.Fields)\n\t\t\t\t\t\t}\n\t\t\t\t\tcase \"multival\":\n\t\t\t\t\t\tif gmvc, ok := groupCondition.Fields.(rules.MultivalCondition); ok {\n\t\t\t\t\t\t\tcreateMultivalCondition := &rules.CreateMultivalCondition{\n\t\t\t\t\t\t\t\tField:         &gmvc.Field,\n\t\t\t\t\t\t\t\tOperator:      &gmvc.Operator,\n\t\t\t\t\t\t\t\tGroupOperator: &gmvc.GroupOperator,\n\t\t\t\t\t\t\t\tConditions:    []*rules.CreateConditionMult{},\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tfor _, groupMultivalSingleCondition := range gmvc.Conditions {\n\t\t\t\t\t\t\t\tcreateMultivalCondition.Conditions = append(createMultivalCondition.Conditions, &rules.CreateConditionMult{\n\t\t\t\t\t\t\t\t\tField:    &groupMultivalSingleCondition.Field,\n\t\t\t\t\t\t\t\t\tOperator: &groupMultivalSingleCondition.Operator,\n\t\t\t\t\t\t\t\t\tValue:    &groupMultivalSingleCondition.Value,\n\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tparsedGroupCondition.MultivalConditions = append(parsedGroupCondition.MultivalConditions, createMultivalCondition)\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\treturn fmt.Errorf(\"expected MultivalCondition, got %T\", groupCondition.Fields)\n\t\t\t\t\t\t}\n\n\t\t\t\t\tdefault:\n\t\t\t\t\t\treturn fmt.Errorf(\"unknown condition type: %s\", groupCondition.Type)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tinput.GroupConditions = append(input.GroupConditions, parsedGroupCondition)\n\t\t\t} else {\n\t\t\t\treturn fmt.Errorf(\"expected GroupCondition, got %T\", jsonCondition.Fields)\n\t\t\t}\n\t\tcase \"multival\":\n\t\t\tif mvc, ok := jsonCondition.Fields.(rules.CreateMultivalCondition); ok {\n\t\t\t\tparsedMultiValCondition := &rules.CreateMultivalCondition{\n\t\t\t\t\tField:         mvc.Field,\n\t\t\t\t\tGroupOperator: mvc.GroupOperator,\n\t\t\t\t\tOperator:      mvc.Operator,\n\t\t\t\t\tConditions:    []*rules.CreateConditionMult{},\n\t\t\t\t}\n\t\t\t\tfor _, multiSingleCondition := range mvc.Conditions {\n\t\t\t\t\tparsedMultiValCondition.Conditions = append(parsedMultiValCondition.Conditions, &rules.CreateConditionMult{\n\t\t\t\t\t\tField:    multiSingleCondition.Field,\n\t\t\t\t\t\tOperator: multiSingleCondition.Operator,\n\t\t\t\t\t\tValue:    multiSingleCondition.Value,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t\tinput.MultivalConditions = append(input.MultivalConditions, parsedMultiValCondition)\n\t\t\t} else {\n\t\t\t\treturn fmt.Errorf(\"expected MultivalCondition, got %T\", jsonCondition.Fields)\n\t\t\t}\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"unknown condition type: %s\", jsonCondition.Type)\n\t\t}\n\t}\n\n\tfc, ok := c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tdata, err := rules.Create(context.TODO(), fc, input)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, data); ok {\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Created workspace-level rule with ID %s\", data.RuleID)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/rule/delete.go",
    "content": "package rule\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/rules\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/scope\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DeleteCommand calls the Fastly API to delete a workspace-level rule.\ntype DeleteCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\truleID      string\n\tworkspaceID argparser.OptionalWorkspaceID\n}\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\n\tc.CmdClause = parent.Command(\"delete\", \"Delete a workspace-level rule\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"rule-id\", \"Rule ID\").Required().StringVar(&c.ruleID)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagNGWAFWorkspaceID,\n\t\tDescription: argparser.FlagNGWAFWorkspaceIDDesc,\n\t\tDst:         &c.workspaceID.Value,\n\t\tAction:      c.workspaceID.Set,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DeleteCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\tif err := c.workspaceID.Parse(); err != nil {\n\t\treturn err\n\t}\n\n\tfc, ok := c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\terr := rules.Delete(context.TODO(), fc, &rules.DeleteInput{\n\t\tRuleID: &c.ruleID,\n\t\tScope: &scope.Scope{\n\t\t\tType:      scope.ScopeTypeWorkspace,\n\t\t\tAppliesTo: []string{c.workspaceID.Value},\n\t\t},\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif c.JSONOutput.Enabled {\n\t\to := struct {\n\t\t\tID      string `json:\"id\"`\n\t\t\tDeleted bool   `json:\"deleted\"`\n\t\t}{\n\t\t\tc.ruleID,\n\t\t\ttrue,\n\t\t}\n\t\t_, err := c.WriteJSON(out, o)\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Deleted workspace-level rule with id: %s\", c.ruleID)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/rule/doc.go",
    "content": "// Package rule contains commands to inspect and manipulate NGWAF workspace-level rules.\npackage rule\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/rule/get.go",
    "content": "package rule\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/rules\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/scope\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// GetCommand calls the Fastly API to get a workspace-level rule.\ntype GetCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\truleID      string\n\tworkspaceID argparser.OptionalWorkspaceID\n}\n\n// NewGetCommand returns a usable command registered under the parent.\nfunc NewGetCommand(parent argparser.Registerer, g *global.Data) *GetCommand {\n\tc := GetCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\n\tc.CmdClause = parent.Command(\"get\", \"Get a workspace-level rule\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"rule-id\", \"Rule ID\").Required().StringVar(&c.ruleID)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagNGWAFWorkspaceID,\n\t\tDescription: argparser.FlagNGWAFWorkspaceIDDesc,\n\t\tDst:         &c.workspaceID.Value,\n\t\tAction:      c.workspaceID.Set,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *GetCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\tif err := c.workspaceID.Parse(); err != nil {\n\t\treturn err\n\t}\n\n\tfc, ok := c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tdata, err := rules.Get(context.TODO(), fc, &rules.GetInput{\n\t\tRuleID: &c.ruleID,\n\t\tScope: &scope.Scope{\n\t\t\tType:      scope.ScopeTypeWorkspace,\n\t\t\tAppliesTo: []string{c.workspaceID.Value},\n\t\t},\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, data); ok {\n\t\treturn err\n\t}\n\n\ttext.PrintRule(out, data)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/rule/list.go",
    "content": "package rule\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\t\"strconv\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/rules\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/scope\"\n)\n\n// ListCommand calls the Fastly API to list all workspace-level rules for your API token.\ntype ListCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tworkspaceID argparser.OptionalWorkspaceID\n\n\t// Optional.\n\taction  argparser.OptionalString\n\tenabled argparser.OptionalString\n}\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\n\tc.CmdClause = parent.Command(\"list\", \"List all workspace-level rules\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagNGWAFWorkspaceID,\n\t\tDescription: argparser.FlagNGWAFWorkspaceIDDesc,\n\t\tDst:         &c.workspaceID.Value,\n\t\tAction:      c.workspaceID.Set,\n\t})\n\n\t// Optional.\n\tc.CmdClause.Flag(\"action\", \"Filter rules based on action.\").Action(c.action.Set).StringVar(&c.action.Value)\n\tc.CmdClause.Flag(\"enabled\", \"Filter rules based on whether the rule is enabled.\").Action(c.enabled.Set).StringVar(&c.enabled.Value)\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *ListCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\tif err := c.workspaceID.Parse(); err != nil {\n\t\treturn err\n\t}\n\n\tfc, ok := c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tinput := &rules.ListInput{\n\t\tScope: &scope.Scope{\n\t\t\tType:      scope.ScopeTypeWorkspace,\n\t\t\tAppliesTo: []string{c.workspaceID.Value},\n\t\t},\n\t}\n\n\tif c.action.WasSet {\n\t\tinput.Action = &c.action.Value\n\t}\n\n\tif c.enabled.WasSet {\n\t\tenabled, _ := strconv.ParseBool(c.enabled.Value)\n\t\tinput.Enabled = &enabled\n\t}\n\n\trules, err := rules.List(context.TODO(), fc, input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, rules); ok {\n\t\treturn err\n\t}\n\n\ttext.PrintRuleTbl(out, rules.Data)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/rule/root.go",
    "content": "package rule\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"rule\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manage NGWAF Account-Level Rules\")\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/rule/rule_test.go",
    "content": "package rule_test\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\t\"testing\"\n\n\troot \"github.com/fastly/cli/pkg/commands/ngwaf\"\n\tsub \"github.com/fastly/cli/pkg/commands/ngwaf/workspace\"\n\tsub2 \"github.com/fastly/cli/pkg/commands/ngwaf/workspace/rule\"\n\tfstfmt \"github.com/fastly/cli/pkg/fmt\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/rules\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/scope\"\n)\n\nconst (\n\tcomplexRulePath = \"testdata/test_complex_rule.json\"\n\tcomplexRuleID   = \"someComplexID\"\n\truleDescription = \"Utility requests\"\n\truleEnabled     = true\n\truleAction      = \"allow\"\n\truleID          = \"someID\"\n\trulePath        = \"testdata/test_rule.json\"\n\truleType        = \"request\"\n\truleWorkspaceID = \"someWorkspaceID\"\n)\n\nvar rule = rules.Rule{\n\tCreatedAt:   testutil.Date,\n\tDescription: ruleDescription,\n\tEnabled:     ruleEnabled,\n\tRuleID:      ruleID,\n\tActions: []rules.Action{\n\t\t{\n\t\t\tType: ruleAction,\n\t\t},\n\t},\n\tType: ruleType,\n\tScope: rules.Scope{\n\t\tType:      string(scope.ScopeTypeWorkspace),\n\t\tAppliesTo: []string{ruleWorkspaceID},\n\t},\n}\n\nvar complexRule = rules.Rule{\n\tCreatedAt:   testutil.Date,\n\tDescription: ruleDescription,\n\tEnabled:     ruleEnabled,\n\tRuleID:      complexRuleID,\n\tActions: []rules.Action{\n\t\t{\n\t\t\tType: ruleAction,\n\t\t},\n\t},\n\tType: ruleType,\n\tScope: rules.Scope{\n\t\tType:      string(scope.ScopeTypeWorkspace),\n\t\tAppliesTo: []string{ruleWorkspaceID},\n\t},\n}\n\nfunc TestRuleCreate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --path flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--workspace-id %s\", ruleWorkspaceID),\n\t\t\tWantError: \"error parsing arguments: required flag --path not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --workspace-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--path %s\", rulePath),\n\t\t\tWantError: \"error reading workspace ID: no workspace ID found\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate internal server error\",\n\t\t\tArgs: fmt.Sprintf(\"--path %s --workspace-id %s\", rulePath, ruleWorkspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusInternalServerError,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusInternalServerError),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"500 - Internal Server Error\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--path %s --workspace-id %s\", rulePath, ruleWorkspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader((testutil.GenJSON(rule)))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Created workspace-level rule with ID %s\", ruleID),\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success with complex rule\",\n\t\t\tArgs: fmt.Sprintf(\"--path %s --workspace-id %s\", complexRulePath, ruleWorkspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader((testutil.GenJSON(complexRule)))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Created workspace-level rule with ID %s\", complexRuleID),\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--path %s --workspace-id %s --json\", rulePath, ruleWorkspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(rule))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(rule),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, sub2.CommandName, \"create\"}, scenarios)\n}\n\nfunc TestRuleDelete(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --workspace-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--rule-id %s\", ruleID),\n\t\t\tWantError: \"error reading workspace ID: no workspace ID found\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --rule-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--workspace-id %s\", ruleWorkspaceID),\n\t\t\tWantError: \"error parsing arguments: required flag --rule-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate bad request\",\n\t\t\tArgs: \"--rule-id bar --workspace-id baz\",\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusBadRequest,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusBadRequest),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(`\n\t\t\t\t\t\t\t{\n    \t\t\t\t\t\t\t\"title\": \"invalid rule ID\",\n    \t\t\t\t\t\t\t\"status\": 400\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t`))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"400 - Bad Request\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--rule-id %s --workspace-id %s\", ruleID, ruleWorkspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusNoContent,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusNoContent),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Deleted workspace-level rule with id: %s\", ruleID),\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--rule-id %s --workspace-id %s --json\", ruleID, ruleWorkspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusNoContent,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusNoContent),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.JSON(`{\"id\": %q, \"deleted\": true}`, ruleID),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, sub2.CommandName, \"delete\"}, scenarios)\n}\n\nfunc TestRuleGet(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --workspace-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--rule-id %s\", ruleID),\n\t\t\tWantError: \"error reading workspace ID: no workspace ID found\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --rule-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--workspace-id %s\", ruleWorkspaceID),\n\t\t\tWantError: \"error parsing arguments: required flag --rule-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate bad request\",\n\t\t\tArgs: \"--rule-id baz --workspace-id bar\",\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusBadRequest,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusBadRequest),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(`\n\t\t\t\t\t\t\t{\n    \t\t\t\t\t\t\t\"title\": \"invalid Rule ID\",\n    \t\t\t\t\t\t\t\"status\": 400\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t`))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"400 - Bad Request\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--rule-id %s --workspace-id %s\", ruleID, ruleWorkspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader((testutil.GenJSON(rule)))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: ruleString,\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--rule-id %s --workspace-id %s --json\", ruleID, ruleWorkspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader((testutil.GenJSON(rule)))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(rule),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, sub2.CommandName, \"get\"}, scenarios)\n}\n\nfunc TestRuleList(t *testing.T) {\n\trulesObject := rules.Rules{\n\t\tData: []rules.Rule{\n\t\t\t{\n\t\t\t\tCreatedAt:   testutil.Date,\n\t\t\t\tDescription: ruleDescription,\n\t\t\t\tEnabled:     ruleEnabled,\n\t\t\t\tRuleID:      ruleID,\n\t\t\t\tActions: []rules.Action{\n\t\t\t\t\t{\n\t\t\t\t\t\tType: ruleAction,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tType: ruleType,\n\t\t\t\tScope: rules.Scope{\n\t\t\t\t\tType:      string(scope.ScopeTypeWorkspace),\n\t\t\t\t\tAppliesTo: []string{ruleWorkspaceID},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tCreatedAt:   testutil.Date,\n\t\t\t\tDescription: ruleDescription + \"2\",\n\t\t\t\tEnabled:     ruleEnabled,\n\t\t\t\tRuleID:      ruleID + \"2\",\n\t\t\t\tActions: []rules.Action{\n\t\t\t\t\t{\n\t\t\t\t\t\tType: ruleAction,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tType: ruleType,\n\t\t\t\tScope: rules.Scope{\n\t\t\t\t\tType:      string(scope.ScopeTypeWorkspace),\n\t\t\t\t\tAppliesTo: []string{ruleWorkspaceID},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tMeta: rules.MetaRules{},\n\t}\n\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName: \"validate internal server error\",\n\t\t\tArgs: \"--workspace-id baz\",\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusInternalServerError,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusInternalServerError),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"500 - Internal Server Error\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success (zero workspace-level Rules)\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s\", ruleWorkspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(rules.Rules{\n\t\t\t\t\t\t\tData: []rules.Rule{},\n\t\t\t\t\t\t\tMeta: rules.MetaRules{},\n\t\t\t\t\t\t}))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: zeroListRulesString,\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s\", ruleWorkspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(rulesObject))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: listRulesString,\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --json\", ruleWorkspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(rulesObject))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(rulesObject),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, sub2.CommandName, \"list\"}, scenarios)\n}\n\nfunc TestRuleUpdate(t *testing.T) {\n\truleObject := rules.Rule{\n\t\tCreatedAt:   testutil.Date,\n\t\tDescription: ruleDescription,\n\t\tRuleID:      ruleID,\n\t}\n\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --rule-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--path %s --workspace-id %s\", rulePath, ruleWorkspaceID),\n\t\t\tWantError: \"error parsing arguments: required flag --rule-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --path flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--rule-id %s --workspace-id %s\", ruleID, ruleWorkspaceID),\n\t\t\tWantError: \"error parsing arguments: required flag --path not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --workspace-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--path %s --rule-id %s\", rulePath, ruleID),\n\t\t\tWantError: \"error reading workspace ID: no workspace ID found\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--rule-id %s --path %s --workspace-id %s\", ruleID, rulePath, ruleWorkspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(ruleObject))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Updated workspace-level rule with id: %s\", ruleID),\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--rule-id %s --path %s --workspace-id %s --json\", ruleID, rulePath, ruleWorkspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(rule))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(rule),\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, sub2.CommandName, \"update\"}, scenarios)\n}\n\nvar listRulesString = strings.TrimSpace(`\nID       Action  Description        Enabled  Type     Scope      Updated At                     Created At\nsomeID   allow   Utility requests   true     request  workspace  0001-01-01 00:00:00 +0000 UTC  2021-06-15 23:00:00 +0000 UTC\nsomeID2  allow   Utility requests2  true     request  workspace  0001-01-01 00:00:00 +0000 UTC  2021-06-15 23:00:00 +0000 UTC\n`) + \"\\n\"\n\nvar zeroListRulesString = strings.TrimSpace(`\nID  Action  Description  Enabled  Type  Scope  Updated At  Created At\n`) + \"\\n\"\n\nvar ruleString = strings.TrimSpace(`\nID: someID\nAction: allow\nDescription: Utility requests\nEnabled: true\nType: request\nScope: workspace\nUpdated (UTC): 0001-01-01 00:00\nCreated (UTC): 2021-06-15 23:00\n`)\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/rule/testdata/test_complex_rule.json",
    "content": "{\n    \"type\": \"request\",\n    \"description\": \"complex_test\",\n    \"enabled\": true,\n    \"expires_at\": \"\",\n    \"group_operator\": \"all\",\n    \"conditions\": [\n        {\n            \"type\": \"single\",\n            \"field\": \"ip\",\n            \"operator\": \"equals\",\n            \"value\": \"1.2.3.4\"\n        },\n        {\n            \"type\": \"single\",\n            \"field\": \"country\",\n            \"operator\": \"equals\",\n            \"value\": \"AE\"\n        },\n        {\n            \"type\": \"group\",\n            \"group_operator\": \"all\",\n            \"conditions\": [\n                {\n                    \"type\": \"single\",\n                    \"field\": \"ip\",\n                    \"operator\": \"equals\",\n                    \"value\": \"2.4.5.6\"\n                },\n                {\n                    \"type\": \"single\",\n                    \"field\": \"country\",\n                    \"operator\": \"equals\",\n                    \"value\": \"AD\"\n                }\n            ]\n        },\n        {\n            \"type\": \"group\",\n            \"group_operator\": \"all\",\n            \"conditions\": [\n                {\n                    \"type\": \"single\",\n                    \"field\": \"domain\",\n                    \"operator\": \"equals\",\n                    \"value\": \"test.com\"\n                },\n                {\n                    \"type\": \"single\",\n                    \"field\": \"agent_name\",\n                    \"operator\": \"equals\",\n                    \"value\": \"test\"\n                }\n            ]\n        },\n        {\n            \"type\": \"group\",\n            \"group_operator\": \"all\",\n            \"conditions\": [\n                {\n                    \"type\": \"single\",\n                    \"field\": \"ip\",\n                    \"operator\": \"in_list\",\n                    \"value\": \"site.blacklist\"\n                },\n                {\n                    \"type\": \"multival\",\n                    \"field\": \"request_header\",\n                    \"operator\": \"exists\",\n                    \"group_operator\": \"all\",\n                    \"conditions\": [\n                        {\n                            \"type\": \"single\",\n                            \"field\": \"name\",\n                            \"operator\": \"equals\",\n                            \"value\": \"x-something\"\n                        },\n                        {\n                            \"type\": \"single\",\n                            \"field\": \"value_string\",\n                            \"operator\": \"equals\",\n                            \"value\": \"abc-123\"\n                        }\n                    ]\n                }\n            ]\n        }\n    ],\n    \"actions\": [\n        {\n            \"type\": \"allow\"\n        }\n    ],\n    \"request_logging\": \"sampled\"\n}"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/rule/testdata/test_rule.json",
    "content": "{\n    \"type\": \"request\",\n    \"enabled\": true,\n    \"description\": \"Utility requests\",\n    \"group_operator\": \"all\",\n    \"request_logging\": \"sampled\",\n    \"conditions\": [\n        {\n            \"type\": \"single\",\n            \"field\": \"path\",\n            \"operator\": \"equals\",\n            \"value\": \"/echo.json\"\n        }\n    ],\n    \"actions\": [\n        {\n            \"type\": \"allow\"\n        }\n    ]\n}"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/rule/update.go",
    "content": "package rule\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"path/filepath\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/rules\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/scope\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// UpdateCommand calls the Fastly API to update a workspace-level rule.\ntype UpdateCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tpath        string\n\truleID      string\n\tworkspaceID argparser.OptionalWorkspaceID\n}\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"update\", \"Update a workspace\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"rule-id\", \"Rule ID\").Required().StringVar(&c.ruleID)\n\tc.CmdClause.Flag(\"path\", \"Path to a json file that contains the rule schema.\").Required().StringVar(&c.path)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagNGWAFWorkspaceID,\n\t\tDescription: argparser.FlagNGWAFWorkspaceIDDesc,\n\t\tDst:         &c.workspaceID.Value,\n\t\tAction:      c.workspaceID.Set,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *UpdateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\tif err := c.workspaceID.Parse(); err != nil {\n\t\treturn err\n\t}\n\trule := &rules.Rule{}\n\tif c.path != \"\" {\n\t\tpath, err := filepath.Abs(c.path)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"error parsing path '%s': %q\", c.path, err)\n\t\t}\n\n\t\tjsonFile, err := os.Open(path)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"error reading path '%s': %q\", c.path, err)\n\t\t}\n\t\tdefer jsonFile.Close()\n\n\t\tbyteValue, err := io.ReadAll(jsonFile)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to read json file: %v\", err)\n\t\t}\n\n\t\tif err := json.Unmarshal(byteValue, rule); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to unmarshal json data: %v\", err)\n\t\t}\n\t}\n\n\tinput := &rules.UpdateInput{\n\t\tActions:            []*rules.UpdateAction{},\n\t\tConditions:         []*rules.UpdateCondition{},\n\t\tDescription:        &rule.Description,\n\t\tGroupConditions:    []*rules.UpdateGroupCondition{},\n\t\tMultivalConditions: []*rules.UpdateMultivalCondition{},\n\t\tEnabled:            &rule.Enabled,\n\t\tType:               &rule.Type,\n\t\tGroupOperator:      &rule.GroupOperator,\n\t\tRequestLogging:     &rule.RequestLogging,\n\t\tRuleID:             &c.ruleID,\n\t\tScope: &scope.Scope{\n\t\t\tType:      scope.ScopeTypeWorkspace,\n\t\t\tAppliesTo: []string{c.workspaceID.Value},\n\t\t},\n\t}\n\n\tfor _, action := range rule.Actions {\n\t\tinput.Actions = append(input.Actions, &rules.UpdateAction{\n\t\t\tAllowInteractive: action.AllowInteractive,\n\t\t\tDeceptionType:    &action.DeceptionType,\n\t\t\tRedirectURL:      &action.RedirectURL,\n\t\t\tResponseCode:     &action.ResponseCode,\n\t\t\tSignal:           &action.Signal,\n\t\t\tType:             &action.Type,\n\t\t})\n\t}\n\n\tif rule.RateLimit != nil {\n\t\tinput.RateLimit = &rules.UpdateRateLimit{\n\t\t\tClientIdentifiers: []*rules.UpdateClientIdentifier{},\n\t\t\tDuration:          &rule.RateLimit.Duration,\n\t\t\tInterval:          &rule.RateLimit.Interval,\n\t\t\tSignal:            &rule.RateLimit.Signal,\n\t\t\tThreshold:         &rule.RateLimit.Threshold,\n\t\t}\n\n\t\tfor _, rateLimit := range rule.RateLimit.ClientIdentifiers {\n\t\t\tinput.RateLimit.ClientIdentifiers = append(input.RateLimit.ClientIdentifiers, &rules.UpdateClientIdentifier{\n\t\t\t\tKey:  &rateLimit.Key,\n\t\t\t\tName: &rateLimit.Name,\n\t\t\t\tType: &rateLimit.Type,\n\t\t\t})\n\t\t}\n\t}\n\n\tfor _, jsonCondition := range rule.Conditions {\n\t\tswitch jsonCondition.Type {\n\t\tcase \"single\":\n\t\t\tif sc, ok := jsonCondition.Fields.(rules.SingleCondition); ok {\n\t\t\t\tinput.Conditions = append(input.Conditions, &rules.UpdateCondition{\n\t\t\t\t\tField:    &sc.Field,\n\t\t\t\t\tOperator: &sc.Operator,\n\t\t\t\t\tValue:    &sc.Value,\n\t\t\t\t})\n\t\t\t} else {\n\t\t\t\treturn fmt.Errorf(\"expected SingleCondition, got %T\", jsonCondition.Fields)\n\t\t\t}\n\t\tcase \"group\":\n\t\t\tif gc, ok := jsonCondition.Fields.(rules.GroupCondition); ok {\n\t\t\t\tparsedGroupCondition := &rules.UpdateGroupCondition{\n\t\t\t\t\tGroupOperator: &gc.GroupOperator,\n\t\t\t\t\tConditions:    []*rules.UpdateCondition{},\n\t\t\t\t}\n\t\t\t\tfor _, groupCondition := range gc.Conditions {\n\t\t\t\t\tswitch groupCondition.Type {\n\t\t\t\t\tcase \"single\":\n\t\t\t\t\t\tif gsc, ok := groupCondition.Fields.(rules.SingleCondition); ok {\n\t\t\t\t\t\t\tparsedGroupCondition.Conditions = append(parsedGroupCondition.Conditions, &rules.UpdateCondition{\n\t\t\t\t\t\t\t\tField:    &gsc.Field,\n\t\t\t\t\t\t\t\tOperator: &gsc.Operator,\n\t\t\t\t\t\t\t\tValue:    &gsc.Value,\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\treturn fmt.Errorf(\"expected Condition, got %T\", groupCondition.Fields)\n\t\t\t\t\t\t}\n\t\t\t\t\tcase \"multival\":\n\t\t\t\t\t\tif gmvc, ok := groupCondition.Fields.(rules.MultivalCondition); ok {\n\t\t\t\t\t\t\tupdateMultivalCondition := &rules.UpdateMultivalCondition{\n\t\t\t\t\t\t\t\tField:         &gmvc.Field,\n\t\t\t\t\t\t\t\tOperator:      &gmvc.Operator,\n\t\t\t\t\t\t\t\tGroupOperator: &gmvc.GroupOperator,\n\t\t\t\t\t\t\t\tConditions:    []*rules.UpdateConditionMult{},\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tfor _, groupMultivalSingleCondition := range gmvc.Conditions {\n\t\t\t\t\t\t\t\tupdateMultivalCondition.Conditions = append(updateMultivalCondition.Conditions, &rules.UpdateConditionMult{\n\t\t\t\t\t\t\t\t\tField:    &groupMultivalSingleCondition.Field,\n\t\t\t\t\t\t\t\t\tOperator: &groupMultivalSingleCondition.Operator,\n\t\t\t\t\t\t\t\t\tValue:    &groupMultivalSingleCondition.Value,\n\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tparsedGroupCondition.MultivalConditions = append(parsedGroupCondition.MultivalConditions, updateMultivalCondition)\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\treturn fmt.Errorf(\"expected MultivalCondition, got %T\", groupCondition.Fields)\n\t\t\t\t\t\t}\n\n\t\t\t\t\tdefault:\n\t\t\t\t\t\treturn fmt.Errorf(\"unknown condition type: %s\", groupCondition.Type)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tinput.GroupConditions = append(input.GroupConditions, parsedGroupCondition)\n\t\t\t} else {\n\t\t\t\treturn fmt.Errorf(\"expected GroupCondition, got %T\", jsonCondition.Fields)\n\t\t\t}\n\t\tcase \"multival\":\n\t\t\tif mvc, ok := jsonCondition.Fields.(rules.UpdateMultivalCondition); ok {\n\t\t\t\tparsedMultiValCondition := &rules.UpdateMultivalCondition{\n\t\t\t\t\tField:         mvc.Field,\n\t\t\t\t\tGroupOperator: mvc.GroupOperator,\n\t\t\t\t\tOperator:      mvc.Operator,\n\t\t\t\t\tConditions:    []*rules.UpdateConditionMult{},\n\t\t\t\t}\n\t\t\t\tfor _, multiSingleCondition := range mvc.Conditions {\n\t\t\t\t\tparsedMultiValCondition.Conditions = append(parsedMultiValCondition.Conditions, &rules.UpdateConditionMult{\n\t\t\t\t\t\tField:    multiSingleCondition.Field,\n\t\t\t\t\t\tOperator: multiSingleCondition.Operator,\n\t\t\t\t\t\tValue:    multiSingleCondition.Value,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t\tinput.MultivalConditions = append(input.MultivalConditions, parsedMultiValCondition)\n\t\t\t} else {\n\t\t\t\treturn fmt.Errorf(\"expected MultivalCondition, got %T\", jsonCondition.Fields)\n\t\t\t}\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"unknown condition type: %s\", jsonCondition.Type)\n\t\t}\n\t}\n\n\tfc, ok := c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tdata, err := rules.Update(context.TODO(), fc, input)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, data); ok {\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Updated workspace-level rule with id: %s\", data.RuleID)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/signallist/create.go",
    "content": "package signallist\n\nimport (\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/scope\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/ngwaf/ngwaflist\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// CreateCommand calls the Fastly API to create workspace-level signal lists.\ntype CreateCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tentries     string\n\tname        string\n\tworkspaceID argparser.OptionalWorkspaceID\n\n\t// Optional.\n\tdescription argparser.OptionalString\n}\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"create\", \"Create a workspace-level signal list\").Alias(\"add\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"entries\", \"Entries for the list. Can either be a comma separated list or a path to a file.\").Required().StringVar(&c.entries)\n\tc.CmdClause.Flag(\"name\", \"User submitted display name of a list.\").Required().StringVar(&c.name)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagNGWAFWorkspaceID,\n\t\tDescription: argparser.FlagNGWAFWorkspaceIDDesc,\n\t\tDst:         &c.workspaceID.Value,\n\t\tAction:      c.workspaceID.Set,\n\t})\n\n\t// Optional.\n\tc.CmdClause.Flag(\"description\", \"User submitted description of the list.\").Action(c.description.Set).StringVar(&c.description.Value)\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *CreateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\tinput := ngwaflist.ListCreateInput{\n\t\tCommandScope: scope.ScopeTypeWorkspace,\n\t\tDescription:  c.description,\n\t\tEntries:      c.entries,\n\t\tName:         c.name,\n\t\tType:         \"signal\",\n\t\tWorkspaceID:  &c.workspaceID,\n\t}\n\n\tvar ok bool\n\tinput.FC, ok = c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tdata, err := ngwaflist.ListCreate(input)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, data); ok {\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Created Workspace Signal List '%s' (list id: %s)\", data.Name, data.ListID)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/signallist/delete.go",
    "content": "package signallist\n\nimport (\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/scope\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/ngwaf/ngwaflist\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DeleteCommand calls the Fastly API to delete a workspace-level signal list.\ntype DeleteCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tlistID      string\n\tworkspaceID argparser.OptionalWorkspaceID\n}\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\n\tc.CmdClause = parent.Command(\"delete\", \"Delete a workspace signal list\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"list-id\", \"List ID\").Required().StringVar(&c.listID)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagNGWAFWorkspaceID,\n\t\tDescription: argparser.FlagNGWAFWorkspaceIDDesc,\n\t\tDst:         &c.workspaceID.Value,\n\t\tAction:      c.workspaceID.Set,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DeleteCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tinput := ngwaflist.ListDeleteInput{\n\t\tCommandScope: scope.ScopeTypeWorkspace,\n\t\tListID:       c.listID,\n\t\tWorkspaceID:  &c.workspaceID,\n\t}\n\n\tvar ok bool\n\tinput.FC, ok = c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\terr := ngwaflist.ListDelete(input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif c.JSONOutput.Enabled {\n\t\to := struct {\n\t\t\tID      string `json:\"id\"`\n\t\t\tDeleted bool   `json:\"deleted\"`\n\t\t}{\n\t\t\tc.listID,\n\t\t\ttrue,\n\t\t}\n\t\t_, err := c.WriteJSON(out, o)\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Deleted Workspace Signal List (list id: %s)\", c.listID)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/signallist/doc.go",
    "content": "// Package signallist contains commands to inspect and manipulate NGWAF workspace-level signal lists.\npackage signallist\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/signallist/get.go",
    "content": "package signallist\n\nimport (\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/ngwaf/ngwaflist\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/scope\"\n)\n\n// GetCommand calls the Fastly API to get a workspace-level signal list.\ntype GetCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tlistID      string\n\tworkspaceID argparser.OptionalWorkspaceID\n}\n\n// NewGetCommand returns a usable command registered under the parent.\nfunc NewGetCommand(parent argparser.Registerer, g *global.Data) *GetCommand {\n\tc := GetCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\n\tc.CmdClause = parent.Command(\"get\", \"Get a workspace-level signal list\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"list-id\", \"List ID\").Required().StringVar(&c.listID)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagNGWAFWorkspaceID,\n\t\tDescription: argparser.FlagNGWAFWorkspaceIDDesc,\n\t\tDst:         &c.workspaceID.Value,\n\t\tAction:      c.workspaceID.Set,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *GetCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tinput := ngwaflist.ListGetInput{\n\t\tCommandScope: scope.ScopeTypeWorkspace,\n\t\tListID:       c.listID,\n\t\tWorkspaceID:  &c.workspaceID,\n\t}\n\n\tvar ok bool\n\tinput.FC, ok = c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tlist, err := ngwaflist.ListGet(input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, list); ok {\n\t\treturn err\n\t}\n\n\ttext.PrintList(out, list)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/signallist/list.go",
    "content": "package signallist\n\nimport (\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/ngwaf/ngwaflist\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/scope\"\n)\n\n// ListCommand calls the Fastly API to list all signal lists for your workspace.\n\ntype ListCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tworkspaceID argparser.OptionalWorkspaceID\n}\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\n\tc.CmdClause = parent.Command(\"list\", \"List all signal lists for your workspace\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagNGWAFWorkspaceID,\n\t\tDescription: argparser.FlagNGWAFWorkspaceIDDesc,\n\t\tDst:         &c.workspaceID.Value,\n\t\tAction:      c.workspaceID.Set,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *ListCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tinput := ngwaflist.ListListInput{\n\t\tCommandScope: scope.ScopeTypeWorkspace,\n\t\tType:         \"signal\",\n\t\tWorkspaceID:  &c.workspaceID,\n\t}\n\n\tvar ok bool\n\tinput.FC, ok = c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tlists, err := ngwaflist.ListList(input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, *lists); ok {\n\t\treturn err\n\t}\n\n\ttext.PrintListTbl(out, lists.Data)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/signallist/root.go",
    "content": "package signallist\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"signal-list\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manage NGWAF Workspace Signal Lists\")\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/signallist/signallist_test.go",
    "content": "package signallist_test\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\t\"testing\"\n\n\troot \"github.com/fastly/cli/pkg/commands/ngwaf\"\n\tsub2 \"github.com/fastly/cli/pkg/commands/ngwaf/signallist\"\n\tsub \"github.com/fastly/cli/pkg/commands/ngwaf/workspace\"\n\tfstfmt \"github.com/fastly/cli/pkg/fmt\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/lists\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/scope\"\n)\n\nconst (\n\tlistID          = \"someListID\"\n\tlistDescription = \"NGWAFCLIList\"\n\tlistEntries     = \"BHH\"\n\tlistType        = \"signal\"\n\tlistName        = \"listName\"\n\tworkspaceID     = \"someWorkspaceID\"\n)\n\nvar stringlist = lists.List{\n\tListID:      listID,\n\tDescription: listDescription,\n\tEntries:     []string{listEntries},\n\tName:        listName,\n\tType:        listType,\n\tCreatedAt:   testutil.Date,\n\tUpdatedAt:   testutil.Date,\n\tScope: lists.Scope{\n\t\tType: string(scope.ScopeTypeWorkspace),\n\t},\n}\n\nvar stringlist2 = lists.List{\n\tListID:      listID + \"2\",\n\tDescription: listDescription + \"2\",\n\tEntries:     []string{listEntries},\n\tName:        listName + \"2\",\n\tType:        listType,\n\tCreatedAt:   testutil.Date,\n\tUpdatedAt:   testutil.Date,\n\tScope: lists.Scope{\n\t\tType: string(scope.ScopeTypeWorkspace),\n\t},\n}\n\nfunc TestSignalListCreate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --entries flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--name %s --workspace-id %s\", listName, workspaceID),\n\t\t\tWantError: \"error parsing arguments: required flag --entries not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --name flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--entries %s --workspace-id %s\", listEntries, workspaceID),\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate missing --workspace-id flag\",\n\t\t\tArgs: fmt.Sprintf(\"--entries %s --name %s\", listEntries, listName),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusNoContent,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusNoContent),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"error reading workspace ID: no workspace ID found\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate internal server error\",\n\t\t\tArgs: fmt.Sprintf(\"--entries %s --name %s --workspace-id %s\", listEntries, listName, workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusInternalServerError,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusInternalServerError),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"500 - Internal Server Error\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--entries %s --name %s --workspace-id %s\", listEntries, listName, workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader((testutil.GenJSON(stringlist)))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Created Workspace Signal List '%s' (list id: %s)\", listName, listID),\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--entries %s --name %s --workspace-id %s --json\", listEntries, listName, workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(stringlist))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(stringlist),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, sub2.CommandName, \"create\"}, scenarios)\n}\n\nfunc TestSignalListDelete(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --list-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--workspace-id %s\", workspaceID),\n\t\t\tWantError: \"error parsing arguments: required flag --list-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate missing --workspace-id flag\",\n\t\t\tArgs: fmt.Sprintf(\"--list-id %s\", listID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusNoContent,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusNoContent),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"error reading workspace ID: no workspace ID found\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate bad request\",\n\t\t\tArgs: \"--list-id bar --workspace-id baz\",\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusBadRequest,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusBadRequest),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(`\n\t\t\t\t\t\t\t{\n    \t\t\t\t\t\t\t\"title\": \"invalid List ID\",\n    \t\t\t\t\t\t\t\"status\": 400\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t`))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"400 - Bad Request\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--list-id %s --workspace-id %s\", listID, workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusNoContent,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusNoContent),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Deleted Workspace Signal List (list id: %s)\", listID),\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--list-id %s --workspace-id %s --json\", listID, workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusNoContent,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusNoContent),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.JSON(`{\"id\": %q, \"deleted\": true}`, listID),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, sub2.CommandName, \"delete\"}, scenarios)\n}\n\nfunc TestSignalListGet(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --list-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--workspace-id %s\", workspaceID),\n\t\t\tWantError: \"error parsing arguments: required flag --list-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate missing --workspace-id flag\",\n\t\t\tArgs: fmt.Sprintf(\"--list-id %s\", listID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusNoContent,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusNoContent),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"error reading workspace ID: no workspace ID found\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate bad request\",\n\t\t\tArgs: \"--list-id baz --workspace-id baz\",\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusBadRequest,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusBadRequest),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(`\n\t\t\t\t\t\t\t{\n    \t\t\t\t\t\t\t\"title\": \"invalid List ID\",\n    \t\t\t\t\t\t\t\"status\": 400\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t`))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"400 - Bad Request\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--list-id %s --workspace-id %s\", listID, workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader((testutil.GenJSON(stringlist)))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: listString,\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--list-id %s --workspace-id %s --json\", listID, workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader((testutil.GenJSON(stringlist)))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(stringlist),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, sub2.CommandName, \"get\"}, scenarios)\n}\n\nfunc TestSignalListList(t *testing.T) {\n\tlistsObject := lists.Lists{\n\t\tData: []lists.List{\n\t\t\tstringlist,\n\t\t\tstringlist2,\n\t\t},\n\t\tMeta: lists.MetaLists{},\n\t}\n\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName: \"validate missing --workspace-id flag\",\n\t\t\tArgs: \"\",\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusNoContent,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusNoContent),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"error reading workspace ID: no workspace ID found\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate internal server error\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s\", workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusInternalServerError,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusInternalServerError),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"500 - Internal Server Error\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success (zero workspaces)\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s\", workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(lists.Lists{\n\t\t\t\t\t\t\tData: []lists.List{},\n\t\t\t\t\t\t\tMeta: lists.MetaLists{},\n\t\t\t\t\t\t}))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: zeroListString,\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s\", workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(listsObject))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: listListsString,\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --json\", workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(listsObject))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(listsObject),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, sub2.CommandName, \"list\"}, scenarios)\n}\n\nfunc TestSignalListUpdate(t *testing.T) {\n\tupdatelist := lists.List{\n\t\tListID:      listID,\n\t\tDescription: listDescription + \"2\",\n\t\tEntries:     []string{listEntries + \"2\"},\n\t\tName:        listName,\n\t\tType:        listType,\n\t\tCreatedAt:   testutil.Date,\n\t\tUpdatedAt:   testutil.Date,\n\t\tScope: lists.Scope{\n\t\t\tType: string(scope.ScopeTypeWorkspace),\n\t\t},\n\t}\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --list-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--workspace-id %s\", workspaceID),\n\t\t\tWantError: \"error parsing arguments: required flag --list-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate missing --workspace-id flag\",\n\t\t\tArgs: fmt.Sprintf(\"--list-id %s\", listID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusNoContent,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusNoContent),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"error reading workspace ID: no workspace ID found\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--list-id %s --description %s --entries %s --workspace-id %s\", listID, listDescription+\"2\", listEntries+\"2\", workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(updatelist))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Updated Workspace Signal List '%s' (list id: %s)\", listName, listID),\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--list-id %s --description %s --entries %s --workspace-id %s --json\", listID, listDescription+\"2\", listEntries+\"2\", workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(updatelist))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(updatelist),\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, sub2.CommandName, \"update\"}, scenarios)\n}\n\nvar listListsString = strings.TrimSpace(`\nID           Name       Description    Type    Scope      Entries  Updated At                     Created At\nsomeListID   listName   NGWAFCLIList   signal  workspace  BHH      2021-06-15 23:00:00 +0000 UTC  2021-06-15 23:00:00 +0000 UTC\nsomeListID2  listName2  NGWAFCLIList2  signal  workspace  BHH      2021-06-15 23:00:00 +0000 UTC  2021-06-15 23:00:00 +0000 UTC\n`) + \"\\n\"\n\nvar zeroListString = strings.TrimSpace(`\nID  Name  Description  Type  Scope  Entries  Updated At  Created At\n`) + \"\\n\"\n\nvar listString = strings.TrimSpace(`\nID: someListID\nName: listName\nDescription: NGWAFCLIList\nType: signal\nEntries: BHH\nScope: workspace\nUpdated (UTC): 2021-06-15 23:00\n`)\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/signallist/update.go",
    "content": "package signallist\n\nimport (\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/scope\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/ngwaf/ngwaflist\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// UpdateCommand calls the Fastly API to update a workspace signal list.\ntype UpdateCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tlistID      string\n\tworkspaceID argparser.OptionalWorkspaceID\n\n\t// Optional.\n\tdescription argparser.OptionalString\n\tentries     argparser.OptionalString\n}\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"update\", \"Update a workspace-level signal list\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"list-id\", \"List ID\").Required().StringVar(&c.listID)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagNGWAFWorkspaceID,\n\t\tDescription: argparser.FlagNGWAFWorkspaceIDDesc,\n\t\tDst:         &c.workspaceID.Value,\n\t\tAction:      c.workspaceID.Set,\n\t})\n\n\t// Optional.\n\tc.CmdClause.Flag(\"description\", \"User submitted description of the list.\").Action(c.description.Set).StringVar(&c.description.Value)\n\tc.CmdClause.Flag(\"entries\", \"Entries for the list. Can either be a comma separated list or a path to a file.\").Action(c.entries.Set).StringVar(&c.entries.Value)\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *UpdateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\tinput := ngwaflist.ListUpdateInput{\n\t\tCommandScope: scope.ScopeTypeWorkspace,\n\t\tDescription:  c.description,\n\t\tEntries:      c.entries,\n\t\tListID:       c.listID,\n\t\tWorkspaceID:  &c.workspaceID,\n\t}\n\n\tvar ok bool\n\tinput.FC, ok = c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tdata, err := ngwaflist.ListUpdate(input)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, data); ok {\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Updated Workspace Signal List '%s' (list id: %s)\", data.Name, data.ListID)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/stringlist/create.go",
    "content": "package stringlist\n\nimport (\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/scope\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/ngwaf/ngwaflist\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// CreateCommand calls the Fastly API to create workspace-level string lists.\ntype CreateCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tentries     string\n\tname        string\n\tworkspaceID argparser.OptionalWorkspaceID\n\n\t// Optional.\n\tdescription argparser.OptionalString\n}\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"create\", \"Create a workspace-level string list\").Alias(\"add\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"entries\", \"Entries for the list. Can either be a comma separated list or a path to a file.\").Required().StringVar(&c.entries)\n\tc.CmdClause.Flag(\"name\", \"User submitted display name of a list.\").Required().StringVar(&c.name)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagNGWAFWorkspaceID,\n\t\tDescription: argparser.FlagNGWAFWorkspaceIDDesc,\n\t\tDst:         &c.workspaceID.Value,\n\t\tAction:      c.workspaceID.Set,\n\t})\n\n\t// Optional.\n\tc.CmdClause.Flag(\"description\", \"User submitted description of the list.\").Action(c.description.Set).StringVar(&c.description.Value)\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *CreateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\tinput := ngwaflist.ListCreateInput{\n\t\tCommandScope: scope.ScopeTypeWorkspace,\n\t\tDescription:  c.description,\n\t\tEntries:      c.entries,\n\t\tName:         c.name,\n\t\tType:         \"string\",\n\t\tWorkspaceID:  &c.workspaceID,\n\t}\n\n\tvar ok bool\n\tinput.FC, ok = c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tdata, err := ngwaflist.ListCreate(input)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, data); ok {\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Created Workspace String List '%s' (list id: %s)\", data.Name, data.ListID)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/stringlist/delete.go",
    "content": "package stringlist\n\nimport (\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/scope\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/ngwaf/ngwaflist\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DeleteCommand calls the Fastly API to delete a workspace-level string list.\ntype DeleteCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tlistID      string\n\tworkspaceID argparser.OptionalWorkspaceID\n}\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\n\tc.CmdClause = parent.Command(\"delete\", \"Delete a workspace string list\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"list-id\", \"List ID\").Required().StringVar(&c.listID)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagNGWAFWorkspaceID,\n\t\tDescription: argparser.FlagNGWAFWorkspaceIDDesc,\n\t\tDst:         &c.workspaceID.Value,\n\t\tAction:      c.workspaceID.Set,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DeleteCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tinput := ngwaflist.ListDeleteInput{\n\t\tCommandScope: scope.ScopeTypeWorkspace,\n\t\tListID:       c.listID,\n\t\tWorkspaceID:  &c.workspaceID,\n\t}\n\n\tvar ok bool\n\tinput.FC, ok = c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\terr := ngwaflist.ListDelete(input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif c.JSONOutput.Enabled {\n\t\to := struct {\n\t\t\tID      string `json:\"id\"`\n\t\t\tDeleted bool   `json:\"deleted\"`\n\t\t}{\n\t\t\tc.listID,\n\t\t\ttrue,\n\t\t}\n\t\t_, err := c.WriteJSON(out, o)\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Deleted Workspace String List (list id: %s)\", c.listID)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/stringlist/doc.go",
    "content": "// Package stringlist contains commands to inspect and manipulate NGWAF workspace-level string lists.\npackage stringlist\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/stringlist/get.go",
    "content": "package stringlist\n\nimport (\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/ngwaf/ngwaflist\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/scope\"\n)\n\n// GetCommand calls the Fastly API to get a workspace-level string list.\ntype GetCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tlistID      string\n\tworkspaceID argparser.OptionalWorkspaceID\n}\n\n// NewGetCommand returns a usable command registered under the parent.\nfunc NewGetCommand(parent argparser.Registerer, g *global.Data) *GetCommand {\n\tc := GetCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\n\tc.CmdClause = parent.Command(\"get\", \"Get a workspace-level string list\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"list-id\", \"List ID\").Required().StringVar(&c.listID)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagNGWAFWorkspaceID,\n\t\tDescription: argparser.FlagNGWAFWorkspaceIDDesc,\n\t\tDst:         &c.workspaceID.Value,\n\t\tAction:      c.workspaceID.Set,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *GetCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tinput := ngwaflist.ListGetInput{\n\t\tCommandScope: scope.ScopeTypeWorkspace,\n\t\tListID:       c.listID,\n\t\tWorkspaceID:  &c.workspaceID,\n\t}\n\n\tvar ok bool\n\tinput.FC, ok = c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tlist, err := ngwaflist.ListGet(input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, list); ok {\n\t\treturn err\n\t}\n\n\ttext.PrintList(out, list)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/stringlist/list.go",
    "content": "package stringlist\n\nimport (\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/ngwaf/ngwaflist\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/scope\"\n)\n\n// ListCommand calls the Fastly API to list all string lists for your workspace.\n\ntype ListCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tworkspaceID argparser.OptionalWorkspaceID\n}\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\n\tc.CmdClause = parent.Command(\"list\", \"List all string lists for your workspace\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagNGWAFWorkspaceID,\n\t\tDescription: argparser.FlagNGWAFWorkspaceIDDesc,\n\t\tDst:         &c.workspaceID.Value,\n\t\tAction:      c.workspaceID.Set,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *ListCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tinput := ngwaflist.ListListInput{\n\t\tCommandScope: scope.ScopeTypeWorkspace,\n\t\tType:         \"string\",\n\t\tWorkspaceID:  &c.workspaceID,\n\t}\n\n\tvar ok bool\n\tinput.FC, ok = c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tlists, err := ngwaflist.ListList(input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, *lists); ok {\n\t\treturn err\n\t}\n\n\ttext.PrintListTbl(out, lists.Data)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/stringlist/root.go",
    "content": "package stringlist\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"string-list\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manage NGWAF Workspace String Lists\")\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/stringlist/stringlist_test.go",
    "content": "package stringlist_test\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\t\"testing\"\n\n\troot \"github.com/fastly/cli/pkg/commands/ngwaf\"\n\tsub2 \"github.com/fastly/cli/pkg/commands/ngwaf/stringlist\"\n\tsub \"github.com/fastly/cli/pkg/commands/ngwaf/workspace\"\n\tfstfmt \"github.com/fastly/cli/pkg/fmt\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/lists\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/scope\"\n)\n\nconst (\n\tlistID          = \"someListID\"\n\tlistDescription = \"NGWAFCLIList\"\n\tlistEntries     = \"1.0.0.0\"\n\tlistType        = \"string\"\n\tlistName        = \"listName\"\n\tworkspaceID     = \"someWorkspaceID\"\n)\n\nvar stringlist = lists.List{\n\tListID:      listID,\n\tDescription: listDescription,\n\tEntries:     []string{listEntries},\n\tName:        listName,\n\tType:        listType,\n\tCreatedAt:   testutil.Date,\n\tUpdatedAt:   testutil.Date,\n\tScope: lists.Scope{\n\t\tType: string(scope.ScopeTypeWorkspace),\n\t},\n}\n\nvar stringlist2 = lists.List{\n\tListID:      listID + \"2\",\n\tDescription: listDescription + \"2\",\n\tEntries:     []string{listEntries},\n\tName:        listName + \"2\",\n\tType:        listType,\n\tCreatedAt:   testutil.Date,\n\tUpdatedAt:   testutil.Date,\n\tScope: lists.Scope{\n\t\tType: string(scope.ScopeTypeWorkspace),\n\t},\n}\n\nfunc TestStringListCreate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --entries flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--name %s --workspace-id %s\", listName, workspaceID),\n\t\t\tWantError: \"error parsing arguments: required flag --entries not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --name flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--entries %s --workspace-id %s\", listEntries, workspaceID),\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate missing --workspace-id flag\",\n\t\t\tArgs: fmt.Sprintf(\"--entries %s --name %s\", listEntries, listName),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusNoContent,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusNoContent),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"error reading workspace ID: no workspace ID found\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate internal server error\",\n\t\t\tArgs: fmt.Sprintf(\"--entries %s --name %s --workspace-id %s\", listEntries, listName, workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusInternalServerError,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusInternalServerError),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"500 - Internal Server Error\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--entries %s --name %s --workspace-id %s\", listEntries, listName, workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader((testutil.GenJSON(stringlist)))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Created Workspace String List '%s' (list id: %s)\", listName, listID),\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--entries %s --name %s --workspace-id %s --json\", listEntries, listName, workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(stringlist))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(stringlist),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, sub2.CommandName, \"create\"}, scenarios)\n}\n\nfunc TestStringListDelete(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --list-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--workspace-id %s\", workspaceID),\n\t\t\tWantError: \"error parsing arguments: required flag --list-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate missing --workspace-id flag\",\n\t\t\tArgs: fmt.Sprintf(\"--list-id %s\", listID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusNoContent,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusNoContent),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"error reading workspace ID: no workspace ID found\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate bad request\",\n\t\t\tArgs: \"--list-id bar --workspace-id baz\",\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusBadRequest,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusBadRequest),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(`\n\t\t\t\t\t\t\t{\n    \t\t\t\t\t\t\t\"title\": \"invalid List ID\",\n    \t\t\t\t\t\t\t\"status\": 400\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t`))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"400 - Bad Request\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--list-id %s --workspace-id %s\", listID, workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusNoContent,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusNoContent),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Deleted Workspace String List (list id: %s)\", listID),\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--list-id %s --workspace-id %s --json\", listID, workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusNoContent,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusNoContent),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.JSON(`{\"id\": %q, \"deleted\": true}`, listID),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, sub2.CommandName, \"delete\"}, scenarios)\n}\n\nfunc TestStringListGet(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --list-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--workspace-id %s\", workspaceID),\n\t\t\tWantError: \"error parsing arguments: required flag --list-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate missing --workspace-id flag\",\n\t\t\tArgs: fmt.Sprintf(\"--list-id %s\", listID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusNoContent,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusNoContent),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"error reading workspace ID: no workspace ID found\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate bad request\",\n\t\t\tArgs: \"--list-id baz --workspace-id baz\",\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusBadRequest,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusBadRequest),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(`\n\t\t\t\t\t\t\t{\n    \t\t\t\t\t\t\t\"title\": \"invalid List ID\",\n    \t\t\t\t\t\t\t\"status\": 400\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t`))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"400 - Bad Request\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--list-id %s --workspace-id %s\", listID, workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader((testutil.GenJSON(stringlist)))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: listString,\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--list-id %s --workspace-id %s --json\", listID, workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader((testutil.GenJSON(stringlist)))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(stringlist),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, sub2.CommandName, \"get\"}, scenarios)\n}\n\nfunc TestStringListList(t *testing.T) {\n\tlistsObject := lists.Lists{\n\t\tData: []lists.List{\n\t\t\tstringlist,\n\t\t\tstringlist2,\n\t\t},\n\t\tMeta: lists.MetaLists{},\n\t}\n\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName: \"validate missing --workspace-id flag\",\n\t\t\tArgs: \"\",\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusNoContent,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusNoContent),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"error reading workspace ID: no workspace ID found\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate internal server error\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s\", workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusInternalServerError,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusInternalServerError),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"500 - Internal Server Error\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success (zero workspaces)\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s\", workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(lists.Lists{\n\t\t\t\t\t\t\tData: []lists.List{},\n\t\t\t\t\t\t\tMeta: lists.MetaLists{},\n\t\t\t\t\t\t}))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: zeroListString,\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s\", workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(listsObject))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: listListsString,\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --json\", workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(listsObject))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(listsObject),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, sub2.CommandName, \"list\"}, scenarios)\n}\n\nfunc TestStringListUpdate(t *testing.T) {\n\tupdatelist := lists.List{\n\t\tListID:      listID,\n\t\tDescription: listDescription + \"2\",\n\t\tEntries:     []string{listEntries + \"2\"},\n\t\tName:        listName,\n\t\tType:        listType,\n\t\tCreatedAt:   testutil.Date,\n\t\tUpdatedAt:   testutil.Date,\n\t\tScope: lists.Scope{\n\t\t\tType: string(scope.ScopeTypeWorkspace),\n\t\t},\n\t}\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --list-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--workspace-id %s\", workspaceID),\n\t\t\tWantError: \"error parsing arguments: required flag --list-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate missing --workspace-id flag\",\n\t\t\tArgs: fmt.Sprintf(\"--list-id %s\", listID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusNoContent,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusNoContent),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"error reading workspace ID: no workspace ID found\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--list-id %s --description %s --entries %s --workspace-id %s\", listID, listDescription+\"2\", listEntries+\"2\", workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(updatelist))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Updated Workspace String List '%s' (list id: %s)\", listName, listID),\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--list-id %s --description %s --entries %s --workspace-id %s --json\", listID, listDescription+\"2\", listEntries+\"2\", workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(updatelist))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(updatelist),\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, sub2.CommandName, \"update\"}, scenarios)\n}\n\nvar listListsString = strings.TrimSpace(`\nID           Name       Description    Type    Scope      Entries  Updated At                     Created At\nsomeListID   listName   NGWAFCLIList   string  workspace  1.0.0.0  2021-06-15 23:00:00 +0000 UTC  2021-06-15 23:00:00 +0000 UTC\nsomeListID2  listName2  NGWAFCLIList2  string  workspace  1.0.0.0  2021-06-15 23:00:00 +0000 UTC  2021-06-15 23:00:00 +0000 UTC\n`) + \"\\n\"\n\nvar zeroListString = strings.TrimSpace(`\nID  Name  Description  Type  Scope  Entries  Updated At  Created At\n`) + \"\\n\"\n\nvar listString = strings.TrimSpace(`\nID: someListID\nName: listName\nDescription: NGWAFCLIList\nType: string\nEntries: 1.0.0.0\nScope: workspace\nUpdated (UTC): 2021-06-15 23:00\n`)\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/stringlist/update.go",
    "content": "package stringlist\n\nimport (\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/scope\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/ngwaf/ngwaflist\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// UpdateCommand calls the Fastly API to update a workspace string list.\ntype UpdateCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tlistID      string\n\tworkspaceID argparser.OptionalWorkspaceID\n\n\t// Optional.\n\tdescription argparser.OptionalString\n\tentries     argparser.OptionalString\n}\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"update\", \"Update a workspace-level string list\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"list-id\", \"List ID\").Required().StringVar(&c.listID)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagNGWAFWorkspaceID,\n\t\tDescription: argparser.FlagNGWAFWorkspaceIDDesc,\n\t\tDst:         &c.workspaceID.Value,\n\t\tAction:      c.workspaceID.Set,\n\t})\n\n\t// Optional.\n\tc.CmdClause.Flag(\"description\", \"User submitted description of the list.\").Action(c.description.Set).StringVar(&c.description.Value)\n\tc.CmdClause.Flag(\"entries\", \"Entries for the list. Can either be a comma separated list or a path to a file.\").Action(c.entries.Set).StringVar(&c.entries.Value)\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *UpdateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\tinput := ngwaflist.ListUpdateInput{\n\t\tCommandScope: scope.ScopeTypeWorkspace,\n\t\tDescription:  c.description,\n\t\tEntries:      c.entries,\n\t\tListID:       c.listID,\n\t\tWorkspaceID:  &c.workspaceID,\n\t}\n\n\tvar ok bool\n\tinput.FC, ok = c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tdata, err := ngwaflist.ListUpdate(input)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, data); ok {\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Updated Workspace String List '%s' (list id: %s)\", data.Name, data.ListID)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/threshold/create.go",
    "content": "package threshold\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/workspaces/thresholds\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// CreateCommand calls the Fastly API to create a workspace threshold.\ntype CreateCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\taction      string\n\tdontNotify  argparser.OptionalString\n\tduration    int\n\tenabled     argparser.OptionalString\n\tinterval    int\n\tlimit       int\n\tname        string\n\tsignal      string\n\tworkspaceID argparser.OptionalWorkspaceID\n\n\t// Optional.\n}\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"create\", \"Create a workspace threshold\").Alias(\"add\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"action\", \"The action to take when the threshold is exceeded. [block, log]\").Required().StringVar(&c.action)\n\tc.CmdClause.Flag(\"do-not-notify\", \"Whether to silence notifications when action is taken. [true, false]\").Required().Action(c.dontNotify.Set).StringVar(&c.dontNotify.Value)\n\tc.CmdClause.Flag(\"duration\", \"The duration the action is in place in seconds. Default duration is 86,400 seconds (1 day).\").Required().IntVar(&c.duration)\n\tc.CmdClause.Flag(\"enabled\", \"Whether the threshold is active. [true, false]\").Required().Action(c.enabled.Set).StringVar(&c.enabled.Value)\n\tc.CmdClause.Flag(\"interval\", \"The threshold interval in seconds. The default interval is 3600 seconds (1 hour).\").Required().IntVar(&c.interval)\n\tc.CmdClause.Flag(\"limit\", \"The threshold limit. Input must be between 1 and 10000. Default limit is 10.\").Required().IntVar(&c.limit)\n\tc.CmdClause.Flag(\"name\", \"User submitted display name of a signal threshold. Input must be between 3 and 50 characters\").Required().StringVar(&c.name)\n\tc.CmdClause.Flag(\"signal\", \"The name of the signal this threshold is acting on\").Required().StringVar(&c.signal)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagNGWAFWorkspaceID,\n\t\tDescription: argparser.FlagNGWAFWorkspaceIDDesc,\n\t\tDst:         &c.workspaceID.Value,\n\t\tAction:      c.workspaceID.Set,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *CreateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\t// Call Parse() to ensure that we check if workspaceID\n\t// is set or to throw the appropriate error.\n\tif err := c.workspaceID.Parse(); err != nil {\n\t\treturn err\n\t}\n\n\tenabled, err := argparser.ConvertBoolFromStringFlag(c.enabled.Value, \"enabled\")\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tdontNotify, err := argparser.ConvertBoolFromStringFlag(c.dontNotify.Value, \"do-not-notify\")\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tinput := &thresholds.CreateInput{\n\t\tAction:      &c.action,\n\t\tDuration:    &c.duration,\n\t\tEnabled:     enabled,\n\t\tInterval:    &c.interval,\n\t\tLimit:       &c.limit,\n\t\tName:        &c.name,\n\t\tDontNotify:  dontNotify,\n\t\tSignal:      &c.signal,\n\t\tWorkspaceID: &c.workspaceID.Value,\n\t}\n\n\tfc, ok := c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tdata, err := thresholds.Create(context.TODO(), fc, input)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, data); ok {\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Created threshold '%s' for workspace '%s'\", data.ThresholdID, c.workspaceID.Value)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/threshold/delete.go",
    "content": "package threshold\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/workspaces/thresholds\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DeleteCommand calls the Fastly API to delete a workspace threshold.\ntype DeleteCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tthresholdID string\n\tworkspaceID argparser.OptionalWorkspaceID\n\n\t// Optional.\n}\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"delete\", \"Deletes a workspace threshold\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagNGWAFWorkspaceID,\n\t\tDescription: argparser.FlagNGWAFWorkspaceIDDesc,\n\t\tDst:         &c.workspaceID.Value,\n\t\tAction:      c.workspaceID.Set,\n\t})\n\tc.CmdClause.Flag(\"threshold-id\", \"Threshold ID\").Required().StringVar(&c.thresholdID)\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DeleteCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\t// Call Parse() to ensure that we check if workspaceID\n\t// is set or to throw the appropriate error.\n\tif err := c.workspaceID.Parse(); err != nil {\n\t\treturn err\n\t}\n\n\tfc, ok := c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\terr := thresholds.Delete(context.TODO(), fc, &thresholds.DeleteInput{\n\t\tThresholdID: &c.thresholdID,\n\t\tWorkspaceID: &c.workspaceID.Value,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif c.JSONOutput.Enabled {\n\t\to := struct {\n\t\t\tID      string `json:\"id\"`\n\t\t\tDeleted bool   `json:\"deleted\"`\n\t\t}{\n\t\t\tc.thresholdID,\n\t\t\ttrue,\n\t\t}\n\t\t_, err := c.WriteJSON(out, o)\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Deleted threshold (id: %s)\", c.thresholdID)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/threshold/doc.go",
    "content": "// Package threshold contains commands to inspect and manipulate NGWAF workspace thresholds.\npackage threshold\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/threshold/get.go",
    "content": "package threshold\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/workspaces/thresholds\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// GetCommand calls the Fastly API to get a workspace threshold.\ntype GetCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tthresholdID string\n\tworkspaceID argparser.OptionalWorkspaceID\n\n\t// Optional.\n}\n\n// NewGetCommand returns a usable command registered under the parent.\nfunc NewGetCommand(parent argparser.Registerer, g *global.Data) *GetCommand {\n\tc := GetCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"get\", \"Retrieves a workspace threshold\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagNGWAFWorkspaceID,\n\t\tDescription: argparser.FlagNGWAFWorkspaceIDDesc,\n\t\tDst:         &c.workspaceID.Value,\n\t\tAction:      c.workspaceID.Set,\n\t})\n\tc.CmdClause.Flag(\"threshold-id\", \"Threshold ID\").Required().StringVar(&c.thresholdID)\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *GetCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\t// Call Parse() to ensure that we check if workspaceID\n\t// is set or to throw the appropriate error.\n\tif err := c.workspaceID.Parse(); err != nil {\n\t\treturn err\n\t}\n\tinput := &thresholds.GetInput{\n\t\tThresholdID: &c.thresholdID,\n\t\tWorkspaceID: &c.workspaceID.Value,\n\t}\n\n\tfc, ok := c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tdata, err := thresholds.Get(context.TODO(), fc, input)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, data); ok {\n\t\treturn err\n\t}\n\n\ttext.PrintThreshold(out, data)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/threshold/list.go",
    "content": "package threshold\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/workspaces/thresholds\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// ListCommand calls the Fastly API to list workspace thresholds.\ntype ListCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tworkspaceID argparser.OptionalWorkspaceID\n\n\t// Optional.\n}\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"list\", \"List workspace thresholds\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagNGWAFWorkspaceID,\n\t\tDescription: argparser.FlagNGWAFWorkspaceIDDesc,\n\t\tDst:         &c.workspaceID.Value,\n\t\tAction:      c.workspaceID.Set,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *ListCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\t// Call Parse() to ensure that we check if workspaceID\n\t// is set or to throw the appropriate error.\n\tif err := c.workspaceID.Parse(); err != nil {\n\t\treturn err\n\t}\n\tinput := &thresholds.ListInput{\n\t\tWorkspaceID: &c.workspaceID.Value,\n\t}\n\n\tfc, ok := c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tdata, err := thresholds.List(context.TODO(), fc, input)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, data); ok {\n\t\treturn err\n\t}\n\n\ttext.PrintThresholdTbl(out, data.Data)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/threshold/root.go",
    "content": "package threshold\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"threshold\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manage NGWAF Workspace Thresholds\")\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/threshold/threshold_test.go",
    "content": "package threshold_test\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\t\"testing\"\n\n\troot \"github.com/fastly/cli/pkg/commands/ngwaf\"\n\tworkspace \"github.com/fastly/cli/pkg/commands/ngwaf/workspace\"\n\tsub \"github.com/fastly/cli/pkg/commands/ngwaf/workspace/threshold\"\n\tfstfmt \"github.com/fastly/cli/pkg/fmt\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/workspaces/thresholds\"\n)\n\nconst (\n\tthresholdAction     = \"block\"\n\tthresholdDuration   = 86400\n\tthresholdEnabled    = true\n\tthresholdID         = \"thresholdID\"\n\tthresholdInterval   = 3600\n\tthresholdLimit      = 10\n\tthresholdName       = \"Test_Threshold\"\n\tthresholdSignal     = \"test-signal\"\n\tthresholdDontNotify = false\n\tworkspaceID         = \"workspaceID\"\n)\n\nvar threshold = thresholds.Threshold{\n\tAction:      thresholdAction,\n\tCreatedAt:   testutil.Date,\n\tDontNotify:  thresholdDontNotify,\n\tDuration:    thresholdDuration,\n\tEnabled:     thresholdEnabled,\n\tInterval:    thresholdInterval,\n\tLimit:       thresholdLimit,\n\tName:        thresholdName,\n\tSignal:      thresholdSignal,\n\tThresholdID: thresholdID,\n}\n\nfunc TestThresholdCreate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --action flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--name %s --signal %s --do-not-notify=%t --duration %d --enabled=%t --interval %d --limit %d --workspace-id %s\", thresholdName, thresholdSignal, thresholdDontNotify, thresholdDuration, thresholdEnabled, thresholdInterval, thresholdLimit, workspaceID),\n\t\t\tWantError: \"error parsing arguments: required flag --action not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --name flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--action %s --signal %s --do-not-notify=%t --duration %d --enabled=%t --interval %d --limit %d --workspace-id %s\", thresholdAction, thresholdSignal, thresholdDontNotify, thresholdDuration, thresholdEnabled, thresholdInterval, thresholdLimit, workspaceID),\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --signal flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--action %s --name %s --do-not-notify=%t --duration %d --enabled=%t --interval %d --limit %d --workspace-id %s\", thresholdAction, thresholdName, thresholdDontNotify, thresholdDuration, thresholdEnabled, thresholdInterval, thresholdLimit, workspaceID),\n\t\t\tWantError: \"error parsing arguments: required flag --signal not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --do-not-notify flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--action %s --name %s --signal %s --duration %d --enabled=%t --interval %d --limit %d --workspace-id %s\", thresholdAction, thresholdName, thresholdSignal, thresholdDuration, thresholdEnabled, thresholdInterval, thresholdLimit, workspaceID),\n\t\t\tWantError: \"error parsing arguments: required flag --do-not-notify not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --duration flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--action %s --name %s --signal %s --do-not-notify=%t --enabled=%t --interval %d --limit %d --workspace-id %s\", thresholdAction, thresholdName, thresholdSignal, thresholdDontNotify, thresholdEnabled, thresholdInterval, thresholdLimit, workspaceID),\n\t\t\tWantError: \"error parsing arguments: required flag --duration not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --enabled flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--action %s --name %s --signal %s --do-not-notify=%t --duration %d --interval %d --limit %d --workspace-id %s\", thresholdAction, thresholdName, thresholdSignal, thresholdDontNotify, thresholdDuration, thresholdInterval, thresholdLimit, workspaceID),\n\t\t\tWantError: \"error parsing arguments: required flag --enabled not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --interval flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--action %s --name %s --signal %s --do-not-notify=%t --duration %d --enabled=%t --limit %d --workspace-id %s\", thresholdAction, thresholdName, thresholdSignal, thresholdDontNotify, thresholdDuration, thresholdEnabled, thresholdLimit, workspaceID),\n\t\t\tWantError: \"error parsing arguments: required flag --interval not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --limit flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--action %s --name %s --signal %s --do-not-notify=%t --duration %d --enabled=%t --interval %d --workspace-id %s\", thresholdAction, thresholdName, thresholdSignal, thresholdDontNotify, thresholdDuration, thresholdEnabled, thresholdInterval, workspaceID),\n\t\t\tWantError: \"error parsing arguments: required flag --limit not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --workspace-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--action %s --name %s --signal %s --do-not-notify=%t --duration %d --enabled=%t --interval %d --limit %d\", thresholdAction, thresholdName, thresholdSignal, thresholdDontNotify, thresholdDuration, thresholdEnabled, thresholdInterval, thresholdLimit),\n\t\t\tWantError: \"error reading workspace ID: no workspace ID found\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate internal server error\",\n\t\t\tArgs: fmt.Sprintf(\"--action %s --name %s --signal %s --do-not-notify=%t --duration %d --enabled=%t --interval %d --limit %d --workspace-id %s\", thresholdAction, thresholdName, thresholdSignal, thresholdDontNotify, thresholdDuration, thresholdEnabled, thresholdInterval, thresholdLimit, workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusInternalServerError,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusInternalServerError),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"500 - Internal Server Error\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--action %s --name %s --signal %s --do-not-notify=%t --duration %d --enabled=%t --interval %d --limit %d --workspace-id %s\", thresholdAction, thresholdName, thresholdSignal, thresholdDontNotify, thresholdDuration, thresholdEnabled, thresholdInterval, thresholdLimit, workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader((testutil.GenJSON(threshold)))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Created threshold '%s' for workspace '%s'\", thresholdID, workspaceID),\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--action %s --name %s --signal %s --do-not-notify=%t --duration %d --enabled=%t --interval %d --limit %d --workspace-id %s --json\", thresholdAction, thresholdName, thresholdSignal, thresholdDontNotify, thresholdDuration, thresholdEnabled, thresholdInterval, thresholdLimit, workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(threshold))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(threshold),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, workspace.CommandName, sub.CommandName, \"create\"}, scenarios)\n}\n\nfunc TestThresholdDelete(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --threshold-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--workspace-id %s\", workspaceID),\n\t\t\tWantError: \"error parsing arguments: required flag --threshold-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --workspace-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--threshold-id %s\", thresholdID),\n\t\t\tWantError: \"error reading workspace ID: no workspace ID found\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate bad request\",\n\t\t\tArgs: fmt.Sprintf(\"--threshold-id %s --workspace-id %s\", thresholdID, workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusBadRequest,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusBadRequest),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(`\n\t\t\t\t\t\t\t{\n    \t\t\t\t\t\t\t\"title\": \"invalid Threshold ID\",\n    \t\t\t\t\t\t\t\"status\": 400\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t`))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"400 - Bad Request\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--threshold-id %s --workspace-id %s\", thresholdID, workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusNoContent,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusNoContent),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Deleted threshold (id: %s)\", thresholdID),\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--threshold-id %s --workspace-id %s --json\", thresholdID, workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusNoContent,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusNoContent),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.JSON(`{\"id\": %q, \"deleted\": true}`, thresholdID),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, workspace.CommandName, sub.CommandName, \"delete\"}, scenarios)\n}\n\nfunc TestThresholdGet(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --threshold-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--workspace-id %s\", workspaceID),\n\t\t\tWantError: \"error parsing arguments: required flag --threshold-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --workspace-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--threshold-id %s\", thresholdID),\n\t\t\tWantError: \"error reading workspace ID: no workspace ID found\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate bad request\",\n\t\t\tArgs: fmt.Sprintf(\"--threshold-id %s --workspace-id %s\", thresholdID, workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusBadRequest,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusBadRequest),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(`\n\t\t\t\t\t\t\t{\n    \t\t\t\t\t\t\t\"title\": \"invalid Threshold ID\",\n    \t\t\t\t\t\t\t\"status\": 400\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t`))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"400 - Bad Request\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--threshold-id %s --workspace-id %s\", thresholdID, workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader((testutil.GenJSON(threshold)))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: thresholdString,\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--threshold-id %s --workspace-id %s --json\", thresholdID, workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader((testutil.GenJSON(threshold)))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(threshold),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, workspace.CommandName, sub.CommandName, \"get\"}, scenarios)\n}\n\nfunc TestThresholdList(t *testing.T) {\n\tthresholdsObject := thresholds.Thresholds{\n\t\tData: []thresholds.Threshold{\n\t\t\t{\n\t\t\t\tAction:      thresholdAction,\n\t\t\t\tCreatedAt:   testutil.Date,\n\t\t\t\tDontNotify:  thresholdDontNotify,\n\t\t\t\tDuration:    thresholdDuration,\n\t\t\t\tEnabled:     thresholdEnabled,\n\t\t\t\tInterval:    thresholdInterval,\n\t\t\t\tLimit:       thresholdLimit,\n\t\t\t\tName:        thresholdName,\n\t\t\t\tSignal:      thresholdSignal,\n\t\t\t\tThresholdID: thresholdID,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAction:      \"log\",\n\t\t\t\tCreatedAt:   testutil.Date,\n\t\t\t\tDontNotify:  true,\n\t\t\t\tDuration:    43200,\n\t\t\t\tEnabled:     false,\n\t\t\t\tInterval:    600,\n\t\t\t\tLimit:       20,\n\t\t\t\tName:        \"Test_Threshold_2\",\n\t\t\t\tSignal:      \"test-signal-2\",\n\t\t\t\tThresholdID: thresholdID + \"2\",\n\t\t\t},\n\t\t},\n\t\tMeta: thresholds.MetaThresholds{},\n\t}\n\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --workspace-id flag\",\n\t\t\tArgs:      \"\",\n\t\t\tWantError: \"error reading workspace ID: no workspace ID found\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate internal server error\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s\", workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusInternalServerError,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusInternalServerError),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"500 - Internal Server Error\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success (zero thresholds)\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s\", workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(thresholds.Thresholds{\n\t\t\t\t\t\t\tData: []thresholds.Threshold{},\n\t\t\t\t\t\t\tMeta: thresholds.MetaThresholds{},\n\t\t\t\t\t\t}))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: zeroListThresholdString,\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s\", workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(thresholdsObject))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: listThresholdString,\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --json\", workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(thresholdsObject))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(thresholdsObject),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, workspace.CommandName, sub.CommandName, \"list\"}, scenarios)\n}\n\nfunc TestThresholdUpdate(t *testing.T) {\n\tthresholdsObject := thresholds.Threshold{\n\t\tAction:      thresholdAction,\n\t\tCreatedAt:   testutil.Date,\n\t\tDontNotify:  thresholdDontNotify,\n\t\tDuration:    thresholdDuration,\n\t\tEnabled:     thresholdEnabled,\n\t\tInterval:    thresholdInterval,\n\t\tLimit:       thresholdLimit,\n\t\tName:        thresholdName,\n\t\tSignal:      thresholdSignal,\n\t\tThresholdID: thresholdID,\n\t}\n\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --threshold-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--workspace-id %s\", workspaceID),\n\t\t\tWantError: \"error parsing arguments: required flag --threshold-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --workspace-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--threshold-id %s\", thresholdID),\n\t\t\tWantError: \"error reading workspace ID: no workspace ID found\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--threshold-id %s --workspace-id %s --action %s --name %s --signal %s --do-not-notify=%t --duration %d --enabled=%t --interval %d --limit %d\", thresholdID, workspaceID, thresholdAction, thresholdName, thresholdSignal, thresholdDontNotify, thresholdDuration, thresholdEnabled, thresholdInterval, thresholdLimit),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(thresholdsObject))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Updated threshold '%s' for workspace '%s'\", thresholdID, workspaceID),\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--threshold-id %s --workspace-id %s --action %s --name %s --signal %s --do-not-notify=%t --duration %d --enabled=%t --interval %d --limit %d --json\", thresholdID, workspaceID, thresholdAction, thresholdName, thresholdSignal, thresholdDontNotify, thresholdDuration, thresholdEnabled, thresholdInterval, thresholdLimit),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(threshold))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(threshold),\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, workspace.CommandName, sub.CommandName, \"update\"}, scenarios)\n}\n\nvar listThresholdString = strings.TrimSpace(`\nSignal         Name              ID            Action  Enabled  Do Not Notify  Limit  Interval  Duration  Created At\ntest-signal    Test_Threshold    thresholdID   block   true     false          10     3600      86400     2021-06-15T23:00:00Z\ntest-signal-2  Test_Threshold_2  thresholdID2  log     false    true           20     600       43200     2021-06-15T23:00:00Z\n`) + \"\\n\"\n\nvar zeroListThresholdString = strings.TrimSpace(`\nSignal  Name  ID  Action  Enabled  Do Not Notify  Limit  Interval  Duration  Created At\n`) + \"\\n\"\n\nvar thresholdString = strings.TrimSpace(`\nSignal: test-signal\nName: Test_Threshold\nAction: block\nDo Not Notify: false\nDuration: 86400\nEnabled: true\nInterval: 3600\nLimit: 10\n`)\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/threshold/update.go",
    "content": "package threshold\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/workspaces/thresholds\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// UpdateCommand calls the Fastly API to update a workspace threshold.\ntype UpdateCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tthresholdID string\n\tworkspaceID argparser.OptionalWorkspaceID\n\n\t// Optional.\n\taction     argparser.OptionalString\n\tdontNotify argparser.OptionalString\n\tduration   argparser.OptionalInt\n\tenabled    argparser.OptionalString\n\tinterval   argparser.OptionalInt\n\tlimit      argparser.OptionalInt\n\tname       argparser.OptionalString\n\tsignal     argparser.OptionalString\n}\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"update\", \"Update a workspace threshold\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagNGWAFWorkspaceID,\n\t\tDescription: argparser.FlagNGWAFWorkspaceIDDesc,\n\t\tDst:         &c.workspaceID.Value,\n\t\tAction:      c.workspaceID.Set,\n\t})\n\tc.CmdClause.Flag(\"threshold-id\", \"Threshold ID\").Required().StringVar(&c.thresholdID)\n\n\t// Optional.\n\tc.CmdClause.Flag(\"action\", \"The action to take when the threshold is exceeded. [block, log]\").Action(c.action.Set).StringVar(&c.action.Value)\n\tc.CmdClause.Flag(\"do-not-notify\", \"Whether to silence notifications when action is taken. [true, false]\").Action(c.dontNotify.Set).StringVar(&c.dontNotify.Value)\n\tc.CmdClause.Flag(\"duration\", \"The duration the action is in place in seconds. Default duration is 86,400 seconds (1 day).\").Action(c.duration.Set).IntVar(&c.duration.Value)\n\tc.CmdClause.Flag(\"enabled\", \"Whether the threshold is active. [true, false]\").Action(c.enabled.Set).StringVar(&c.enabled.Value)\n\tc.CmdClause.Flag(\"interval\", \"The threshold interval in seconds. The default interval is 3600 seconds (1 hour).\").Action(c.interval.Set).IntVar(&c.interval.Value)\n\tc.CmdClause.Flag(\"limit\", \"The threshold limit. Input must be between 1 and 10000. Default limit is 10.\").Action(c.limit.Set).IntVar(&c.limit.Value)\n\tc.CmdClause.Flag(\"name\", \"User submitted display name of a signal threshold. Input must be between 3 and 50 characters\").Action(c.name.Set).StringVar(&c.name.Value)\n\tc.CmdClause.Flag(\"signal\", \"The name of the signal this threshold is acting on\").Action(c.signal.Set).StringVar(&c.signal.Value)\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *UpdateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\t// Call Parse() to ensure that we check if workspaceID\n\t// is set or to throw the appropriate error.\n\tif err := c.workspaceID.Parse(); err != nil {\n\t\treturn err\n\t}\n\tinput := &thresholds.UpdateInput{\n\t\tThresholdID: &c.thresholdID,\n\t\tWorkspaceID: &c.workspaceID.Value,\n\t}\n\tif c.action.WasSet {\n\t\tinput.Action = &c.action.Value\n\t}\n\tif c.dontNotify.WasSet {\n\t\tdontNotify, err := argparser.ConvertBoolFromStringFlag(c.dontNotify.Value, \"do-not-notify\")\n\t\tif err != nil {\n\t\t\tc.Globals.ErrLog.Add(err)\n\t\t\treturn err\n\t\t}\n\t\tinput.DontNotify = dontNotify\n\t}\n\tif c.duration.WasSet {\n\t\tinput.Duration = &c.duration.Value\n\t}\n\tif c.enabled.WasSet {\n\t\tenabled, err := argparser.ConvertBoolFromStringFlag(c.enabled.Value, \"enabled\")\n\t\tif err != nil {\n\t\t\tc.Globals.ErrLog.Add(err)\n\t\t\treturn err\n\t\t}\n\t\tinput.Enabled = enabled\n\t}\n\tif c.interval.WasSet {\n\t\tinput.Interval = &c.interval.Value\n\t}\n\tif c.limit.WasSet {\n\t\tinput.Limit = &c.limit.Value\n\t}\n\tif c.name.WasSet {\n\t\tinput.Name = &c.name.Value\n\t}\n\tif c.signal.WasSet {\n\t\tinput.Signal = &c.signal.Value\n\t}\n\n\tfc, ok := c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tdata, err := thresholds.Update(context.TODO(), fc, input)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, data); ok {\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Updated threshold '%s' for workspace '%s'\", data.ThresholdID, c.workspaceID.Value)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/update.go",
    "content": "package workspace\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/workspaces\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// UpdateCommand calls the Fastly API to update workspaces.\ntype UpdateCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tworkspaceID string\n\n\t// Optional.\n\tdescription         argparser.OptionalString\n\tblockingMode        argparser.OptionalString\n\tname                argparser.OptionalString\n\tattackThresholds    argparser.OptionalString\n\tdefaultBlockingCode argparser.OptionalInt\n\tdefaultRedirectURL  argparser.OptionalString\n\tclientIPHeaders     argparser.OptionalString\n\tipAnonymization     argparser.OptionalString\n}\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"update\", \"Update a workspace\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"workspace-id\", \"Workspace ID\").Required().StringVar(&c.workspaceID)\n\n\t// Optional.\n\tc.CmdClause.Flag(\"description\", \"User submitted description of a workspace.\").Action(c.description.Set).StringVar(&c.description.Value)\n\tc.CmdClause.Flag(\"blockingMode\", \"User configured mode blocking mode.\").Action(c.blockingMode.Set).StringVar(&c.blockingMode.Value)\n\tc.CmdClause.Flag(\"name\", \"User submitted display name of a workspace.\").Action(c.name.Set).StringVar(&c.name.Value)\n\tc.CmdClause.Flag(\"attackThresholds\", \"Attack threshold parameters for system site alerts. Each threshold value is the number of attack signals per IP address that must be detected during the interval before the related IP address is flagged. Input accepted as colon separated string: Immediate:OneMinute:TenMinutes:OneHour\").Action(c.attackThresholds.Set).StringVar(&c.attackThresholds.Value)\n\tc.CmdClause.Flag(\"clientIPHeaders\", \"Specify the request header containing the client IP address. Input accepted as colon separated string.\").Action(c.clientIPHeaders.Set).StringVar(&c.clientIPHeaders.Value)\n\tc.CmdClause.Flag(\"defaultBlockingCode\", \"Default status code that is returned when a request to your web application is blocked.\").Action(c.defaultBlockingCode.Set).IntVar(&c.defaultBlockingCode.Value)\n\tc.CmdClause.Flag(\"defaultRedirectURL\", \"Redirect url to be used if code 301 or 302 is used.\").Action(c.defaultRedirectURL.Set).StringVar(&c.defaultRedirectURL.Value)\n\tc.CmdClause.Flag(\"ipAnonymization\", \"Agents will anonymize IP addresses according to the option selected.\").Action(c.ipAnonymization.Set).StringVar(&c.ipAnonymization.Value)\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *UpdateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\tvar err error\n\tinput := &workspaces.UpdateInput{\n\t\tWorkspaceID: &c.workspaceID,\n\t}\n\n\tif c.blockingMode.WasSet {\n\t\tinput.Mode = &c.blockingMode.Value\n\t}\n\tif c.description.WasSet {\n\t\tinput.Description = &c.description.Value\n\t}\n\tif c.name.WasSet {\n\t\tinput.Name = &c.name.Value\n\t}\n\n\tif c.attackThresholds.WasSet {\n\t\tinput.AttackSignalThresholds, err = parseUpdateAttackSignalThresholds(c.attackThresholds.Value)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tif c.clientIPHeaders.WasSet {\n\t\tinput.ClientIPHeaders = strings.Split(c.clientIPHeaders.Value, \":\")\n\t}\n\tif c.defaultBlockingCode.WasSet {\n\t\tinput.DefaultBlockingResponseCode = &c.defaultBlockingCode.Value\n\t}\n\tif c.defaultRedirectURL.WasSet {\n\t\tinput.DefaultRedirectURL = &c.defaultRedirectURL.Value\n\t}\n\tif c.ipAnonymization.WasSet {\n\t\tinput.IPAnonymization = &c.ipAnonymization.Value\n\t}\n\n\tfc, ok := c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tdata, err := workspaces.Update(context.TODO(), fc, input)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, data); ok {\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Updated workspace '%s' (workspace-id: %s)\", data.Name, data.WorkspaceID)\n\treturn nil\n}\n\nfunc parseUpdateAttackSignalThresholds(thresholds string) (*workspaces.AttackSignalThresholdsUpdateInput, error) {\n\tthresholdsArray := strings.Split(thresholds, \":\")\n\tif len(thresholdsArray) != 4 {\n\t\treturn nil, errors.New(\"wrong number of inputs for Attack Signal Thresholds\")\n\t}\n\timmediate, err := strconv.ParseBool(thresholdsArray[0])\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\toneMinute, err := strconv.Atoi(thresholdsArray[1])\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\ttenMinutes, err := strconv.Atoi(thresholdsArray[2])\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\toneHour, err := strconv.Atoi(thresholdsArray[3])\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &workspaces.AttackSignalThresholdsUpdateInput{\n\t\tOneMinute:  &oneMinute,\n\t\tTenMinutes: &tenMinutes,\n\t\tOneHour:    &oneHour,\n\t\tImmediate:  &immediate,\n\t}, nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/virtualpatch/list.go",
    "content": "package virtualpatch\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/workspaces/virtualpatches\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// ListCommand calls the Fastly API to list virtual patches in a workspace.\ntype ListCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tworkspaceID string\n}\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\n\tc.CmdClause = parent.Command(\"list\", \"List virtual patches in a workspace\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"workspace-id\", \"Workspace ID\").Required().StringVar(&c.workspaceID)\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *ListCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tfc, ok := c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tdata, err := virtualpatches.List(context.TODO(), fc, &virtualpatches.ListInput{\n\t\tWorkspaceID: &c.workspaceID,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, data); ok {\n\t\treturn err\n\t}\n\n\t// Currently we are leaving the table to output the default\n\t// number of virtual patches, which is 100. At this time\n\t// this is sufficient as there are only 40 total, however,\n\t// we may need to rework this in the future.\n\ttext.PrintVirtualPatchTbl(out, data.Data)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/virtualpatch/retrieve.go",
    "content": "package virtualpatch\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/workspaces/virtualpatches\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// GetCommand calls the Fastly API to get a virtual patch.\ntype GetCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tvirtualpatchID string\n\tworkspaceID    string\n}\n\n// NewGetCommand returns a usable command registered under the parent.\nfunc NewRetrieveCommand(parent argparser.Registerer, g *global.Data) *GetCommand {\n\tc := GetCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\n\tc.CmdClause = parent.Command(\"retrieve\", \"Retrieve a virtual patch\").Alias(\"get\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"virtual-patch-id\", \"Virtual Patch ID\").Required().StringVar(&c.virtualpatchID)\n\tc.CmdClause.Flag(\"workspace-id\", \"Workspace ID\").Required().StringVar(&c.workspaceID)\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *GetCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tfc, ok := c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tdata, err := virtualpatches.Get(context.TODO(), fc, &virtualpatches.GetInput{\n\t\tVirtualPatchID: &c.virtualpatchID,\n\t\tWorkspaceID:    &c.workspaceID,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, data); ok {\n\t\treturn err\n\t}\n\n\ttext.PrintVirtualPatch(out, data)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/virtualpatch/root.go",
    "content": "package virtualpatch\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"virtualpatch\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manage NGWAF Virtual Patches\")\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/virtualpatch/update.go",
    "content": "package virtualpatch\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/workspaces/virtualpatches\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// UpdateCommand calls the Fastly API to update virtual patches.\ntype UpdateCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tvirtualpatchID string\n\tworkspaceID    string\n\n\t// Optional.\n\tenabled argparser.OptionalString\n\tmode    argparser.OptionalString\n}\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"update\", \"Update a virtual patch\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"virtual-patch-id\", \"Virtual Patch ID\").Required().StringVar(&c.virtualpatchID)\n\tc.CmdClause.Flag(\"workspace-id\", \"Workspace ID\").Required().StringVar(&c.workspaceID)\n\n\t// Optional.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        \"enabled\",\n\t\tDescription: \"Specify the toggle status indicator of the virtual patch.\",\n\t\tAction:      c.enabled.Set,\n\t\tDst:         &c.enabled.Value,\n\t})\n\tc.CmdClause.Flag(\"mode\", \"Specify the action to take when a signal for virtual patch is detected.\").Action(c.mode.Set).StringVar(&c.mode.Value)\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *UpdateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tvar err error\n\tinput := &virtualpatches.UpdateInput{\n\t\tVirtualPatchID: &c.virtualpatchID,\n\t\tWorkspaceID:    &c.workspaceID,\n\t}\n\tif c.enabled.WasSet {\n\t\tenabled, err := argparser.ConvertBoolFromStringFlag(c.enabled.Value, \"enabled\")\n\t\tif err != nil {\n\t\t\tc.Globals.ErrLog.Add(err)\n\t\t\treturn err\n\t\t}\n\t\tinput.Enabled = enabled\n\t}\n\tif c.mode.WasSet {\n\t\tinput.Mode = &c.mode.Value\n\t}\n\n\tfc, ok := c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tdata, err := virtualpatches.Update(context.TODO(), fc, input)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, data); ok {\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Updated virtual patch '%s' (enabled: %t, mode: %s)\", data.ID, data.Enabled, data.Mode)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/virtualpatch/virtualpatch_test.go",
    "content": "package virtualpatch_test\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\t\"testing\"\n\n\troot \"github.com/fastly/cli/pkg/commands/ngwaf\"\n\tworkspace \"github.com/fastly/cli/pkg/commands/ngwaf/workspace\"\n\tsub \"github.com/fastly/cli/pkg/commands/ngwaf/workspace/virtualpatch\"\n\tfstfmt \"github.com/fastly/cli/pkg/fmt\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/workspaces/virtualpatches\"\n)\n\nconst (\n\tvirtualpatchID          = \"CVE-2017-5638\"\n\tvirtualpatchDescription = \"Apache Struts multipart/form remote execution\"\n\tvirtualpatchEnabled     = false\n\tvirtualpatchMode        = \"log\"\n\tworkspaceID             = \"nBw2ENWfOY1M2dpSwK1l5R\"\n)\n\nvar virtualpatch = virtualpatches.VirtualPatch{\n\tID:          virtualpatchID,\n\tDescription: virtualpatchDescription,\n\tEnabled:     virtualpatchEnabled,\n\tMode:        virtualpatchMode,\n}\n\nfunc TestVirtualPatchRetrieve(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --workspace-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--virtual-patch-id %s\", virtualpatchID),\n\t\t\tWantError: \"error parsing arguments: required flag --workspace-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --virtual-patch-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--workspace-id %s\", workspaceID),\n\t\t\tWantError: \"error parsing arguments: required flag --virtual-patch-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate not found\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --virtual-patch-id invalid\", workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusNotFound,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusNotFound),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(`\n\t\t\t\t\t\t\t{\n    \t\t\t\t\t\t\t\"title\": \"This resource does not exist\",\n    \t\t\t\t\t\t\t\"status\": 404\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t`))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"404 - Not Found\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --virtual-patch-id %s\", workspaceID, virtualpatchID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader((testutil.GenJSON(virtualpatch)))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: virtualpatchString,\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --virtual-patch-id %s --json\", workspaceID, virtualpatchID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader((testutil.GenJSON(virtualpatch)))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(virtualpatch),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, workspace.CommandName, sub.CommandName, \"retrieve\"}, scenarios)\n}\n\nfunc TestVirtualPatchList(t *testing.T) {\n\tvirtualpatchesObject := virtualpatches.VirtualPatches{\n\t\tData: []virtualpatches.VirtualPatch{\n\t\t\t{\n\t\t\t\tID:          \"CVE-2024-5806\",\n\t\t\t\tDescription: \"Progress MOVEit Transfer Authentication Bypass Vulnerability\",\n\t\t\t\tEnabled:     false,\n\t\t\t\tMode:        \"log\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tID:          \"CVE-2024-34102\",\n\t\t\t\tDescription: \"Adobe Commerce and Magento Open Source Unauthenticated XML Entity Injection\",\n\t\t\t\tEnabled:     false,\n\t\t\t\tMode:        \"log\",\n\t\t\t},\n\t\t},\n\t}\n\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --workspace-id flag\",\n\t\t\tArgs:      \"\",\n\t\t\tWantError: \"error parsing arguments: required flag --workspace-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate internal server error\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s\", workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusInternalServerError,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusInternalServerError),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"500 - Internal Server Error\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success (zero virtual patches)\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s\", workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(virtualpatches.VirtualPatches{\n\t\t\t\t\t\t\tData: []virtualpatches.VirtualPatch{},\n\t\t\t\t\t\t}))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: zeroListVirtualPatchString,\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s\", workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(virtualpatchesObject))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: listVirtualPatchString,\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --json\", workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(virtualpatchesObject))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(virtualpatchesObject),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, workspace.CommandName, sub.CommandName, \"list\"}, scenarios)\n}\n\nfunc TestVirtualPatchUpdate(t *testing.T) {\n\tupdatedVirtualPatch := virtualpatches.VirtualPatch{\n\t\tID:          virtualpatchID,\n\t\tDescription: virtualpatchDescription,\n\t\tEnabled:     true,\n\t\tMode:        \"block\",\n\t}\n\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --workspace-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--virtual-patch-id %s\", virtualpatchID),\n\t\t\tWantError: \"error parsing arguments: required flag --workspace-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --virtual-patch-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--workspace-id %s\", workspaceID),\n\t\t\tWantError: \"error parsing arguments: required flag --virtual-patch-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate not found\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --virtual-patch-id invalid --enabled true\", workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusNotFound,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusNotFound),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(`\n\t\t\t\t\t\t\t{\n    \t\t\t\t\t\t\t\"title\": \"This resource does not exist\",\n    \t\t\t\t\t\t\t\"status\": 404\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t`))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"404 - Not Found\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate invalid enabled flag value\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --virtual-patch-id %s --enabled maybe\", workspaceID, virtualpatchID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(updatedVirtualPatch))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"'enabled' flag must be one of the following [true, false]\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success with enabled flag\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --virtual-patch-id %s --enabled true\", workspaceID, virtualpatchID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(updatedVirtualPatch))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Updated virtual patch '%s' (enabled: %t, mode: %s)\", updatedVirtualPatch.ID, updatedVirtualPatch.Enabled, updatedVirtualPatch.Mode),\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success with mode flag\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --virtual-patch-id %s --mode block\", workspaceID, virtualpatchID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(updatedVirtualPatch))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Updated virtual patch '%s' (enabled: %t, mode: %s)\", updatedVirtualPatch.ID, updatedVirtualPatch.Enabled, updatedVirtualPatch.Mode),\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success with both flags\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --virtual-patch-id %s --enabled true --mode block\", workspaceID, virtualpatchID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(updatedVirtualPatch))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Updated virtual patch '%s' (enabled: %t, mode: %s)\", updatedVirtualPatch.ID, updatedVirtualPatch.Enabled, updatedVirtualPatch.Mode),\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --virtual-patch-id %s --enabled true --mode block --json\", workspaceID, virtualpatchID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(updatedVirtualPatch))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(updatedVirtualPatch),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, workspace.CommandName, sub.CommandName, \"update\"}, scenarios)\n}\n\nvar virtualpatchString = strings.TrimSpace(`\nID: CVE-2017-5638\nDescription: Apache Struts multipart/form remote execution\nEnabled: false\nMode: log\n`)\n\nvar listVirtualPatchString = strings.TrimSpace(`\nID              Description                                                                  Enabled  Mode\nCVE-2024-5806   Progress MOVEit Transfer Authentication Bypass Vulnerability                 false    log\nCVE-2024-34102  Adobe Commerce and Magento Open Source Unauthenticated XML Entity Injection  false    log\n`) + \"\\n\"\n\nvar zeroListVirtualPatchString = strings.TrimSpace(`\nID  Description  Enabled  Mode\n`) + \"\\n\"\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/wildcardlist/create.go",
    "content": "package wildcardlist\n\nimport (\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/scope\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/ngwaf/ngwaflist\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// CreateCommand calls the Fastly API to create workspace-level wildcard lists.\ntype CreateCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tentries     string\n\tname        string\n\tworkspaceID argparser.OptionalWorkspaceID\n\n\t// Optional.\n\tdescription argparser.OptionalString\n}\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"create\", \"Create a workspace-level wildcard list\").Alias(\"add\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"entries\", \"Entries for the list. Can either be a comma separated list or a path to a file.\").Required().StringVar(&c.entries)\n\tc.CmdClause.Flag(\"name\", \"User submitted display name of a list.\").Required().StringVar(&c.name)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagNGWAFWorkspaceID,\n\t\tDescription: argparser.FlagNGWAFWorkspaceIDDesc,\n\t\tDst:         &c.workspaceID.Value,\n\t\tAction:      c.workspaceID.Set,\n\t})\n\n\t// Optional.\n\tc.CmdClause.Flag(\"description\", \"User submitted description of the list.\").Action(c.description.Set).StringVar(&c.description.Value)\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *CreateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\tinput := ngwaflist.ListCreateInput{\n\t\tCommandScope: scope.ScopeTypeWorkspace,\n\t\tDescription:  c.description,\n\t\tEntries:      c.entries,\n\t\tName:         c.name,\n\t\tType:         \"wildcard\",\n\t\tWorkspaceID:  &c.workspaceID,\n\t}\n\n\tvar ok bool\n\tinput.FC, ok = c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tdata, err := ngwaflist.ListCreate(input)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, data); ok {\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Created Workspace Wildcard List '%s' (list id: %s)\", data.Name, data.ListID)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/wildcardlist/delete.go",
    "content": "package wildcardlist\n\nimport (\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/scope\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/ngwaf/ngwaflist\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DeleteCommand calls the Fastly API to delete a workspace-level wildcardip list.\ntype DeleteCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tlistID      string\n\tworkspaceID argparser.OptionalWorkspaceID\n}\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\n\tc.CmdClause = parent.Command(\"delete\", \"Delete a workspace wildcard list\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"list-id\", \"List ID\").Required().StringVar(&c.listID)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagNGWAFWorkspaceID,\n\t\tDescription: argparser.FlagNGWAFWorkspaceIDDesc,\n\t\tDst:         &c.workspaceID.Value,\n\t\tAction:      c.workspaceID.Set,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DeleteCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tinput := ngwaflist.ListDeleteInput{\n\t\tCommandScope: scope.ScopeTypeWorkspace,\n\t\tListID:       c.listID,\n\t\tWorkspaceID:  &c.workspaceID,\n\t}\n\n\tvar ok bool\n\tinput.FC, ok = c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\terr := ngwaflist.ListDelete(input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif c.JSONOutput.Enabled {\n\t\to := struct {\n\t\t\tID      string `json:\"id\"`\n\t\t\tDeleted bool   `json:\"deleted\"`\n\t\t}{\n\t\t\tc.listID,\n\t\t\ttrue,\n\t\t}\n\t\t_, err := c.WriteJSON(out, o)\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Deleted Workspace Wildcard List (list id: %s)\", c.listID)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/wildcardlist/doc.go",
    "content": "// Package wildcardlist contains commands to inspect and manipulate NGWAF workspace-level wildcard lists.\npackage wildcardlist\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/wildcardlist/get.go",
    "content": "package wildcardlist\n\nimport (\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/ngwaf/ngwaflist\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/scope\"\n)\n\n// GetCommand calls the Fastly API to get a workspace-level wildcard list.\ntype GetCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tlistID      string\n\tworkspaceID argparser.OptionalWorkspaceID\n}\n\n// NewGetCommand returns a usable command registered under the parent.\nfunc NewGetCommand(parent argparser.Registerer, g *global.Data) *GetCommand {\n\tc := GetCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\n\tc.CmdClause = parent.Command(\"get\", \"Get a workspace-level wildcard list\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"list-id\", \"List ID\").Required().StringVar(&c.listID)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagNGWAFWorkspaceID,\n\t\tDescription: argparser.FlagNGWAFWorkspaceIDDesc,\n\t\tDst:         &c.workspaceID.Value,\n\t\tAction:      c.workspaceID.Set,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *GetCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tinput := ngwaflist.ListGetInput{\n\t\tCommandScope: scope.ScopeTypeWorkspace,\n\t\tListID:       c.listID,\n\t\tWorkspaceID:  &c.workspaceID,\n\t}\n\n\tvar ok bool\n\tinput.FC, ok = c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tlist, err := ngwaflist.ListGet(input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, list); ok {\n\t\treturn err\n\t}\n\n\ttext.PrintList(out, list)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/wildcardlist/list.go",
    "content": "package wildcardlist\n\nimport (\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/ngwaf/ngwaflist\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/scope\"\n)\n\n// ListCommand calls the Fastly API to list all wildcard lists for your API token.\n\ntype ListCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tworkspaceID argparser.OptionalWorkspaceID\n}\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\n\tc.CmdClause = parent.Command(\"list\", \"List all wildcard lists for your workspace\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagNGWAFWorkspaceID,\n\t\tDescription: argparser.FlagNGWAFWorkspaceIDDesc,\n\t\tDst:         &c.workspaceID.Value,\n\t\tAction:      c.workspaceID.Set,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *ListCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tinput := ngwaflist.ListListInput{\n\t\tCommandScope: scope.ScopeTypeWorkspace,\n\t\tType:         \"wildcard\",\n\t\tWorkspaceID:  &c.workspaceID,\n\t}\n\n\tvar ok bool\n\tinput.FC, ok = c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tlists, err := ngwaflist.ListList(input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, *lists); ok {\n\t\treturn err\n\t}\n\n\ttext.PrintListTbl(out, lists.Data)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/wildcardlist/root.go",
    "content": "package wildcardlist\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"wildcard-list\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manage NGWAF Account Wildcard Lists\")\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/wildcardlist/update.go",
    "content": "package wildcardlist\n\nimport (\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/scope\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/ngwaf/ngwaflist\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// UpdateCommand calls the Fastly API to update a workspace wildcard list.\ntype UpdateCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tlistID      string\n\tworkspaceID argparser.OptionalWorkspaceID\n\n\t// Optional.\n\tdescription argparser.OptionalString\n\tentries     argparser.OptionalString\n}\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"update\", \"Update a workspace-level wildcard list\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"list-id\", \"List ID\").Required().StringVar(&c.listID)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagNGWAFWorkspaceID,\n\t\tDescription: argparser.FlagNGWAFWorkspaceIDDesc,\n\t\tDst:         &c.workspaceID.Value,\n\t\tAction:      c.workspaceID.Set,\n\t})\n\n\t// Optional.\n\tc.CmdClause.Flag(\"description\", \"User submitted description of the list.\").Action(c.description.Set).StringVar(&c.description.Value)\n\tc.CmdClause.Flag(\"entries\", \"Entries for the list. Can either be a comma separated list or a path to a file.\").Action(c.entries.Set).StringVar(&c.entries.Value)\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *UpdateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\tinput := ngwaflist.ListUpdateInput{\n\t\tCommandScope: scope.ScopeTypeWorkspace,\n\t\tDescription:  c.description,\n\t\tEntries:      c.entries,\n\t\tListID:       c.listID,\n\t\tWorkspaceID:  &c.workspaceID,\n\t}\n\n\tvar ok bool\n\tinput.FC, ok = c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tdata, err := ngwaflist.ListUpdate(input)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, data); ok {\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Updated Workspace Wildcard List '%s' (list id: %s)\", data.Name, data.ListID)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/wildcardlist/wildcardlist_test.go",
    "content": "package wildcardlist_test\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\t\"testing\"\n\n\troot \"github.com/fastly/cli/pkg/commands/ngwaf\"\n\tsub \"github.com/fastly/cli/pkg/commands/ngwaf/workspace\"\n\tsub2 \"github.com/fastly/cli/pkg/commands/ngwaf/workspace/wildcardlist\"\n\tfstfmt \"github.com/fastly/cli/pkg/fmt\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/lists\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/scope\"\n)\n\nconst (\n\tlistID          = \"someListID\"\n\tlistDescription = \"NGWAFCLIList\"\n\tlistEntries     = \"1.0.0.0\"\n\tlistType        = \"wildcard\"\n\tlistName        = \"listName\"\n\tworkspaceID     = \"someWorkspaceID\"\n)\n\nvar stringlist = lists.List{\n\tListID:      listID,\n\tDescription: listDescription,\n\tEntries:     []string{listEntries},\n\tName:        listName,\n\tType:        listType,\n\tCreatedAt:   testutil.Date,\n\tUpdatedAt:   testutil.Date,\n\tScope: lists.Scope{\n\t\tType: string(scope.ScopeTypeWorkspace),\n\t},\n}\n\nvar stringlist2 = lists.List{\n\tListID:      listID + \"2\",\n\tDescription: listDescription + \"2\",\n\tEntries:     []string{listEntries},\n\tName:        listName + \"2\",\n\tType:        listType,\n\tCreatedAt:   testutil.Date,\n\tUpdatedAt:   testutil.Date,\n\tScope: lists.Scope{\n\t\tType: string(scope.ScopeTypeWorkspace),\n\t},\n}\n\nfunc TestWildcardListCreate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --entries flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--name %s --workspace-id %s\", listName, workspaceID),\n\t\t\tWantError: \"error parsing arguments: required flag --entries not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --name flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--entries %s --workspace-id %s\", listEntries, workspaceID),\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate missing --workspace-id flag\",\n\t\t\tArgs: fmt.Sprintf(\"--entries %s --name %s\", listEntries, listName),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusNoContent,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusNoContent),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"error reading workspace ID: no workspace ID found\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate internal server error\",\n\t\t\tArgs: fmt.Sprintf(\"--entries %s --name %s --workspace-id %s\", listEntries, listName, workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusInternalServerError,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusInternalServerError),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"500 - Internal Server Error\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--entries %s --name %s --workspace-id %s\", listEntries, listName, workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader((testutil.GenJSON(stringlist)))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Created Workspace Wildcard List '%s' (list id: %s)\", listName, listID),\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--entries %s --name %s --workspace-id %s --json\", listEntries, listName, workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(stringlist))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(stringlist),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, sub2.CommandName, \"create\"}, scenarios)\n}\n\nfunc TestWildcardListDelete(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --list-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--workspace-id %s\", workspaceID),\n\t\t\tWantError: \"error parsing arguments: required flag --list-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate missing --workspace-id flag\",\n\t\t\tArgs: fmt.Sprintf(\"--list-id %s\", listID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusNoContent,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusNoContent),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"error reading workspace ID: no workspace ID found\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate bad request\",\n\t\t\tArgs: \"--list-id bar --workspace-id baz\",\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusBadRequest,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusBadRequest),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(`\n\t\t\t\t\t\t\t{\n    \t\t\t\t\t\t\t\"title\": \"invalid List ID\",\n    \t\t\t\t\t\t\t\"status\": 400\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t`))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"400 - Bad Request\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--list-id %s --workspace-id %s\", listID, workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusNoContent,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusNoContent),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Deleted Workspace Wildcard List (list id: %s)\", listID),\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--list-id %s --workspace-id %s --json\", listID, workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusNoContent,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusNoContent),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.JSON(`{\"id\": %q, \"deleted\": true}`, listID),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, sub2.CommandName, \"delete\"}, scenarios)\n}\n\nfunc TestWildcardListGet(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --list-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--workspace-id %s\", workspaceID),\n\t\t\tWantError: \"error parsing arguments: required flag --list-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate missing --workspace-id flag\",\n\t\t\tArgs: fmt.Sprintf(\"--list-id %s\", listID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusNoContent,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusNoContent),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"error reading workspace ID: no workspace ID found\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate bad request\",\n\t\t\tArgs: \"--list-id baz --workspace-id baz\",\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusBadRequest,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusBadRequest),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(`\n\t\t\t\t\t\t\t{\n    \t\t\t\t\t\t\t\"title\": \"invalid List ID\",\n    \t\t\t\t\t\t\t\"status\": 400\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t`))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"400 - Bad Request\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--list-id %s --workspace-id %s\", listID, workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader((testutil.GenJSON(stringlist)))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: listString,\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--list-id %s --workspace-id %s --json\", listID, workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader((testutil.GenJSON(stringlist)))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(stringlist),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, sub2.CommandName, \"get\"}, scenarios)\n}\n\nfunc TestWildcardListList(t *testing.T) {\n\tlistsObject := lists.Lists{\n\t\tData: []lists.List{\n\t\t\tstringlist,\n\t\t\tstringlist2,\n\t\t},\n\t\tMeta: lists.MetaLists{},\n\t}\n\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName: \"validate missing --workspace-id flag\",\n\t\t\tArgs: \"\",\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusNoContent,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusNoContent),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"error reading workspace ID: no workspace ID found\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate internal server error\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s\", workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusInternalServerError,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusInternalServerError),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"500 - Internal Server Error\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success (zero workspaces)\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s\", workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(lists.Lists{\n\t\t\t\t\t\t\tData: []lists.List{},\n\t\t\t\t\t\t\tMeta: lists.MetaLists{},\n\t\t\t\t\t\t}))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: zeroListString,\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s\", workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(listsObject))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: listListsString,\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --json\", workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(listsObject))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(listsObject),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, sub2.CommandName, \"list\"}, scenarios)\n}\n\nfunc TestWildcardListUpdate(t *testing.T) {\n\tupdatelist := lists.List{\n\t\tListID:      listID,\n\t\tDescription: listDescription + \"2\",\n\t\tEntries:     []string{listEntries + \"2\"},\n\t\tName:        listName,\n\t\tType:        listType,\n\t\tCreatedAt:   testutil.Date,\n\t\tUpdatedAt:   testutil.Date,\n\t\tScope: lists.Scope{\n\t\t\tType: string(scope.ScopeTypeWorkspace),\n\t\t},\n\t}\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --list-id flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--workspace-id %s\", workspaceID),\n\t\t\tWantError: \"error parsing arguments: required flag --list-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate missing --workspace-id flag\",\n\t\t\tArgs: fmt.Sprintf(\"--list-id %s\", listID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusNoContent,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusNoContent),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"error reading workspace ID: no workspace ID found\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--list-id %s --description %s --entries %s --workspace-id %s\", listID, listDescription+\"2\", listEntries+\"2\", workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(updatelist))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Updated Workspace Wildcard List '%s' (list id: %s)\", listName, listID),\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--list-id %s --description %s --entries %s --workspace-id %s --json\", listID, listDescription+\"2\", listEntries+\"2\", workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(updatelist))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(updatelist),\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, sub2.CommandName, \"update\"}, scenarios)\n}\n\nvar listListsString = strings.TrimSpace(`\nID           Name       Description    Type      Scope      Entries  Updated At                     Created At\nsomeListID   listName   NGWAFCLIList   wildcard  workspace  1.0.0.0  2021-06-15 23:00:00 +0000 UTC  2021-06-15 23:00:00 +0000 UTC\nsomeListID2  listName2  NGWAFCLIList2  wildcard  workspace  1.0.0.0  2021-06-15 23:00:00 +0000 UTC  2021-06-15 23:00:00 +0000 UTC\n`) + \"\\n\"\n\nvar zeroListString = strings.TrimSpace(`\nID  Name  Description  Type  Scope  Entries  Updated At  Created At\n`) + \"\\n\"\n\nvar listString = strings.TrimSpace(`\nID: someListID\nName: listName\nDescription: NGWAFCLIList\nType: wildcard\nEntries: 1.0.0.0\nScope: workspace\nUpdated (UTC): 2021-06-15 23:00\n`)\n"
  },
  {
    "path": "pkg/commands/ngwaf/workspace/workspace_test.go",
    "content": "package workspace_test\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\t\"testing\"\n\n\troot \"github.com/fastly/cli/pkg/commands/ngwaf\"\n\tsub \"github.com/fastly/cli/pkg/commands/ngwaf/workspace\"\n\tfstfmt \"github.com/fastly/cli/pkg/fmt\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/workspaces\"\n)\n\nconst (\n\tworkspaceDescription     = \"NGWAFCLIWorkspace\"\n\tworkspaceClientIPHeaders = \"these:are:headers\"\n\tworkspaceID              = \"someID\"\n\tworkspaceMode            = \"log\"\n\tworkspaceName            = \"CLIWorkspace\"\n)\n\nvar workspace = workspaces.Workspace{\n\tAttackSignalThresholds: workspaces.AttackSignalThresholds{\n\t\tImmediate:  false,\n\t\tOneMinute:  0,\n\t\tTenMinutes: 0,\n\t\tOneHour:    0,\n\t},\n\tClientIPHeaders: []string{\"these\", \"are\", \"headers\"},\n\tCreatedAt:       testutil.Date,\n\tDescription:     workspaceDescription,\n\tMode:            workspaceMode,\n\tName:            workspaceName,\n\tWorkspaceID:     workspaceID,\n}\n\nfunc TestWorkspacesCreate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --description flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--blockingMode %s --name %s\", workspaceMode, workspaceName),\n\t\t\tWantError: \"error parsing arguments: required flag --description not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --blockingMode flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--description %s --name %s\", workspaceDescription, workspaceName),\n\t\t\tWantError: \"error parsing arguments: required flag --blockingMode not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --name flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--description %s --blockingMode %s\", workspaceDescription, workspaceMode),\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate internal server error\",\n\t\t\tArgs: fmt.Sprintf(\"--description %s --name %s --blockingMode %s\", workspaceDescription, workspaceName, \"invalidMode\"),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusInternalServerError,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusInternalServerError),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"500 - Internal Server Error\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--description %s --name %s --blockingMode %s --clientIPHeaders %s\", workspaceDescription, workspaceName, workspaceMode, workspaceClientIPHeaders),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader((testutil.GenJSON(workspace)))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Created workspace '%s' (workspace-id: %s)\", workspaceName, workspaceID),\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--description %s --name %s --blockingMode %s --clientIPHeaders %s --json\", workspaceDescription, workspaceName, workspaceMode, workspaceClientIPHeaders),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(workspace))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(workspace),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"create\"}, scenarios)\n}\n\nfunc TestWorkspaceDelete(t *testing.T) {\n\tconst workspaceID = \"workspaceID\"\n\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --workspace-id flag\",\n\t\t\tArgs:      \"\",\n\t\t\tWantError: \"error parsing arguments: required flag --workspace-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate bad request\",\n\t\t\tArgs: \"--workspace-id bar\",\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusBadRequest,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusBadRequest),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(`\n\t\t\t\t\t\t\t{\n    \t\t\t\t\t\t\t\"title\": \"invalid Workspace ID\",\n    \t\t\t\t\t\t\t\"status\": 400\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t`))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"400 - Bad Request\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s\", workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusNoContent,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusNoContent),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Deleted workspace (id: %s)\", workspaceID),\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --json\", workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusNoContent,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusNoContent),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.JSON(`{\"id\": %q, \"deleted\": true}`, workspaceID),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"delete\"}, scenarios)\n}\n\nfunc TestWorkspaceGet(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --workspace-id flag\",\n\t\t\tArgs:      \"\",\n\t\t\tWantError: \"error parsing arguments: required flag --workspace-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate bad request\",\n\t\t\tArgs: \"--workspace-id baz\",\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusBadRequest,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusBadRequest),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(`\n\t\t\t\t\t\t\t{\n    \t\t\t\t\t\t\t\"title\": \"invalid Workspace ID\",\n    \t\t\t\t\t\t\t\"status\": 400\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t`))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"400 - Bad Request\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s\", workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader((testutil.GenJSON(workspace)))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: workspaceString,\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --json\", workspaceID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader((testutil.GenJSON(workspace)))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(workspace),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"get\"}, scenarios)\n}\n\nfunc TestWorkspaceList(t *testing.T) {\n\tworkspacesObject := workspaces.Workspaces{\n\t\tData: []workspaces.Workspace{\n\t\t\t{\n\t\t\t\tCreatedAt:   testutil.Date,\n\t\t\t\tDescription: workspaceDescription,\n\t\t\t\tMode:        workspaceMode,\n\t\t\t\tName:        workspaceName,\n\t\t\t\tWorkspaceID: workspaceID,\n\t\t\t},\n\t\t\t{\n\t\t\t\tCreatedAt:   testutil.Date,\n\t\t\t\tDescription: workspaceDescription,\n\t\t\t\tMode:        workspaceMode,\n\t\t\t\tName:        workspaceName + \"2\",\n\t\t\t\tWorkspaceID: workspaceID + \"2\",\n\t\t\t},\n\t\t},\n\t\tMeta: workspaces.MetaWorkspaces{},\n\t}\n\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName: \"validate internal server error\",\n\t\t\tArgs: \"\",\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusInternalServerError,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusInternalServerError),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"500 - Internal Server Error\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success (zero workspaces)\",\n\t\t\tArgs: \"\",\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(workspaces.Workspaces{\n\t\t\t\t\t\t\tData: []workspaces.Workspace{},\n\t\t\t\t\t\t\tMeta: workspaces.MetaWorkspaces{},\n\t\t\t\t\t\t}))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: zeroListWorkspaceString,\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: \"\",\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(workspacesObject))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: listWorkspaceString,\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: \"--json\",\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(workspacesObject))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(workspacesObject),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"list\"}, scenarios)\n}\n\nfunc TestWorkspaceUpdate(t *testing.T) {\n\tworkspacesObject := workspaces.Workspace{\n\t\tCreatedAt:   testutil.Date,\n\t\tDescription: workspaceDescription,\n\t\tMode:        workspaceMode,\n\t\tName:        workspaceName,\n\t\tWorkspaceID: workspaceID,\n\t}\n\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --workspace-id flag\",\n\t\t\tArgs:      \"\",\n\t\t\tWantError: \"error parsing arguments: required flag --workspace-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --description %s --name %s --blockingMode %s --clientIPHeaders %s\", workspaceID, workspaceDescription, workspaceName, workspaceMode, workspaceClientIPHeaders),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(workspacesObject))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Updated workspace '%s' (workspace-id: %s)\", workspaceName, workspaceID),\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--workspace-id %s --description %s --name %s --blockingMode %s --clientIPHeaders %s --json\", workspaceID, workspaceDescription, workspaceName, workspaceMode, workspaceClientIPHeaders),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(workspace))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(workspace),\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"update\"}, scenarios)\n}\n\nvar listWorkspaceString = strings.TrimSpace(`\nID       Name           Description        Mode  Created At\nsomeID   CLIWorkspace   NGWAFCLIWorkspace  log   2021-06-15 23:00:00 +0000 UTC\nsomeID2  CLIWorkspace2  NGWAFCLIWorkspace  log   2021-06-15 23:00:00 +0000 UTC\n`) + \"\\n\"\n\nvar zeroListWorkspaceString = strings.TrimSpace(`\nID  Name  Description  Mode  Created At\n`) + \"\\n\"\n\nvar workspaceString = strings.TrimSpace(`\nID: someID\nName: CLIWorkspace\nDescription: NGWAFCLIWorkspace\nMode: log\nAttack Signal Thresholds:\n\tImmediate: false\n\tOne Minute: 0\n\tTen Minutes: 0\n\tOne Hour: 0\nClient IP Headers: these, are, headers\nUpdated (UTC): 0001-01-01 00:00\n`)\n"
  },
  {
    "path": "pkg/commands/objectstorage/accesskeys/accesskeys_test.go",
    "content": "package accesskeys_test\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\t\"testing\"\n\n\troot \"github.com/fastly/cli/pkg/commands/objectstorage\"\n\tsub \"github.com/fastly/cli/pkg/commands/objectstorage/accesskeys\"\n\tfstfmt \"github.com/fastly/cli/pkg/fmt\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n\t\"github.com/fastly/go-fastly/v15/fastly/objectstorage/accesskeys\"\n)\n\nconst (\n\takID          = \"accessKeyId\"\n\takSecret      = \"accessKeySecret\"\n\takDescription = \"accessKeyDescription\"\n\takPermission  = \"read-only-objects\"\n\takBucket1     = \"bucket1\"\n\takBucket2     = \"bucket2\"\n)\n\nvar ak = accesskeys.AccessKey{\n\tAccessKeyID: akID,\n\tSecretKey:   akSecret,\n\tDescription: akDescription,\n\tPermission:  akPermission,\n\tBuckets:     []string{akBucket1, akBucket2},\n\tCreatedAt:   testutil.Date,\n}\n\nfunc TestAccessKeysCreate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --description flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--permission %s\", akPermission),\n\t\t\tWantError: \"error parsing arguments: required flag --description not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --permission flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--description %s\", akDescription),\n\t\t\tWantError: \"error parsing arguments: required flag --permission not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate internal server error\",\n\t\t\tArgs: fmt.Sprintf(\"--description %s --permission %s\", akDescription, akPermission),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusInternalServerError,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusInternalServerError),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"500 - Internal Server Error\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--description %s --permission %s --bucket %s --bucket %s\", akDescription, akPermission, akBucket1, akBucket2),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader((testutil.GenJSON(ak)))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Created access key (id: %s, secret: %s)\", akID, ak.SecretKey),\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--description %s --permission %s --json\", akDescription, akPermission),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(ak))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(ak),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"create\"}, scenarios)\n}\n\nfunc TestAccessKeysDelete(t *testing.T) {\n\tconst accessKeyID = \"accessKeyID\"\n\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --ak-id flag\",\n\t\t\tArgs:      \"\",\n\t\t\tWantError: \"error parsing arguments: required flag --ak-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate bad request\",\n\t\t\tArgs: \"--ak-id bar\",\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusBadRequest,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusBadRequest),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(`\n\t\t\t\t\t\t\t{\n    \t\t\t\t\t\t\t\"title\": \"invalid Access Key ID\",\n    \t\t\t\t\t\t\t\"status\": 400\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t`))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"400 - Bad Request\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--ak-id %s\", accessKeyID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusNoContent,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusNoContent),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.Success(\"Deleted access key (id: %s)\", accessKeyID),\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--ak-id %s --json\", accessKeyID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusNoContent,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusNoContent),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.JSON(`{\"id\": %q, \"deleted\": true}`, accessKeyID),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"delete\"}, scenarios)\n}\n\nfunc TestAccessKeysGet(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --ak-id flag\",\n\t\t\tArgs:      \"\",\n\t\t\tWantError: \"error parsing arguments: required flag --ak-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate bad request\",\n\t\t\tArgs: \"--ak-id baz\",\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusBadRequest,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusBadRequest),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(`\n\t\t\t\t\t\t\t{\n    \t\t\t\t\t\t\t\"title\": \"invalid Access Key ID\",\n    \t\t\t\t\t\t\t\"status\": 400\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t`))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"400 - Bad Request\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: fmt.Sprintf(\"--ak-id %s\", akID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader((testutil.GenJSON(ak)))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: akString,\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: fmt.Sprintf(\"--ak-id %s --json\", akID),\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader((testutil.GenJSON(ak)))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(ak),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"get\"}, scenarios)\n}\n\nfunc TestAccessKeysList(t *testing.T) {\n\tacesskeysobject := accesskeys.AccessKeys{\n\t\tData: []accesskeys.AccessKey{\n\t\t\t{\n\t\t\t\tAccessKeyID: \"foo\",\n\t\t\t\tSecretKey:   \"bar\",\n\t\t\t\tDescription: \"bat\",\n\t\t\t\tPermission:  akPermission,\n\t\t\t},\n\t\t\t{\n\t\t\t\tAccessKeyID: \"foobar\",\n\t\t\t\tSecretKey:   \"baz\",\n\t\t\t\tDescription: \"bizz\",\n\t\t\t\tPermission:  akPermission,\n\t\t\t\tBuckets:     []string{\"b1\", \"b2\"},\n\t\t\t},\n\t\t},\n\t\tMeta: accesskeys.MetaAccessKeys{},\n\t}\n\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName: \"validate internal server error\",\n\t\t\tArgs: \"\",\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusInternalServerError,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusInternalServerError),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"500 - Internal Server Error\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success (zero access keys)\",\n\t\t\tArgs: \"\",\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(accesskeys.AccessKeys{\n\t\t\t\t\t\t\tData: []accesskeys.AccessKey{},\n\t\t\t\t\t\t\tMeta: accesskeys.MetaAccessKeys{},\n\t\t\t\t\t\t}))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: zeroListAccessKeysString,\n\t\t},\n\t\t{\n\t\t\tName: \"validate API success\",\n\t\t\tArgs: \"\",\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(acesskeysobject))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: listAccessKeysString,\n\t\t},\n\t\t{\n\t\t\tName: \"validate optional --json flag\",\n\t\t\tArgs: \"--json\",\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(acesskeysobject))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: fstfmt.EncodeJSON(acesskeysobject),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"list-access-keys\"}, scenarios)\n}\n\nvar akString = strings.TrimSpace(`\nID: accessKeyId\nSecret: accessKeySecret\nDescription: accessKeyDescription\nPermission: read-only-objects\nBuckets: [bucket1 bucket2]\nCreated (UTC): 2021-06-15 23:00\n`) + \"\\n\"\n\nvar listAccessKeysString = strings.TrimSpace(`\nID      Secret  Description  Permission         Buckets  Created At\nfoo     bar     bat          read-only-objects  all      0001-01-01 00:00:00 +0000 UTC\nfoobar  baz     bizz         read-only-objects  [b1 b2]  0001-01-01 00:00:00 +0000 UTC\n`) + \"\\n\"\n\nvar zeroListAccessKeysString = strings.TrimSpace(`\nID  Secret  Description  Permission  Buckets  Created At\n`) + \"\\n\"\n"
  },
  {
    "path": "pkg/commands/objectstorage/accesskeys/create.go",
    "content": "package accesskeys\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly/objectstorage/accesskeys\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// CreateCommand calls the Fastly API to create an access key.\ntype CreateCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tdescription string\n\tpermission  string\n\n\t// Optional.\n\tbuckets []string\n}\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\n\tc.CmdClause = parent.Command(\"create\", \"Create an access key\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"description\", \"Description of the access key\").Required().StringVar(&c.description)\n\tc.CmdClause.Flag(\"permission\", \"Permissions to be given to the access key (read-write-admin, read-only-admin, read-write-objects, read-only-objects)\").Required().StringVar(&c.permission)\n\n\t// Optional.\n\tc.CmdClause.Flag(\"bucket\", \"Bucket to be associated with the access key. Set flag multiple times to include multiple buckets. If omitted, all buckets are associated\").StringsVar(&c.buckets)\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *CreateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tfc, ok := c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\taccessKey, err := accesskeys.Create(context.TODO(), fc, &accesskeys.CreateInput{\n\t\tDescription: &c.description,\n\t\tPermission:  &c.permission,\n\t\tBuckets:     &c.buckets,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, accessKey); ok {\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Created access key (id: %s, secret: %s)\", accessKey.AccessKeyID, accessKey.SecretKey)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/objectstorage/accesskeys/delete.go",
    "content": "package accesskeys\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly/objectstorage/accesskeys\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DeleteCommand calls the Fastly API to delete an access key.\ntype DeleteCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\tid string\n}\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\n\tc.CmdClause = parent.Command(\"delete\", \"Delete an access key\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"ak-id\", \"Access key ID\").Required().StringVar(&c.id)\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DeleteCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tfc, ok := c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\terr := accesskeys.Delete(context.TODO(), fc, &accesskeys.DeleteInput{\n\t\tAccessKeyID: &c.id,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif c.JSONOutput.Enabled {\n\t\to := struct {\n\t\t\tID      string `json:\"id\"`\n\t\t\tDeleted bool   `json:\"deleted\"`\n\t\t}{\n\t\t\tc.id,\n\t\t\ttrue,\n\t\t}\n\t\t_, err := c.WriteJSON(out, o)\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Deleted access key (id: %s)\", c.id)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/objectstorage/accesskeys/doc.go",
    "content": "// Package accesskeys contains commands to inspect and manipulate access keys.\npackage accesskeys\n"
  },
  {
    "path": "pkg/commands/objectstorage/accesskeys/get.go",
    "content": "package accesskeys\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly/objectstorage/accesskeys\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// GetCommand calls the Fastly API to get an access key.\ntype GetCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// Required.\n\taccessKeyID string\n}\n\n// NewGetCommand returns a usable command registered under the parent.\nfunc NewGetCommand(parent argparser.Registerer, g *global.Data) *GetCommand {\n\tc := GetCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\n\tc.CmdClause = parent.Command(\"get\", \"Get an access key\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"ak-id\", \"Access key ID\").Required().StringVar(&c.accessKeyID)\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *GetCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tfc, ok := c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\taccessKey, err := accesskeys.Get(context.TODO(), fc, &accesskeys.GetInput{\n\t\tAccessKeyID: &c.accessKeyID,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, accessKey); ok {\n\t\treturn err\n\t}\n\n\ttext.PrintAccessKey(out, accessKey)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/objectstorage/accesskeys/list.go",
    "content": "package accesskeys\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly/objectstorage/accesskeys\"\n)\n\n// ListCommand calls the Fastly API to list all access keys.\ntype ListCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n}\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\n\tc.CmdClause = parent.Command(\"list\", \"List all access keys\").Alias(\"list-access-keys\")\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag())\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *ListCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tfc, ok := c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\taccessKeys, err := accesskeys.ListAccessKeys(context.TODO(), fc)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, accessKeys); ok {\n\t\treturn err\n\t}\n\n\ttext.PrintAccessKeyTbl(out, accessKeys.Data)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/objectstorage/accesskeys/root.go",
    "content": "package accesskeys\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"access-keys\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manipulate Fastly access keys\")\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/objectstorage/doc.go",
    "content": "// Package objectstorage contains commands to inspect and manipulate stored objects.\npackage objectstorage\n"
  },
  {
    "path": "pkg/commands/objectstorage/root.go",
    "content": "package objectstorage\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"object-storage\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manage object storage\")\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/pop/doc.go",
    "content": "// Package pop contains commands to inspect and manipulate Fastly POP data.\npackage pop\n"
  },
  {
    "path": "pkg/commands/pop/pop_test.go",
    "content": "package pop_test\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"io\"\n\t\"testing\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/app\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/mock\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n)\n\nfunc TestAllDatacenters(t *testing.T) {\n\tvar stdout bytes.Buffer\n\targs := testutil.SplitArgs(\"pops\")\n\tapi := mock.API{\n\t\tAllDatacentersFn: func(_ context.Context) ([]fastly.Datacenter, error) {\n\t\t\treturn []fastly.Datacenter{\n\t\t\t\t{\n\t\t\t\t\tName:   fastly.ToPointer(\"Foobar\"),\n\t\t\t\t\tCode:   fastly.ToPointer(\"FBR\"),\n\t\t\t\t\tGroup:  fastly.ToPointer(\"Bar\"),\n\t\t\t\t\tShield: fastly.ToPointer(\"Baz\"),\n\t\t\t\t\tCoordinates: &fastly.Coordinates{\n\t\t\t\t\t\tLatitude:  fastly.ToPointer(float64(1)),\n\t\t\t\t\t\tLongitude: fastly.ToPointer(float64(2)),\n\t\t\t\t\t\tX:         fastly.ToPointer(float64(3)),\n\t\t\t\t\t\tY:         fastly.ToPointer(float64(4)),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}, nil\n\t\t},\n\t}\n\tapp.Init = func(_ []string, _ io.Reader) (*global.Data, error) {\n\t\topts := testutil.MockGlobalData(args, &stdout)\n\t\topts.APIClientFactory = mock.APIClient(api)\n\t\treturn opts, nil\n\t}\n\terr := app.Run(args, nil)\n\ttestutil.AssertNoError(t, err)\n\ttestutil.AssertString(t, \"\\nNAME    CODE  GROUP  SHIELD  COORDINATES\\nFoobar  FBR   Bar    Baz     {Latitude:1 Longitude:2 X:3 Y:4}\\n\", stdout.String())\n}\n"
  },
  {
    "path": "pkg/commands/pop/root.go",
    "content": "package pop\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"pops\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"List Fastly datacenters\")\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, out io.Writer) error {\n\tdcs, err := c.Globals.APIClient.AllDatacenters(context.TODO())\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\ttext.Break(out)\n\tt := text.NewTable(out)\n\tt.AddHeader(\"NAME\", \"CODE\", \"GROUP\", \"SHIELD\", \"COORDINATES\")\n\tfor _, dc := range dcs {\n\t\tt.AddLine(\n\t\t\tfastly.ToValue(dc.Name),\n\t\t\tfastly.ToValue(dc.Code),\n\t\t\tfastly.ToValue(dc.Group),\n\t\t\tfastly.ToValue(dc.Shield),\n\t\t\tCoordinates(dc.Coordinates),\n\t\t)\n\t}\n\tt.Print()\n\treturn nil\n}\n\n// Coordinates returns a stringified object of coordinate data.\nfunc Coordinates(c *fastly.Coordinates) string {\n\tif c != nil {\n\t\treturn fmt.Sprintf(\n\t\t\t`{Latitude:%v Longitude:%v X:%v Y:%v}`,\n\t\t\tfastly.ToValue(c.Latitude),\n\t\t\tfastly.ToValue(c.Longitude),\n\t\t\tfastly.ToValue(c.X),\n\t\t\tfastly.ToValue(c.Y),\n\t\t)\n\t}\n\treturn \"\"\n}\n"
  },
  {
    "path": "pkg/commands/products/doc.go",
    "content": "// Package products contains commands to inspect and manipulate Fastly products.\npackage products\n"
  },
  {
    "path": "pkg/commands/products/products_test.go",
    "content": "package products_test\n\nimport (\n\t\"testing\"\n\n\troot \"github.com/fastly/cli/pkg/commands/products\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n)\n\nfunc TestProductEnablement(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing Service ID\",\n\t\t\tEnvVars:   map[string]string{\"FASTLY_SERVICE_ID\": \"\"},\n\t\t\tWantError: \"failed to identify Service ID: error reading service: no service ID found\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate invalid enable/disable flag combo\",\n\t\t\tArgs:      \"--enable fanout --disable fanout\",\n\t\t\tWantError: \"invalid flag combination: --enable and --disable\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate flag parsing error for enabling product\",\n\t\t\tArgs:      \"--service-id 123 --enable foo\",\n\t\t\tWantError: \"error parsing arguments: enum value must be one of api_discovery,bot_management,brotli_compression,domain_inspector,fanout,image_optimizer,log_explorer_insights,origin_inspector,websockets, got 'foo'\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate flag parsing error for disabling product\",\n\t\t\tArgs:      \"--service-id 123 --disable foo\",\n\t\t\tWantError: \"error parsing arguments: enum value must be one of api_discovery,bot_management,brotli_compression,domain_inspector,fanout,image_optimizer,log_explorer_insights,origin_inspector,websockets, got 'foo'\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate invalid json/verbose flag combo\",\n\t\t\tArgs:      \"--service-id 123 --json --verbose\",\n\t\t\tWantError: \"invalid flag combination, --verbose and --json\",\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName}, scenarios)\n}\n"
  },
  {
    "path": "pkg/commands/products/root.go",
    "content": "package products\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n\t\"github.com/fastly/go-fastly/v15/fastly/products/apidiscovery\"\n\t\"github.com/fastly/go-fastly/v15/fastly/products/botmanagement\"\n\t\"github.com/fastly/go-fastly/v15/fastly/products/brotlicompression\"\n\t\"github.com/fastly/go-fastly/v15/fastly/products/domaininspector\"\n\t\"github.com/fastly/go-fastly/v15/fastly/products/fanout\"\n\t\"github.com/fastly/go-fastly/v15/fastly/products/imageoptimizer\"\n\t\"github.com/fastly/go-fastly/v15/fastly/products/logexplorerinsights\"\n\t\"github.com/fastly/go-fastly/v15/fastly/products/origininspector\"\n\t\"github.com/fastly/go-fastly/v15/fastly/products/websockets\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tdisableProduct string\n\tenableProduct  string\n\tserviceName    argparser.OptionalServiceNameID\n}\n\n// ProductEnablementOptions is a list of products that can be enabled/disabled.\nvar ProductEnablementOptions = []string{\n\t\"api_discovery\",\n\t\"bot_management\",\n\t\"brotli_compression\",\n\t\"domain_inspector\",\n\t\"fanout\",\n\t\"image_optimizer\",\n\t\"log_explorer_insights\",\n\t\"origin_inspector\",\n\t\"websockets\",\n}\n\n// ProductStatus indicates the status for each product.\ntype ProductStatus struct {\n\tAPIDiscovery        bool `json:\"api_discovery\"`\n\tBotManagement       bool `json:\"bot_management\"`\n\tBrotliCompression   bool `json:\"brotli_compression\"`\n\tDomainInspector     bool `json:\"domain_inspector\"`\n\tFanout              bool `json:\"fanout\"`\n\tImageOptimizer      bool `json:\"image_optimizer\"`\n\tLogExplorerInsights bool `json:\"log_explorer_insights\"`\n\tOriginInspector     bool `json:\"origin_inspector\"`\n\tWebSockets          bool `json:\"websockets\"`\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"products\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Enable, disable, and check the enablement status of products\")\n\n\t// Optional.\n\tc.CmdClause.Flag(\"disable\", \"Disable product\").HintOptions(ProductEnablementOptions...).EnumVar(&c.disableProduct, ProductEnablementOptions...)\n\tc.CmdClause.Flag(\"enable\", \"Enable product\").HintOptions(ProductEnablementOptions...).EnumVar(&c.enableProduct, ProductEnablementOptions...)\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.enableProduct != \"\" && c.disableProduct != \"\" {\n\t\treturn fsterr.ErrInvalidEnableDisableFlagCombo\n\t}\n\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tserviceID, _, _, err := argparser.ServiceID(c.serviceName, *c.Globals.Manifest, c.Globals.APIClient, c.Globals.ErrLog)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to identify Service ID: %w\", err)\n\t}\n\n\tac, ok := c.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to convert interface to a fastly client\")\n\t}\n\n\tif c.enableProduct != \"\" {\n\t\tswitch c.enableProduct {\n\t\tcase \"api_discovery\":\n\t\t\t_, err = apidiscovery.Enable(context.TODO(), ac, serviceID)\n\t\tcase \"bot_management\":\n\t\t\t_, err = botmanagement.Enable(context.TODO(), ac, serviceID)\n\t\tcase \"brotli_compression\":\n\t\t\t_, err = brotlicompression.Enable(context.TODO(), ac, serviceID)\n\t\tcase \"domain_inspector\":\n\t\t\t_, err = domaininspector.Enable(context.TODO(), ac, serviceID)\n\t\tcase \"fanout\":\n\t\t\t_, err = fanout.Enable(context.TODO(), ac, serviceID)\n\t\tcase \"image_optimizer\":\n\t\t\t_, err = imageoptimizer.Enable(context.TODO(), ac, serviceID)\n\t\tcase \"log_explorer_insights\":\n\t\t\t_, err = logexplorerinsights.Enable(context.TODO(), ac, serviceID)\n\t\tcase \"origin_inspector\":\n\t\t\t_, err = origininspector.Enable(context.TODO(), ac, serviceID)\n\t\tcase \"websockets\":\n\t\t\t_, err = websockets.Enable(context.TODO(), ac, serviceID)\n\t\tdefault:\n\t\t\treturn errors.New(\"unrecognised product\")\n\t\t}\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to enable product '%s': %w\", c.enableProduct, err)\n\t\t}\n\t\ttext.Success(out, \"Successfully enabled product '%s'\", c.enableProduct)\n\t\treturn nil\n\t}\n\n\tif c.disableProduct != \"\" {\n\t\tswitch c.disableProduct {\n\t\tcase \"api_discovery\":\n\t\t\terr = apidiscovery.Disable(context.TODO(), ac, serviceID)\n\t\tcase \"bot_management\":\n\t\t\terr = botmanagement.Disable(context.TODO(), ac, serviceID)\n\t\tcase \"brotli_compression\":\n\t\t\terr = brotlicompression.Disable(context.TODO(), ac, serviceID)\n\t\tcase \"domain_inspector\":\n\t\t\terr = domaininspector.Disable(context.TODO(), ac, serviceID)\n\t\tcase \"fanout\":\n\t\t\terr = fanout.Disable(context.TODO(), ac, serviceID)\n\t\tcase \"image_optimizer\":\n\t\t\terr = imageoptimizer.Disable(context.TODO(), ac, serviceID)\n\t\tcase \"log_explorer_insights\":\n\t\t\terr = logexplorerinsights.Disable(context.TODO(), ac, serviceID)\n\t\tcase \"origin_inspector\":\n\t\t\terr = origininspector.Disable(context.TODO(), ac, serviceID)\n\t\tcase \"websockets\":\n\t\t\terr = websockets.Disable(context.TODO(), ac, serviceID)\n\t\tdefault:\n\t\t\treturn errors.New(\"unrecognised product\")\n\t\t}\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to disable product '%s': %w\", c.disableProduct, err)\n\t\t}\n\t\ttext.Success(out, \"Successfully disabled product '%s'\", c.disableProduct)\n\t\treturn nil\n\t}\n\n\tps := ProductStatus{}\n\n\tif _, err = apidiscovery.Get(context.TODO(), ac, serviceID); err == nil {\n\t\tps.APIDiscovery = true\n\t}\n\tif _, err = botmanagement.Get(context.TODO(), ac, serviceID); err == nil {\n\t\tps.BotManagement = true\n\t}\n\tif _, err = brotlicompression.Get(context.TODO(), ac, serviceID); err == nil {\n\t\tps.BrotliCompression = true\n\t}\n\tif _, err = domaininspector.Get(context.TODO(), ac, serviceID); err == nil {\n\t\tps.DomainInspector = true\n\t}\n\tif _, err = fanout.Get(context.TODO(), ac, serviceID); err == nil {\n\t\tps.Fanout = true\n\t}\n\tif _, err = imageoptimizer.Get(context.TODO(), ac, serviceID); err == nil {\n\t\tps.ImageOptimizer = true\n\t}\n\tif _, err = logexplorerinsights.Get(context.TODO(), ac, serviceID); err == nil {\n\t\tps.LogExplorerInsights = true\n\t}\n\tif _, err = origininspector.Get(context.TODO(), ac, serviceID); err == nil {\n\t\tps.OriginInspector = true\n\t}\n\tif _, err = websockets.Get(context.TODO(), ac, serviceID); err == nil {\n\t\tps.WebSockets = true\n\t}\n\n\tif ok, err := c.WriteJSON(out, ps); ok {\n\t\treturn err\n\t}\n\n\tt := text.NewTable(out)\n\tt.AddHeader(\"PRODUCT\", \"ENABLED\")\n\tt.AddLine(\"API Discovery\", ps.APIDiscovery)\n\tt.AddLine(\"Bot Management\", ps.BotManagement)\n\tt.AddLine(\"Brotli Compression\", ps.BrotliCompression)\n\tt.AddLine(\"Domain Inspector\", ps.DomainInspector)\n\tt.AddLine(\"Fanout\", ps.Fanout)\n\tt.AddLine(\"Image Optimizer\", ps.ImageOptimizer)\n\tt.AddLine(\"Log Explorer & Insights\", ps.LogExplorerInsights)\n\tt.AddLine(\"Origin Inspector\", ps.OriginInspector)\n\tt.AddLine(\"WebSockets\", ps.WebSockets)\n\tt.Print()\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/profile/create.go",
    "content": "package profile\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"io/fs\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tauthcmd \"github.com/fastly/cli/pkg/commands/auth\"\n\t\"github.com/fastly/cli/pkg/config\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// CreateCommand represents a Kingpin command.\ntype CreateCommand struct {\n\targparser.Base\n\n\tprofile string\n\tsso     bool\n}\n\n// NewCreateCommand returns a new command registered in the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tvar c CreateCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(\"create\", \"Create user profile (deprecated: use 'fastly auth login' or 'fastly auth add' instead)\")\n\tc.CmdClause.Arg(\"profile\", \"Profile to create (default 'user')\").Default(\"user\").Short('p').StringVar(&c.profile)\n\tc.CmdClause.Flag(\"sso\", \"Create an SSO-based token\").BoolVar(&c.sso)\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *CreateCommand) Exec(in io.Reader, out io.Writer) (err error) {\n\tif !c.Globals.Flags.Quiet {\n\t\ttext.Deprecated(\"This command will be removed in a future release. Use 'fastly auth login' or 'fastly auth add' instead.\\n\\n\")\n\t}\n\n\tif c.Globals.Verbose() {\n\t\ttext.Break(out)\n\t}\n\ttext.Output(out, \"Creating profile '%s'\", c.profile)\n\n\tif c.Globals.Config.GetAuthToken(c.profile) != nil {\n\t\treturn fsterr.RemediationError{\n\t\t\tInner:       fmt.Errorf(\"profile '%s' already exists\", c.profile),\n\t\t\tRemediation: \"Re-run the command and pass a different value for the 'profile' argument.\",\n\t\t}\n\t}\n\n\tmakeDefault := true\n\tif name, _ := c.Globals.Config.GetDefaultAuthToken(); name != \"\" && !c.Globals.Flags.AutoYes && !c.Globals.Flags.NonInteractive {\n\t\tmakeDefault, err = c.promptForDefault(in, out)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\ttext.Break(out)\n\n\tif c.sso {\n\t\tif err := authcmd.RunSSOWithTokenName(in, out, c.Globals, false, false, c.profile); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to authenticate: %w\", err)\n\t\t}\n\t\tif makeDefault {\n\t\t\tc.Globals.Config.Auth.Default = c.profile\n\t\t}\n\t\ttext.Break(out)\n\t} else {\n\t\tif err := c.staticTokenFlow(makeDefault, in, out); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tif err := c.persistCfg(); err != nil {\n\t\treturn err\n\t}\n\n\tdisplayCfgPath(c.Globals.ConfigPath, out)\n\ttext.Success(out, \"Profile '%s' created\", c.profile)\n\treturn nil\n}\n\nfunc (c *CreateCommand) staticTokenFlow(makeDefault bool, in io.Reader, out io.Writer) error {\n\ttoken, err := promptForToken(in, out, c.Globals.ErrLog)\n\tif err != nil {\n\t\treturn err\n\t}\n\ttext.Break(out)\n\n\tspinner, err := text.NewSpinner(out)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tdefer func() {\n\t\tif err != nil {\n\t\t\tc.Globals.ErrLog.Add(err)\n\t\t}\n\t}()\n\n\tvar md *authcmd.TokenMetadata\n\terr = spinner.Process(\"Validating token\", func(_ *text.SpinnerWrapper) error {\n\t\tmd, err = authcmd.FetchTokenMetadata(c.Globals, token)\n\t\treturn err\n\t})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn spinner.Process(\"Persisting configuration\", func(_ *text.SpinnerWrapper) error {\n\t\tauthcmd.BuildAndStoreStaticToken(c.Globals, token, c.profile, md, makeDefault)\n\t\treturn nil\n\t})\n}\n\nfunc promptForToken(in io.Reader, out io.Writer, errLog fsterr.LogInterface) (string, error) {\n\ttext.Output(out, \"An API token is used to authenticate requests to the Fastly API. To create a token, visit https://manage.fastly.com/account/personal/tokens\\n\\n\")\n\ttoken, err := text.InputSecure(out, text.Prompt(\"Fastly API token: \"), in, validateTokenNotEmpty)\n\tif err != nil {\n\t\terrLog.Add(err)\n\t\treturn \"\", err\n\t}\n\ttext.Break(out)\n\treturn token, nil\n}\n\nfunc validateTokenNotEmpty(s string) error {\n\tif s == \"\" {\n\t\treturn ErrEmptyToken\n\t}\n\treturn nil\n}\n\n// ErrEmptyToken is returned when a user tries to supply an empty string as a\n// token in the terminal prompt.\nvar ErrEmptyToken = errors.New(\"token cannot be empty\")\n\nfunc (c *CreateCommand) persistCfg() error {\n\tdir := filepath.Dir(c.Globals.ConfigPath)\n\tfi, err := os.Stat(dir)\n\tswitch {\n\tcase err == nil && !fi.IsDir():\n\t\treturn fmt.Errorf(\"config file path %s isn't a directory\", dir)\n\tcase err != nil && errors.Is(err, fs.ErrNotExist):\n\t\tif err := os.MkdirAll(dir, config.DirectoryPermissions); err != nil {\n\t\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\t\"Directory\":   dir,\n\t\t\t\t\"Permissions\": config.DirectoryPermissions,\n\t\t\t})\n\t\t\treturn fmt.Errorf(\"error creating config file directory: %w\", err)\n\t\t}\n\t}\n\n\tif err := c.Globals.Config.Write(c.Globals.ConfigPath); err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn fmt.Errorf(\"error saving config file: %w\", err)\n\t}\n\n\treturn nil\n}\n\nfunc displayCfgPath(path string, out io.Writer) {\n\tfilePath := strings.ReplaceAll(path, \" \", `\\ `)\n\ttext.Break(out)\n\ttext.Description(out, \"You can find your configuration file at\", filePath)\n}\n\nfunc (c *CreateCommand) promptForDefault(in io.Reader, out io.Writer) (bool, error) {\n\tcont, err := text.AskYesNo(out, \"\\nSet this profile to be your default? [y/N] \", in)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn false, err\n\t}\n\treturn cont, nil\n}\n"
  },
  {
    "path": "pkg/commands/profile/delete.go",
    "content": "package profile\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DeleteCommand represents a Kingpin command.\ntype DeleteCommand struct {\n\targparser.Base\n\n\tprofile string\n}\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tvar c DeleteCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(\"delete\", \"Delete user profile (deprecated: use 'fastly auth delete' instead)\")\n\tc.CmdClause.Arg(\"profile\", \"Profile to delete\").Short('x').Required().StringVar(&c.profile)\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DeleteCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif !c.Globals.Flags.Quiet {\n\t\ttext.Deprecated(\"This command will be removed in a future release. Use 'fastly auth delete' instead.\\n\\n\")\n\t}\n\n\tif !c.Globals.Config.DeleteAuthToken(c.profile) {\n\t\treturn fmt.Errorf(\"the specified profile does not exist\")\n\t}\n\n\tif err := c.Globals.Config.Write(c.Globals.ConfigPath); err != nil {\n\t\treturn err\n\t}\n\tif c.Globals.Verbose() {\n\t\ttext.Break(out)\n\t}\n\ttext.Success(out, \"Profile '%s' deleted\", c.profile)\n\n\tif c.Globals.Config.Auth.Default == \"\" && len(c.Globals.Config.Auth.Tokens) > 0 {\n\t\ttext.Break(out)\n\t\ttext.Warning(out, \"At least one account profile should be set as the 'default'. Run `fastly profile update <NAME>` and ensure the profile is set to be the default.\")\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/profile/doc.go",
    "content": "// Package profile contains commands to manage user profiles.\npackage profile\n"
  },
  {
    "path": "pkg/commands/profile/list.go",
    "content": "package profile\n\nimport (\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/config\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// ListCommand represents a Kingpin command.\ntype ListCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n}\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tvar c ListCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(\"list\", \"List user profiles (deprecated: use 'fastly auth list' instead)\")\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *ListCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif !c.Globals.Flags.Quiet && !c.JSONOutput.Enabled {\n\t\ttext.Deprecated(\"This command will be removed in a future release. Use 'fastly auth list' instead.\\n\\n\")\n\t}\n\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tif ok, err := c.WriteJSON(out, c.Globals.Config.Auth.Tokens); ok {\n\t\treturn err\n\t}\n\n\tif len(c.Globals.Config.Auth.Tokens) == 0 {\n\t\tmsg := \"no profiles available\"\n\t\treturn fsterr.RemediationError{\n\t\t\tInner:       errors.New(msg),\n\t\t\tRemediation: fsterr.ProfileRemediation(),\n\t\t}\n\t}\n\n\tdefaultName := c.Globals.Config.Auth.Default\n\n\tif defaultName != \"\" {\n\t\tif at := c.Globals.Config.Auth.Tokens[defaultName]; at != nil {\n\t\t\tif c.Globals.Verbose() {\n\t\t\t\ttext.Break(out)\n\t\t\t}\n\t\t\ttext.Info(out, \"Default profile highlighted in red.\\n\\n\")\n\t\t\tdisplay(defaultName, at, true, out, text.BoldRed)\n\t\t}\n\t}\n\n\tfor name, at := range c.Globals.Config.Auth.Tokens {\n\t\tif name != defaultName {\n\t\t\ttext.Break(out)\n\t\t\tdisplay(name, at, false, out, text.Bold)\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc display(name string, at *config.AuthToken, isDefault bool, out io.Writer, style func(a ...any) string) {\n\ttext.Output(out, style(name))\n\ttext.Break(out)\n\ttext.Output(out, \"%s: %t\", style(\"Default\"), isDefault)\n\ttext.Output(out, \"%s: %s\", style(\"Email\"), at.Email)\n\ttext.Output(out, \"%s: %s\", style(\"Token\"), at.Token)\n\tisSSO := at.Type == config.AuthTokenTypeSSO\n\ttext.Output(out, \"%s: %t\", style(\"SSO\"), isSSO)\n\tif isSSO {\n\t\ttext.Output(out, \"%s: %s\", style(\"Account ID\"), at.AccountID)\n\t\ttext.Output(out, \"%s: %s\", style(\"Label\"), at.Label)\n\t}\n}\n"
  },
  {
    "path": "pkg/commands/profile/profile_test.go",
    "content": "package profile_test\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"path/filepath\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\troot \"github.com/fastly/cli/pkg/commands/profile\"\n\t\"github.com/fastly/cli/pkg/config\"\n\t\"github.com/fastly/cli/pkg/mock\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n\tfsttime \"github.com/fastly/cli/pkg/time\"\n)\n\nfunc TestProfileCreate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName: \"validate profile creation works\",\n\t\t\tArgs: \"foo\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetCurrentUserFn: getCurrentUser,\n\t\t\t\tGetTokenSelfFn:   getToken,\n\t\t\t},\n\t\t\tStdin: []string{\"some_token\"},\n\t\t\tEnv: &testutil.EnvConfig{\n\t\t\t\tOpts: &testutil.EnvOpts{\n\t\t\t\t\tCopy: []testutil.FileIO{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tSrc: filepath.Join(\"testdata\", \"config.toml\"),\n\t\t\t\t\t\t\tDst: \"config.toml\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tEditScenario: func(scenario *testutil.CLIScenario, rootdir string) {\n\t\t\t\t\tscenario.ConfigPath = filepath.Join(rootdir, \"config.toml\")\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutputs: []string{\n\t\t\t\t\"Fastly API token:\",\n\t\t\t\t\"Validating token\",\n\t\t\t\t\"Persisting configuration\",\n\t\t\t\t\"Profile 'foo' created\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tName: \"validate profile duplication\",\n\t\t\tArgs: \"foo\",\n\t\t\tEnv: &testutil.EnvConfig{\n\t\t\t\tOpts: &testutil.EnvOpts{\n\t\t\t\t\tCopy: []testutil.FileIO{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tSrc: filepath.Join(\"testdata\", \"config.toml\"),\n\t\t\t\t\t\t\tDst: \"config.toml\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tEditScenario: func(scenario *testutil.CLIScenario, rootdir string) {\n\t\t\t\t\tscenario.ConfigPath = filepath.Join(rootdir, \"config.toml\")\n\t\t\t\t},\n\t\t\t},\n\t\t\tConfigFile: &config.File{\n\t\t\t\tAuth: config.Auth{\n\t\t\t\t\tDefault: \"foo\",\n\t\t\t\t\tTokens: config.AuthTokens{\n\t\t\t\t\t\t\"foo\": &config.AuthToken{\n\t\t\t\t\t\t\tType:  config.AuthTokenTypeStatic,\n\t\t\t\t\t\t\tToken: \"123\",\n\t\t\t\t\t\t\tEmail: \"foo@example.com\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"profile 'foo' already exists\",\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, \"create\"}, scenarios)\n}\n\nfunc TestProfileDelete(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName: \"validate profile deletion works\",\n\t\t\tArgs: \"foo\",\n\t\t\tEnv: &testutil.EnvConfig{\n\t\t\t\tOpts: &testutil.EnvOpts{\n\t\t\t\t\tCopy: []testutil.FileIO{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tSrc: filepath.Join(\"testdata\", \"config.toml\"),\n\t\t\t\t\t\t\tDst: \"config.toml\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tEditScenario: func(scenario *testutil.CLIScenario, rootdir string) {\n\t\t\t\t\tscenario.ConfigPath = filepath.Join(rootdir, \"config.toml\")\n\t\t\t\t},\n\t\t\t},\n\t\t\tConfigFile: &config.File{\n\t\t\t\tAuth: config.Auth{\n\t\t\t\t\tDefault: \"foo\",\n\t\t\t\t\tTokens: config.AuthTokens{\n\t\t\t\t\t\t\"foo\": &config.AuthToken{\n\t\t\t\t\t\t\tType:  config.AuthTokenTypeStatic,\n\t\t\t\t\t\t\tToken: \"123\",\n\t\t\t\t\t\t\tEmail: \"foo@example.com\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: \"Profile 'foo' deleted\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate incorrect profile\",\n\t\t\tArgs: \"unknown\",\n\t\t\tEnv: &testutil.EnvConfig{\n\t\t\t\tOpts: &testutil.EnvOpts{\n\t\t\t\t\tCopy: []testutil.FileIO{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tSrc: filepath.Join(\"testdata\", \"config.toml\"),\n\t\t\t\t\t\t\tDst: \"config.toml\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tEditScenario: func(scenario *testutil.CLIScenario, rootdir string) {\n\t\t\t\t\tscenario.ConfigPath = filepath.Join(rootdir, \"config.toml\")\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"the specified profile does not exist\",\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, \"delete\"}, scenarios)\n}\n\nfunc TestProfileList(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName: \"validate listing profiles works\",\n\t\t\tEnv: &testutil.EnvConfig{\n\t\t\t\tOpts: &testutil.EnvOpts{\n\t\t\t\t\tCopy: []testutil.FileIO{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tSrc: filepath.Join(\"testdata\", \"config.toml\"),\n\t\t\t\t\t\t\tDst: \"config.toml\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tEditScenario: func(scenario *testutil.CLIScenario, rootdir string) {\n\t\t\t\t\tscenario.ConfigPath = filepath.Join(rootdir, \"config.toml\")\n\t\t\t\t},\n\t\t\t},\n\t\t\tConfigFile: &config.File{\n\t\t\t\tAuth: config.Auth{\n\t\t\t\t\tDefault: \"foo\",\n\t\t\t\t\tTokens: config.AuthTokens{\n\t\t\t\t\t\t\"foo\": &config.AuthToken{\n\t\t\t\t\t\t\tType:  config.AuthTokenTypeStatic,\n\t\t\t\t\t\t\tToken: \"123\",\n\t\t\t\t\t\t\tEmail: \"foo@example.com\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"bar\": &config.AuthToken{\n\t\t\t\t\t\t\tType:  config.AuthTokenTypeStatic,\n\t\t\t\t\t\t\tToken: \"456\",\n\t\t\t\t\t\t\tEmail: \"bar@example.com\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutputs: []string{\n\t\t\t\t\"Default profile highlighted in red.\",\n\t\t\t\t\"foo\\n\\nDefault: true\\nEmail: foo@example.com\\nToken: 123\",\n\t\t\t\t\"bar\\n\\nDefault: false\\nEmail: bar@example.com\\nToken: 456\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tName: \"validate no profiles defined\",\n\t\t\tEnv: &testutil.EnvConfig{\n\t\t\t\tOpts: &testutil.EnvOpts{\n\t\t\t\t\tCopy: []testutil.FileIO{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tSrc: filepath.Join(\"testdata\", \"config.toml\"),\n\t\t\t\t\t\t\tDst: \"config.toml\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tEditScenario: func(scenario *testutil.CLIScenario, rootdir string) {\n\t\t\t\t\tscenario.ConfigPath = filepath.Join(rootdir, \"config.toml\")\n\t\t\t\t},\n\t\t\t},\n\t\t\tConfigFile: &config.File{},\n\t\t\tWantError:  \"no profiles available\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate listing profiles with --verbose and --json causes an error\",\n\t\t\tArgs: \"--verbose --json\",\n\t\t\tEnv: &testutil.EnvConfig{\n\t\t\t\tOpts: &testutil.EnvOpts{\n\t\t\t\t\tCopy: []testutil.FileIO{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tSrc: filepath.Join(\"testdata\", \"config.toml\"),\n\t\t\t\t\t\t\tDst: \"config.toml\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tEditScenario: func(scenario *testutil.CLIScenario, rootdir string) {\n\t\t\t\t\tscenario.ConfigPath = filepath.Join(rootdir, \"config.toml\")\n\t\t\t\t},\n\t\t\t},\n\t\t\tConfigFile: &config.File{\n\t\t\t\tAuth: config.Auth{\n\t\t\t\t\tTokens: config.AuthTokens{\n\t\t\t\t\t\t\"foo\": &config.AuthToken{\n\t\t\t\t\t\t\tType:  config.AuthTokenTypeStatic,\n\t\t\t\t\t\t\tToken: \"123\",\n\t\t\t\t\t\t\tEmail: \"foo@example.com\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"invalid flag combination, --verbose and --json\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate listing profiles with --json displays data correctly\",\n\t\t\tArgs: \"--json\",\n\t\t\tEnv: &testutil.EnvConfig{\n\t\t\t\tOpts: &testutil.EnvOpts{\n\t\t\t\t\tCopy: []testutil.FileIO{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tSrc: filepath.Join(\"testdata\", \"config.toml\"),\n\t\t\t\t\t\t\tDst: \"config.toml\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tEditScenario: func(scenario *testutil.CLIScenario, rootdir string) {\n\t\t\t\t\tscenario.ConfigPath = filepath.Join(rootdir, \"config.toml\")\n\t\t\t\t},\n\t\t\t},\n\t\t\tConfigFile: &config.File{\n\t\t\t\tAuth: config.Auth{\n\t\t\t\t\tTokens: config.AuthTokens{\n\t\t\t\t\t\t\"foo\": &config.AuthToken{\n\t\t\t\t\t\t\tType:  config.AuthTokenTypeStatic,\n\t\t\t\t\t\t\tToken: \"123\",\n\t\t\t\t\t\t\tEmail: \"foo@example.com\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"bar\": &config.AuthToken{\n\t\t\t\t\t\t\tType:  config.AuthTokenTypeStatic,\n\t\t\t\t\t\t\tToken: \"456\",\n\t\t\t\t\t\t\tEmail: \"bar@example.com\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutputs: []string{\n\t\t\t\t`\"bar\"`,\n\t\t\t\t`\"token\": \"456\"`,\n\t\t\t\t`\"foo\"`,\n\t\t\t\t`\"token\": \"123\"`,\n\t\t\t},\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, \"list\"}, scenarios)\n}\n\nfunc TestProfileSwitch(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName: \"validate switching to unknown profile returns an error\",\n\t\t\tArgs: \"unknown\",\n\t\t\tEnv: &testutil.EnvConfig{\n\t\t\t\tOpts: &testutil.EnvOpts{\n\t\t\t\t\tCopy: []testutil.FileIO{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tSrc: filepath.Join(\"testdata\", \"config.toml\"),\n\t\t\t\t\t\t\tDst: \"config.toml\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tEditScenario: func(scenario *testutil.CLIScenario, rootdir string) {\n\t\t\t\t\tscenario.ConfigPath = filepath.Join(rootdir, \"config.toml\")\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"the profile 'unknown' does not exist\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate switching profiles works\",\n\t\t\tArgs: \"bar\",\n\t\t\tEnv: &testutil.EnvConfig{\n\t\t\t\tOpts: &testutil.EnvOpts{\n\t\t\t\t\tCopy: []testutil.FileIO{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tSrc: filepath.Join(\"testdata\", \"config.toml\"),\n\t\t\t\t\t\t\tDst: \"config.toml\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tEditScenario: func(scenario *testutil.CLIScenario, rootdir string) {\n\t\t\t\t\tscenario.ConfigPath = filepath.Join(rootdir, \"config.toml\")\n\t\t\t\t},\n\t\t\t},\n\t\t\tConfigFile: &config.File{\n\t\t\t\tAuth: config.Auth{\n\t\t\t\t\tDefault: \"foo\",\n\t\t\t\t\tTokens: config.AuthTokens{\n\t\t\t\t\t\t\"foo\": &config.AuthToken{\n\t\t\t\t\t\t\tType:  config.AuthTokenTypeStatic,\n\t\t\t\t\t\t\tToken: \"123\",\n\t\t\t\t\t\t\tEmail: \"foo@example.com\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"bar\": &config.AuthToken{\n\t\t\t\t\t\t\tType:  config.AuthTokenTypeStatic,\n\t\t\t\t\t\t\tToken: \"456\",\n\t\t\t\t\t\t\tEmail: \"bar@example.com\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: \"Profile switched to 'bar'\",\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, \"switch\"}, scenarios)\n}\n\nfunc TestProfileToken(t *testing.T) {\n\tnow := time.Now()\n\texpiredAt := now.Add(-600 * time.Second)\n\tsoonExpireAt := now.Add(30 * time.Second)\n\tlaterExpireAt := now.Add(1200 * time.Second)\n\tlongTTLExpireAt := now.Add(60 * time.Second)\n\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName: \"validate deprecation warning appears by default\",\n\t\t\tEnv: &testutil.EnvConfig{\n\t\t\t\tOpts: &testutil.EnvOpts{\n\t\t\t\t\tCopy: []testutil.FileIO{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tSrc: filepath.Join(\"testdata\", \"config.toml\"),\n\t\t\t\t\t\t\tDst: \"config.toml\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tEditScenario: func(scenario *testutil.CLIScenario, rootdir string) {\n\t\t\t\t\tscenario.ConfigPath = filepath.Join(rootdir, \"config.toml\")\n\t\t\t\t},\n\t\t\t},\n\t\t\tConfigFile: &config.File{\n\t\t\t\tAuth: config.Auth{\n\t\t\t\t\tDefault: \"foo\",\n\t\t\t\t\tTokens: config.AuthTokens{\n\t\t\t\t\t\t\"foo\": &config.AuthToken{\n\t\t\t\t\t\t\tType:  config.AuthTokenTypeStatic,\n\t\t\t\t\t\t\tToken: \"123\",\n\t\t\t\t\t\t\tEmail: \"foo@example.com\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutputs: []string{\"123\"},\n\t\t},\n\t\t{\n\t\t\tName: \"validate --quiet suppresses deprecation warning\",\n\t\t\tArgs: \"--quiet\",\n\t\t\tEnv: &testutil.EnvConfig{\n\t\t\t\tOpts: &testutil.EnvOpts{\n\t\t\t\t\tCopy: []testutil.FileIO{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tSrc: filepath.Join(\"testdata\", \"config.toml\"),\n\t\t\t\t\t\t\tDst: \"config.toml\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tEditScenario: func(scenario *testutil.CLIScenario, rootdir string) {\n\t\t\t\t\tscenario.ConfigPath = filepath.Join(rootdir, \"config.toml\")\n\t\t\t\t},\n\t\t\t},\n\t\t\tConfigFile: &config.File{\n\t\t\t\tAuth: config.Auth{\n\t\t\t\t\tDefault: \"foo\",\n\t\t\t\t\tTokens: config.AuthTokens{\n\t\t\t\t\t\t\"foo\": &config.AuthToken{\n\t\t\t\t\t\t\tType:  config.AuthTokenTypeStatic,\n\t\t\t\t\t\t\tToken: \"123\",\n\t\t\t\t\t\t\tEmail: \"foo@example.com\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: \"123\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate the active profile non-SSO token is displayed by default\",\n\t\t\tEnv: &testutil.EnvConfig{\n\t\t\t\tOpts: &testutil.EnvOpts{\n\t\t\t\t\tCopy: []testutil.FileIO{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tSrc: filepath.Join(\"testdata\", \"config.toml\"),\n\t\t\t\t\t\t\tDst: \"config.toml\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tEditScenario: func(scenario *testutil.CLIScenario, rootdir string) {\n\t\t\t\t\tscenario.ConfigPath = filepath.Join(rootdir, \"config.toml\")\n\t\t\t\t},\n\t\t\t},\n\t\t\tConfigFile: &config.File{\n\t\t\t\tAuth: config.Auth{\n\t\t\t\t\tDefault: \"foo\",\n\t\t\t\t\tTokens: config.AuthTokens{\n\t\t\t\t\t\t\"foo\": &config.AuthToken{\n\t\t\t\t\t\t\tType:  config.AuthTokenTypeStatic,\n\t\t\t\t\t\t\tToken: \"123\",\n\t\t\t\t\t\t\tEmail: \"foo@example.com\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"bar\": &config.AuthToken{\n\t\t\t\t\t\t\tType:  config.AuthTokenTypeStatic,\n\t\t\t\t\t\t\tToken: \"456\",\n\t\t\t\t\t\t\tEmail: \"bar@example.com\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: \"123\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate non-SSO token is displayed for the specified profile\",\n\t\t\tArgs: \"bar\",\n\t\t\tEnv: &testutil.EnvConfig{\n\t\t\t\tOpts: &testutil.EnvOpts{\n\t\t\t\t\tCopy: []testutil.FileIO{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tSrc: filepath.Join(\"testdata\", \"config.toml\"),\n\t\t\t\t\t\t\tDst: \"config.toml\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tEditScenario: func(scenario *testutil.CLIScenario, rootdir string) {\n\t\t\t\t\tscenario.ConfigPath = filepath.Join(rootdir, \"config.toml\")\n\t\t\t\t},\n\t\t\t},\n\t\t\tConfigFile: &config.File{\n\t\t\t\tAuth: config.Auth{\n\t\t\t\t\tDefault: \"foo\",\n\t\t\t\t\tTokens: config.AuthTokens{\n\t\t\t\t\t\t\"foo\": &config.AuthToken{\n\t\t\t\t\t\t\tType:  config.AuthTokenTypeStatic,\n\t\t\t\t\t\t\tToken: \"123\",\n\t\t\t\t\t\t\tEmail: \"foo@example.com\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"bar\": &config.AuthToken{\n\t\t\t\t\t\t\tType:  config.AuthTokenTypeStatic,\n\t\t\t\t\t\t\tToken: \"456\",\n\t\t\t\t\t\t\tEmail: \"bar@example.com\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: \"456\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate non-SSO token is displayed for the specified profile using global --profile\",\n\t\t\tArgs: \"--profile bar\",\n\t\t\tEnv: &testutil.EnvConfig{\n\t\t\t\tOpts: &testutil.EnvOpts{\n\t\t\t\t\tCopy: []testutil.FileIO{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tSrc: filepath.Join(\"testdata\", \"config.toml\"),\n\t\t\t\t\t\t\tDst: \"config.toml\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tEditScenario: func(scenario *testutil.CLIScenario, rootdir string) {\n\t\t\t\t\tscenario.ConfigPath = filepath.Join(rootdir, \"config.toml\")\n\t\t\t\t},\n\t\t\t},\n\t\t\tConfigFile: &config.File{\n\t\t\t\tAuth: config.Auth{\n\t\t\t\t\tDefault: \"foo\",\n\t\t\t\t\tTokens: config.AuthTokens{\n\t\t\t\t\t\t\"foo\": &config.AuthToken{\n\t\t\t\t\t\t\tType:  config.AuthTokenTypeStatic,\n\t\t\t\t\t\t\tToken: \"123\",\n\t\t\t\t\t\t\tEmail: \"foo@example.com\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"bar\": &config.AuthToken{\n\t\t\t\t\t\t\tType:  config.AuthTokenTypeStatic,\n\t\t\t\t\t\t\tToken: \"456\",\n\t\t\t\t\t\t\tEmail: \"bar@example.com\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: \"456\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate an unrecognised profile causes an error\",\n\t\t\tArgs: \"unknown\",\n\t\t\tEnv: &testutil.EnvConfig{\n\t\t\t\tOpts: &testutil.EnvOpts{\n\t\t\t\t\tCopy: []testutil.FileIO{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tSrc: filepath.Join(\"testdata\", \"config.toml\"),\n\t\t\t\t\t\t\tDst: \"config.toml\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tEditScenario: func(scenario *testutil.CLIScenario, rootdir string) {\n\t\t\t\t\tscenario.ConfigPath = filepath.Join(rootdir, \"config.toml\")\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"profile 'unknown' does not exist\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate that an expired SSO token generates an error\",\n\t\t\tEnv: &testutil.EnvConfig{\n\t\t\t\tOpts: &testutil.EnvOpts{\n\t\t\t\t\tCopy: []testutil.FileIO{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tSrc: filepath.Join(\"testdata\", \"config.toml\"),\n\t\t\t\t\t\t\tDst: \"config.toml\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tEditScenario: func(scenario *testutil.CLIScenario, rootdir string) {\n\t\t\t\t\tscenario.ConfigPath = filepath.Join(rootdir, \"config.toml\")\n\t\t\t\t},\n\t\t\t},\n\t\t\tConfigFile: &config.File{\n\t\t\t\tAuth: config.Auth{\n\t\t\t\t\tDefault: \"foo\",\n\t\t\t\t\tTokens: config.AuthTokens{\n\t\t\t\t\t\t\"foo\": &config.AuthToken{\n\t\t\t\t\t\t\tType:             config.AuthTokenTypeSSO,\n\t\t\t\t\t\t\tToken:            \"123\",\n\t\t\t\t\t\t\tEmail:            \"foo@example.com\",\n\t\t\t\t\t\t\tRefreshExpiresAt: expiredAt.Format(time.RFC3339),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: fmt.Sprintf(\"the token in profile 'foo' expired at '%s'\", expiredAt.UTC().Format(fsttime.Format)),\n\t\t},\n\t\t{\n\t\t\tName: \"validate that a soon-to-expire SSO token generates an error\",\n\t\t\tEnv: &testutil.EnvConfig{\n\t\t\t\tOpts: &testutil.EnvOpts{\n\t\t\t\t\tCopy: []testutil.FileIO{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tSrc: filepath.Join(\"testdata\", \"config.toml\"),\n\t\t\t\t\t\t\tDst: \"config.toml\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tEditScenario: func(scenario *testutil.CLIScenario, rootdir string) {\n\t\t\t\t\tscenario.ConfigPath = filepath.Join(rootdir, \"config.toml\")\n\t\t\t\t},\n\t\t\t},\n\t\t\tConfigFile: &config.File{\n\t\t\t\tAuth: config.Auth{\n\t\t\t\t\tDefault: \"foo\",\n\t\t\t\t\tTokens: config.AuthTokens{\n\t\t\t\t\t\t\"foo\": &config.AuthToken{\n\t\t\t\t\t\t\tType:             config.AuthTokenTypeSSO,\n\t\t\t\t\t\t\tToken:            \"123\",\n\t\t\t\t\t\t\tEmail:            \"foo@example.com\",\n\t\t\t\t\t\t\tRefreshExpiresAt: soonExpireAt.Format(time.RFC3339),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: fmt.Sprintf(\"the token in profile 'foo' will expire at '%s'\", soonExpireAt.UTC().Format(fsttime.Format)),\n\t\t},\n\t\t{\n\t\t\tName: \"validate that a soon-to-expire SSO token with a non-default TTL does not generate an error\",\n\t\t\tArgs: \"--ttl 30s\",\n\t\t\tEnv: &testutil.EnvConfig{\n\t\t\t\tOpts: &testutil.EnvOpts{\n\t\t\t\t\tCopy: []testutil.FileIO{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tSrc: filepath.Join(\"testdata\", \"config.toml\"),\n\t\t\t\t\t\t\tDst: \"config.toml\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tEditScenario: func(scenario *testutil.CLIScenario, rootdir string) {\n\t\t\t\t\tscenario.ConfigPath = filepath.Join(rootdir, \"config.toml\")\n\t\t\t\t},\n\t\t\t},\n\t\t\tConfigFile: &config.File{\n\t\t\t\tAuth: config.Auth{\n\t\t\t\t\tDefault: \"foo\",\n\t\t\t\t\tTokens: config.AuthTokens{\n\t\t\t\t\t\t\"foo\": &config.AuthToken{\n\t\t\t\t\t\t\tType:             config.AuthTokenTypeSSO,\n\t\t\t\t\t\t\tToken:            \"123\",\n\t\t\t\t\t\t\tEmail:            \"foo@example.com\",\n\t\t\t\t\t\t\tRefreshExpiresAt: longTTLExpireAt.Format(time.RFC3339),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: \"123\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate that an SSO token with a long non-default TTL generates an error\",\n\t\t\tArgs: \"--ttl 1800s\",\n\t\t\tEnv: &testutil.EnvConfig{\n\t\t\t\tOpts: &testutil.EnvOpts{\n\t\t\t\t\tCopy: []testutil.FileIO{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tSrc: filepath.Join(\"testdata\", \"config.toml\"),\n\t\t\t\t\t\t\tDst: \"config.toml\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tEditScenario: func(scenario *testutil.CLIScenario, rootdir string) {\n\t\t\t\t\tscenario.ConfigPath = filepath.Join(rootdir, \"config.toml\")\n\t\t\t\t},\n\t\t\t},\n\t\t\tConfigFile: &config.File{\n\t\t\t\tAuth: config.Auth{\n\t\t\t\t\tDefault: \"foo\",\n\t\t\t\t\tTokens: config.AuthTokens{\n\t\t\t\t\t\t\"foo\": &config.AuthToken{\n\t\t\t\t\t\t\tType:             config.AuthTokenTypeSSO,\n\t\t\t\t\t\t\tToken:            \"123\",\n\t\t\t\t\t\t\tEmail:            \"foo@example.com\",\n\t\t\t\t\t\t\tRefreshExpiresAt: laterExpireAt.Format(time.RFC3339),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: fmt.Sprintf(\"the token in profile 'foo' will expire at '%s'\", laterExpireAt.UTC().Format(fsttime.Format)),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, \"token\"}, scenarios)\n}\n\nfunc TestProfileUpdate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName: \"validate updating unknown profile returns an error\",\n\t\t\tArgs: \"unknown\",\n\t\t\tEnv: &testutil.EnvConfig{\n\t\t\t\tOpts: &testutil.EnvOpts{\n\t\t\t\t\tCopy: []testutil.FileIO{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tSrc: filepath.Join(\"testdata\", \"config.toml\"),\n\t\t\t\t\t\t\tDst: \"config.toml\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tEditScenario: func(scenario *testutil.CLIScenario, rootdir string) {\n\t\t\t\t\tscenario.ConfigPath = filepath.Join(rootdir, \"config.toml\")\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: \"the profile 'unknown' does not exist\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate updating profile works\",\n\t\t\tArgs: \"bar\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetCurrentUserFn: getCurrentUser,\n\t\t\t\tGetTokenSelfFn:   getToken,\n\t\t\t},\n\t\t\tEnv: &testutil.EnvConfig{\n\t\t\t\tOpts: &testutil.EnvOpts{\n\t\t\t\t\tCopy: []testutil.FileIO{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tSrc: filepath.Join(\"testdata\", \"config.toml\"),\n\t\t\t\t\t\t\tDst: \"config.toml\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tEditScenario: func(scenario *testutil.CLIScenario, rootdir string) {\n\t\t\t\t\tscenario.ConfigPath = filepath.Join(rootdir, \"config.toml\")\n\t\t\t\t},\n\t\t\t},\n\t\t\tConfigFile: &config.File{\n\t\t\t\tAuth: config.Auth{\n\t\t\t\t\tDefault: \"foo\",\n\t\t\t\t\tTokens: config.AuthTokens{\n\t\t\t\t\t\t\"foo\": &config.AuthToken{\n\t\t\t\t\t\t\tType:  config.AuthTokenTypeStatic,\n\t\t\t\t\t\t\tToken: \"123\",\n\t\t\t\t\t\t\tEmail: \"foo@example.com\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"bar\": &config.AuthToken{\n\t\t\t\t\t\t\tType:  config.AuthTokenTypeStatic,\n\t\t\t\t\t\t\tToken: \"456\",\n\t\t\t\t\t\t\tEmail: \"bar@example.com\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tStdin: []string{\n\t\t\t\t\"\",  // we skip updating the token\n\t\t\t\t\"y\", // we set the profile to be the default\n\t\t\t},\n\t\t\tWantOutput: \"Profile 'bar' updated\",\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, \"update\"}, scenarios)\n}\n\nfunc getCurrentUser(_ context.Context) (*fastly.User, error) {\n\treturn &fastly.User{\n\t\tLogin:      fastly.ToPointer(\"foo@example.com\"),\n\t\tCustomerID: fastly.ToPointer(\"abc\"),\n\t}, nil\n}\n\nfunc getToken(_ context.Context) (*fastly.Token, error) {\n\tt := testutil.Date\n\n\treturn &fastly.Token{\n\t\tTokenID:    fastly.ToPointer(\"123\"),\n\t\tName:       fastly.ToPointer(\"Foo\"),\n\t\tUserID:     fastly.ToPointer(\"456\"),\n\t\tServices:   []string{\"a\", \"b\"},\n\t\tScope:      fastly.ToPointer(fastly.TokenScope(fmt.Sprintf(\"%s %s\", fastly.PurgeAllScope, fastly.GlobalReadScope))),\n\t\tIP:         fastly.ToPointer(\"127.0.0.1\"),\n\t\tCreatedAt:  &t,\n\t\tExpiresAt:  &t,\n\t\tLastUsedAt: &t,\n\t}, nil\n}\n"
  },
  {
    "path": "pkg/commands/profile/root.go",
    "content": "package profile\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"profile\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manage user profiles (deprecated: use 'fastly auth' instead)\").Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/profile/switch.go",
    "content": "package profile\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tauthcmd \"github.com/fastly/cli/pkg/commands/auth\"\n\t\"github.com/fastly/cli/pkg/config\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// SwitchCommand represents a Kingpin command.\ntype SwitchCommand struct {\n\targparser.Base\n\n\tprofile string\n}\n\n// NewSwitchCommand returns a usable command registered under the parent.\nfunc NewSwitchCommand(parent argparser.Registerer, g *global.Data) *SwitchCommand {\n\tvar c SwitchCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(\"switch\", \"Switch user profile (deprecated: use 'fastly auth use' instead)\")\n\tc.CmdClause.Arg(\"profile\", \"Profile to switch to\").Short('p').Required().StringVar(&c.profile)\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *SwitchCommand) Exec(in io.Reader, out io.Writer) error {\n\tif !c.Globals.Flags.Quiet {\n\t\ttext.Deprecated(\"This command will be removed in a future release. Use 'fastly auth use' instead.\\n\\n\")\n\t}\n\n\tat := c.Globals.Config.GetAuthToken(c.profile)\n\tif at == nil {\n\t\terr := fmt.Errorf(\"the profile '%s' does not exist\", c.profile)\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn fsterr.RemediationError{\n\t\t\tInner:       err,\n\t\t\tRemediation: fsterr.ProfileRemediation(),\n\t\t}\n\t}\n\n\tif at.Type == config.AuthTokenTypeSSO {\n\t\tif err := authcmd.RunSSOWithTokenName(in, out, c.Globals, false, false, c.profile); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to authenticate: %w\", err)\n\t\t}\n\t\tif err := c.Globals.Config.SetDefaultAuthToken(c.profile); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif err := c.Globals.Config.Write(c.Globals.ConfigPath); err != nil {\n\t\t\treturn fmt.Errorf(\"error saving config file: %w\", err)\n\t\t}\n\t\ttext.Success(out, \"\\nProfile switched to '%s'\", c.profile)\n\t\treturn nil\n\t}\n\n\tif err := c.Globals.Config.SetDefaultAuthToken(c.profile); err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn fsterr.RemediationError{\n\t\t\tInner:       err,\n\t\t\tRemediation: fsterr.ProfileRemediation(),\n\t\t}\n\t}\n\n\tif err := c.Globals.Config.Write(c.Globals.ConfigPath); err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn fmt.Errorf(\"error saving config file: %w\", err)\n\t}\n\n\tif c.Globals.Verbose() {\n\t\ttext.Break(out)\n\t}\n\ttext.Success(out, \"Profile switched to '%s'\", c.profile)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/profile/testdata/config.toml",
    "content": "config_version = 2\n\n[fastly]\n  api_endpoint = \"https://api.fastly.com\"\n"
  },
  {
    "path": "pkg/commands/profile/token.go",
    "content": "package profile\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"time\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/config\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n\tfsttime \"github.com/fastly/cli/pkg/time\"\n)\n\n// TokenCommand represents a Kingpin command.\ntype TokenCommand struct {\n\targparser.Base\n\tprofile  string\n\ttokenTTL time.Duration\n}\n\n// NewTokenCommand returns a new command registered in the parent.\nfunc NewTokenCommand(parent argparser.Registerer, g *global.Data) *TokenCommand {\n\tvar c TokenCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(\"token\", \"Print API token (deprecated: use 'fastly auth show' instead)\")\n\tc.CmdClause.Arg(\"profile\", \"Print API token for the named profile\").Short('p').StringVar(&c.profile)\n\tc.CmdClause.Flag(\"ttl\", \"Amount of time for which the token must be valid (in seconds 's', minutes 'm', or hours 'h')\").Default(defaultTokenTTL.String()).DurationVar(&c.tokenTTL)\n\treturn &c\n}\n\nconst defaultTokenTTL time.Duration = 5 * time.Minute\n\n// Exec implements the command interface.\nfunc (c *TokenCommand) Exec(_ io.Reader, out io.Writer) (err error) {\n\tif !c.Globals.Flags.Quiet {\n\t\ttext.Deprecated(\"This command will be removed in a future release. Use 'fastly auth show' instead.\\n\\n\")\n\t}\n\n\tvar name string\n\tif c.profile != \"\" {\n\t\tname = c.profile\n\t}\n\tif c.Globals.Flags.Profile != \"\" {\n\t\tname = c.Globals.Flags.Profile\n\t}\n\n\tif name != \"\" {\n\t\tat := c.Globals.Config.GetAuthToken(name)\n\t\tif at != nil {\n\t\t\tif err = checkTokenValidity(name, at, c.tokenTTL); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\ttext.Output(out, at.Token)\n\t\t\treturn nil\n\t\t}\n\t\tmsg := fmt.Sprintf(\"the profile '%s' does not exist\", name)\n\t\treturn fsterr.RemediationError{\n\t\t\tInner:       errors.New(msg),\n\t\t\tRemediation: fsterr.ProfileRemediation(),\n\t\t}\n\t}\n\n\tif name, at := c.Globals.Config.GetDefaultAuthToken(); at != nil {\n\t\tif err = checkTokenValidity(name, at, c.tokenTTL); err != nil {\n\t\t\treturn err\n\t\t}\n\t\ttext.Output(out, at.Token)\n\t\treturn nil\n\t}\n\treturn fsterr.RemediationError{\n\t\tInner:       errors.New(\"no profiles available\"),\n\t\tRemediation: fsterr.ProfileRemediation(),\n\t}\n}\n\nfunc checkTokenValidity(name string, at *config.AuthToken, ttl time.Duration) error {\n\tvar expiryStr string\n\tif at.Type == config.AuthTokenTypeSSO {\n\t\texpiryStr = at.RefreshExpiresAt\n\t} else {\n\t\texpiryStr = at.APITokenExpiresAt\n\t}\n\tif expiryStr == \"\" {\n\t\treturn nil\n\t}\n\n\texpiry, err := time.Parse(time.RFC3339, expiryStr)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif expiry.After(time.Now().Add(ttl)) {\n\t\treturn nil\n\t}\n\n\tvar msg string\n\tif expiry.Before(time.Now()) {\n\t\tmsg = fmt.Sprintf(\"the token in profile '%s' expired at '%s'\", name, expiry.UTC().Format(fsttime.Format))\n\t} else {\n\t\tmsg = fmt.Sprintf(\"the token in profile '%s' will expire at '%s'\", name, expiry.UTC().Format(fsttime.Format))\n\t}\n\n\treturn fsterr.RemediationError{\n\t\tInner:       errors.New(msg),\n\t\tRemediation: fsterr.TokenExpirationRemediation(),\n\t}\n}\n"
  },
  {
    "path": "pkg/commands/profile/update.go",
    "content": "package profile\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tauthcmd \"github.com/fastly/cli/pkg/commands/auth\"\n\t\"github.com/fastly/cli/pkg/config\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// UpdateCommand represents a Kingpin command.\ntype UpdateCommand struct {\n\targparser.Base\n\n\tprofile string\n\tsso     bool\n}\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tvar c UpdateCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(\"update\", \"Update user profile (deprecated: use 'fastly auth login' or 'fastly auth add' instead)\")\n\tc.CmdClause.Arg(\"profile\", \"Profile to update (defaults to the currently active profile)\").Short('p').StringVar(&c.profile)\n\tc.CmdClause.Flag(\"sso\", \"Update profile to use an SSO-based token\").BoolVar(&c.sso)\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *UpdateCommand) Exec(in io.Reader, out io.Writer) error {\n\tif !c.Globals.Flags.Quiet {\n\t\ttext.Deprecated(\"This command will be removed in a future release. Use 'fastly auth login' or 'fastly auth add' instead.\\n\\n\")\n\t}\n\n\tprofileName, at, err := c.identifyProfile()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to identify the profile to update: %w\", err)\n\t}\n\tif c.Globals.Verbose() {\n\t\ttext.Break(out)\n\t}\n\ttext.Info(out, \"Profile being updated: '%s'.\\n\\n\", profileName)\n\n\tif err := c.updateToken(profileName, at, in, out); err != nil {\n\t\treturn fmt.Errorf(\"failed to update token: %w\", err)\n\t}\n\n\tmakeDefault := true\n\tif !c.Globals.Flags.AutoYes && !c.Globals.Flags.NonInteractive {\n\t\ttext.Break(out)\n\t\tmakeDefault, err = text.AskYesNo(out, text.BoldYellow(\"Make profile the default? [y/N] \"), in)\n\t\ttext.Break(out)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tif makeDefault {\n\t\tif err := c.Globals.Config.SetDefaultAuthToken(profileName); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to update token: %w\", err)\n\t\t}\n\t\tif err := c.Globals.Config.Write(c.Globals.ConfigPath); err != nil {\n\t\t\tc.Globals.ErrLog.Add(err)\n\t\t\treturn fmt.Errorf(\"error saving config file: %w\", err)\n\t\t}\n\t}\n\n\ttext.Success(out, \"\\nProfile '%s' updated\", profileName)\n\treturn nil\n}\n\nfunc (c *UpdateCommand) identifyProfile() (string, *config.AuthToken, error) {\n\tif c.profile == \"\" && c.Globals.Flags.Profile == \"\" {\n\t\tname, at := c.Globals.Config.GetDefaultAuthToken()\n\t\tif at == nil {\n\t\t\treturn \"\", nil, fsterr.RemediationError{\n\t\t\t\tInner:       fmt.Errorf(\"no active profile\"),\n\t\t\t\tRemediation: \"At least one account profile should be set as the 'default'. Run `fastly profile update <NAME>` and ensure the profile is set to be the default.\",\n\t\t\t}\n\t\t}\n\t\treturn name, at, nil\n\t}\n\n\tprofileName := c.profile\n\tif c.Globals.Flags.Profile != \"\" {\n\t\tprofileName = c.Globals.Flags.Profile\n\t}\n\tat := c.Globals.Config.GetAuthToken(profileName)\n\tif at == nil {\n\t\tmsg := fmt.Sprintf(\"the profile '%s' does not exist\", profileName)\n\t\treturn \"\", nil, fsterr.RemediationError{\n\t\t\tInner:       errors.New(msg),\n\t\t\tRemediation: fsterr.ProfileRemediation(),\n\t\t}\n\t}\n\n\treturn profileName, at, nil\n}\n\nfunc (c *UpdateCommand) updateToken(profileName string, at *config.AuthToken, in io.Reader, out io.Writer) error {\n\tif c.sso || at.Type == config.AuthTokenTypeSSO {\n\t\tif err := authcmd.RunSSOWithTokenName(in, out, c.Globals, false, false, profileName); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to authenticate: %w\", err)\n\t\t}\n\t\ttext.Break(out)\n\t\treturn nil\n\t}\n\n\ttoken, err := text.InputSecure(out, text.BoldYellow(\"Profile token: (leave blank to skip): \"), in)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\tif token == \"\" {\n\t\ttoken = at.Token\n\t}\n\ttext.Break(out)\n\n\tspinner, err := text.NewSpinner(out)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer func() {\n\t\tif err != nil {\n\t\t\tc.Globals.ErrLog.Add(err)\n\t\t}\n\t}()\n\n\tvar md *authcmd.TokenMetadata\n\terr = spinner.Process(\"Validating token\", func(_ *text.SpinnerWrapper) error {\n\t\tmd, err = authcmd.FetchTokenMetadata(c.Globals, token)\n\t\treturn err\n\t})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tauthcmd.BuildAndStoreStaticToken(c.Globals, token, profileName, md, false)\n\n\tif err := c.Globals.Config.Write(c.Globals.ConfigPath); err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn fmt.Errorf(\"error saving config file: %w\", err)\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/secretstore/create.go",
    "content": "package secretstore\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\n\tc.CmdClause = parent.Command(\"create\", \"Create a new secret store\")\n\n\t// Required.\n\tc.RegisterFlag(storeNameFlag(&c.Input.Name)) // --name\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\n\treturn &c\n}\n\n// CreateCommand calls the Fastly API to create an appropriate resource.\ntype CreateCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tInput fastly.CreateSecretStoreInput\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *CreateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\to, err := c.Globals.APIClient.CreateSecretStore(context.TODO(), &c.Input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Created Secret Store '%s' (%s)\", o.Name, o.StoreID)\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/secretstore/delete.go",
    "content": "package secretstore\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\n\tc.CmdClause = parent.Command(\"delete\", \"Delete a secret store\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StoreIDFlag(&c.Input.StoreID)) // --store-id\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\n\treturn &c\n}\n\n// DeleteCommand calls the Fastly API to delete an appropriate resource.\ntype DeleteCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tInput fastly.DeleteSecretStoreInput\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DeleteCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\terr := c.Globals.APIClient.DeleteSecretStore(context.TODO(), &c.Input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif c.JSONOutput.Enabled {\n\t\to := struct {\n\t\t\tID      string `json:\"id\"`\n\t\t\tDeleted bool   `json:\"deleted\"`\n\t\t}{\n\t\t\tc.Input.StoreID,\n\t\t\ttrue,\n\t\t}\n\t\t_, err := c.WriteJSON(out, o)\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Deleted Secret Store '%s'\", c.Input.StoreID)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/secretstore/describe.go",
    "content": "package secretstore\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// NewDescribeCommand returns a usable command registered under the parent.\nfunc NewDescribeCommand(parent argparser.Registerer, g *global.Data) *DescribeCommand {\n\tc := DescribeCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\n\tc.CmdClause = parent.Command(\"describe\", \"Retrieve a single secret store\").Alias(\"get\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StoreIDFlag(&c.Input.StoreID)) // --store-id\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\n\treturn &c\n}\n\n// DescribeCommand calls the Fastly API to describe an appropriate resource.\ntype DescribeCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tInput fastly.GetSecretStoreInput\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DescribeCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\to, err := c.Globals.APIClient.GetSecretStore(context.TODO(), &c.Input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\ttext.PrintSecretStore(out, \"\", o)\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/secretstore/doc.go",
    "content": "// Package secretstore contains commands to inspect and manipulate Fastly edge\n// secret stores.\n//\n// https://www.fastly.com/documentation/reference/api/services/resources/secret-store\npackage secretstore\n"
  },
  {
    "path": "pkg/commands/secretstore/flags.go",
    "content": "package secretstore\n\nimport (\n\t\"github.com/fastly/cli/pkg/argparser\"\n)\n\nfunc storeNameFlag(dst *string) argparser.StringFlagOpts {\n\treturn argparser.StringFlagOpts{\n\t\tName:        \"name\",\n\t\tShort:       'n',\n\t\tDescription: \"Store name\",\n\t\tDst:         dst,\n\t\tRequired:    true,\n\t}\n}\n"
  },
  {
    "path": "pkg/commands/secretstore/helper_test.go",
    "content": "package secretstore_test\n\nimport (\n\t\"bytes\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\nfunc fmtStore(s *fastly.SecretStore) string {\n\tvar b bytes.Buffer\n\ttext.PrintSecretStore(&b, \"\", s)\n\treturn b.String()\n}\n\nfunc fmtStores(s []fastly.SecretStore) string {\n\tvar b bytes.Buffer\n\ttext.PrintSecretStoresTbl(&b, s)\n\treturn b.String()\n}\n"
  },
  {
    "path": "pkg/commands/secretstore/list.go",
    "content": "package secretstore\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\n\tc.CmdClause = parent.Command(\"list\", \"List secret stores\")\n\n\t// Optional.\n\tc.RegisterFlag(argparser.CursorFlag(&c.Input.Cursor))  // --cursor\n\tc.RegisterFlagBool(c.JSONFlag())                       // --json\n\tc.RegisterFlagInt(argparser.LimitFlag(&c.Input.Limit)) // --limit\n\n\treturn &c\n}\n\n// ListCommand calls the Fastly API to list appropriate resources.\ntype ListCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\t// NOTE: API returns 10 items even when --limit is set to smaller.\n\tInput fastly.ListSecretStoresInput\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *ListCommand) Exec(in io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tvar data []fastly.SecretStore\n\n\tfor {\n\t\to, err := c.Globals.APIClient.ListSecretStores(context.TODO(), &c.Input)\n\t\tif err != nil {\n\t\t\tc.Globals.ErrLog.Add(err)\n\t\t\treturn err\n\t\t}\n\n\t\tif o != nil {\n\t\t\tdata = append(data, o.Data...)\n\n\t\t\tif c.JSONOutput.Enabled || c.Globals.Flags.NonInteractive || c.Globals.Flags.AutoYes {\n\t\t\t\tif o.Meta.NextCursor != \"\" {\n\t\t\t\t\tc.Input.Cursor = o.Meta.NextCursor\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\ttext.PrintSecretStoresTbl(out, o.Data)\n\n\t\t\tif o.Meta.NextCursor != \"\" {\n\t\t\t\ttext.Break(out)\n\t\t\t\tprintNext, err := text.AskYesNo(out, \"Print next page [y/N]: \", in)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tif printNext {\n\t\t\t\t\ttext.Break(out)\n\t\t\t\t\tc.Input.Cursor = o.Meta.NextCursor\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tbreak\n\t}\n\n\tok, err := c.WriteJSON(out, data)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Only print output here if we've not already printed JSON.\n\t// And only if we're non interactive.\n\t// Otherwise interactive mode would have displayed each page of data.\n\tif !ok && (c.Globals.Flags.NonInteractive || c.Globals.Flags.AutoYes) {\n\t\ttext.PrintSecretStoresTbl(out, data)\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/secretstore/root.go",
    "content": "package secretstore\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootNameStore is the base command name for secret store operations.\nconst RootNameStore = \"secret-store\"\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"secret-store\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tc := RootCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\n\tc.CmdClause = parent.Command(CommandName, \"Manipulate Fastly Secret Stores\")\n\n\treturn &c\n}\n\n// RootCommand is the parent command for all 'store' subcommands.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/secretstore/secretstore_test.go",
    "content": "package secretstore_test\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/app\"\n\t\"github.com/fastly/cli/pkg/commands/secretstore\"\n\tfstfmt \"github.com/fastly/cli/pkg/fmt\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/mock\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n)\n\nfunc TestCreateStoreCommand(t *testing.T) {\n\tconst (\n\t\tstoreName = \"test123\"\n\t\tstoreID   = \"store-id-123\"\n\t)\n\tnow := time.Now()\n\n\tscenarios := []struct {\n\t\targs           string\n\t\tapi            mock.API\n\t\twantAPIInvoked bool\n\t\twantError      string\n\t\twantOutput     string\n\t}{\n\t\t{\n\t\t\targs:      \"create\",\n\t\t\twantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\targs: fmt.Sprintf(\"create --name %s\", storeName),\n\t\t\tapi: mock.API{\n\t\t\t\tCreateSecretStoreFn: func(_ context.Context, _ *fastly.CreateSecretStoreInput) (*fastly.SecretStore, error) {\n\t\t\t\t\treturn nil, errors.New(\"invalid request\")\n\t\t\t\t},\n\t\t\t},\n\t\t\twantAPIInvoked: true,\n\t\t\twantError:      \"invalid request\",\n\t\t},\n\t\t{\n\t\t\targs: fmt.Sprintf(\"create --name %s\", storeName),\n\t\t\tapi: mock.API{\n\t\t\t\tCreateSecretStoreFn: func(_ context.Context, i *fastly.CreateSecretStoreInput) (*fastly.SecretStore, error) {\n\t\t\t\t\treturn &fastly.SecretStore{\n\t\t\t\t\t\tStoreID: storeID,\n\t\t\t\t\t\tName:    i.Name,\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\twantAPIInvoked: true,\n\t\t\twantOutput:     fstfmt.Success(\"Created Secret Store '%s' (%s)\", storeName, storeID),\n\t\t},\n\t\t{\n\t\t\targs: fmt.Sprintf(\"create --name %s --json\", storeName),\n\t\t\tapi: mock.API{\n\t\t\t\tCreateSecretStoreFn: func(_ context.Context, i *fastly.CreateSecretStoreInput) (*fastly.SecretStore, error) {\n\t\t\t\t\treturn &fastly.SecretStore{\n\t\t\t\t\t\tStoreID:   storeID,\n\t\t\t\t\t\tName:      i.Name,\n\t\t\t\t\t\tCreatedAt: now,\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\twantAPIInvoked: true,\n\t\t\twantOutput:     fstfmt.JSON(`{\"created_at\": %q, \"name\": %q, \"id\": %q}`, now.Format(time.RFC3339Nano), storeName, storeID),\n\t\t},\n\t}\n\n\tfor _, testcase := range scenarios {\n\t\ttestcase := testcase\n\t\tt.Run(testcase.args, func(t *testing.T) {\n\t\t\tvar stdout bytes.Buffer\n\t\t\targs := testutil.SplitArgs(secretstore.RootNameStore + \" \" + testcase.args)\n\t\t\topts := testutil.MockGlobalData(args, &stdout)\n\n\t\t\tf := testcase.api.CreateSecretStoreFn\n\t\t\tvar apiInvoked bool\n\t\t\ttestcase.api.CreateSecretStoreFn = func(ctx context.Context, i *fastly.CreateSecretStoreInput) (*fastly.SecretStore, error) {\n\t\t\t\tapiInvoked = true\n\t\t\t\treturn f(ctx, i)\n\t\t\t}\n\n\t\t\tapp.Init = func(_ []string, _ io.Reader) (*global.Data, error) {\n\t\t\t\topts.APIClientFactory = mock.APIClient(testcase.api)\n\t\t\t\treturn opts, nil\n\t\t\t}\n\t\t\terr := app.Run(args, nil)\n\n\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\ttestutil.AssertString(t, testcase.wantOutput, stdout.String())\n\t\t\tif apiInvoked != testcase.wantAPIInvoked {\n\t\t\t\tt.Fatalf(\"API CreateSecretStore invoked = %v, want %v\", apiInvoked, testcase.wantAPIInvoked)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestDeleteStoreCommand(t *testing.T) {\n\tconst storeID = \"test123\"\n\terrStoreNotFound := errors.New(\"store not found\")\n\n\tscenarios := []struct {\n\t\targs           string\n\t\tapi            mock.API\n\t\twantAPIInvoked bool\n\t\twantError      string\n\t\twantOutput     string\n\t}{\n\t\t{\n\t\t\targs:      \"delete\",\n\t\t\twantError: \"error parsing arguments: required flag --store-id not provided\",\n\t\t},\n\t\t{\n\t\t\targs: \"delete --store-id DOES-NOT-EXIST\",\n\t\t\tapi: mock.API{\n\t\t\t\tDeleteSecretStoreFn: func(_ context.Context, i *fastly.DeleteSecretStoreInput) error {\n\t\t\t\t\tif i.StoreID != storeID {\n\t\t\t\t\t\treturn errStoreNotFound\n\t\t\t\t\t}\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t},\n\t\t\twantAPIInvoked: true,\n\t\t\twantError:      errStoreNotFound.Error(),\n\t\t},\n\t\t{\n\t\t\targs: fmt.Sprintf(\"delete --store-id %s\", storeID),\n\t\t\tapi: mock.API{\n\t\t\t\tDeleteSecretStoreFn: func(_ context.Context, i *fastly.DeleteSecretStoreInput) error {\n\t\t\t\t\tif i.StoreID != storeID {\n\t\t\t\t\t\treturn errStoreNotFound\n\t\t\t\t\t}\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t},\n\t\t\twantAPIInvoked: true,\n\t\t\twantOutput:     fstfmt.Success(\"Deleted Secret Store '%s'\\n\", storeID),\n\t\t},\n\t\t{\n\t\t\targs: fmt.Sprintf(\"delete --store-id %s --json\", storeID),\n\t\t\tapi: mock.API{\n\t\t\t\tDeleteSecretStoreFn: func(_ context.Context, i *fastly.DeleteSecretStoreInput) error {\n\t\t\t\t\tif i.StoreID != storeID {\n\t\t\t\t\t\treturn errStoreNotFound\n\t\t\t\t\t}\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t},\n\t\t\twantAPIInvoked: true,\n\t\t\twantOutput:     fstfmt.JSON(`{\"id\": %q, \"deleted\": true}`, storeID),\n\t\t},\n\t}\n\n\tfor _, testcase := range scenarios {\n\t\ttestcase := testcase\n\t\tt.Run(testcase.args, func(t *testing.T) {\n\t\t\tvar stdout bytes.Buffer\n\t\t\targs := testutil.SplitArgs(secretstore.RootNameStore + \" \" + testcase.args)\n\t\t\topts := testutil.MockGlobalData(args, &stdout)\n\n\t\t\tf := testcase.api.DeleteSecretStoreFn\n\t\t\tvar apiInvoked bool\n\t\t\ttestcase.api.DeleteSecretStoreFn = func(ctx context.Context, i *fastly.DeleteSecretStoreInput) error {\n\t\t\t\tapiInvoked = true\n\t\t\t\treturn f(ctx, i)\n\t\t\t}\n\n\t\t\tapp.Init = func(_ []string, _ io.Reader) (*global.Data, error) {\n\t\t\t\topts.APIClientFactory = mock.APIClient(testcase.api)\n\t\t\t\treturn opts, nil\n\t\t\t}\n\t\t\terr := app.Run(args, nil)\n\n\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\ttestutil.AssertString(t, testcase.wantOutput, stdout.String())\n\t\t\tif apiInvoked != testcase.wantAPIInvoked {\n\t\t\t\tt.Fatalf(\"API DeleteSecretStore invoked = %v, want %v\", apiInvoked, testcase.wantAPIInvoked)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestDescribeStoreCommand(t *testing.T) {\n\tconst (\n\t\tstoreName = \"test123\"\n\t\tstoreID   = \"store-id-123\"\n\t)\n\n\tscenarios := []struct {\n\t\targs           string\n\t\tapi            mock.API\n\t\twantAPIInvoked bool\n\t\twantError      string\n\t\twantOutput     string\n\t}{\n\t\t{\n\t\t\targs:      \"get\",\n\t\t\twantError: \"error parsing arguments: required flag --store-id not provided\",\n\t\t},\n\t\t{\n\t\t\targs: fmt.Sprintf(\"get --store-id %s\", storeID),\n\t\t\tapi: mock.API{\n\t\t\t\tGetSecretStoreFn: func(_ context.Context, _ *fastly.GetSecretStoreInput) (*fastly.SecretStore, error) {\n\t\t\t\t\treturn nil, errors.New(\"invalid request\")\n\t\t\t\t},\n\t\t\t},\n\t\t\twantAPIInvoked: true,\n\t\t\twantError:      \"invalid request\",\n\t\t},\n\t\t{\n\t\t\targs: fmt.Sprintf(\"get --store-id %s\", storeID),\n\t\t\tapi: mock.API{\n\t\t\t\tGetSecretStoreFn: func(_ context.Context, i *fastly.GetSecretStoreInput) (*fastly.SecretStore, error) {\n\t\t\t\t\treturn &fastly.SecretStore{\n\t\t\t\t\t\tStoreID: i.StoreID,\n\t\t\t\t\t\tName:    storeName,\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\twantAPIInvoked: true,\n\t\t\twantOutput: fmtStore(&fastly.SecretStore{\n\t\t\t\tStoreID: storeID,\n\t\t\t\tName:    storeName,\n\t\t\t}),\n\t\t},\n\t\t{\n\t\t\targs: fmt.Sprintf(\"get --store-id %s --json\", storeID),\n\t\t\tapi: mock.API{\n\t\t\t\tGetSecretStoreFn: func(_ context.Context, i *fastly.GetSecretStoreInput) (*fastly.SecretStore, error) {\n\t\t\t\t\treturn &fastly.SecretStore{\n\t\t\t\t\t\tStoreID: i.StoreID,\n\t\t\t\t\t\tName:    storeName,\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\twantAPIInvoked: true,\n\t\t\twantOutput: fstfmt.EncodeJSON(&fastly.SecretStore{\n\t\t\t\tStoreID: storeID,\n\t\t\t\tName:    storeName,\n\t\t\t}),\n\t\t},\n\t}\n\n\tfor _, testcase := range scenarios {\n\t\ttestcase := testcase\n\t\tt.Run(testcase.args, func(t *testing.T) {\n\t\t\tvar stdout bytes.Buffer\n\t\t\targs := testutil.SplitArgs(secretstore.RootNameStore + \" \" + testcase.args)\n\t\t\topts := testutil.MockGlobalData(args, &stdout)\n\n\t\t\tf := testcase.api.GetSecretStoreFn\n\t\t\tvar apiInvoked bool\n\t\t\ttestcase.api.GetSecretStoreFn = func(ctx context.Context, i *fastly.GetSecretStoreInput) (*fastly.SecretStore, error) {\n\t\t\t\tapiInvoked = true\n\t\t\t\treturn f(ctx, i)\n\t\t\t}\n\n\t\t\tapp.Init = func(_ []string, _ io.Reader) (*global.Data, error) {\n\t\t\t\topts.APIClientFactory = mock.APIClient(testcase.api)\n\t\t\t\treturn opts, nil\n\t\t\t}\n\t\t\terr := app.Run(args, nil)\n\n\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\ttestutil.AssertString(t, testcase.wantOutput, stdout.String())\n\t\t\tif apiInvoked != testcase.wantAPIInvoked {\n\t\t\t\tt.Fatalf(\"API GetSecretStore invoked = %v, want %v\", apiInvoked, testcase.wantAPIInvoked)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestListStoresCommand(t *testing.T) {\n\tconst (\n\t\tstoreName = \"test123\"\n\t\tstoreID   = \"store-id-123\"\n\t)\n\n\tstores := &fastly.SecretStores{\n\t\tMeta: fastly.SecretStoreMeta{\n\t\t\tLimit: 123,\n\t\t},\n\t\tData: []fastly.SecretStore{\n\t\t\t{StoreID: storeID, Name: storeName},\n\t\t},\n\t}\n\n\tscenarios := []struct {\n\t\targs           string\n\t\tapi            mock.API\n\t\twantAPIInvoked bool\n\t\twantError      string\n\t\twantOutput     string\n\t}{\n\t\t{\n\t\t\targs: \"list\",\n\t\t\tapi: mock.API{\n\t\t\t\tListSecretStoresFn: func(_ context.Context, _ *fastly.ListSecretStoresInput) (*fastly.SecretStores, error) {\n\t\t\t\t\treturn nil, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\twantAPIInvoked: true,\n\t\t},\n\t\t{\n\t\t\targs: \"list\",\n\t\t\tapi: mock.API{\n\t\t\t\tListSecretStoresFn: func(_ context.Context, _ *fastly.ListSecretStoresInput) (*fastly.SecretStores, error) {\n\t\t\t\t\treturn nil, errors.New(\"unknown error\")\n\t\t\t\t},\n\t\t\t},\n\t\t\twantAPIInvoked: true,\n\t\t\twantError:      \"unknown error\",\n\t\t},\n\t\t{\n\t\t\targs: \"list\",\n\t\t\tapi: mock.API{\n\t\t\t\tListSecretStoresFn: func(_ context.Context, _ *fastly.ListSecretStoresInput) (*fastly.SecretStores, error) {\n\t\t\t\t\treturn stores, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\twantAPIInvoked: true,\n\t\t\twantOutput:     fmtStores(stores.Data),\n\t\t},\n\t\t{\n\t\t\targs: \"list --json\",\n\t\t\tapi: mock.API{\n\t\t\t\tListSecretStoresFn: func(_ context.Context, _ *fastly.ListSecretStoresInput) (*fastly.SecretStores, error) {\n\t\t\t\t\treturn stores, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\twantAPIInvoked: true,\n\t\t\twantOutput:     fstfmt.EncodeJSON([]fastly.SecretStore{stores.Data[0]}),\n\t\t},\n\t}\n\n\tfor _, testcase := range scenarios {\n\t\ttestcase := testcase\n\t\tt.Run(testcase.args, func(t *testing.T) {\n\t\t\tvar stdout bytes.Buffer\n\t\t\targs := testutil.SplitArgs(secretstore.RootNameStore + \" \" + testcase.args)\n\t\t\topts := testutil.MockGlobalData(args, &stdout)\n\n\t\t\tf := testcase.api.ListSecretStoresFn\n\t\t\tvar apiInvoked bool\n\t\t\ttestcase.api.ListSecretStoresFn = func(ctx context.Context, i *fastly.ListSecretStoresInput) (*fastly.SecretStores, error) {\n\t\t\t\tapiInvoked = true\n\t\t\t\treturn f(ctx, i)\n\t\t\t}\n\n\t\t\tapp.Init = func(_ []string, _ io.Reader) (*global.Data, error) {\n\t\t\t\topts.APIClientFactory = mock.APIClient(testcase.api)\n\t\t\t\treturn opts, nil\n\t\t\t}\n\t\t\terr := app.Run(args, nil)\n\n\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\ttestutil.AssertStringContains(t, stdout.String(), testcase.wantOutput)\n\t\t\tif apiInvoked != testcase.wantAPIInvoked {\n\t\t\t\tt.Fatalf(\"API ListSecretStores invoked = %v, want %v\", apiInvoked, testcase.wantAPIInvoked)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/commands/secretstoreentry/create.go",
    "content": "package secretstoreentry\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/base64\"\n\t\"encoding/hex\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"os\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\nconst (\n\t// Maximum secret length, as defined at https://www.fastly.com/documentation/reference/api/services/resources/secret-store-secret\n\tmaxSecretKiB = 64\n\tmaxSecretLen = maxSecretKiB * 1024\n)\n\n// verificationKey is Fastly's Ed25519 public key, used to verify signatures\n// on client keys returned by the API. Fastly holds the corresponding private\n// key and signs client keys on their side.\n//\n// This key is meant to be long-lived and infrequently (if ever) rotated.\n// Hardcoding it in the CLI provides a trust anchor that prevents MITM attacks\n// where an attacker could substitute a different key.\n//\n// When Fastly rotates it, we will need to update this value and release a\n// new version of the CLI. Users can also override this check with\n// the FASTLY_USE_API_SIGNING_KEY environment variable.\nvar verificationKey = mustDecode(\"CrO/A92vkxEZjtTW7D/Sr+1EMf/q9BahC0sfLkWa+0k=\")\n\nfunc mustDecode(s string) []byte {\n\tb, err := base64.StdEncoding.DecodeString(s)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn b\n}\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\n\tc.CmdClause = parent.Command(\"create\", \"Create a new secret within specified store\")\n\n\t// Required.\n\tc.RegisterFlag(secretNameFlag(&c.Input.Name))           // --name\n\tc.RegisterFlag(argparser.StoreIDFlag(&c.Input.StoreID)) // --store-id\n\n\t// Optional.\n\tc.RegisterFlag(secretFileFlag(&c.secretFile)) // --file\n\tc.RegisterFlagBool(c.JSONFlag())              // --json\n\tc.RegisterFlagBool(argparser.BoolFlagOpts{\n\t\tName:        \"recreate\",\n\t\tDescription: \"Recreate secret by name (errors if secret doesn't already exist)\",\n\t\tDst:         &c.recreate,\n\t\tRequired:    false,\n\t})\n\tc.RegisterFlagBool(argparser.BoolFlagOpts{\n\t\tName:        \"recreate-allow\",\n\t\tDescription: \"Create or recreate secret by name\",\n\t\tDst:         &c.recreateAllow,\n\t\tRequired:    false,\n\t})\n\tc.RegisterFlagBool(secretStdinFlag(&c.secretSTDIN)) // --stdin\n\n\treturn &c\n}\n\n// CreateCommand calls the Fastly API to create an appropriate resource.\ntype CreateCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tInput         fastly.CreateSecretInput\n\trecreate      bool\n\trecreateAllow bool\n\tsecretFile    string\n\tsecretSTDIN   bool\n}\n\nvar errMultipleSecretValue = fsterr.RemediationError{\n\tInner:       fmt.Errorf(\"invalid flag combination, --file and --stdin\"),\n\tRemediation: \"Use one of --file or --stdin flag\",\n}\n\nvar errMaxSecretLength = fsterr.RemediationError{\n\tInner:       fmt.Errorf(\"max secret size exceeded\"),\n\tRemediation: fmt.Sprintf(\"Maximum secret size is %dKiB\", maxSecretKiB),\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *CreateCommand) Exec(in io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\tif c.secretFile != \"\" && c.secretSTDIN {\n\t\treturn errMultipleSecretValue\n\t}\n\n\tswitch {\n\tcase c.recreate && c.recreateAllow:\n\t\treturn fsterr.RemediationError{\n\t\t\tInner:       fmt.Errorf(\"invalid flag combination, --recreate and --recreate-allow\"),\n\t\t\tRemediation: \"Use either --recreate or --recreate-allow, not both.\",\n\t\t}\n\tcase c.recreate:\n\t\tc.Input.Method = http.MethodPatch\n\tcase c.recreateAllow:\n\t\tc.Input.Method = http.MethodPut\n\t}\n\n\t// Read secret's value: either from STDIN, a file, or prompt.\n\tswitch {\n\tcase c.secretSTDIN:\n\t\t// Determine if 'in' has data available.\n\t\tif in == nil || text.IsTTY(in) {\n\t\t\treturn fsterr.ErrNoSTDINData\n\t\t}\n\t\tvar buf bytes.Buffer\n\t\tif _, err := buf.ReadFrom(in); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tc.Input.Secret = buf.Bytes()\n\n\tcase c.secretFile != \"\":\n\t\tvar err error\n\t\t// nosemgrep: trailofbits.go.questionable-assignment.questionable-assignment\n\t\tif c.Input.Secret, err = os.ReadFile(c.secretFile); err != nil {\n\t\t\treturn err\n\t\t}\n\n\tdefault:\n\t\tsecret, err := text.InputSecure(out, \"Secret: \", in)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tc.Input.Secret = []byte(secret)\n\t}\n\n\tif len(c.Input.Secret) > maxSecretLen {\n\t\treturn errMaxSecretLength\n\t}\n\n\tck, err := c.Globals.APIClient.CreateClientKey(context.TODO())\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tapiPublicKey, err := c.Globals.APIClient.GetSigningKey(context.TODO())\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif !bytes.Equal(apiPublicKey, verificationKey) && os.Getenv(\"FASTLY_USE_API_SIGNING_KEY\") == \"\" {\n\t\terr := fmt.Errorf(\"API public key does not match expected verification key\")\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif !ck.VerifySignature(apiPublicKey) {\n\t\terr := fmt.Errorf(\"unable to verify signature of client key\")\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\twrapped, err := ck.Encrypt(c.Input.Secret)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tc.Input.Secret = wrapped\n\tc.Input.ClientKey = ck.PublicKey\n\n\to, err := c.Globals.APIClient.CreateSecret(context.TODO(), &c.Input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\taction := \"Created\"\n\tif o.Recreated {\n\t\taction = \"Recreated\"\n\t}\n\ttext.Success(out, \"%s secret '%s' in Secret Store '%s' (digest: %s)\", action, o.Name, c.Input.StoreID, hex.EncodeToString(o.Digest))\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/secretstoreentry/delete.go",
    "content": "package secretstoreentry\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\n\tc.CmdClause = parent.Command(\"delete\", \"Delete a secret\")\n\n\t// Required.\n\tc.RegisterFlag(secretNameFlag(&c.Input.Name))           // --name\n\tc.RegisterFlag(argparser.StoreIDFlag(&c.Input.StoreID)) // --store-id\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\n\treturn &c\n}\n\n// DeleteCommand calls the Fastly API to delete an appropriate resource.\ntype DeleteCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tInput fastly.DeleteSecretInput\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DeleteCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\terr := c.Globals.APIClient.DeleteSecret(context.TODO(), &c.Input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif c.JSONOutput.Enabled {\n\t\to := struct {\n\t\t\tName    string `json:\"name\"`\n\t\t\tID      string `json:\"store_id\"`\n\t\t\tDeleted bool   `json:\"deleted\"`\n\t\t}{\n\t\t\tc.Input.Name,\n\t\t\tc.Input.StoreID,\n\t\t\ttrue,\n\t\t}\n\t\t_, err := c.WriteJSON(out, o)\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Deleted secret '%s' from Secret Store '%s'\", c.Input.Name, c.Input.StoreID)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/secretstoreentry/describe.go",
    "content": "package secretstoreentry\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// NewDescribeCommand returns a usable command registered under the parent.\nfunc NewDescribeCommand(parent argparser.Registerer, g *global.Data) *DescribeCommand {\n\tc := DescribeCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\n\tc.CmdClause = parent.Command(\"describe\", \"Retrieve a single secret\").Alias(\"get\")\n\n\t// Required.\n\tc.RegisterFlag(secretNameFlag(&c.Input.Name))           // --name\n\tc.RegisterFlag(argparser.StoreIDFlag(&c.Input.StoreID)) // --store-id\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\n\treturn &c\n}\n\n// DescribeCommand calls the Fastly API to describe an appropriate resource.\ntype DescribeCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tInput fastly.GetSecretInput\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DescribeCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\to, err := c.Globals.APIClient.GetSecret(context.TODO(), &c.Input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\ttext.PrintSecret(out, \"\", o)\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/secretstoreentry/doc.go",
    "content": "// Package secretstoreentry contains commands to inspect and manipulate Fastly\n// edge secret store data.\n//\n// https://www.fastly.com/documentation/reference/api/services/resources/secret-store\npackage secretstoreentry\n"
  },
  {
    "path": "pkg/commands/secretstoreentry/flags.go",
    "content": "package secretstoreentry\n\nimport (\n\t\"github.com/fastly/cli/pkg/argparser\"\n)\n\nfunc secretNameFlag(dst *string) argparser.StringFlagOpts {\n\treturn argparser.StringFlagOpts{\n\t\tName:        \"name\",\n\t\tShort:       'n',\n\t\tDescription: \"Secret name\",\n\t\tDst:         dst,\n\t\tRequired:    true,\n\t}\n}\n\nfunc secretFileFlag(dst *string) argparser.StringFlagOpts {\n\treturn argparser.StringFlagOpts{\n\t\tName:        \"file\",\n\t\tShort:       'f',\n\t\tDescription: \"Read secret value from file instead of prompt\",\n\t\tDst:         dst,\n\t\tRequired:    false,\n\t}\n}\n\nfunc secretStdinFlag(dst *bool) argparser.BoolFlagOpts {\n\treturn argparser.BoolFlagOpts{\n\t\tName:        \"stdin\",\n\t\tDescription: \"Read secret value from STDIN instead of prompt\",\n\t\tDst:         dst,\n\t\tRequired:    false,\n\t}\n}\n"
  },
  {
    "path": "pkg/commands/secretstoreentry/helper_test.go",
    "content": "package secretstoreentry_test\n\nimport (\n\t\"bytes\"\n\n\t\"github.com/fastly/cli/pkg/text\"\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n)\n\nfunc fmtSecret(s *fastly.Secret) string {\n\tvar b bytes.Buffer\n\ttext.PrintSecret(&b, \"\", s)\n\treturn b.String()\n}\n\nfunc fmtSecrets(s *fastly.Secrets) string {\n\tvar b bytes.Buffer\n\ttext.PrintSecretsTbl(&b, s)\n\treturn b.String()\n}\n"
  },
  {
    "path": "pkg/commands/secretstoreentry/list.go",
    "content": "package secretstoreentry\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\n\tc.CmdClause = parent.Command(\"list\", \"List secrets within a specified store\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StoreIDFlag(&c.Input.StoreID)) // --store-id\n\n\t// Optional.\n\tc.RegisterFlag(argparser.CursorFlag(&c.Input.Cursor))  // --cursor\n\tc.RegisterFlagBool(c.JSONFlag())                       // --json\n\tc.RegisterFlagInt(argparser.LimitFlag(&c.Input.Limit)) // --limit\n\n\treturn &c\n}\n\n// ListCommand calls the Fastly API to list appropriate resources.\ntype ListCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tInput fastly.ListSecretsInput\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *ListCommand) Exec(in io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tfor {\n\t\to, err := c.Globals.APIClient.ListSecrets(context.TODO(), &c.Input)\n\t\tif err != nil {\n\t\t\tc.Globals.ErrLog.Add(err)\n\t\t\treturn err\n\t\t}\n\n\t\tif ok, err := c.WriteJSON(out, o); ok {\n\t\t\t// No pagination prompt w/ JSON output.\n\t\t\treturn err\n\t\t}\n\n\t\ttext.PrintSecretsTbl(out, o)\n\n\t\tif o != nil && o.Meta.NextCursor != \"\" {\n\t\t\t// Check if 'out' is interactive before prompting.\n\t\t\tif !c.Globals.Flags.NonInteractive && !c.Globals.Flags.AutoYes && text.IsTTY(out) {\n\t\t\t\tprintNext, err := text.AskYesNo(out, \"Print next page [y/N]: \", in)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tif printNext {\n\t\t\t\t\tc.Input.Cursor = o.Meta.NextCursor\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn nil\n\t}\n}\n"
  },
  {
    "path": "pkg/commands/secretstoreentry/root.go",
    "content": "package secretstoreentry\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootNameSecret is the base command name for secret operations.\nconst RootNameSecret = \"secret-store-entry\"\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"secret-store-entry\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tc := RootCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\n\tc.CmdClause = parent.Command(CommandName, \"Manipulate Fastly Secret Store secrets\")\n\n\treturn &c\n}\n\n// RootCommand is the parent command for all 'secret' subcommands.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/secretstoreentry/secretstoreentry_test.go",
    "content": "package secretstoreentry_test\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"crypto/ed25519\"\n\t\"crypto/rand\"\n\t\"encoding/hex\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"os\"\n\t\"path\"\n\t\"runtime\"\n\t\"testing\"\n\t\"time\"\n\n\t\"golang.org/x/crypto/nacl/box\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/app\"\n\t\"github.com/fastly/cli/pkg/commands/secretstoreentry\"\n\tfstfmt \"github.com/fastly/cli/pkg/fmt\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/mock\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n)\n\nfunc TestCreateSecretCommand(t *testing.T) {\n\tconst (\n\t\tstoreID      = \"store123\"\n\t\tsecretName   = \"testsecret\"\n\t\tsecretDigest = \"digest\"\n\t\tsecretValue  = \"the secret\"\n\t)\n\n\ttmpDir := t.TempDir()\n\tsecretFile := path.Join(tmpDir, \"secret-file\")\n\tif err := os.WriteFile(secretFile, []byte(secretValue), 0o600); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdoesNotExistFile := path.Join(tmpDir, \"DOES-NOT-EXIST\")\n\n\tckPub, ckPriv, err := box.GenerateKey(rand.Reader)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tskPub, skPriv, err := ed25519.GenerateKey(rand.Reader)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tck := &fastly.ClientKey{\n\t\tPublicKey: ckPub[:],\n\t\tSignature: ed25519.Sign(skPriv, ckPub[:]),\n\t\tExpiresAt: time.Now().Add(time.Hour),\n\t}\n\n\tmockCreateClientKey := func(_ context.Context) (*fastly.ClientKey, error) { return ck, nil }\n\tmockGetSigningKey := func(_ context.Context) (ed25519.PublicKey, error) { return skPub, nil }\n\n\tdecrypt := func(ciphertext []byte) (string, error) {\n\t\tplaintext, ok := box.OpenAnonymous(nil, ciphertext, ckPub, ckPriv)\n\t\tif !ok {\n\t\t\treturn \"\", errors.New(\"failed to decrypt\")\n\t\t}\n\t\treturn string(plaintext), nil\n\t}\n\n\tscenarios := []struct {\n\t\targs           string\n\t\tstdin          string\n\t\tapi            mock.API\n\t\twantAPIInvoked bool\n\t\twantError      string\n\t\twantOutput     string\n\t}{\n\t\t{\n\t\t\targs:      \"create --name test\",\n\t\t\twantError: \"error parsing arguments: required flag --store-id not provided\",\n\t\t},\n\t\t{\n\t\t\targs:      \"create --store-id abc123\",\n\t\t\twantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\targs: fmt.Sprintf(\"create --store-id %s --name %s --file %s\", storeID, secretName, doesNotExistFile),\n\t\t\twantError: func() string {\n\t\t\t\tif runtime.GOOS == \"windows\" {\n\t\t\t\t\treturn \"The system cannot find the file specified\"\n\t\t\t\t}\n\t\t\t\treturn \"no such file or directory\"\n\t\t\t}(),\n\t\t},\n\t\t{\n\t\t\targs:      fmt.Sprintf(\"create --store-id %s --name %s --stdin\", storeID, secretName),\n\t\t\twantError: \"unable to read from STDIN\",\n\t\t},\n\t\t{\n\t\t\targs:      fmt.Sprintf(\"create --store-id %s --name %s --stdin --recreate --recreate-allow\", storeID, secretName),\n\t\t\twantError: \"invalid flag combination, --recreate and --recreate-allow\",\n\t\t},\n\t\t// Read from STDIN.\n\t\t{\n\t\t\targs:  fmt.Sprintf(\"create --store-id %s --name %s --stdin\", storeID, secretName),\n\t\t\tstdin: secretValue,\n\t\t\tapi: mock.API{\n\t\t\t\tCreateClientKeyFn: mockCreateClientKey,\n\t\t\t\tGetSigningKeyFn:   mockGetSigningKey,\n\t\t\t\tCreateSecretFn: func(_ context.Context, i *fastly.CreateSecretInput) (*fastly.Secret, error) {\n\t\t\t\t\tif got, err := decrypt(i.Secret); err != nil {\n\t\t\t\t\t\treturn nil, err\n\t\t\t\t\t} else if got != secretValue {\n\t\t\t\t\t\treturn nil, fmt.Errorf(\"invalid secret: %s\", got)\n\t\t\t\t\t}\n\t\t\t\t\treturn &fastly.Secret{\n\t\t\t\t\t\tName:   i.Name,\n\t\t\t\t\t\tDigest: []byte(secretDigest),\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\twantAPIInvoked: true,\n\t\t\twantOutput:     fstfmt.Success(\"Created secret '%s' in Secret Store '%s' (digest: %s)\", secretName, storeID, hex.EncodeToString([]byte(secretDigest))),\n\t\t},\n\t\t// Read from file.\n\t\t{\n\t\t\targs: fmt.Sprintf(\"create --store-id %s --name %s --file %s\", storeID, secretName, secretFile),\n\t\t\tapi: mock.API{\n\t\t\t\tCreateClientKeyFn: mockCreateClientKey,\n\t\t\t\tGetSigningKeyFn:   mockGetSigningKey,\n\t\t\t\tCreateSecretFn: func(_ context.Context, i *fastly.CreateSecretInput) (*fastly.Secret, error) {\n\t\t\t\t\tif got, err := decrypt(i.Secret); err != nil {\n\t\t\t\t\t\treturn nil, err\n\t\t\t\t\t} else if got != secretValue {\n\t\t\t\t\t\treturn nil, fmt.Errorf(\"invalid secret: %s\", got)\n\t\t\t\t\t}\n\t\t\t\t\treturn &fastly.Secret{\n\t\t\t\t\t\tName:   i.Name,\n\t\t\t\t\t\tDigest: []byte(secretDigest),\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\twantAPIInvoked: true,\n\t\t\twantOutput:     fstfmt.Success(\"Created secret '%s' in Secret Store '%s' (digest: %s)\", secretName, storeID, hex.EncodeToString([]byte(secretDigest))),\n\t\t},\n\t\t{\n\t\t\targs: fmt.Sprintf(\"create --store-id %s --name %s --file %s --json\", storeID, secretName, secretFile),\n\t\t\tapi: mock.API{\n\t\t\t\tCreateClientKeyFn: mockCreateClientKey,\n\t\t\t\tGetSigningKeyFn:   mockGetSigningKey,\n\t\t\t\tCreateSecretFn: func(_ context.Context, i *fastly.CreateSecretInput) (*fastly.Secret, error) {\n\t\t\t\t\tif got, err := decrypt(i.Secret); err != nil {\n\t\t\t\t\t\treturn nil, err\n\t\t\t\t\t} else if got != secretValue {\n\t\t\t\t\t\treturn nil, fmt.Errorf(\"invalid secret: %s\", got)\n\t\t\t\t\t}\n\t\t\t\t\treturn &fastly.Secret{\n\t\t\t\t\t\tName:   i.Name,\n\t\t\t\t\t\tDigest: []byte(secretDigest),\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\twantAPIInvoked: true,\n\t\t\twantOutput: fstfmt.EncodeJSON(&fastly.Secret{\n\t\t\t\tName:   secretName,\n\t\t\t\tDigest: []byte(secretDigest),\n\t\t\t}),\n\t\t},\n\t\t// CreateOrRecreate\n\t\t{\n\t\t\targs: fmt.Sprintf(\"create --store-id %s --name %s --file %s --json --recreate-allow\", storeID, secretName, secretFile),\n\t\t\tapi: mock.API{\n\t\t\t\tCreateClientKeyFn: mockCreateClientKey,\n\t\t\t\tGetSigningKeyFn:   mockGetSigningKey,\n\t\t\t\tCreateSecretFn: func(_ context.Context, i *fastly.CreateSecretInput) (*fastly.Secret, error) {\n\t\t\t\t\tif got, want := i.Method, http.MethodPut; got != want {\n\t\t\t\t\t\treturn nil, fmt.Errorf(\"got method %q, want %q\", got, want)\n\t\t\t\t\t}\n\t\t\t\t\tif got, err := decrypt(i.Secret); err != nil {\n\t\t\t\t\t\treturn nil, err\n\t\t\t\t\t} else if got != secretValue {\n\t\t\t\t\t\treturn nil, fmt.Errorf(\"invalid secret: %s\", got)\n\t\t\t\t\t}\n\t\t\t\t\treturn &fastly.Secret{\n\t\t\t\t\t\tName:   i.Name,\n\t\t\t\t\t\tDigest: []byte(secretDigest),\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\twantAPIInvoked: true,\n\t\t\twantOutput: fstfmt.EncodeJSON(&fastly.Secret{\n\t\t\t\tName:   secretName,\n\t\t\t\tDigest: []byte(secretDigest),\n\t\t\t}),\n\t\t},\n\t\t// Recreate\n\t\t{\n\t\t\targs: fmt.Sprintf(\"create --store-id %s --name %s --file %s --json --recreate\", storeID, secretName, secretFile),\n\t\t\tapi: mock.API{\n\t\t\t\tCreateClientKeyFn: mockCreateClientKey,\n\t\t\t\tGetSigningKeyFn:   mockGetSigningKey,\n\t\t\t\tCreateSecretFn: func(_ context.Context, i *fastly.CreateSecretInput) (*fastly.Secret, error) {\n\t\t\t\t\tif got, want := i.Method, http.MethodPatch; got != want {\n\t\t\t\t\t\treturn nil, fmt.Errorf(\"got method %q, want %q\", got, want)\n\t\t\t\t\t}\n\t\t\t\t\tif got, err := decrypt(i.Secret); err != nil {\n\t\t\t\t\t\treturn nil, err\n\t\t\t\t\t} else if got != secretValue {\n\t\t\t\t\t\treturn nil, fmt.Errorf(\"invalid secret: %s\", got)\n\t\t\t\t\t}\n\t\t\t\t\treturn &fastly.Secret{\n\t\t\t\t\t\tName:      i.Name,\n\t\t\t\t\t\tDigest:    []byte(secretDigest),\n\t\t\t\t\t\tRecreated: true,\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\twantAPIInvoked: true,\n\t\t\twantOutput: fstfmt.EncodeJSON(&fastly.Secret{\n\t\t\t\tName:      secretName,\n\t\t\t\tDigest:    []byte(secretDigest),\n\t\t\t\tRecreated: true,\n\t\t\t}),\n\t\t},\n\t}\n\n\tfor _, testcase := range scenarios {\n\t\ttestcase := testcase\n\t\tt.Run(testcase.args, func(t *testing.T) {\n\t\t\tvar stdout bytes.Buffer\n\t\t\targs := testutil.SplitArgs(secretstoreentry.RootNameSecret + \" \" + testcase.args)\n\t\t\topts := testutil.MockGlobalData(args, &stdout)\n\t\t\tif testcase.stdin != \"\" {\n\t\t\t\tvar stdin bytes.Buffer\n\t\t\t\tstdin.WriteString(testcase.stdin)\n\t\t\t\topts.Input = &stdin\n\t\t\t}\n\n\t\t\tf := testcase.api.CreateSecretFn\n\t\t\tvar apiInvoked bool\n\t\t\ttestcase.api.CreateSecretFn = func(ctx context.Context, i *fastly.CreateSecretInput) (*fastly.Secret, error) {\n\t\t\t\tapiInvoked = true\n\t\t\t\treturn f(ctx, i)\n\t\t\t}\n\n\t\t\t// Tests generate their own signing keys, which won't match\n\t\t\t// the hardcoded value.  Disable the check against the\n\t\t\t// hardcoded value.\n\t\t\tt.Setenv(\"FASTLY_USE_API_SIGNING_KEY\", \"1\")\n\n\t\t\tapp.Init = func(_ []string, _ io.Reader) (*global.Data, error) {\n\t\t\t\topts.APIClientFactory = mock.APIClient(testcase.api)\n\t\t\t\treturn opts, nil\n\t\t\t}\n\t\t\terr := app.Run(args, nil)\n\n\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\ttestutil.AssertString(t, testcase.wantOutput, stdout.String())\n\t\t\tif apiInvoked != testcase.wantAPIInvoked {\n\t\t\t\tt.Fatalf(\"API CreateSecret invoked = %v, want %v\", apiInvoked, testcase.wantAPIInvoked)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestDeleteSecretCommand(t *testing.T) {\n\tconst (\n\t\tstoreID    = \"test123\"\n\t\tsecretName = \"testName\"\n\t)\n\n\tscenarios := []struct {\n\t\targs           string\n\t\tapi            mock.API\n\t\twantAPIInvoked bool\n\t\twantError      string\n\t\twantOutput     string\n\t}{\n\t\t{\n\t\t\targs:      \"delete --name test\",\n\t\t\twantError: \"error parsing arguments: required flag --store-id not provided\",\n\t\t},\n\t\t{\n\t\t\targs:      \"delete --store-id test\",\n\t\t\twantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\targs: fmt.Sprintf(\"delete --store-id %s --name DOES-NOT-EXIST\", storeID),\n\t\t\tapi: mock.API{\n\t\t\t\tDeleteSecretFn: func(_ context.Context, i *fastly.DeleteSecretInput) error {\n\t\t\t\t\tif i.StoreID != storeID || i.Name != secretName {\n\t\t\t\t\t\treturn errors.New(\"not found\")\n\t\t\t\t\t}\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t},\n\t\t\twantAPIInvoked: true,\n\t\t\twantError:      \"not found\",\n\t\t},\n\t\t{\n\t\t\targs: fmt.Sprintf(\"delete --store-id %s --name %s\", storeID, secretName),\n\t\t\tapi: mock.API{\n\t\t\t\tDeleteSecretFn: func(_ context.Context, i *fastly.DeleteSecretInput) error {\n\t\t\t\t\tif i.StoreID != storeID || i.Name != secretName {\n\t\t\t\t\t\treturn errors.New(\"not found\")\n\t\t\t\t\t}\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t},\n\t\t\twantAPIInvoked: true,\n\t\t\twantOutput:     fstfmt.Success(\"Deleted secret '%s' from Secret Store '%s'\", secretName, storeID),\n\t\t},\n\t\t{\n\t\t\targs: fmt.Sprintf(\"delete --store-id %s --name %s --json\", storeID, secretName),\n\t\t\tapi: mock.API{\n\t\t\t\tDeleteSecretFn: func(_ context.Context, i *fastly.DeleteSecretInput) error {\n\t\t\t\t\tif i.StoreID != storeID || i.Name != secretName {\n\t\t\t\t\t\treturn errors.New(\"not found\")\n\t\t\t\t\t}\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t},\n\t\t\twantAPIInvoked: true,\n\t\t\twantOutput:     fstfmt.JSON(`{\"name\": %q, \"store_id\": %q,  \"deleted\": true}`, secretName, storeID),\n\t\t},\n\t}\n\n\tfor _, testcase := range scenarios {\n\t\ttestcase := testcase\n\t\tt.Run(testcase.args, func(t *testing.T) {\n\t\t\tvar stdout bytes.Buffer\n\t\t\targs := testutil.SplitArgs(secretstoreentry.RootNameSecret + \" \" + testcase.args)\n\t\t\topts := testutil.MockGlobalData(args, &stdout)\n\n\t\t\tf := testcase.api.DeleteSecretFn\n\t\t\tvar apiInvoked bool\n\t\t\ttestcase.api.DeleteSecretFn = func(ctx context.Context, i *fastly.DeleteSecretInput) error {\n\t\t\t\tapiInvoked = true\n\t\t\t\treturn f(ctx, i)\n\t\t\t}\n\n\t\t\tapp.Init = func(_ []string, _ io.Reader) (*global.Data, error) {\n\t\t\t\topts.APIClientFactory = mock.APIClient(testcase.api)\n\t\t\t\treturn opts, nil\n\t\t\t}\n\t\t\terr := app.Run(args, nil)\n\n\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\ttestutil.AssertString(t, testcase.wantOutput, stdout.String())\n\t\t\tif apiInvoked != testcase.wantAPIInvoked {\n\t\t\t\tt.Fatalf(\"API DeleteSecret invoked = %v, want %v\", apiInvoked, testcase.wantAPIInvoked)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestDescribeSecretCommand(t *testing.T) {\n\tconst (\n\t\tstoreID     = \"testid\"\n\t\tstoreName   = \"testname\"\n\t\tstoreDigest = \"testdigest\"\n\t)\n\n\tscenarios := []struct {\n\t\targs           string\n\t\tapi            mock.API\n\t\twantAPIInvoked bool\n\t\twantError      string\n\t\twantOutput     string\n\t}{\n\t\t{\n\t\t\targs:      \"get --store-id abc\",\n\t\t\twantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\targs:      \"get --name abc\",\n\t\t\twantError: \"error parsing arguments: required flag --store-id not provided\",\n\t\t},\n\t\t{\n\t\t\targs: fmt.Sprintf(\"get --store-id %s --name %s\", \"DOES-NOT-EXIST\", storeName),\n\t\t\tapi: mock.API{\n\t\t\t\tGetSecretFn: func(_ context.Context, i *fastly.GetSecretInput) (*fastly.Secret, error) {\n\t\t\t\t\tif i.StoreID != storeID || i.Name != storeName {\n\t\t\t\t\t\treturn nil, errors.New(\"invalid request\")\n\t\t\t\t\t}\n\t\t\t\t\treturn &fastly.Secret{\n\t\t\t\t\t\tName:   storeName,\n\t\t\t\t\t\tDigest: []byte(storeDigest),\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\twantAPIInvoked: true,\n\t\t\twantError:      \"invalid request\",\n\t\t},\n\t\t{\n\t\t\targs: fmt.Sprintf(\"get --store-id %s --name %s\", storeID, storeName),\n\t\t\tapi: mock.API{\n\t\t\t\tGetSecretFn: func(_ context.Context, i *fastly.GetSecretInput) (*fastly.Secret, error) {\n\t\t\t\t\tif i.StoreID != storeID || i.Name != storeName {\n\t\t\t\t\t\treturn nil, errors.New(\"invalid request\")\n\t\t\t\t\t}\n\t\t\t\t\treturn &fastly.Secret{\n\t\t\t\t\t\tName:   storeName,\n\t\t\t\t\t\tDigest: []byte(storeDigest),\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\twantAPIInvoked: true,\n\t\t\twantOutput: fmtSecret(&fastly.Secret{\n\t\t\t\tName:   storeName,\n\t\t\t\tDigest: []byte(storeDigest),\n\t\t\t}),\n\t\t},\n\t\t{\n\t\t\targs: fmt.Sprintf(\"get --store-id %s --name %s --json\", storeID, storeName),\n\t\t\tapi: mock.API{\n\t\t\t\tGetSecretFn: func(_ context.Context, i *fastly.GetSecretInput) (*fastly.Secret, error) {\n\t\t\t\t\tif i.StoreID != storeID || i.Name != storeName {\n\t\t\t\t\t\treturn nil, errors.New(\"invalid request\")\n\t\t\t\t\t}\n\t\t\t\t\treturn &fastly.Secret{\n\t\t\t\t\t\tName:   storeName,\n\t\t\t\t\t\tDigest: []byte(storeDigest),\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\twantAPIInvoked: true,\n\t\t\twantOutput: fstfmt.EncodeJSON(&fastly.Secret{\n\t\t\t\tName:   storeName,\n\t\t\t\tDigest: []byte(storeDigest),\n\t\t\t}),\n\t\t},\n\t}\n\n\tfor _, testcase := range scenarios {\n\t\ttestcase := testcase\n\t\tt.Run(testcase.args, func(t *testing.T) {\n\t\t\tvar stdout bytes.Buffer\n\t\t\targs := testutil.SplitArgs(secretstoreentry.RootNameSecret + \" \" + testcase.args)\n\t\t\topts := testutil.MockGlobalData(args, &stdout)\n\n\t\t\tf := testcase.api.GetSecretFn\n\t\t\tvar apiInvoked bool\n\t\t\ttestcase.api.GetSecretFn = func(ctx context.Context, i *fastly.GetSecretInput) (*fastly.Secret, error) {\n\t\t\t\tapiInvoked = true\n\t\t\t\treturn f(ctx, i)\n\t\t\t}\n\n\t\t\tapp.Init = func(_ []string, _ io.Reader) (*global.Data, error) {\n\t\t\t\topts.APIClientFactory = mock.APIClient(testcase.api)\n\t\t\t\treturn opts, nil\n\t\t\t}\n\t\t\terr := app.Run(args, nil)\n\n\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\ttestutil.AssertString(t, testcase.wantOutput, stdout.String())\n\t\t\tif apiInvoked != testcase.wantAPIInvoked {\n\t\t\t\tt.Fatalf(\"API GetSecret invoked = %v, want %v\", apiInvoked, testcase.wantAPIInvoked)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestListSecretsCommand(t *testing.T) {\n\tconst (\n\t\tsecretName = \"test123\"\n\t\tstoreID    = \"store-id-123\"\n\t)\n\n\tsecrets := &fastly.Secrets{\n\t\tMeta: fastly.SecretStoreMeta{\n\t\t\tLimit:      123,\n\t\t\tNextCursor: \"abc\",\n\t\t},\n\t\tData: []fastly.Secret{\n\t\t\t{Name: secretName, Digest: []byte(secretName)},\n\t\t},\n\t}\n\n\tscenarios := []struct {\n\t\targs           string\n\t\tapi            mock.API\n\t\twantAPIInvoked bool\n\t\twantError      string\n\t\twantOutput     string\n\t}{\n\t\t{\n\t\t\targs:      \"list\",\n\t\t\twantError: \"required flag --store-id not provided\",\n\t\t},\n\t\t{\n\t\t\targs: fmt.Sprintf(\"list --store-id %s\", storeID),\n\t\t\tapi: mock.API{\n\t\t\t\tListSecretsFn: func(_ context.Context, _ *fastly.ListSecretsInput) (*fastly.Secrets, error) {\n\t\t\t\t\treturn secrets, errors.New(\"unknown error\")\n\t\t\t\t},\n\t\t\t},\n\t\t\twantAPIInvoked: true,\n\t\t\twantError:      \"unknown error\",\n\t\t},\n\t\t{\n\t\t\targs: fmt.Sprintf(\"list --store-id %s\", storeID),\n\t\t\tapi: mock.API{\n\t\t\t\tListSecretsFn: func(_ context.Context, _ *fastly.ListSecretsInput) (*fastly.Secrets, error) {\n\t\t\t\t\treturn secrets, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\twantAPIInvoked: true,\n\t\t\twantOutput:     fmtSecrets(secrets),\n\t\t},\n\t\t{\n\t\t\targs: fmt.Sprintf(\"list --store-id %s --json\", storeID),\n\t\t\tapi: mock.API{\n\t\t\t\tListSecretsFn: func(_ context.Context, _ *fastly.ListSecretsInput) (*fastly.Secrets, error) {\n\t\t\t\t\treturn secrets, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\twantAPIInvoked: true,\n\t\t\twantOutput:     fstfmt.EncodeJSON(secrets),\n\t\t},\n\t}\n\n\tfor _, testcase := range scenarios {\n\t\ttestcase := testcase\n\t\tt.Run(testcase.args, func(t *testing.T) {\n\t\t\tvar stdout bytes.Buffer\n\t\t\targs := testutil.SplitArgs(secretstoreentry.RootNameSecret + \" \" + testcase.args)\n\t\t\topts := testutil.MockGlobalData(args, &stdout)\n\n\t\t\tf := testcase.api.ListSecretsFn\n\t\t\tvar apiInvoked bool\n\t\t\ttestcase.api.ListSecretsFn = func(ctx context.Context, i *fastly.ListSecretsInput) (*fastly.Secrets, error) {\n\t\t\t\tapiInvoked = true\n\t\t\t\treturn f(ctx, i)\n\t\t\t}\n\n\t\t\tapp.Init = func(_ []string, _ io.Reader) (*global.Data, error) {\n\t\t\t\topts.APIClientFactory = mock.APIClient(testcase.api)\n\t\t\t\treturn opts, nil\n\t\t\t}\n\t\t\terr := app.Run(args, nil)\n\n\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\ttestutil.AssertString(t, testcase.wantOutput, stdout.String())\n\t\t\tif apiInvoked != testcase.wantAPIInvoked {\n\t\t\t\tt.Fatalf(\"API ListSecrets invoked = %v, want %v\", apiInvoked, testcase.wantAPIInvoked)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/commands/service/acl/acl_test.go",
    "content": "package acl_test\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\troot \"github.com/fastly/cli/pkg/commands/service\"\n\tsub \"github.com/fastly/cli/pkg/commands/service/acl\"\n\t\"github.com/fastly/cli/pkg/mock\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n)\n\nfunc TestACLCreate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --name flag\",\n\t\t\tArgs:      \"--version 3\",\n\t\t\tEnvVars:   map[string]string{\"FASTLY_SERVICE_ID\": \"\"},\n\t\t\tWantError: \"error reading service: no service ID found\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --version flag\",\n\t\t\tArgs:      \"--name foo\",\n\t\t\tWantError: \"error parsing arguments: required flag --version not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --service-id flag\",\n\t\t\tArgs:      \"--name foo --version 3\",\n\t\t\tEnvVars:   map[string]string{\"FASTLY_SERVICE_ID\": \"\"},\n\t\t\tWantError: \"error reading service: no service ID found\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate CreateACL API error\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tCreateACLFn: func(_ context.Context, _ *fastly.CreateACLInput) (*fastly.ACL, error) {\n\t\t\t\t\treturn nil, testutil.Err\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:      \"--name foo --service-id 123 --version 3\",\n\t\t\tWantError: testutil.Err.Error(),\n\t\t},\n\t\t{\n\t\t\tName: \"validate CreateACL API success\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tCreateACLFn: func(_ context.Context, i *fastly.CreateACLInput) (*fastly.ACL, error) {\n\t\t\t\t\treturn &fastly.ACL{\n\t\t\t\t\t\tACLID:          fastly.ToPointer(\"456\"),\n\t\t\t\t\t\tName:           i.Name,\n\t\t\t\t\t\tServiceID:      fastly.ToPointer(i.ServiceID),\n\t\t\t\t\t\tServiceVersion: fastly.ToPointer(i.ServiceVersion),\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:       \"--name foo --service-id 123 --version 3\",\n\t\t\tWantOutput: \"Created ACL 'foo' (id: 456, service: 123, version: 3)\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate --autoclone results in cloned service version\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tCreateACLFn: func(_ context.Context, i *fastly.CreateACLInput) (*fastly.ACL, error) {\n\t\t\t\t\treturn &fastly.ACL{\n\t\t\t\t\t\tACLID:          fastly.ToPointer(\"456\"),\n\t\t\t\t\t\tName:           i.Name,\n\t\t\t\t\t\tServiceID:      fastly.ToPointer(i.ServiceID),\n\t\t\t\t\t\tServiceVersion: fastly.ToPointer(i.ServiceVersion),\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:       \"--autoclone --name foo --service-id 123 --version 1\",\n\t\t\tWantOutput: \"Created ACL 'foo' (id: 456, service: 123, version: 4)\",\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"create\"}, scenarios)\n}\n\nfunc TestACLDelete(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --name flag\",\n\t\t\tArgs:      \"--version 3\",\n\t\t\tEnvVars:   map[string]string{\"FASTLY_SERVICE_ID\": \"\"},\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --version flag\",\n\t\t\tArgs:      \"--name foobar\",\n\t\t\tWantError: \"error parsing arguments: required flag --version not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --service-id flag\",\n\t\t\tArgs:      \"--name foobar --version 3\",\n\t\t\tEnvVars:   map[string]string{\"FASTLY_SERVICE_ID\": \"\"},\n\t\t\tWantError: \"error reading service: no service ID found\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate DeleteACL API error\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tDeleteACLFn: func(_ context.Context, _ *fastly.DeleteACLInput) error {\n\t\t\t\t\treturn testutil.Err\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:      \"--name foobar --service-id 123 --version 3\",\n\t\t\tWantError: testutil.Err.Error(),\n\t\t},\n\t\t{\n\t\t\tName: \"validate DeleteACL API success\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tDeleteACLFn: func(_ context.Context, _ *fastly.DeleteACLInput) error {\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:       \"--name foobar --service-id 123 --version 3\",\n\t\t\tWantOutput: \"Deleted ACL 'foobar' (service: 123, version: 3)\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API error when modifying active version\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tDeleteACLFn: func(_ context.Context, i *fastly.DeleteACLInput) error {\n\t\t\t\t\treturn fmt.Errorf(\"Cannot update version %d. Versions that have been activated cannot be updated\", i.ServiceVersion)\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:      \"--name foobar --service-id 123 --version 3\",\n\t\t\tWantError: \"Cannot update version 3. Versions that have been activated cannot be updated\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API error when modifying locked version\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tDeleteACLFn: func(_ context.Context, i *fastly.DeleteACLInput) error {\n\t\t\t\t\treturn fmt.Errorf(\"Cannot update version %d. Versions that have been locked cannot be updated\", i.ServiceVersion)\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:      \"--name foobar --service-id 123 --version 3\",\n\t\t\tWantError: \"Cannot update version 3. Versions that have been locked cannot be updated\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate --autoclone results in cloned service version\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tDeleteACLFn: func(_ context.Context, _ *fastly.DeleteACLInput) error {\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:       \"--autoclone --name foo --service-id 123 --version 1\",\n\t\t\tWantOutput: \"Deleted ACL 'foo' (service: 123, version: 4)\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate --autoclone on locked version\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tDeleteACLFn: func(_ context.Context, i *fastly.DeleteACLInput) error {\n\t\t\t\t\t// Verify operation happens on the cloned version (4), not original (2)\n\t\t\t\t\tif i.ServiceVersion != 4 {\n\t\t\t\t\t\treturn fmt.Errorf(\"expected operation on cloned version 4, got %d\", i.ServiceVersion)\n\t\t\t\t\t}\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:       \"--autoclone --name foo --service-id 123 --version 2\",\n\t\t\tWantOutput: \"Deleted ACL 'foo' (service: 123, version: 4)\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate --autoclone on editable version\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tDeleteACLFn: func(_ context.Context, i *fastly.DeleteACLInput) error {\n\t\t\t\t\t// Verify operation happens on the cloned version (4), not original (3)\n\t\t\t\t\tif i.ServiceVersion != 4 {\n\t\t\t\t\t\treturn fmt.Errorf(\"expected operation on cloned version 4, got %d\", i.ServiceVersion)\n\t\t\t\t\t}\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:       \"--autoclone --name foo --service-id 123 --version 3\",\n\t\t\tWantOutput: \"Deleted ACL 'foo' (service: 123, version: 4)\",\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"delete\"}, scenarios)\n}\n\nfunc TestACLDescribe(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --name flag\",\n\t\t\tArgs:      \"--version 3\",\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --version flag\",\n\t\t\tArgs:      \"--name foobar\",\n\t\t\tWantError: \"error parsing arguments: required flag --version not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --service-id flag\",\n\t\t\tArgs:      \"--name foobar --version 3\",\n\t\t\tEnvVars:   map[string]string{\"FASTLY_SERVICE_ID\": \"\"},\n\t\t\tWantError: \"error reading service: no service ID found\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate GetACL API error\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tGetACLFn: func(_ context.Context, _ *fastly.GetACLInput) (*fastly.ACL, error) {\n\t\t\t\t\treturn nil, testutil.Err\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:      \"--name foobar --service-id 123 --version 3\",\n\t\t\tWantError: testutil.Err.Error(),\n\t\t},\n\t\t{\n\t\t\tName: \"validate GetACL API success\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tGetACLFn:     getACL,\n\t\t\t},\n\t\t\tArgs:       \"--name foobar --service-id 123 --version 3\",\n\t\t\tWantOutput: \"\\nService ID: 123\\nService Version: 3\\n\\nName: foobar\\nID: 456\\n\\nCreated at: 2021-06-15 23:00:00 +0000 UTC\\nUpdated at: 2021-06-15 23:00:00 +0000 UTC\\nDeleted at: 2021-06-15 23:00:00 +0000 UTC\\n\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate missing --autoclone flag is OK\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tGetACLFn:     getACL,\n\t\t\t},\n\t\t\tArgs:       \"--name foobar --service-id 123 --version 1\",\n\t\t\tWantOutput: \"\\nService ID: 123\\nService Version: 1\\n\\nName: foobar\\nID: 456\\n\\nCreated at: 2021-06-15 23:00:00 +0000 UTC\\nUpdated at: 2021-06-15 23:00:00 +0000 UTC\\nDeleted at: 2021-06-15 23:00:00 +0000 UTC\\n\",\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"describe\"}, scenarios)\n}\n\nfunc TestACLList(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --version flag\",\n\t\t\tWantError: \"error parsing arguments: required flag --version not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --service-id flag\",\n\t\t\tArgs:      \"--version 3\",\n\t\t\tEnvVars:   map[string]string{\"FASTLY_SERVICE_ID\": \"\"},\n\t\t\tWantError: \"error reading service: no service ID found\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate ListACLs API error\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tListACLsFn: func(_ context.Context, _ *fastly.ListACLsInput) ([]*fastly.ACL, error) {\n\t\t\t\t\treturn nil, testutil.Err\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:      \"--service-id 123 --version 3\",\n\t\t\tWantError: testutil.Err.Error(),\n\t\t},\n\t\t{\n\t\t\tName: \"validate ListACLs API success\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tListACLsFn:   listACLs,\n\t\t\t},\n\t\t\tArgs:       \"--service-id 123 --version 3\",\n\t\t\tWantOutput: \"SERVICE ID  VERSION  NAME  ID\\n123         3        foo   456\\n123         3        bar   789\\n\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate missing --autoclone flag is OK\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tListACLsFn:   listACLs,\n\t\t\t},\n\t\t\tArgs:       \"--service-id 123 --version 1\",\n\t\t\tWantOutput: \"SERVICE ID  VERSION  NAME  ID\\n123         1        foo   456\\n123         1        bar   789\\n\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate --verbose flag\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tListACLsFn:   listACLs,\n\t\t\t},\n\t\t\tArgs:       \"--service-id 123 --verbose --version 1\",\n\t\t\tWantOutput: \"Fastly API endpoint: https://api.fastly.com\\nFastly API token provided via config file (auth: user)\\n\\nService ID (via --service-id): 123\\n\\nService Version: 1\\n\\nName: foo\\nID: 456\\n\\nCreated at: 2021-06-15 23:00:00 +0000 UTC\\nUpdated at: 2021-06-15 23:00:00 +0000 UTC\\nDeleted at: 2021-06-15 23:00:00 +0000 UTC\\n\\nName: bar\\nID: 789\\n\\nCreated at: 2021-06-15 23:00:00 +0000 UTC\\nUpdated at: 2021-06-15 23:00:00 +0000 UTC\\nDeleted at: 2021-06-15 23:00:00 +0000 UTC\\n\\n\",\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"list\"}, scenarios)\n}\n\nfunc TestACLUpdate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --name flag\",\n\t\t\tArgs:      \"--new-name beepboop --version 3\",\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --new-name flag\",\n\t\t\tArgs:      \"--name foobar --version 3\",\n\t\t\tWantError: \"error parsing arguments: required flag --new-name not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --version flag\",\n\t\t\tArgs:      \"--name foobar --new-name beepboop\",\n\t\t\tWantError: \"error parsing arguments: required flag --version not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --service-id flag\",\n\t\t\tArgs:      \"--name foobar --new-name beepboop --version 3\",\n\t\t\tEnvVars:   map[string]string{\"FASTLY_SERVICE_ID\": \"\"},\n\t\t\tWantError: \"error reading service: no service ID found\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate UpdateACL API error\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tUpdateACLFn: func(_ context.Context, _ *fastly.UpdateACLInput) (*fastly.ACL, error) {\n\t\t\t\t\treturn nil, testutil.Err\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:      \"--name foobar --new-name beepboop --service-id 123 --version 3\",\n\t\t\tWantError: testutil.Err.Error(),\n\t\t},\n\t\t{\n\t\t\tName: \"validate UpdateACL API success\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tUpdateACLFn: func(_ context.Context, i *fastly.UpdateACLInput) (*fastly.ACL, error) {\n\t\t\t\t\treturn &fastly.ACL{\n\t\t\t\t\t\tACLID:          fastly.ToPointer(\"456\"),\n\t\t\t\t\t\tName:           i.NewName,\n\t\t\t\t\t\tServiceID:      fastly.ToPointer(i.ServiceID),\n\t\t\t\t\t\tServiceVersion: fastly.ToPointer(i.ServiceVersion),\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:       \"--name foobar --new-name beepboop --service-id 123 --version 3\",\n\t\t\tWantOutput: \"Updated ACL 'beepboop' (previously: 'foobar', service: 123, version: 3)\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API error when modifying active version\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tUpdateACLFn: func(_ context.Context, i *fastly.UpdateACLInput) (*fastly.ACL, error) {\n\t\t\t\t\treturn nil, fmt.Errorf(\"Cannot update version %d. Versions that have been activated cannot be updated\", i.ServiceVersion)\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:      \"--name foobar --new-name beepboop --service-id 123 --version 3\",\n\t\t\tWantError: \"Cannot update version 3. Versions that have been activated cannot be updated\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API error when modifying locked version\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tUpdateACLFn: func(_ context.Context, i *fastly.UpdateACLInput) (*fastly.ACL, error) {\n\t\t\t\t\treturn nil, fmt.Errorf(\"Cannot update version %d. Versions that have been locked cannot be updated\", i.ServiceVersion)\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:      \"--name foobar --new-name beepboop --service-id 123 --version 3\",\n\t\t\tWantError: \"Cannot update version 3. Versions that have been locked cannot be updated\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate --autoclone results in cloned service version\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tUpdateACLFn: func(_ context.Context, i *fastly.UpdateACLInput) (*fastly.ACL, error) {\n\t\t\t\t\treturn &fastly.ACL{\n\t\t\t\t\t\tACLID:          fastly.ToPointer(\"456\"),\n\t\t\t\t\t\tName:           i.NewName,\n\t\t\t\t\t\tServiceID:      fastly.ToPointer(i.ServiceID),\n\t\t\t\t\t\tServiceVersion: fastly.ToPointer(i.ServiceVersion),\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:       \"--autoclone --name foobar --new-name beepboop --service-id 123 --version 1\",\n\t\t\tWantOutput: \"Updated ACL 'beepboop' (previously: 'foobar', service: 123, version: 4)\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate --autoclone on locked version\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tUpdateACLFn: func(_ context.Context, i *fastly.UpdateACLInput) (*fastly.ACL, error) {\n\t\t\t\t\t// Verify operation happens on the cloned version (4), not original (2)\n\t\t\t\t\tif i.ServiceVersion != 4 {\n\t\t\t\t\t\treturn nil, fmt.Errorf(\"expected operation on cloned version 4, got %d\", i.ServiceVersion)\n\t\t\t\t\t}\n\t\t\t\t\treturn &fastly.ACL{\n\t\t\t\t\t\tACLID:          fastly.ToPointer(\"456\"),\n\t\t\t\t\t\tName:           i.NewName,\n\t\t\t\t\t\tServiceID:      fastly.ToPointer(i.ServiceID),\n\t\t\t\t\t\tServiceVersion: fastly.ToPointer(i.ServiceVersion),\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:       \"--autoclone --name foobar --new-name beepboop --service-id 123 --version 2\",\n\t\t\tWantOutput: \"Updated ACL 'beepboop' (previously: 'foobar', service: 123, version: 4)\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate --autoclone on editable version\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tUpdateACLFn: func(_ context.Context, i *fastly.UpdateACLInput) (*fastly.ACL, error) {\n\t\t\t\t\t// Verify operation happens on the cloned version (4), not original (3)\n\t\t\t\t\tif i.ServiceVersion != 4 {\n\t\t\t\t\t\treturn nil, fmt.Errorf(\"expected operation on cloned version 4, got %d\", i.ServiceVersion)\n\t\t\t\t\t}\n\t\t\t\t\treturn &fastly.ACL{\n\t\t\t\t\t\tACLID:          fastly.ToPointer(\"456\"),\n\t\t\t\t\t\tName:           i.NewName,\n\t\t\t\t\t\tServiceID:      fastly.ToPointer(i.ServiceID),\n\t\t\t\t\t\tServiceVersion: fastly.ToPointer(i.ServiceVersion),\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:       \"--autoclone --name foobar --new-name beepboop --service-id 123 --version 3\",\n\t\t\tWantOutput: \"Updated ACL 'beepboop' (previously: 'foobar', service: 123, version: 4)\",\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"update\"}, scenarios)\n}\n\nfunc getACL(_ context.Context, i *fastly.GetACLInput) (*fastly.ACL, error) {\n\tt := testutil.Date\n\n\treturn &fastly.ACL{\n\t\tACLID:          fastly.ToPointer(\"456\"),\n\t\tName:           fastly.ToPointer(i.Name),\n\t\tServiceID:      fastly.ToPointer(i.ServiceID),\n\t\tServiceVersion: fastly.ToPointer(i.ServiceVersion),\n\n\t\tCreatedAt: &t,\n\t\tDeletedAt: &t,\n\t\tUpdatedAt: &t,\n\t}, nil\n}\n\nfunc listACLs(_ context.Context, i *fastly.ListACLsInput) ([]*fastly.ACL, error) {\n\tt := testutil.Date\n\tvs := []*fastly.ACL{\n\t\t{\n\t\t\tACLID:          fastly.ToPointer(\"456\"),\n\t\t\tName:           fastly.ToPointer(\"foo\"),\n\t\t\tServiceID:      fastly.ToPointer(i.ServiceID),\n\t\t\tServiceVersion: fastly.ToPointer(i.ServiceVersion),\n\n\t\t\tCreatedAt: &t,\n\t\t\tDeletedAt: &t,\n\t\t\tUpdatedAt: &t,\n\t\t},\n\t\t{\n\t\t\tACLID:          fastly.ToPointer(\"789\"),\n\t\t\tName:           fastly.ToPointer(\"bar\"),\n\t\t\tServiceID:      fastly.ToPointer(i.ServiceID),\n\t\t\tServiceVersion: fastly.ToPointer(i.ServiceVersion),\n\n\t\t\tCreatedAt: &t,\n\t\t\tDeletedAt: &t,\n\t\t\tUpdatedAt: &t,\n\t\t},\n\t}\n\treturn vs, nil\n}\n"
  },
  {
    "path": "pkg/commands/service/acl/create.go",
    "content": "package acl\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\n\tc.CmdClause = parent.Command(\"create\", \"Create a new ACL attached to the specified service version\").Alias(\"add\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.autoClone.Set,\n\t\tDst:    &c.autoClone.Value,\n\t})\n\tc.CmdClause.Flag(\"name\", \"Name for the ACL. Must start with an alphanumeric character and contain only alphanumeric characters, underscores, and whitespace\").Action(c.name.Set).StringVar(&c.name.Value)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\n\treturn &c\n}\n\n// CreateCommand calls the Fastly API to create an appropriate resource.\ntype CreateCommand struct {\n\targparser.Base\n\n\tautoClone      argparser.OptionalAutoClone\n\tname           argparser.OptionalString\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *CreateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(false),\n\t\tLocked:             optional.Of(false),\n\t\tAutoCloneFlag:      c.autoClone,\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tErrLog:             c.Globals.ErrLog,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": errors.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tinput := c.constructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\ta, err := c.Globals.APIClient.CreateACL(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": serviceVersion.Number,\n\t\t})\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Created ACL '%s' (id: %s, service: %s, version: %d)\", fastly.ToValue(a.Name), fastly.ToValue(a.ACLID), fastly.ToValue(a.ServiceID), fastly.ToValue(a.ServiceVersion))\n\treturn nil\n}\n\n// constructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *CreateCommand) constructInput(serviceID string, serviceVersion int) *fastly.CreateACLInput {\n\tinput := fastly.CreateACLInput{\n\t\tServiceID:      serviceID,\n\t\tServiceVersion: serviceVersion,\n\t}\n\tif c.name.WasSet {\n\t\tinput.Name = &c.name.Value\n\t}\n\treturn &input\n}\n"
  },
  {
    "path": "pkg/commands/service/acl/delete.go",
    "content": "package acl\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"delete\", \"Delete an ACL from the specified service version\").Alias(\"remove\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"The name of the ACL to delete\").Required().StringVar(&c.name)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.autoClone.Set,\n\t\tDst:    &c.autoClone.Value,\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\n\treturn &c\n}\n\n// DeleteCommand calls the Fastly API to delete an appropriate resource.\ntype DeleteCommand struct {\n\targparser.Base\n\n\tautoClone      argparser.OptionalAutoClone\n\tname           string\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DeleteCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(false),\n\t\tLocked:             optional.Of(false),\n\t\tAutoCloneFlag:      c.autoClone,\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": errors.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tinput := c.constructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\terr = c.Globals.APIClient.DeleteACL(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fastly.ToValue(serviceVersion.Number),\n\t\t})\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Deleted ACL '%s' (service: %s, version: %d)\", c.name, serviceID, fastly.ToValue(serviceVersion.Number))\n\treturn nil\n}\n\n// constructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *DeleteCommand) constructInput(serviceID string, serviceVersion int) *fastly.DeleteACLInput {\n\tvar input fastly.DeleteACLInput\n\n\tinput.Name = c.name\n\tinput.ServiceID = serviceID\n\tinput.ServiceVersion = serviceVersion\n\n\treturn &input\n}\n"
  },
  {
    "path": "pkg/commands/service/acl/describe.go",
    "content": "package acl\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// NewDescribeCommand returns a usable command registered under the parent.\nfunc NewDescribeCommand(parent argparser.Registerer, g *global.Data) *DescribeCommand {\n\tc := DescribeCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"describe\", \"Retrieve a single ACL by name for the version and service\").Alias(\"get\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"The name of the ACL\").Required().StringVar(&c.name)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\n\treturn &c\n}\n\n// DescribeCommand calls the Fastly API to describe an appropriate resource.\ntype DescribeCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tname           string\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DescribeCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fsterr.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tinput := c.constructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\n\to, err := c.Globals.APIClient.GetACL(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fastly.ToValue(serviceVersion.Number),\n\t\t})\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\treturn c.print(out, o)\n}\n\n// constructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *DescribeCommand) constructInput(serviceID string, serviceVersion int) *fastly.GetACLInput {\n\tvar input fastly.GetACLInput\n\n\tinput.Name = c.name\n\tinput.ServiceID = serviceID\n\tinput.ServiceVersion = serviceVersion\n\n\treturn &input\n}\n\n// print displays the information returned from the API.\nfunc (c *DescribeCommand) print(out io.Writer, a *fastly.ACL) error {\n\tif !c.Globals.Verbose() {\n\t\tfmt.Fprintf(out, \"\\nService ID: %s\\n\", fastly.ToValue(a.ServiceID))\n\t}\n\tfmt.Fprintf(out, \"Service Version: %d\\n\\n\", fastly.ToValue(a.ServiceVersion))\n\tfmt.Fprintf(out, \"Name: %s\\n\", fastly.ToValue(a.Name))\n\tfmt.Fprintf(out, \"ID: %s\\n\\n\", fastly.ToValue(a.ACLID))\n\tif a.CreatedAt != nil {\n\t\tfmt.Fprintf(out, \"Created at: %s\\n\", a.CreatedAt)\n\t}\n\tif a.UpdatedAt != nil {\n\t\tfmt.Fprintf(out, \"Updated at: %s\\n\", a.UpdatedAt)\n\t}\n\tif a.DeletedAt != nil {\n\t\tfmt.Fprintf(out, \"Deleted at: %s\\n\", a.DeletedAt)\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/acl/doc.go",
    "content": "// Package acl contains commands to inspect and manipulate Fastly ACLs.\npackage acl\n"
  },
  {
    "path": "pkg/commands/service/acl/list.go",
    "content": "package acl\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"list\", \"List ACLs\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\n\treturn &c\n}\n\n// ListCommand calls the Fastly API to list appropriate resources.\ntype ListCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *ListCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fsterr.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tinput := c.constructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\n\to, err := c.Globals.APIClient.ListACLs(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fastly.ToValue(serviceVersion.Number),\n\t\t})\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\tif c.Globals.Verbose() {\n\t\tc.printVerbose(out, fastly.ToValue(serviceVersion.Number), o)\n\t} else {\n\t\terr = c.printSummary(out, o)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\n// constructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *ListCommand) constructInput(serviceID string, serviceVersion int) *fastly.ListACLsInput {\n\tvar input fastly.ListACLsInput\n\n\tinput.ServiceID = serviceID\n\tinput.ServiceVersion = serviceVersion\n\n\treturn &input\n}\n\n// printVerbose displays the information returned from the API in a verbose\n// format.\nfunc (c *ListCommand) printVerbose(out io.Writer, serviceVersion int, as []*fastly.ACL) {\n\tfmt.Fprintf(out, \"Service Version: %d\\n\\n\", serviceVersion)\n\n\tfor _, a := range as {\n\t\tfmt.Fprintf(out, \"Name: %s\\n\", fastly.ToValue(a.Name))\n\t\tfmt.Fprintf(out, \"ID: %s\\n\\n\", fastly.ToValue(a.ACLID))\n\n\t\tif a.CreatedAt != nil {\n\t\t\tfmt.Fprintf(out, \"Created at: %s\\n\", a.CreatedAt)\n\t\t}\n\t\tif a.UpdatedAt != nil {\n\t\t\tfmt.Fprintf(out, \"Updated at: %s\\n\", a.UpdatedAt)\n\t\t}\n\t\tif a.DeletedAt != nil {\n\t\t\tfmt.Fprintf(out, \"Deleted at: %s\\n\", a.DeletedAt)\n\t\t}\n\n\t\tfmt.Fprintf(out, \"\\n\")\n\t}\n}\n\n// printSummary displays the information returned from the API in a summarised\n// format.\nfunc (c *ListCommand) printSummary(out io.Writer, as []*fastly.ACL) error {\n\tt := text.NewTable(out)\n\tt.AddHeader(\"SERVICE ID\", \"VERSION\", \"NAME\", \"ID\")\n\tfor _, a := range as {\n\t\tt.AddLine(\n\t\t\tfastly.ToValue(a.ServiceID),\n\t\t\tfastly.ToValue(a.ServiceVersion),\n\t\t\tfastly.ToValue(a.Name),\n\t\t\tfastly.ToValue(a.ACLID),\n\t\t)\n\t}\n\tt.Print()\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/acl/root.go",
    "content": "package acl\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"acl\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manipulate Fastly ACLs (Access Control Lists)\")\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/service/acl/update.go",
    "content": "package acl\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"update\", \"Update an ACL for a particular service and version\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"The name of the ACL to update\").Required().StringVar(&c.name)\n\tc.CmdClause.Flag(\"new-name\", \"The new name of the ACL\").Required().StringVar(&c.newName)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.autoClone.Set,\n\t\tDst:    &c.autoClone.Value,\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\n\treturn &c\n}\n\n// UpdateCommand calls the Fastly API to update an appropriate resource.\ntype UpdateCommand struct {\n\targparser.Base\n\n\tautoClone      argparser.OptionalAutoClone\n\tname           string\n\tnewName        string\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *UpdateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(false),\n\t\tLocked:             optional.Of(false),\n\t\tAutoCloneFlag:      c.autoClone,\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": errors.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tinput := c.constructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\n\ta, err := c.Globals.APIClient.UpdateACL(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": serviceVersion.Number,\n\t\t})\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Updated ACL '%s' (previously: '%s', service: %s, version: %d)\", fastly.ToValue(a.Name), input.Name, fastly.ToValue(a.ServiceID), fastly.ToValue(a.ServiceVersion))\n\treturn nil\n}\n\n// constructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *UpdateCommand) constructInput(serviceID string, serviceVersion int) *fastly.UpdateACLInput {\n\tvar input fastly.UpdateACLInput\n\n\tinput.Name = c.name\n\tinput.NewName = &c.newName\n\tinput.ServiceID = serviceID\n\tinput.ServiceVersion = serviceVersion\n\n\treturn &input\n}\n"
  },
  {
    "path": "pkg/commands/service/aclentry/aclentry_test.go",
    "content": "package aclentry_test\n\nimport (\n\t\"context\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\troot \"github.com/fastly/cli/pkg/commands/service\"\n\tsub \"github.com/fastly/cli/pkg/commands/service/aclentry\"\n\t\"github.com/fastly/cli/pkg/mock\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n)\n\nfunc TestACLEntryCreate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --acl-id flag\",\n\t\t\tArgs:      \"--ip 127.0.0.1\",\n\t\t\tWantError: \"error parsing arguments: required flag --acl-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --ip flag\",\n\t\t\tArgs:      \"--acl-id 123\",\n\t\t\tEnvVars:   map[string]string{\"FASTLY_SERVICE_ID\": \"\"},\n\t\t\tWantError: \"error reading service: no service ID found\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --service-id flag\",\n\t\t\tArgs:      \"--acl-id 123 --ip 127.0.0.1\",\n\t\t\tEnvVars:   map[string]string{\"FASTLY_SERVICE_ID\": \"\"},\n\t\t\tWantError: \"error reading service: no service ID found\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate CreateACLEntry API error\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tCreateACLEntryFn: func(_ context.Context, _ *fastly.CreateACLEntryInput) (*fastly.ACLEntry, error) {\n\t\t\t\t\treturn nil, testutil.Err\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:      \"--acl-id 123 --ip 127.0.0.1 --service-id 123\",\n\t\t\tWantError: testutil.Err.Error(),\n\t\t},\n\t\t{\n\t\t\tName: \"validate CreateACLEntry API success\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tCreateACLEntryFn: func(_ context.Context, i *fastly.CreateACLEntryInput) (*fastly.ACLEntry, error) {\n\t\t\t\t\treturn &fastly.ACLEntry{\n\t\t\t\t\t\tACLID:     fastly.ToPointer(i.ACLID),\n\t\t\t\t\t\tEntryID:   fastly.ToPointer(\"456\"),\n\t\t\t\t\t\tIP:        i.IP,\n\t\t\t\t\t\tServiceID: fastly.ToPointer(i.ServiceID),\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:       \"--acl-id 123 --ip 127.0.0.1 --service-id 123\",\n\t\t\tWantOutput: \"Created ACL entry '456' (ip: 127.0.0.1, negated: false, service: 123)\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate CreateACLEntry API success with negated IP\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tCreateACLEntryFn: func(_ context.Context, i *fastly.CreateACLEntryInput) (*fastly.ACLEntry, error) {\n\t\t\t\t\treturn &fastly.ACLEntry{\n\t\t\t\t\t\tACLID:     fastly.ToPointer(i.ACLID),\n\t\t\t\t\t\tEntryID:   fastly.ToPointer(\"456\"),\n\t\t\t\t\t\tIP:        i.IP,\n\t\t\t\t\t\tServiceID: fastly.ToPointer(i.ServiceID),\n\t\t\t\t\t\tNegated:   fastly.ToPointer(bool(fastly.ToValue(i.Negated))),\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:       \"--acl-id 123 --ip 127.0.0.1 --negated --service-id 123\",\n\t\t\tWantOutput: \"Created ACL entry '456' (ip: 127.0.0.1, negated: true, service: 123)\",\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"create\"}, scenarios)\n}\n\nfunc TestACLEntryDelete(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --acl-id flag\",\n\t\t\tArgs:      \"--id 456\",\n\t\t\tWantError: \"error parsing arguments: required flag --acl-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --id flag\",\n\t\t\tArgs:      \"--acl-id 123\",\n\t\t\tWantError: \"error parsing arguments: required flag --id not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --service-id flag\",\n\t\t\tArgs:      \"--acl-id 123 --id 456\",\n\t\t\tEnvVars:   map[string]string{\"FASTLY_SERVICE_ID\": \"\"},\n\t\t\tWantError: \"error reading service: no service ID found\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate DeleteACL API error\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tDeleteACLEntryFn: func(_ context.Context, _ *fastly.DeleteACLEntryInput) error {\n\t\t\t\t\treturn testutil.Err\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:      \"--acl-id 123 --id 456 --service-id 123\",\n\t\t\tWantError: testutil.Err.Error(),\n\t\t},\n\t\t{\n\t\t\tName: \"validate DeleteACL API success\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tDeleteACLEntryFn: func(_ context.Context, _ *fastly.DeleteACLEntryInput) error {\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:       \"--acl-id 123 --id 456 --service-id 123\",\n\t\t\tWantOutput: \"Deleted ACL entry '456' (service: 123)\",\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"delete\"}, scenarios)\n}\n\nfunc TestACLEntryDescribe(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --acl-id flag\",\n\t\t\tArgs:      \"--id 456\",\n\t\t\tWantError: \"error parsing arguments: required flag --acl-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --id flag\",\n\t\t\tArgs:      \"--acl-id 123\",\n\t\t\tWantError: \"error parsing arguments: required flag --id not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --service-id flag\",\n\t\t\tArgs:      \"--acl-id 123 --id 456\",\n\t\t\tEnvVars:   map[string]string{\"FASTLY_SERVICE_ID\": \"\"},\n\t\t\tWantError: \"error reading service: no service ID found\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate GetACL API error\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tGetACLEntryFn: func(_ context.Context, _ *fastly.GetACLEntryInput) (*fastly.ACLEntry, error) {\n\t\t\t\t\treturn nil, testutil.Err\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:      \"--acl-id 123 --id 456 --service-id 123\",\n\t\t\tWantError: testutil.Err.Error(),\n\t\t},\n\t\t{\n\t\t\tName: \"validate GetACL API success\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:  testutil.GetVersion,\n\t\t\t\tGetACLEntryFn: getACLEntry,\n\t\t\t},\n\t\t\tArgs:       \"--acl-id 123 --id 456 --service-id 123\",\n\t\t\tWantOutput: \"\\nService ID: 123\\nACL ID: 123\\nID: 456\\nIP: 127.0.0.1\\nSubnet: 0\\nNegated: false\\nComment: \\n\\nCreated at: 2021-06-15 23:00:00 +0000 UTC\\nUpdated at: 2021-06-15 23:00:00 +0000 UTC\\nDeleted at: 2021-06-15 23:00:00 +0000 UTC\\n\",\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"describe\"}, scenarios)\n}\n\nfunc TestACLEntryList(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --acl-id flag\",\n\t\t\tWantError: \"error parsing arguments: required flag --acl-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --service-id flag\",\n\t\t\tArgs:      \"--acl-id 123\",\n\t\t\tEnvVars:   map[string]string{\"FASTLY_SERVICE_ID\": \"\"},\n\t\t\tWantError: \"error reading service: no service ID found\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate ListACLEntries API error (via GetNext() call)\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tGetACLEntriesFn: func(ctx context.Context, _ *fastly.GetACLEntriesInput) *fastly.ListPaginator[fastly.ACLEntry] {\n\t\t\t\t\treturn fastly.NewPaginator[fastly.ACLEntry](ctx, &mock.HTTPClient{\n\t\t\t\t\t\tErrors: []error{\n\t\t\t\t\t\t\ttestutil.Err,\n\t\t\t\t\t\t},\n\t\t\t\t\t\tResponses: []*http.Response{nil},\n\t\t\t\t\t}, fastly.ListOpts{}, \"/example\")\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:      \"--acl-id 123 --service-id 123\",\n\t\t\tWantError: testutil.Err.Error(),\n\t\t},\n\t\t{\n\t\t\tName: \"validate ListACLEntries API success\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tGetACLEntriesFn: func(ctx context.Context, _ *fastly.GetACLEntriesInput) *fastly.ListPaginator[fastly.ACLEntry] {\n\t\t\t\t\treturn fastly.NewPaginator[fastly.ACLEntry](ctx, &mock.HTTPClient{\n\t\t\t\t\t\tErrors: []error{nil},\n\t\t\t\t\t\tResponses: []*http.Response{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tBody: io.NopCloser(strings.NewReader(`[\n                  {\n                    \"id\": \"456\",\n                    \"service_id\": \"123\",\n                    \"acl_id\": \"xyz\",\n                    \"ip\": \"127.0.0.1\",\n                    \"negated\": 0,\n                    \"subnet\": 0,\n                    \"comment\": \"\",\n                    \"created_at\": \"2020-04-21T18:14:32+00:00\",\n                    \"updated_at\": \"2020-04-21T18:14:32+00:00\",\n                    \"deleted_at\": null\n                  },\n                  {\n                    \"id\": \"789\",\n                    \"service_id\": \"123\",\n                    \"acl_id\": \"xyz\",\n                    \"ip\": \"127.0.0.2\",\n                    \"negated\": 1,\n                    \"subnet\": 0,\n                    \"comment\": \"\",\n                    \"created_at\": \"2020-04-21T18:14:32+00:00\",\n                    \"updated_at\": \"2020-04-21T18:14:32+00:00\",\n                    \"deleted_at\": null\n                  }\n                ]`)),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t}, fastly.ListOpts{}, \"/example\")\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:       \"--acl-id 123 --service-id 123\",\n\t\t\tWantOutput: listACLEntriesOutput,\n\t\t},\n\t\t{\n\t\t\tName: \"validate --verbose flag\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tGetACLEntriesFn: func(ctx context.Context, _ *fastly.GetACLEntriesInput) *fastly.ListPaginator[fastly.ACLEntry] {\n\t\t\t\t\treturn fastly.NewPaginator[fastly.ACLEntry](ctx, &mock.HTTPClient{\n\t\t\t\t\t\tErrors: []error{nil},\n\t\t\t\t\t\tResponses: []*http.Response{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tBody: io.NopCloser(strings.NewReader(`[\n                  {\n                    \"id\": \"456\",\n                    \"service_id\": \"123\",\n                    \"acl_id\": \"123\",\n                    \"ip\": \"127.0.0.1\",\n                    \"negated\": 0,\n                    \"subnet\": 0,\n                    \"comment\": \"foo\",\n                    \"created_at\": \"2021-06-15T23:00:00Z\",\n                    \"updated_at\": \"2021-06-15T23:00:00Z\",\n                    \"deleted_at\": \"2021-06-15T23:00:00Z\"\n                  },\n                  {\n                    \"id\": \"789\",\n                    \"service_id\": \"123\",\n                    \"acl_id\": \"123\",\n                    \"ip\": \"127.0.0.2\",\n                    \"negated\": 1,\n                    \"subnet\": 0,\n                    \"comment\": \"bar\",\n                    \"created_at\": \"2021-06-15T23:00:00Z\",\n                    \"updated_at\": \"2021-06-15T23:00:00Z\",\n                    \"deleted_at\": \"2021-06-15T23:00:00Z\"\n                  }\n                ]`)),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t}, fastly.ListOpts{}, \"/example\")\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:       \"--acl-id 123 --per-page 1 --service-id 123 --verbose\",\n\t\t\tWantOutput: listACLEntriesOutputVerbose,\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"list\"}, scenarios)\n}\n\nvar listACLEntriesOutput = `SERVICE ID  ID   IP         SUBNET  NEGATED\n123         456  127.0.0.1  0       false\n123         789  127.0.0.2  0       true\n`\n\nvar listACLEntriesOutputVerbose = `Fastly API endpoint: https://api.fastly.com\nFastly API token provided via config file (auth: user)\n\nService ID (via --service-id): 123\n\nACL ID: 123\nID: 456\nIP: 127.0.0.1\nSubnet: 0\nNegated: false\nComment: foo\n\nCreated at: 2021-06-15 23:00:00 +0000 UTC\nUpdated at: 2021-06-15 23:00:00 +0000 UTC\nDeleted at: 2021-06-15 23:00:00 +0000 UTC\n\nACL ID: 123\nID: 789\nIP: 127.0.0.2\nSubnet: 0\nNegated: true\nComment: bar\n\nCreated at: 2021-06-15 23:00:00 +0000 UTC\nUpdated at: 2021-06-15 23:00:00 +0000 UTC\nDeleted at: 2021-06-15 23:00:00 +0000 UTC\n\n`\n\nfunc TestACLEntryUpdate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --acl-id flag\",\n\t\t\tWantError: \"error parsing arguments: required flag --acl-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --service-id flag\",\n\t\t\tArgs:      \"--acl-id 123\",\n\t\t\tEnvVars:   map[string]string{\"FASTLY_SERVICE_ID\": \"\"},\n\t\t\tWantError: \"error reading service: no service ID found\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --id flag for single entry update\",\n\t\t\tArgs:      \"--acl-id 123 --service-id 123\",\n\t\t\tWantError: \"no ID found\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate UpdateACL API error\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tUpdateACLEntryFn: func(_ context.Context, _ *fastly.UpdateACLEntryInput) (*fastly.ACLEntry, error) {\n\t\t\t\t\treturn nil, testutil.Err\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:      \"--acl-id 123 --id 456 --service-id 123\",\n\t\t\tWantError: testutil.Err.Error(),\n\t\t},\n\t\t{\n\t\t\tName: \"validate error from --file set with invalid json\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tBatchModifyACLEntriesFn: func(_ context.Context, _ *fastly.BatchModifyACLEntriesInput) error {\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:      `--acl-id 123 --file {\"foo\":\"bar\"} --id 456 --service-id 123`,\n\t\t\tWantError: \"missing 'entries'\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate error from --file set with zero json entries\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tBatchModifyACLEntriesFn: func(_ context.Context, _ *fastly.BatchModifyACLEntriesInput) error {\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:      `--acl-id 123 --file {\"entries\":[]} --id 456 --service-id 123`,\n\t\t\tWantError: \"missing 'entries'\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate success with --file\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tBatchModifyACLEntriesFn: func(_ context.Context, _ *fastly.BatchModifyACLEntriesInput) error {\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:       \"--acl-id 123 --file testdata/batch.json --id 456 --service-id 123\",\n\t\t\tWantOutput: \"Updated 3 ACL entries (service: 123)\",\n\t\t},\n\t\t// NOTE: When specifying JSON inline be sure not to have any spaces, and don't\n\t\t// try to side-step it by wrapping in single quotes as the CLI parser will\n\t\t// get confused (it will consider the single quotes as being part of the\n\t\t// string it has parsed, e.g. \"'{...}'\" which means a json.Unmarshal error).\n\t\t{\n\t\t\tName: \"validate success with --file as inline json\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tBatchModifyACLEntriesFn: func(_ context.Context, _ *fastly.BatchModifyACLEntriesInput) error {\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:       `--acl-id 123 --file {\"entries\":[{\"op\":\"create\",\"ip\":\"127.0.0.1\",\"subnet\":8},{\"op\":\"update\"},{\"op\":\"upsert\"}]} --id 456 --service-id 123`,\n\t\t\tWantOutput: \"Updated 3 ACL entries (service: 123)\",\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"update\"}, scenarios)\n}\n\nfunc getACLEntry(_ context.Context, i *fastly.GetACLEntryInput) (*fastly.ACLEntry, error) {\n\tt := testutil.Date\n\n\treturn &fastly.ACLEntry{\n\t\tACLID:     fastly.ToPointer(i.ACLID),\n\t\tEntryID:   fastly.ToPointer(i.EntryID),\n\t\tIP:        fastly.ToPointer(\"127.0.0.1\"),\n\t\tServiceID: fastly.ToPointer(i.ServiceID),\n\n\t\tCreatedAt: &t,\n\t\tDeletedAt: &t,\n\t\tUpdatedAt: &t,\n\t}, nil\n}\n"
  },
  {
    "path": "pkg/commands/service/aclentry/create.go",
    "content": "package aclentry\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"create\", \"Add an ACL entry to an ACL\").Alias(\"add\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"acl-id\", \"Alphanumeric string identifying a ACL\").Required().StringVar(&c.aclID)\n\n\t// Optional.\n\tc.CmdClause.Flag(\"comment\", \"A freeform descriptive note\").Action(c.comment.Set).StringVar(&c.comment.Value)\n\tc.CmdClause.Flag(\"ip\", \"An IP address\").Action(c.ip.Set).StringVar(&c.ip.Value)\n\tc.CmdClause.Flag(\"negated\", \"Whether to negate the match\").Action(c.negated.Set).BoolVar(&c.negated.Value)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\tc.CmdClause.Flag(\"subnet\", \"Number of bits for the subnet mask applied to the IP address\").Action(c.subnet.Set).IntVar(&c.subnet.Value)\n\n\treturn &c\n}\n\n// CreateCommand calls the Fastly API to create an appropriate resource.\ntype CreateCommand struct {\n\targparser.Base\n\n\taclID       string\n\tcomment     argparser.OptionalString\n\tip          argparser.OptionalString\n\tnegated     argparser.OptionalBool\n\tserviceName argparser.OptionalServiceNameID\n\tsubnet      argparser.OptionalInt\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *CreateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, source, flag, err := argparser.ServiceID(c.serviceName, *c.Globals.Manifest, c.Globals.APIClient, c.Globals.ErrLog)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif c.Globals.Verbose() {\n\t\targparser.DisplayServiceID(serviceID, flag, source, out)\n\t}\n\n\tinput := c.constructInput(serviceID)\n\n\ta, err := c.Globals.APIClient.CreateACLEntry(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\": serviceID,\n\t\t})\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Created ACL entry '%s' (ip: %s, negated: %t, service: %s)\", fastly.ToValue(a.EntryID), fastly.ToValue(a.IP), fastly.ToValue(a.Negated), fastly.ToValue(a.ServiceID))\n\treturn nil\n}\n\n// constructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *CreateCommand) constructInput(serviceID string) *fastly.CreateACLEntryInput {\n\tinput := fastly.CreateACLEntryInput{\n\t\tACLID:     c.aclID,\n\t\tServiceID: serviceID,\n\t}\n\tif c.ip.WasSet {\n\t\tinput.IP = &c.ip.Value\n\t}\n\tif c.comment.WasSet {\n\t\tinput.Comment = &c.comment.Value\n\t}\n\tif c.negated.WasSet {\n\t\tinput.Negated = fastly.ToPointer(fastly.Compatibool(c.negated.Value))\n\t}\n\tif c.subnet.WasSet {\n\t\tinput.Subnet = &c.subnet.Value\n\t}\n\n\treturn &input\n}\n"
  },
  {
    "path": "pkg/commands/service/aclentry/delete.go",
    "content": "package aclentry\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"delete\", \"Delete an ACL entry from a specified ACL\").Alias(\"remove\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"acl-id\", \"Alphanumeric string identifying a ACL\").Required().StringVar(&c.aclID)\n\tc.CmdClause.Flag(\"id\", \"Alphanumeric string identifying an ACL Entry\").Required().StringVar(&c.id)\n\n\t// Optional.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\n\treturn &c\n}\n\n// DeleteCommand calls the Fastly API to delete an appropriate resource.\ntype DeleteCommand struct {\n\targparser.Base\n\n\taclID       string\n\tid          string\n\tserviceName argparser.OptionalServiceNameID\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DeleteCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, source, flag, err := argparser.ServiceID(c.serviceName, *c.Globals.Manifest, c.Globals.APIClient, c.Globals.ErrLog)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif c.Globals.Verbose() {\n\t\targparser.DisplayServiceID(serviceID, flag, source, out)\n\t}\n\n\tinput := c.constructInput(serviceID)\n\terr = c.Globals.APIClient.DeleteACLEntry(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\": serviceID,\n\t\t})\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Deleted ACL entry '%s' (service: %s)\", input.EntryID, serviceID)\n\treturn nil\n}\n\n// constructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *DeleteCommand) constructInput(serviceID string) *fastly.DeleteACLEntryInput {\n\tvar input fastly.DeleteACLEntryInput\n\n\tinput.ACLID = c.aclID\n\tinput.EntryID = c.id\n\tinput.ServiceID = serviceID\n\n\treturn &input\n}\n"
  },
  {
    "path": "pkg/commands/service/aclentry/describe.go",
    "content": "package aclentry\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// NewDescribeCommand returns a usable command registered under the parent.\nfunc NewDescribeCommand(parent argparser.Registerer, g *global.Data) *DescribeCommand {\n\tc := DescribeCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"describe\", \"Retrieve a single ACL entry\").Alias(\"get\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"acl-id\", \"Alphanumeric string identifying a ACL\").Required().StringVar(&c.aclID)\n\tc.CmdClause.Flag(\"id\", \"Alphanumeric string identifying an ACL Entry\").Required().StringVar(&c.id)\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\n\treturn &c\n}\n\n// DescribeCommand calls the Fastly API to describe an appropriate resource.\ntype DescribeCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\taclID       string\n\tid          string\n\tserviceName argparser.OptionalServiceNameID\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DescribeCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tserviceID, source, flag, err := argparser.ServiceID(c.serviceName, *c.Globals.Manifest, c.Globals.APIClient, c.Globals.ErrLog)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif c.Globals.Verbose() {\n\t\targparser.DisplayServiceID(serviceID, flag, source, out)\n\t}\n\n\tinput := c.constructInput(serviceID)\n\n\to, err := c.Globals.APIClient.GetACLEntry(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\": serviceID,\n\t\t})\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\treturn c.print(out, o)\n}\n\n// constructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *DescribeCommand) constructInput(serviceID string) *fastly.GetACLEntryInput {\n\tvar input fastly.GetACLEntryInput\n\n\tinput.ACLID = c.aclID\n\tinput.EntryID = c.id\n\tinput.ServiceID = serviceID\n\n\treturn &input\n}\n\n// print displays the information returned from the API.\nfunc (c *DescribeCommand) print(out io.Writer, a *fastly.ACLEntry) error {\n\tif !c.Globals.Verbose() {\n\t\tfmt.Fprintf(out, \"\\nService ID: %s\\n\", fastly.ToValue(a.ServiceID))\n\t}\n\tfmt.Fprintf(out, \"ACL ID: %s\\n\", fastly.ToValue(a.ACLID))\n\tfmt.Fprintf(out, \"ID: %s\\n\", fastly.ToValue(a.EntryID))\n\tfmt.Fprintf(out, \"IP: %s\\n\", fastly.ToValue(a.IP))\n\tfmt.Fprintf(out, \"Subnet: %d\\n\", fastly.ToValue(a.Subnet))\n\tfmt.Fprintf(out, \"Negated: %t\\n\", fastly.ToValue(a.Negated))\n\tfmt.Fprintf(out, \"Comment: %s\\n\\n\", fastly.ToValue(a.Comment))\n\n\tif a.CreatedAt != nil {\n\t\tfmt.Fprintf(out, \"Created at: %s\\n\", a.CreatedAt)\n\t}\n\tif a.UpdatedAt != nil {\n\t\tfmt.Fprintf(out, \"Updated at: %s\\n\", a.UpdatedAt)\n\t}\n\tif a.DeletedAt != nil {\n\t\tfmt.Fprintf(out, \"Deleted at: %s\\n\", a.DeletedAt)\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/aclentry/doc.go",
    "content": "// Package aclentry contains commands to inspect and manipulate Fastly ACL\n// entries.\npackage aclentry\n"
  },
  {
    "path": "pkg/commands/service/aclentry/list.go",
    "content": "package aclentry\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"list\", \"List ACLs\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"acl-id\", \"Alphanumeric string identifying a ACL\").Required().StringVar(&c.aclID)\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\n\tc.CmdClause.Flag(\"direction\", \"Direction in which to sort results\").Default(argparser.PaginationDirection[0]).HintOptions(argparser.PaginationDirection...).EnumVar(&c.direction, argparser.PaginationDirection...)\n\tc.CmdClause.Flag(\"page\", \"Page number of data set to fetch\").IntVar(&c.page)\n\tc.CmdClause.Flag(\"per-page\", \"Number of records per page\").IntVar(&c.perPage)\n\tc.CmdClause.Flag(\"sort\", \"Field on which to sort\").Default(\"created\").StringVar(&c.sort)\n\n\treturn &c\n}\n\n// ListCommand calls the Fastly API to list appropriate resources.\ntype ListCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\taclID       string\n\tdirection   string\n\tpage        int\n\tperPage     int\n\tserviceName argparser.OptionalServiceNameID\n\tsort        string\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *ListCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tserviceID, source, flag, err := argparser.ServiceID(c.serviceName, *c.Globals.Manifest, c.Globals.APIClient, c.Globals.ErrLog)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif c.Globals.Verbose() {\n\t\targparser.DisplayServiceID(serviceID, flag, source, out)\n\t}\n\n\tinput := c.constructInput(serviceID)\n\tpaginator := c.Globals.APIClient.GetACLEntries(context.TODO(), input)\n\n\tvar o []*fastly.ACLEntry\n\tfor paginator.HasNext() {\n\t\tdata, err := paginator.GetNext()\n\t\tif err != nil {\n\t\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\t\"ACL ID\":          c.aclID,\n\t\t\t\t\"Service ID\":      serviceID,\n\t\t\t\t\"Remaining Pages\": paginator.Remaining(),\n\t\t\t})\n\t\t\treturn err\n\t\t}\n\t\to = append(o, data...)\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\tif c.Globals.Verbose() {\n\t\tc.printVerbose(out, o)\n\t} else {\n\t\terr = c.printSummary(out, o)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\n// constructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *ListCommand) constructInput(serviceID string) *fastly.GetACLEntriesInput {\n\tvar input fastly.GetACLEntriesInput\n\n\tinput.ACLID = c.aclID\n\tif c.direction != \"\" {\n\t\tinput.Direction = fastly.ToPointer(c.direction)\n\t}\n\tif c.page > 0 {\n\t\tinput.Page = fastly.ToPointer(c.page)\n\t}\n\tif c.perPage > 0 {\n\t\tinput.PerPage = fastly.ToPointer(c.perPage)\n\t}\n\tinput.ServiceID = serviceID\n\tif c.sort != \"\" {\n\t\tinput.Sort = fastly.ToPointer(c.sort)\n\t}\n\n\treturn &input\n}\n\n// printVerbose displays the information returned from the API in a verbose\n// format.\nfunc (c *ListCommand) printVerbose(out io.Writer, as []*fastly.ACLEntry) {\n\tfor _, a := range as {\n\t\tfmt.Fprintf(out, \"ACL ID: %s\\n\", fastly.ToValue(a.ACLID))\n\t\tfmt.Fprintf(out, \"ID: %s\\n\", fastly.ToValue(a.EntryID))\n\t\tfmt.Fprintf(out, \"IP: %s\\n\", fastly.ToValue(a.IP))\n\t\tfmt.Fprintf(out, \"Subnet: %d\\n\", fastly.ToValue(a.Subnet))\n\t\tfmt.Fprintf(out, \"Negated: %t\\n\", fastly.ToValue(a.Negated))\n\t\tfmt.Fprintf(out, \"Comment: %s\\n\\n\", fastly.ToValue(a.Comment))\n\n\t\tif a.CreatedAt != nil {\n\t\t\tfmt.Fprintf(out, \"Created at: %s\\n\", a.CreatedAt)\n\t\t}\n\t\tif a.UpdatedAt != nil {\n\t\t\tfmt.Fprintf(out, \"Updated at: %s\\n\", a.UpdatedAt)\n\t\t}\n\t\tif a.DeletedAt != nil {\n\t\t\tfmt.Fprintf(out, \"Deleted at: %s\\n\", a.DeletedAt)\n\t\t}\n\n\t\tfmt.Fprintf(out, \"\\n\")\n\t}\n}\n\n// printSummary displays the information returned from the API in a summarised\n// format.\nfunc (c *ListCommand) printSummary(out io.Writer, as []*fastly.ACLEntry) error {\n\tt := text.NewTable(out)\n\tt.AddHeader(\"SERVICE ID\", \"ID\", \"IP\", \"SUBNET\", \"NEGATED\")\n\tfor _, a := range as {\n\t\tvar subnet int\n\t\tif a.Subnet != nil {\n\t\t\tsubnet = *a.Subnet\n\t\t}\n\t\tt.AddLine(\n\t\t\tfastly.ToValue(a.ServiceID),\n\t\t\tfastly.ToValue(a.EntryID),\n\t\t\tfastly.ToValue(a.IP),\n\t\t\tsubnet,\n\t\t\tfastly.ToValue(a.Negated),\n\t\t)\n\t}\n\tt.Print()\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/aclentry/root.go",
    "content": "package aclentry\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"acl-entry\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manipulate Fastly ACL (Access Control List) entries\")\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/service/aclentry/testdata/batch.json",
    "content": "{\n  \"entries\": [\n    {\n      \"op\": \"create\",\n      \"ip\": \"192.168.0.1\",\n      \"subnet\": 8\n    },\n    {\n      \"op\": \"update\",\n      \"id\": \"6yxNzlOpW1V7JfSwvLGtOc\",\n      \"ip\": \"192.168.0.2\",\n      \"subnet\": 16\n    },\n    {\n      \"op\": \"delete\",\n      \"id\": \"6yxNzlOpW1V7JfSwvLGtOc\"\n    }\n  ]\n}\n"
  },
  {
    "path": "pkg/commands/service/aclentry/update.go",
    "content": "package aclentry\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"update\", \"Update an ACL entry for a specified ACL\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"acl-id\", \"Alphanumeric string identifying a ACL\").Required().StringVar(&c.aclID)\n\n\t// Optional.\n\tc.CmdClause.Flag(\"comment\", \"A freeform descriptive note\").Action(c.comment.Set).StringVar(&c.comment.Value)\n\tc.CmdClause.Flag(\"file\", \"Batch update json passed as file path or content, e.g. $(< batch.json)\").Action(c.file.Set).StringVar(&c.file.Value)\n\tc.CmdClause.Flag(\"id\", \"Alphanumeric string identifying an ACL Entry\").Action(c.id.Set).StringVar(&c.id.Value)\n\tc.CmdClause.Flag(\"ip\", \"An IP address\").Action(c.ip.Set).StringVar(&c.ip.Value)\n\tc.CmdClause.Flag(\"negated\", \"Whether to negate the match\").Action(c.negated.Set).BoolVar(&c.negated.Value)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\tc.CmdClause.Flag(\"subnet\", \"Number of bits for the subnet mask applied to the IP address\").Action(c.subnet.Set).IntVar(&c.subnet.Value)\n\n\treturn &c\n}\n\n// UpdateCommand calls the Fastly API to update an appropriate resource.\ntype UpdateCommand struct {\n\targparser.Base\n\n\taclID       string\n\tcomment     argparser.OptionalString\n\tfile        argparser.OptionalString\n\tid          argparser.OptionalString\n\tip          argparser.OptionalString\n\tnegated     argparser.OptionalBool\n\tserviceName argparser.OptionalServiceNameID\n\tsubnet      argparser.OptionalInt\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *UpdateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, source, flag, err := argparser.ServiceID(c.serviceName, *c.Globals.Manifest, c.Globals.APIClient, c.Globals.ErrLog)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif c.Globals.Verbose() {\n\t\targparser.DisplayServiceID(serviceID, flag, source, out)\n\t}\n\n\tif c.file.WasSet {\n\t\tinput, err := c.constructBatchInput(serviceID)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\terr = c.Globals.APIClient.BatchModifyACLEntries(context.TODO(), input)\n\t\tif err != nil {\n\t\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\t\"Service ID\": serviceID,\n\t\t\t})\n\t\t\treturn err\n\t\t}\n\n\t\ttext.Success(out, \"Updated %d ACL entries (service: %s)\", len(input.Entries), serviceID)\n\t\treturn nil\n\t}\n\n\tinput, err := c.constructInput(serviceID)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\ta, err := c.Globals.APIClient.UpdateACLEntry(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\": serviceID,\n\t\t})\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Updated ACL entry '%s' (ip: %s, service: %s)\", fastly.ToValue(a.EntryID), fastly.ToValue(a.IP), fastly.ToValue(a.ServiceID))\n\treturn nil\n}\n\n// constructBatchInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *UpdateCommand) constructBatchInput(serviceID string) (*fastly.BatchModifyACLEntriesInput, error) {\n\tvar input fastly.BatchModifyACLEntriesInput\n\n\tinput.ACLID = c.aclID\n\tinput.ServiceID = serviceID\n\n\ts := argparser.Content(c.file.Value)\n\tbs := []byte(s)\n\n\terr := json.Unmarshal(bs, &input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"File\": s,\n\t\t})\n\t\treturn nil, err\n\t}\n\n\tif len(input.Entries) == 0 {\n\t\terr := fsterr.RemediationError{\n\t\t\tInner:       fmt.Errorf(\"missing 'entries' %s\", c.file.Value),\n\t\t\tRemediation: \"Consult the API documentation for the JSON format: https://www.fastly.com/documentation/reference/api/acls/acl-entry#bulk-update-acl-entries\",\n\t\t}\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"File\": string(bs),\n\t\t})\n\t\treturn nil, err\n\t}\n\n\treturn &input, nil\n}\n\n// constructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *UpdateCommand) constructInput(serviceID string) (*fastly.UpdateACLEntryInput, error) {\n\tvar input fastly.UpdateACLEntryInput\n\n\tif !c.id.WasSet {\n\t\treturn nil, fsterr.ErrNoID\n\t}\n\n\tinput.ACLID = c.aclID\n\tinput.EntryID = c.id.Value\n\tinput.ServiceID = serviceID\n\n\tif c.comment.WasSet {\n\t\tinput.Comment = &c.comment.Value\n\t}\n\tif c.ip.WasSet {\n\t\tinput.IP = &c.ip.Value\n\t}\n\tif c.negated.WasSet {\n\t\tinput.Negated = fastly.ToPointer(fastly.Compatibool(c.negated.Value))\n\t}\n\tif c.subnet.WasSet {\n\t\tinput.Subnet = &c.subnet.Value\n\t}\n\n\treturn &input, nil\n}\n"
  },
  {
    "path": "pkg/commands/service/alert/alert_test.go",
    "content": "package alerts_test\n\nimport (\n\t\"context\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\troot \"github.com/fastly/cli/pkg/commands/service\"\n\tsub \"github.com/fastly/cli/pkg/commands/service/alert\"\n\t\"github.com/fastly/cli/pkg/mock\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n)\n\nfunc TestAlertsCreate(t *testing.T) {\n\tcreateFlags := flagList{\n\t\tFlags: []flag{\n\t\t\t{Flag: \"--name\", Value: \"name\"},\n\t\t\t{Flag: \"--description\", Value: \"description\"},\n\t\t\t{Flag: \"--metric\", Value: \"status_5xx\"},\n\t\t\t{Flag: \"--source\", Value: \"stats\"},\n\t\t\t{Flag: \"--type\", Value: \"above_threshold\"},\n\t\t\t{Flag: \"--period\", Value: \"5m\"},\n\t\t\t{Flag: \"--threshold\", Value: \"10.0\"},\n\t\t},\n\t}\n\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName: \"ok all required\",\n\t\t\tArgs: createFlags.String(),\n\t\t\tAPI:  &mock.API{CreateAlertDefinitionFn: CreateAlertDefinitionResponse},\n\t\t},\n\t\t{\n\t\t\tName:      \"no name\",\n\t\t\tArgs:      createFlags.Remove(\"--name\").String(),\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"no description\",\n\t\t\tArgs:      createFlags.Remove(\"--description\").String(),\n\t\t\tWantError: \"error parsing arguments: required flag --description not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"no metric\",\n\t\t\tArgs:      createFlags.Remove(\"--metric\").String(),\n\t\t\tWantError: \"error parsing arguments: required flag --metric not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"no source\",\n\t\t\tArgs:      createFlags.Remove(\"--source\").String(),\n\t\t\tWantError: \"error parsing arguments: required flag --source not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"no type\",\n\t\t\tArgs:      createFlags.Remove(\"--type\").String(),\n\t\t\tWantError: \"error parsing arguments: required flag --type not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"no period\",\n\t\t\tArgs:      createFlags.Remove(\"--period\").String(),\n\t\t\tWantError: \"error parsing arguments: required flag --period not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"no threshold\",\n\t\t\tArgs:      createFlags.Remove(\"--threshold\").String(),\n\t\t\tWantError: \"error parsing arguments: required flag --threshold not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"ok optional json\",\n\t\t\tArgs: createFlags.Add(flag{Flag: \"--json\"}).String(),\n\t\t\tAPI:  &mock.API{CreateAlertDefinitionFn: CreateAlertDefinitionResponse},\n\t\t},\n\t\t{\n\t\t\tName: \"ok optional ignoreBelow\",\n\t\t\tArgs: createFlags.Add(flag{Flag: \"--ignoreBelow\", Value: \"5.0\"}).String(),\n\t\t\tAPI:  &mock.API{CreateAlertDefinitionFn: CreateAlertDefinitionResponse},\n\t\t},\n\t\t{\n\t\t\tName: \"ok optional service-id\",\n\t\t\tArgs: createFlags.Add(flag{Flag: \"--service-id\", Value: \"ABC\"}).String(),\n\t\t\tAPI:  &mock.API{CreateAlertDefinitionFn: CreateAlertDefinitionResponse},\n\t\t},\n\t\t{\n\t\t\tName: \"ok optional dimensions\",\n\t\t\tArgs: createFlags.\n\t\t\t\tChange(flag{Flag: \"--source\", Value: \"origins\"}).\n\t\t\t\tAdd(flag{Flag: \"--dimensions\", Value: \"fastly.com\"}).\n\t\t\t\tAdd(flag{Flag: \"--dimensions\", Value: \"fastly2.com\"}).String(),\n\t\t\tAPI: &mock.API{CreateAlertDefinitionFn: CreateAlertDefinitionResponse},\n\t\t},\n\t\t{\n\t\t\tName: \"ok optional integrations\",\n\t\t\tArgs: createFlags.\n\t\t\t\tAdd(flag{Flag: \"--integrations\", Value: \"ABC1\"}).\n\t\t\t\tAdd(flag{Flag: \"--integrations\", Value: \"ABC2\"}).String(),\n\t\t\tAPI: &mock.API{CreateAlertDefinitionFn: CreateAlertDefinitionResponse},\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"create\"}, scenarios)\n}\n\nfunc TestAlertsUpdate(t *testing.T) {\n\tupdateFlags := flagList{\n\t\tFlags: []flag{\n\t\t\t{Flag: \"--id\", Value: \"ABC\"},\n\t\t\t{Flag: \"--name\", Value: \"name\"},\n\t\t\t{Flag: \"--description\", Value: \"description\"},\n\t\t\t{Flag: \"--metric\", Value: \"status_5xx\"},\n\t\t\t{Flag: \"--source\", Value: \"stats\"},\n\t\t\t{Flag: \"--type\", Value: \"above_threshold\"},\n\t\t\t{Flag: \"--period\", Value: \"5m\"},\n\t\t\t{Flag: \"--threshold\", Value: \"10.0\"},\n\t\t},\n\t}\n\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName: \"ok all required\",\n\t\t\tArgs: updateFlags.String(),\n\t\t\tAPI:  &mock.API{UpdateAlertDefinitionFn: UpdateAlertDefinitionResponse},\n\t\t},\n\t\t{\n\t\t\tName:      \"no id\",\n\t\t\tArgs:      updateFlags.Remove(\"--id\").String(),\n\t\t\tWantError: \"error parsing arguments: required flag --id not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"no name\",\n\t\t\tArgs:      updateFlags.Remove(\"--name\").String(),\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"no description\",\n\t\t\tArgs:      updateFlags.Remove(\"--description\").String(),\n\t\t\tWantError: \"error parsing arguments: required flag --description not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"no metric\",\n\t\t\tArgs:      updateFlags.Remove(\"--metric\").String(),\n\t\t\tWantError: \"error parsing arguments: required flag --metric not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"no source\",\n\t\t\tArgs:      updateFlags.Remove(\"--source\").String(),\n\t\t\tWantError: \"error parsing arguments: required flag --source not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"no type\",\n\t\t\tArgs:      updateFlags.Remove(\"--type\").String(),\n\t\t\tWantError: \"error parsing arguments: required flag --type not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"no period\",\n\t\t\tArgs:      updateFlags.Remove(\"--period\").String(),\n\t\t\tWantError: \"error parsing arguments: required flag --period not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"no threshold\",\n\t\t\tArgs:      updateFlags.Remove(\"--threshold\").String(),\n\t\t\tWantError: \"error parsing arguments: required flag --threshold not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"ok optional json\",\n\t\t\tArgs: updateFlags.Add(flag{Flag: \"--json\"}).String(),\n\t\t\tAPI:  &mock.API{UpdateAlertDefinitionFn: UpdateAlertDefinitionResponse},\n\t\t},\n\t\t{\n\t\t\tName: \"ok optional ignoreBelow\",\n\t\t\tArgs: updateFlags.Add(flag{Flag: \"--ignoreBelow\", Value: \"5.0\"}).String(),\n\t\t\tAPI:  &mock.API{UpdateAlertDefinitionFn: UpdateAlertDefinitionResponse},\n\t\t},\n\t\t{\n\t\t\tName: \"ok optional dimensions\",\n\t\t\tArgs: updateFlags.\n\t\t\t\tChange(flag{Flag: \"--source\", Value: \"origins\"}).\n\t\t\t\tAdd(flag{Flag: \"--dimensions\", Value: \"fastly.com\"}).\n\t\t\t\tAdd(flag{Flag: \"--dimensions\", Value: \"fastly2.com\"}).String(),\n\t\t\tAPI: &mock.API{UpdateAlertDefinitionFn: UpdateAlertDefinitionResponse},\n\t\t},\n\t\t{\n\t\t\tName: \"ok optional integrations\",\n\t\t\tArgs: updateFlags.Add(flag{Flag: \"--integrations\", Value: \"ABC1\"}).Add(flag{Flag: \"--integrations\", Value: \"ABC2\"}).String(),\n\t\t\tAPI:  &mock.API{UpdateAlertDefinitionFn: UpdateAlertDefinitionResponse},\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"update\"}, scenarios)\n}\n\nfunc TestAlertsDelete(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"no definition id\",\n\t\t\tWantError: \"error parsing arguments: required flag --id not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"ok\",\n\t\t\tArgs: \"--id ABC\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tDeleteAlertDefinitionFn: func(_ context.Context, _ *fastly.DeleteAlertDefinitionInput) error {\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"delete\"}, scenarios)\n}\n\nfunc TestAlertsDescribe(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"no definition id\",\n\t\t\tWantError: \"error parsing arguments: required flag --id not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"ok\",\n\t\t\tArgs: \"--id ABC\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tGetAlertDefinitionFn: func(_ context.Context, _ *fastly.GetAlertDefinitionInput) (*fastly.AlertDefinition, error) {\n\t\t\t\t\tresponse := &mockDefinition\n\t\t\t\t\treturn response, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: listAlertsOutput,\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"describe\"}, scenarios)\n}\n\nfunc TestAlertsList(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:       \"ok\",\n\t\t\tAPI:        &mock.API{ListAlertDefinitionsFn: ListAlertDefinitionsEmptyResponse},\n\t\t\tWantOutput: listAlertsEmptyOutput,\n\t\t},\n\t\t{\n\t\t\tName: \"ok verbose\",\n\t\t\tArgs: \"-v\",\n\t\t\tAPI:  &mock.API{ListAlertDefinitionsFn: ListAlertDefinitionsEmptyResponse},\n\t\t},\n\t\t{\n\t\t\tName: \"ok json\",\n\t\t\tArgs: \"-j\",\n\t\t\tAPI:  &mock.API{ListAlertDefinitionsFn: ListAlertDefinitionsEmptyResponse},\n\t\t},\n\t\t{\n\t\t\tName: \"ok cursor\",\n\t\t\tArgs: \"--cursor ABC\",\n\t\t\tAPI:  &mock.API{ListAlertDefinitionsFn: ListAlertDefinitionsEmptyResponse},\n\t\t},\n\t\t{\n\t\t\tName: \"ok limit\",\n\t\t\tArgs: \"--limit 1\",\n\t\t\tAPI:  &mock.API{ListAlertDefinitionsFn: ListAlertDefinitionsEmptyResponse},\n\t\t},\n\t\t{\n\t\t\tName: \"ok definition name\",\n\t\t\tArgs: \"--name test\",\n\t\t\tAPI:  &mock.API{ListAlertDefinitionsFn: ListAlertDefinitionsEmptyResponse},\n\t\t},\n\t\t{\n\t\t\tName: \"ok sort name\",\n\t\t\tArgs: \"--sort name\",\n\t\t\tAPI:  &mock.API{ListAlertDefinitionsFn: ListAlertDefinitionsEmptyResponse},\n\t\t},\n\t\t{\n\t\t\tName: \"ok sort updated_at\",\n\t\t\tArgs: \"--sort updated_at\",\n\t\t\tAPI:  &mock.API{ListAlertDefinitionsFn: ListAlertDefinitionsEmptyResponse},\n\t\t},\n\t\t{\n\t\t\tName: \"ok sort created_at asc\",\n\t\t\tArgs: \"--sort created_at --order asc\",\n\t\t\tAPI:  &mock.API{ListAlertDefinitionsFn: ListAlertDefinitionsEmptyResponse},\n\t\t},\n\t\t{\n\t\t\tName: \"ok sort created_at desc\",\n\t\t\tArgs: \"--sort created_at --order desc\",\n\t\t\tAPI:  &mock.API{ListAlertDefinitionsFn: ListAlertDefinitionsEmptyResponse},\n\t\t},\n\t\t{\n\t\t\tName: \"ok service id\",\n\t\t\tArgs: \"--service-id ABC\",\n\t\t\tAPI:  &mock.API{ListAlertDefinitionsFn: ListAlertDefinitionsEmptyResponse},\n\t\t},\n\t\t{\n\t\t\tName: \"validate ListAlerts API success\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tListAlertDefinitionsFn: func(_ context.Context, _ *fastly.ListAlertDefinitionsInput) (*fastly.AlertDefinitionsResponse, error) {\n\t\t\t\t\tresponse := &fastly.AlertDefinitionsResponse{\n\t\t\t\t\t\tData: []fastly.AlertDefinition{mockDefinition},\n\t\t\t\t\t\tMeta: fastly.AlertsMeta{\n\t\t\t\t\t\t\tTotal:      1,\n\t\t\t\t\t\t\tLimit:      100,\n\t\t\t\t\t\t\tNextCursor: \"\",\n\t\t\t\t\t\t\tSort:       \"-name\",\n\t\t\t\t\t\t},\n\t\t\t\t\t}\n\t\t\t\t\treturn response, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: listAlertsOutput,\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"list\"}, scenarios)\n}\n\nfunc TestAlertsHistoryList(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:       \"ok\",\n\t\t\tAPI:        &mock.API{ListAlertHistoryFn: ListAlertHistoryEmptyResponse},\n\t\t\tWantOutput: listAlertHistoryEmptyOutput,\n\t\t},\n\t\t{\n\t\t\tName: \"ok verbose\",\n\t\t\tArgs: \"-v\",\n\t\t\tAPI:  &mock.API{ListAlertHistoryFn: ListAlertHistoryEmptyResponse},\n\t\t},\n\t\t{\n\t\t\tName: \"ok json\",\n\t\t\tArgs: \"--json\",\n\t\t\tAPI:  &mock.API{ListAlertHistoryFn: ListAlertHistoryEmptyResponse},\n\t\t},\n\t\t{\n\t\t\tName: \"ok cursor\",\n\t\t\tArgs: \"--cursor ABC\",\n\t\t\tAPI:  &mock.API{ListAlertHistoryFn: ListAlertHistoryEmptyResponse},\n\t\t},\n\t\t{\n\t\t\tName: \"ok limit\",\n\t\t\tArgs: \"--limit 1\",\n\t\t\tAPI:  &mock.API{ListAlertHistoryFn: ListAlertHistoryEmptyResponse},\n\t\t},\n\t\t{\n\t\t\tName: \"ok status\",\n\t\t\tArgs: \"--status active\",\n\t\t\tAPI:  &mock.API{ListAlertHistoryFn: ListAlertHistoryEmptyResponse},\n\t\t},\n\t\t{\n\t\t\tName: \"ok sort start\",\n\t\t\tArgs: \"--sort start\",\n\t\t\tAPI:  &mock.API{ListAlertHistoryFn: ListAlertHistoryEmptyResponse},\n\t\t},\n\t\t{\n\t\t\tName: \"ok sort start asc\",\n\t\t\tArgs: \"--sort start --order asc\",\n\t\t\tAPI:  &mock.API{ListAlertHistoryFn: ListAlertHistoryEmptyResponse},\n\t\t},\n\t\t{\n\t\t\tName: \"ok sort start desc\",\n\t\t\tArgs: \"--sort start --order desc\",\n\t\t\tAPI:  &mock.API{ListAlertHistoryFn: ListAlertHistoryEmptyResponse},\n\t\t},\n\t\t{\n\t\t\tName: \"ok service id\",\n\t\t\tArgs: \"--service-id ABC\",\n\t\t\tAPI:  &mock.API{ListAlertHistoryFn: ListAlertHistoryEmptyResponse},\n\t\t},\n\t\t{\n\t\t\tName: \"ok definition id\",\n\t\t\tArgs: \"--definition-id ABC\",\n\t\t\tAPI:  &mock.API{ListAlertHistoryFn: ListAlertHistoryEmptyResponse},\n\t\t},\n\t\t{\n\t\t\tName: \"validate ListAlerts API success\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tListAlertHistoryFn: func(_ context.Context, _ *fastly.ListAlertHistoryInput) (*fastly.AlertHistoryResponse, error) {\n\t\t\t\t\tresponse := &fastly.AlertHistoryResponse{\n\t\t\t\t\t\tData: []fastly.AlertHistory{mockHistory},\n\t\t\t\t\t\tMeta: fastly.AlertsMeta{\n\t\t\t\t\t\t\tTotal:      1,\n\t\t\t\t\t\t\tLimit:      100,\n\t\t\t\t\t\t\tNextCursor: \"\",\n\t\t\t\t\t\t\tSort:       \"-start\",\n\t\t\t\t\t\t},\n\t\t\t\t\t}\n\t\t\t\t\treturn response, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: listAlertsHistoryOutput,\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"history\"}, scenarios)\n}\n\ntype flag struct {\n\tFlag  string\n\tValue string\n}\n\nfunc (t *flag) String() string {\n\tif t.Value == \"\" {\n\t\treturn t.Flag\n\t}\n\treturn strings.Join([]string{t.Flag, t.Value}, \" \")\n}\n\ntype flagList struct {\n\tFlags []flag\n}\n\nfunc (t *flagList) Add(flag flag) *flagList {\n\tnewTuples := flagList{}\n\tnewTuples.Flags = append(newTuples.Flags, t.Flags...)\n\tnewTuples.Flags = append(newTuples.Flags, flag)\n\treturn &newTuples\n}\n\nfunc (t *flagList) Change(flag flag) *flagList {\n\tnewTuples := flagList{}\n\tfor i := range t.Flags {\n\t\tif t.Flags[i].Flag == flag.Flag {\n\t\t\tnewTuples.Flags = append(newTuples.Flags, flag)\n\t\t} else {\n\t\t\tnewTuples.Flags = append(newTuples.Flags, t.Flags[i])\n\t\t}\n\t}\n\treturn &newTuples\n}\n\nfunc (t *flagList) Remove(flag string) *flagList {\n\tnewTuples := flagList{}\n\tfor i := range t.Flags {\n\t\tif t.Flags[i].Flag != flag {\n\t\t\tnewTuples.Flags = append(newTuples.Flags, t.Flags[i])\n\t\t}\n\t}\n\treturn &newTuples\n}\n\nfunc (t *flagList) String() string {\n\tvar strs []string\n\tfor i := range t.Flags {\n\t\tstrs = append(strs, t.Flags[i].String())\n\t}\n\treturn strings.Join(strs, \" \")\n}\n\nvar mockTime = time.Date(2024, 0o5, 0o1, 12, 0o0, 11, 0, time.UTC)\n\nvar ListAlertDefinitionsEmptyResponse = func(_ context.Context, _ *fastly.ListAlertDefinitionsInput) (*fastly.AlertDefinitionsResponse, error) {\n\tresponse := &fastly.AlertDefinitionsResponse{\n\t\tData: []fastly.AlertDefinition{},\n\t\tMeta: fastly.AlertsMeta{\n\t\t\tTotal:      0,\n\t\t\tLimit:      100,\n\t\t\tNextCursor: \"\",\n\t\t\tSort:       \"-name\",\n\t\t},\n\t}\n\treturn response, nil\n}\n\nvar ListAlertHistoryEmptyResponse = func(_ context.Context, _ *fastly.ListAlertHistoryInput) (*fastly.AlertHistoryResponse, error) {\n\tresponse := &fastly.AlertHistoryResponse{\n\t\tData: []fastly.AlertHistory{},\n\t\tMeta: fastly.AlertsMeta{\n\t\t\tTotal:      0,\n\t\t\tLimit:      100,\n\t\t\tNextCursor: \"\",\n\t\t\tSort:       \"-start\",\n\t\t},\n\t}\n\treturn response, nil\n}\n\nvar mockDefinition = fastly.AlertDefinition{\n\tID:             \"ABC\",\n\tName:           \"name\",\n\tDescription:    \"description\",\n\tSource:         \"stats\",\n\tMetric:         \"status_5xx\",\n\tServiceID:      \"SVC\",\n\tDimensions:     map[string][]string{},\n\tIntegrationIDs: []string{},\n\tEvaluationStrategy: map[string]any{\n\t\t\"type\":      \"above_threshold\",\n\t\t\"period\":    \"5m\",\n\t\t\"threshold\": 10.0,\n\t},\n\tUpdatedAt: mockTime,\n\tCreatedAt: mockTime,\n}\n\nvar mockHistory = fastly.AlertHistory{\n\tID:           \"ABC\",\n\tDefinitionID: mockDefinition.ID,\n\tDefinition:   mockDefinition,\n\tStatus:       \"active\",\n\tStart:        mockTime,\n\tEnd:          mockTime,\n}\n\nvar CreateAlertDefinitionResponse = func(_ context.Context, _ *fastly.CreateAlertDefinitionInput) (*fastly.AlertDefinition, error) {\n\tresponse := &mockDefinition\n\treturn response, nil\n}\n\nvar UpdateAlertDefinitionResponse = func(_ context.Context, _ *fastly.UpdateAlertDefinitionInput) (*fastly.AlertDefinition, error) {\n\tresponse := &mockDefinition\n\treturn response, nil\n}\n\nvar listAlertsEmptyOutput = `DEFINITION ID  SERVICE ID  NAME  SOURCE  METRIC  TYPE  THRESHOLD  PERIOD`\n\nvar listAlertsOutput = `DEFINITION ID  SERVICE ID  NAME  SOURCE  METRIC      TYPE             THRESHOLD  PERIOD\nABC            SVC         name  stats   status_5xx  above_threshold  10         5m\n`\n\nvar listAlertHistoryEmptyOutput = `HISTORY ID  DEFINITION ID  STATUS  START  END`\n\nvar listAlertsHistoryOutput = `HISTORY ID  DEFINITION ID  STATUS  START                          END\nABC         ABC            active  2024-05-01 12:00:11 +0000 UTC  2024-05-01 12:00:11 +0000 UTC\n`\n"
  },
  {
    "path": "pkg/commands/service/alert/common.go",
    "content": "package alerts\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/text\"\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n)\n\n// evaluationType is a list of supported evaluation types.\nvar evaluationType = []string{\"above_threshold\", \"all_above_threshold\", \"below_threshold\", \"percent_absolute\", \"percent_decrease\", \"percent_increase\"}\n\n// evaluationPeriod is a list of supported evaluation periods.\nvar evaluationPeriod = []string{\"2m\", \"3m\", \"5m\", \"15m\", \"30m\"}\n\nfunc printDefinition(out io.Writer, indent uint, definition *fastly.AlertDefinition) {\n\tif definition != nil {\n\t\ttext.Indent(out, indent, \"Definition ID: %s\", definition.ID)\n\t\ttext.Indent(out, indent, \"Service ID: %s\", definition.ServiceID)\n\t\ttext.Indent(out, indent, \"Name: %s\", definition.Name)\n\t\ttext.Indent(out, indent, \"Source: %s\", definition.Source)\n\n\t\tdimensions, ok := definition.Dimensions[definition.Source]\n\t\tif ok && len(dimensions) > 0 {\n\t\t\ttext.Indent(out, indent, \"Dimensions:\")\n\t\t\tfor i := range dimensions {\n\t\t\t\ttext.Indent(out, indent+4, \"  %s\", dimensions[i])\n\t\t\t}\n\t\t}\n\n\t\ttext.Indent(out, indent, \"Metric: %s\", definition.Metric)\n\n\t\ttext.Indent(out, indent, \"Evaluation Strategy:\")\n\t\teType, _ := definition.EvaluationStrategy[\"type\"].(string)\n\t\ttext.Indent(out, indent+4, \"  Type: %s\", eType)\n\n\t\tperiod, _ := definition.EvaluationStrategy[\"period\"].(string)\n\t\ttext.Indent(out, indent+4, \"  Period: %s\", period)\n\n\t\tthreshold, _ := definition.EvaluationStrategy[\"threshold\"].(float64)\n\t\ttext.Indent(out, indent+4, \"  Threshold: %v\", threshold)\n\n\t\tif ignoreBelow, ok := definition.EvaluationStrategy[\"ignore_below\"].(float64); ok {\n\t\t\ttext.Indent(out, indent+4, \"  IgnoreBelow: %v\", ignoreBelow)\n\t\t}\n\n\t\tintegrations := definition.IntegrationIDs\n\t\tif len(integrations) > 0 {\n\t\t\ttext.Indent(out, indent, \"Integrations:\")\n\t\t\tfor i := range integrations {\n\t\t\t\ttext.Indent(out, indent, \"  %s\", integrations[i])\n\t\t\t}\n\t\t}\n\n\t\ttext.Indent(out, indent, \"Created at: %s\", definition.CreatedAt)\n\t\ttext.Indent(out, indent, \"Updated at: %s\", definition.UpdatedAt)\n\t}\n}\n\n// printSummary displays the information returned from the API in a summarised\n// format.\nfunc printSummary(out io.Writer, as []*fastly.AlertDefinition) {\n\tt := text.NewTable(out)\n\tt.AddHeader(\"DEFINITION ID\", \"SERVICE ID\", \"NAME\", \"SOURCE\", \"METRIC\", \"TYPE\", \"THRESHOLD\", \"PERIOD\")\n\tfor _, a := range as {\n\t\teType, _ := a.EvaluationStrategy[\"type\"].(string)\n\t\tperiod, _ := a.EvaluationStrategy[\"period\"].(string)\n\t\tthreshold, _ := a.EvaluationStrategy[\"threshold\"].(float64)\n\t\tt.AddLine(\n\t\t\ta.ID,\n\t\t\ta.ServiceID,\n\t\t\ta.Name,\n\t\t\ta.Source,\n\t\t\ta.Metric,\n\t\t\teType,\n\t\t\tthreshold,\n\t\t\tperiod,\n\t\t)\n\t}\n\tt.Print()\n}\n\n// printVerbose displays the information returned from the API in a verbose\n// format.\nfunc printVerbose(out io.Writer, as []*fastly.AlertDefinition) {\n\tfor _, a := range as {\n\t\tprintDefinition(out, 0, a)\n\t\tfmt.Fprintf(out, \"\\n\")\n\t}\n}\n\nfunc printHistory(out io.Writer, history *fastly.AlertHistory) {\n\tif history != nil {\n\t\tstart := history.Start.UTC().String()\n\t\tend := history.End.UTC().String()\n\t\tfmt.Fprintf(out, \"History ID: %s\\n\", history.ID)\n\t\tfmt.Fprintf(out, \"Definition:\\n\")\n\t\tprintDefinition(out, 4, &history.Definition)\n\t\tfmt.Fprintf(out, \"Status: %s\\n\", history.Status)\n\t\tfmt.Fprintf(out, \"Start: %s\\n\", start)\n\t\tfmt.Fprintf(out, \"End: %s\\n\", end)\n\t\tfmt.Fprintf(out, \"\\n\")\n\t}\n}\n\n// printSummary displays the information returned from the API in a summarised\n// format.\nfunc printHistorySummary(out io.Writer, as []*fastly.AlertHistory) {\n\tt := text.NewTable(out)\n\tt.AddHeader(\"HISTORY ID\", \"DEFINITION ID\", \"STATUS\", \"START\", \"END\")\n\tfor _, a := range as {\n\t\tstart := a.Start.UTC().String()\n\t\tend := a.End.UTC().String()\n\t\tt.AddLine(\n\t\t\ta.ID,\n\t\t\ta.DefinitionID,\n\t\t\ta.Status,\n\t\t\tstart,\n\t\t\tend,\n\t\t)\n\t}\n\tt.Print()\n}\n\n// printVerbose displays the information returned from the API in a verbose\n// format.\nfunc printHistoryVerbose(out io.Writer, history []*fastly.AlertHistory) {\n\tfor _, h := range history {\n\t\tprintHistory(out, h)\n\t}\n}\n"
  },
  {
    "path": "pkg/commands/service/alert/create.go",
    "content": "package alerts\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n)\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\n\tc.CmdClause = parent.Command(\"create\", \"Create Alert\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"description\", \"Additional text that is included in the alert notification.\").Required().StringVar(&c.description)\n\tc.CmdClause.Flag(\"metric\", \"Metric name to alert on for a specific source.\").Required().StringVar(&c.metric)\n\tc.CmdClause.Flag(\"name\", \"Name of the alert definition.\").Required().StringVar(&c.name)\n\tc.CmdClause.Flag(\"period\", \"Period of time to evaluate whether the conditions have been met. The data is polled every minute.\").Required().HintOptions(evaluationPeriod...).EnumVar(&c.period, evaluationPeriod...)\n\tc.CmdClause.Flag(\"source\", \"Source where the metric comes from.\").Required().StringVar(&c.source)\n\tc.CmdClause.Flag(\"threshold\", \"Threshold used to alert.\").Required().Float64Var(&c.threshold)\n\tc.CmdClause.Flag(\"type\", \"Type of strategy to use to evaluate.\").Required().HintOptions(evaluationType...).EnumVar(&c.eType, evaluationType...)\n\n\t// Optional.\n\tc.CmdClause.Flag(\"dimensions\", \"Dimensions filters depending on the source type.\").Action(c.dimensions.Set).StringsVar(&c.dimensions.Value)\n\tc.CmdClause.Flag(\"ignoreBelow\", \"IgnoreBelow is the threshold for the denominator value used in evaluations that calculate a rate or ratio. Usually used to filter out noise.\").Action(c.ignoreBelow.Set).Float64Var(&c.ignoreBelow.Value)\n\tc.CmdClause.Flag(\"integrations\", \"Integrations are a list of integrations used to notify when alert fires.\").Action(c.integrations.Set).StringsVar(&c.integrations.Value)\n\tc.RegisterFlagBool(c.JSONFlag())                                                                                                   // --json\n\tc.CmdClause.Flag(argparser.FlagServiceIDName, \"ServiceID of the definition\").Action(c.serviceID.Set).StringVar(&c.serviceID.Value) // --service-id\n\n\treturn &c\n}\n\n// CreateCommand calls the Fastly API to list appropriate resources.\ntype CreateCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tdescription string\n\teType       string\n\tmetric      string\n\tname        string\n\tperiod      string\n\tsource      string\n\tthreshold   float64\n\n\tdimensions   argparser.OptionalStringSlice\n\tignoreBelow  argparser.OptionalFloat64\n\tintegrations argparser.OptionalStringSlice\n\tserviceID    argparser.OptionalString\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *CreateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tinput := c.constructInput()\n\tdefinition, err := c.Globals.APIClient.CreateAlertDefinition(context.TODO(), input)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, definition); ok {\n\t\treturn err\n\t}\n\n\tdefinitions := []*fastly.AlertDefinition{definition}\n\tif c.Globals.Verbose() {\n\t\tprintVerbose(out, definitions)\n\t} else {\n\t\tprintSummary(out, definitions)\n\t}\n\treturn nil\n}\n\n// constructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *CreateCommand) constructInput() *fastly.CreateAlertDefinitionInput {\n\tinput := fastly.CreateAlertDefinitionInput{\n\t\tDescription: &c.description,\n\t\tEvaluationStrategy: map[string]any{\n\t\t\t\"type\":      c.eType,\n\t\t\t\"period\":    c.period,\n\t\t\t\"threshold\": c.threshold,\n\t\t},\n\t\tMetric: &c.metric,\n\t\tName:   &c.name,\n\t\tSource: &c.source,\n\t}\n\n\tif c.ignoreBelow.WasSet {\n\t\tinput.EvaluationStrategy[\"ignore_below\"] = c.ignoreBelow.Value\n\t}\n\n\tif c.serviceID.WasSet {\n\t\tinput.ServiceID = &c.serviceID.Value\n\t}\n\n\tdimensions := map[string][]string{}\n\tif c.source == \"origins\" || c.source == \"domains\" {\n\t\tvar filter []string\n\t\tif c.dimensions.WasSet {\n\t\t\tfilter = c.dimensions.Value\n\t\t}\n\t\tdimensions[c.source] = filter\n\t}\n\tinput.Dimensions = dimensions\n\n\tinput.IntegrationIDs = []string{}\n\tif c.integrations.WasSet {\n\t\tinput.IntegrationIDs = c.integrations.Value\n\t}\n\n\treturn &input\n}\n"
  },
  {
    "path": "pkg/commands/service/alert/delete.go",
    "content": "package alerts\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/text\"\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\n\tc.CmdClause = parent.Command(\"delete\", \"Delete Alert\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"id\", \"Alphanumeric string identifying an Alert definition\").Required().StringVar(&c.definitionID)\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\n\treturn &c\n}\n\n// DeleteCommand calls the Fastly API to delete appropriate resource.\ntype DeleteCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tdefinitionID string\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DeleteCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tinput := c.constructInput()\n\terr := c.Globals.APIClient.DeleteAlertDefinition(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Definition ID\": c.definitionID,\n\t\t})\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Deleted Alert entry '%s'\", c.definitionID)\n\treturn nil\n}\n\n// constructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *DeleteCommand) constructInput() *fastly.DeleteAlertDefinitionInput {\n\tinput := fastly.DeleteAlertDefinitionInput{\n\t\tID: &c.definitionID,\n\t}\n\treturn &input\n}\n"
  },
  {
    "path": "pkg/commands/service/alert/describe.go",
    "content": "package alerts\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// NewDescribeCommand returns a usable command registered under the parent.\nfunc NewDescribeCommand(parent argparser.Registerer, g *global.Data) *DescribeCommand {\n\tc := DescribeCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\n\tc.CmdClause = parent.Command(\"describe\", \"Describe Alert\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"id\", \"Alphanumeric string identifying an Alert definition\").Required().StringVar(&c.definitionID)\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\n\treturn &c\n}\n\n// DescribeCommand calls the Fastly API to describe appropriate resource.\ntype DescribeCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tdefinitionID string\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DescribeCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tinput := c.constructInput()\n\tdefinition, err := c.Globals.APIClient.GetAlertDefinition(context.TODO(), input)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, definition); ok {\n\t\treturn err\n\t}\n\n\tdefinitions := []*fastly.AlertDefinition{definition}\n\tif c.Globals.Verbose() {\n\t\tprintVerbose(out, definitions)\n\t} else {\n\t\tprintSummary(out, definitions)\n\t}\n\treturn nil\n}\n\n// constructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *DescribeCommand) constructInput() *fastly.GetAlertDefinitionInput {\n\tinput := fastly.GetAlertDefinitionInput{\n\t\tID: &c.definitionID,\n\t}\n\treturn &input\n}\n"
  },
  {
    "path": "pkg/commands/service/alert/doc.go",
    "content": "// Package alerts contains commands to inspect and manipulate Fastly Service Alerts.\npackage alerts\n"
  },
  {
    "path": "pkg/commands/service/alert/list.go",
    "content": "package alerts\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/text\"\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"list\", \"List Alerts\")\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\tc.CmdClause.Flag(\"cursor\", \"Pagination cursor (Use 'next_cursor' value from list output)\").Action(c.cursor.Set).StringVar(&c.cursor.Value)\n\tc.CmdClause.Flag(\"limit\", \"Maximum number of items to list\").Action(c.limit.Set).IntVar(&c.limit.Value)\n\tc.CmdClause.Flag(\"name\", \"Name of the definition\").Action(c.definitionName.Set).StringVar(&c.definitionName.Value)\n\tc.CmdClause.Flag(\"order\", \"Sort by one of the following [asc, desc]\").Action(c.order.Set).StringVar(&c.order.Value)\n\tc.CmdClause.Flag(\"sort\", \"Sort by one of the following [name, created_at, updated_at]\").Action(c.sort.Set).StringVar(&c.sort.Value)\n\tc.CmdClause.Flag(argparser.FlagServiceIDName, \"ServiceID of the definition\").Action(c.serviceID.Set).StringVar(&c.serviceID.Value) // --service-id\n\n\treturn &c\n}\n\n// ListCommand calls the Fastly API to list appropriate resources.\ntype ListCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tcursor         argparser.OptionalString\n\tlimit          argparser.OptionalInt\n\tdefinitionName argparser.OptionalString\n\tserviceID      argparser.OptionalString\n\tsort           argparser.OptionalString\n\torder          argparser.OptionalString\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *ListCommand) Exec(in io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tinput, err := c.constructInput()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfor {\n\t\tdefinitions, err := c.Globals.APIClient.ListAlertDefinitions(context.TODO(), input)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif ok, err := c.WriteJSON(out, definitions); ok {\n\t\t\t// No pagination prompt w/ JSON output.\n\t\t\treturn err\n\t\t}\n\n\t\tdefinitionsPtr := make([]*fastly.AlertDefinition, len(definitions.Data))\n\t\tfor i := range definitions.Data {\n\t\t\tdefinitionsPtr[i] = &definitions.Data[i]\n\t\t}\n\n\t\tif c.Globals.Verbose() {\n\t\t\tprintVerbose(out, definitionsPtr)\n\t\t} else {\n\t\t\tprintSummary(out, definitionsPtr)\n\t\t}\n\n\t\tif definitions != nil && definitions.Meta.NextCursor != \"\" {\n\t\t\t// Check if 'out' is interactive before prompting.\n\t\t\tif !c.Globals.Flags.NonInteractive && !c.Globals.Flags.AutoYes && text.IsTTY(out) {\n\t\t\t\tprintNext, err := text.AskYesNo(out, \"Print next page [y/N]: \", in)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tif printNext {\n\t\t\t\t\tinput.Cursor = &definitions.Meta.NextCursor\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn nil\n\t}\n}\n\n// constructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *ListCommand) constructInput() (*fastly.ListAlertDefinitionsInput, error) {\n\tinput := fastly.ListAlertDefinitionsInput{}\n\tif c.cursor.WasSet {\n\t\tinput.Cursor = &c.cursor.Value\n\t}\n\tif c.limit.WasSet {\n\t\tinput.Limit = &c.limit.Value\n\t}\n\tif c.definitionName.WasSet {\n\t\tinput.Name = &c.definitionName.Value\n\t}\n\tif c.serviceID.WasSet {\n\t\tinput.ServiceID = &c.serviceID.Value\n\t}\n\tvar sign string\n\tvar err error\n\tif c.order.WasSet {\n\t\tsign, err = argparser.ConvertOrderFromStringFlag(c.order.Value, \"order\")\n\t\tif err != nil {\n\t\t\tc.Globals.ErrLog.Add(err)\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tif c.sort.WasSet {\n\t\tstr := sign + c.sort.Value\n\t\tinput.Sort = &str\n\t}\n\n\treturn &input, nil\n}\n"
  },
  {
    "path": "pkg/commands/service/alert/list_history.go",
    "content": "package alerts\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/text\"\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// NewListHistoryCommand returns a usable command registered under the parent.\nfunc NewListHistoryCommand(parent argparser.Registerer, g *global.Data) *ListHistoryCommand {\n\tc := ListHistoryCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"history\", \"List history\")\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\tc.CmdClause.Flag(\"after\", \"After filter history record that either started or ended after a specific date\").Action(c.after.Set).StringVar(&c.after.Value)\n\tc.CmdClause.Flag(\"before\", \"Before filter history record that either started or ended before a specific date\").Action(c.before.Set).StringVar(&c.before.Value)\n\tc.CmdClause.Flag(\"cursor\", \"Pagination cursor (Use 'next_cursor' value from list output)\").Action(c.cursor.Set).StringVar(&c.cursor.Value)\n\tc.CmdClause.Flag(\"definition-id\", \"Unique identifier of the definition\").Action(c.definitionID.Set).StringVar(&c.definitionID.Value)\n\tc.CmdClause.Flag(\"limit\", \"Maximum number of items to list\").Action(c.limit.Set).IntVar(&c.limit.Value)\n\tc.CmdClause.Flag(\"order\", \"Sort by one of the following [asc, desc]\").Action(c.order.Set).StringVar(&c.order.Value)\n\tc.CmdClause.Flag(\"sort\", \"Sort by one of the following [start]\").Action(c.sort.Set).StringVar(&c.sort.Value)\n\tc.CmdClause.Flag(argparser.FlagServiceIDName, \"ServiceID of the definition\").Action(c.serviceID.Set).StringVar(&c.serviceID.Value) // --service-id\n\tc.CmdClause.Flag(\"status\", \"Status of the history record [active, resolved]\").Action(c.status.Set).StringVar(&c.status.Value)\n\n\treturn &c\n}\n\n// ListHistoryCommand calls the Fastly API to list appropriate resources.\ntype ListHistoryCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tcursor argparser.OptionalString\n\tlimit  argparser.OptionalInt\n\tsort   argparser.OptionalString\n\torder  argparser.OptionalString\n\n\tstatus       argparser.OptionalString\n\tbefore       argparser.OptionalString\n\tafter        argparser.OptionalString\n\tdefinitionID argparser.OptionalString\n\tserviceID    argparser.OptionalString\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *ListHistoryCommand) Exec(in io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tinput, err := c.constructInput()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfor {\n\t\thistory, err := c.Globals.APIClient.ListAlertHistory(context.TODO(), input)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif ok, err := c.WriteJSON(out, history); ok {\n\t\t\treturn err\n\t\t}\n\n\t\thistoryPtr := make([]*fastly.AlertHistory, len(history.Data))\n\t\tfor i := range history.Data {\n\t\t\thistoryPtr[i] = &history.Data[i]\n\t\t}\n\n\t\tif c.Globals.Verbose() {\n\t\t\tprintHistoryVerbose(out, historyPtr)\n\t\t} else {\n\t\t\tprintHistorySummary(out, historyPtr)\n\t\t}\n\n\t\tif history != nil && history.Meta.NextCursor != \"\" {\n\t\t\t// Check if 'out' is interactive before prompting.\n\t\t\tif !c.Globals.Flags.NonInteractive && !c.Globals.Flags.AutoYes && text.IsTTY(out) {\n\t\t\t\tprintNext, err := text.AskYesNo(out, \"Print next page [y/N]: \", in)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tif printNext {\n\t\t\t\t\tinput.Cursor = &history.Meta.NextCursor\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn nil\n\t}\n}\n\n// constructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *ListHistoryCommand) constructInput() (*fastly.ListAlertHistoryInput, error) {\n\tinput := fastly.ListAlertHistoryInput{}\n\tif c.cursor.WasSet {\n\t\tinput.Cursor = &c.cursor.Value\n\t}\n\tif c.limit.WasSet {\n\t\tinput.Limit = &c.limit.Value\n\t}\n\tvar sign string\n\tvar err error\n\tif c.order.WasSet {\n\t\tsign, err = argparser.ConvertOrderFromStringFlag(c.order.Value, \"order\")\n\t\tif err != nil {\n\t\t\tc.Globals.ErrLog.Add(err)\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tif c.sort.WasSet {\n\t\tstr := sign + c.sort.Value\n\t\tinput.Sort = &str\n\t}\n\n\tif c.serviceID.WasSet {\n\t\tinput.ServiceID = &c.serviceID.Value\n\t}\n\n\tif c.definitionID.WasSet {\n\t\tinput.DefinitionID = &c.definitionID.Value\n\t}\n\n\tif c.status.WasSet {\n\t\tinput.Status = &c.status.Value\n\t}\n\n\tif c.before.WasSet {\n\t\tinput.Before = &c.before.Value\n\t}\n\n\tif c.after.WasSet {\n\t\tinput.After = &c.after.Value\n\t}\n\n\treturn &input, nil\n}\n"
  },
  {
    "path": "pkg/commands/service/alert/root.go",
    "content": "package alerts\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"alert\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manipulate Fastly Service Alerts\")\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/service/alert/update.go",
    "content": "package alerts\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\n\tc.CmdClause = parent.Command(\"update\", \"Update Alert\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"description\", \"Additional text that is included in the alert notification.\").Required().StringVar(&c.description)\n\tc.CmdClause.Flag(\"id\", \"A unique identifier for a definition.\").Required().StringVar(&c.definitionID)\n\tc.CmdClause.Flag(\"metric\", \"Metric name to alert on for a specific source.\").Required().StringVar(&c.metric)\n\tc.CmdClause.Flag(\"name\", \"Name of the alert definition.\").Required().StringVar(&c.name)\n\tc.CmdClause.Flag(\"period\", \"Period of time to evaluate whether the conditions have been met. The data is polled every minute.\").Required().HintOptions(evaluationPeriod...).EnumVar(&c.period, evaluationPeriod...)\n\tc.CmdClause.Flag(\"source\", \"Source where the metric comes from.\").Required().StringVar(&c.source)\n\tc.CmdClause.Flag(\"threshold\", \"Threshold used to alert.\").Required().Float64Var(&c.threshold)\n\tc.CmdClause.Flag(\"type\", \"Type of strategy to use to evaluate.\").Required().HintOptions(evaluationType...).EnumVar(&c.eType, evaluationType...)\n\n\t// Optional.\n\tc.CmdClause.Flag(\"dimensions\", \"Dimensions filters depending on the source type.\").Action(c.dimensions.Set).StringsVar(&c.dimensions.Value)\n\tc.CmdClause.Flag(\"ignoreBelow\", \"IgnoreBelow is the threshold for the denominator value used in evaluations that calculate a rate or ratio. Usually used to filter out noise.\").Action(c.ignoreBelow.Set).Float64Var(&c.ignoreBelow.Value)\n\tc.CmdClause.Flag(\"integrations\", \"Integrations are a list of integrations used to notify when alert fires.\").Action(c.integrations.Set).StringsVar(&c.integrations.Value)\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\n\treturn &c\n}\n\n// UpdateCommand calls the Fastly API to list appropriate resources.\ntype UpdateCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tdefinitionID string\n\tdescription  string\n\teType        string\n\tmetric       string\n\tname         string\n\tperiod       string\n\tsource       string\n\tthreshold    float64\n\n\tdimensions   argparser.OptionalStringSlice\n\tignoreBelow  argparser.OptionalFloat64\n\tintegrations argparser.OptionalStringSlice\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *UpdateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tinput := c.constructInput()\n\tdefinition, err := c.Globals.APIClient.UpdateAlertDefinition(context.TODO(), input)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, definition); ok {\n\t\treturn err\n\t}\n\n\tdefinitions := []*fastly.AlertDefinition{definition}\n\tif c.Globals.Verbose() {\n\t\tprintVerbose(out, definitions)\n\t} else {\n\t\tprintSummary(out, definitions)\n\t}\n\treturn nil\n}\n\n// constructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *UpdateCommand) constructInput() *fastly.UpdateAlertDefinitionInput {\n\tinput := fastly.UpdateAlertDefinitionInput{\n\t\tID:          &c.definitionID,\n\t\tDescription: &c.description,\n\t\tEvaluationStrategy: map[string]any{\n\t\t\t\"type\":      c.eType,\n\t\t\t\"period\":    c.period,\n\t\t\t\"threshold\": c.threshold,\n\t\t},\n\t\tMetric: &c.metric,\n\t\tName:   &c.name,\n\t}\n\n\tif c.ignoreBelow.WasSet {\n\t\tinput.EvaluationStrategy[\"ignore_below\"] = c.ignoreBelow.Value\n\t}\n\n\tdimensions := map[string][]string{}\n\tif c.source == \"origins\" || c.source == \"domains\" {\n\t\tvar filter []string\n\t\tif c.dimensions.WasSet {\n\t\t\tfilter = c.dimensions.Value\n\t\t}\n\t\tdimensions[c.source] = filter\n\t}\n\tinput.Dimensions = dimensions\n\n\tinput.IntegrationIDs = []string{}\n\tif c.integrations.WasSet {\n\t\tinput.IntegrationIDs = c.integrations.Value\n\t}\n\n\treturn &input\n}\n"
  },
  {
    "path": "pkg/commands/service/auth/create.go",
    "content": "package auth\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// Permissions is a list of supported permission values.\n// https://www.fastly.com/documentation/reference/api/account/service-authorization#data-model\nvar Permissions = []string{\"full\", \"read_only\", \"purge_select\", \"purge_all\"}\n\n// CreateCommand calls the Fastly API to create a service authorization.\ntype CreateCommand struct {\n\targparser.Base\n\tinput       fastly.CreateServiceAuthorizationInput\n\tserviceName argparser.OptionalServiceNameID\n\tuserID      string\n}\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"create\", \"Create service authorization\").Alias(\"add\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"user-id\", \"Alphanumeric string identifying the user\").Required().Short('u').StringVar(&c.userID)\n\n\t// Optional.\n\t// NOTE: We default to 'read_only' for security reasons.\n\t// The API otherwise defaults to 'full' permissions!\n\tc.CmdClause.Flag(\"permission\", \"The permission the user has in relation to the service (default: read_only)\").HintOptions(Permissions...).Default(\"read_only\").Short('p').EnumVar(&c.input.Permission, Permissions...)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *CreateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, source, flag, err := argparser.ServiceID(c.serviceName, *c.Globals.Manifest, c.Globals.APIClient, c.Globals.ErrLog)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":   c.Globals.Manifest.Flag.ServiceID,\n\t\t\t\"Service Name\": c.serviceName.Value,\n\t\t})\n\t\treturn err\n\t}\n\tif c.Globals.Flags.Verbose {\n\t\targparser.DisplayServiceID(serviceID, flag, source, out)\n\t}\n\n\tc.input.Service = &fastly.SAService{\n\t\tID: serviceID,\n\t}\n\tc.input.User = &fastly.SAUser{\n\t\tID: c.userID,\n\t}\n\n\ts, err := c.Globals.APIClient.CreateServiceAuthorization(context.TODO(), &c.input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\": serviceID,\n\t\t\t\"Flag\":       flag,\n\t\t})\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Created service authorization %s\", s.ID)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/auth/delete.go",
    "content": "package auth\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DeleteCommand calls the Fastly API to delete service authorizations.\ntype DeleteCommand struct {\n\targparser.Base\n\tInput fastly.DeleteServiceAuthorizationInput\n}\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"delete\", \"Delete service authorization\").Alias(\"remove\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"id\", \"ID of the service authorization to delete\").Required().StringVar(&c.Input.ID)\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DeleteCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif err := c.Globals.APIClient.DeleteServiceAuthorization(context.TODO(), &c.Input); err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service Authorization ID\": c.Input.ID,\n\t\t})\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Deleted service authorization %s\", c.Input.ID)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/auth/describe.go",
    "content": "package auth\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/time\"\n)\n\n// DescribeCommand calls the Fastly API to describe a service authorization for a service.\ntype DescribeCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tInput fastly.GetServiceAuthorizationInput\n}\n\n// NewDescribeCommand returns a usable command registered under the parent.\nfunc NewDescribeCommand(parent argparser.Registerer, g *global.Data) *DescribeCommand {\n\tc := DescribeCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"describe\", \"Show service authorization\").Alias(\"get\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"id\", \"ID of the service authorization to retrieve\").Required().StringVar(&c.Input.ID)\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DescribeCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\to, err := c.Globals.APIClient.GetServiceAuthorization(context.TODO(), &c.Input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service Authorization ID\": c.Input.ID,\n\t\t})\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\treturn c.print(o, out)\n}\n\nfunc (c *DescribeCommand) print(s *fastly.ServiceAuthorization, out io.Writer) error {\n\tfmt.Fprintf(out, \"Auth ID: %s\\n\", s.ID)\n\tfmt.Fprintf(out, \"User ID: %s\\n\", s.User.ID)\n\tfmt.Fprintf(out, \"Service ID: %s\\n\", s.Service.ID)\n\tfmt.Fprintf(out, \"Permission: %s\\n\", s.Permission)\n\n\tif s.CreatedAt != nil {\n\t\tfmt.Fprintf(out, \"Created (UTC): %s\\n\", s.CreatedAt.UTC().Format(time.Format))\n\t}\n\tif s.UpdatedAt != nil {\n\t\tfmt.Fprintf(out, \"Last edited (UTC): %s\\n\", s.UpdatedAt.UTC().Format(time.Format))\n\t}\n\tif s.DeletedAt != nil {\n\t\tfmt.Fprintf(out, \"Deleted (UTC): %s\\n\", s.DeletedAt.UTC().Format(time.Format))\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/auth/doc.go",
    "content": "// Package auth contains commands to inspect and manipulate authorization\n// to Fastly services.\npackage auth\n"
  },
  {
    "path": "pkg/commands/service/auth/list.go",
    "content": "package auth\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n\t\"github.com/fastly/cli/pkg/time\"\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n)\n\n// ListCommand calls the Fastly API to list service authorizations.\ntype ListCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tinput fastly.ListServiceAuthorizationsInput\n}\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tvar c ListCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(\"list\", \"List service authorizations\")\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\tc.CmdClause.Flag(\"page\", \"Page number of data set to fetch\").IntVar(&c.input.PageNumber)\n\tc.CmdClause.Flag(\"per-page\", \"Number of records per page\").IntVar(&c.input.PageSize)\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *ListCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\to, err := c.Globals.APIClient.ListServiceAuthorizations(context.TODO(), &c.input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Page Number\": c.input.PageNumber,\n\t\t\t\"Page Size\":   c.input.PageSize,\n\t\t})\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\tif !c.Globals.Verbose() {\n\t\tif len(o.Items) > 0 {\n\t\t\ttw := text.NewTable(out)\n\t\t\ttw.AddHeader(\"AUTH ID\", \"USER ID\", \"SERVICE ID\", \"PERMISSION\")\n\n\t\t\tfor _, s := range o.Items {\n\t\t\t\ttw.AddLine(s.ID, s.User.ID, s.Service.ID, s.Permission)\n\t\t\t}\n\t\t\ttw.Print()\n\n\t\t\treturn nil\n\t\t}\n\t}\n\n\tfor _, s := range o.Items {\n\t\tfmt.Fprintf(out, \"Auth ID: %s\\n\", s.ID)\n\t\tfmt.Fprintf(out, \"User ID: %s\\n\", s.User.ID)\n\t\tfmt.Fprintf(out, \"Service ID: %s\\n\", s.Service.ID)\n\t\tfmt.Fprintf(out, \"Permission: %s\\n\", s.Permission)\n\n\t\tif s.CreatedAt != nil {\n\t\t\tfmt.Fprintf(out, \"Created (UTC): %s\\n\", s.CreatedAt.UTC().Format(time.Format))\n\t\t}\n\t\tif s.UpdatedAt != nil {\n\t\t\tfmt.Fprintf(out, \"Last edited (UTC): %s\\n\", s.UpdatedAt.UTC().Format(time.Format))\n\t\t}\n\t\tif s.DeletedAt != nil {\n\t\t\tfmt.Fprintf(out, \"Deleted (UTC): %s\\n\", s.DeletedAt.UTC().Format(time.Format))\n\t\t}\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/auth/root.go",
    "content": "package auth\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"auth\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Allow users to access only specified services\")\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/service/auth/service_test.go",
    "content": "package auth_test\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"testing\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\troot \"github.com/fastly/cli/pkg/commands/service\"\n\tsub \"github.com/fastly/cli/pkg/commands/service/auth\"\n\t\"github.com/fastly/cli/pkg/mock\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n)\n\nfunc TestServiceAuthCreate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"missing required flag\",\n\t\t\tArgs:      \"\",\n\t\t\tWantError: \"error parsing arguments: required flag --user-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"api failure\",\n\t\t\tArgs:      \"--user-id 123 --service-id 123\",\n\t\t\tAPI:       &mock.API{CreateServiceAuthorizationFn: createServiceAuthError},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t\t{\n\t\t\tName:       \"success\",\n\t\t\tArgs:       \"--user-id 123 --service-id 123\",\n\t\t\tAPI:        &mock.API{CreateServiceAuthorizationFn: createServiceAuthOK},\n\t\t\tWantOutput: \"Created service authorization 12345\",\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"create\"}, scenarios)\n}\n\nfunc TestServiceAuthList(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"invalid flag combination\",\n\t\t\tArgs:      \"--verbose --json\",\n\t\t\tWantError: \"invalid flag combination, --verbose and --json\",\n\t\t},\n\t\t{\n\t\t\tName:      \"api failure\",\n\t\t\tArgs:      \"\",\n\t\t\tAPI:       &mock.API{ListServiceAuthorizationsFn: listServiceAuthError},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t\t{\n\t\t\tName:       \"success\",\n\t\t\tArgs:       \"\",\n\t\t\tAPI:        &mock.API{ListServiceAuthorizationsFn: listServiceAuthOK},\n\t\t\tWantOutput: \"AUTH ID  USER ID  SERVICE ID  PERMISSION\\n123      456      789         read_only\\n\",\n\t\t},\n\t\t{\n\t\t\tName: \"success with json\",\n\t\t\tArgs: \"--json\",\n\t\t\tAPI:  &mock.API{ListServiceAuthorizationsFn: listServiceAuthOK},\n\t\t\tWantOutput: `{\n  \"Info\": {\n    \"links\": {},\n    \"meta\": {}\n  },\n  \"Items\": [\n    {\n      \"CreatedAt\": null,\n      \"DeletedAt\": null,\n      \"ID\": \"123\",\n      \"Permission\": \"read_only\",\n      \"Service\": {\n        \"ID\": \"789\"\n      },\n      \"UpdatedAt\": null,\n      \"User\": {\n        \"ID\": \"456\"\n      }\n    }\n  ]\n}`,\n\t\t},\n\t\t{\n\t\t\tName:       \"success with verbose\",\n\t\t\tArgs:       \"--verbose\",\n\t\t\tAPI:        &mock.API{ListServiceAuthorizationsFn: listServiceAuthOK},\n\t\t\tWantOutput: \"Fastly API endpoint: https://api.fastly.com\\nFastly API token provided via config file (auth: user)\\n\\nAuth ID: 123\\nUser ID: 456\\nService ID: 789\\nPermission: read_only\\n\",\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"list\"}, scenarios)\n}\n\nfunc TestServiceAuthDescribe(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"missing required flag\",\n\t\t\tArgs:      \"\",\n\t\t\tWantError: \"error parsing arguments: required flag --id not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"invalid flag combination\",\n\t\t\tArgs:      \"--id 123 --verbose --json\",\n\t\t\tWantError: \"invalid flag combination, --verbose and --json\",\n\t\t},\n\t\t{\n\t\t\tName:      \"api failure\",\n\t\t\tArgs:      \"--id 123\",\n\t\t\tAPI:       &mock.API{GetServiceAuthorizationFn: describeServiceAuthError},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t\t{\n\t\t\tName:       \"success\",\n\t\t\tArgs:       \"--id 123\",\n\t\t\tAPI:        &mock.API{GetServiceAuthorizationFn: describeServiceAuthOK},\n\t\t\tWantOutput: \"Auth ID: 12345\\nUser ID: 456\\nService ID: 789\\nPermission: read_only\\n\",\n\t\t},\n\t\t{\n\t\t\tName: \"success with json\",\n\t\t\tArgs: \"--id 123 --json\",\n\t\t\tAPI:  &mock.API{GetServiceAuthorizationFn: describeServiceAuthOK},\n\t\t\tWantOutput: `{\n  \"CreatedAt\": null,\n  \"DeletedAt\": null,\n  \"ID\": \"12345\",\n  \"Permission\": \"read_only\",\n  \"Service\": {\n    \"ID\": \"789\"\n  },\n  \"UpdatedAt\": null,\n  \"User\": {\n    \"ID\": \"456\"\n  }\n}`,\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"describe\"}, scenarios)\n}\n\nfunc TestServiceAuthUpdate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"missing id flag\",\n\t\t\tArgs:      \"--permission full\",\n\t\t\tWantError: \"error parsing arguments: required flag --id not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"missing permission flag\",\n\t\t\tArgs:      \"--id 123\",\n\t\t\tWantError: \"error parsing arguments: required flag --permission not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"api failure\",\n\t\t\tArgs:      \"--id 123 --permission full\",\n\t\t\tAPI:       &mock.API{UpdateServiceAuthorizationFn: updateServiceAuthError},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t\t{\n\t\t\tName:       \"success\",\n\t\t\tArgs:       \"--id 123 --permission full\",\n\t\t\tAPI:        &mock.API{UpdateServiceAuthorizationFn: updateServiceAuthOK},\n\t\t\tWantOutput: \"Updated service authorization 123\",\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"update\"}, scenarios)\n}\n\nfunc TestServiceAuthDelete(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"missing required flag\",\n\t\t\tArgs:      \"\",\n\t\t\tWantError: \"error parsing arguments: required flag --id not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"api failure\",\n\t\t\tArgs:      \"--id 123\",\n\t\t\tAPI:       &mock.API{DeleteServiceAuthorizationFn: deleteServiceAuthError},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t\t{\n\t\t\tName:       \"success\",\n\t\t\tArgs:       \"--id 123\",\n\t\t\tAPI:        &mock.API{DeleteServiceAuthorizationFn: deleteServiceAuthOK},\n\t\t\tWantOutput: \"Deleted service authorization 123\",\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"delete\"}, scenarios)\n}\n\nvar errTest = errors.New(\"fixture error\")\n\nfunc createServiceAuthError(_ context.Context, _ *fastly.CreateServiceAuthorizationInput) (*fastly.ServiceAuthorization, error) {\n\treturn nil, errTest\n}\n\nfunc createServiceAuthOK(_ context.Context, _ *fastly.CreateServiceAuthorizationInput) (*fastly.ServiceAuthorization, error) {\n\treturn &fastly.ServiceAuthorization{\n\t\tID: \"12345\",\n\t}, nil\n}\n\nfunc listServiceAuthError(_ context.Context, _ *fastly.ListServiceAuthorizationsInput) (*fastly.ServiceAuthorizations, error) {\n\treturn nil, errTest\n}\n\nfunc listServiceAuthOK(_ context.Context, _ *fastly.ListServiceAuthorizationsInput) (*fastly.ServiceAuthorizations, error) {\n\treturn &fastly.ServiceAuthorizations{\n\t\tItems: []*fastly.ServiceAuthorization{\n\t\t\t{\n\t\t\t\tID: \"123\",\n\t\t\t\tUser: &fastly.SAUser{\n\t\t\t\t\tID: \"456\",\n\t\t\t\t},\n\t\t\t\tService: &fastly.SAService{\n\t\t\t\t\tID: \"789\",\n\t\t\t\t},\n\t\t\t\tPermission: \"read_only\",\n\t\t\t},\n\t\t},\n\t}, nil\n}\n\nfunc describeServiceAuthError(_ context.Context, _ *fastly.GetServiceAuthorizationInput) (*fastly.ServiceAuthorization, error) {\n\treturn nil, errTest\n}\n\nfunc describeServiceAuthOK(_ context.Context, _ *fastly.GetServiceAuthorizationInput) (*fastly.ServiceAuthorization, error) {\n\treturn &fastly.ServiceAuthorization{\n\t\tID: \"12345\",\n\t\tUser: &fastly.SAUser{\n\t\t\tID: \"456\",\n\t\t},\n\t\tService: &fastly.SAService{\n\t\t\tID: \"789\",\n\t\t},\n\t\tPermission: \"read_only\",\n\t}, nil\n}\n\nfunc updateServiceAuthError(_ context.Context, _ *fastly.UpdateServiceAuthorizationInput) (*fastly.ServiceAuthorization, error) {\n\treturn nil, errTest\n}\n\nfunc updateServiceAuthOK(_ context.Context, _ *fastly.UpdateServiceAuthorizationInput) (*fastly.ServiceAuthorization, error) {\n\treturn &fastly.ServiceAuthorization{\n\t\tID: \"12345\",\n\t}, nil\n}\n\nfunc deleteServiceAuthError(_ context.Context, _ *fastly.DeleteServiceAuthorizationInput) error {\n\treturn errTest\n}\n\nfunc deleteServiceAuthOK(_ context.Context, _ *fastly.DeleteServiceAuthorizationInput) error {\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/auth/testdata/fastly-no-serviceid.toml",
    "content": "manifest_version = 2\nname = \"Default Rust template\"\ndescription = \"Default package template for Rust based edge compute projects.\"\nauthors = [\"phamann <patrick@fastly.com>\"]\nlanguage = \"rust\"\n"
  },
  {
    "path": "pkg/commands/service/auth/testdata/fastly-valid.toml",
    "content": "manifest_version = 2\nname = \"Default Rust template\"\ndescription = \"Default package template for Rust based edge compute projects.\"\nauthors = [\"phamann <patrick@fastly.com>\"]\nlanguage = \"rust\"\nservice_id = \"123\"\n"
  },
  {
    "path": "pkg/commands/service/auth/update.go",
    "content": "package auth\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// UpdateCommand calls the Fastly API to update service authorizations.\ntype UpdateCommand struct {\n\targparser.Base\n\n\tinput fastly.UpdateServiceAuthorizationInput\n}\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"update\", \"Update service authorization\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"id\", \"ID of the service authorization to delete\").Required().StringVar(&c.input.ID)\n\tc.CmdClause.Flag(\"permission\", \"The permission the user has in relation to the service\").Required().HintOptions(Permissions...).Short('p').EnumVar(&c.input.Permission, Permissions...)\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *UpdateCommand) Exec(_ io.Reader, out io.Writer) error {\n\ts, err := c.Globals.APIClient.UpdateServiceAuthorization(context.TODO(), &c.input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service Authorization ID\": c.input.ID,\n\t\t})\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Updated service authorization %s\", s.ID)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/backend/backend_test.go",
    "content": "package backend_test\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\troot \"github.com/fastly/cli/pkg/commands/service\"\n\tsub \"github.com/fastly/cli/pkg/commands/service/backend\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/mock\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n)\n\nfunc TestBackendCreate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"--version 1\",\n\t\t\tEnvVars:   map[string]string{\"FASTLY_SERVICE_ID\": \"\"},\n\t\t\tWantError: \"error reading service: no service ID found\",\n\t\t},\n\t\t// The following test appends --autoclone\n\t\t// so we can be sure the backend creation error still occurs.\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --address example.com --name www.test.com --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:    testutil.GetVersion,\n\t\t\t\tCloneVersionFn:  testutil.CloneVersionResult(4),\n\t\t\t\tCreateBackendFn: createBackendError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t\t// The following test is the same as the 'locked' test above but it appends --autoclone\n\t\t// so we can be sure the backend creation error still occurs.\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --address example.com --name www.test.com --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:    testutil.GetVersion,\n\t\t\t\tCloneVersionFn:  testutil.CloneVersionResult(4),\n\t\t\t\tCreateBackendFn: createBackendError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t\t// The following test is the same as above but with an IP address for the\n\t\t// --address flag instead of a hostname.\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --address 127.0.0.1 --name www.test.com --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:    testutil.GetVersion,\n\t\t\t\tCloneVersionFn:  testutil.CloneVersionResult(4),\n\t\t\t\tCreateBackendFn: createBackendError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t\t// The following test is the same as above but mocks a successful backend\n\t\t// creation so we can validate the correct service version was utilised.\n\t\t//\n\t\t// NOTE: Added --port flag to validate that a nil pointer dereference is\n\t\t// not triggered at runtime when parsing the arguments.\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --address 127.0.0.1 --name www.test.com --autoclone --port 8080\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:    testutil.GetVersion,\n\t\t\t\tCloneVersionFn:  testutil.CloneVersionResult(4),\n\t\t\t\tCreateBackendFn: createBackendWithPort(8080),\n\t\t\t},\n\t\t\tWantOutput: \"Created backend www.test.com (service 123 version 4)\",\n\t\t},\n\t\t// We test that setting an invalid host override does not result in an error\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --address 127.0.0.1 --override-host invalid-host-override --name www.test.com --autoclone --port 8080\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:    testutil.GetVersion,\n\t\t\t\tCloneVersionFn:  testutil.CloneVersionResult(4),\n\t\t\t\tCreateBackendFn: createBackendWithPort(8080),\n\t\t\t},\n\t\t\tWantOutput: \"Created backend www.test.com (service 123 version 4)\",\n\t\t},\n\t\t// The following test validates that --service-name can replace --service-id\n\t\t{\n\t\t\tArgs: \"--service-name test-service --version 1 --address 127.0.0.1 --name www.test.com --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tGetServicesFn: func(ctx context.Context, _ *fastly.GetServicesInput) *fastly.ListPaginator[fastly.Service] {\n\t\t\t\t\treturn fastly.NewPaginator[fastly.Service](ctx, &mock.HTTPClient{\n\t\t\t\t\t\tErrors: []error{nil},\n\t\t\t\t\t\tResponses: []*http.Response{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tBody: io.NopCloser(strings.NewReader(`[{\"id\": \"123\", \"name\": \"test-service\"}]`)),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t}, fastly.ListOpts{}, \"/example\")\n\t\t\t\t},\n\t\t\t\tCloneVersionFn:  testutil.CloneVersionResult(4),\n\t\t\t\tCreateBackendFn: createBackendOK,\n\t\t\t},\n\t\t\tWantOutput: \"Created backend www.test.com (service 123 version 4)\",\n\t\t},\n\t\t// The following test is the same as above but appends both --use-ssl and\n\t\t// --verbose so we may validate the expected output message regarding a\n\t\t// missing port is displayed.\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --address 127.0.0.1 --name www.test.com --autoclone --use-ssl --verbose\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:    testutil.GetVersion,\n\t\t\t\tCloneVersionFn:  testutil.CloneVersionResult(4),\n\t\t\t\tCreateBackendFn: createBackendWithPort(443),\n\t\t\t},\n\t\t\tWantOutput: \"Use-ssl was set but no port was specified, using default port 443\",\n\t\t},\n\t\t// The following test is the same as above but appends --port, --use-ssl and\n\t\t// --verbose so we may validate a successful backend creation.\n\t\t//\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --address 127.0.0.1 --name www.test.com --autoclone --port 8443 --use-ssl --verbose\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:    testutil.GetVersion,\n\t\t\t\tCloneVersionFn:  testutil.CloneVersionResult(4),\n\t\t\t\tCreateBackendFn: createBackendWithPort(8443),\n\t\t\t},\n\t\t\tWantOutput: \"Created backend www.test.com (service 123 version 4)\",\n\t\t},\n\t\t// The following test specifies a service version that's 'inactive' and not 'locked',\n\t\t// and subsequently we expect it to be the same editable version.\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 3 --address 127.0.0.1 --name www.test.com\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:    testutil.GetVersion,\n\t\t\t\tCreateBackendFn: createBackendOK,\n\t\t\t},\n\t\t\tWantOutput: \"Created backend www.test.com (service 123 version 3)\",\n\t\t},\n\t\t// The following tests verify parsing of the --tcp-ka-enable flag.\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 3 --address 127.0.0.1 --name www.test.com --tcp-ka-enabled=true\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:    testutil.GetVersion,\n\t\t\t\tCreateBackendFn: createBackendOK,\n\t\t\t},\n\t\t\tWantOutput: \"Created backend www.test.com (service 123 version 3)\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 3 --address 127.0.0.1 --name www.test.com --tcp-ka-enabled=false\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:    testutil.GetVersion,\n\t\t\t\tCreateBackendFn: createBackendOK,\n\t\t\t},\n\t\t\tWantOutput: \"Created backend www.test.com (service 123 version 3)\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 3 --address 127.0.0.1 --name www.test.com --tcp-ka-enabled=invalid\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:    testutil.GetVersion,\n\t\t\t\tCreateBackendFn: createBackendOK,\n\t\t\t},\n\t\t\tWantError: \"'tcp-ka-enabled' flag must be one of the following [true, false]\",\n\t\t},\n\t\t// The following tests verify parsing of the --prefer-ipv6 flag.\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 3 --address 127.0.0.1 --name www.test.com --prefer-ipv6=true\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:    testutil.GetVersion,\n\t\t\t\tCreateBackendFn: createBackendOK,\n\t\t\t},\n\t\t\tWantOutput: \"Created backend www.test.com (service 123 version 3)\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 3 --address 127.0.0.1 --name www.test.com --prefer-ipv6=false\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:    testutil.GetVersion,\n\t\t\t\tCreateBackendFn: createBackendOK,\n\t\t\t},\n\t\t\tWantOutput: \"Created backend www.test.com (service 123 version 3)\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 3 --address 127.0.0.1 --name www.test.com --prefer-ipv6=invalid\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:    testutil.GetVersion,\n\t\t\t\tCreateBackendFn: createBackendOK,\n\t\t\t},\n\t\t\tWantError: \"'prefer-ipv6' flag must be one of the following [true, false]\",\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"create\"}, scenarios)\n}\n\nfunc TestBackendList(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --json\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tListBackendsFn: listBackendsOK,\n\t\t\t},\n\t\t\tWantOutput: listBackendsJSONOutput,\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --json --verbose\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tListBackendsFn: listBackendsOK,\n\t\t\t},\n\t\t\tWantError: fsterr.ErrInvalidVerboseJSONCombo.Error(),\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tListBackendsFn: listBackendsOK,\n\t\t\t},\n\t\t\tWantOutput: listBackendsShortOutput,\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --verbose\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tListBackendsFn: listBackendsOK,\n\t\t\t},\n\t\t\tWantOutput: listBackendsVerboseOutput,\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 -v\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tListBackendsFn: listBackendsOK,\n\t\t\t},\n\t\t\tWantOutput: listBackendsVerboseOutput,\n\t\t},\n\t\t{\n\t\t\tArgs: \"--verbose --service-id 123 --version 1\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tListBackendsFn: listBackendsOK,\n\t\t\t},\n\t\t\tWantOutput: listBackendsVerboseOutput,\n\t\t},\n\t\t{\n\t\t\tArgs: \"-v --service-id 123 --version 1\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tListBackendsFn: listBackendsOK,\n\t\t\t},\n\t\t\tWantOutput: listBackendsVerboseOutput,\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tListBackendsFn: listBackendsError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"list\"}, scenarios)\n}\n\nfunc TestBackendDescribe(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"--service-id 123 --version 1\",\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name www.test.com\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tGetBackendFn: getBackendError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name www.test.com\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tGetBackendFn: getBackendOK,\n\t\t\t},\n\t\t\tWantOutput: describeBackendOutput,\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"describe\"}, scenarios)\n}\n\nfunc TestBackendUpdate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"--service-id 123 --version 2 --new-name www.test.com --comment \",\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name www.test.com --new-name www.example.com --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:    testutil.GetVersion,\n\t\t\t\tCloneVersionFn:  testutil.CloneVersionResult(4),\n\t\t\t\tGetBackendFn:    getBackendOK,\n\t\t\t\tUpdateBackendFn: updateBackendError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name www.test.com --new-name www.example.com --comment  --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:    testutil.GetVersion,\n\t\t\t\tCloneVersionFn:  testutil.CloneVersionResult(4),\n\t\t\t\tGetBackendFn:    getBackendOK,\n\t\t\t\tUpdateBackendFn: updateBackendOK,\n\t\t\t},\n\t\t\tWantOutput: \"Updated backend www.example.com (service 123 version 4)\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API error when modifying active version\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tGetBackendFn: getBackendOK,\n\t\t\t\tUpdateBackendFn: func(_ context.Context, i *fastly.UpdateBackendInput) (*fastly.Backend, error) {\n\t\t\t\t\treturn nil, fmt.Errorf(\"Cannot update version %d. Versions that have been activated cannot be updated\", i.ServiceVersion)\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:      \"--name www.test.com --new-name www.example.com --service-id 123 --version 3\",\n\t\t\tWantError: \"Cannot update version 3. Versions that have been activated cannot be updated\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API error when modifying locked version\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tGetBackendFn: getBackendOK,\n\t\t\t\tUpdateBackendFn: func(_ context.Context, i *fastly.UpdateBackendInput) (*fastly.Backend, error) {\n\t\t\t\t\treturn nil, fmt.Errorf(\"Cannot update version %d. Versions that have been locked cannot be updated\", i.ServiceVersion)\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:      \"--name www.test.com --new-name www.example.com --service-id 123 --version 3\",\n\t\t\tWantError: \"Cannot update version 3. Versions that have been locked cannot be updated\",\n\t\t},\n\t\t// The following tests verify parsing of the --tcp-ka-enable flag.\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 3 --name www.test.com --tcp-ka-enabled=true --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:    testutil.GetVersion,\n\t\t\t\tCloneVersionFn:  testutil.CloneVersionResult(4),\n\t\t\t\tGetBackendFn:    getBackendOK,\n\t\t\t\tUpdateBackendFn: updateBackendOK,\n\t\t\t},\n\t\t\tWantOutput: \"Updated backend  (service 123 version 4)\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name www.test.com --tcp-ka-enabled=false --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:    testutil.GetVersion,\n\t\t\t\tCloneVersionFn:  testutil.CloneVersionResult(4),\n\t\t\t\tGetBackendFn:    getBackendOK,\n\t\t\t\tUpdateBackendFn: updateBackendOK,\n\t\t\t},\n\t\t\tWantOutput: \"Updated backend  (service 123 version 4)\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name www.test.com --tcp-ka-enabled=invalid --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:    testutil.GetVersion,\n\t\t\t\tCloneVersionFn:  testutil.CloneVersionResult(4),\n\t\t\t\tGetBackendFn:    getBackendOK,\n\t\t\t\tUpdateBackendFn: updateBackendOK,\n\t\t\t},\n\t\t\tWantError: \"'tcp-ka-enabled' flag must be one of the following [true, false]\",\n\t\t},\n\t\t// The following tests verify parsing of the --prefer-ipv6 flag.\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name www.test.com --prefer-ipv6=true --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:    testutil.GetVersion,\n\t\t\t\tCloneVersionFn:  testutil.CloneVersionResult(4),\n\t\t\t\tGetBackendFn:    getBackendOK,\n\t\t\t\tUpdateBackendFn: updateBackendOK,\n\t\t\t},\n\t\t\tWantOutput: \"Updated backend  (service 123 version 4)\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name www.test.com --prefer-ipv6=false --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:    testutil.GetVersion,\n\t\t\t\tCloneVersionFn:  testutil.CloneVersionResult(4),\n\t\t\t\tGetBackendFn:    getBackendOK,\n\t\t\t\tUpdateBackendFn: updateBackendOK,\n\t\t\t},\n\t\t\tWantOutput: \"Updated backend  (service 123 version 4)\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name www.test.com --prefer-ipv6=invalid --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:    testutil.GetVersion,\n\t\t\t\tCloneVersionFn:  testutil.CloneVersionResult(4),\n\t\t\t\tGetBackendFn:    getBackendOK,\n\t\t\t\tUpdateBackendFn: updateBackendOK,\n\t\t\t},\n\t\t\tWantError: \"'prefer-ipv6' flag must be one of the following [true, false]\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate --autoclone results in cloned service version\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:    testutil.GetVersion,\n\t\t\t\tCloneVersionFn:  testutil.CloneVersionResult(4),\n\t\t\t\tGetBackendFn:    getBackendOK,\n\t\t\t\tUpdateBackendFn: updateBackendOK,\n\t\t\t},\n\t\t\tArgs:       \"--autoclone --name www.test.com --new-name www.example.com --service-id 123 --version 1\",\n\t\t\tWantOutput: \"Updated backend www.example.com (service 123 version 4)\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate --autoclone on locked version\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tGetBackendFn:   getBackendOK,\n\t\t\t\tUpdateBackendFn: func(_ context.Context, i *fastly.UpdateBackendInput) (*fastly.Backend, error) {\n\t\t\t\t\t// Verify operation happens on the cloned version (4), not original (2)\n\t\t\t\t\tif i.ServiceVersion != 4 {\n\t\t\t\t\t\treturn nil, fmt.Errorf(\"expected operation on cloned version 4, got %d\", i.ServiceVersion)\n\t\t\t\t\t}\n\t\t\t\t\treturn &fastly.Backend{\n\t\t\t\t\t\tServiceID:      fastly.ToPointer(i.ServiceID),\n\t\t\t\t\t\tServiceVersion: fastly.ToPointer(i.ServiceVersion),\n\t\t\t\t\t\tName:           i.NewName,\n\t\t\t\t\t\tComment:        i.Comment,\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:       \"--autoclone --name www.test.com --new-name www.example.com --service-id 123 --version 2\",\n\t\t\tWantOutput: \"Updated backend www.example.com (service 123 version 4)\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate --autoclone on editable version\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tGetBackendFn:   getBackendOK,\n\t\t\t\tUpdateBackendFn: func(_ context.Context, i *fastly.UpdateBackendInput) (*fastly.Backend, error) {\n\t\t\t\t\t// Verify operation happens on the cloned version (4), not original (3)\n\t\t\t\t\tif i.ServiceVersion != 4 {\n\t\t\t\t\t\treturn nil, fmt.Errorf(\"expected operation on cloned version 4, got %d\", i.ServiceVersion)\n\t\t\t\t\t}\n\t\t\t\t\treturn &fastly.Backend{\n\t\t\t\t\t\tServiceID:      fastly.ToPointer(i.ServiceID),\n\t\t\t\t\t\tServiceVersion: fastly.ToPointer(i.ServiceVersion),\n\t\t\t\t\t\tName:           i.NewName,\n\t\t\t\t\t\tComment:        i.Comment,\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:       \"--autoclone --name www.test.com --new-name www.example.com --service-id 123 --version 3\",\n\t\t\tWantOutput: \"Updated backend www.example.com (service 123 version 4)\",\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"update\"}, scenarios)\n}\n\nfunc TestBackendDelete(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"--service-id 123 --version 1\",\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name www.test.com --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:    testutil.GetVersion,\n\t\t\t\tCloneVersionFn:  testutil.CloneVersionResult(4),\n\t\t\t\tDeleteBackendFn: deleteBackendError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name www.test.com --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:    testutil.GetVersion,\n\t\t\t\tCloneVersionFn:  testutil.CloneVersionResult(4),\n\t\t\t\tDeleteBackendFn: deleteBackendOK,\n\t\t\t},\n\t\t\tWantOutput: \"Deleted backend www.test.com (service 123 version 4)\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API error when modifying active version\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tDeleteBackendFn: func(_ context.Context, i *fastly.DeleteBackendInput) error {\n\t\t\t\t\treturn fmt.Errorf(\"Cannot update version %d. Versions that have been activated cannot be updated\", i.ServiceVersion)\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:      \"--name www.test.com --service-id 123 --version 3\",\n\t\t\tWantError: \"Cannot update version 3. Versions that have been activated cannot be updated\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API error when modifying locked version\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tDeleteBackendFn: func(_ context.Context, i *fastly.DeleteBackendInput) error {\n\t\t\t\t\treturn fmt.Errorf(\"Cannot update version %d. Versions that have been locked cannot be updated\", i.ServiceVersion)\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:      \"--name www.test.com --service-id 123 --version 3\",\n\t\t\tWantError: \"Cannot update version 3. Versions that have been locked cannot be updated\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate --autoclone results in cloned service version\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:    testutil.GetVersion,\n\t\t\t\tCloneVersionFn:  testutil.CloneVersionResult(4),\n\t\t\t\tDeleteBackendFn: deleteBackendOK,\n\t\t\t},\n\t\t\tArgs:       \"--autoclone --name www.test.com --service-id 123 --version 1\",\n\t\t\tWantOutput: \"Deleted backend www.test.com (service 123 version 4)\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate --autoclone on locked version\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tDeleteBackendFn: func(_ context.Context, i *fastly.DeleteBackendInput) error {\n\t\t\t\t\t// Verify operation happens on the cloned version (4), not original (2)\n\t\t\t\t\tif i.ServiceVersion != 4 {\n\t\t\t\t\t\treturn fmt.Errorf(\"expected operation on cloned version 4, got %d\", i.ServiceVersion)\n\t\t\t\t\t}\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:       \"--autoclone --name www.test.com --service-id 123 --version 2\",\n\t\t\tWantOutput: \"Deleted backend www.test.com (service 123 version 4)\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate --autoclone on editable version\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tDeleteBackendFn: func(_ context.Context, i *fastly.DeleteBackendInput) error {\n\t\t\t\t\t// Verify operation happens on the cloned version (4), not original (3)\n\t\t\t\t\tif i.ServiceVersion != 4 {\n\t\t\t\t\t\treturn fmt.Errorf(\"expected operation on cloned version 4, got %d\", i.ServiceVersion)\n\t\t\t\t\t}\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:       \"--autoclone --name www.test.com --service-id 123 --version 3\",\n\t\t\tWantOutput: \"Deleted backend www.test.com (service 123 version 4)\",\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"delete\"}, scenarios)\n}\n\nvar errTest = errors.New(\"fixture error\")\n\nfunc createBackendOK(_ context.Context, i *fastly.CreateBackendInput) (*fastly.Backend, error) {\n\tif i.Name == nil {\n\t\ti.Name = fastly.ToPointer(\"\")\n\t}\n\treturn &fastly.Backend{\n\t\tServiceID:      fastly.ToPointer(i.ServiceID),\n\t\tServiceVersion: fastly.ToPointer(i.ServiceVersion),\n\t\tName:           i.Name,\n\t}, nil\n}\n\nfunc createBackendError(_ context.Context, _ *fastly.CreateBackendInput) (*fastly.Backend, error) {\n\treturn nil, errTest\n}\n\nfunc createBackendWithPort(wantPort int) func(_ context.Context, _ *fastly.CreateBackendInput) (*fastly.Backend, error) {\n\treturn func(ctx context.Context, i *fastly.CreateBackendInput) (*fastly.Backend, error) {\n\t\tswitch {\n\t\t// if overridehost is set, should be a non \"\" value\n\t\tcase i.Port != nil && *i.Port == wantPort && ((i.OverrideHost == nil) || (i.OverrideHost != nil && *i.OverrideHost != \"\")):\n\t\t\treturn createBackendOK(ctx, i)\n\t\tdefault:\n\t\t\treturn createBackendError(ctx, i)\n\t\t}\n\t}\n}\n\nfunc listBackendsOK(_ context.Context, i *fastly.ListBackendsInput) ([]*fastly.Backend, error) {\n\treturn []*fastly.Backend{\n\t\t{\n\t\t\tAddress:        fastly.ToPointer(\"www.test.com\"),\n\t\t\tComment:        fastly.ToPointer(\"test\"),\n\t\t\tName:           fastly.ToPointer(\"test.com\"),\n\t\t\tPort:           fastly.ToPointer(80),\n\t\t\tServiceID:      fastly.ToPointer(i.ServiceID),\n\t\t\tServiceVersion: fastly.ToPointer(i.ServiceVersion),\n\t\t},\n\t\t{\n\t\t\tAddress:        fastly.ToPointer(\"www.example.com\"),\n\t\t\tComment:        fastly.ToPointer(\"example\"),\n\t\t\tName:           fastly.ToPointer(\"example.com\"),\n\t\t\tPort:           fastly.ToPointer(443),\n\t\t\tServiceID:      fastly.ToPointer(i.ServiceID),\n\t\t\tServiceVersion: fastly.ToPointer(i.ServiceVersion),\n\t\t},\n\t}, nil\n}\n\nfunc listBackendsError(_ context.Context, _ *fastly.ListBackendsInput) ([]*fastly.Backend, error) {\n\treturn nil, errTest\n}\n\nvar listBackendsJSONOutput = strings.TrimSpace(`\n[\n  {\n    \"Address\": \"www.test.com\",\n    \"AutoLoadbalance\": null,\n    \"BetweenBytesTimeout\": null,\n    \"Comment\": \"test\",\n    \"ConnectTimeout\": null,\n    \"CreatedAt\": null,\n    \"DeletedAt\": null,\n    \"ErrorThreshold\": null,\n    \"FirstByteTimeout\": null,\n    \"HealthCheck\": null,\n    \"Hostname\": null,\n    \"KeepAliveTime\": null,\n    \"MaxConn\": null,\n    \"MaxUse\": null,\n    \"MaxLifetime\": null,\n    \"MaxTLSVersion\": null,\n    \"MinTLSVersion\": null,\n    \"Name\": \"test.com\",\n    \"OverrideHost\": null,\n    \"Port\": 80,\n    \"PreferIPv6\": null,\n    \"RequestCondition\": null,\n    \"ShareKey\": null,\n    \"SSLCACert\": null,\n    \"SSLCertHostname\": null,\n    \"SSLCheckCert\": null,\n    \"SSLCiphers\": null,\n    \"SSLClientCert\": null,\n    \"SSLClientKey\": null,\n    \"SSLSNIHostname\": null,\n    \"ServiceID\": \"123\",\n    \"ServiceVersion\": 1,\n    \"Shield\": null,\n    \"TCPKeepAliveEnable\": null,\n    \"TCPKeepAliveIntvl\": null,\n    \"TCPKeepAliveProbes\": null,\n    \"TCPKeepAliveTime\": null,\n    \"UpdatedAt\": null,\n    \"UseSSL\": null,\n    \"Weight\": null\n  },\n  {\n    \"Address\": \"www.example.com\",\n    \"AutoLoadbalance\": null,\n    \"BetweenBytesTimeout\": null,\n    \"Comment\": \"example\",\n    \"ConnectTimeout\": null,\n    \"CreatedAt\": null,\n    \"DeletedAt\": null,\n    \"ErrorThreshold\": null,\n    \"FirstByteTimeout\": null,\n    \"HealthCheck\": null,\n    \"Hostname\": null,\n    \"KeepAliveTime\": null,\n    \"MaxConn\": null,\n    \"MaxUse\": null,\n    \"MaxLifetime\": null,\n    \"MaxTLSVersion\": null,\n    \"MinTLSVersion\": null,\n    \"Name\": \"example.com\",\n    \"OverrideHost\": null,\n    \"Port\": 443,\n    \"PreferIPv6\": null,\n    \"RequestCondition\": null,\n    \"ShareKey\": null,\n    \"SSLCACert\": null,\n    \"SSLCertHostname\": null,\n    \"SSLCheckCert\": null,\n    \"SSLCiphers\": null,\n    \"SSLClientCert\": null,\n    \"SSLClientKey\": null,\n    \"SSLSNIHostname\": null,\n    \"ServiceID\": \"123\",\n    \"ServiceVersion\": 1,\n    \"Shield\": null,\n    \"TCPKeepAliveEnable\": null,\n    \"TCPKeepAliveIntvl\": null,\n    \"TCPKeepAliveProbes\": null,\n    \"TCPKeepAliveTime\": null,\n    \"UpdatedAt\": null,\n    \"UseSSL\": null,\n    \"Weight\": null\n  }\n]\n`) + \"\\n\"\n\nvar listBackendsShortOutput = strings.TrimSpace(`\nSERVICE  VERSION  NAME         ADDRESS          PORT  COMMENT\n123      1        test.com     www.test.com     80    test\n123      1        example.com  www.example.com  443   example\n`) + \"\\n\"\n\nvar listBackendsVerboseOutput = strings.Join([]string{\n\t\"Fastly API endpoint: https://api.fastly.com\",\n\t\"Fastly API token provided via config file (auth: user)\",\n\t\"\",\n\t\"Service ID (via --service-id): 123\",\n\t\"\",\n\t\"Version: 1\",\n\t\"\tBackend 1/2\",\n\t\"\t\tName: test.com\",\n\t\"\t\tComment: test\",\n\t\"\t\tAddress: www.test.com\",\n\t\"\t\tPort: 80\",\n\t\"\t\tOverride host: \",\n\t\"\t\tConnect timeout: 0\",\n\t\"\t\tMax connections: 0\",\n\t\"\t\tMax connection use: 0\",\n\t\"\t\tMax connection lifetime: 0\",\n\t\"\t\tFirst byte timeout: 0\",\n\t\"\t\tBetween bytes timeout: 0\",\n\t\"\t\tAuto loadbalance: false\",\n\t\"\t\tWeight: 0\",\n\t\"\t\tHealthcheck: \",\n\t\"\t\tShield: \",\n\t\"\t\tUse SSL: false\",\n\t\"\t\tSSL check cert: false\",\n\t\"\t\tSSL CA cert: \",\n\t\"\t\tSSL client cert: \",\n\t\"\t\tSSL client key: \",\n\t\"\t\tSSL cert hostname: \",\n\t\"\t\tSSL SNI hostname: \",\n\t\"\t\tMin TLS version: \",\n\t\"\t\tMax TLS version: \",\n\t\"\t\tSSL ciphers: \",\n\t\"\t\tHTTP KeepAlive Timeout: 0\",\n\t\"\t\tTCP KeepAlive Enabled: unset\",\n\t\"\t\tTCP KeepAlive Interval: 0\",\n\t\"\t\tTCP KeepAlive Probes: 0\",\n\t\"\t\tTCP KeepAlive Timeout: 0\",\n\t\"\tBackend 2/2\",\n\t\"\t\tName: example.com\",\n\t\"\t\tComment: example\",\n\t\"\t\tAddress: www.example.com\",\n\t\"\t\tPort: 443\",\n\t\"\t\tOverride host: \",\n\t\"\t\tConnect timeout: 0\",\n\t\"\t\tMax connections: 0\",\n\t\"\t\tMax connection use: 0\",\n\t\"\t\tMax connection lifetime: 0\",\n\t\"\t\tFirst byte timeout: 0\",\n\t\"\t\tBetween bytes timeout: 0\",\n\t\"\t\tAuto loadbalance: false\",\n\t\"\t\tWeight: 0\",\n\t\"\t\tHealthcheck: \",\n\t\"\t\tShield: \",\n\t\"\t\tUse SSL: false\",\n\t\"\t\tSSL check cert: false\",\n\t\"\t\tSSL CA cert: \",\n\t\"\t\tSSL client cert: \",\n\t\"\t\tSSL client key: \",\n\t\"\t\tSSL cert hostname: \",\n\t\"\t\tSSL SNI hostname: \",\n\t\"\t\tMin TLS version: \",\n\t\"\t\tMax TLS version: \",\n\t\"\t\tSSL ciphers: \",\n\t\"\t\tHTTP KeepAlive Timeout: 0\",\n\t\"\t\tTCP KeepAlive Enabled: unset\",\n\t\"\t\tTCP KeepAlive Interval: 0\",\n\t\"\t\tTCP KeepAlive Probes: 0\",\n\t\"\t\tTCP KeepAlive Timeout: 0\",\n}, \"\\n\") + \"\\n\\n\"\n\nfunc getBackendOK(_ context.Context, i *fastly.GetBackendInput) (*fastly.Backend, error) {\n\treturn &fastly.Backend{\n\t\tServiceID:      fastly.ToPointer(i.ServiceID),\n\t\tServiceVersion: fastly.ToPointer(i.ServiceVersion),\n\t\tName:           fastly.ToPointer(\"test.com\"),\n\t\tAddress:        fastly.ToPointer(\"www.test.com\"),\n\t\tPort:           fastly.ToPointer(80),\n\t\tComment:        fastly.ToPointer(\"test\"),\n\t}, nil\n}\n\nfunc getBackendError(_ context.Context, _ *fastly.GetBackendInput) (*fastly.Backend, error) {\n\treturn nil, errTest\n}\n\nvar describeBackendOutput = strings.Join([]string{\n\t\"\\nService ID: 123\",\n\t\"Service Version: 1\\n\",\n\t\"Name: test.com\",\n\t\"Comment: test\",\n\t\"Address: www.test.com\",\n\t\"Port: 80\",\n\t\"Prefer IPv6: false\",\n\t\"Override host: \",\n\t\"Connect timeout: 0\",\n\t\"Max connections: 0\",\n\t\"Max connection use: 0\",\n\t\"Max connection lifetime: 0\",\n\t\"First byte timeout: 0\",\n\t\"Between bytes timeout: 0\",\n\t\"Auto loadbalance: false\",\n\t\"Weight: 0\",\n\t\"Healthcheck: \",\n\t\"Shield: \",\n\t\"Use SSL: false\",\n\t\"SSL check cert: false\",\n\t\"SSL CA cert: \",\n\t\"SSL client cert: \",\n\t\"SSL client key: \",\n\t\"SSL cert hostname: \",\n\t\"SSL SNI hostname: \",\n\t\"Min TLS version: \",\n\t\"Max TLS version: \",\n\t\"SSL ciphers: \",\n\t\"HTTP KeepAlive Timeout: 0\",\n\t\"TCP KeepAlive Enabled: unset\",\n\t\"TCP KeepAlive Interval: 0\",\n\t\"TCP KeepAlive Probes: 0\",\n\t\"TCP KeepAlive Timeout: 0\",\n}, \"\\n\") + \"\\n\"\n\nfunc updateBackendOK(_ context.Context, i *fastly.UpdateBackendInput) (*fastly.Backend, error) {\n\treturn &fastly.Backend{\n\t\tServiceID:      fastly.ToPointer(i.ServiceID),\n\t\tServiceVersion: fastly.ToPointer(i.ServiceVersion),\n\t\tName:           i.NewName,\n\t\tComment:        i.Comment,\n\t}, nil\n}\n\nfunc updateBackendError(_ context.Context, _ *fastly.UpdateBackendInput) (*fastly.Backend, error) {\n\treturn nil, errTest\n}\n\nfunc deleteBackendOK(_ context.Context, _ *fastly.DeleteBackendInput) error {\n\treturn nil\n}\n\nfunc deleteBackendError(_ context.Context, _ *fastly.DeleteBackendInput) error {\n\treturn errTest\n}\n"
  },
  {
    "path": "pkg/commands/service/backend/create.go",
    "content": "package backend\n\nimport (\n\t\"context\"\n\t\"io\"\n\t\"net\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// CreateCommand calls the Fastly API to create backends.\ntype CreateCommand struct {\n\targparser.Base\n\n\t// Required.\n\tserviceVersion argparser.OptionalServiceVersion\n\n\t// Optional.\n\taddress             argparser.OptionalString\n\tautoClone           argparser.OptionalAutoClone\n\tautoLoadBalance     argparser.OptionalBool\n\tbetweenBytesTimeout argparser.OptionalInt\n\tcomment             argparser.OptionalString\n\tconnectTimeout      argparser.OptionalInt\n\tfirstByteTimeout    argparser.OptionalInt\n\thealthCheck         argparser.OptionalString\n\tmaxConn             argparser.OptionalInt\n\tmaxUse              argparser.OptionalInt\n\tmaxLifetime         argparser.OptionalInt\n\tmaxTLSVersion       argparser.OptionalString\n\tminTLSVersion       argparser.OptionalString\n\tname                argparser.OptionalString\n\tnoSSLCheckCert      argparser.OptionalBool\n\toverrideHost        argparser.OptionalString\n\tport                argparser.OptionalInt\n\tpreferIPv6          argparser.OptionalString\n\trequestCondition    argparser.OptionalString\n\tserviceName         argparser.OptionalServiceNameID\n\tshield              argparser.OptionalString\n\tsslCACert           argparser.OptionalString\n\tsslCertHostname     argparser.OptionalString\n\tsslCheckCert        argparser.OptionalBool\n\tsslCiphers          argparser.OptionalString\n\tsslClientCert       argparser.OptionalString\n\tsslClientKey        argparser.OptionalString\n\tsslSNIHostname      argparser.OptionalString\n\ttcpKaEnable         argparser.OptionalString\n\ttcpKaInterval       argparser.OptionalInt\n\ttcpKaProbes         argparser.OptionalInt\n\ttcpKaTime           argparser.OptionalInt\n\thttpKaTime          argparser.OptionalInt\n\tuseSSL              argparser.OptionalBool\n\tweight              argparser.OptionalInt\n}\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"create\", \"Create a backend on a Fastly service version\").Alias(\"add\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\n\tc.CmdClause.Flag(\"address\", \"A hostname, IPv4, or IPv6 address for the backend\").Action(c.address.Set).StringVar(&c.address.Value)\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.autoClone.Set,\n\t\tDst:    &c.autoClone.Value,\n\t})\n\tc.CmdClause.Flag(\"auto-loadbalance\", \"Whether or not this backend should be automatically load balanced\").Action(c.autoLoadBalance.Set).BoolVar(&c.autoLoadBalance.Value)\n\tc.CmdClause.Flag(\"between-bytes-timeout\", \"How long to wait between bytes in milliseconds\").Action(c.betweenBytesTimeout.Set).IntVar(&c.betweenBytesTimeout.Value)\n\tc.CmdClause.Flag(\"comment\", \"A descriptive note\").Action(c.comment.Set).StringVar(&c.comment.Value)\n\tc.CmdClause.Flag(\"connect-timeout\", \"How long to wait for a timeout in milliseconds\").Action(c.connectTimeout.Set).IntVar(&c.connectTimeout.Value)\n\tc.CmdClause.Flag(\"first-byte-timeout\", \"How long to wait for the first bytes in milliseconds\").Action(c.firstByteTimeout.Set).IntVar(&c.firstByteTimeout.Value)\n\tc.CmdClause.Flag(\"healthcheck\", \"The name of the healthcheck to use with this backend\").Action(c.healthCheck.Set).StringVar(&c.healthCheck.Value)\n\tc.CmdClause.Flag(\"max-conn\", \"Maximum number of connections\").Action(c.maxConn.Set).IntVar(&c.maxConn.Value)\n\tc.CmdClause.Flag(\"max-use\", \"Maximum number of requests allowed over a single, pooled HTTP keepalive connection to this backend; 0 is treated as unlimited.\").Action(c.maxUse.Set).IntVar(&c.maxUse.Value)\n\tc.CmdClause.Flag(\"max-lifetime\", \"Maximum time from creation (in milliseconds) that a pooled HTTP keepalive connection will be eligible for reuse; 0 is treated as unlimited.\").Action(c.maxLifetime.Set).IntVar(&c.maxLifetime.Value)\n\tc.CmdClause.Flag(\"max-tls-version\", \"Maximum allowed TLS version on SSL connections to this backend\").Action(c.maxTLSVersion.Set).StringVar(&c.maxTLSVersion.Value)\n\tc.CmdClause.Flag(\"min-tls-version\", \"Minimum allowed TLS version on SSL connections to this backend\").Action(c.minTLSVersion.Set).StringVar(&c.minTLSVersion.Value)\n\tc.CmdClause.Flag(\"name\", \"Backend name\").Short('n').Action(c.name.Set).StringVar(&c.name.Value)\n\tc.CmdClause.Flag(\"no-ssl-check-cert\", \"Skip checking SSL certs\").Action(c.noSSLCheckCert.Set).BoolVar(&c.noSSLCheckCert.Value)\n\tc.CmdClause.Flag(\"override-host\", \"The hostname to override the Host header\").Action(c.overrideHost.Set).StringVar(&c.overrideHost.Value)\n\tc.CmdClause.Flag(\"port\", \"Port number of the address\").Action(c.port.Set).IntVar(&c.port.Value)\n\tc.CmdClause.Flag(\"prefer-ipv6\", \"Prefer IPv6 connections [true, false]\").Action(c.preferIPv6.Set).StringVar(&c.preferIPv6.Value)\n\tc.CmdClause.Flag(\"request-condition\", \"Condition, which if met, will select this backend during a request\").Action(c.requestCondition.Set).StringVar(&c.requestCondition.Value)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\tc.CmdClause.Flag(\"shield\", \"The shield POP designated to reduce inbound load on this origin by serving the cached data to the rest of the network\").Action(c.shield.Set).StringVar(&c.shield.Value)\n\tc.CmdClause.Flag(\"ssl-ca-cert\", \"CA certificate attached to origin\").Action(c.sslCACert.Set).StringVar(&c.sslCACert.Value)\n\tc.CmdClause.Flag(\"ssl-cert-hostname\", \"Overrides ssl_hostname, but only for cert verification. Does not affect SNI at all.\").Action(c.sslCertHostname.Set).StringVar(&c.sslCertHostname.Value)\n\tc.CmdClause.Flag(\"ssl-check-cert\", \"Be strict on checking SSL certs\").Action(c.sslCheckCert.Set).BoolVar(&c.sslCheckCert.Value)\n\tc.CmdClause.Flag(\"ssl-ciphers\", \"List of OpenSSL ciphers (https://www.openssl.org/docs/man1.0.2/man1/ciphers)\").Action(c.sslCiphers.Set).StringVar(&c.sslCiphers.Value)\n\tc.CmdClause.Flag(\"ssl-client-cert\", \"Client certificate attached to origin\").Action(c.sslClientCert.Set).StringVar(&c.sslClientCert.Value)\n\tc.CmdClause.Flag(\"ssl-client-key\", \"Client key attached to origin\").Action(c.sslClientKey.Set).StringVar(&c.sslClientKey.Value)\n\tc.CmdClause.Flag(\"ssl-sni-hostname\", \"Overrides ssl_hostname, but only for SNI in the handshake. Does not affect cert validation at all.\").Action(c.sslSNIHostname.Set).StringVar(&c.sslSNIHostname.Value)\n\tc.CmdClause.Flag(\"tcp-ka-enabled\", \"Enable TCP keepalive probes [true, false]\").Action(c.tcpKaEnable.Set).StringVar(&c.tcpKaEnable.Value)\n\tc.CmdClause.Flag(\"tcp-ka-interval\", \"Configure how long to wait between sending each TCP keepalive probe.\").Action(c.tcpKaInterval.Set).IntVar(&c.tcpKaInterval.Value)\n\tc.CmdClause.Flag(\"tcp-ka-probes\", \"Configure how many unacknowledged TCP keepalive probes to send before considering the connection dead.\").Action(c.tcpKaProbes.Set).IntVar(&c.tcpKaProbes.Value)\n\tc.CmdClause.Flag(\"tcp-ka-time\", \"Configure how long to wait after the last sent data before sending TCP keepalive probes.\").Action(c.tcpKaTime.Set).IntVar(&c.tcpKaTime.Value)\n\tc.CmdClause.Flag(\"http-ka-time\", \"Configure how long to keep idle HTTP keepalive connections in the connection pool.\").Action(c.httpKaTime.Set).IntVar(&c.httpKaTime.Value)\n\tc.CmdClause.Flag(\"use-ssl\", \"Whether or not to use SSL to reach the backend\").Action(c.useSSL.Set).BoolVar(&c.useSSL.Value)\n\tc.CmdClause.Flag(\"weight\", \"Weight used to load balance this backend against others\").Action(c.weight.Set).IntVar(&c.weight.Value)\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *CreateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(false),\n\t\tLocked:             optional.Of(false),\n\t\tAutoCloneFlag:      c.autoClone,\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fsterr.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\tinput := fastly.CreateBackendInput{\n\t\tServiceID:      serviceID,\n\t\tServiceVersion: fastly.ToValue(serviceVersion.Number),\n\t}\n\n\tif c.name.WasSet {\n\t\tinput.Name = &c.name.Value\n\t}\n\tif c.address.WasSet {\n\t\tinput.Address = &c.address.Value\n\t}\n\tif c.autoLoadBalance.WasSet {\n\t\tinput.AutoLoadbalance = fastly.ToPointer(fastly.Compatibool(c.autoLoadBalance.Value))\n\t}\n\tif c.betweenBytesTimeout.WasSet {\n\t\tinput.BetweenBytesTimeout = &c.betweenBytesTimeout.Value\n\t}\n\tif c.comment.WasSet {\n\t\tinput.Comment = &c.comment.Value\n\t}\n\tif c.connectTimeout.WasSet {\n\t\tinput.ConnectTimeout = &c.connectTimeout.Value\n\t}\n\tif c.firstByteTimeout.WasSet {\n\t\tinput.FirstByteTimeout = &c.firstByteTimeout.Value\n\t}\n\tif c.healthCheck.WasSet {\n\t\tinput.HealthCheck = &c.healthCheck.Value\n\t}\n\tif c.maxConn.WasSet {\n\t\tinput.MaxConn = &c.maxConn.Value\n\t}\n\tif c.maxUse.WasSet {\n\t\tinput.MaxUse = &c.maxUse.Value\n\t}\n\tif c.maxLifetime.WasSet {\n\t\tinput.MaxLifetime = &c.maxLifetime.Value\n\t}\n\tif c.maxTLSVersion.WasSet {\n\t\tinput.MaxTLSVersion = &c.maxTLSVersion.Value\n\t}\n\tif c.minTLSVersion.WasSet {\n\t\tinput.MinTLSVersion = &c.minTLSVersion.Value\n\t}\n\tif c.noSSLCheckCert.WasSet {\n\t\tinput.SSLCheckCert = fastly.ToPointer(fastly.Compatibool(false))\n\t}\n\tif c.overrideHost.WasSet {\n\t\tinput.OverrideHost = &c.overrideHost.Value\n\t}\n\tif c.preferIPv6.WasSet {\n\t\tpreferIPv6, err := argparser.ConvertBoolFromStringFlag(c.preferIPv6.Value, \"prefer-ipv6\")\n\t\tif err != nil {\n\t\t\tc.Globals.ErrLog.Add(err)\n\t\t\treturn err\n\t\t}\n\n\t\tinput.PreferIPv6 = fastly.ToPointer(fastly.Compatibool(*preferIPv6))\n\t}\n\tif c.requestCondition.WasSet {\n\t\tinput.RequestCondition = &c.requestCondition.Value\n\t}\n\tif c.shield.WasSet {\n\t\tinput.Shield = &c.shield.Value\n\t}\n\tif c.sslCACert.WasSet {\n\t\tinput.SSLCACert = &c.sslCACert.Value\n\t}\n\tif c.sslCertHostname.WasSet {\n\t\tinput.SSLCertHostname = &c.sslCertHostname.Value\n\t}\n\tif c.sslCheckCert.WasSet {\n\t\ttext.Deprecated(\"The Fastly API defaults `ssl_check_cert` to true. Use `--no-ssl-check-cert` to disable this setting.\\n\\n\")\n\t\tinput.SSLCheckCert = fastly.ToPointer(fastly.Compatibool(c.sslCheckCert.Value))\n\t}\n\tif c.sslCiphers.WasSet {\n\t\tinput.SSLCiphers = &c.sslCiphers.Value\n\t}\n\tif c.sslClientCert.WasSet {\n\t\tinput.SSLClientCert = &c.sslClientCert.Value\n\t}\n\tif c.sslClientKey.WasSet {\n\t\tinput.SSLClientKey = &c.sslClientKey.Value\n\t}\n\tif c.sslSNIHostname.WasSet {\n\t\tinput.SSLSNIHostname = &c.sslSNIHostname.Value\n\t}\n\tif c.tcpKaEnable.WasSet {\n\t\ttcpKaEnable, err := argparser.ConvertBoolFromStringFlag(c.tcpKaEnable.Value, \"tcp-ka-enabled\")\n\t\tif err != nil {\n\t\t\tc.Globals.ErrLog.Add(err)\n\t\t\treturn err\n\t\t}\n\n\t\tinput.TCPKeepAliveEnable = tcpKaEnable\n\t}\n\tif c.tcpKaInterval.WasSet {\n\t\tinput.TCPKeepAliveIntvl = &c.tcpKaInterval.Value\n\t}\n\tif c.tcpKaProbes.WasSet {\n\t\tinput.TCPKeepAliveProbes = &c.tcpKaProbes.Value\n\t}\n\tif c.tcpKaTime.WasSet {\n\t\tinput.TCPKeepAliveTime = &c.tcpKaTime.Value\n\t}\n\tif c.httpKaTime.WasSet {\n\t\tinput.KeepAliveTime = &c.httpKaTime.Value\n\t}\n\tif c.weight.WasSet {\n\t\tinput.Weight = &c.weight.Value\n\t}\n\n\tswitch {\n\tcase c.port.WasSet:\n\t\tinput.Port = &c.port.Value\n\tcase c.useSSL.WasSet && c.useSSL.Value:\n\t\tif c.Globals.Flags.Verbose {\n\t\t\ttext.Warning(out, \"Use-ssl was set but no port was specified, using default port 443\\n\\n\")\n\t\t}\n\t\tinput.Port = fastly.ToPointer(443)\n\t}\n\n\tif input.Address != nil && !c.overrideHost.WasSet && !c.sslCertHostname.WasSet && !c.sslSNIHostname.WasSet {\n\t\toverrideHost, sslSNIHostname, sslCertHostname := SetBackendHostDefaults(*input.Address)\n\t\tif overrideHost != \"\" {\n\t\t\tinput.OverrideHost = &overrideHost\n\t\t}\n\t\tinput.SSLSNIHostname = &sslSNIHostname\n\t\tinput.SSLCertHostname = &sslCertHostname\n\t} else {\n\t\tif c.overrideHost.WasSet {\n\t\t\tinput.OverrideHost = &c.overrideHost.Value\n\t\t}\n\t\tif c.sslCertHostname.WasSet {\n\t\t\tinput.SSLCertHostname = &c.sslCertHostname.Value\n\t\t}\n\t\tif c.sslSNIHostname.WasSet {\n\t\t\tinput.SSLSNIHostname = &c.sslSNIHostname.Value\n\t\t}\n\t}\n\n\tb, err := c.Globals.APIClient.CreateBackend(context.TODO(), &input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": serviceVersion.Number,\n\t\t})\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Created backend %s (service %s version %d)\", fastly.ToValue(b.Name), fastly.ToValue(b.ServiceID), fastly.ToValue(b.ServiceVersion))\n\treturn nil\n}\n\n// SetBackendHostDefaults configures the OverrideHost and SSLSNIHostname fields.\n//\n// By default we set the override_host and ssl_sni_hostname properties of the\n// Backend object to the hostname, unless the given input is an IP.\nfunc SetBackendHostDefaults(address string) (overrideHost, sslSNIHostname, sslCertHostname string) {\n\tif _, err := net.LookupAddr(address); err != nil {\n\t\toverrideHost = address\n\t}\n\tif overrideHost != \"\" {\n\t\tsslSNIHostname = overrideHost\n\t\tsslCertHostname = overrideHost\n\t}\n\treturn overrideHost, sslSNIHostname, sslCertHostname\n}\n"
  },
  {
    "path": "pkg/commands/service/backend/delete.go",
    "content": "package backend\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DeleteCommand calls the Fastly API to delete backends.\ntype DeleteCommand struct {\n\targparser.Base\n\tInput          fastly.DeleteBackendInput\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n\tautoClone      argparser.OptionalAutoClone\n}\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"delete\", \"Delete a backend on a Fastly service version\").Alias(\"remove\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"Backend name\").Short('n').Required().StringVar(&c.Input.Name)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.autoClone.Set,\n\t\tDst:    &c.autoClone.Value,\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DeleteCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(false),\n\t\tLocked:             optional.Of(false),\n\t\tAutoCloneFlag:      c.autoClone,\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": errors.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tc.Input.ServiceID = serviceID\n\tc.Input.ServiceVersion = fastly.ToValue(serviceVersion.Number)\n\n\tif err := c.Globals.APIClient.DeleteBackend(context.TODO(), &c.Input); err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": serviceVersion.Number,\n\t\t})\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Deleted backend %s (service %s version %d)\", c.Input.Name, c.Input.ServiceID, c.Input.ServiceVersion)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/backend/describe.go",
    "content": "package backend\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// DescribeCommand calls the Fastly API to describe a backend.\ntype DescribeCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tInput          fastly.GetBackendInput\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n}\n\n// NewDescribeCommand returns a usable command registered under the parent.\nfunc NewDescribeCommand(parent argparser.Registerer, g *global.Data) *DescribeCommand {\n\tc := DescribeCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"describe\", \"Show detailed information about a backend on a Fastly service version\").Alias(\"get\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"Name of backend\").Short('n').Required().StringVar(&c.Input.Name)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DescribeCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fsterr.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tc.Input.ServiceID = serviceID\n\tc.Input.ServiceVersion = fastly.ToValue(serviceVersion.Number)\n\n\to, err := c.Globals.APIClient.GetBackend(context.TODO(), &c.Input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": serviceVersion.Number,\n\t\t})\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\treturn c.print(out, o)\n}\n\n// print displays the information returned from the API.\nfunc (c *DescribeCommand) print(out io.Writer, b *fastly.Backend) error {\n\tif !c.Globals.Verbose() {\n\t\tfmt.Fprintf(out, \"\\nService ID: %s\\n\", fastly.ToValue(b.ServiceID))\n\t}\n\tfmt.Fprintf(out, \"Service Version: %d\\n\\n\", fastly.ToValue(b.ServiceVersion))\n\tfmt.Fprintf(out, \"Name: %s\\n\", fastly.ToValue(b.Name))\n\tfmt.Fprintf(out, \"Comment: %v\\n\", fastly.ToValue(b.Comment))\n\tfmt.Fprintf(out, \"Address: %v\\n\", fastly.ToValue(b.Address))\n\tfmt.Fprintf(out, \"Port: %v\\n\", fastly.ToValue(b.Port))\n\tfmt.Fprintf(out, \"Prefer IPv6: %v\\n\", fastly.ToValue(b.PreferIPv6))\n\tfmt.Fprintf(out, \"Override host: %v\\n\", fastly.ToValue(b.OverrideHost))\n\tfmt.Fprintf(out, \"Connect timeout: %v\\n\", fastly.ToValue(b.ConnectTimeout))\n\tfmt.Fprintf(out, \"Max connections: %v\\n\", fastly.ToValue(b.MaxConn))\n\tfmt.Fprintf(out, \"Max connection use: %v\\n\", fastly.ToValue(b.MaxUse))\n\tfmt.Fprintf(out, \"Max connection lifetime: %v\\n\", fastly.ToValue(b.MaxLifetime))\n\tfmt.Fprintf(out, \"First byte timeout: %v\\n\", fastly.ToValue(b.FirstByteTimeout))\n\tfmt.Fprintf(out, \"Between bytes timeout: %v\\n\", fastly.ToValue(b.BetweenBytesTimeout))\n\tfmt.Fprintf(out, \"Auto loadbalance: %v\\n\", fastly.ToValue(b.AutoLoadbalance))\n\tfmt.Fprintf(out, \"Weight: %v\\n\", fastly.ToValue(b.Weight))\n\tfmt.Fprintf(out, \"Healthcheck: %v\\n\", fastly.ToValue(b.HealthCheck))\n\tfmt.Fprintf(out, \"Shield: %v\\n\", fastly.ToValue(b.Shield))\n\tfmt.Fprintf(out, \"Use SSL: %v\\n\", fastly.ToValue(b.UseSSL))\n\tfmt.Fprintf(out, \"SSL check cert: %v\\n\", fastly.ToValue(b.SSLCheckCert))\n\tfmt.Fprintf(out, \"SSL CA cert: %v\\n\", fastly.ToValue(b.SSLCACert))\n\tfmt.Fprintf(out, \"SSL client cert: %v\\n\", fastly.ToValue(b.SSLClientCert))\n\tfmt.Fprintf(out, \"SSL client key: %v\\n\", fastly.ToValue(b.SSLClientKey))\n\tfmt.Fprintf(out, \"SSL cert hostname: %v\\n\", fastly.ToValue(b.SSLCertHostname))\n\tfmt.Fprintf(out, \"SSL SNI hostname: %v\\n\", fastly.ToValue(b.SSLSNIHostname))\n\tfmt.Fprintf(out, \"Min TLS version: %v\\n\", fastly.ToValue(b.MinTLSVersion))\n\tfmt.Fprintf(out, \"Max TLS version: %v\\n\", fastly.ToValue(b.MaxTLSVersion))\n\tfmt.Fprintf(out, \"SSL ciphers: %v\\n\", fastly.ToValue(b.SSLCiphers))\n\tfmt.Fprintf(out, \"HTTP KeepAlive Timeout: %v\\n\", fastly.ToValue(b.KeepAliveTime))\n\tif b.TCPKeepAliveEnable == nil {\n\t\tfmt.Fprintf(out, \"TCP KeepAlive Enabled: unset\\n\")\n\t} else {\n\t\tfmt.Fprintf(out, \"TCP KeepAlive Enabled: %v\\n\", fastly.ToValue(b.TCPKeepAliveEnable))\n\t}\n\tfmt.Fprintf(out, \"TCP KeepAlive Interval: %v\\n\", fastly.ToValue(b.TCPKeepAliveIntvl))\n\tfmt.Fprintf(out, \"TCP KeepAlive Probes: %v\\n\", fastly.ToValue(b.TCPKeepAliveProbes))\n\tfmt.Fprintf(out, \"TCP KeepAlive Timeout: %v\\n\", fastly.ToValue(b.TCPKeepAliveTime))\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/backend/doc.go",
    "content": "// Package backend contains commands to inspect and manipulate Fastly service backends.\npackage backend\n"
  },
  {
    "path": "pkg/commands/service/backend/list.go",
    "content": "package backend\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// ListCommand calls the Fastly API to list backends.\ntype ListCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tInput          fastly.ListBackendsInput\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n}\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"list\", \"List backends on a Fastly service version\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *ListCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fsterr.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tc.Input.ServiceID = serviceID\n\tc.Input.ServiceVersion = fastly.ToValue(serviceVersion.Number)\n\n\to, err := c.Globals.APIClient.ListBackends(context.TODO(), &c.Input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fastly.ToValue(serviceVersion.Number),\n\t\t})\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\tif !c.Globals.Verbose() {\n\t\ttw := text.NewTable(out)\n\t\ttw.AddHeader(\"SERVICE\", \"VERSION\", \"NAME\", \"ADDRESS\", \"PORT\", \"COMMENT\")\n\t\tfor _, backend := range o {\n\t\t\ttw.AddLine(\n\t\t\t\tfastly.ToValue(backend.ServiceID),\n\t\t\t\tfastly.ToValue(backend.ServiceVersion),\n\t\t\t\tfastly.ToValue(backend.Name),\n\t\t\t\tfastly.ToValue(backend.Address),\n\t\t\t\tfastly.ToValue(backend.Port),\n\t\t\t\tfastly.ToValue(backend.Comment),\n\t\t\t)\n\t\t}\n\t\ttw.Print()\n\t\treturn nil\n\t}\n\n\tfmt.Fprintf(out, \"Version: %d\\n\", c.Input.ServiceVersion)\n\tfor i, backend := range o {\n\t\tfmt.Fprintf(out, \"\\tBackend %d/%d\\n\", i+1, len(o))\n\t\ttext.PrintBackend(out, \"\\t\\t\", backend)\n\t}\n\tfmt.Fprintln(out)\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/backend/root.go",
    "content": "package backend\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"backend\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manipulate Fastly service version backends\")\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/service/backend/update.go",
    "content": "package backend\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// UpdateCommand calls the Fastly API to update backends.\ntype UpdateCommand struct {\n\targparser.Base\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n\tautoClone      argparser.OptionalAutoClone\n\n\tname                string\n\tAddress             argparser.OptionalString\n\tAutoLoadbalance     argparser.OptionalBool\n\tBetweenBytesTimeout argparser.OptionalInt\n\tComment             argparser.OptionalString\n\tConnectTimeout      argparser.OptionalInt\n\tFirstByteTimeout    argparser.OptionalInt\n\tHealthCheck         argparser.OptionalString\n\tHostname            argparser.OptionalString\n\tMaxConn             argparser.OptionalInt\n\tMaxUse              argparser.OptionalInt\n\tMaxLifetime         argparser.OptionalInt\n\tMaxTLSVersion       argparser.OptionalString\n\tMinTLSVersion       argparser.OptionalString\n\tNewName             argparser.OptionalString\n\tNoSSLCheckCert      argparser.OptionalBool\n\tOverrideHost        argparser.OptionalString\n\tPort                argparser.OptionalInt\n\tpreferIPv6          argparser.OptionalString\n\tRequestCondition    argparser.OptionalString\n\tSSLCACert           argparser.OptionalString\n\tSSLCertHostname     argparser.OptionalString\n\tSSLCheckCert        argparser.OptionalBool\n\tSSLCiphers          argparser.OptionalString\n\tSSLClientCert       argparser.OptionalString\n\tSSLClientKey        argparser.OptionalString\n\tSSLSNIHostname      argparser.OptionalString\n\tShield              argparser.OptionalString\n\tTCPKaEnable         argparser.OptionalString\n\tTCPKaInterval       argparser.OptionalInt\n\tTCPKaProbes         argparser.OptionalInt\n\tTCPKaTime           argparser.OptionalInt\n\tHTTPKaTime          argparser.OptionalInt\n\tUseSSL              argparser.OptionalBool\n\tWeight              argparser.OptionalInt\n}\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"update\", \"Update a backend on a Fastly service version\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\tc.CmdClause.Flag(\"name\", \"backend name\").Short('n').Required().StringVar(&c.name)\n\n\t// Optional.\n\tc.CmdClause.Flag(\"address\", \"A hostname, IPv4, or IPv6 address for the backend\").Action(c.Address.Set).StringVar(&c.Address.Value)\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.autoClone.Set,\n\t\tDst:    &c.autoClone.Value,\n\t})\n\tc.CmdClause.Flag(\"auto-loadbalance\", \"Whether or not this backend should be automatically load balanced\").Action(c.AutoLoadbalance.Set).BoolVar(&c.AutoLoadbalance.Value)\n\tc.CmdClause.Flag(\"between-bytes-timeout\", \"How long to wait between bytes in milliseconds\").Action(c.BetweenBytesTimeout.Set).IntVar(&c.BetweenBytesTimeout.Value)\n\tc.CmdClause.Flag(\"comment\", \"A descriptive note\").Action(c.Comment.Set).StringVar(&c.Comment.Value)\n\tc.CmdClause.Flag(\"connect-timeout\", \"How long to wait for a timeout in milliseconds\").Action(c.ConnectTimeout.Set).IntVar(&c.ConnectTimeout.Value)\n\tc.CmdClause.Flag(\"first-byte-timeout\", \"How long to wait for the first bytes in milliseconds\").Action(c.FirstByteTimeout.Set).IntVar(&c.FirstByteTimeout.Value)\n\tc.CmdClause.Flag(\"healthcheck\", \"The name of the healthcheck to use with this backend\").Action(c.HealthCheck.Set).StringVar(&c.HealthCheck.Value)\n\tc.CmdClause.Flag(\"max-conn\", \"Maximum number of connections\").Action(c.MaxConn.Set).IntVar(&c.MaxConn.Value)\n\tc.CmdClause.Flag(\"max-use\", \"Maximum number of requests allowed over a single, pooled HTTP keepalive connection to this backend; 0 is treated as unlimited.\").Action(c.MaxUse.Set).IntVar(&c.MaxUse.Value)\n\tc.CmdClause.Flag(\"max-lifetime\", \"Maximum time from creation (in milliseconds) that a pooled HTTP keepalive connection will be eligible for reuse; 0 is treated as unlimited.\").Action(c.MaxLifetime.Set).IntVar(&c.MaxLifetime.Value)\n\tc.CmdClause.Flag(\"max-tls-version\", \"Maximum allowed TLS version on SSL connections to this backend\").Action(c.MaxTLSVersion.Set).StringVar(&c.MaxTLSVersion.Value)\n\tc.CmdClause.Flag(\"min-tls-version\", \"Minimum allowed TLS version on SSL connections to this backend\").Action(c.MinTLSVersion.Set).StringVar(&c.MinTLSVersion.Value)\n\tc.CmdClause.Flag(\"new-name\", \"New backend name\").Action(c.NewName.Set).StringVar(&c.NewName.Value)\n\tc.CmdClause.Flag(\"no-ssl-check-cert\", \"Skip checking SSL certs\").Action(c.NoSSLCheckCert.Set).BoolVar(&c.NoSSLCheckCert.Value)\n\tc.CmdClause.Flag(\"override-host\", \"The hostname to override the Host header\").Action(c.OverrideHost.Set).StringVar(&c.OverrideHost.Value)\n\tc.CmdClause.Flag(\"port\", \"Port number of the address\").Action(c.Port.Set).IntVar(&c.Port.Value)\n\tc.CmdClause.Flag(\"prefer-ipv6\", \"Prefer IPv6 connections\").Action(c.preferIPv6.Set).StringVar(&c.preferIPv6.Value)\n\tc.CmdClause.Flag(\"request-condition\", \"condition, which if met, will select this backend during a request\").Action(c.RequestCondition.Set).StringVar(&c.RequestCondition.Value)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\tc.CmdClause.Flag(\"shield\", \"The shield POP designated to reduce inbound load on this origin by serving the cached data to the rest of the network\").Action(c.Shield.Set).StringVar(&c.Shield.Value)\n\tc.CmdClause.Flag(\"ssl-ca-cert\", \"CA certificate attached to origin\").Action(c.SSLCACert.Set).StringVar(&c.SSLCACert.Value)\n\tc.CmdClause.Flag(\"ssl-cert-hostname\", \"Overrides ssl_hostname, but only for cert verification. Does not affect SNI at all.\").Action(c.SSLCertHostname.Set).StringVar(&c.SSLCertHostname.Value)\n\tc.CmdClause.Flag(\"ssl-check-cert\", \"Be strict on checking SSL certs\").Action(c.SSLCheckCert.Set).BoolVar(&c.SSLCheckCert.Value)\n\tc.CmdClause.Flag(\"ssl-ciphers\", \"List of OpenSSL ciphers (https://www.openssl.org/docs/man1.0.2/man1/ciphers)\").Action(c.SSLCiphers.Set).StringVar(&c.SSLCiphers.Value)\n\tc.CmdClause.Flag(\"ssl-client-cert\", \"Client certificate attached to origin\").Action(c.SSLClientCert.Set).StringVar(&c.SSLClientCert.Value)\n\tc.CmdClause.Flag(\"ssl-client-key\", \"Client key attached to origin\").Action(c.SSLClientKey.Set).StringVar(&c.SSLClientKey.Value)\n\tc.CmdClause.Flag(\"ssl-sni-hostname\", \"Overrides ssl_hostname, but only for SNI in the handshake. Does not affect cert validation at all.\").Action(c.SSLSNIHostname.Set).StringVar(&c.SSLSNIHostname.Value)\n\tc.CmdClause.Flag(\"tcp-ka-enabled\", \"Enable TCP keepalive probes [true, false]\").Action(c.TCPKaEnable.Set).StringVar(&c.TCPKaEnable.Value)\n\tc.CmdClause.Flag(\"tcp-ka-interval\", \"Configure how long to wait between sending each TCP keepalive probe.\").Action(c.TCPKaInterval.Set).IntVar(&c.TCPKaInterval.Value)\n\tc.CmdClause.Flag(\"tcp-ka-probes\", \"Configure how many unacknowledged TCP keepalive probes to send before considering the connection dead.\").Action(c.TCPKaProbes.Set).IntVar(&c.TCPKaProbes.Value)\n\tc.CmdClause.Flag(\"tcp-ka-time\", \"Configure how long to wait after the last sent data before sending TCP keepalive probes.\").Action(c.TCPKaTime.Set).IntVar(&c.TCPKaTime.Value)\n\tc.CmdClause.Flag(\"http-ka-time\", \"Configure how long to keep idle HTTP keepalive connections in the connection pool.\").Action(c.HTTPKaTime.Set).IntVar(&c.HTTPKaTime.Value)\n\tc.CmdClause.Flag(\"use-ssl\", \"Whether or not to use SSL to reach the backend\").Action(c.UseSSL.Set).BoolVar(&c.UseSSL.Value)\n\tc.CmdClause.Flag(\"weight\", \"Weight used to load balance this backend against others\").Action(c.Weight.Set).IntVar(&c.Weight.Value)\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *UpdateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(false),\n\t\tLocked:             optional.Of(false),\n\t\tAutoCloneFlag:      c.autoClone,\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fsterr.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tinput := &fastly.UpdateBackendInput{\n\t\tServiceID:      serviceID,\n\t\tServiceVersion: fastly.ToValue(serviceVersion.Number),\n\t\tName:           c.name,\n\t}\n\n\tif c.NewName.WasSet {\n\t\tinput.NewName = &c.NewName.Value\n\t}\n\n\tif c.Comment.WasSet {\n\t\tinput.Comment = &c.Comment.Value\n\t}\n\n\tif c.Address.WasSet {\n\t\tinput.Address = &c.Address.Value\n\t}\n\n\tif c.Port.WasSet {\n\t\tinput.Port = &c.Port.Value\n\t}\n\n\tif c.OverrideHost.WasSet {\n\t\tinput.OverrideHost = &c.OverrideHost.Value\n\t}\n\n\tif c.preferIPv6.WasSet {\n\t\tpreferIPv6, err := argparser.ConvertBoolFromStringFlag(c.preferIPv6.Value, \"prefer-ipv6\")\n\t\tif err != nil {\n\t\t\tc.Globals.ErrLog.Add(err)\n\t\t\treturn err\n\t\t}\n\t\tinput.PreferIPv6 = fastly.ToPointer(fastly.Compatibool(*preferIPv6))\n\t}\n\n\tif c.ConnectTimeout.WasSet {\n\t\tinput.ConnectTimeout = &c.ConnectTimeout.Value\n\t}\n\n\tif c.MaxConn.WasSet {\n\t\tinput.MaxConn = &c.MaxConn.Value\n\t}\n\n\tif c.MaxUse.WasSet {\n\t\tinput.MaxUse = &c.MaxUse.Value\n\t}\n\tif c.MaxLifetime.WasSet {\n\t\tinput.MaxLifetime = &c.MaxLifetime.Value\n\t}\n\n\tif c.FirstByteTimeout.WasSet {\n\t\tinput.FirstByteTimeout = &c.FirstByteTimeout.Value\n\t}\n\n\tif c.BetweenBytesTimeout.WasSet {\n\t\tinput.BetweenBytesTimeout = &c.BetweenBytesTimeout.Value\n\t}\n\n\tif c.AutoLoadbalance.WasSet {\n\t\tinput.AutoLoadbalance = fastly.ToPointer(fastly.Compatibool(c.AutoLoadbalance.Value))\n\t}\n\n\tif c.Weight.WasSet {\n\t\tinput.Weight = &c.Weight.Value\n\t}\n\n\tif c.RequestCondition.WasSet {\n\t\tinput.RequestCondition = &c.RequestCondition.Value\n\t}\n\n\tif c.HealthCheck.WasSet {\n\t\tinput.HealthCheck = &c.HealthCheck.Value\n\t}\n\n\tif c.Shield.WasSet {\n\t\tinput.Shield = &c.Shield.Value\n\t}\n\n\tif c.UseSSL.WasSet {\n\t\tinput.UseSSL = fastly.ToPointer(fastly.Compatibool(c.UseSSL.Value))\n\t}\n\n\tif c.NoSSLCheckCert.WasSet {\n\t\tinput.SSLCheckCert = fastly.ToPointer(fastly.Compatibool(false))\n\t}\n\n\tif c.SSLCheckCert.WasSet {\n\t\ttext.Deprecated(\"The Fastly API defaults `ssl_check_cert` to true. Use `--no-ssl-check-cert` to disable this setting.\\n\\n\")\n\t\tinput.SSLCheckCert = fastly.ToPointer(fastly.Compatibool(c.SSLCheckCert.Value))\n\t}\n\n\tif c.SSLCACert.WasSet {\n\t\tinput.SSLCACert = &c.SSLCACert.Value\n\t}\n\n\tif c.SSLClientCert.WasSet {\n\t\tinput.SSLClientCert = &c.SSLClientCert.Value\n\t}\n\n\tif c.SSLClientKey.WasSet {\n\t\tinput.SSLClientKey = &c.SSLClientKey.Value\n\t}\n\n\tif c.SSLCertHostname.WasSet {\n\t\tinput.SSLCertHostname = &c.SSLCertHostname.Value\n\t}\n\n\tif c.SSLSNIHostname.WasSet {\n\t\tinput.SSLSNIHostname = &c.SSLSNIHostname.Value\n\t}\n\n\tif c.MinTLSVersion.WasSet {\n\t\tinput.MinTLSVersion = &c.MinTLSVersion.Value\n\t}\n\n\tif c.MaxTLSVersion.WasSet {\n\t\tinput.MaxTLSVersion = &c.MaxTLSVersion.Value\n\t}\n\n\tif c.SSLCiphers.WasSet {\n\t\tinput.SSLCiphers = &c.SSLCiphers.Value\n\t}\n\n\tif c.TCPKaEnable.WasSet {\n\t\ttcpKaEnable, err := argparser.ConvertBoolFromStringFlag(c.TCPKaEnable.Value, \"tcp-ka-enabled\")\n\t\tif err != nil {\n\t\t\tc.Globals.ErrLog.Add(err)\n\t\t\treturn err\n\t\t}\n\t\tinput.TCPKeepAliveEnable = tcpKaEnable\n\t}\n\tif c.TCPKaInterval.WasSet {\n\t\tinput.TCPKeepAliveIntvl = &c.TCPKaInterval.Value\n\t}\n\tif c.TCPKaProbes.WasSet {\n\t\tinput.TCPKeepAliveProbes = &c.TCPKaProbes.Value\n\t}\n\tif c.TCPKaTime.WasSet {\n\t\tinput.TCPKeepAliveTime = &c.TCPKaTime.Value\n\t}\n\n\tif c.HTTPKaTime.WasSet {\n\t\tinput.KeepAliveTime = &c.HTTPKaTime.Value\n\t}\n\n\tb, err := c.Globals.APIClient.UpdateBackend(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": serviceVersion.Number,\n\t\t})\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Updated backend %s (service %s version %d)\", fastly.ToValue(b.Name), fastly.ToValue(b.ServiceID), fastly.ToValue(b.ServiceVersion))\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/create.go",
    "content": "package service\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// CreateCommand calls the Fastly API to create services.\ntype CreateCommand struct {\n\targparser.Base\n\n\t// Optional.\n\tcomment argparser.OptionalString\n\tname    argparser.OptionalString\n\tstype   argparser.OptionalString\n}\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"create\", \"Create a Fastly service\").Alias(\"add\")\n\n\t// Optional.\n\tc.CmdClause.Flag(\"comment\", \"Human-readable comment\").Action(c.comment.Set).StringVar(&c.comment.Value)\n\tc.CmdClause.Flag(\"name\", \"Service name\").Short('n').Action(c.name.Set).StringVar(&c.name.Value)\n\tc.CmdClause.Flag(\"type\", `Service type. Can be one of \"wasm\" or \"vcl\", defaults to \"vcl\".`).Default(\"vcl\").Action(c.stype.Set).EnumVar(&c.stype.Value, \"wasm\", \"vcl\")\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *CreateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tinput := fastly.CreateServiceInput{}\n\n\tif c.name.WasSet {\n\t\tinput.Name = &c.name.Value\n\t}\n\tif c.comment.WasSet {\n\t\tinput.Comment = &c.comment.Value\n\t}\n\tif c.stype.WasSet {\n\t\tinput.Type = &c.stype.Value\n\t}\n\ts, err := c.Globals.APIClient.CreateService(context.TODO(), &input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service Name\": input.Name,\n\t\t\t\"Type\":         input.Type,\n\t\t\t\"Comment\":      input.Comment,\n\t\t})\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Created service %s\", fastly.ToValue(s.ServiceID))\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/delete.go",
    "content": "package service\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/manifest\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DeleteCommand calls the Fastly API to delete services.\ntype DeleteCommand struct {\n\targparser.Base\n\tInput       fastly.DeleteServiceInput\n\tforce       bool\n\tserviceName argparser.OptionalServiceNameID\n}\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"delete\", \"Delete a Fastly service\").Alias(\"remove\")\n\n\t// Optional.\n\tc.CmdClause.Flag(\"force\", \"Force deletion of an active service\").Short('f').BoolVar(&c.force)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DeleteCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, source, flag, err := argparser.ServiceID(c.serviceName, *c.Globals.Manifest, c.Globals.APIClient, c.Globals.ErrLog)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif c.Globals.Verbose() {\n\t\targparser.DisplayServiceID(serviceID, flag, source, out)\n\t}\n\n\tc.Input.ServiceID = serviceID\n\n\tif c.force {\n\t\ts, err := c.Globals.APIClient.GetServiceDetails(context.TODO(), &fastly.GetServiceDetailsInput{\n\t\t\tServiceID: serviceID,\n\t\t})\n\t\tif err != nil {\n\t\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\t\"Service ID\": serviceID,\n\t\t\t})\n\t\t\treturn err\n\t\t}\n\n\t\tif s.ActiveVersion != nil && fastly.ToValue(s.ActiveVersion.Number) != 0 {\n\t\t\t_, err := c.Globals.APIClient.DeactivateVersion(context.TODO(), &fastly.DeactivateVersionInput{\n\t\t\t\tServiceID:      serviceID,\n\t\t\t\tServiceVersion: fastly.ToValue(s.ActiveVersion.Number),\n\t\t\t})\n\t\t\tif err != nil {\n\t\t\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\t\t\"Service ID\":      serviceID,\n\t\t\t\t\t\"Service Version\": fastly.ToValue(s.ActiveVersion.Number),\n\t\t\t\t})\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\n\tif err := c.Globals.APIClient.DeleteService(context.TODO(), &c.Input); err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\": serviceID,\n\t\t})\n\t\treturn errors.RemediationError{\n\t\t\tInner:       err,\n\t\t\tRemediation: fmt.Sprintf(\"Try %s\\n\", text.Bold(\"fastly service delete --force\")),\n\t\t}\n\t}\n\n\t// Ensure that VCL service users are unaffected by checking if the Service ID\n\t// was acquired via the fastly.toml manifest.\n\tif source == manifest.SourceFile {\n\t\tif err := c.Globals.Manifest.File.Read(manifest.Filename); err != nil {\n\t\t\tc.Globals.ErrLog.Add(err)\n\t\t\treturn fmt.Errorf(\"error reading fastly.toml: %w\", err)\n\t\t}\n\t\tc.Globals.Manifest.File.ServiceID = \"\"\n\t\tif err := c.Globals.Manifest.File.Write(manifest.Filename); err != nil {\n\t\t\tc.Globals.ErrLog.Add(err)\n\t\t\treturn fmt.Errorf(\"error updating fastly.toml: %w\", err)\n\t\t}\n\t}\n\n\ttext.Success(out, \"Deleted service ID %s\", c.Input.ServiceID)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/describe.go",
    "content": "package service\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/manifest\"\n\t\"github.com/fastly/cli/pkg/text\"\n\t\"github.com/fastly/cli/pkg/time\"\n)\n\n// DescribeCommand calls the Fastly API to describe a service.\ntype DescribeCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tInput       fastly.GetServiceDetailsInput\n\tserviceName argparser.OptionalServiceNameID\n}\n\n// NewDescribeCommand returns a usable command registered under the parent.\nfunc NewDescribeCommand(parent argparser.Registerer, g *global.Data) *DescribeCommand {\n\tc := DescribeCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"describe\", \"Show detailed information about a Fastly service\").Alias(\"get\")\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DescribeCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tserviceID, source, flag, err := argparser.ServiceID(c.serviceName, *c.Globals.Manifest, c.Globals.APIClient, c.Globals.ErrLog)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif c.Globals.Verbose() {\n\t\targparser.DisplayServiceID(serviceID, flag, source, out)\n\t}\n\n\tif source == manifest.SourceUndefined && !c.serviceName.WasSet {\n\t\terr := fsterr.ErrNoServiceID\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tc.Input.ServiceID = serviceID\n\n\to, err := c.Globals.APIClient.GetServiceDetails(context.TODO(), &c.Input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\": serviceID,\n\t\t})\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\treturn c.print(o, out)\n}\n\nfunc (c *DescribeCommand) print(s *fastly.ServiceDetail, out io.Writer) error {\n\tfmt.Fprintf(out, \"ID: %s\\n\", fastly.ToValue(s.ServiceID))\n\tfmt.Fprintf(out, \"Name: %s\\n\", fastly.ToValue(s.Name))\n\tfmt.Fprintf(out, \"Type: %s\\n\", fastly.ToValue(s.Type))\n\tfmt.Fprintf(out, \"Comment: %s\\n\", fastly.ToValue(s.Comment))\n\tfmt.Fprintf(out, \"Customer ID: %s\\n\", fastly.ToValue(s.CustomerID))\n\tif s.CreatedAt != nil {\n\t\tfmt.Fprintf(out, \"Created (UTC): %s\\n\", s.CreatedAt.UTC().Format(time.Format))\n\t}\n\tif s.UpdatedAt != nil {\n\t\tfmt.Fprintf(out, \"Last edited (UTC): %s\\n\", s.UpdatedAt.UTC().Format(time.Format))\n\t}\n\tif s.DeletedAt != nil {\n\t\tfmt.Fprintf(out, \"Deleted (UTC): %s\\n\", s.DeletedAt.UTC().Format(time.Format))\n\t}\n\tif s.ActiveVersion != nil {\n\t\tfmt.Fprintf(out, \"Active version:\\n\")\n\t\ttext.PrintVersion(out, \"\\t\", s.ActiveVersion)\n\t}\n\tfmt.Fprintf(out, \"Versions: %d\\n\", len(s.Versions))\n\tfor j, version := range s.Versions {\n\t\tfmt.Fprintf(out, \"\\tVersion %d/%d\\n\", j+1, len(s.Versions))\n\t\ttext.PrintVersion(out, \"\\t\\t\", version)\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/dictionary/create.go",
    "content": "package dictionary\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// CreateCommand calls the Fastly API to create a service.\ntype CreateCommand struct {\n\targparser.Base\n\n\t// Required.\n\tserviceVersion argparser.OptionalServiceVersion\n\n\t// Optional.\n\tautoClone   argparser.OptionalAutoClone\n\tname        argparser.OptionalString\n\tserviceName argparser.OptionalServiceNameID\n\twriteOnly   argparser.OptionalBool\n}\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"create\", \"Create a Fastly edge dictionary on a Fastly service version\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.autoClone.Set,\n\t\tDst:    &c.autoClone.Value,\n\t})\n\tc.CmdClause.Flag(\"name\", \"Name of Dictionary\").Short('n').Action(c.name.Set).StringVar(&c.name.Value)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\tc.CmdClause.Flag(\"write-only\", \"Whether to mark this dictionary as write-only\").Action(c.writeOnly.Set).BoolVar(&c.writeOnly.Value)\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *CreateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(false),\n\t\tLocked:             optional.Of(false),\n\t\tAutoCloneFlag:      c.autoClone,\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": errors.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tserviceVersionNumber := fastly.ToValue(serviceVersion.Number)\n\n\tinput := fastly.CreateDictionaryInput{\n\t\tServiceID:      serviceID,\n\t\tServiceVersion: serviceVersionNumber,\n\t}\n\tif c.name.WasSet {\n\t\tinput.Name = &c.name.Value\n\t}\n\tif c.writeOnly.WasSet {\n\t\tinput.WriteOnly = fastly.ToPointer(fastly.Compatibool(c.writeOnly.Value))\n\t}\n\n\td, err := c.Globals.APIClient.CreateDictionary(context.TODO(), &input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": serviceVersionNumber,\n\t\t})\n\t\treturn err\n\t}\n\n\tvar writeOnlyOutput string\n\tif fastly.ToValue(d.WriteOnly) {\n\t\twriteOnlyOutput = \"as write-only \"\n\t}\n\n\ttext.Success(out, \"Created dictionary %s %s(id %s, service %s, version %d)\", fastly.ToValue(d.Name), writeOnlyOutput, fastly.ToValue(d.DictionaryID), fastly.ToValue(d.ServiceID), fastly.ToValue(d.ServiceVersion))\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/dictionary/delete.go",
    "content": "package dictionary\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DeleteCommand calls the Fastly API to delete a service.\ntype DeleteCommand struct {\n\targparser.Base\n\tInput          fastly.DeleteDictionaryInput\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n\tautoClone      argparser.OptionalAutoClone\n}\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"delete\", \"Delete a Fastly edge dictionary from a Fastly service version\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"Name of Dictionary\").Short('n').Required().StringVar(&c.Input.Name)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.autoClone.Set,\n\t\tDst:    &c.autoClone.Value,\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DeleteCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(false),\n\t\tLocked:             optional.Of(false),\n\t\tAutoCloneFlag:      c.autoClone,\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": errors.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tc.Input.ServiceID = serviceID\n\tc.Input.ServiceVersion = fastly.ToValue(serviceVersion.Number)\n\n\terr = c.Globals.APIClient.DeleteDictionary(context.TODO(), &c.Input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fastly.ToValue(serviceVersion.Number),\n\t\t})\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Deleted dictionary %s (service %s version %d)\", c.Input.Name, c.Input.ServiceID, c.Input.ServiceVersion)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/dictionary/describe.go",
    "content": "package dictionary\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DescribeCommand calls the Fastly API to describe a dictionary.\ntype DescribeCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tInput          fastly.GetDictionaryInput\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n}\n\n// NewDescribeCommand returns a usable command registered under the parent.\nfunc NewDescribeCommand(parent argparser.Registerer, g *global.Data) *DescribeCommand {\n\tc := DescribeCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"describe\", \"Show detailed information about a Fastly edge dictionary\").Alias(\"get\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"Name of Dictionary\").Short('n').Required().StringVar(&c.Input.Name)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DescribeCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fsterr.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tserviceVersionNumber := fastly.ToValue(serviceVersion.Number)\n\n\tc.Input.ServiceID = serviceID\n\tc.Input.ServiceVersion = serviceVersionNumber\n\n\tdictionary, err := c.Globals.APIClient.GetDictionary(context.TODO(), &c.Input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": serviceVersionNumber,\n\t\t})\n\t\treturn err\n\t}\n\tdictionaryID := fastly.ToValue(dictionary.DictionaryID)\n\n\tvar (\n\t\tinfo  *fastly.DictionaryInfo\n\t\titems []*fastly.DictionaryItem\n\t)\n\n\tif c.Globals.Verbose() || c.JSONOutput.Enabled {\n\t\tinfoInput := fastly.GetDictionaryInfoInput{\n\t\t\tServiceID:      c.Input.ServiceID,\n\t\t\tServiceVersion: c.Input.ServiceVersion,\n\t\t\tDictionaryID:   dictionaryID,\n\t\t}\n\t\tinfo, err = c.Globals.APIClient.GetDictionaryInfo(context.TODO(), &infoInput)\n\t\tif err != nil {\n\t\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\t\"Service ID\":      serviceID,\n\t\t\t\t\"Service Version\": serviceVersionNumber,\n\t\t\t})\n\t\t\treturn err\n\t\t}\n\t\titemInput := fastly.ListDictionaryItemsInput{\n\t\t\tServiceID:    c.Input.ServiceID,\n\t\t\tDictionaryID: dictionaryID,\n\t\t}\n\t\titems, err = c.Globals.APIClient.ListDictionaryItems(context.TODO(), &itemInput)\n\t\tif err != nil {\n\t\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\t\"Service ID\":      serviceID,\n\t\t\t\t\"Service Version\": serviceVersionNumber,\n\t\t\t})\n\t\t\treturn err\n\t\t}\n\t}\n\n\tif c.JSONOutput.Enabled {\n\t\t// NOTE: When not using JSON you have to provide the --verbose flag to get\n\t\t// some extra information about the dictionary. When using --json we go\n\t\t// ahead and acquire that info and combine it into the JSON output.\n\t\ttype container struct {\n\t\t\t*fastly.Dictionary\n\t\t\t*fastly.DictionaryInfo\n\t\t\tItems []*fastly.DictionaryItem\n\t\t}\n\n\t\to := &container{Dictionary: dictionary, DictionaryInfo: info, Items: items}\n\n\t\tif ok, err := c.WriteJSON(out, o); ok {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tif !c.Globals.Verbose() {\n\t\ttext.Output(out, \"Service ID: %s\", fastly.ToValue(dictionary.ServiceID))\n\t}\n\ttext.Output(out, \"Version: %d\", fastly.ToValue(dictionary.ServiceVersion))\n\ttext.PrintDictionary(out, \"\", dictionary)\n\n\tif c.Globals.Verbose() {\n\t\ttext.Output(out, \"Digest: %s\", fastly.ToValue(info.Digest))\n\t\ttext.Output(out, \"Item Count: %d\", fastly.ToValue(info.ItemCount))\n\n\t\tfor i, item := range items {\n\t\t\ttext.Output(out, \"Item %d/%d:\", i+1, len(items))\n\t\t\ttext.PrintDictionaryItemKV(out, \"\t\", item)\n\t\t}\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/dictionary/dictionary_test.go",
    "content": "package dictionary_test\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\troot \"github.com/fastly/cli/pkg/commands/service\"\n\tsub \"github.com/fastly/cli/pkg/commands/service/dictionary\"\n\t\"github.com/fastly/cli/pkg/mock\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n)\n\nfunc TestDictionaryDescribe(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"--version 1 --service-id 123\",\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--version 1 --service-id 123 --name dict-1\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:    testutil.GetVersion,\n\t\t\t\tGetDictionaryFn: describeDictionaryOK,\n\t\t\t},\n\t\t\tWantOutput: describeDictionaryOutput,\n\t\t},\n\t\t{\n\t\t\tArgs: \"--version 1 --service-id 123 --name dict-1\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:    testutil.GetVersion,\n\t\t\t\tGetDictionaryFn: describeDictionaryOKDeleted,\n\t\t\t},\n\t\t\tWantOutput: describeDictionaryOutputDeleted,\n\t\t},\n\t\t{\n\t\t\tArgs: \"--version 1 --service-id 123 --name dict-1 --verbose\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:          testutil.GetVersion,\n\t\t\t\tGetDictionaryFn:       describeDictionaryOK,\n\t\t\t\tGetDictionaryInfoFn:   getDictionaryInfoOK,\n\t\t\t\tListDictionaryItemsFn: listDictionaryItemsOK,\n\t\t\t},\n\t\t\tWantOutput: describeDictionaryOutputVerbose,\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"describe\"}, scenarios)\n}\n\nfunc TestDictionaryCreate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"--version 1\",\n\t\t\tEnvVars:   map[string]string{\"FASTLY_SERVICE_ID\": \"\"},\n\t\t\tWantError: \"error reading service: no service ID found\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--version 1 --service-id 123 --name denylist --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:       testutil.GetVersion,\n\t\t\t\tCloneVersionFn:     testutil.CloneVersionResult(4),\n\t\t\t\tCreateDictionaryFn: createDictionaryOK,\n\t\t\t},\n\t\t\tWantOutput: createDictionaryOutput,\n\t\t},\n\t\t{\n\t\t\tArgs: \"--version 1 --service-id 123 --name denylist --write-only --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:       testutil.GetVersion,\n\t\t\t\tCloneVersionFn:     testutil.CloneVersionResult(4),\n\t\t\t\tCreateDictionaryFn: createDictionaryOK,\n\t\t\t},\n\t\t\tWantOutput: createDictionaryOutputWriteOnly,\n\t\t},\n\t\t{\n\t\t\tArgs: \"--version 1 --service-id 123 --name denylist --write-only fish --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t},\n\t\t\tWantError: \"error parsing arguments: unexpected 'fish'\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--version 1 --service-id 123 --name denylist --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:       testutil.GetVersion,\n\t\t\t\tCloneVersionFn:     testutil.CloneVersionResult(4),\n\t\t\t\tCreateDictionaryFn: createDictionaryDuplicate,\n\t\t\t},\n\t\t\tWantError: \"Duplicate record\",\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"create\"}, scenarios)\n}\n\nfunc TestDeleteDictionary(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"--service-id 123 --version 1\",\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name allowlist --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:       testutil.GetVersion,\n\t\t\t\tCloneVersionFn:     testutil.CloneVersionResult(4),\n\t\t\t\tDeleteDictionaryFn: deleteDictionaryOK,\n\t\t\t},\n\t\t\tWantOutput: deleteDictionaryOutput,\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name allowlist --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:       testutil.GetVersion,\n\t\t\t\tCloneVersionFn:     testutil.CloneVersionResult(4),\n\t\t\t\tDeleteDictionaryFn: deleteDictionaryError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"delete\"}, scenarios)\n}\n\nfunc TestListDictionary(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs: \"--version 1\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:       testutil.GetVersion,\n\t\t\t\tListDictionariesFn: listDictionariesOk,\n\t\t\t},\n\t\t\tEnvVars:   map[string]string{\"FASTLY_SERVICE_ID\": \"\"},\n\t\t\tWantError: \"error reading service: no service ID found\",\n\t\t},\n\t\t{\n\t\t\tArgs:      \"--service-id 123\",\n\t\t\tWantError: \"error parsing arguments: required flag --version not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--version 1 --service-id 123\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:       testutil.GetVersion,\n\t\t\t\tListDictionariesFn: listDictionariesOk,\n\t\t\t},\n\t\t\tWantOutput: listDictionariesOutput,\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"list\"}, scenarios)\n}\n\nfunc TestUpdateDictionary(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"--version 1 --name oldname --new-name newname\",\n\t\t\tEnvVars:   map[string]string{\"FASTLY_SERVICE_ID\": \"\"},\n\t\t\tWantError: \"error reading service: no service ID found\",\n\t\t},\n\t\t{\n\t\t\tArgs:      \"--service-id 123 --name oldname --new-name newname\",\n\t\t\tWantError: \"error parsing arguments: required flag --version not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs:      \"--service-id 123 --version 1 --new-name newname\",\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name oldname --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t},\n\t\t\tWantError: \"error parsing arguments: required flag --new-name or --write-only not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name oldname --new-name dict-1 --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:       testutil.GetVersion,\n\t\t\t\tCloneVersionFn:     testutil.CloneVersionResult(4),\n\t\t\t\tUpdateDictionaryFn: updateDictionaryNameOK,\n\t\t\t},\n\t\t\tWantOutput: updateDictionaryNameOutput,\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name oldname --new-name dict-1 --write-only true --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:       testutil.GetVersion,\n\t\t\t\tCloneVersionFn:     testutil.CloneVersionResult(4),\n\t\t\t\tUpdateDictionaryFn: updateDictionaryNameOK,\n\t\t\t},\n\t\t\tWantOutput: updateDictionaryNameOutput,\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name oldname --write-only true --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:       testutil.GetVersion,\n\t\t\t\tCloneVersionFn:     testutil.CloneVersionResult(4),\n\t\t\t\tUpdateDictionaryFn: updateDictionaryWriteOnlyOK,\n\t\t\t},\n\t\t\tWantOutput: updateDictionaryOutput,\n\t\t},\n\t\t{\n\t\t\tArgs: \"-v --service-id 123 --version 1 --name oldname --new-name dict-1 --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:       testutil.GetVersion,\n\t\t\t\tCloneVersionFn:     testutil.CloneVersionResult(4),\n\t\t\t\tUpdateDictionaryFn: updateDictionaryNameOK,\n\t\t\t},\n\t\t\tWantOutput: updateDictionaryOutputVerbose,\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name oldname --new-name dict-1 --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:       testutil.GetVersion,\n\t\t\t\tCloneVersionFn:     testutil.CloneVersionResult(4),\n\t\t\t\tUpdateDictionaryFn: updateDictionaryError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"update\"}, scenarios)\n}\n\nfunc describeDictionaryOK(_ context.Context, i *fastly.GetDictionaryInput) (*fastly.Dictionary, error) {\n\treturn &fastly.Dictionary{\n\t\tServiceID:      fastly.ToPointer(i.ServiceID),\n\t\tServiceVersion: fastly.ToPointer(i.ServiceVersion),\n\t\tName:           fastly.ToPointer(i.Name),\n\t\tCreatedAt:      testutil.MustParseTimeRFC3339(\"2001-02-03T04:05:06Z\"),\n\t\tWriteOnly:      fastly.ToPointer(false),\n\t\tDictionaryID:   fastly.ToPointer(\"456\"),\n\t\tUpdatedAt:      testutil.MustParseTimeRFC3339(\"2001-02-03T04:05:07Z\"),\n\t}, nil\n}\n\nfunc describeDictionaryOKDeleted(_ context.Context, i *fastly.GetDictionaryInput) (*fastly.Dictionary, error) {\n\treturn &fastly.Dictionary{\n\t\tServiceID:      fastly.ToPointer(i.ServiceID),\n\t\tServiceVersion: fastly.ToPointer(i.ServiceVersion),\n\t\tName:           fastly.ToPointer(i.Name),\n\t\tCreatedAt:      testutil.MustParseTimeRFC3339(\"2001-02-03T04:05:06Z\"),\n\t\tWriteOnly:      fastly.ToPointer(false),\n\t\tDictionaryID:   fastly.ToPointer(\"456\"),\n\t\tUpdatedAt:      testutil.MustParseTimeRFC3339(\"2001-02-03T04:05:07Z\"),\n\t\tDeletedAt:      testutil.MustParseTimeRFC3339(\"2001-02-03T04:05:08Z\"),\n\t}, nil\n}\n\nfunc createDictionaryOK(_ context.Context, i *fastly.CreateDictionaryInput) (*fastly.Dictionary, error) {\n\tif i.WriteOnly == nil {\n\t\ti.WriteOnly = fastly.ToPointer(fastly.Compatibool(false))\n\t}\n\treturn &fastly.Dictionary{\n\t\tServiceID:      fastly.ToPointer(i.ServiceID),\n\t\tServiceVersion: fastly.ToPointer(i.ServiceVersion),\n\t\tName:           i.Name,\n\t\tCreatedAt:      testutil.MustParseTimeRFC3339(\"2001-02-03T04:05:06Z\"),\n\t\tWriteOnly:      fastly.ToPointer(bool(fastly.ToValue(i.WriteOnly))),\n\t\tDictionaryID:   fastly.ToPointer(\"456\"),\n\t\tUpdatedAt:      testutil.MustParseTimeRFC3339(\"2001-02-03T04:05:07Z\"),\n\t}, nil\n}\n\n// getDictionaryInfoOK mocks the response from fastly.GetDictionaryInfo, which\n// is not otherwise used in the fastly-cli and will need to be updated here if\n// that call changes. This function requires i.ID to equal \"456\" to enforce the\n// input to this call matches the response to GetDictionaryInfo in\n// describeDictionaryOK.\nfunc getDictionaryInfoOK(_ context.Context, i *fastly.GetDictionaryInfoInput) (*fastly.DictionaryInfo, error) {\n\tif i.DictionaryID == \"456\" {\n\t\treturn &fastly.DictionaryInfo{\n\t\t\tItemCount:   fastly.ToPointer(2),\n\t\t\tLastUpdated: testutil.MustParseTimeRFC3339(\"2001-02-03T04:05:07Z\"),\n\t\t\tDigest:      fastly.ToPointer(\"digest_hash\"),\n\t\t}, nil\n\t}\n\treturn nil, errFail\n}\n\n// listDictionaryItemsOK mocks the response from fastly.ListDictionaryItems\n// which is primarily used in the fastly-cli.dictionaryitem package and will\n// need to be updated here if that call changes.\nfunc listDictionaryItemsOK(_ context.Context, i *fastly.ListDictionaryItemsInput) ([]*fastly.DictionaryItem, error) {\n\treturn []*fastly.DictionaryItem{\n\t\t{\n\t\t\tServiceID:    fastly.ToPointer(i.ServiceID),\n\t\t\tDictionaryID: fastly.ToPointer(i.DictionaryID),\n\t\t\tItemKey:      fastly.ToPointer(\"foo\"),\n\t\t\tItemValue:    fastly.ToPointer(\"bar\"),\n\t\t\tCreatedAt:    testutil.MustParseTimeRFC3339(\"2001-02-03T04:05:06Z\"),\n\t\t\tUpdatedAt:    testutil.MustParseTimeRFC3339(\"2001-02-03T04:05:07Z\"),\n\t\t},\n\t\t{\n\t\t\tServiceID:    fastly.ToPointer(i.ServiceID),\n\t\t\tDictionaryID: fastly.ToPointer(i.DictionaryID),\n\t\t\tItemKey:      fastly.ToPointer(\"baz\"),\n\t\t\tItemValue:    fastly.ToPointer(\"bear\"),\n\t\t\tCreatedAt:    testutil.MustParseTimeRFC3339(\"2001-02-03T04:05:06Z\"),\n\t\t\tUpdatedAt:    testutil.MustParseTimeRFC3339(\"2001-02-03T04:05:07Z\"),\n\t\t\tDeletedAt:    testutil.MustParseTimeRFC3339(\"2001-02-03T04:06:08Z\"),\n\t\t},\n\t}, nil\n}\n\nfunc createDictionaryDuplicate(_ context.Context, _ *fastly.CreateDictionaryInput) (*fastly.Dictionary, error) {\n\treturn nil, errors.New(\"Duplicate record\")\n}\n\nfunc deleteDictionaryOK(_ context.Context, _ *fastly.DeleteDictionaryInput) error {\n\treturn nil\n}\n\nfunc deleteDictionaryError(_ context.Context, _ *fastly.DeleteDictionaryInput) error {\n\treturn errTest\n}\n\nfunc listDictionariesOk(_ context.Context, i *fastly.ListDictionariesInput) ([]*fastly.Dictionary, error) {\n\treturn []*fastly.Dictionary{\n\t\t{\n\t\t\tServiceID:      fastly.ToPointer(i.ServiceID),\n\t\t\tServiceVersion: fastly.ToPointer(i.ServiceVersion),\n\t\t\tName:           fastly.ToPointer(\"dict-1\"),\n\t\t\tCreatedAt:      testutil.MustParseTimeRFC3339(\"2001-02-03T04:05:06Z\"),\n\t\t\tWriteOnly:      fastly.ToPointer(false),\n\t\t\tDictionaryID:   fastly.ToPointer(\"456\"),\n\t\t\tUpdatedAt:      testutil.MustParseTimeRFC3339(\"2001-02-03T04:05:07Z\"),\n\t\t},\n\t\t{\n\t\t\tServiceID:      fastly.ToPointer(i.ServiceID),\n\t\t\tServiceVersion: fastly.ToPointer(i.ServiceVersion),\n\t\t\tName:           fastly.ToPointer(\"dict-2\"),\n\t\t\tCreatedAt:      testutil.MustParseTimeRFC3339(\"2001-02-03T04:05:06Z\"),\n\t\t\tWriteOnly:      fastly.ToPointer(false),\n\t\t\tDictionaryID:   fastly.ToPointer(\"456\"),\n\t\t\tUpdatedAt:      testutil.MustParseTimeRFC3339(\"2001-02-03T04:05:07Z\"),\n\t\t},\n\t}, nil\n}\n\nfunc updateDictionaryNameOK(_ context.Context, i *fastly.UpdateDictionaryInput) (*fastly.Dictionary, error) {\n\treturn &fastly.Dictionary{\n\t\tServiceID:      fastly.ToPointer(i.ServiceID),\n\t\tServiceVersion: fastly.ToPointer(i.ServiceVersion),\n\t\tName:           i.NewName,\n\t\tCreatedAt:      testutil.MustParseTimeRFC3339(\"2001-02-03T04:05:06Z\"),\n\t\tWriteOnly:      fastly.ToPointer(bool(fastly.ToValue(i.WriteOnly))),\n\t\tDictionaryID:   fastly.ToPointer(\"456\"),\n\t\tUpdatedAt:      testutil.MustParseTimeRFC3339(\"2001-02-03T04:05:07Z\"),\n\t}, nil\n}\n\nfunc updateDictionaryWriteOnlyOK(_ context.Context, i *fastly.UpdateDictionaryInput) (*fastly.Dictionary, error) {\n\treturn &fastly.Dictionary{\n\t\tServiceID:      fastly.ToPointer(i.ServiceID),\n\t\tServiceVersion: fastly.ToPointer(i.ServiceVersion),\n\t\tName:           fastly.ToPointer(i.Name),\n\t\tCreatedAt:      testutil.MustParseTimeRFC3339(\"2001-02-03T04:05:06Z\"),\n\t\tWriteOnly:      fastly.ToPointer(bool(fastly.ToValue(i.WriteOnly))),\n\t\tDictionaryID:   fastly.ToPointer(\"456\"),\n\t\tUpdatedAt:      testutil.MustParseTimeRFC3339(\"2001-02-03T04:05:07Z\"),\n\t}, nil\n}\n\nfunc updateDictionaryError(_ context.Context, _ *fastly.UpdateDictionaryInput) (*fastly.Dictionary, error) {\n\treturn nil, errTest\n}\n\nvar (\n\terrTest = errors.New(\"an expected error occurred\")\n\terrFail = errors.New(\"this error should not be returned and indicates a failure in the code\")\n)\n\nvar (\n\tcreateDictionaryOutput          = \"SUCCESS: Created dictionary denylist (id 456, service 123, version 4)\\n\"\n\tcreateDictionaryOutputWriteOnly = \"SUCCESS: Created dictionary denylist as write-only (id 456, service 123, version 4)\\n\"\n\tdeleteDictionaryOutput          = \"SUCCESS: Deleted dictionary allowlist (service 123 version 4)\\n\"\n\tupdateDictionaryOutput          = \"SUCCESS: Updated dictionary oldname (service 123 version 4)\\n\"\n\tupdateDictionaryNameOutput      = \"SUCCESS: Updated dictionary dict-1 (service 123 version 4)\\n\"\n)\n\nvar updateDictionaryOutputVerbose = strings.Join(\n\t[]string{\n\t\t\"Fastly API endpoint: https://api.fastly.com\",\n\t\t\"Fastly API token provided via config file (auth: user)\",\n\t\t\"\",\n\t\t\"Service ID (via --service-id): 123\",\n\t\t\"\",\n\t\t\"INFO: Service version 1 is not editable, so it was automatically cloned because --autoclone is enabled. Now operating on\",\n\t\t\"version 4.\",\n\t\t\"\",\n\t\tstrings.TrimSpace(updateDictionaryNameOutput),\n\t\t\"\",\n\t\tupdateDictionaryOutputVersionCloned,\n\t},\n\t\"\\n\")\n\nvar updateDictionaryOutputVersionCloned = strings.TrimSpace(`\nVersion: 4\nID: 456\nName: dict-1\nWrite Only: false\nCreated (UTC): 2001-02-03 04:05\nLast edited (UTC): 2001-02-03 04:05\n`) + \"\\n\"\n\nvar describeDictionaryOutput = strings.TrimSpace(`\nService ID: 123\nVersion: 1\nID: 456\nName: dict-1\nWrite Only: false\nCreated (UTC): 2001-02-03 04:05\nLast edited (UTC): 2001-02-03 04:05\n`) + \"\\n\"\n\nvar describeDictionaryOutputDeleted = strings.TrimSpace(`\nService ID: 123\nVersion: 1\nID: 456\nName: dict-1\nWrite Only: false\nCreated (UTC): 2001-02-03 04:05\nLast edited (UTC): 2001-02-03 04:05\nDeleted (UTC): 2001-02-03 04:05\n`) + \"\\n\"\n\nvar describeDictionaryOutputVerbose = strings.TrimSpace(`\nFastly API endpoint: https://api.fastly.com\nFastly API token provided via config file (auth: user)\n\nService ID (via --service-id): 123\n\nVersion: 1\nID: 456\nName: dict-1\nWrite Only: false\nCreated (UTC): 2001-02-03 04:05\nLast edited (UTC): 2001-02-03 04:05\nDigest: digest_hash\nItem Count: 2\nItem 1/2:\n\tItem Key: foo\n\tItem Value: bar\nItem 2/2:\n\tItem Key: baz\n\tItem Value: bear\n`) + \"\\n\"\n\nvar listDictionariesOutput = \"\\n\" + strings.TrimSpace(`\nService ID: 123\nVersion: 1\nID: 456\nName: dict-1\nWrite Only: false\nCreated (UTC): 2001-02-03 04:05\nLast edited (UTC): 2001-02-03 04:05\nID: 456\nName: dict-2\nWrite Only: false\nCreated (UTC): 2001-02-03 04:05\nLast edited (UTC): 2001-02-03 04:05\n`) + \"\\n\"\n"
  },
  {
    "path": "pkg/commands/service/dictionary/doc.go",
    "content": "// Package dictionary contains commands to inspect and manipulate Fastly edge\n// dictionaries.\npackage dictionary\n"
  },
  {
    "path": "pkg/commands/service/dictionary/list.go",
    "content": "package dictionary\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// ListCommand calls the Fastly API to list dictionaries.\ntype ListCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tInput          fastly.ListDictionariesInput\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n}\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"list\", \"List all dictionaries on a Fastly service version\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *ListCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fsterr.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\tserviceVersionNumber := fastly.ToValue(serviceVersion.Number)\n\n\tc.Input.ServiceID = serviceID\n\tc.Input.ServiceVersion = serviceVersionNumber\n\n\to, err := c.Globals.APIClient.ListDictionaries(context.TODO(), &c.Input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": serviceVersionNumber,\n\t\t})\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\tif !c.Globals.Verbose() {\n\t\tfmt.Fprintf(out, \"\\nService ID: %s\\n\", serviceID)\n\t}\n\ttext.Output(out, \"Version: %d\", c.Input.ServiceVersion)\n\tfor _, dictionary := range o {\n\t\ttext.PrintDictionary(out, \"\", dictionary)\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/dictionary/root.go",
    "content": "package dictionary\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"dictionary\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manipulate Fastly edge dictionaries\")\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/service/dictionary/update.go",
    "content": "package dictionary\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"strconv\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// UpdateCommand calls the Fastly API to update a dictionary.\ntype UpdateCommand struct {\n\targparser.Base\n\n\t// TODO: make input consistent across commands (most are title case)\n\tinput          fastly.UpdateDictionaryInput\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n\tautoClone      argparser.OptionalAutoClone\n\n\tnewname   argparser.OptionalString\n\twriteOnly argparser.OptionalString\n}\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"update\", \"Update name of dictionary on a Fastly service version\").Alias(\"get\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"Old name of Dictionary\").Short('n').Required().StringVar(&c.input.Name)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.autoClone.Set,\n\t\tDst:    &c.autoClone.Value,\n\t})\n\tc.CmdClause.Flag(\"new-name\", \"New name of Dictionary\").Action(c.newname.Set).StringVar(&c.newname.Value)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\tc.CmdClause.Flag(\"write-only\", \"Whether to mark this dictionary as write-only. Can be true or false (defaults to false)\").Action(c.writeOnly.Set).StringVar(&c.writeOnly.Value)\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *UpdateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(false),\n\t\tLocked:             optional.Of(false),\n\t\tAutoCloneFlag:      c.autoClone,\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fsterr.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tserviceVersionNumber := fastly.ToValue(serviceVersion.Number)\n\n\tc.input.ServiceID = serviceID\n\tc.input.ServiceVersion = serviceVersionNumber\n\n\tif !c.newname.WasSet && !c.writeOnly.WasSet {\n\t\treturn fsterr.RemediationError{Inner: fmt.Errorf(\"error parsing arguments: required flag --new-name or --write-only not provided\"), Remediation: \"To fix this error, provide at least one of the aforementioned flags\"}\n\t}\n\tif c.newname.WasSet {\n\t\tc.input.NewName = &c.newname.Value\n\t}\n\n\tif c.writeOnly.WasSet {\n\t\twriteOnly, err := strconv.ParseBool(c.writeOnly.Value)\n\t\tif err != nil {\n\t\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\t\"Service ID\":      serviceID,\n\t\t\t\t\"Service Version\": serviceVersionNumber,\n\t\t\t})\n\t\t\treturn err\n\t\t}\n\t\tc.input.WriteOnly = fastly.ToPointer(fastly.Compatibool(writeOnly))\n\t}\n\n\td, err := c.Globals.APIClient.UpdateDictionary(context.TODO(), &c.input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": serviceVersionNumber,\n\t\t})\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Updated dictionary %s (service %s version %d)\", fastly.ToValue(d.Name), fastly.ToValue(d.ServiceID), fastly.ToValue(d.ServiceVersion))\n\tif c.Globals.Verbose() {\n\t\ttext.Output(out, \"\\nVersion: %d\\n\", fastly.ToValue(d.ServiceVersion))\n\t\ttext.PrintDictionary(out, \"\", d)\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/dictionaryentry/create.go",
    "content": "package dictionaryentry\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// CreateCommand calls the Fastly API to create a dictionary item.\ntype CreateCommand struct {\n\targparser.Base\n\tInput              fastly.CreateDictionaryItemInput\n\titemKey, itemValue string\n\tserviceName        argparser.OptionalServiceNameID\n}\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"create\", \"Create a new item on a Fastly edge dictionary\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"dictionary-id\", \"Dictionary ID\").Required().StringVar(&c.Input.DictionaryID)\n\tc.CmdClause.Flag(\"key\", \"Dictionary item key\").Required().StringVar(&c.itemKey)\n\tc.CmdClause.Flag(\"value\", \"Dictionary item value\").Required().StringVar(&c.itemValue)\n\n\t// Optional.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *CreateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, source, flag, err := argparser.ServiceID(c.serviceName, *c.Globals.Manifest, c.Globals.APIClient, c.Globals.ErrLog)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif c.Globals.Verbose() {\n\t\targparser.DisplayServiceID(serviceID, flag, source, out)\n\t}\n\n\tc.Input.ItemKey = &c.itemKey\n\tc.Input.ItemValue = &c.itemValue\n\tc.Input.ServiceID = serviceID\n\t_, err = c.Globals.APIClient.CreateDictionaryItem(context.TODO(), &c.Input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\": serviceID,\n\t\t})\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Created dictionary item %s (service %s, dictionary %s)\", fastly.ToValue(c.Input.ItemKey), c.Input.ServiceID, c.Input.DictionaryID)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/dictionaryentry/delete.go",
    "content": "package dictionaryentry\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DeleteCommand calls the Fastly API to delete a service.\ntype DeleteCommand struct {\n\targparser.Base\n\tInput       fastly.DeleteDictionaryItemInput\n\tserviceName argparser.OptionalServiceNameID\n}\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"delete\", \"Delete an item from a Fastly edge dictionary\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"dictionary-id\", \"Dictionary ID\").Required().StringVar(&c.Input.DictionaryID)\n\tc.CmdClause.Flag(\"key\", \"Dictionary item key\").Required().StringVar(&c.Input.ItemKey)\n\n\t// Optional.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DeleteCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, source, flag, err := argparser.ServiceID(c.serviceName, *c.Globals.Manifest, c.Globals.APIClient, c.Globals.ErrLog)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif c.Globals.Verbose() {\n\t\targparser.DisplayServiceID(serviceID, flag, source, out)\n\t}\n\n\tc.Input.ServiceID = serviceID\n\terr = c.Globals.APIClient.DeleteDictionaryItem(context.TODO(), &c.Input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\": serviceID,\n\t\t})\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Deleted dictionary item %s (service %s, dictionary %s)\", c.Input.ItemKey, c.Input.ServiceID, c.Input.DictionaryID)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/dictionaryentry/describe.go",
    "content": "package dictionaryentry\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DescribeCommand calls the Fastly API to describe a dictionary item.\ntype DescribeCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tInput       fastly.GetDictionaryItemInput\n\tserviceName argparser.OptionalServiceNameID\n}\n\n// NewDescribeCommand returns a usable command registered under the parent.\nfunc NewDescribeCommand(parent argparser.Registerer, g *global.Data) *DescribeCommand {\n\tc := DescribeCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"describe\", \"Show detailed information about a Fastly edge dictionary item\").Alias(\"get\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"dictionary-id\", \"Dictionary ID\").Required().StringVar(&c.Input.DictionaryID)\n\tc.CmdClause.Flag(\"key\", \"Dictionary item key\").Required().StringVar(&c.Input.ItemKey)\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DescribeCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tserviceID, source, flag, err := argparser.ServiceID(c.serviceName, *c.Globals.Manifest, c.Globals.APIClient, c.Globals.ErrLog)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif c.Globals.Verbose() {\n\t\targparser.DisplayServiceID(serviceID, flag, source, out)\n\t}\n\n\tc.Input.ServiceID = serviceID\n\n\to, err := c.Globals.APIClient.GetDictionaryItem(context.TODO(), &c.Input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\": serviceID,\n\t\t})\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\tif !c.Globals.Verbose() {\n\t\tfmt.Fprintf(out, \"\\nService ID: %s\\n\", c.Input.ServiceID)\n\t}\n\ttext.PrintDictionaryItem(out, \"\", o)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/dictionaryentry/dictionaryitem_test.go",
    "content": "package dictionaryentry_test\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\t\"net/http\"\n\t\"os\"\n\t\"strings\"\n\t\"testing\"\n\n\troot \"github.com/fastly/cli/pkg/commands/service\"\n\tsub \"github.com/fastly/cli/pkg/commands/service/dictionaryentry\"\n\t\"github.com/fastly/cli/pkg/mock\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n)\n\nfunc TestDictionaryItemDescribe(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"--service-id 123 --key foo\",\n\t\t\tAPI:       &mock.API{GetDictionaryItemFn: describeDictionaryItemOK},\n\t\t\tWantError: \"error parsing arguments: required flag --dictionary-id not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs:      \"--service-id 123 --dictionary-id 456\",\n\t\t\tAPI:       &mock.API{GetDictionaryItemFn: describeDictionaryItemOK},\n\t\t\tWantError: \"error parsing arguments: required flag --key not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs:       \"--service-id 123 --dictionary-id 456 --key foo\",\n\t\t\tAPI:        &mock.API{GetDictionaryItemFn: describeDictionaryItemOK},\n\t\t\tWantOutput: describeDictionaryItemOutput,\n\t\t},\n\t\t{\n\t\t\tArgs:       \"--service-id 123 --dictionary-id 456 --key foo-deleted\",\n\t\t\tAPI:        &mock.API{GetDictionaryItemFn: describeDictionaryItemOKDeleted},\n\t\t\tWantOutput: describeDictionaryItemOutputDeleted,\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"describe\"}, scenarios)\n}\n\nfunc TestDictionaryItemsList(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"--service-id 123\",\n\t\t\tWantError: \"error parsing arguments: required flag --dictionary-id not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs:      \"--dictionary-id 456\",\n\t\t\tEnvVars:   map[string]string{\"FASTLY_SERVICE_ID\": \"\"},\n\t\t\tWantError: \"error reading service: no service ID found\",\n\t\t},\n\t\t{\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tGetDictionaryItemsFn: func(ctx context.Context, _ *fastly.GetDictionaryItemsInput) *fastly.ListPaginator[fastly.DictionaryItem] {\n\t\t\t\t\treturn fastly.NewPaginator[fastly.DictionaryItem](ctx, &mock.HTTPClient{\n\t\t\t\t\t\tErrors: []error{\n\t\t\t\t\t\t\ttestutil.Err,\n\t\t\t\t\t\t},\n\t\t\t\t\t\tResponses: []*http.Response{nil},\n\t\t\t\t\t}, fastly.ListOpts{}, \"/example\")\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:      \"--service-id 123 --dictionary-id 456\",\n\t\t\tWantError: testutil.Err.Error(),\n\t\t},\n\t\t{\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tGetDictionaryItemsFn: func(ctx context.Context, _ *fastly.GetDictionaryItemsInput) *fastly.ListPaginator[fastly.DictionaryItem] {\n\t\t\t\t\treturn fastly.NewPaginator[fastly.DictionaryItem](ctx, &mock.HTTPClient{\n\t\t\t\t\t\tErrors: []error{nil},\n\t\t\t\t\t\tResponses: []*http.Response{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tBody: io.NopCloser(strings.NewReader(`[\n                  {\n                    \"dictionary_id\": \"123\",\n                    \"item_key\": \"foo\",\n                    \"item_value\": \"bar\",\n                    \"created_at\": \"2021-06-15T23:00:00Z\",\n                    \"updated_at\": \"2021-06-15T23:00:00Z\"\n                  },\n                  {\n                    \"dictionary_id\": \"456\",\n                    \"item_key\": \"baz\",\n                    \"item_value\": \"qux\",\n                    \"created_at\": \"2021-06-15T23:00:00Z\",\n                    \"updated_at\": \"2021-06-15T23:00:00Z\",\n                    \"deleted_at\": \"2021-06-15T23:00:00Z\"\n                  }\n                ]`)),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t}, fastly.ListOpts{}, \"/example\")\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:       \"--service-id 123 --dictionary-id 456 --per-page 1\",\n\t\t\tWantOutput: listDictionaryItemsOutput,\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"list\"}, scenarios)\n}\n\nfunc TestDictionaryItemCreate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"--service-id 123\",\n\t\t\tAPI:       &mock.API{CreateDictionaryItemFn: createDictionaryItemOK},\n\t\t\tWantError: \"error parsing arguments: required flag \",\n\t\t},\n\t\t{\n\t\t\tArgs:      \"--service-id 123 --dictionary-id 456\",\n\t\t\tAPI:       &mock.API{CreateDictionaryItemFn: createDictionaryItemOK},\n\t\t\tWantError: \"error parsing arguments: required flag \",\n\t\t},\n\t\t{\n\t\t\tArgs:       \"--service-id 123 --dictionary-id 456 --key foo --value bar\",\n\t\t\tAPI:        &mock.API{CreateDictionaryItemFn: createDictionaryItemOK},\n\t\t\tWantOutput: \"SUCCESS: Created dictionary item foo (service 123, dictionary 456)\\n\",\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"create\"}, scenarios)\n}\n\nfunc TestDictionaryItemUpdate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"--service-id 123\",\n\t\t\tAPI:       &mock.API{UpdateDictionaryItemFn: updateDictionaryItemOK},\n\t\t\tWantError: \"error parsing arguments: required flag --dictionary-id not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs:      \"--service-id 123 --dictionary-id 456\",\n\t\t\tAPI:       &mock.API{UpdateDictionaryItemFn: updateDictionaryItemOK},\n\t\t\tWantError: \"an empty value is not allowed for either the '--key' or '--value' flags\",\n\t\t},\n\t\t{\n\t\t\tArgs:       \"--service-id 123 --dictionary-id 456 --key foo --value bar\",\n\t\t\tAPI:        &mock.API{UpdateDictionaryItemFn: updateDictionaryItemOK},\n\t\t\tWantOutput: updateDictionaryItemOutput,\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"update\"}, scenarios)\n\n\t// File-based test: invalid json\n\tt.Run(\"invalid json file\", func(t *testing.T) {\n\t\tfilePath := testutil.MakeTempFile(t, `{invalid\": \"json\"}`)\n\t\tdefer os.RemoveAll(filePath)\n\n\t\tscenarios := []testutil.CLIScenario{\n\t\t\t{\n\t\t\t\tArgs:      \"--service-id 123 --dictionary-id 456 --file \" + filePath,\n\t\t\t\tWantError: \"invalid character 'i' looking for beginning of object key string\",\n\t\t\t},\n\t\t}\n\t\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"update\"}, scenarios)\n\t})\n\n\t// NOTE: We don't specify the full error value in the WantError field\n\t// because this would cause an error on different OS'. For example, Unix\n\t// systems report 'no such file or directory', while Windows will report\n\t// 'The system cannot find the file specified'.\n\tt.Run(\"missing file\", func(t *testing.T) {\n\t\tscenarios := []testutil.CLIScenario{\n\t\t\t{\n\t\t\t\tArgs:      \"--service-id 123 --dictionary-id 456 --file missingPath\",\n\t\t\t\tWantError: \"open missingPath:\",\n\t\t\t},\n\t\t}\n\t\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"update\"}, scenarios)\n\t})\n\n\t// File-based test: batch modify error\n\tt.Run(\"batch modify error\", func(t *testing.T) {\n\t\tfilePath := testutil.MakeTempFile(t, dictionaryItemBatchModifyInputOK)\n\t\tdefer os.RemoveAll(filePath)\n\n\t\tscenarios := []testutil.CLIScenario{\n\t\t\t{\n\t\t\t\tArgs:      \"--service-id 123 --dictionary-id 456 --file \" + filePath,\n\t\t\t\tAPI:       &mock.API{BatchModifyDictionaryItemsFn: batchModifyDictionaryItemsError},\n\t\t\t\tWantError: errTest.Error(),\n\t\t\t},\n\t\t}\n\t\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"update\"}, scenarios)\n\t})\n\n\t// File-based test: batch modify success\n\tt.Run(\"batch modify success\", func(t *testing.T) {\n\t\tfilePath := testutil.MakeTempFile(t, dictionaryItemBatchModifyInputOK)\n\t\tdefer os.RemoveAll(filePath)\n\n\t\tscenarios := []testutil.CLIScenario{\n\t\t\t{\n\t\t\t\tArgs:       \"--service-id 123 --dictionary-id 456 --file \" + filePath,\n\t\t\t\tAPI:        &mock.API{BatchModifyDictionaryItemsFn: batchModifyDictionaryItemsOK},\n\t\t\t\tWantOutput: \"SUCCESS: Made 4 modifications of Dictionary 456 on service 123\\n\",\n\t\t\t},\n\t\t}\n\t\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"update\"}, scenarios)\n\t})\n}\n\nfunc TestDictionaryItemDelete(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"--service-id 123\",\n\t\t\tAPI:       &mock.API{DeleteDictionaryItemFn: deleteDictionaryItemOK},\n\t\t\tWantError: \"error parsing arguments: required flag \",\n\t\t},\n\t\t{\n\t\t\tArgs:      \"--service-id 123 --dictionary-id 456\",\n\t\t\tAPI:       &mock.API{DeleteDictionaryItemFn: deleteDictionaryItemOK},\n\t\t\tWantError: \"error parsing arguments: required flag \",\n\t\t},\n\t\t{\n\t\t\tArgs:       \"--service-id 123 --dictionary-id 456 --key foo\",\n\t\t\tAPI:        &mock.API{DeleteDictionaryItemFn: deleteDictionaryItemOK},\n\t\t\tWantOutput: \"SUCCESS: Deleted dictionary item foo (service 123, dictionary 456)\\n\",\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"delete\"}, scenarios)\n}\n\nfunc describeDictionaryItemOK(_ context.Context, i *fastly.GetDictionaryItemInput) (*fastly.DictionaryItem, error) {\n\treturn &fastly.DictionaryItem{\n\t\tServiceID:    fastly.ToPointer(i.ServiceID),\n\t\tDictionaryID: fastly.ToPointer(i.DictionaryID),\n\t\tItemKey:      fastly.ToPointer(i.ItemKey),\n\t\tItemValue:    fastly.ToPointer(\"bar\"),\n\t\tCreatedAt:    testutil.MustParseTimeRFC3339(\"2001-02-03T04:05:06Z\"),\n\t\tUpdatedAt:    testutil.MustParseTimeRFC3339(\"2001-02-03T04:05:07Z\"),\n\t}, nil\n}\n\nvar describeDictionaryItemOutput = \"\\n\" + `Service ID: 123\nDictionary ID: 456\nItem Key: foo\nItem Value: bar\nCreated (UTC): 2001-02-03 04:05\nLast edited (UTC): 2001-02-03 04:05\n`\n\nvar updateDictionaryItemOutput = `SUCCESS: Updated dictionary item (service 123)\n\nDictionary ID: 456\nItem Key: foo\nItem Value: bar\nCreated (UTC): 2001-02-03 04:05\nLast edited (UTC): 2001-02-03 04:05\n`\n\nfunc describeDictionaryItemOKDeleted(_ context.Context, i *fastly.GetDictionaryItemInput) (*fastly.DictionaryItem, error) {\n\treturn &fastly.DictionaryItem{\n\t\tServiceID:    fastly.ToPointer(i.ServiceID),\n\t\tDictionaryID: fastly.ToPointer(i.DictionaryID),\n\t\tItemKey:      fastly.ToPointer(i.ItemKey),\n\t\tItemValue:    fastly.ToPointer(\"bar\"),\n\t\tCreatedAt:    testutil.MustParseTimeRFC3339(\"2001-02-03T04:05:06Z\"),\n\t\tUpdatedAt:    testutil.MustParseTimeRFC3339(\"2001-02-03T04:05:07Z\"),\n\t\tDeletedAt:    testutil.MustParseTimeRFC3339(\"2001-02-03T04:06:08Z\"),\n\t}, nil\n}\n\nvar describeDictionaryItemOutputDeleted = \"\\n\" + strings.TrimSpace(`\nService ID: 123\nDictionary ID: 456\nItem Key: foo-deleted\nItem Value: bar\nCreated (UTC): 2001-02-03 04:05\nLast edited (UTC): 2001-02-03 04:05\nDeleted (UTC): 2001-02-03 04:06\n`) + \"\\n\"\n\nvar listDictionaryItemsOutput = \"\\n\" + strings.TrimSpace(`\nService ID: 123\nItem: 1/2\n\tDictionary ID: 123\n\tItem Key: foo\n\tItem Value: bar\n\tCreated (UTC): 2021-06-15 23:00\n\tLast edited (UTC): 2021-06-15 23:00\n\nItem: 2/2\n\tDictionary ID: 456\n\tItem Key: baz\n\tItem Value: qux\n\tCreated (UTC): 2021-06-15 23:00\n\tLast edited (UTC): 2021-06-15 23:00\n\tDeleted (UTC): 2021-06-15 23:00\n`) + \"\\n\\n\"\n\nfunc createDictionaryItemOK(_ context.Context, i *fastly.CreateDictionaryItemInput) (*fastly.DictionaryItem, error) {\n\treturn &fastly.DictionaryItem{\n\t\tServiceID:    fastly.ToPointer(i.ServiceID),\n\t\tDictionaryID: fastly.ToPointer(i.DictionaryID),\n\t\tItemKey:      i.ItemKey,\n\t\tItemValue:    i.ItemValue,\n\t\tCreatedAt:    testutil.MustParseTimeRFC3339(\"2001-02-03T04:05:06Z\"),\n\t\tUpdatedAt:    testutil.MustParseTimeRFC3339(\"2001-02-03T04:05:07Z\"),\n\t}, nil\n}\n\nfunc updateDictionaryItemOK(_ context.Context, i *fastly.UpdateDictionaryItemInput) (*fastly.DictionaryItem, error) {\n\treturn &fastly.DictionaryItem{\n\t\tServiceID:    fastly.ToPointer(i.ServiceID),\n\t\tDictionaryID: fastly.ToPointer(i.DictionaryID),\n\t\tItemKey:      fastly.ToPointer(i.ItemKey),\n\t\tItemValue:    fastly.ToPointer(i.ItemValue),\n\t\tCreatedAt:    testutil.MustParseTimeRFC3339(\"2001-02-03T04:05:06Z\"),\n\t\tUpdatedAt:    testutil.MustParseTimeRFC3339(\"2001-02-03T04:05:07Z\"),\n\t}, nil\n}\n\nfunc deleteDictionaryItemOK(_ context.Context, _ *fastly.DeleteDictionaryItemInput) error {\n\treturn nil\n}\n\nvar dictionaryItemBatchModifyInputOK = `\n{\n\t\"items\": [\n\t\t{\n\t\t  \"op\": \"create\",\n\t\t  \"item_key\": \"some_key\",\n\t\t  \"item_value\": \"new_value\"\n\t\t},\n\t\t{\n\t\t  \"op\": \"update\",\n\t\t  \"item_key\": \"some_key\",\n\t\t  \"item_value\": \"new_value\"\n\t\t},\n\t\t{\n\t\t  \"op\": \"upsert\",\n\t\t  \"item_key\": \"some_key\",\n\t\t  \"item_value\": \"new_value\"\n\t\t},\n\t\t{\n\t\t  \"op\": \"delete\",\n\t\t  \"item_key\": \"some_key\"\n\t\t}\n\t]\n}`\n\nfunc batchModifyDictionaryItemsOK(_ context.Context, _ *fastly.BatchModifyDictionaryItemsInput) error {\n\treturn nil\n}\n\nfunc batchModifyDictionaryItemsError(_ context.Context, _ *fastly.BatchModifyDictionaryItemsInput) error {\n\treturn errTest\n}\n\nvar errTest = errors.New(\"an expected error occurred\")\n"
  },
  {
    "path": "pkg/commands/service/dictionaryentry/doc.go",
    "content": "// Package dictionaryentry contains commands to inspect and manipulate Fastly edge\n// dictionary items.\npackage dictionaryentry\n"
  },
  {
    "path": "pkg/commands/service/dictionaryentry/list.go",
    "content": "package dictionaryentry\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// ListCommand calls the Fastly API to list dictionary items.\ntype ListCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tdirection     string\n\tinput         fastly.GetDictionaryItemsInput\n\tpage, perPage int\n\tserviceName   argparser.OptionalServiceNameID\n\tsort          string\n}\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"list\", \"List items in a Fastly edge dictionary\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"dictionary-id\", \"Dictionary ID\").Required().StringVar(&c.input.DictionaryID)\n\n\t// Optional.\n\tc.CmdClause.Flag(\"direction\", \"Direction in which to sort results\").Default(argparser.PaginationDirection[0]).HintOptions(argparser.PaginationDirection...).EnumVar(&c.direction, argparser.PaginationDirection...)\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\tc.CmdClause.Flag(\"page\", \"Page number of data set to fetch\").IntVar(&c.page)\n\tc.CmdClause.Flag(\"per-page\", \"Number of records per page\").IntVar(&c.perPage)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\tc.CmdClause.Flag(\"sort\", \"Field on which to sort\").Default(\"created\").StringVar(&c.sort)\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *ListCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tserviceID, source, flag, err := argparser.ServiceID(c.serviceName, *c.Globals.Manifest, c.Globals.APIClient, c.Globals.ErrLog)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif c.Globals.Verbose() {\n\t\targparser.DisplayServiceID(serviceID, flag, source, out)\n\t}\n\n\tc.input.Direction = &c.direction\n\tc.input.Page = &c.page\n\tc.input.PerPage = &c.perPage\n\tc.input.ServiceID = serviceID\n\tc.input.Sort = &c.sort\n\tpaginator := c.Globals.APIClient.GetDictionaryItems(context.TODO(), &c.input)\n\n\tvar o []*fastly.DictionaryItem\n\tfor paginator.HasNext() {\n\t\tdata, err := paginator.GetNext()\n\t\tif err != nil {\n\t\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\t\"Dictionary ID\":   c.input.DictionaryID,\n\t\t\t\t\"Service ID\":      serviceID,\n\t\t\t\t\"Remaining Pages\": paginator.Remaining(),\n\t\t\t})\n\t\t\treturn err\n\t\t}\n\t\to = append(o, data...)\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\tif !c.Globals.Verbose() {\n\t\tfmt.Fprintf(out, \"\\nService ID: %s\\n\", c.input.ServiceID)\n\t}\n\tfor i, dictionary := range o {\n\t\ttext.Output(out, \"Item: %d/%d\", i+1, len(o))\n\t\ttext.PrintDictionaryItem(out, \"\\t\", dictionary)\n\t\ttext.Break(out)\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/dictionaryentry/root.go",
    "content": "package dictionaryentry\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"dictionary-entry\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manipulate Fastly edge dictionary items\")\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/service/dictionaryentry/update.go",
    "content": "package dictionaryentry\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// UpdateCommand calls the Fastly API to update a dictionary item.\ntype UpdateCommand struct {\n\targparser.Base\n\n\tInput       fastly.UpdateDictionaryItemInput\n\tInputBatch  fastly.BatchModifyDictionaryItemsInput\n\tfile        argparser.OptionalString\n\tserviceName argparser.OptionalServiceNameID\n}\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"update\", \"Update or insert an item on a Fastly edge dictionary\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"dictionary-id\", \"Dictionary ID\").Required().StringVar(&c.Input.DictionaryID)\n\n\t// Optional.\n\tc.CmdClause.Flag(\"file\", \"Batch update json file\").Action(c.file.Set).StringVar(&c.file.Value)\n\tc.CmdClause.Flag(\"key\", \"Dictionary item key\").StringVar(&c.Input.ItemKey)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\tc.CmdClause.Flag(\"value\", \"Dictionary item value\").StringVar(&c.Input.ItemValue)\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *UpdateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, source, flag, err := argparser.ServiceID(c.serviceName, *c.Globals.Manifest, c.Globals.APIClient, c.Globals.ErrLog)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif c.Globals.Verbose() {\n\t\targparser.DisplayServiceID(serviceID, flag, source, out)\n\t}\n\n\tc.Input.ServiceID = serviceID\n\tc.InputBatch.ServiceID = serviceID\n\tc.InputBatch.DictionaryID = c.Input.DictionaryID\n\n\tif c.file.WasSet {\n\t\terr := c.batchModify(out)\n\t\tif err != nil {\n\t\t\tc.Globals.ErrLog.Add(err)\n\t\t\treturn err\n\t\t}\n\t\treturn nil\n\t}\n\n\tif c.Input.ItemKey == \"\" || c.Input.ItemValue == \"\" {\n\t\treturn fmt.Errorf(\"an empty value is not allowed for either the '--key' or '--value' flags\")\n\t}\n\n\td, err := c.Globals.APIClient.UpdateDictionaryItem(context.TODO(), &c.Input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Updated dictionary item (service %s)\\n\\n\", fastly.ToValue(d.ServiceID))\n\ttext.PrintDictionaryItem(out, \"\", d)\n\treturn nil\n}\n\nfunc (c *UpdateCommand) batchModify(out io.Writer) error {\n\tjsonFile, err := os.Open(c.file.Value)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tjsonBytes, err := io.ReadAll(jsonFile)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\terr = json.Unmarshal(jsonBytes, &c.InputBatch)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif len(c.InputBatch.Items) == 0 {\n\t\treturn fmt.Errorf(\"item key not found in file %s\", c.file.Value)\n\t}\n\n\terr = c.Globals.APIClient.BatchModifyDictionaryItems(context.TODO(), &c.InputBatch)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Made %d modifications of Dictionary %s on service %s\", len(c.InputBatch.Items), c.Input.DictionaryID, c.InputBatch.ServiceID)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/doc.go",
    "content": "// Package service contains commands to inspect and manipulate Fastly services.\npackage service\n"
  },
  {
    "path": "pkg/commands/service/domain/create.go",
    "content": "package domain\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// CreateCommand calls the Fastly API to create domains.\ntype CreateCommand struct {\n\targparser.Base\n\n\t// Required.\n\tserviceVersion argparser.OptionalServiceVersion\n\n\t// Optional.\n\tautoClone   argparser.OptionalAutoClone\n\tcomment     argparser.OptionalString\n\tname        argparser.OptionalString\n\tserviceName argparser.OptionalServiceNameID\n}\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"create\", \"Create a domain on a Fastly service version\").Alias(\"add\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.autoClone.Set,\n\t\tDst:    &c.autoClone.Value,\n\t})\n\tc.CmdClause.Flag(\"comment\", \"A descriptive note\").Action(c.comment.Set).StringVar(&c.comment.Value)\n\tc.CmdClause.Flag(\"name\", \"Domain name\").Short('n').Action(c.name.Set).StringVar(&c.name.Value)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *CreateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(false),\n\t\tLocked:             optional.Of(false),\n\t\tAutoCloneFlag:      c.autoClone,\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": errors.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\tinput := fastly.CreateDomainInput{\n\t\tServiceID:      serviceID,\n\t\tServiceVersion: fastly.ToValue(serviceVersion.Number),\n\t}\n\tif c.name.WasSet {\n\t\tinput.Name = &c.name.Value\n\t}\n\tif c.comment.WasSet {\n\t\tinput.Comment = &c.comment.Value\n\t}\n\td, err := c.Globals.APIClient.CreateDomain(context.TODO(), &input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fastly.ToValue(serviceVersion.Number),\n\t\t})\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Created domain %s (service %s version %d)\", fastly.ToValue(d.Name), fastly.ToValue(d.ServiceID), fastly.ToValue(d.ServiceVersion))\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/domain/delete.go",
    "content": "package domain\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DeleteCommand calls the Fastly API to delete domains.\ntype DeleteCommand struct {\n\targparser.Base\n\tInput          fastly.DeleteDomainInput\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n\tautoClone      argparser.OptionalAutoClone\n}\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"delete\", \"Delete a domain on a Fastly service version\").Alias(\"remove\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"Domain name\").Short('n').Required().StringVar(&c.Input.Name)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.autoClone.Set,\n\t\tDst:    &c.autoClone.Value,\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DeleteCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(false),\n\t\tLocked:             optional.Of(false),\n\t\tAutoCloneFlag:      c.autoClone,\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": errors.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tc.Input.ServiceID = serviceID\n\tc.Input.ServiceVersion = fastly.ToValue(serviceVersion.Number)\n\n\tif err := c.Globals.APIClient.DeleteDomain(context.TODO(), &c.Input); err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fastly.ToValue(serviceVersion.Number),\n\t\t})\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Deleted domain %s (service %s version %d)\", c.Input.Name, c.Input.ServiceID, c.Input.ServiceVersion)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/domain/describe.go",
    "content": "package domain\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// DescribeCommand calls the Fastly API to describe a domain.\ntype DescribeCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tInput          fastly.GetDomainInput\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n}\n\n// NewDescribeCommand returns a usable command registered under the parent.\nfunc NewDescribeCommand(parent argparser.Registerer, g *global.Data) *DescribeCommand {\n\tc := DescribeCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"describe\", \"Show detailed information about a domain on a Fastly service version\").Alias(\"get\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"Name of domain\").Short('n').Required().StringVar(&c.Input.Name)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DescribeCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fsterr.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tc.Input.ServiceID = serviceID\n\tc.Input.ServiceVersion = fastly.ToValue(serviceVersion.Number)\n\n\to, err := c.Globals.APIClient.GetDomain(context.TODO(), &c.Input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fastly.ToValue(serviceVersion.Number),\n\t\t})\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\tif !c.Globals.Verbose() {\n\t\tfmt.Fprintf(out, \"\\nService ID: %s\\n\", fastly.ToValue(o.ServiceID))\n\t}\n\tfmt.Fprintf(out, \"Version: %d\\n\", fastly.ToValue(o.ServiceVersion))\n\tfmt.Fprintf(out, \"Name: %s\\n\", fastly.ToValue(o.Name))\n\tfmt.Fprintf(out, \"Comment: %v\\n\", fastly.ToValue(o.Comment))\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/domain/doc.go",
    "content": "// Package domain contains commands to inspect and manipulate Fastly service domains.\npackage domain\n"
  },
  {
    "path": "pkg/commands/service/domain/domain_test.go",
    "content": "package domain_test\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\troot \"github.com/fastly/cli/pkg/commands/service\"\n\tsub \"github.com/fastly/cli/pkg/commands/service/domain\"\n\t\"github.com/fastly/cli/pkg/mock\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n)\n\nfunc TestDomainCreate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"--version 1\",\n\t\t\tEnvVars:   map[string]string{\"FASTLY_SERVICE_ID\": \"\"},\n\t\t\tWantError: \"error reading service: no service ID found\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name www.test.com --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tCreateDomainFn: createDomainOK,\n\t\t\t},\n\t\t\tWantOutput: \"Created domain www.test.com (service 123 version 4)\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name www.test.com --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tCreateDomainFn: createDomainError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"create\"}, scenarios)\n}\n\nfunc TestDomainList(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:  testutil.GetVersion,\n\t\t\t\tListDomainsFn: listDomainsOK,\n\t\t\t},\n\t\t\tWantOutput: listDomainsShortOutput,\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --verbose\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:  testutil.GetVersion,\n\t\t\t\tListDomainsFn: listDomainsOK,\n\t\t\t},\n\t\t\tWantOutput: listDomainsVerboseOutput,\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 -v\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:  testutil.GetVersion,\n\t\t\t\tListDomainsFn: listDomainsOK,\n\t\t\t},\n\t\t\tWantOutput: listDomainsVerboseOutput,\n\t\t},\n\t\t{\n\t\t\tArgs: \"--verbose --service-id 123 --version 1\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:  testutil.GetVersion,\n\t\t\t\tListDomainsFn: listDomainsOK,\n\t\t\t},\n\t\t\tWantOutput: listDomainsVerboseOutput,\n\t\t},\n\t\t{\n\t\t\tArgs: \"-v --service-id 123 --version 1\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:  testutil.GetVersion,\n\t\t\t\tListDomainsFn: listDomainsOK,\n\t\t\t},\n\t\t\tWantOutput: listDomainsVerboseOutput,\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:  testutil.GetVersion,\n\t\t\t\tListDomainsFn: listDomainsError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"list\"}, scenarios)\n}\n\nfunc TestDomainDescribe(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"--service-id 123 --version 1\",\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name www.test.com\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tGetDomainFn:  getDomainError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name www.test.com\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tGetDomainFn:  getDomainOK,\n\t\t\t},\n\t\t\tWantOutput: describeDomainOutput,\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"describe\"}, scenarios)\n}\n\nfunc TestDomainUpdate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"--service-id 123 --version 1 --new-name www.test.com --comment \",\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name www.test.com --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tUpdateDomainFn: updateDomainOK,\n\t\t\t},\n\t\t\tWantError: \"error parsing arguments: must provide either --new-name or --comment to update domain\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name www.test.com --new-name www.example.com --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tUpdateDomainFn: updateDomainError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name www.test.com --new-name www.example.com --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tUpdateDomainFn: updateDomainOK,\n\t\t\t},\n\t\t\tWantOutput: \"Updated domain www.example.com (service 123 version 4)\",\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"update\"}, scenarios)\n}\n\nfunc TestDomainDelete(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"--service-id 123 --version 1\",\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name www.test.com --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tDeleteDomainFn: deleteDomainError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name www.test.com --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tDeleteDomainFn: deleteDomainOK,\n\t\t\t},\n\t\t\tWantOutput: \"Deleted domain www.test.com (service 123 version 4)\",\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"delete\"}, scenarios)\n}\n\nfunc TestDomainValidate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --version flag\",\n\t\t\tWantError: \"error parsing arguments: required flag --version not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --service-id flag\",\n\t\t\tArgs:      \"--version 3\",\n\t\t\tEnvVars:   map[string]string{\"FASTLY_SERVICE_ID\": \"\"},\n\t\t\tWantError: \"error reading service: no service ID found\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate missing --name flag\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t},\n\t\t\tArgs:      \"--service-id 123 --version 3\",\n\t\t\tWantError: \"error parsing arguments: must provide --name flag\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate ValidateDomain API error\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tValidateDomainFn: func(_ context.Context, _ *fastly.ValidateDomainInput) (*fastly.DomainValidationResult, error) {\n\t\t\t\t\treturn nil, testutil.Err\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:      \"--name foo.example.com --service-id 123 --version 3\",\n\t\t\tWantError: testutil.Err.Error(),\n\t\t},\n\t\t{\n\t\t\tName: \"validate ValidateAllDomains API error\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tValidateAllDomainsFn: func(_ context.Context, _ *fastly.ValidateAllDomainsInput) ([]*fastly.DomainValidationResult, error) {\n\t\t\t\t\treturn nil, testutil.Err\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:      \"--all --service-id 123 --version 3\",\n\t\t\tWantError: testutil.Err.Error(),\n\t\t},\n\t\t{\n\t\t\tName: \"validate ValidateDomain API success\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:     testutil.GetVersion,\n\t\t\t\tValidateDomainFn: validateDomain,\n\t\t\t},\n\t\t\tArgs:       \"--name foo.example.com --service-id 123 --version 3\",\n\t\t\tWantOutput: validateAPISuccess(3),\n\t\t},\n\t\t{\n\t\t\tName: \"validate ValidateAllDomains API success\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:         testutil.GetVersion,\n\t\t\t\tValidateAllDomainsFn: validateAllDomains,\n\t\t\t},\n\t\t\tArgs:       \"--all --service-id 123 --version 3\",\n\t\t\tWantOutput: validateAllAPISuccess(),\n\t\t},\n\t\t{\n\t\t\tName: \"validate missing --autoclone flag is OK\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:     testutil.GetVersion,\n\t\t\t\tValidateDomainFn: validateDomain,\n\t\t\t},\n\t\t\tArgs:       \"--name foo.example.com --service-id 123 --version 1\",\n\t\t\tWantOutput: validateAPISuccess(1),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"validate\"}, scenarios)\n}\n\nvar errTest = errors.New(\"fixture error\")\n\nfunc createDomainOK(_ context.Context, i *fastly.CreateDomainInput) (*fastly.Domain, error) {\n\treturn &fastly.Domain{\n\t\tServiceID:      fastly.ToPointer(i.ServiceID),\n\t\tServiceVersion: fastly.ToPointer(i.ServiceVersion),\n\t\tName:           i.Name,\n\t}, nil\n}\n\nfunc createDomainError(_ context.Context, _ *fastly.CreateDomainInput) (*fastly.Domain, error) {\n\treturn nil, errTest\n}\n\nfunc listDomainsOK(_ context.Context, i *fastly.ListDomainsInput) ([]*fastly.Domain, error) {\n\treturn []*fastly.Domain{\n\t\t{\n\t\t\tServiceID:      fastly.ToPointer(i.ServiceID),\n\t\t\tServiceVersion: fastly.ToPointer(i.ServiceVersion),\n\t\t\tName:           fastly.ToPointer(\"www.test.com\"),\n\t\t\tComment:        fastly.ToPointer(\"test\"),\n\t\t},\n\t\t{\n\t\t\tServiceID:      fastly.ToPointer(i.ServiceID),\n\t\t\tServiceVersion: fastly.ToPointer(i.ServiceVersion),\n\t\t\tName:           fastly.ToPointer(\"www.example.com\"),\n\t\t\tComment:        fastly.ToPointer(\"example\"),\n\t\t},\n\t}, nil\n}\n\nfunc listDomainsError(_ context.Context, _ *fastly.ListDomainsInput) ([]*fastly.Domain, error) {\n\treturn nil, errTest\n}\n\nvar listDomainsShortOutput = strings.TrimSpace(`\nSERVICE  VERSION  NAME             COMMENT\n123      1        www.test.com     test\n123      1        www.example.com  example\n`) + \"\\n\"\n\nvar listDomainsVerboseOutput = strings.TrimSpace(`\nFastly API endpoint: https://api.fastly.com\nFastly API token provided via config file (auth: user)\n\nService ID (via --service-id): 123\n\nVersion: 1\n\tDomain 1/2\n\t\tName: www.test.com\n\t\tComment: test\n\tDomain 2/2\n\t\tName: www.example.com\n\t\tComment: example\n`) + \"\\n\\n\"\n\nfunc getDomainOK(_ context.Context, i *fastly.GetDomainInput) (*fastly.Domain, error) {\n\treturn &fastly.Domain{\n\t\tServiceID:      fastly.ToPointer(i.ServiceID),\n\t\tServiceVersion: fastly.ToPointer(i.ServiceVersion),\n\t\tName:           fastly.ToPointer(i.Name),\n\t\tComment:        fastly.ToPointer(\"test\"),\n\t}, nil\n}\n\nfunc getDomainError(_ context.Context, _ *fastly.GetDomainInput) (*fastly.Domain, error) {\n\treturn nil, errTest\n}\n\nvar describeDomainOutput = \"\\n\" + strings.TrimSpace(`\nService ID: 123\nVersion: 1\nName: www.test.com\nComment: test\n`) + \"\\n\"\n\nfunc updateDomainOK(_ context.Context, i *fastly.UpdateDomainInput) (*fastly.Domain, error) {\n\treturn &fastly.Domain{\n\t\tServiceID:      fastly.ToPointer(i.ServiceID),\n\t\tServiceVersion: fastly.ToPointer(i.ServiceVersion),\n\t\tName:           i.NewName,\n\t}, nil\n}\n\nfunc updateDomainError(_ context.Context, _ *fastly.UpdateDomainInput) (*fastly.Domain, error) {\n\treturn nil, errTest\n}\n\nfunc deleteDomainOK(_ context.Context, _ *fastly.DeleteDomainInput) error {\n\treturn nil\n}\n\nfunc deleteDomainError(_ context.Context, _ *fastly.DeleteDomainInput) error {\n\treturn errTest\n}\n\nfunc validateDomain(_ context.Context, i *fastly.ValidateDomainInput) (*fastly.DomainValidationResult, error) {\n\treturn &fastly.DomainValidationResult{\n\t\tMetadata: &fastly.DomainMetadata{\n\t\t\tServiceID:      fastly.ToPointer(i.ServiceID),\n\t\t\tServiceVersion: fastly.ToPointer(i.ServiceVersion),\n\t\t\tName:           fastly.ToPointer(i.Name),\n\t\t},\n\t\tCName: fastly.ToPointer(\"foo\"),\n\t\tValid: fastly.ToPointer(true),\n\t}, nil\n}\n\nfunc validateAllDomains(_ context.Context, i *fastly.ValidateAllDomainsInput) ([]*fastly.DomainValidationResult, error) {\n\treturn []*fastly.DomainValidationResult{\n\t\t{\n\t\t\tMetadata: &fastly.DomainMetadata{\n\t\t\t\tServiceID:      fastly.ToPointer(i.ServiceID),\n\t\t\t\tServiceVersion: fastly.ToPointer(i.ServiceVersion),\n\t\t\t\tName:           fastly.ToPointer(\"foo.example.com\"),\n\t\t\t},\n\t\t\tCName: fastly.ToPointer(\"foo\"),\n\t\t\tValid: fastly.ToPointer(true),\n\t\t},\n\t\t{\n\t\t\tMetadata: &fastly.DomainMetadata{\n\t\t\t\tServiceID:      fastly.ToPointer(i.ServiceID),\n\t\t\t\tServiceVersion: fastly.ToPointer(i.ServiceVersion),\n\t\t\t\tName:           fastly.ToPointer(\"bar.example.com\"),\n\t\t\t},\n\t\t\tCName: fastly.ToPointer(\"bar\"),\n\t\t\tValid: fastly.ToPointer(true),\n\t\t},\n\t}, nil\n}\n\nfunc validateAPISuccess(version int) string {\n\treturn fmt.Sprintf(`\nService ID: 123\nService Version: %d\n\nName: foo.example.com\nValid: true\nCNAME: foo`, version)\n}\n\nfunc validateAllAPISuccess() string {\n\treturn `\nService ID: 123\nService Version: 3\n\nName: foo.example.com\nValid: true\nCNAME: foo\n\nName: bar.example.com\nValid: true\nCNAME: bar`\n}\n"
  },
  {
    "path": "pkg/commands/service/domain/list.go",
    "content": "package domain\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// ListCommand calls the Fastly API to list domains.\ntype ListCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tInput          fastly.ListDomainsInput\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n}\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"list\", \"List domains on a Fastly service version\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *ListCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fsterr.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tc.Input.ServiceID = serviceID\n\tc.Input.ServiceVersion = fastly.ToValue(serviceVersion.Number)\n\n\to, err := c.Globals.APIClient.ListDomains(context.TODO(), &c.Input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fastly.ToValue(serviceVersion.Number),\n\t\t})\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\tif !c.Globals.Verbose() {\n\t\ttw := text.NewTable(out)\n\t\ttw.AddHeader(\"SERVICE\", \"VERSION\", \"NAME\", \"COMMENT\")\n\t\tfor _, domain := range o {\n\t\t\ttw.AddLine(\n\t\t\t\tfastly.ToValue(domain.ServiceID),\n\t\t\t\tfastly.ToValue(domain.ServiceVersion),\n\t\t\t\tfastly.ToValue(domain.Name),\n\t\t\t\tfastly.ToValue(domain.Comment),\n\t\t\t)\n\t\t}\n\t\ttw.Print()\n\t\treturn nil\n\t}\n\n\tfmt.Fprintf(out, \"Version: %d\\n\", c.Input.ServiceVersion)\n\tfor i, domain := range o {\n\t\tfmt.Fprintf(out, \"\\tDomain %d/%d\\n\", i+1, len(o))\n\t\tfmt.Fprintf(out, \"\\t\\tName: %s\\n\", fastly.ToValue(domain.Name))\n\t\tfmt.Fprintf(out, \"\\t\\tComment: %v\\n\", fastly.ToValue(domain.Comment))\n\t}\n\tfmt.Fprintln(out)\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/domain/root.go",
    "content": "package domain\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"domain\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manipulate Fastly service version domains\")\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/service/domain/update.go",
    "content": "package domain\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// UpdateCommand calls the Fastly API to update domains.\ntype UpdateCommand struct {\n\targparser.Base\n\tinput          fastly.UpdateDomainInput\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n\tautoClone      argparser.OptionalAutoClone\n\n\tNewName argparser.OptionalString\n\tComment argparser.OptionalString\n}\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"update\", \"Update a domain on a Fastly service version\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"Domain name\").Short('n').Required().StringVar(&c.input.Name)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.autoClone.Set,\n\t\tDst:    &c.autoClone.Value,\n\t})\n\tc.CmdClause.Flag(\"comment\", \"A descriptive note\").Action(c.Comment.Set).StringVar(&c.Comment.Value)\n\tc.CmdClause.Flag(\"new-name\", \"New domain name\").Action(c.NewName.Set).StringVar(&c.NewName.Value)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *UpdateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(false),\n\t\tLocked:             optional.Of(false),\n\t\tAutoCloneFlag:      c.autoClone,\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": errors.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tc.input.ServiceID = serviceID\n\tc.input.ServiceVersion = fastly.ToValue(serviceVersion.Number)\n\n\t// If neither arguments are provided, error with useful message.\n\tif !c.NewName.WasSet && !c.Comment.WasSet {\n\t\treturn fmt.Errorf(\"error parsing arguments: must provide either --new-name or --comment to update domain\")\n\t}\n\n\tif c.NewName.WasSet {\n\t\tc.input.NewName = &c.NewName.Value\n\t}\n\tif c.Comment.WasSet {\n\t\tc.input.Comment = &c.Comment.Value\n\t}\n\n\td, err := c.Globals.APIClient.UpdateDomain(context.TODO(), &c.input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fastly.ToValue(serviceVersion.Number),\n\t\t\t\"New Name\":        c.NewName.Value,\n\t\t\t\"Comment\":         c.Comment.Value,\n\t\t})\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Updated domain %s (service %s version %d)\", fastly.ToValue(d.Name), fastly.ToValue(d.ServiceID), fastly.ToValue(d.ServiceVersion))\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/domain/validate.go",
    "content": "package domain\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// NewValidateCommand returns a usable command registered under the parent.\nfunc NewValidateCommand(parent argparser.Registerer, g *global.Data) *ValidateCommand {\n\tvar c ValidateCommand\n\tc.CmdClause = parent.Command(\"validate\", \"Checks the status of a specific domain's DNS record for a Service Version\")\n\tc.Globals = g\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.CmdClause.Flag(\"all\", \"Checks the status of all domains' DNS records for a Service Version\").Short('a').BoolVar(&c.all)\n\tc.CmdClause.Flag(\"name\", \"The name of the domain associated with this service\").Short('n').Action(c.name.Set).StringVar(&c.name.Value)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\n\treturn &c\n}\n\n// ValidateCommand calls the Fastly API to describe an appropriate resource.\ntype ValidateCommand struct {\n\targparser.Base\n\n\tall            bool\n\tname           argparser.OptionalString\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *ValidateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": errors.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tserviceVersionNumber := fastly.ToValue(serviceVersion.Number)\n\n\tif c.all {\n\t\tinput := c.constructInputAll(serviceID, serviceVersionNumber)\n\n\t\tr, err := c.Globals.APIClient.ValidateAllDomains(context.TODO(), input)\n\t\tif err != nil {\n\t\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\t\"Service ID\":      serviceID,\n\t\t\t\t\"Service Version\": serviceVersionNumber,\n\t\t\t})\n\t\t\treturn err\n\t\t}\n\n\t\tc.printAll(out, r)\n\t\treturn nil\n\t}\n\n\tinput, err := c.constructInput(serviceID, serviceVersionNumber)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tr, err := c.Globals.APIClient.ValidateDomain(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": serviceVersionNumber,\n\t\t\t\"Domain Name\":     c.name,\n\t\t})\n\t\treturn err\n\t}\n\n\tc.print(out, r)\n\treturn nil\n}\n\n// constructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *ValidateCommand) constructInput(serviceID string, serviceVersion int) (*fastly.ValidateDomainInput, error) {\n\tvar input fastly.ValidateDomainInput\n\n\tinput.ServiceID = serviceID\n\tinput.ServiceVersion = serviceVersion\n\n\tif !c.name.WasSet {\n\t\treturn nil, errors.RemediationError{\n\t\t\tInner:       fmt.Errorf(\"error parsing arguments: must provide --name flag\"),\n\t\t\tRemediation: \"Alternatively pass --all to validate all domains.\",\n\t\t}\n\t}\n\tinput.Name = c.name.Value\n\n\treturn &input, nil\n}\n\n// print displays the information returned from the API.\nfunc (c *ValidateCommand) print(out io.Writer, r *fastly.DomainValidationResult) {\n\tfmt.Fprintf(out, \"\\nService ID: %s\\n\", fastly.ToValue(r.Metadata.ServiceID))\n\tfmt.Fprintf(out, \"Service Version: %d\\n\\n\", fastly.ToValue(r.Metadata.ServiceVersion))\n\tfmt.Fprintf(out, \"Name: %s\\n\", fastly.ToValue(r.Metadata.Name))\n\tfmt.Fprintf(out, \"Valid: %t\\n\", fastly.ToValue(r.Valid))\n\n\tif r.CName != nil {\n\t\tfmt.Fprintf(out, \"CNAME: %s\\n\", *r.CName)\n\t}\n\tif r.Metadata.CreatedAt != nil {\n\t\tfmt.Fprintf(out, \"Created at: %s\\n\", r.Metadata.CreatedAt)\n\t}\n\tif r.Metadata.UpdatedAt != nil {\n\t\tfmt.Fprintf(out, \"Updated at: %s\\n\", r.Metadata.UpdatedAt)\n\t}\n\tif r.Metadata.DeletedAt != nil {\n\t\tfmt.Fprintf(out, \"Deleted at: %s\\n\", r.Metadata.DeletedAt)\n\t}\n\tfmt.Fprintf(out, \"\\n\")\n}\n\n// constructInputAll transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *ValidateCommand) constructInputAll(serviceID string, serviceVersion int) *fastly.ValidateAllDomainsInput {\n\tvar input fastly.ValidateAllDomainsInput\n\n\tinput.ServiceID = serviceID\n\tinput.ServiceVersion = serviceVersion\n\n\treturn &input\n}\n\n// printAll displays all domain validation results returned from the API.\nfunc (c *ValidateCommand) printAll(out io.Writer, rs []*fastly.DomainValidationResult) {\n\tfor i, r := range rs {\n\t\t// We only need to print the Service ID/Version once.\n\t\tif i == 0 {\n\t\t\tfmt.Fprintf(out, \"\\nService ID: %s\\n\", fastly.ToValue(r.Metadata.ServiceID))\n\t\t\tfmt.Fprintf(out, \"Service Version: %d\\n\\n\", fastly.ToValue(r.Metadata.ServiceVersion))\n\t\t}\n\t\tfmt.Fprintf(out, \"Name: %s\\n\", fastly.ToValue(r.Metadata.Name))\n\t\tfmt.Fprintf(out, \"Valid: %t\\n\", fastly.ToValue(r.Valid))\n\n\t\tif r.CName != nil {\n\t\t\tfmt.Fprintf(out, \"CNAME: %s\\n\", *r.CName)\n\t\t}\n\t\tif r.Metadata.CreatedAt != nil {\n\t\t\tfmt.Fprintf(out, \"Created at: %s\\n\", r.Metadata.CreatedAt)\n\t\t}\n\t\tif r.Metadata.UpdatedAt != nil {\n\t\t\tfmt.Fprintf(out, \"Updated at: %s\\n\", r.Metadata.UpdatedAt)\n\t\t}\n\t\tif r.Metadata.DeletedAt != nil {\n\t\t\tfmt.Fprintf(out, \"Deleted at: %s\\n\", r.Metadata.DeletedAt)\n\t\t}\n\t\tfmt.Fprintf(out, \"\\n\")\n\t}\n}\n"
  },
  {
    "path": "pkg/commands/service/healthcheck/create.go",
    "content": "package healthcheck\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// CreateCommand calls the Fastly API to create healthchecks.\ntype CreateCommand struct {\n\targparser.Base\n\n\t// Required.\n\tserviceVersion argparser.OptionalServiceVersion\n\n\t// Optional.\n\tautoClone        argparser.OptionalAutoClone\n\tcheckInterval    argparser.OptionalInt\n\tcomment          argparser.OptionalString\n\texpectedResponse argparser.OptionalInt\n\thost             argparser.OptionalString\n\thttpVersion      argparser.OptionalString\n\tinitial          argparser.OptionalInt\n\tmethod           argparser.OptionalString\n\tname             argparser.OptionalString\n\tpath             argparser.OptionalString\n\tserviceName      argparser.OptionalServiceNameID\n\tthreshold        argparser.OptionalInt\n\ttimeout          argparser.OptionalInt\n\twindow           argparser.OptionalInt\n}\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"create\", \"Create a healthcheck on a Fastly service version\").Alias(\"add\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.autoClone.Set,\n\t\tDst:    &c.autoClone.Value,\n\t})\n\tc.CmdClause.Flag(\"check-interval\", \"How often to run the healthcheck in milliseconds\").Action(c.checkInterval.Set).IntVar(&c.checkInterval.Value)\n\tc.CmdClause.Flag(\"comment\", \"A descriptive note\").Action(c.comment.Set).StringVar(&c.comment.Value)\n\tc.CmdClause.Flag(\"expected-response\", \"The status code expected from the host\").Action(c.expectedResponse.Set).IntVar(&c.expectedResponse.Value)\n\tc.CmdClause.Flag(\"host\", \"Which host to check\").Action(c.host.Set).StringVar(&c.host.Value)\n\tc.CmdClause.Flag(\"http-version\", \"Whether to use version 1.0 or 1.1 HTTP\").Action(c.httpVersion.Set).StringVar(&c.httpVersion.Value)\n\tc.CmdClause.Flag(\"initial\", \"When loading a config, the initial number of probes to be seen as OK\").Action(c.initial.Set).IntVar(&c.initial.Value)\n\tc.CmdClause.Flag(\"method\", \"Which HTTP method to use\").Action(c.method.Set).StringVar(&c.method.Value)\n\tc.CmdClause.Flag(\"name\", \"Healthcheck name\").Short('n').Action(c.name.Set).StringVar(&c.name.Value)\n\tc.CmdClause.Flag(\"path\", \"The path to check\").Action(c.path.Set).StringVar(&c.path.Value)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\tc.CmdClause.Flag(\"threshold\", \"How many healthchecks must succeed to be considered healthy\").Action(c.threshold.Set).IntVar(&c.threshold.Value)\n\tc.CmdClause.Flag(\"timeout\", \"Timeout in milliseconds\").Action(c.timeout.Set).IntVar(&c.timeout.Value)\n\tc.CmdClause.Flag(\"window\", \"The number of most recent healthcheck queries to keep for this healthcheck\").Action(c.window.Set).IntVar(&c.window.Value)\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *CreateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(false),\n\t\tLocked:             optional.Of(false),\n\t\tAutoCloneFlag:      c.autoClone,\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": errors.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\tinput := fastly.CreateHealthCheckInput{\n\t\tServiceID:      serviceID,\n\t\tServiceVersion: fastly.ToValue(serviceVersion.Number),\n\t}\n\n\tif c.name.WasSet {\n\t\tinput.Name = &c.name.Value\n\t}\n\tif c.comment.WasSet {\n\t\tinput.Comment = &c.comment.Value\n\t}\n\tif c.method.WasSet {\n\t\tinput.Method = &c.method.Value\n\t}\n\tif c.host.WasSet {\n\t\tinput.Host = &c.host.Value\n\t}\n\tif c.path.WasSet {\n\t\tinput.Path = &c.path.Value\n\t}\n\tif c.httpVersion.WasSet {\n\t\tinput.HTTPVersion = &c.httpVersion.Value\n\t}\n\tif c.timeout.WasSet {\n\t\tinput.Timeout = &c.timeout.Value\n\t}\n\tif c.checkInterval.WasSet {\n\t\tinput.CheckInterval = &c.checkInterval.Value\n\t}\n\tif c.expectedResponse.WasSet {\n\t\tinput.ExpectedResponse = &c.expectedResponse.Value\n\t}\n\tif c.window.WasSet {\n\t\tinput.Window = &c.window.Value\n\t}\n\tif c.threshold.WasSet {\n\t\tinput.Threshold = &c.threshold.Value\n\t}\n\tif c.initial.WasSet {\n\t\tinput.Initial = &c.initial.Value\n\t}\n\n\th, err := c.Globals.APIClient.CreateHealthCheck(context.TODO(), &input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": serviceVersion.Number,\n\t\t})\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Created healthcheck %s (service %s version %d)\", fastly.ToValue(h.Name), fastly.ToValue(h.ServiceID), fastly.ToValue(h.ServiceVersion))\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/healthcheck/delete.go",
    "content": "package healthcheck\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DeleteCommand calls the Fastly API to delete healthchecks.\ntype DeleteCommand struct {\n\targparser.Base\n\tInput          fastly.DeleteHealthCheckInput\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n\tautoClone      argparser.OptionalAutoClone\n}\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"delete\", \"Delete a healthcheck on a Fastly service version\").Alias(\"remove\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"Healthcheck name\").Short('n').Required().StringVar(&c.Input.Name)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.autoClone.Set,\n\t\tDst:    &c.autoClone.Value,\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DeleteCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(false),\n\t\tLocked:             optional.Of(false),\n\t\tAutoCloneFlag:      c.autoClone,\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": errors.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tc.Input.ServiceID = serviceID\n\tc.Input.ServiceVersion = fastly.ToValue(serviceVersion.Number)\n\n\tif err := c.Globals.APIClient.DeleteHealthCheck(context.TODO(), &c.Input); err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fastly.ToValue(serviceVersion.Number),\n\t\t})\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Deleted healthcheck %s (service %s version %d)\", c.Input.Name, c.Input.ServiceID, c.Input.ServiceVersion)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/healthcheck/describe.go",
    "content": "package healthcheck\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DescribeCommand calls the Fastly API to describe a healthcheck.\ntype DescribeCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tInput          fastly.GetHealthCheckInput\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n}\n\n// NewDescribeCommand returns a usable command registered under the parent.\nfunc NewDescribeCommand(parent argparser.Registerer, g *global.Data) *DescribeCommand {\n\tc := DescribeCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"describe\", \"Show detailed information about a healthcheck on a Fastly service version\").Alias(\"get\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"Name of healthcheck\").Short('n').Required().StringVar(&c.Input.Name)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DescribeCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fsterr.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tc.Input.ServiceID = serviceID\n\tc.Input.ServiceVersion = fastly.ToValue(serviceVersion.Number)\n\n\to, err := c.Globals.APIClient.GetHealthCheck(context.TODO(), &c.Input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fastly.ToValue(serviceVersion.Number),\n\t\t})\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\tif !c.Globals.Verbose() {\n\t\tfmt.Fprintf(out, \"\\nService ID: %s\\n\", fastly.ToValue(o.ServiceID))\n\t}\n\tfmt.Fprintf(out, \"Version: %d\\n\", fastly.ToValue(o.ServiceVersion))\n\ttext.PrintHealthCheck(out, \"\", o)\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/healthcheck/doc.go",
    "content": "// Package healthcheck contains commands to inspect and manipulate Fastly service healthchecks.\npackage healthcheck\n"
  },
  {
    "path": "pkg/commands/service/healthcheck/healthcheck_test.go",
    "content": "package healthcheck_test\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"net/http\"\n\t\"strings\"\n\t\"testing\"\n\n\troot \"github.com/fastly/cli/pkg/commands/service\"\n\tsub \"github.com/fastly/cli/pkg/commands/service/healthcheck\"\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/mock\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n)\n\nfunc TestHealthCheckCreate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"--version 1\",\n\t\t\tEnvVars:   map[string]string{\"FASTLY_SERVICE_ID\": \"\"},\n\t\t\tWantError: \"error reading service: no service ID found\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name www.test.com --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:        testutil.GetVersion,\n\t\t\t\tCloneVersionFn:      testutil.CloneVersionResult(4),\n\t\t\t\tCreateHealthCheckFn: createHealthCheckError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t\t// NOTE: Added --timeout flag to validate that a nil pointer dereference is\n\t\t// not triggered at runtime when parsing the arguments.\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name www.test.com --autoclone --timeout 10\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:        testutil.GetVersion,\n\t\t\t\tCloneVersionFn:      testutil.CloneVersionResult(4),\n\t\t\t\tCreateHealthCheckFn: createHealthCheckOK,\n\t\t\t},\n\t\t\tWantOutput: \"Created healthcheck www.test.com (service 123 version 4)\",\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"create\"}, scenarios)\n}\n\nfunc TestHealthCheckList(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:       testutil.GetVersion,\n\t\t\t\tListHealthChecksFn: listHealthChecksOK,\n\t\t\t},\n\t\t\tWantOutput: listHealthChecksShortOutput,\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --verbose\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:       testutil.GetVersion,\n\t\t\t\tListHealthChecksFn: listHealthChecksOK,\n\t\t\t},\n\t\t\tWantOutput: listHealthChecksVerboseOutput,\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 -v\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:       testutil.GetVersion,\n\t\t\t\tListHealthChecksFn: listHealthChecksOK,\n\t\t\t},\n\t\t\tWantOutput: listHealthChecksVerboseOutput,\n\t\t},\n\t\t{\n\t\t\tArgs: \"--verbose --service-id 123 --version 1\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:       testutil.GetVersion,\n\t\t\t\tListHealthChecksFn: listHealthChecksOK,\n\t\t\t},\n\t\t\tWantOutput: listHealthChecksVerboseOutput,\n\t\t},\n\t\t{\n\t\t\tArgs: \"-v --service-id 123 --version 1\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:       testutil.GetVersion,\n\t\t\t\tListHealthChecksFn: listHealthChecksOK,\n\t\t\t},\n\t\t\tWantOutput: listHealthChecksVerboseOutput,\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:       testutil.GetVersion,\n\t\t\t\tListHealthChecksFn: listHealthChecksError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"list\"}, scenarios)\n}\n\nfunc TestHealthCheckDescribe(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"--service-id 123 --version 1\",\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name www.test.com\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:     testutil.GetVersion,\n\t\t\t\tGetHealthCheckFn: getHealthCheckError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name www.test.com\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:     testutil.GetVersion,\n\t\t\t\tGetHealthCheckFn: getHealthCheckOK,\n\t\t\t},\n\t\t\tWantOutput: describeHealthCheckOutput,\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"describe\"}, scenarios)\n}\n\nfunc TestHealthCheckUpdate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"--service-id 123 --version 1 --new-name www.test.com --comment \",\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name www.test.com --new-name www.example.com --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:        testutil.GetVersion,\n\t\t\t\tCloneVersionFn:      testutil.CloneVersionResult(4),\n\t\t\t\tUpdateHealthCheckFn: updateHealthCheckOK,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name www.test.com --new-name www.example.com --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:        testutil.GetVersion,\n\t\t\t\tCloneVersionFn:      testutil.CloneVersionResult(4),\n\t\t\t\tUpdateHealthCheckFn: updateHealthCheckError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name www.test.com --new-name www.example.com --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:        testutil.GetVersion,\n\t\t\t\tCloneVersionFn:      testutil.CloneVersionResult(4),\n\t\t\t\tUpdateHealthCheckFn: updateHealthCheckOK,\n\t\t\t},\n\t\t\tWantOutput: \"Updated healthcheck www.example.com (service 123 version 4)\",\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"update\"}, scenarios)\n}\n\nfunc TestHealthCheckDelete(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      (\"--service-id 123 --version 1\"),\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name www.test.com --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:        testutil.GetVersion,\n\t\t\t\tCloneVersionFn:      testutil.CloneVersionResult(4),\n\t\t\t\tDeleteHealthCheckFn: deleteHealthCheckError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name www.test.com --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:        testutil.GetVersion,\n\t\t\t\tCloneVersionFn:      testutil.CloneVersionResult(4),\n\t\t\t\tDeleteHealthCheckFn: deleteHealthCheckOK,\n\t\t\t},\n\t\t\tWantOutput: \"Deleted healthcheck www.test.com (service 123 version 4)\",\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"delete\"}, scenarios)\n}\n\nvar errTest = errors.New(\"fixture error\")\n\nfunc createHealthCheckOK(_ context.Context, i *fastly.CreateHealthCheckInput) (*fastly.HealthCheck, error) {\n\treturn &fastly.HealthCheck{\n\t\tServiceID:      fastly.ToPointer(i.ServiceID),\n\t\tServiceVersion: fastly.ToPointer(i.ServiceVersion),\n\t\tName:           i.Name,\n\t\tHost:           fastly.ToPointer(\"www.test.com\"),\n\t\tPath:           fastly.ToPointer(\"/health\"),\n\t}, nil\n}\n\nfunc createHealthCheckError(_ context.Context, _ *fastly.CreateHealthCheckInput) (*fastly.HealthCheck, error) {\n\treturn nil, errTest\n}\n\nfunc listHealthChecksOK(_ context.Context, i *fastly.ListHealthChecksInput) ([]*fastly.HealthCheck, error) {\n\treturn []*fastly.HealthCheck{\n\t\t{\n\t\t\tServiceID:      fastly.ToPointer(i.ServiceID),\n\t\t\tServiceVersion: fastly.ToPointer(i.ServiceVersion),\n\t\t\tName:           fastly.ToPointer(\"test\"),\n\t\t\tComment:        fastly.ToPointer(\"test\"),\n\t\t\tMethod:         fastly.ToPointer(http.MethodHead),\n\t\t\tHost:           fastly.ToPointer(\"www.test.com\"),\n\t\t\tPath:           fastly.ToPointer(\"/health\"),\n\t\t},\n\t\t{\n\t\t\tServiceID:      fastly.ToPointer(i.ServiceID),\n\t\t\tServiceVersion: fastly.ToPointer(i.ServiceVersion),\n\t\t\tName:           fastly.ToPointer(\"example\"),\n\t\t\tComment:        fastly.ToPointer(\"example\"),\n\t\t\tMethod:         fastly.ToPointer(http.MethodHead),\n\t\t\tHost:           fastly.ToPointer(\"www.example.com\"),\n\t\t\tPath:           fastly.ToPointer(\"/health\"),\n\t\t},\n\t}, nil\n}\n\nfunc listHealthChecksError(_ context.Context, _ *fastly.ListHealthChecksInput) ([]*fastly.HealthCheck, error) {\n\treturn nil, errTest\n}\n\nvar listHealthChecksShortOutput = strings.TrimSpace(`\nSERVICE  VERSION  NAME     METHOD  HOST             PATH\n123      1        test     HEAD    www.test.com     /health\n123      1        example  HEAD    www.example.com  /health\n`) + \"\\n\"\n\nvar listHealthChecksVerboseOutput = strings.Join([]string{\n\t\"Fastly API endpoint: https://api.fastly.com\",\n\t\"Fastly API token provided via config file (auth: user)\",\n\t\"\",\n\t\"Service ID (via --service-id): 123\",\n\t\"\",\n\t\"Version: 1\",\n\t\"\tHealthcheck 1/2\",\n\t\"\t\tName: test\",\n\t\"\t\tComment: test\",\n\t\"\t\tMethod: HEAD\",\n\t\"\t\tHost: www.test.com\",\n\t\"\t\tPath: /health\",\n\t\"\t\tHTTP version: \",\n\t\"\t\tTimeout: 0\",\n\t\"\t\tCheck interval: 0\",\n\t\"\t\tExpected response: 0\",\n\t\"\t\tWindow: 0\",\n\t\"\t\tThreshold: 0\",\n\t\"\t\tInitial: 0\",\n\t\"\tHealthcheck 2/2\",\n\t\"\t\tName: example\",\n\t\"\t\tComment: example\",\n\t\"\t\tMethod: HEAD\",\n\t\"\t\tHost: www.example.com\",\n\t\"\t\tPath: /health\",\n\t\"\t\tHTTP version: \",\n\t\"\t\tTimeout: 0\",\n\t\"\t\tCheck interval: 0\",\n\t\"\t\tExpected response: 0\",\n\t\"\t\tWindow: 0\",\n\t\"\t\tThreshold: 0\",\n\t\"\t\tInitial: 0\",\n}, \"\\n\") + \"\\n\\n\"\n\nfunc getHealthCheckOK(_ context.Context, i *fastly.GetHealthCheckInput) (*fastly.HealthCheck, error) {\n\treturn &fastly.HealthCheck{\n\t\tServiceID:      fastly.ToPointer(i.ServiceID),\n\t\tServiceVersion: fastly.ToPointer(i.ServiceVersion),\n\t\tName:           fastly.ToPointer(\"test\"),\n\t\tMethod:         fastly.ToPointer(http.MethodHead),\n\t\tHost:           fastly.ToPointer(\"www.test.com\"),\n\t\tPath:           fastly.ToPointer(\"/healthcheck\"),\n\t\tComment:        fastly.ToPointer(\"test\"),\n\t}, nil\n}\n\nfunc getHealthCheckError(_ context.Context, _ *fastly.GetHealthCheckInput) (*fastly.HealthCheck, error) {\n\treturn nil, errTest\n}\n\nvar describeHealthCheckOutput = \"\\n\" + strings.Join([]string{\n\t\"Service ID: 123\",\n\t\"Version: 1\",\n\t\"Name: test\",\n\t\"Comment: test\",\n\t\"Method: HEAD\",\n\t\"Host: www.test.com\",\n\t\"Path: /healthcheck\",\n\t\"HTTP version: \",\n\t\"Timeout: 0\",\n\t\"Check interval: 0\",\n\t\"Expected response: 0\",\n\t\"Window: 0\",\n\t\"Threshold: 0\",\n\t\"Initial: 0\",\n}, \"\\n\") + \"\\n\"\n\nfunc updateHealthCheckOK(_ context.Context, i *fastly.UpdateHealthCheckInput) (*fastly.HealthCheck, error) {\n\treturn &fastly.HealthCheck{\n\t\tServiceID:      fastly.ToPointer(i.ServiceID),\n\t\tServiceVersion: fastly.ToPointer(i.ServiceVersion),\n\t\tName:           i.NewName,\n\t}, nil\n}\n\nfunc updateHealthCheckError(_ context.Context, _ *fastly.UpdateHealthCheckInput) (*fastly.HealthCheck, error) {\n\treturn nil, errTest\n}\n\nfunc deleteHealthCheckOK(_ context.Context, _ *fastly.DeleteHealthCheckInput) error {\n\treturn nil\n}\n\nfunc deleteHealthCheckError(_ context.Context, _ *fastly.DeleteHealthCheckInput) error {\n\treturn errTest\n}\n"
  },
  {
    "path": "pkg/commands/service/healthcheck/list.go",
    "content": "package healthcheck\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// ListCommand calls the Fastly API to list healthchecks.\ntype ListCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tInput          fastly.ListHealthChecksInput\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n}\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"list\", \"List healthchecks on a Fastly service version\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *ListCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fsterr.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tc.Input.ServiceID = serviceID\n\tc.Input.ServiceVersion = fastly.ToValue(serviceVersion.Number)\n\n\to, err := c.Globals.APIClient.ListHealthChecks(context.TODO(), &c.Input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fastly.ToValue(serviceVersion.Number),\n\t\t})\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\tif !c.Globals.Verbose() {\n\t\ttw := text.NewTable(out)\n\t\ttw.AddHeader(\"SERVICE\", \"VERSION\", \"NAME\", \"METHOD\", \"HOST\", \"PATH\")\n\t\tfor _, hc := range o {\n\t\t\ttw.AddLine(\n\t\t\t\tfastly.ToValue(hc.ServiceID),\n\t\t\t\tfastly.ToValue(hc.ServiceVersion),\n\t\t\t\tfastly.ToValue(hc.Name),\n\t\t\t\tfastly.ToValue(hc.Method),\n\t\t\t\tfastly.ToValue(hc.Host),\n\t\t\t\tfastly.ToValue(hc.Path),\n\t\t\t)\n\t\t}\n\t\ttw.Print()\n\t\treturn nil\n\t}\n\n\tfmt.Fprintf(out, \"Version: %d\\n\", c.Input.ServiceVersion)\n\tfor i, hc := range o {\n\t\tfmt.Fprintf(out, \"\\tHealthcheck %d/%d\\n\", i+1, len(o))\n\t\ttext.PrintHealthCheck(out, \"\\t\\t\", hc)\n\t}\n\tfmt.Fprintln(out)\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/healthcheck/root.go",
    "content": "package healthcheck\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"healthcheck\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manipulate Fastly service version healthchecks\")\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/service/healthcheck/update.go",
    "content": "package healthcheck\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// UpdateCommand calls the Fastly API to update healthchecks.\ntype UpdateCommand struct {\n\targparser.Base\n\tinput          fastly.UpdateHealthCheckInput\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n\tautoClone      argparser.OptionalAutoClone\n\n\tNewName          argparser.OptionalString\n\tComment          argparser.OptionalString\n\tMethod           argparser.OptionalString\n\tHost             argparser.OptionalString\n\tPath             argparser.OptionalString\n\tHTTPVersion      argparser.OptionalString\n\tTimeout          argparser.OptionalInt\n\tCheckInterval    argparser.OptionalInt\n\tExpectedResponse argparser.OptionalInt\n\tWindow           argparser.OptionalInt\n\tThreshold        argparser.OptionalInt\n\tInitial          argparser.OptionalInt\n}\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"update\", \"Update a healthcheck on a Fastly service version\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"Healthcheck name\").Short('n').Required().StringVar(&c.input.Name)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.autoClone.Set,\n\t\tDst:    &c.autoClone.Value,\n\t})\n\tc.CmdClause.Flag(\"check-interval\", \"How often to run the healthcheck in milliseconds\").Action(c.CheckInterval.Set).IntVar(&c.CheckInterval.Value)\n\tc.CmdClause.Flag(\"comment\", \"A descriptive note\").Action(c.Comment.Set).StringVar(&c.Comment.Value)\n\tc.CmdClause.Flag(\"expected-response\", \"The status code expected from the host\").Action(c.ExpectedResponse.Set).IntVar(&c.ExpectedResponse.Value)\n\tc.CmdClause.Flag(\"host\", \"Which host to check\").Action(c.Host.Set).StringVar(&c.Host.Value)\n\tc.CmdClause.Flag(\"http-version\", \"Whether to use version 1.0 or 1.1 HTTP\").Action(c.HTTPVersion.Set).StringVar(&c.HTTPVersion.Value)\n\tc.CmdClause.Flag(\"initial\", \"When loading a config, the initial number of probes to be seen as OK\").Action(c.Initial.Set).IntVar(&c.Initial.Value)\n\tc.CmdClause.Flag(\"method\", \"Which HTTP method to use\").Action(c.Method.Set).StringVar(&c.Method.Value)\n\tc.CmdClause.Flag(\"new-name\", \"Healthcheck name\").Action(c.NewName.Set).StringVar(&c.NewName.Value)\n\tc.CmdClause.Flag(\"path\", \"The path to check\").Action(c.Path.Set).StringVar(&c.Path.Value)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\tc.CmdClause.Flag(\"threshold\", \"How many healthchecks must succeed to be considered healthy\").Action(c.Threshold.Set).IntVar(&c.Threshold.Value)\n\tc.CmdClause.Flag(\"timeout\", \"Timeout in milliseconds\").Action(c.Timeout.Set).IntVar(&c.Timeout.Value)\n\tc.CmdClause.Flag(\"window\", \"The number of most recent healthcheck queries to keep for this healthcheck\").Action(c.Window.Set).IntVar(&c.Window.Value)\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *UpdateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(false),\n\t\tLocked:             optional.Of(false),\n\t\tAutoCloneFlag:      c.autoClone,\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": errors.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tc.input.ServiceID = serviceID\n\tc.input.ServiceVersion = fastly.ToValue(serviceVersion.Number)\n\n\tif c.NewName.WasSet {\n\t\tc.input.NewName = &c.NewName.Value\n\t}\n\tif c.Comment.WasSet {\n\t\tc.input.Comment = &c.Comment.Value\n\t}\n\tif c.Method.WasSet {\n\t\tc.input.Method = &c.Method.Value\n\t}\n\tif c.Host.WasSet {\n\t\tc.input.Host = &c.Host.Value\n\t}\n\tif c.Path.WasSet {\n\t\tc.input.Path = &c.Path.Value\n\t}\n\tif c.HTTPVersion.WasSet {\n\t\tc.input.HTTPVersion = &c.HTTPVersion.Value\n\t}\n\tif c.Timeout.WasSet {\n\t\tc.input.Timeout = &c.Timeout.Value\n\t}\n\tif c.CheckInterval.WasSet {\n\t\tc.input.CheckInterval = &c.CheckInterval.Value\n\t}\n\tif c.ExpectedResponse.WasSet {\n\t\tc.input.ExpectedResponse = &c.ExpectedResponse.Value\n\t}\n\tif c.Window.WasSet {\n\t\tc.input.Window = &c.Window.Value\n\t}\n\tif c.Threshold.WasSet {\n\t\tc.input.Threshold = &c.Threshold.Value\n\t}\n\tif c.Initial.WasSet {\n\t\tc.input.Initial = &c.Initial.Value\n\t}\n\n\th, err := c.Globals.APIClient.UpdateHealthCheck(context.TODO(), &c.input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fastly.ToValue(serviceVersion.Number),\n\t\t})\n\t\treturn err\n\t}\n\n\ttext.Success(out,\n\t\t\"Updated healthcheck %s (service %s version %d)\",\n\t\tfastly.ToValue(h.Name),\n\t\tfastly.ToValue(h.ServiceID),\n\t\tfastly.ToValue(h.ServiceVersion),\n\t)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/imageoptimizerdefaults/doc.go",
    "content": "// Package imageoptimizerdefaults contains commands to configure default settings for Fastly Image Optimizer requests.\npackage imageoptimizerdefaults\n"
  },
  {
    "path": "pkg/commands/service/imageoptimizerdefaults/get.go",
    "content": "package imageoptimizerdefaults\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// GetCommand calls the Fastly API to describe the Image Optimizer default settings for a service.\ntype GetCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tInput          fastly.GetImageOptimizerDefaultSettingsInput\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n}\n\n// NewGetCommand returns a usable command registered under the parent.\nfunc NewGetCommand(parent argparser.Registerer, g *global.Data) *GetCommand {\n\tc := GetCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"get\", \"Retrieve the current Image Optimizer default settings\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t\tRequired:    true,\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *GetCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fsterr.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tc.Input.ServiceID = serviceID\n\tc.Input.ServiceVersion = fastly.ToValue(serviceVersion.Number)\n\n\to, err := c.Globals.APIClient.GetImageOptimizerDefaultSettings(context.TODO(), &c.Input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fastly.ToValue(serviceVersion.Number),\n\t\t})\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\tfmt.Fprintf(out, \"Allow Video: %t\\n\", o.AllowVideo)\n\tfmt.Fprintf(out, \"JPEG Quality: %d\\n\", o.JpegQuality)\n\tfmt.Fprintf(out, \"JPEG Type: %s\\n\", o.JpegType)\n\tfmt.Fprintf(out, \"Resize Filter: %s\\n\", o.ResizeFilter)\n\tfmt.Fprintf(out, \"Upscale: %t\\n\", o.Upscale)\n\tfmt.Fprintf(out, \"WebP: %t\\n\", o.Webp)\n\tfmt.Fprintf(out, \"WebP Quality: %d\\n\", o.WebpQuality)\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/imageoptimizerdefaults/imageoptimizer_test.go",
    "content": "package imageoptimizerdefaults_test\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"testing\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\troot \"github.com/fastly/cli/pkg/commands/service\"\n\tsub \"github.com/fastly/cli/pkg/commands/service/imageoptimizerdefaults\"\n\t\"github.com/fastly/cli/pkg/mock\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n)\n\nfunc TestImageOptimizerDefaultsUpdate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --service-id flag\",\n\t\t\tArgs:      \"--version 1\",\n\t\t\tWantError: \"error parsing arguments: required flag --service-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --version flag\",\n\t\t\tArgs:      \"--service-id 123\",\n\t\t\tWantError: \"error parsing arguments: required flag --version not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate missing optional flags\",\n\t\t\tArgs: \"--service-id 123 --version 1\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:                          testutil.GetVersion,\n\t\t\t\tUpdateImageOptimizerDefaultSettingsFn: updateImageOptimizerDefaultsValidationError,\n\t\t\t},\n\t\t\t// For future clarity, this order is coming from Go-Fastly. We should fix that at some point.\n\t\t\tWantError: \"problem with field 'ResizeFilter, Webp, WebpQuality, JpegType, JpegQuality, Upscale, AllowVideo': at least one of the available optional fields is required\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate successful boolean updates of webp, upscale and allow-video\",\n\t\t\tArgs: \"--service-id 123 --version 1 --webp=true --upscale=false --allow-video=true\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:                          testutil.GetVersion,\n\t\t\t\tUpdateImageOptimizerDefaultSettingsFn: updateImageOptimizerDefaultsWithBoolsOK,\n\t\t\t},\n\t\t\tWantOutput: \"Updated Image Optimizer default settings for service 123 (version 1)\\n\\nAllow Video: true\\nJPEG Quality: 85\\nJPEG Type: auto\\nResize Filter: lanczos3\\nUpscale: false\\nWebP: true\\nWebP Quality: 85\\n\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate successful update of the --resize, --webp-quality and --jpeg-quality flags\",\n\t\t\tArgs: \"--service-id 123 --version 1 --resize-filter bicubic --webp-quality 90 --jpeg-quality 80\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:                          testutil.GetVersion,\n\t\t\t\tUpdateImageOptimizerDefaultSettingsFn: updateImageOptimizerDefaultsWithOptionsOK,\n\t\t\t},\n\t\t\tWantOutput: \"Updated Image Optimizer default settings for service 123 (version 1)\\n\\nAllow Video: false\\nJPEG Quality: 80\\nJPEG Type: auto\\nResize Filter: bicubic\\nUpscale: false\\nWebP: false\\nWebP Quality: 90\\n\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate incorrect input for the --webp flag\",\n\t\t\tArgs: \"--service-id 123 --version 1 --webp invalid\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:                          testutil.GetVersion,\n\t\t\t\tUpdateImageOptimizerDefaultSettingsFn: updateImageOptimizerDefaultsOK,\n\t\t\t},\n\t\t\tWantError: \"'webp' flag must be one of the following [true, false]\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate incorrect input for the --upscale flag\",\n\t\t\tArgs: \"--service-id 123 --version 1 --upscale invalid\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:                          testutil.GetVersion,\n\t\t\t\tUpdateImageOptimizerDefaultSettingsFn: updateImageOptimizerDefaultsOK,\n\t\t\t},\n\t\t\tWantError: \"'upscale' flag must be one of the following [true, false]\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate incorrect input for the --allow-video flag\",\n\t\t\tArgs: \"--service-id 123 --version 1 --allow-video invalid\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:                          testutil.GetVersion,\n\t\t\t\tUpdateImageOptimizerDefaultSettingsFn: updateImageOptimizerDefaultsOK,\n\t\t\t},\n\t\t\tWantError: \"'allow-video' flag must be one of the following [true, false]\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate incorrect input for the --resize-filter flag\",\n\t\t\tArgs: \"--service-id 123 --version 1 --resize-filter invalid\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:                          testutil.GetVersion,\n\t\t\t\tUpdateImageOptimizerDefaultSettingsFn: updateImageOptimizerDefaultsOK,\n\t\t\t},\n\t\t\tWantError: \"invalid resize filter: invalid. Valid options: lanczos3, lanczos2, bicubic, bilinear, nearest\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate incorrect input for the --jpeg-type flag\",\n\t\t\tArgs: \"--service-id 123 --version 1 --jpeg-type invalid\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:                          testutil.GetVersion,\n\t\t\t\tUpdateImageOptimizerDefaultSettingsFn: updateImageOptimizerDefaultsOK,\n\t\t\t},\n\t\t\tWantError: \"invalid jpeg type: invalid. Valid options: auto, baseline, progressive\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API error handling\",\n\t\t\tArgs: \"--service-id 123 --version 1 --webp true\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:                          testutil.GetVersion,\n\t\t\t\tUpdateImageOptimizerDefaultSettingsFn: updateImageOptimizerDefaultsError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"update\"}, scenarios)\n}\n\nfunc updateImageOptimizerDefaultsOK(_ context.Context, _ *fastly.UpdateImageOptimizerDefaultSettingsInput) (*fastly.ImageOptimizerDefaultSettings, error) {\n\treturn &fastly.ImageOptimizerDefaultSettings{\n\t\tResizeFilter: \"lanczos3\",\n\t\tWebp:         false,\n\t\tWebpQuality:  85,\n\t\tJpegType:     \"auto\",\n\t\tJpegQuality:  85,\n\t\tUpscale:      false,\n\t\tAllowVideo:   false,\n\t}, nil\n}\n\nfunc updateImageOptimizerDefaultsWithBoolsOK(_ context.Context, i *fastly.UpdateImageOptimizerDefaultSettingsInput) (*fastly.ImageOptimizerDefaultSettings, error) {\n\treturn &fastly.ImageOptimizerDefaultSettings{\n\t\tResizeFilter: \"lanczos3\",\n\t\tWebp:         fastly.ToValue(i.Webp),\n\t\tWebpQuality:  85,\n\t\tJpegType:     \"auto\",\n\t\tJpegQuality:  85,\n\t\tUpscale:      fastly.ToValue(i.Upscale),\n\t\tAllowVideo:   fastly.ToValue(i.AllowVideo),\n\t}, nil\n}\n\nfunc updateImageOptimizerDefaultsWithOptionsOK(_ context.Context, i *fastly.UpdateImageOptimizerDefaultSettingsInput) (*fastly.ImageOptimizerDefaultSettings, error) {\n\tresizeFilter := \"bicubic\"\n\tif i.ResizeFilter != nil {\n\t\tswitch *i.ResizeFilter {\n\t\tcase fastly.ImageOptimizerLanczos3:\n\t\t\tresizeFilter = \"lanczos3\"\n\t\tcase fastly.ImageOptimizerLanczos2:\n\t\t\tresizeFilter = \"lanczos2\"\n\t\tcase fastly.ImageOptimizerBicubic:\n\t\t\tresizeFilter = \"bicubic\"\n\t\tcase fastly.ImageOptimizerBilinear:\n\t\t\tresizeFilter = \"bilinear\"\n\t\tcase fastly.ImageOptimizerNearest:\n\t\t\tresizeFilter = \"nearest\"\n\t\t}\n\t}\n\tjpegType := \"auto\"\n\tif i.JpegType != nil {\n\t\tswitch *i.JpegType {\n\t\tcase fastly.ImageOptimizerAuto:\n\t\t\tjpegType = \"auto\"\n\t\tcase fastly.ImageOptimizerBaseline:\n\t\t\tjpegType = \"baseline\"\n\t\tcase fastly.ImageOptimizerProgressive:\n\t\t\tjpegType = \"progressive\"\n\t\t}\n\t}\n\treturn &fastly.ImageOptimizerDefaultSettings{\n\t\tResizeFilter: resizeFilter,\n\t\tWebp:         false,\n\t\tWebpQuality:  fastly.ToValue(i.WebpQuality),\n\t\tJpegType:     jpegType,\n\t\tJpegQuality:  fastly.ToValue(i.JpegQuality),\n\t\tUpscale:      false,\n\t\tAllowVideo:   false,\n\t}, nil\n}\n\nfunc updateImageOptimizerDefaultsValidationError(_ context.Context, _ *fastly.UpdateImageOptimizerDefaultSettingsInput) (*fastly.ImageOptimizerDefaultSettings, error) {\n\treturn nil, errors.New(\"problem with field 'ResizeFilter, Webp, WebpQuality, JpegType, JpegQuality, Upscale, AllowVideo': at least one of the available optional fields is required\")\n}\n\nfunc updateImageOptimizerDefaultsError(_ context.Context, _ *fastly.UpdateImageOptimizerDefaultSettingsInput) (*fastly.ImageOptimizerDefaultSettings, error) {\n\treturn nil, errTest\n}\n\nfunc TestImageOptimizerDefaultsGet(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --service-id flag\",\n\t\t\tArgs:      \"--version 1\",\n\t\t\tWantError: \"error parsing arguments: required flag --service-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --version flag\",\n\t\t\tArgs:      \"--service-id 123\",\n\t\t\tWantError: \"error parsing arguments: required flag --version not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate successful get with no flags\",\n\t\t\tArgs: \"--service-id 123 --version 1\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:                       testutil.GetVersion,\n\t\t\t\tGetImageOptimizerDefaultSettingsFn: getImageOptimizerDefaultsOK,\n\t\t\t},\n\t\t\tWantOutput: \"Allow Video: false\\nJPEG Quality: 85\\nJPEG Type: auto\\nResize Filter: lanczos3\\nUpscale: false\\nWebP: false\\nWebP Quality: 85\\n\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate successful get with --json flag\",\n\t\t\tArgs: \"--service-id 123 --version 1 --json\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:                       testutil.GetVersion,\n\t\t\t\tGetImageOptimizerDefaultSettingsFn: getImageOptimizerDefaultsOK,\n\t\t\t},\n\t\t\tWantOutput: \"{\\n  \\\"resize_filter\\\": \\\"lanczos3\\\",\\n  \\\"webp\\\": false,\\n  \\\"webp_quality\\\": 85,\\n  \\\"jpeg_type\\\": \\\"auto\\\",\\n  \\\"jpeg_quality\\\": 85,\\n  \\\"upscale\\\": false,\\n  \\\"allow_video\\\": false\\n}\\n\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API error handling\",\n\t\t\tArgs: \"--service-id 123 --version 1\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:                       testutil.GetVersion,\n\t\t\t\tGetImageOptimizerDefaultSettingsFn: getImageOptimizerDefaultsError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"get\"}, scenarios)\n}\n\nfunc getImageOptimizerDefaultsOK(_ context.Context, _ *fastly.GetImageOptimizerDefaultSettingsInput) (*fastly.ImageOptimizerDefaultSettings, error) {\n\treturn &fastly.ImageOptimizerDefaultSettings{\n\t\tResizeFilter: \"lanczos3\",\n\t\tWebp:         false,\n\t\tWebpQuality:  85,\n\t\tJpegType:     \"auto\",\n\t\tJpegQuality:  85,\n\t\tUpscale:      false,\n\t\tAllowVideo:   false,\n\t}, nil\n}\n\nfunc getImageOptimizerDefaultsError(_ context.Context, _ *fastly.GetImageOptimizerDefaultSettingsInput) (*fastly.ImageOptimizerDefaultSettings, error) {\n\treturn nil, errTest\n}\n\nvar errTest = errors.New(\"fixture error\")\n"
  },
  {
    "path": "pkg/commands/service/imageoptimizerdefaults/root.go",
    "content": "package imageoptimizerdefaults\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"imageoptimizer\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manipulate Fastly service Image Optimizer default settings\")\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/service/imageoptimizerdefaults/update.go",
    "content": "package imageoptimizerdefaults\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// UpdateCommand calls the Fastly API to update Image Optimizer default settings for a service.\ntype UpdateCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tInput          fastly.UpdateImageOptimizerDefaultSettingsInput\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n\n\t// Image Optimizer setting flags\n\tallowVideo   argparser.OptionalString\n\tjpegQuality  argparser.OptionalInt\n\tjpegType     argparser.OptionalString\n\tresizeFilter argparser.OptionalString\n\tupscale      argparser.OptionalString\n\twebp         argparser.OptionalString\n\twebpQuality  argparser.OptionalInt\n}\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"update\", \"Update Image Optimizer default settings for a service\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t\tRequired:    true,\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional flags for Image Optimizer settings\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        \"allow-video\",\n\t\tDescription: \"Enables GIF to MP4 transformations on this service [true, false]\",\n\t\tAction:      c.allowVideo.Set,\n\t\tDst:         &c.allowVideo.Value,\n\t})\n\tc.RegisterFlagInt(argparser.IntFlagOpts{\n\t\tName:        \"jpeg-quality\",\n\t\tDescription: \"The default quality to use with JPEG output (1-100)\",\n\t\tDst:         &c.jpegQuality.Value,\n\t\tAction:      c.jpegQuality.Set,\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        \"jpeg-type\",\n\t\tDescription: \"The default type of JPEG output to use (auto, baseline, progressive)\",\n\t\tDst:         &c.jpegType.Value,\n\t\tAction:      c.jpegType.Set,\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        \"resize-filter\",\n\t\tDescription: \"The type of filter to use while resizing an image (lanczos3, lanczos2, bicubic, bilinear, nearest)\",\n\t\tDst:         &c.resizeFilter.Value,\n\t\tAction:      c.resizeFilter.Set,\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        \"upscale\",\n\t\tDescription: \"Whether or not we should allow output images to render at sizes larger than input [true, false]\",\n\t\tAction:      c.upscale.Set,\n\t\tDst:         &c.upscale.Value,\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        \"webp\",\n\t\tDescription: \"Controls whether or not to default to WebP output when the client supports it [true, false]\",\n\t\tAction:      c.webp.Set,\n\t\tDst:         &c.webp.Value,\n\t})\n\tc.RegisterFlagInt(argparser.IntFlagOpts{\n\t\tName:        \"webp-quality\",\n\t\tDescription: \"The default quality to use with WebP output (1-100)\",\n\t\tDst:         &c.webpQuality.Value,\n\t\tAction:      c.webpQuality.Set,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *UpdateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fsterr.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tc.Input.ServiceID = serviceID\n\tc.Input.ServiceVersion = fastly.ToValue(serviceVersion.Number)\n\n\t// Set optional fields only if they were provided\n\tif c.resizeFilter.WasSet {\n\t\t// Convert string to ImageOptimizerResizeFilter constant\n\t\tswitch c.resizeFilter.Value {\n\t\tcase \"lanczos3\":\n\t\t\tfilter := fastly.ImageOptimizerLanczos3\n\t\t\tc.Input.ResizeFilter = &filter\n\t\tcase \"lanczos2\":\n\t\t\tfilter := fastly.ImageOptimizerLanczos2\n\t\t\tc.Input.ResizeFilter = &filter\n\t\tcase \"bicubic\":\n\t\t\tfilter := fastly.ImageOptimizerBicubic\n\t\t\tc.Input.ResizeFilter = &filter\n\t\tcase \"bilinear\":\n\t\t\tfilter := fastly.ImageOptimizerBilinear\n\t\t\tc.Input.ResizeFilter = &filter\n\t\tcase \"nearest\":\n\t\t\tfilter := fastly.ImageOptimizerNearest\n\t\t\tc.Input.ResizeFilter = &filter\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"invalid resize filter: %s. Valid options: lanczos3, lanczos2, bicubic, bilinear, nearest\", c.resizeFilter.Value)\n\t\t}\n\t}\n\tif c.webp.WasSet {\n\t\twebp, err := argparser.ConvertBoolFromStringFlag(c.webp.Value, \"webp\")\n\t\tif err != nil {\n\t\t\tc.Globals.ErrLog.Add(err)\n\t\t\treturn err\n\t\t}\n\t\tc.Input.Webp = webp\n\t}\n\tif c.webpQuality.WasSet {\n\t\tc.Input.WebpQuality = &c.webpQuality.Value\n\t}\n\tif c.jpegType.WasSet {\n\t\t// Convert string to JPEG type constant\n\t\tswitch c.jpegType.Value {\n\t\tcase \"auto\":\n\t\t\tjpegType := fastly.ImageOptimizerAuto\n\t\t\tc.Input.JpegType = &jpegType\n\t\tcase \"baseline\":\n\t\t\tjpegType := fastly.ImageOptimizerBaseline\n\t\t\tc.Input.JpegType = &jpegType\n\t\tcase \"progressive\":\n\t\t\tjpegType := fastly.ImageOptimizerProgressive\n\t\t\tc.Input.JpegType = &jpegType\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"invalid jpeg type: %s. Valid options: auto, baseline, progressive\", c.jpegType.Value)\n\t\t}\n\t}\n\tif c.jpegQuality.WasSet {\n\t\tc.Input.JpegQuality = &c.jpegQuality.Value\n\t}\n\tif c.upscale.WasSet {\n\t\tupscale, err := argparser.ConvertBoolFromStringFlag(c.upscale.Value, \"upscale\")\n\t\tif err != nil {\n\t\t\tc.Globals.ErrLog.Add(err)\n\t\t\treturn err\n\t\t}\n\t\tc.Input.Upscale = upscale\n\t}\n\tif c.allowVideo.WasSet {\n\t\tallowVideo, err := argparser.ConvertBoolFromStringFlag(c.allowVideo.Value, \"allow-video\")\n\t\tif err != nil {\n\t\t\tc.Globals.ErrLog.Add(err)\n\t\t\treturn err\n\t\t}\n\t\tc.Input.AllowVideo = allowVideo\n\t}\n\n\to, err := c.Globals.APIClient.UpdateImageOptimizerDefaultSettings(context.TODO(), &c.Input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fastly.ToValue(serviceVersion.Number),\n\t\t})\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\tfmt.Fprintf(out, \"Updated Image Optimizer default settings for service %s (version %d)\\n\", serviceID, fastly.ToValue(serviceVersion.Number))\n\tfmt.Fprintf(out, \"\\nAllow Video: %t\\n\", o.AllowVideo)\n\tfmt.Fprintf(out, \"JPEG Quality: %d\\n\", o.JpegQuality)\n\tfmt.Fprintf(out, \"JPEG Type: %s\\n\", o.JpegType)\n\tfmt.Fprintf(out, \"Resize Filter: %s\\n\", o.ResizeFilter)\n\tfmt.Fprintf(out, \"Upscale: %t\\n\", o.Upscale)\n\tfmt.Fprintf(out, \"WebP: %t\\n\", o.Webp)\n\tfmt.Fprintf(out, \"WebP Quality: %d\\n\", o.WebpQuality)\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/list.go",
    "content": "package service\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"strconv\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n\t\"github.com/fastly/cli/pkg/time\"\n)\n\n// ListCommand calls the Fastly API to list services.\ntype ListCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tdirection     string\n\tpage, perPage int\n\tinput         fastly.GetServicesInput\n\tsort          string\n}\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"list\", \"List Fastly services\")\n\n\t// Optional.\n\tc.CmdClause.Flag(\"direction\", \"Direction in which to sort results\").Default(argparser.PaginationDirection[0]).HintOptions(argparser.PaginationDirection...).EnumVar(&c.direction, argparser.PaginationDirection...)\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\tc.CmdClause.Flag(\"page\", \"Page number of data set to fetch\").IntVar(&c.page)\n\tc.CmdClause.Flag(\"per-page\", \"Number of records per page\").IntVar(&c.perPage)\n\tc.CmdClause.Flag(\"sort\", \"Field on which to sort\").Default(\"created\").StringVar(&c.sort)\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *ListCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tc.input.Direction = &c.direction\n\tc.input.Page = &c.page\n\tc.input.PerPage = &c.perPage\n\tc.input.Sort = &c.sort\n\tpaginator := c.Globals.APIClient.GetServices(context.TODO(), &c.input)\n\n\tvar o []*fastly.Service\n\tfor paginator.HasNext() {\n\t\tdata, err := paginator.GetNext()\n\t\tif err != nil {\n\t\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\t\"Remaining Pages\": paginator.Remaining(),\n\t\t\t})\n\t\t\treturn err\n\t\t}\n\t\to = append(o, data...)\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\tif !c.Globals.Verbose() {\n\t\ttw := text.NewTable(out)\n\t\ttw.AddHeader(\"NAME\", \"ID\", \"TYPE\", \"ACTIVE VERSION\", \"LAST EDITED (UTC)\")\n\t\tfor _, service := range o {\n\t\t\tupdatedAt := \"n/a\"\n\t\t\tif service.UpdatedAt != nil {\n\t\t\t\tupdatedAt = service.UpdatedAt.UTC().Format(time.Format)\n\t\t\t}\n\n\t\t\tactiveVersion := strconv.Itoa(fastly.ToValue(service.ActiveVersion))\n\t\t\tfor _, v := range service.Versions {\n\t\t\t\tif fastly.ToValue(v.Number) == fastly.ToValue(service.ActiveVersion) && !fastly.ToValue(v.Active) {\n\t\t\t\t\tactiveVersion = \"n/a\"\n\t\t\t\t}\n\t\t\t}\n\n\t\t\ttw.AddLine(\n\t\t\t\tfastly.ToValue(service.Name),\n\t\t\t\tfastly.ToValue(service.ServiceID),\n\t\t\t\tfastly.ToValue(service.Type),\n\t\t\t\tactiveVersion,\n\t\t\t\tupdatedAt,\n\t\t\t)\n\t\t}\n\t\ttw.Print()\n\t\treturn nil\n\t}\n\n\tfor i, service := range o {\n\t\tfmt.Fprintf(out, \"Service %d/%d\\n\", i+1, len(o))\n\t\ttext.PrintService(out, \"\\t\", service)\n\t\tfmt.Fprintln(out)\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/azureblob/azureblob_integration_test.go",
    "content": "package azureblob_test\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/mock\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n\n\troot \"github.com/fastly/cli/pkg/commands/service\"\n\tparent \"github.com/fastly/cli/pkg/commands/service/logging\"\n\tsub \"github.com/fastly/cli/pkg/commands/service/logging/azureblob\"\n)\n\nfunc TestBlobStorageCreate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name log --account-name account --container log --sas-token abc --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:        testutil.GetVersion,\n\t\t\t\tCloneVersionFn:      testutil.CloneVersionResult(4),\n\t\t\t\tCreateBlobStorageFn: createBlobStorageOK,\n\t\t\t},\n\t\t\tWantOutput: \"Created Azure Blob Storage logging endpoint log (service 123 version 4)\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name log --account-name account --container log --sas-token abc --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:        testutil.GetVersion,\n\t\t\t\tCloneVersionFn:      testutil.CloneVersionResult(4),\n\t\t\t\tCreateBlobStorageFn: createBlobStorageError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name log --account-name account --container log --sas-token abc --compression-codec zstd --gzip-level 9 --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:        testutil.GetVersion,\n\t\t\t\tCloneVersionFn:      testutil.CloneVersionResult(4),\n\t\t\t\tCreateBlobStorageFn: createBlobStorageError,\n\t\t\t},\n\t\t\tWantError: \"error parsing arguments: the --compression-codec flag is mutually exclusive with the --gzip-level flag\",\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"create\"}, scenarios)\n}\n\nfunc TestBlobStorageList(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:       testutil.GetVersion,\n\t\t\t\tListBlobStoragesFn: listBlobStoragesOK,\n\t\t\t},\n\t\t\tWantOutput: listBlobStoragesShortOutput,\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --verbose\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:       testutil.GetVersion,\n\t\t\t\tListBlobStoragesFn: listBlobStoragesOK,\n\t\t\t},\n\t\t\tWantOutput: listBlobStoragesVerboseOutput,\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 -v\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:       testutil.GetVersion,\n\t\t\t\tListBlobStoragesFn: listBlobStoragesOK,\n\t\t\t},\n\t\t\tWantOutput: listBlobStoragesVerboseOutput,\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:       testutil.GetVersion,\n\t\t\t\tListBlobStoragesFn: listBlobStoragesError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"list\"}, scenarios)\n}\n\nfunc TestBlobStorageDescribe(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"--service-id 123 --version 1\",\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:     testutil.GetVersion,\n\t\t\t\tGetBlobStorageFn: getBlobStorageError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:     testutil.GetVersion,\n\t\t\t\tGetBlobStorageFn: getBlobStorageOK,\n\t\t\t},\n\t\t\tWantOutput: describeBlobStorageOutput,\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"describe\"}, scenarios)\n}\n\nfunc TestBlobStorageUpdate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"--service-id 123 --version 1 --new-name log\",\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs --new-name log --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:        testutil.GetVersion,\n\t\t\t\tCloneVersionFn:      testutil.CloneVersionResult(4),\n\t\t\t\tUpdateBlobStorageFn: updateBlobStorageError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs --new-name log --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:        testutil.GetVersion,\n\t\t\t\tCloneVersionFn:      testutil.CloneVersionResult(4),\n\t\t\t\tUpdateBlobStorageFn: updateBlobStorageOK,\n\t\t\t},\n\t\t\tWantOutput: \"Updated Azure Blob Storage logging endpoint log (service 123 version 4)\",\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"update\"}, scenarios)\n}\n\nfunc TestBlobStorageDelete(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"--service-id 123 --version 1\",\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:        testutil.GetVersion,\n\t\t\t\tCloneVersionFn:      testutil.CloneVersionResult(4),\n\t\t\t\tDeleteBlobStorageFn: deleteBlobStorageError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:        testutil.GetVersion,\n\t\t\t\tCloneVersionFn:      testutil.CloneVersionResult(4),\n\t\t\t\tDeleteBlobStorageFn: deleteBlobStorageOK,\n\t\t\t},\n\t\t\tWantOutput: \"Deleted Azure Blob Storage logging endpoint logs (service 123 version 4)\",\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"delete\"}, scenarios)\n}\n\nvar errTest = errors.New(\"fixture error\")\n\nfunc createBlobStorageOK(_ context.Context, i *fastly.CreateBlobStorageInput) (*fastly.BlobStorage, error) {\n\ts := fastly.BlobStorage{\n\t\tServiceID:         fastly.ToPointer(i.ServiceID),\n\t\tServiceVersion:    fastly.ToPointer(i.ServiceVersion),\n\t\tName:              fastly.ToPointer(\"log\"),\n\t\tPath:              fastly.ToPointer(\"/logs\"),\n\t\tAccountName:       fastly.ToPointer(\"account\"),\n\t\tContainer:         fastly.ToPointer(\"container\"),\n\t\tSASToken:          fastly.ToPointer(\"token\"),\n\t\tPeriod:            fastly.ToPointer(3600),\n\t\tTimestampFormat:   fastly.ToPointer(\"%Y-%m-%dT%H:%M:%S.000\"),\n\t\tPublicKey:         fastly.ToPointer(pgpPublicKey()),\n\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\tFormatVersion:     fastly.ToPointer(2),\n\t\tMessageType:       fastly.ToPointer(\"classic\"),\n\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\tCompressionCodec:  fastly.ToPointer(\"zstd\"),\n\t}\n\n\treturn &s, nil\n}\n\nfunc createBlobStorageError(_ context.Context, _ *fastly.CreateBlobStorageInput) (*fastly.BlobStorage, error) {\n\treturn nil, errTest\n}\n\nfunc listBlobStoragesOK(_ context.Context, i *fastly.ListBlobStoragesInput) ([]*fastly.BlobStorage, error) {\n\treturn []*fastly.BlobStorage{\n\t\t{\n\t\t\tServiceID:         fastly.ToPointer(i.ServiceID),\n\t\t\tServiceVersion:    fastly.ToPointer(i.ServiceVersion),\n\t\t\tName:              fastly.ToPointer(\"logs\"),\n\t\t\tPath:              fastly.ToPointer(\"/logs\"),\n\t\t\tAccountName:       fastly.ToPointer(\"account\"),\n\t\t\tContainer:         fastly.ToPointer(\"container\"),\n\t\t\tSASToken:          fastly.ToPointer(\"token\"),\n\t\t\tPeriod:            fastly.ToPointer(3600),\n\t\t\tTimestampFormat:   fastly.ToPointer(\"%Y-%m-%dT%H:%M:%S.000\"),\n\t\t\tPublicKey:         fastly.ToPointer(pgpPublicKey()),\n\t\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\t\tFormatVersion:     fastly.ToPointer(2),\n\t\t\tMessageType:       fastly.ToPointer(\"classic\"),\n\t\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\t\tCompressionCodec:  fastly.ToPointer(\"zstd\"),\n\t\t\tProcessingRegion:  fastly.ToPointer(\"us\"),\n\t\t},\n\t\t{\n\t\t\tServiceID:         fastly.ToPointer(i.ServiceID),\n\t\t\tServiceVersion:    fastly.ToPointer(i.ServiceVersion),\n\t\t\tName:              fastly.ToPointer(\"analytics\"),\n\t\t\tAccountName:       fastly.ToPointer(\"account\"),\n\t\t\tContainer:         fastly.ToPointer(\"analytics\"),\n\t\t\tSASToken:          fastly.ToPointer(\"token\"),\n\t\t\tPath:              fastly.ToPointer(\"/logs\"),\n\t\t\tPeriod:            fastly.ToPointer(86400),\n\t\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\t\tFormatVersion:     fastly.ToPointer(2),\n\t\t\tMessageType:       fastly.ToPointer(\"classic\"),\n\t\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\t\tTimestampFormat:   fastly.ToPointer(\"%Y-%m-%dT%H:%M:%S.000\"),\n\t\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\t\tPublicKey:         fastly.ToPointer(pgpPublicKey()),\n\t\t\tCompressionCodec:  fastly.ToPointer(\"zstd\"),\n\t\t\tProcessingRegion:  fastly.ToPointer(\"us\"),\n\t\t},\n\t}, nil\n}\n\nfunc listBlobStoragesError(_ context.Context, _ *fastly.ListBlobStoragesInput) ([]*fastly.BlobStorage, error) {\n\treturn nil, errTest\n}\n\nvar listBlobStoragesShortOutput = strings.TrimSpace(`\nSERVICE  VERSION  NAME\n123      1        logs\n123      1        analytics\n`) + \"\\n\"\n\nvar listBlobStoragesVerboseOutput = strings.TrimSpace(`\nFastly API endpoint: https://api.fastly.com\nFastly API token provided via config file (auth: user)\n\nService ID (via --service-id): 123\n\nVersion: 1\n\tBlobStorage 1/2\n\t\tService ID: 123\n\t\tVersion: 1\n\t\tName: logs\n\t\tContainer: container\n\t\tAccount name: account\n\t\tSAS token: token\n\t\tPath: /logs\n\t\tPeriod: 3600\n\t\tGZip level: 0\n\t\tFormat: %h %l %u %t \"%r\" %>s %b\n\t\tFormat version: 2\n\t\tResponse condition: Prevent default logging\n\t\tMessage type: classic\n\t\tTimestamp format: %Y-%m-%dT%H:%M:%S.000\n\t\tPlacement: none\n\t\tPublic key: `+pgpPublicKey()+`\n\t\tFile max bytes: 0\n\t\tCompression codec: zstd\n\t\tProcessing region: us\n\tBlobStorage 2/2\n\t\tService ID: 123\n\t\tVersion: 1\n\t\tName: analytics\n\t\tContainer: analytics\n\t\tAccount name: account\n\t\tSAS token: token\n\t\tPath: /logs\n\t\tPeriod: 86400\n\t\tGZip level: 0\n\t\tFormat: %h %l %u %t \"%r\" %>s %b\n\t\tFormat version: 2\n\t\tResponse condition: Prevent default logging\n\t\tMessage type: classic\n\t\tTimestamp format: %Y-%m-%dT%H:%M:%S.000\n\t\tPlacement: none\n\t\tPublic key: `+pgpPublicKey()+`\n\t\tFile max bytes: 0\n\t\tCompression codec: zstd\n\t\tProcessing region: us\n`) + \"\\n\\n\"\n\nfunc getBlobStorageOK(_ context.Context, i *fastly.GetBlobStorageInput) (*fastly.BlobStorage, error) {\n\treturn &fastly.BlobStorage{\n\t\tServiceID:         fastly.ToPointer(i.ServiceID),\n\t\tServiceVersion:    fastly.ToPointer(i.ServiceVersion),\n\t\tName:              fastly.ToPointer(\"logs\"),\n\t\tContainer:         fastly.ToPointer(\"container\"),\n\t\tAccountName:       fastly.ToPointer(\"account\"),\n\t\tSASToken:          fastly.ToPointer(\"token\"),\n\t\tPath:              fastly.ToPointer(\"/logs\"),\n\t\tPeriod:            fastly.ToPointer(3600),\n\t\tGzipLevel:         fastly.ToPointer(0),\n\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\tFormatVersion:     fastly.ToPointer(2),\n\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\tMessageType:       fastly.ToPointer(\"classic\"),\n\t\tTimestampFormat:   fastly.ToPointer(\"%Y-%m-%dT%H:%M:%S.000\"),\n\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\tProcessingRegion:  fastly.ToPointer(\"us\"),\n\t\tPublicKey:         fastly.ToPointer(pgpPublicKey()),\n\t\tCompressionCodec:  fastly.ToPointer(\"zstd\"),\n\t}, nil\n}\n\nfunc getBlobStorageError(_ context.Context, _ *fastly.GetBlobStorageInput) (*fastly.BlobStorage, error) {\n\treturn nil, errTest\n}\n\nvar describeBlobStorageOutput = \"\\n\" + strings.TrimSpace(`\nAccount name: account\nCompression codec: zstd\nContainer: container\nFile max bytes: 0\nFormat: %h %l %u %t \"%r\" %>s %b\nFormat version: 2\nGZip level: 0\nMessage type: classic\nName: logs\nPath: /logs\nPeriod: 3600\nPlacement: none\nProcessing region: us\nPublic key: `+pgpPublicKey()+`\nResponse condition: Prevent default logging\nSAS token: token\nService ID: 123\nTimestamp format: %Y-%m-%dT%H:%M:%S.000\nVersion: 1\n`) + \"\\n\"\n\nfunc updateBlobStorageOK(_ context.Context, i *fastly.UpdateBlobStorageInput) (*fastly.BlobStorage, error) {\n\treturn &fastly.BlobStorage{\n\t\tServiceID:         fastly.ToPointer(i.ServiceID),\n\t\tServiceVersion:    fastly.ToPointer(i.ServiceVersion),\n\t\tName:              fastly.ToPointer(\"log\"),\n\t\tContainer:         fastly.ToPointer(\"container\"),\n\t\tAccountName:       fastly.ToPointer(\"account\"),\n\t\tSASToken:          fastly.ToPointer(\"token\"),\n\t\tPath:              fastly.ToPointer(\"/logs\"),\n\t\tPeriod:            fastly.ToPointer(3600),\n\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\tFormatVersion:     fastly.ToPointer(2),\n\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\tMessageType:       fastly.ToPointer(\"classic\"),\n\t\tTimestampFormat:   fastly.ToPointer(\"%Y-%m-%dT%H:%M:%S.000\"),\n\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\tPublicKey:         fastly.ToPointer(pgpPublicKey()),\n\t\tCompressionCodec:  fastly.ToPointer(\"zstd\"),\n\t}, nil\n}\n\nfunc updateBlobStorageError(_ context.Context, _ *fastly.UpdateBlobStorageInput) (*fastly.BlobStorage, error) {\n\treturn nil, errTest\n}\n\nfunc deleteBlobStorageOK(_ context.Context, _ *fastly.DeleteBlobStorageInput) error {\n\treturn nil\n}\n\nfunc deleteBlobStorageError(_ context.Context, _ *fastly.DeleteBlobStorageInput) error {\n\treturn errTest\n}\n\n// pgpPublicKey returns a PEM encoded PGP public key suitable for testing.\nfunc pgpPublicKey() string {\n\treturn strings.TrimSpace(`-----BEGIN PGP PUBLIC KEY BLOCK-----\nmQENBFyUD8sBCACyFnB39AuuTygseek+eA4fo0cgwva6/FSjnWq7riouQee8GgQ/\nibXTRyv4iVlwI12GswvMTIy7zNvs1R54i0qvsLr+IZ4GVGJqs6ZJnvQcqe3xPoR4\n8AnBfw90o32r/LuHf6QCJXi+AEu35koNlNAvLJ2B+KACaNB7N0EeWmqpV/1V2k9p\nlDYk+th7LcCuaFNGqKS/PrMnnMqR6VDLCjHhNx4KR79b0Twm/2qp6an3hyNRu8Gn\ndwxpf1/BUu3JWf+LqkN4Y3mbOmSUL3MaJNvyQguUzTfS0P0uGuBDHrJCVkMZCzDB\n89ag55jCPHyGeHBTd02gHMWzsg3WMBWvCsrzABEBAAG0JXRlcnJhZm9ybSAodGVz\ndCkgPHRlc3RAdGVycmFmb3JtLmNvbT6JAU4EEwEIADgWIQSHYyc6Kj9l6HzQsau6\nvFFc9jxV/wUCXJQPywIbAwULCQgHAgYVCgkICwIEFgIDAQIeAQIXgAAKCRC6vFFc\n9jxV/815CAClb32OxV7wG01yF97TzlyTl8TnvjMtoG29Mw4nSyg+mjM3b8N7iXm9\nOLX59fbDAWtBSldSZE22RXd3CvlFOG/EnKBXSjBtEqfyxYSnyOPkMPBYWGL/ApkX\nSvPYJ4LKdvipYToKFh3y9kk2gk1DcDBDyaaHvR+3rv1u3aoy7/s2EltAfDS3ZQIq\n7/cWTLJml/lleeB/Y6rPj8xqeCYhE5ahw9gsV/Mdqatl24V9Tks30iijx0Hhw+Gx\nkATUikMGr2GDVqoIRga5kXI7CzYff4rkc0Twn47fMHHHe/KY9M2yVnMHUXmAZwbG\nM1cMI/NH1DjevCKdGBLcRJlhuLPKF/anuQENBFyUD8sBCADIpd7r7GuPd6n/Ikxe\nu6h7umV6IIPoAm88xCYpTbSZiaK30Svh6Ywra9jfE2KlU9o6Y/art8ip0VJ3m07L\n4RSfSpnzqgSwdjSq5hNour2Fo/BzYhK7yaz2AzVSbe33R0+RYhb4b/6N+bKbjwGF\nftCsqVFMH+PyvYkLbvxyQrHlA9woAZaNThI1ztO5rGSnGUR8xt84eup28WIFKg0K\nUEGUcTzz+8QGAwAra+0ewPXo/AkO+8BvZjDidP417u6gpBHOJ9qYIcO9FxHeqFyu\nYrjlrxowEgXn5wO8xuNz6Vu1vhHGDHGDsRbZF8pv1d5O+0F1G7ttZ2GRRgVBZPwi\nkiyRABEBAAGJATYEGAEIACAWIQSHYyc6Kj9l6HzQsau6vFFc9jxV/wUCXJQPywIb\nDAAKCRC6vFFc9jxV/9YOCACe8qmOSnKQpQfW+PqYOqo3dt7JyweTs3FkD6NT8Zml\ndYy/vkstbTjPpX6aTvUZjkb46BVi7AOneVHpD5GBqvRsZ9iVgDYHaehmLCdKiG5L\n3Tp90NN+QY5WDbsGmsyk6+6ZMYejb4qYfweQeduOj27aavCJdLkCYMoRKfcFYI8c\nFaNmEfKKy/r1PO20NXEG6t9t05K/frHy6ZG8bCNYdpagfFVot47r9JaQqWlTNtIR\n5+zkkSq/eG9BEtRij3a6cTdQbktdBzx2KBeI0PYc1vlZR0LpuFKZqY9vlE6vTGLR\nwMfrTEOvx0NxUM3rpaCgEmuWbB1G1Hu371oyr4srrr+N\n=28dr\n-----END PGP PUBLIC KEY BLOCK-----\n`)\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/azureblob/azureblob_test.go",
    "content": "package azureblob_test\n\nimport (\n\t\"bytes\"\n\t\"testing\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/service/logging/azureblob\"\n\t\"github.com/fastly/cli/pkg/config\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/manifest\"\n\t\"github.com/fastly/cli/pkg/mock\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n)\n\nfunc TestCreateBlobStorageInput(t *testing.T) {\n\tfor _, testcase := range []struct {\n\t\tname      string\n\t\tcmd       *azureblob.CreateCommand\n\t\twant      *fastly.CreateBlobStorageInput\n\t\twantError string\n\t}{\n\t\t{\n\t\t\tname: \"required values set flag serviceID\",\n\t\t\tcmd:  createCommandRequired(),\n\t\t\twant: &fastly.CreateBlobStorageInput{\n\t\t\t\tServiceID:      \"123\",\n\t\t\t\tServiceVersion: 4,\n\t\t\t\tName:           fastly.ToPointer(\"logs\"),\n\t\t\t\tAccountName:    fastly.ToPointer(\"account\"),\n\t\t\t\tContainer:      fastly.ToPointer(\"container\"),\n\t\t\t\tSASToken:       fastly.ToPointer(\"token\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"all values set flag serviceID\",\n\t\t\tcmd:  createCommandAll(),\n\t\t\twant: &fastly.CreateBlobStorageInput{\n\t\t\t\tServiceID:         \"123\",\n\t\t\t\tServiceVersion:    4,\n\t\t\t\tName:              fastly.ToPointer(\"logs\"),\n\t\t\t\tContainer:         fastly.ToPointer(\"container\"),\n\t\t\t\tAccountName:       fastly.ToPointer(\"account\"),\n\t\t\t\tSASToken:          fastly.ToPointer(\"token\"),\n\t\t\t\tPath:              fastly.ToPointer(\"/log\"),\n\t\t\t\tPeriod:            fastly.ToPointer(3600),\n\t\t\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\t\t\tMessageType:       fastly.ToPointer(\"classic\"),\n\t\t\t\tFormatVersion:     fastly.ToPointer(2),\n\t\t\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\t\t\tTimestampFormat:   fastly.ToPointer(\"%Y-%m-%dT%H:%M:%S.000\"),\n\t\t\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\t\t\tProcessingRegion:  fastly.ToPointer(\"eu\"),\n\t\t\t\tPublicKey:         fastly.ToPointer(pgpPublicKey()),\n\t\t\t\tCompressionCodec:  fastly.ToPointer(\"zstd\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:      \"error missing serviceID\",\n\t\t\tcmd:       createCommandMissingServiceID(),\n\t\t\twant:      nil,\n\t\t\twantError: errors.ErrNoServiceID.Error(),\n\t\t},\n\t} {\n\t\tt.Run(testcase.name, func(t *testing.T) {\n\t\t\tif testcase.wantError == errors.ErrNoServiceID.Error() {\n\t\t\t\tt.Setenv(\"FASTLY_SERVICE_ID\", \"\")\n\t\t\t}\n\t\t\tvar bs []byte\n\t\t\tout := bytes.NewBuffer(bs)\n\t\t\tverboseMode := true\n\n\t\t\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\t\t\tAutoCloneFlag:      testcase.cmd.AutoClone,\n\t\t\t\tAPIClient:          testcase.cmd.Globals.APIClient,\n\t\t\t\tManifest:           testcase.cmd.Manifest,\n\t\t\t\tOut:                out,\n\t\t\t\tServiceVersionFlag: testcase.cmd.ServiceVersion,\n\t\t\t\tVerboseMode:        verboseMode,\n\t\t\t})\n\n\t\t\tswitch {\n\t\t\tcase err != nil && testcase.wantError == \"\":\n\t\t\t\tt.Fatalf(\"unexpected error getting service details: %v\", err)\n\t\t\t\treturn\n\t\t\tcase err != nil && testcase.wantError != \"\":\n\t\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\t\treturn\n\t\t\tcase err == nil && testcase.wantError != \"\":\n\t\t\t\tt.Fatalf(\"expected error, have nil (service details: %s, %d)\", serviceID, serviceVersion.Number)\n\t\t\tcase err == nil && testcase.wantError == \"\":\n\t\t\t\thave, err := testcase.cmd.ConstructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\t\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\t\ttestutil.AssertEqual(t, testcase.want, have)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestUpdateBlobStorageInput(t *testing.T) {\n\tscenarios := []struct {\n\t\tname      string\n\t\tcmd       *azureblob.UpdateCommand\n\t\tapi       mock.API\n\t\twant      *fastly.UpdateBlobStorageInput\n\t\twantError string\n\t}{\n\t\t{\n\t\t\tname: \"all values set flag serviceID\",\n\t\t\tcmd:  updateCommandAll(),\n\t\t\tapi: mock.API{\n\t\t\t\tGetVersionFn:     testutil.GetVersion,\n\t\t\t\tCloneVersionFn:   testutil.CloneVersionResult(4),\n\t\t\t\tGetBlobStorageFn: getBlobStorageOK,\n\t\t\t},\n\t\t\twant: &fastly.UpdateBlobStorageInput{\n\t\t\t\tServiceID:         \"123\",\n\t\t\t\tServiceVersion:    4,\n\t\t\t\tName:              \"logs\",\n\t\t\t\tNewName:           fastly.ToPointer(\"new1\"),\n\t\t\t\tContainer:         fastly.ToPointer(\"new2\"),\n\t\t\t\tAccountName:       fastly.ToPointer(\"new3\"),\n\t\t\t\tSASToken:          fastly.ToPointer(\"new4\"),\n\t\t\t\tPath:              fastly.ToPointer(\"new5\"),\n\t\t\t\tPeriod:            fastly.ToPointer(3601),\n\t\t\t\tGzipLevel:         fastly.ToPointer(0),\n\t\t\t\tFormat:            fastly.ToPointer(\"new6\"),\n\t\t\t\tFormatVersion:     fastly.ToPointer(3),\n\t\t\t\tResponseCondition: fastly.ToPointer(\"new7\"),\n\t\t\t\tMessageType:       fastly.ToPointer(\"new8\"),\n\t\t\t\tTimestampFormat:   fastly.ToPointer(\"new9\"),\n\t\t\t\tPlacement:         fastly.ToPointer(\"new10\"),\n\t\t\t\tProcessingRegion:  fastly.ToPointer(\"eu\"),\n\t\t\t\tPublicKey:         fastly.ToPointer(\"new11\"),\n\t\t\t\tCompressionCodec:  fastly.ToPointer(\"new12\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"no updates\",\n\t\t\tcmd:  updateCommandNoUpdates(),\n\t\t\tapi: mock.API{\n\t\t\t\tGetVersionFn:     testutil.GetVersion,\n\t\t\t\tCloneVersionFn:   testutil.CloneVersionResult(4),\n\t\t\t\tGetBlobStorageFn: getBlobStorageOK,\n\t\t\t},\n\t\t\twant: &fastly.UpdateBlobStorageInput{\n\t\t\t\tServiceID:      \"123\",\n\t\t\t\tServiceVersion: 4,\n\t\t\t\tName:           \"logs\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:      \"error missing serviceID\",\n\t\t\tcmd:       updateCommandMissingServiceID(),\n\t\t\twant:      nil,\n\t\t\twantError: errors.ErrNoServiceID.Error(),\n\t\t},\n\t}\n\tfor testcaseIdx := range scenarios {\n\t\ttestcase := &scenarios[testcaseIdx]\n\t\tt.Run(testcase.name, func(t *testing.T) {\n\t\t\tif testcase.wantError == errors.ErrNoServiceID.Error() {\n\t\t\t\tt.Setenv(\"FASTLY_SERVICE_ID\", \"\")\n\t\t\t}\n\t\t\ttestcase.cmd.Globals.APIClient = testcase.api\n\n\t\t\tvar bs []byte\n\t\t\tout := bytes.NewBuffer(bs)\n\t\t\tverboseMode := true\n\n\t\t\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\t\t\tAutoCloneFlag:      testcase.cmd.AutoClone,\n\t\t\t\tAPIClient:          testcase.api,\n\t\t\t\tManifest:           testcase.cmd.Manifest,\n\t\t\t\tOut:                out,\n\t\t\t\tServiceVersionFlag: testcase.cmd.ServiceVersion,\n\t\t\t\tVerboseMode:        verboseMode,\n\t\t\t})\n\n\t\t\tswitch {\n\t\t\tcase err != nil && testcase.wantError == \"\":\n\t\t\t\tt.Fatalf(\"unexpected error getting service details: %v\", err)\n\t\t\t\treturn\n\t\t\tcase err != nil && testcase.wantError != \"\":\n\t\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\t\treturn\n\t\t\tcase err == nil && testcase.wantError != \"\":\n\t\t\t\tt.Fatalf(\"expected error, have nil (service details: %s, %d)\", serviceID, serviceVersion.Number)\n\t\t\tcase err == nil && testcase.wantError == \"\":\n\t\t\t\thave, err := testcase.cmd.ConstructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\t\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\t\ttestutil.AssertEqual(t, testcase.want, have)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc createCommandRequired() *azureblob.CreateCommand {\n\tvar b bytes.Buffer\n\n\tg := global.Data{\n\t\tConfig: config.File{},\n\t\tEnv:    config.Environment{},\n\t\tOutput: &b,\n\t}\n\t// TODO: make consistent (in all other logging files) with syslog_test which\n\t// uses a testcase.api field to assign the mock API to the global client.\n\tg.APIClient, _ = mock.APIClient(mock.API{\n\t\tGetVersionFn:   testutil.GetVersion,\n\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t})(\"token\", \"endpoint\", false)\n\n\treturn &azureblob.CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: &g,\n\t\t},\n\t\tManifest: manifest.Data{\n\t\t\tFlag: manifest.Flag{\n\t\t\t\tServiceID: \"123\",\n\t\t\t},\n\t\t},\n\t\tServiceVersion: argparser.OptionalServiceVersion{\n\t\t\tOptionalString: argparser.OptionalString{Value: \"1\"},\n\t\t},\n\t\tAutoClone: argparser.OptionalAutoClone{\n\t\t\tOptionalBool: argparser.OptionalBool{\n\t\t\t\tOptional: argparser.Optional{\n\t\t\t\t\tWasSet: true,\n\t\t\t\t},\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t},\n\t\tEndpointName: argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"logs\"},\n\t\tContainer:    argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"container\"},\n\t\tAccountName:  argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"account\"},\n\t\tSASToken:     argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"token\"},\n\t}\n}\n\nfunc createCommandAll() *azureblob.CreateCommand {\n\tvar b bytes.Buffer\n\n\tg := global.Data{\n\t\tConfig: config.File{},\n\t\tEnv:    config.Environment{},\n\t\tOutput: &b,\n\t}\n\tg.APIClient, _ = mock.APIClient(mock.API{\n\t\tGetVersionFn:   testutil.GetVersion,\n\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t})(\"token\", \"endpoint\", false)\n\n\treturn &azureblob.CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: &g,\n\t\t},\n\t\tManifest: manifest.Data{\n\t\t\tFlag: manifest.Flag{\n\t\t\t\tServiceID: \"123\",\n\t\t\t},\n\t\t},\n\t\tServiceVersion: argparser.OptionalServiceVersion{\n\t\t\tOptionalString: argparser.OptionalString{Value: \"1\"},\n\t\t},\n\t\tAutoClone: argparser.OptionalAutoClone{\n\t\t\tOptionalBool: argparser.OptionalBool{\n\t\t\t\tOptional: argparser.Optional{\n\t\t\t\t\tWasSet: true,\n\t\t\t\t},\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t},\n\t\tEndpointName:      argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"logs\"},\n\t\tContainer:         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"container\"},\n\t\tAccountName:       argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"account\"},\n\t\tSASToken:          argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"token\"},\n\t\tPath:              argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"/log\"},\n\t\tPeriod:            argparser.OptionalInt{Optional: argparser.Optional{WasSet: true}, Value: 3600},\n\t\tFormat:            argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: `%h %l %u %t \"%r\" %>s %b`},\n\t\tFormatVersion:     argparser.OptionalInt{Optional: argparser.Optional{WasSet: true}, Value: 2},\n\t\tResponseCondition: argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"Prevent default logging\"},\n\t\tTimestampFormat:   argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"%Y-%m-%dT%H:%M:%S.000\"},\n\t\tPlacement:         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"none\"},\n\t\tProcessingRegion:  argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"eu\"},\n\t\tMessageType:       argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"classic\"},\n\t\tPublicKey:         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: pgpPublicKey()},\n\t\tCompressionCodec:  argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"zstd\"},\n\t}\n}\n\nfunc createCommandMissingServiceID() *azureblob.CreateCommand {\n\tres := createCommandAll()\n\tres.Manifest = manifest.Data{}\n\tres.ServiceVersion = argparser.OptionalServiceVersion{}\n\treturn res\n}\n\nfunc updateCommandNoUpdates() *azureblob.UpdateCommand {\n\tvar b bytes.Buffer\n\n\tg := global.Data{\n\t\tConfig: config.File{},\n\t\tEnv:    config.Environment{},\n\t\tOutput: &b,\n\t}\n\n\treturn &azureblob.UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: &g,\n\t\t},\n\t\tManifest: manifest.Data{\n\t\t\tFlag: manifest.Flag{\n\t\t\t\tServiceID: \"123\",\n\t\t\t},\n\t\t},\n\t\tEndpointName: \"logs\",\n\t\tServiceVersion: argparser.OptionalServiceVersion{\n\t\t\tOptionalString: argparser.OptionalString{Value: \"1\"},\n\t\t},\n\t\tAutoClone: argparser.OptionalAutoClone{\n\t\t\tOptionalBool: argparser.OptionalBool{\n\t\t\t\tOptional: argparser.Optional{\n\t\t\t\t\tWasSet: true,\n\t\t\t\t},\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t},\n\t}\n}\n\nfunc updateCommandAll() *azureblob.UpdateCommand {\n\tvar b bytes.Buffer\n\n\tg := global.Data{\n\t\tConfig: config.File{},\n\t\tEnv:    config.Environment{},\n\t\tOutput: &b,\n\t}\n\n\treturn &azureblob.UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: &g,\n\t\t},\n\t\tManifest: manifest.Data{\n\t\t\tFlag: manifest.Flag{\n\t\t\t\tServiceID: \"123\",\n\t\t\t},\n\t\t},\n\t\tEndpointName: \"logs\",\n\t\tServiceVersion: argparser.OptionalServiceVersion{\n\t\t\tOptionalString: argparser.OptionalString{Value: \"1\"},\n\t\t},\n\t\tAutoClone: argparser.OptionalAutoClone{\n\t\t\tOptionalBool: argparser.OptionalBool{\n\t\t\t\tOptional: argparser.Optional{\n\t\t\t\t\tWasSet: true,\n\t\t\t\t},\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t},\n\t\tNewName:           argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new1\"},\n\t\tContainer:         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new2\"},\n\t\tAccountName:       argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new3\"},\n\t\tSASToken:          argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new4\"},\n\t\tPath:              argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new5\"},\n\t\tPeriod:            argparser.OptionalInt{Optional: argparser.Optional{WasSet: true}, Value: 3601},\n\t\tGzipLevel:         argparser.OptionalInt{Optional: argparser.Optional{WasSet: true}, Value: 0},\n\t\tFormat:            argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new6\"},\n\t\tFormatVersion:     argparser.OptionalInt{Optional: argparser.Optional{WasSet: true}, Value: 3},\n\t\tResponseCondition: argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new7\"},\n\t\tMessageType:       argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new8\"},\n\t\tTimestampFormat:   argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new9\"},\n\t\tPlacement:         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new10\"},\n\t\tProcessingRegion:  argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"eu\"},\n\t\tPublicKey:         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new11\"},\n\t\tCompressionCodec:  argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new12\"},\n\t}\n}\n\nfunc updateCommandMissingServiceID() *azureblob.UpdateCommand {\n\tres := updateCommandAll()\n\tres.Manifest = manifest.Data{}\n\tres.ServiceVersion = argparser.OptionalServiceVersion{}\n\treturn res\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/azureblob/create.go",
    "content": "package azureblob\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/service/logging/logflags\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/manifest\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// CreateCommand calls the Fastly API to create an Azure Blob Storage logging endpoint.\ntype CreateCommand struct {\n\targparser.Base\n\tManifest manifest.Data\n\n\t// Required.\n\tServiceName    argparser.OptionalServiceNameID\n\tServiceVersion argparser.OptionalServiceVersion\n\n\t// Optional.\n\tEndpointName      argparser.OptionalString\n\tContainer         argparser.OptionalString\n\tAccountName       argparser.OptionalString\n\tSASToken          argparser.OptionalString\n\tAutoClone         argparser.OptionalAutoClone\n\tPath              argparser.OptionalString\n\tPeriod            argparser.OptionalInt\n\tGzipLevel         argparser.OptionalInt\n\tMessageType       argparser.OptionalString\n\tFormat            argparser.OptionalString\n\tFormatVersion     argparser.OptionalInt\n\tResponseCondition argparser.OptionalString\n\tTimestampFormat   argparser.OptionalString\n\tPlacement         argparser.OptionalString\n\tProcessingRegion  argparser.OptionalString\n\tPublicKey         argparser.OptionalString\n\tFileMaxBytes      argparser.OptionalInt\n\tCompressionCodec  argparser.OptionalString\n}\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"create\", \"Create an Azure Blob Storage logging endpoint on a Fastly service version\").Alias(\"add\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.ServiceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.CmdClause.Flag(\"account-name\", \"The unique Azure Blob Storage namespace in which your data objects are stored\").Action(c.AccountName.Set).StringVar(&c.AccountName.Value)\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.AutoClone.Set,\n\t\tDst:    &c.AutoClone.Value,\n\t})\n\tlogflags.CompressionCodec(c.CmdClause, &c.CompressionCodec)\n\tc.CmdClause.Flag(\"container\", \"The name of the Azure Blob Storage container in which to store logs\").Action(c.Container.Set).StringVar(&c.Container.Value)\n\tc.CmdClause.Flag(\"file-max-bytes\", \"The maximum size of a log file in bytes\").Action(c.FileMaxBytes.Set).IntVar(&c.FileMaxBytes.Value)\n\tlogflags.Format(c.CmdClause, &c.Format)\n\tlogflags.FormatVersion(c.CmdClause, &c.FormatVersion)\n\tlogflags.GzipLevel(c.CmdClause, &c.GzipLevel)\n\tlogflags.MessageType(c.CmdClause, &c.MessageType)\n\tc.CmdClause.Flag(\"name\", \"The name of the Azure Blob Storage logging object. Used as a primary key for API access\").Short('n').Action(c.EndpointName.Set).StringVar(&c.EndpointName.Value)\n\tlogflags.Path(c.CmdClause, &c.Path)\n\tlogflags.Period(c.CmdClause, &c.Period)\n\tlogflags.Placement(c.CmdClause, &c.Placement)\n\tlogflags.ProcessingRegion(c.CmdClause, &c.ProcessingRegion, \"Azure Blob Storage\")\n\tlogflags.PublicKey(c.CmdClause, &c.PublicKey)\n\tlogflags.ResponseCondition(c.CmdClause, &c.ResponseCondition)\n\tc.CmdClause.Flag(\"sas-token\", \"The Azure shared access signature providing write access to the blob service objects. Be sure to update your token before it expires or the logging functionality will not work\").Action(c.SASToken.Set).StringVar(&c.SASToken.Value)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.ServiceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.ServiceName.Value,\n\t})\n\tlogflags.TimestampFormat(c.CmdClause, &c.TimestampFormat)\n\treturn &c\n}\n\n// ConstructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *CreateCommand) ConstructInput(serviceID string, serviceVersion int) (*fastly.CreateBlobStorageInput, error) {\n\tvar input fastly.CreateBlobStorageInput\n\n\tinput.ServiceID = serviceID\n\tinput.ServiceVersion = serviceVersion\n\n\tif c.EndpointName.WasSet {\n\t\tinput.Name = &c.EndpointName.Value\n\t}\n\tif c.Container.WasSet {\n\t\tinput.Container = &c.Container.Value\n\t}\n\tif c.AccountName.WasSet {\n\t\tinput.AccountName = &c.AccountName.Value\n\t}\n\tif c.SASToken.WasSet {\n\t\tinput.SASToken = &c.SASToken.Value\n\t}\n\n\t// The following blocks enforces the mutual exclusivity of the\n\t// CompressionCodec and GzipLevel flags.\n\tif c.CompressionCodec.WasSet && c.GzipLevel.WasSet {\n\t\treturn nil, fmt.Errorf(\"error parsing arguments: the --compression-codec flag is mutually exclusive with the --gzip-level flag\")\n\t}\n\n\tif c.Path.WasSet {\n\t\tinput.Path = &c.Path.Value\n\t}\n\tif c.Period.WasSet {\n\t\tinput.Period = &c.Period.Value\n\t}\n\tif c.GzipLevel.WasSet {\n\t\tinput.GzipLevel = &c.GzipLevel.Value\n\t}\n\tif c.Format.WasSet {\n\t\tinput.Format = fastly.ToPointer(argparser.Content(c.Format.Value))\n\t}\n\tif c.FormatVersion.WasSet {\n\t\tinput.FormatVersion = &c.FormatVersion.Value\n\t}\n\tif c.ResponseCondition.WasSet {\n\t\tinput.ResponseCondition = &c.ResponseCondition.Value\n\t}\n\tif c.MessageType.WasSet {\n\t\tinput.MessageType = &c.MessageType.Value\n\t}\n\tif c.TimestampFormat.WasSet {\n\t\tinput.TimestampFormat = &c.TimestampFormat.Value\n\t}\n\tif c.Placement.WasSet {\n\t\tinput.Placement = &c.Placement.Value\n\t}\n\tif c.ProcessingRegion.WasSet {\n\t\tinput.ProcessingRegion = &c.ProcessingRegion.Value\n\t}\n\tif c.PublicKey.WasSet {\n\t\tinput.PublicKey = &c.PublicKey.Value\n\t}\n\tif c.FileMaxBytes.WasSet {\n\t\tinput.FileMaxBytes = &c.FileMaxBytes.Value\n\t}\n\tif c.CompressionCodec.WasSet {\n\t\tinput.CompressionCodec = &c.CompressionCodec.Value\n\t}\n\n\treturn &input, nil\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *CreateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(false),\n\t\tLocked:             optional.Of(false),\n\t\tAutoCloneFlag:      c.AutoClone,\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.ServiceName,\n\t\tServiceVersionFlag: c.ServiceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": errors.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tinput, err := c.ConstructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fastly.ToValue(serviceVersion.Number),\n\t\t})\n\t\treturn err\n\t}\n\n\td, err := c.Globals.APIClient.CreateBlobStorage(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fastly.ToValue(serviceVersion.Number),\n\t\t})\n\t\treturn err\n\t}\n\n\ttext.Success(\n\t\tout,\n\t\t\"Created Azure Blob Storage logging endpoint %s (service %s version %d)\",\n\t\tfastly.ToValue(d.Name),\n\t\tfastly.ToValue(d.ServiceID),\n\t\tfastly.ToValue(d.ServiceVersion),\n\t)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/azureblob/delete.go",
    "content": "package azureblob\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DeleteCommand calls the Fastly API to delete an Azure Blob Storage logging endpoint.\ntype DeleteCommand struct {\n\targparser.Base\n\tInput          fastly.DeleteBlobStorageInput\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n\tautoClone      argparser.OptionalAutoClone\n}\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"delete\", \"Delete an Azure Blob Storage logging endpoint on a Fastly service version\").Alias(\"remove\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.autoClone.Set,\n\t\tDst:    &c.autoClone.Value,\n\t})\n\tc.CmdClause.Flag(\"name\", \"The name of the Azure Blob Storage logging object\").Short('n').Required().StringVar(&c.Input.Name)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DeleteCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(false),\n\t\tLocked:             optional.Of(false),\n\t\tAutoCloneFlag:      c.autoClone,\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": errors.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tc.Input.ServiceID = serviceID\n\tc.Input.ServiceVersion = fastly.ToValue(serviceVersion.Number)\n\n\tif err := c.Globals.APIClient.DeleteBlobStorage(context.TODO(), &c.Input); err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fastly.ToValue(serviceVersion.Number),\n\t\t})\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Deleted Azure Blob Storage logging endpoint %s (service %s version %d)\", c.Input.Name, c.Input.ServiceID, c.Input.ServiceVersion)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/azureblob/describe.go",
    "content": "package azureblob\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DescribeCommand calls the Fastly API to describe an Azure Blob Storage logging endpoint.\ntype DescribeCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tInput          fastly.GetBlobStorageInput\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n}\n\n// NewDescribeCommand returns a usable command registered under the parent.\nfunc NewDescribeCommand(parent argparser.Registerer, g *global.Data) *DescribeCommand {\n\tc := DescribeCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"describe\", \"Show detailed information about an Azure Blob Storage logging endpoint on a Fastly service version\").Alias(\"get\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"The name of the Azure Blob Storage logging object\").Short('n').Required().StringVar(&c.Input.Name)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DescribeCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fsterr.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tc.Input.ServiceID = serviceID\n\tc.Input.ServiceVersion = fastly.ToValue(serviceVersion.Number)\n\n\to, err := c.Globals.APIClient.GetBlobStorage(context.TODO(), &c.Input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fastly.ToValue(serviceVersion.Number),\n\t\t})\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\tlines := text.Lines{\n\t\t\"Account name\":       fastly.ToValue(o.AccountName),\n\t\t\"Compression codec\":  fastly.ToValue(o.CompressionCodec),\n\t\t\"Container\":          fastly.ToValue(o.Container),\n\t\t\"File max bytes\":     fastly.ToValue(o.FileMaxBytes),\n\t\t\"Format version\":     fastly.ToValue(o.FormatVersion),\n\t\t\"Format\":             fastly.ToValue(o.Format),\n\t\t\"GZip level\":         fastly.ToValue(o.GzipLevel),\n\t\t\"Message type\":       fastly.ToValue(o.MessageType),\n\t\t\"Name\":               fastly.ToValue(o.Name),\n\t\t\"Path\":               fastly.ToValue(o.Path),\n\t\t\"Period\":             fastly.ToValue(o.Period),\n\t\t\"Placement\":          fastly.ToValue(o.Placement),\n\t\t\"Processing region\":  fastly.ToValue(o.ProcessingRegion),\n\t\t\"Public key\":         fastly.ToValue(o.PublicKey),\n\t\t\"Response condition\": fastly.ToValue(o.ResponseCondition),\n\t\t\"SAS token\":          fastly.ToValue(o.SASToken),\n\t\t\"Timestamp format\":   fastly.ToValue(o.TimestampFormat),\n\t\t\"Version\":            fastly.ToValue(o.ServiceVersion),\n\t}\n\tif !c.Globals.Verbose() {\n\t\tlines[\"Service ID\"] = fastly.ToValue(o.ServiceID)\n\t}\n\ttext.PrintLines(out, lines)\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/azureblob/doc.go",
    "content": "// Package azureblob contains commands to inspect and manipulate Fastly service Azure Blob Storage\n// logging endpoints.\npackage azureblob\n"
  },
  {
    "path": "pkg/commands/service/logging/azureblob/list.go",
    "content": "package azureblob\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// ListCommand calls the Fastly API to list Azure Blob Storage logging endpoints.\ntype ListCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tInput          fastly.ListBlobStoragesInput\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n}\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"list\", \"List Azure Blob Storage logging endpoints on a Fastly service version\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *ListCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fsterr.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tc.Input.ServiceID = serviceID\n\tc.Input.ServiceVersion = fastly.ToValue(serviceVersion.Number)\n\n\to, err := c.Globals.APIClient.ListBlobStorages(context.TODO(), &c.Input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fastly.ToValue(serviceVersion.Number),\n\t\t})\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\tif !c.Globals.Verbose() {\n\t\ttw := text.NewTable(out)\n\t\ttw.AddHeader(\"SERVICE\", \"VERSION\", \"NAME\")\n\t\tfor _, azureblob := range o {\n\t\t\ttw.AddLine(\n\t\t\t\tfastly.ToValue(azureblob.ServiceID),\n\t\t\t\tfastly.ToValue(azureblob.ServiceVersion),\n\t\t\t\tfastly.ToValue(azureblob.Name),\n\t\t\t)\n\t\t}\n\t\ttw.Print()\n\t\treturn nil\n\t}\n\n\tfmt.Fprintf(out, \"Version: %d\\n\", c.Input.ServiceVersion)\n\tfor i, azureblob := range o {\n\t\tfmt.Fprintf(out, \"\\tBlobStorage %d/%d\\n\", i+1, len(o))\n\t\tfmt.Fprintf(out, \"\\t\\tService ID: %s\\n\", fastly.ToValue(azureblob.ServiceID))\n\t\tfmt.Fprintf(out, \"\\t\\tVersion: %d\\n\", fastly.ToValue(azureblob.ServiceVersion))\n\t\tfmt.Fprintf(out, \"\\t\\tName: %s\\n\", fastly.ToValue(azureblob.Name))\n\t\tfmt.Fprintf(out, \"\\t\\tContainer: %s\\n\", fastly.ToValue(azureblob.Container))\n\t\tfmt.Fprintf(out, \"\\t\\tAccount name: %s\\n\", fastly.ToValue(azureblob.AccountName))\n\t\tfmt.Fprintf(out, \"\\t\\tSAS token: %s\\n\", fastly.ToValue(azureblob.SASToken))\n\t\tfmt.Fprintf(out, \"\\t\\tPath: %s\\n\", fastly.ToValue(azureblob.Path))\n\t\tfmt.Fprintf(out, \"\\t\\tPeriod: %d\\n\", fastly.ToValue(azureblob.Period))\n\t\tfmt.Fprintf(out, \"\\t\\tGZip level: %d\\n\", fastly.ToValue(azureblob.GzipLevel))\n\t\tfmt.Fprintf(out, \"\\t\\tFormat: %s\\n\", fastly.ToValue(azureblob.Format))\n\t\tfmt.Fprintf(out, \"\\t\\tFormat version: %d\\n\", fastly.ToValue(azureblob.FormatVersion))\n\t\tfmt.Fprintf(out, \"\\t\\tResponse condition: %s\\n\", fastly.ToValue(azureblob.ResponseCondition))\n\t\tfmt.Fprintf(out, \"\\t\\tMessage type: %s\\n\", fastly.ToValue(azureblob.MessageType))\n\t\tfmt.Fprintf(out, \"\\t\\tTimestamp format: %s\\n\", fastly.ToValue(azureblob.TimestampFormat))\n\t\tfmt.Fprintf(out, \"\\t\\tPlacement: %s\\n\", fastly.ToValue(azureblob.Placement))\n\t\tfmt.Fprintf(out, \"\\t\\tPublic key: %s\\n\", fastly.ToValue(azureblob.PublicKey))\n\t\tfmt.Fprintf(out, \"\\t\\tFile max bytes: %d\\n\", fastly.ToValue(azureblob.FileMaxBytes))\n\t\tfmt.Fprintf(out, \"\\t\\tCompression codec: %s\\n\", fastly.ToValue(azureblob.CompressionCodec))\n\t\tfmt.Fprintf(out, \"\\t\\tProcessing region: %s\\n\", fastly.ToValue(azureblob.ProcessingRegion))\n\t}\n\tfmt.Fprintln(out)\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/azureblob/root.go",
    "content": "package azureblob\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"azureblob\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manipulate Fastly service version Azure Blob Storage logging endpoints\")\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/azureblob/update.go",
    "content": "package azureblob\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/service/logging/logflags\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/manifest\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// UpdateCommand calls the Fastly API to update an Azure Blob Storage logging endpoint.\ntype UpdateCommand struct {\n\targparser.Base\n\tManifest manifest.Data\n\n\t// Required.\n\tEndpointName   string\n\tServiceName    argparser.OptionalServiceNameID\n\tServiceVersion argparser.OptionalServiceVersion\n\n\t// Optional.\n\tAutoClone         argparser.OptionalAutoClone\n\tNewName           argparser.OptionalString\n\tAccountName       argparser.OptionalString\n\tContainer         argparser.OptionalString\n\tSASToken          argparser.OptionalString\n\tPath              argparser.OptionalString\n\tPeriod            argparser.OptionalInt\n\tGzipLevel         argparser.OptionalInt\n\tMessageType       argparser.OptionalString\n\tFormat            argparser.OptionalString\n\tFormatVersion     argparser.OptionalInt\n\tResponseCondition argparser.OptionalString\n\tTimestampFormat   argparser.OptionalString\n\tPlacement         argparser.OptionalString\n\tProcessingRegion  argparser.OptionalString\n\tPublicKey         argparser.OptionalString\n\tFileMaxBytes      argparser.OptionalInt\n\tCompressionCodec  argparser.OptionalString\n}\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"update\", \"Update an Azure Blob Storage logging endpoint on a Fastly service version\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.ServiceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.AutoClone.Set,\n\t\tDst:    &c.AutoClone.Value,\n\t})\n\tc.CmdClause.Flag(\"name\", \"The name of the Azure Blob Storage logging object\").Short('n').Required().StringVar(&c.EndpointName)\n\n\t// Optional.\n\tc.CmdClause.Flag(\"account-name\", \"The unique Azure Blob Storage namespace in which your data objects are stored\").Action(c.AccountName.Set).StringVar(&c.AccountName.Value)\n\tlogflags.CompressionCodec(c.CmdClause, &c.CompressionCodec)\n\tc.CmdClause.Flag(\"container\", \"The name of the Azure Blob Storage container in which to store logs\").Action(c.Container.Set).StringVar(&c.Container.Value)\n\tc.CmdClause.Flag(\"file-max-bytes\", \"The maximum size of a log file in bytes\").Action(c.FileMaxBytes.Set).IntVar(&c.FileMaxBytes.Value)\n\tlogflags.Format(c.CmdClause, &c.Format)\n\tlogflags.FormatVersion(c.CmdClause, &c.FormatVersion)\n\tlogflags.GzipLevel(c.CmdClause, &c.GzipLevel)\n\tlogflags.MessageType(c.CmdClause, &c.MessageType)\n\tc.CmdClause.Flag(\"new-name\", \"New name of the Azure Blob Storage logging object\").Action(c.NewName.Set).StringVar(&c.NewName.Value)\n\tlogflags.Path(c.CmdClause, &c.Path)\n\tlogflags.Period(c.CmdClause, &c.Period)\n\tlogflags.Placement(c.CmdClause, &c.Placement)\n\tlogflags.ProcessingRegion(c.CmdClause, &c.ProcessingRegion, \"Azure Blob Storage\")\n\tlogflags.PublicKey(c.CmdClause, &c.PublicKey)\n\tlogflags.ResponseCondition(c.CmdClause, &c.ResponseCondition)\n\tc.CmdClause.Flag(\"sas-token\", \"The Azure shared access signature providing write access to the blob service objects. Be sure to update your token before it expires or the logging functionality will not work\").Action(c.SASToken.Set).StringVar(&c.SASToken.Value)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.ServiceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.ServiceName.Value,\n\t})\n\tlogflags.TimestampFormat(c.CmdClause, &c.TimestampFormat)\n\treturn &c\n}\n\n// ConstructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *UpdateCommand) ConstructInput(serviceID string, serviceVersion int) (*fastly.UpdateBlobStorageInput, error) {\n\tinput := fastly.UpdateBlobStorageInput{\n\t\tServiceID:      serviceID,\n\t\tServiceVersion: serviceVersion,\n\t\tName:           c.EndpointName,\n\t}\n\n\t// Set new values if set by user.\n\tif c.NewName.WasSet {\n\t\tinput.NewName = &c.NewName.Value\n\t}\n\tif c.Path.WasSet {\n\t\tinput.Path = &c.Path.Value\n\t}\n\tif c.AccountName.WasSet {\n\t\tinput.AccountName = &c.AccountName.Value\n\t}\n\tif c.Container.WasSet {\n\t\tinput.Container = &c.Container.Value\n\t}\n\tif c.SASToken.WasSet {\n\t\tinput.SASToken = &c.SASToken.Value\n\t}\n\tif c.Period.WasSet {\n\t\tinput.Period = &c.Period.Value\n\t}\n\tif c.GzipLevel.WasSet {\n\t\tinput.GzipLevel = &c.GzipLevel.Value\n\t}\n\tif c.Format.WasSet {\n\t\tinput.Format = fastly.ToPointer(argparser.Content(c.Format.Value))\n\t}\n\tif c.FormatVersion.WasSet {\n\t\tinput.FormatVersion = &c.FormatVersion.Value\n\t}\n\tif c.ResponseCondition.WasSet {\n\t\tinput.ResponseCondition = &c.ResponseCondition.Value\n\t}\n\tif c.MessageType.WasSet {\n\t\tinput.MessageType = &c.MessageType.Value\n\t}\n\tif c.TimestampFormat.WasSet {\n\t\tinput.TimestampFormat = &c.TimestampFormat.Value\n\t}\n\tif c.Placement.WasSet {\n\t\tinput.Placement = &c.Placement.Value\n\t}\n\tif c.ProcessingRegion.WasSet {\n\t\tinput.ProcessingRegion = &c.ProcessingRegion.Value\n\t}\n\tif c.PublicKey.WasSet {\n\t\tinput.PublicKey = &c.PublicKey.Value\n\t}\n\tif c.FileMaxBytes.WasSet {\n\t\tinput.FileMaxBytes = &c.FileMaxBytes.Value\n\t}\n\tif c.CompressionCodec.WasSet {\n\t\tinput.CompressionCodec = &c.CompressionCodec.Value\n\t}\n\n\treturn &input, nil\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *UpdateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(false),\n\t\tLocked:             optional.Of(false),\n\t\tAutoCloneFlag:      c.AutoClone,\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.ServiceName,\n\t\tServiceVersionFlag: c.ServiceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": errors.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tinput, err := c.ConstructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fastly.ToValue(serviceVersion.Number),\n\t\t})\n\t\treturn err\n\t}\n\n\tazureblob, err := c.Globals.APIClient.UpdateBlobStorage(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fastly.ToValue(serviceVersion.Number),\n\t\t})\n\t\treturn err\n\t}\n\n\ttext.Success(\n\t\tout,\n\t\t\"Updated Azure Blob Storage logging endpoint %s (service %s version %d)\",\n\t\tfastly.ToValue(azureblob.Name),\n\t\tfastly.ToValue(azureblob.ServiceID),\n\t\tfastly.ToValue(azureblob.ServiceVersion),\n\t)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/bigquery/bigquery_integration_test.go",
    "content": "package bigquery_test\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/mock\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n\n\troot \"github.com/fastly/cli/pkg/commands/service\"\n\tparent \"github.com/fastly/cli/pkg/commands/service/logging\"\n\tsub \"github.com/fastly/cli/pkg/commands/service/logging/bigquery\"\n)\n\nfunc TestBigQueryCreate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name log --project-id project123 --dataset logs --table logs --user user@domain.com --secret-key `\\\"-----BEGIN RSA PRIVATE KEY-----MIIEogIBAAKCA\\\"` --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:     testutil.GetVersion,\n\t\t\t\tCloneVersionFn:   testutil.CloneVersionResult(4),\n\t\t\t\tCreateBigQueryFn: createBigQueryOK,\n\t\t\t},\n\t\t\tWantOutput: \"Created BigQuery logging endpoint log (service 123 version 4)\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name log --project-id project123 --dataset logs --table logs --user user@domain.com --secret-key `\\\"-----BEGIN RSA PRIVATE KEY-----MIIEogIBAAKCA\\\"` --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:     testutil.GetVersion,\n\t\t\t\tCloneVersionFn:   testutil.CloneVersionResult(4),\n\t\t\t\tCreateBigQueryFn: createBigQueryError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"create\"}, scenarios)\n}\n\nfunc TestBigQueryList(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:     testutil.GetVersion,\n\t\t\t\tListBigQueriesFn: listBigQueriesOK,\n\t\t\t},\n\t\t\tWantOutput: listBigQueriesShortOutput,\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --verbose\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:     testutil.GetVersion,\n\t\t\t\tListBigQueriesFn: listBigQueriesOK,\n\t\t\t},\n\t\t\tWantOutput: listBigQueriesVerboseOutput,\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 -v\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:     testutil.GetVersion,\n\t\t\t\tListBigQueriesFn: listBigQueriesOK,\n\t\t\t},\n\t\t\tWantOutput: listBigQueriesVerboseOutput,\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:     testutil.GetVersion,\n\t\t\t\tListBigQueriesFn: listBigQueriesError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"list\"}, scenarios)\n}\n\nfunc TestBigQueryDescribe(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"--service-id 123 --version 1\",\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:  testutil.GetVersion,\n\t\t\t\tGetBigQueryFn: getBigQueryError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:  testutil.GetVersion,\n\t\t\t\tGetBigQueryFn: getBigQueryOK,\n\t\t\t},\n\t\t\tWantOutput: describeBigQueryOutput,\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"describe\"}, scenarios)\n}\n\nfunc TestBigQueryUpdate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"--service-id 123 --version 1 --new-name log\",\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs --new-name log --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:     testutil.GetVersion,\n\t\t\t\tCloneVersionFn:   testutil.CloneVersionResult(4),\n\t\t\t\tUpdateBigQueryFn: updateBigQueryError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs --new-name log --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:     testutil.GetVersion,\n\t\t\t\tCloneVersionFn:   testutil.CloneVersionResult(4),\n\t\t\t\tUpdateBigQueryFn: updateBigQueryOK,\n\t\t\t},\n\t\t\tWantOutput: \"Updated BigQuery logging endpoint log (service 123 version 4)\",\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"update\"}, scenarios)\n}\n\nfunc TestBigQueryDelete(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"--service-id 123 --version 1\",\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:     testutil.GetVersion,\n\t\t\t\tCloneVersionFn:   testutil.CloneVersionResult(4),\n\t\t\t\tDeleteBigQueryFn: deleteBigQueryError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:     testutil.GetVersion,\n\t\t\t\tCloneVersionFn:   testutil.CloneVersionResult(4),\n\t\t\t\tDeleteBigQueryFn: deleteBigQueryOK,\n\t\t\t},\n\t\t\tWantOutput: \"Deleted BigQuery logging endpoint logs (service 123 version 4)\",\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"delete\"}, scenarios)\n}\n\nvar errTest = errors.New(\"fixture error\")\n\nfunc createBigQueryOK(_ context.Context, i *fastly.CreateBigQueryInput) (*fastly.BigQuery, error) {\n\treturn &fastly.BigQuery{\n\t\tServiceID:      fastly.ToPointer(i.ServiceID),\n\t\tServiceVersion: fastly.ToPointer(i.ServiceVersion),\n\t\tName:           i.Name,\n\t}, nil\n}\n\nfunc createBigQueryError(_ context.Context, _ *fastly.CreateBigQueryInput) (*fastly.BigQuery, error) {\n\treturn nil, errTest\n}\n\nfunc listBigQueriesOK(_ context.Context, i *fastly.ListBigQueriesInput) ([]*fastly.BigQuery, error) {\n\treturn []*fastly.BigQuery{\n\t\t{\n\t\t\tServiceID:         fastly.ToPointer(i.ServiceID),\n\t\t\tServiceVersion:    fastly.ToPointer(i.ServiceVersion),\n\t\t\tName:              fastly.ToPointer(\"logs\"),\n\t\t\tProjectID:         fastly.ToPointer(\"my-project\"),\n\t\t\tDataset:           fastly.ToPointer(\"raw-logs\"),\n\t\t\tTable:             fastly.ToPointer(\"logs\"),\n\t\t\tUser:              fastly.ToPointer(\"service-account@domain.com\"),\n\t\t\tAccountName:       fastly.ToPointer(\"none\"),\n\t\t\tSecretKey:         fastly.ToPointer(\"-----BEGIN RSA PRIVATE KEY-----MIIEogIBAAKCA\"),\n\t\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\t\tTemplate:          fastly.ToPointer(\"%Y%m%d\"),\n\t\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\t\tProcessingRegion:  fastly.ToPointer(\"us\"),\n\t\t},\n\t\t{\n\t\t\tServiceID:         fastly.ToPointer(i.ServiceID),\n\t\t\tServiceVersion:    fastly.ToPointer(i.ServiceVersion),\n\t\t\tName:              fastly.ToPointer(\"analytics\"),\n\t\t\tProjectID:         fastly.ToPointer(\"my-project\"),\n\t\t\tDataset:           fastly.ToPointer(\"analytics\"),\n\t\t\tTable:             fastly.ToPointer(\"logs\"),\n\t\t\tUser:              fastly.ToPointer(\"service-account@domain.com\"),\n\t\t\tAccountName:       fastly.ToPointer(\"none\"),\n\t\t\tSecretKey:         fastly.ToPointer(\"-----BEGIN RSA PRIVATE KEY-----MIIEogIBAAKCA\"),\n\t\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\t\tTemplate:          fastly.ToPointer(\"%Y%m%d\"),\n\t\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\t\tProcessingRegion:  fastly.ToPointer(\"us\"),\n\t\t},\n\t}, nil\n}\n\nfunc listBigQueriesError(_ context.Context, _ *fastly.ListBigQueriesInput) ([]*fastly.BigQuery, error) {\n\treturn nil, errTest\n}\n\nvar listBigQueriesShortOutput = strings.TrimSpace(`\nSERVICE  VERSION  NAME\n123      1        logs\n123      1        analytics\n`) + \"\\n\"\n\nvar listBigQueriesVerboseOutput = strings.TrimSpace(`\nFastly API endpoint: https://api.fastly.com\nFastly API token provided via config file (auth: user)\n\nService ID (via --service-id): 123\n\nVersion: 1\n\tBigQuery 1/2\n\t\tService ID: 123\n\t\tVersion: 1\n\t\tName: logs\n\t\tFormat: %h %l %u %t \"%r\" %>s %b\n\t\tUser: service-account@domain.com\n\t\tAccount name: none\n\t\tProject ID: my-project\n\t\tDataset: raw-logs\n\t\tTable: logs\n\t\tTemplate suffix: %Y%m%d\n\t\tSecret key: -----BEGIN RSA PRIVATE KEY-----MIIEogIBAAKCA\n\t\tResponse condition: Prevent default logging\n\t\tPlacement: none\n\t\tFormat version: 0\n\t\tProcessing region: us\n\tBigQuery 2/2\n\t\tService ID: 123\n\t\tVersion: 1\n\t\tName: analytics\n\t\tFormat: %h %l %u %t \"%r\" %>s %b\n\t\tUser: service-account@domain.com\n\t\tAccount name: none\n\t\tProject ID: my-project\n\t\tDataset: analytics\n\t\tTable: logs\n\t\tTemplate suffix: %Y%m%d\n\t\tSecret key: -----BEGIN RSA PRIVATE KEY-----MIIEogIBAAKCA\n\t\tResponse condition: Prevent default logging\n\t\tPlacement: none\n\t\tFormat version: 0\n\t\tProcessing region: us\n`) + \"\\n\\n\"\n\nfunc getBigQueryOK(_ context.Context, i *fastly.GetBigQueryInput) (*fastly.BigQuery, error) {\n\treturn &fastly.BigQuery{\n\t\tServiceID:         fastly.ToPointer(i.ServiceID),\n\t\tServiceVersion:    fastly.ToPointer(i.ServiceVersion),\n\t\tName:              fastly.ToPointer(\"logs\"),\n\t\tProjectID:         fastly.ToPointer(\"my-project\"),\n\t\tDataset:           fastly.ToPointer(\"raw-logs\"),\n\t\tTable:             fastly.ToPointer(\"logs\"),\n\t\tUser:              fastly.ToPointer(\"service-account@domain.com\"),\n\t\tAccountName:       fastly.ToPointer(\"none\"),\n\t\tSecretKey:         fastly.ToPointer(\"-----BEGIN RSA PRIVATE KEY-----MIIEogIBAAKCA\"),\n\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\tTemplate:          fastly.ToPointer(\"%Y%m%d\"),\n\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\tProcessingRegion:  fastly.ToPointer(\"us\"),\n\t}, nil\n}\n\nfunc getBigQueryError(_ context.Context, _ *fastly.GetBigQueryInput) (*fastly.BigQuery, error) {\n\treturn nil, errTest\n}\n\nvar describeBigQueryOutput = \"\\n\" + strings.TrimSpace(`\nAccount name: none\nDataset: raw-logs\nFormat: %h %l %u %t \"%r\" %>s %b\nFormat version: 0\nName: logs\nPlacement: none\nProcessing region: us\nProject ID: my-project\nResponse condition: Prevent default logging\nSecret key: -----BEGIN RSA PRIVATE KEY-----MIIEogIBAAKCA\nService ID: 123\nTable: logs\nTemplate suffix: %Y%m%d\nUser: service-account@domain.com\nVersion: 1\n`) + \"\\n\"\n\nfunc updateBigQueryOK(_ context.Context, i *fastly.UpdateBigQueryInput) (*fastly.BigQuery, error) {\n\treturn &fastly.BigQuery{\n\t\tServiceID:         fastly.ToPointer(i.ServiceID),\n\t\tServiceVersion:    fastly.ToPointer(i.ServiceVersion),\n\t\tName:              fastly.ToPointer(\"log\"),\n\t\tProjectID:         fastly.ToPointer(\"my-project\"),\n\t\tDataset:           fastly.ToPointer(\"raw-logs\"),\n\t\tTable:             fastly.ToPointer(\"logs\"),\n\t\tUser:              fastly.ToPointer(\"service-account@domain.com\"),\n\t\tSecretKey:         fastly.ToPointer(\"-----BEGIN RSA PRIVATE KEY-----MIIEogIBAAKCA\"),\n\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\tTemplate:          fastly.ToPointer(\"%Y%m%d\"),\n\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t}, nil\n}\n\nfunc updateBigQueryError(_ context.Context, _ *fastly.UpdateBigQueryInput) (*fastly.BigQuery, error) {\n\treturn nil, errTest\n}\n\nfunc deleteBigQueryOK(_ context.Context, _ *fastly.DeleteBigQueryInput) error {\n\treturn nil\n}\n\nfunc deleteBigQueryError(_ context.Context, _ *fastly.DeleteBigQueryInput) error {\n\treturn errTest\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/bigquery/bigquery_test.go",
    "content": "package bigquery_test\n\nimport (\n\t\"bytes\"\n\t\"testing\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/service/logging/bigquery\"\n\t\"github.com/fastly/cli/pkg/config\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/manifest\"\n\t\"github.com/fastly/cli/pkg/mock\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n)\n\nfunc TestCreateBigQueryInput(t *testing.T) {\n\tfor _, testcase := range []struct {\n\t\tname      string\n\t\tcmd       *bigquery.CreateCommand\n\t\twant      *fastly.CreateBigQueryInput\n\t\twantError string\n\t}{\n\t\t{\n\t\t\tname: \"required values set flag serviceID\",\n\t\t\tcmd:  createCommandRequired(),\n\t\t\twant: &fastly.CreateBigQueryInput{\n\t\t\t\tServiceID:      \"123\",\n\t\t\t\tServiceVersion: 4,\n\t\t\t\tName:           fastly.ToPointer(\"log\"),\n\t\t\t\tProjectID:      fastly.ToPointer(\"123\"),\n\t\t\t\tDataset:        fastly.ToPointer(\"dataset\"),\n\t\t\t\tTable:          fastly.ToPointer(\"table\"),\n\t\t\t\tUser:           fastly.ToPointer(\"user\"),\n\t\t\t\tSecretKey:      fastly.ToPointer(\"-----BEGIN PRIVATE KEY-----foo\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"all values set flag serviceID\",\n\t\t\tcmd:  createCommandAll(),\n\t\t\twant: &fastly.CreateBigQueryInput{\n\t\t\t\tServiceID:         \"123\",\n\t\t\t\tServiceVersion:    4,\n\t\t\t\tName:              fastly.ToPointer(\"log\"),\n\t\t\t\tProjectID:         fastly.ToPointer(\"123\"),\n\t\t\t\tDataset:           fastly.ToPointer(\"dataset\"),\n\t\t\t\tTable:             fastly.ToPointer(\"table\"),\n\t\t\t\tTemplate:          fastly.ToPointer(\"template\"),\n\t\t\t\tUser:              fastly.ToPointer(\"user\"),\n\t\t\t\tSecretKey:         fastly.ToPointer(\"-----BEGIN PRIVATE KEY-----foo\"),\n\t\t\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\t\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\t\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\t\t\tProcessingRegion:  fastly.ToPointer(\"eu\"),\n\t\t\t\tFormatVersion:     fastly.ToPointer(2),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:      \"error missing serviceID\",\n\t\t\tcmd:       createCommandMissingServiceID(),\n\t\t\twant:      nil,\n\t\t\twantError: errors.ErrNoServiceID.Error(),\n\t\t},\n\t} {\n\t\tt.Run(testcase.name, func(t *testing.T) {\n\t\t\tif testcase.wantError == errors.ErrNoServiceID.Error() {\n\t\t\t\tt.Setenv(\"FASTLY_SERVICE_ID\", \"\")\n\t\t\t}\n\t\t\tvar bs []byte\n\t\t\tout := bytes.NewBuffer(bs)\n\t\t\tverboseMode := true\n\n\t\t\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\t\t\tAutoCloneFlag:      testcase.cmd.AutoClone,\n\t\t\t\tAPIClient:          testcase.cmd.Globals.APIClient,\n\t\t\t\tManifest:           testcase.cmd.Manifest,\n\t\t\t\tOut:                out,\n\t\t\t\tServiceVersionFlag: testcase.cmd.ServiceVersion,\n\t\t\t\tVerboseMode:        verboseMode,\n\t\t\t})\n\n\t\t\tswitch {\n\t\t\tcase err != nil && testcase.wantError == \"\":\n\t\t\t\tt.Fatalf(\"unexpected error getting service details: %v\", err)\n\t\t\t\treturn\n\t\t\tcase err != nil && testcase.wantError != \"\":\n\t\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\t\treturn\n\t\t\tcase err == nil && testcase.wantError != \"\":\n\t\t\t\tt.Fatalf(\"expected error, have nil (service details: %s, %d)\", serviceID, serviceVersion.Number)\n\t\t\tcase err == nil && testcase.wantError == \"\":\n\t\t\t\thave, err := testcase.cmd.ConstructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\t\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\t\ttestutil.AssertEqual(t, testcase.want, have)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestUpdateBigQueryInput(t *testing.T) {\n\tscenarios := []struct {\n\t\tname      string\n\t\tcmd       *bigquery.UpdateCommand\n\t\tapi       mock.API\n\t\twant      *fastly.UpdateBigQueryInput\n\t\twantError string\n\t}{\n\t\t{\n\t\t\tname: \"no updates\",\n\t\t\tcmd:  updateCommandNoUpdates(),\n\t\t\tapi: mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tGetBigQueryFn:  getBigQueryOK,\n\t\t\t},\n\t\t\twant: &fastly.UpdateBigQueryInput{\n\t\t\t\tServiceID:      \"123\",\n\t\t\t\tServiceVersion: 4,\n\t\t\t\tName:           \"log\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"all values set flag serviceID\",\n\t\t\tcmd:  updateCommandAll(),\n\t\t\tapi: mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tGetBigQueryFn:  getBigQueryOK,\n\t\t\t},\n\t\t\twant: &fastly.UpdateBigQueryInput{\n\t\t\t\tServiceID:         \"123\",\n\t\t\t\tServiceVersion:    4,\n\t\t\t\tName:              \"log\",\n\t\t\t\tNewName:           fastly.ToPointer(\"new1\"),\n\t\t\t\tProjectID:         fastly.ToPointer(\"new2\"),\n\t\t\t\tDataset:           fastly.ToPointer(\"new3\"),\n\t\t\t\tTable:             fastly.ToPointer(\"new4\"),\n\t\t\t\tUser:              fastly.ToPointer(\"new5\"),\n\t\t\t\tSecretKey:         fastly.ToPointer(\"new6\"),\n\t\t\t\tTemplate:          fastly.ToPointer(\"new7\"),\n\t\t\t\tResponseCondition: fastly.ToPointer(\"new8\"),\n\t\t\t\tPlacement:         fastly.ToPointer(\"new9\"),\n\t\t\t\tProcessingRegion:  fastly.ToPointer(\"eu\"),\n\t\t\t\tFormat:            fastly.ToPointer(\"new10\"),\n\t\t\t\tFormatVersion:     fastly.ToPointer(3),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:      \"error missing serviceID\",\n\t\t\tcmd:       updateCommandMissingServiceID(),\n\t\t\twant:      nil,\n\t\t\twantError: errors.ErrNoServiceID.Error(),\n\t\t},\n\t}\n\tfor testcaseIdx := range scenarios {\n\t\ttestcase := &scenarios[testcaseIdx]\n\t\tt.Run(testcase.name, func(t *testing.T) {\n\t\t\tif testcase.wantError == errors.ErrNoServiceID.Error() {\n\t\t\t\tt.Setenv(\"FASTLY_SERVICE_ID\", \"\")\n\t\t\t}\n\t\t\ttestcase.cmd.Globals.APIClient = testcase.api\n\n\t\t\tvar bs []byte\n\t\t\tout := bytes.NewBuffer(bs)\n\t\t\tverboseMode := true\n\n\t\t\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\t\t\tAutoCloneFlag:      testcase.cmd.AutoClone,\n\t\t\t\tAPIClient:          testcase.api,\n\t\t\t\tManifest:           testcase.cmd.Manifest,\n\t\t\t\tOut:                out,\n\t\t\t\tServiceVersionFlag: testcase.cmd.ServiceVersion,\n\t\t\t\tVerboseMode:        verboseMode,\n\t\t\t})\n\n\t\t\tswitch {\n\t\t\tcase err != nil && testcase.wantError == \"\":\n\t\t\t\tt.Fatalf(\"unexpected error getting service details: %v\", err)\n\t\t\t\treturn\n\t\t\tcase err != nil && testcase.wantError != \"\":\n\t\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\t\treturn\n\t\t\tcase err == nil && testcase.wantError != \"\":\n\t\t\t\tt.Fatalf(\"expected error, have nil (service details: %s, %d)\", serviceID, serviceVersion.Number)\n\t\t\tcase err == nil && testcase.wantError == \"\":\n\t\t\t\thave, err := testcase.cmd.ConstructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\t\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\t\ttestutil.AssertEqual(t, testcase.want, have)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc createCommandRequired() *bigquery.CreateCommand {\n\tvar b bytes.Buffer\n\n\tg := global.Data{\n\t\tConfig: config.File{},\n\t\tEnv:    config.Environment{},\n\t\tOutput: &b,\n\t}\n\tg.APIClient, _ = mock.APIClient(mock.API{\n\t\tGetVersionFn:   testutil.GetVersion,\n\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t})(\"token\", \"endpoint\", false)\n\n\treturn &bigquery.CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: &g,\n\t\t},\n\t\tManifest: manifest.Data{\n\t\t\tFlag: manifest.Flag{\n\t\t\t\tServiceID: \"123\",\n\t\t\t},\n\t\t},\n\t\tServiceVersion: argparser.OptionalServiceVersion{\n\t\t\tOptionalString: argparser.OptionalString{Value: \"1\"},\n\t\t},\n\t\tAutoClone: argparser.OptionalAutoClone{\n\t\t\tOptionalBool: argparser.OptionalBool{\n\t\t\t\tOptional: argparser.Optional{\n\t\t\t\t\tWasSet: true,\n\t\t\t\t},\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t},\n\t\tEndpointName: argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"log\"},\n\t\tProjectID:    argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"123\"},\n\t\tDataset:      argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"dataset\"},\n\t\tTable:        argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"table\"},\n\t\tUser:         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"user\"},\n\t\tSecretKey:    argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"-----BEGIN PRIVATE KEY-----foo\"},\n\t}\n}\n\nfunc createCommandAll() *bigquery.CreateCommand {\n\tvar b bytes.Buffer\n\n\tg := global.Data{\n\t\tConfig: config.File{},\n\t\tEnv:    config.Environment{},\n\t\tOutput: &b,\n\t}\n\tg.APIClient, _ = mock.APIClient(mock.API{\n\t\tGetVersionFn:   testutil.GetVersion,\n\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t})(\"token\", \"endpoint\", false)\n\n\treturn &bigquery.CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: &g,\n\t\t},\n\t\tManifest: manifest.Data{\n\t\t\tFlag: manifest.Flag{\n\t\t\t\tServiceID: \"123\",\n\t\t\t},\n\t\t},\n\t\tServiceVersion: argparser.OptionalServiceVersion{\n\t\t\tOptionalString: argparser.OptionalString{Value: \"1\"},\n\t\t},\n\t\tAutoClone: argparser.OptionalAutoClone{\n\t\t\tOptionalBool: argparser.OptionalBool{\n\t\t\t\tOptional: argparser.Optional{\n\t\t\t\t\tWasSet: true,\n\t\t\t\t},\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t},\n\t\tEndpointName:      argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"log\"},\n\t\tProjectID:         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"123\"},\n\t\tDataset:           argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"dataset\"},\n\t\tTable:             argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"table\"},\n\t\tUser:              argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"user\"},\n\t\tSecretKey:         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"-----BEGIN PRIVATE KEY-----foo\"},\n\t\tTemplate:          argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"template\"},\n\t\tResponseCondition: argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"Prevent default logging\"},\n\t\tPlacement:         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"none\"},\n\t\tProcessingRegion:  argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"eu\"},\n\t\tFormat:            argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: `%h %l %u %t \"%r\" %>s %b`},\n\t\tFormatVersion:     argparser.OptionalInt{Optional: argparser.Optional{WasSet: true}, Value: 2},\n\t}\n}\n\nfunc createCommandMissingServiceID() *bigquery.CreateCommand {\n\tres := createCommandAll()\n\tres.Manifest = manifest.Data{}\n\tres.ServiceVersion = argparser.OptionalServiceVersion{}\n\treturn res\n}\n\nfunc updateCommandNoUpdates() *bigquery.UpdateCommand {\n\tvar b bytes.Buffer\n\n\tg := global.Data{\n\t\tConfig: config.File{},\n\t\tEnv:    config.Environment{},\n\t\tOutput: &b,\n\t}\n\n\treturn &bigquery.UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: &g,\n\t\t},\n\t\tManifest: manifest.Data{\n\t\t\tFlag: manifest.Flag{\n\t\t\t\tServiceID: \"123\",\n\t\t\t},\n\t\t},\n\t\tEndpointName: \"log\",\n\t\tServiceVersion: argparser.OptionalServiceVersion{\n\t\t\tOptionalString: argparser.OptionalString{Value: \"1\"},\n\t\t},\n\t\tAutoClone: argparser.OptionalAutoClone{\n\t\t\tOptionalBool: argparser.OptionalBool{\n\t\t\t\tOptional: argparser.Optional{\n\t\t\t\t\tWasSet: true,\n\t\t\t\t},\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t},\n\t}\n}\n\nfunc updateCommandAll() *bigquery.UpdateCommand {\n\tvar b bytes.Buffer\n\n\tg := global.Data{\n\t\tConfig: config.File{},\n\t\tEnv:    config.Environment{},\n\t\tOutput: &b,\n\t}\n\n\treturn &bigquery.UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: &g,\n\t\t},\n\t\tManifest: manifest.Data{\n\t\t\tFlag: manifest.Flag{\n\t\t\t\tServiceID: \"123\",\n\t\t\t},\n\t\t},\n\t\tEndpointName: \"log\",\n\t\tServiceVersion: argparser.OptionalServiceVersion{\n\t\t\tOptionalString: argparser.OptionalString{Value: \"1\"},\n\t\t},\n\t\tAutoClone: argparser.OptionalAutoClone{\n\t\t\tOptionalBool: argparser.OptionalBool{\n\t\t\t\tOptional: argparser.Optional{\n\t\t\t\t\tWasSet: true,\n\t\t\t\t},\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t},\n\t\tNewName:           argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new1\"},\n\t\tProjectID:         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new2\"},\n\t\tDataset:           argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new3\"},\n\t\tTable:             argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new4\"},\n\t\tUser:              argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new5\"},\n\t\tSecretKey:         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new6\"},\n\t\tTemplate:          argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new7\"},\n\t\tResponseCondition: argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new8\"},\n\t\tPlacement:         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new9\"},\n\t\tProcessingRegion:  argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"eu\"},\n\t\tFormat:            argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new10\"},\n\t\tFormatVersion:     argparser.OptionalInt{Optional: argparser.Optional{WasSet: true}, Value: 3},\n\t}\n}\n\nfunc updateCommandMissingServiceID() *bigquery.UpdateCommand {\n\tres := updateCommandAll()\n\tres.Manifest = manifest.Data{}\n\tres.ServiceVersion = argparser.OptionalServiceVersion{}\n\treturn res\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/bigquery/create.go",
    "content": "package bigquery\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/service/logging/logflags\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/manifest\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// CreateCommand calls the Fastly API to create a BigQuery logging endpoint.\ntype CreateCommand struct {\n\targparser.Base\n\tManifest manifest.Data\n\n\t// Required.\n\tServiceName    argparser.OptionalServiceNameID\n\tServiceVersion argparser.OptionalServiceVersion\n\n\t// Optional.\n\tAccountName       argparser.OptionalString\n\tAutoClone         argparser.OptionalAutoClone\n\tDataset           argparser.OptionalString\n\tEndpointName      argparser.OptionalString // Can't shadow argparser.Base method Name().\n\tFormat            argparser.OptionalString\n\tFormatVersion     argparser.OptionalInt\n\tPlacement         argparser.OptionalString\n\tProcessingRegion  argparser.OptionalString\n\tProjectID         argparser.OptionalString\n\tResponseCondition argparser.OptionalString\n\tSecretKey         argparser.OptionalString\n\tTable             argparser.OptionalString\n\tTemplate          argparser.OptionalString\n\tUser              argparser.OptionalString\n}\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"create\", \"Create a BigQuery logging endpoint on a Fastly service version\").Alias(\"add\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.ServiceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tlogflags.AccountName(c.CmdClause, &c.AccountName)\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.AutoClone.Set,\n\t\tDst:    &c.AutoClone.Value,\n\t})\n\tc.CmdClause.Flag(\"dataset\", \"Your BigQuery dataset\").Action(c.Dataset.Set).StringVar(&c.Dataset.Value)\n\tlogflags.Format(c.CmdClause, &c.Format)\n\tlogflags.FormatVersion(c.CmdClause, &c.FormatVersion)\n\tc.CmdClause.Flag(\"name\", \"The name of the BigQuery logging object. Used as a primary key for API access\").Short('n').Action(c.EndpointName.Set).StringVar(&c.EndpointName.Value)\n\tlogflags.Placement(c.CmdClause, &c.Placement)\n\tlogflags.ProcessingRegion(c.CmdClause, &c.ProcessingRegion, \"BigQuery\")\n\tc.CmdClause.Flag(\"project-id\", \"Your Google Cloud Platform project ID\").Action(c.ProjectID.Set).StringVar(&c.ProjectID.Value)\n\tlogflags.ResponseCondition(c.CmdClause, &c.ResponseCondition)\n\tc.CmdClause.Flag(\"secret-key\", \"Your Google Cloud Platform account secret key. The private_key field in your service account authentication JSON.\").Action(c.SecretKey.Set).StringVar(&c.SecretKey.Value)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.ServiceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.ServiceName.Value,\n\t})\n\tc.CmdClause.Flag(\"table\", \"Your BigQuery table\").Action(c.Table.Set).StringVar(&c.Table.Value)\n\tc.CmdClause.Flag(\"template-suffix\", \"BigQuery table name suffix template\").Action(c.Template.Set).StringVar(&c.Template.Value)\n\tc.CmdClause.Flag(\"user\", \"Your Google Cloud Platform service account email address. The client_email field in your service account authentication JSON.\").Action(c.User.Set).StringVar(&c.User.Value)\n\treturn &c\n}\n\n// ConstructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *CreateCommand) ConstructInput(serviceID string, serviceVersion int) (*fastly.CreateBigQueryInput, error) {\n\tinput := fastly.CreateBigQueryInput{\n\t\tServiceID:      serviceID,\n\t\tServiceVersion: serviceVersion,\n\t}\n\n\tif c.AccountName.WasSet {\n\t\tinput.AccountName = &c.AccountName.Value\n\t}\n\tif c.Dataset.WasSet {\n\t\tinput.Dataset = &c.Dataset.Value\n\t}\n\tif c.EndpointName.WasSet {\n\t\tinput.Name = &c.EndpointName.Value\n\t}\n\tif c.Format.WasSet {\n\t\tinput.Format = fastly.ToPointer(argparser.Content(c.Format.Value))\n\t}\n\tif c.FormatVersion.WasSet {\n\t\tinput.FormatVersion = &c.FormatVersion.Value\n\t}\n\tif c.Placement.WasSet {\n\t\tinput.Placement = &c.Placement.Value\n\t}\n\tif c.ProcessingRegion.WasSet {\n\t\tinput.ProcessingRegion = &c.ProcessingRegion.Value\n\t}\n\tif c.ProjectID.WasSet {\n\t\tinput.ProjectID = &c.ProjectID.Value\n\t}\n\tif c.ResponseCondition.WasSet {\n\t\tinput.ResponseCondition = &c.ResponseCondition.Value\n\t}\n\tif c.SecretKey.WasSet {\n\t\tinput.SecretKey = &c.SecretKey.Value\n\t}\n\tif c.Table.WasSet {\n\t\tinput.Table = &c.Table.Value\n\t}\n\tif c.Template.WasSet {\n\t\tinput.Template = &c.Template.Value\n\t}\n\tif c.User.WasSet {\n\t\tinput.User = &c.User.Value\n\t}\n\n\treturn &input, nil\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *CreateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(false),\n\t\tLocked:             optional.Of(false),\n\t\tAutoCloneFlag:      c.AutoClone,\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.ServiceName,\n\t\tServiceVersionFlag: c.ServiceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fsterr.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tinput, err := c.ConstructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fastly.ToValue(serviceVersion.Number),\n\t\t})\n\t\treturn err\n\t}\n\n\td, err := c.Globals.APIClient.CreateBigQuery(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fastly.ToValue(serviceVersion.Number),\n\t\t})\n\t\treturn err\n\t}\n\n\ttext.Success(\n\t\tout,\n\t\t\"Created BigQuery logging endpoint %s (service %s version %d)\",\n\t\tfastly.ToValue(d.Name),\n\t\tfastly.ToValue(d.ServiceID),\n\t\tfastly.ToValue(d.ServiceVersion),\n\t)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/bigquery/delete.go",
    "content": "package bigquery\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DeleteCommand calls the Fastly API to delete a BigQuery logging endpoint.\ntype DeleteCommand struct {\n\targparser.Base\n\tInput          fastly.DeleteBigQueryInput\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n\tautoClone      argparser.OptionalAutoClone\n}\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"delete\", \"Delete a BigQuery logging endpoint on a Fastly service version\").Alias(\"remove\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"The name of the BigQuery logging object\").Short('n').Required().StringVar(&c.Input.Name)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.autoClone.Set,\n\t\tDst:    &c.autoClone.Value,\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DeleteCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(false),\n\t\tLocked:             optional.Of(false),\n\t\tAutoCloneFlag:      c.autoClone,\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": errors.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tc.Input.ServiceID = serviceID\n\tc.Input.ServiceVersion = fastly.ToValue(serviceVersion.Number)\n\n\tif err := c.Globals.APIClient.DeleteBigQuery(context.TODO(), &c.Input); err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fastly.ToValue(serviceVersion.Number),\n\t\t})\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Deleted BigQuery logging endpoint %s (service %s version %d)\", c.Input.Name, c.Input.ServiceID, c.Input.ServiceVersion)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/bigquery/describe.go",
    "content": "package bigquery\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DescribeCommand calls the Fastly API to describe a BigQuery logging endpoint.\ntype DescribeCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tInput          fastly.GetBigQueryInput\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n}\n\n// NewDescribeCommand returns a usable command registered under the parent.\nfunc NewDescribeCommand(parent argparser.Registerer, g *global.Data) *DescribeCommand {\n\tc := DescribeCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"describe\", \"Show detailed information about a BigQuery logging endpoint on a Fastly service version\").Alias(\"get\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"The name of the BigQuery logging object\").Short('n').Required().StringVar(&c.Input.Name)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DescribeCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fsterr.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tc.Input.ServiceID = serviceID\n\tc.Input.ServiceVersion = fastly.ToValue(serviceVersion.Number)\n\n\to, err := c.Globals.APIClient.GetBigQuery(context.TODO(), &c.Input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fastly.ToValue(serviceVersion.Number),\n\t\t})\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\tlines := text.Lines{\n\t\t\"Account name\":       fastly.ToValue(o.AccountName),\n\t\t\"Dataset\":            fastly.ToValue(o.Dataset),\n\t\t\"Format version\":     fastly.ToValue(o.FormatVersion),\n\t\t\"Format\":             fastly.ToValue(o.Format),\n\t\t\"Name\":               fastly.ToValue(o.Name),\n\t\t\"Placement\":          fastly.ToValue(o.Placement),\n\t\t\"Processing region\":  fastly.ToValue(o.ProcessingRegion),\n\t\t\"Project ID\":         fastly.ToValue(o.ProjectID),\n\t\t\"Response condition\": fastly.ToValue(o.ResponseCondition),\n\t\t\"Secret key\":         fastly.ToValue(o.SecretKey),\n\t\t\"Table\":              fastly.ToValue(o.Table),\n\t\t\"Template suffix\":    fastly.ToValue(o.Template),\n\t\t\"User\":               fastly.ToValue(o.User),\n\t\t\"Version\":            fastly.ToValue(o.ServiceVersion),\n\t}\n\tif !c.Globals.Verbose() {\n\t\tlines[\"Service ID\"] = fastly.ToValue(o.ServiceID)\n\t}\n\ttext.PrintLines(out, lines)\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/bigquery/doc.go",
    "content": "// Package bigquery contains commands to inspect and manipulate Fastly service\n// BigQuery logging endpoints.\npackage bigquery\n"
  },
  {
    "path": "pkg/commands/service/logging/bigquery/list.go",
    "content": "package bigquery\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// ListCommand calls the Fastly API to list BigQuery logging endpoints.\ntype ListCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tInput          fastly.ListBigQueriesInput\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n}\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"list\", \"List BigQuery endpoints on a Fastly service version\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *ListCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fsterr.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tc.Input.ServiceID = serviceID\n\tc.Input.ServiceVersion = fastly.ToValue(serviceVersion.Number)\n\n\to, err := c.Globals.APIClient.ListBigQueries(context.TODO(), &c.Input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fastly.ToValue(serviceVersion.Number),\n\t\t})\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\tif !c.Globals.Verbose() {\n\t\ttw := text.NewTable(out)\n\t\ttw.AddHeader(\"SERVICE\", \"VERSION\", \"NAME\")\n\t\tfor _, bq := range o {\n\t\t\ttw.AddLine(\n\t\t\t\tfastly.ToValue(bq.ServiceID),\n\t\t\t\tfastly.ToValue(bq.ServiceVersion),\n\t\t\t\tfastly.ToValue(bq.Name),\n\t\t\t)\n\t\t}\n\t\ttw.Print()\n\t\treturn nil\n\t}\n\n\tfmt.Fprintf(out, \"Version: %d\\n\", c.Input.ServiceVersion)\n\tfor i, bq := range o {\n\t\tfmt.Fprintf(out, \"\\tBigQuery %d/%d\\n\", i+1, len(o))\n\t\tfmt.Fprintf(out, \"\\t\\tService ID: %s\\n\", fastly.ToValue(bq.ServiceID))\n\t\tfmt.Fprintf(out, \"\\t\\tVersion: %d\\n\", fastly.ToValue(bq.ServiceVersion))\n\t\tfmt.Fprintf(out, \"\\t\\tName: %s\\n\", fastly.ToValue(bq.Name))\n\t\tfmt.Fprintf(out, \"\\t\\tFormat: %s\\n\", fastly.ToValue(bq.Format))\n\t\tfmt.Fprintf(out, \"\\t\\tUser: %s\\n\", fastly.ToValue(bq.User))\n\t\tfmt.Fprintf(out, \"\\t\\tAccount name: %s\\n\", fastly.ToValue(bq.AccountName))\n\t\tfmt.Fprintf(out, \"\\t\\tProject ID: %s\\n\", fastly.ToValue(bq.ProjectID))\n\t\tfmt.Fprintf(out, \"\\t\\tDataset: %s\\n\", fastly.ToValue(bq.Dataset))\n\t\tfmt.Fprintf(out, \"\\t\\tTable: %s\\n\", fastly.ToValue(bq.Table))\n\t\tfmt.Fprintf(out, \"\\t\\tTemplate suffix: %s\\n\", fastly.ToValue(bq.Template))\n\t\tfmt.Fprintf(out, \"\\t\\tSecret key: %s\\n\", fastly.ToValue(bq.SecretKey))\n\t\tfmt.Fprintf(out, \"\\t\\tResponse condition: %s\\n\", fastly.ToValue(bq.ResponseCondition))\n\t\tfmt.Fprintf(out, \"\\t\\tPlacement: %s\\n\", fastly.ToValue(bq.Placement))\n\t\tfmt.Fprintf(out, \"\\t\\tFormat version: %d\\n\", fastly.ToValue(bq.FormatVersion))\n\t\tfmt.Fprintf(out, \"\\t\\tProcessing region: %s\\n\", fastly.ToValue(bq.ProcessingRegion))\n\t}\n\tfmt.Fprintln(out)\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/bigquery/root.go",
    "content": "package bigquery\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"bigquery\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manipulate Fastly service version BigQuery logging endpoints\")\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/bigquery/update.go",
    "content": "package bigquery\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/service/logging/logflags\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/manifest\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// UpdateCommand calls the Fastly API to update a BigQuery logging endpoint.\ntype UpdateCommand struct {\n\targparser.Base\n\tManifest manifest.Data\n\n\t// Required.\n\tEndpointName   string // Can't shadow argparser.Base method Name().\n\tServiceName    argparser.OptionalServiceNameID\n\tServiceVersion argparser.OptionalServiceVersion\n\n\t// Optional.\n\tAccountName       argparser.OptionalString\n\tAutoClone         argparser.OptionalAutoClone\n\tDataset           argparser.OptionalString\n\tFormat            argparser.OptionalString\n\tFormatVersion     argparser.OptionalInt\n\tNewName           argparser.OptionalString\n\tPlacement         argparser.OptionalString\n\tProcessingRegion  argparser.OptionalString\n\tProjectID         argparser.OptionalString\n\tResponseCondition argparser.OptionalString\n\tSecretKey         argparser.OptionalString\n\tTable             argparser.OptionalString\n\tTemplate          argparser.OptionalString\n\tUser              argparser.OptionalString\n}\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"update\", \"Update a BigQuery logging endpoint on a Fastly service version\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"The name of the BigQuery logging object\").Short('n').Required().StringVar(&c.EndpointName)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.ServiceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tlogflags.AccountName(c.CmdClause, &c.AccountName)\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.AutoClone.Set,\n\t\tDst:    &c.AutoClone.Value,\n\t})\n\tc.CmdClause.Flag(\"dataset\", \"Your BigQuery dataset\").Action(c.Dataset.Set).StringVar(&c.Dataset.Value)\n\tlogflags.Format(c.CmdClause, &c.Format)\n\tlogflags.FormatVersion(c.CmdClause, &c.FormatVersion)\n\tc.CmdClause.Flag(\"new-name\", \"New name of the BigQuery logging object\").Action(c.NewName.Set).StringVar(&c.NewName.Value)\n\tlogflags.Placement(c.CmdClause, &c.Placement)\n\tlogflags.ProcessingRegion(c.CmdClause, &c.ProcessingRegion, \"BigQuery\")\n\tc.CmdClause.Flag(\"project-id\", \"Your Google Cloud Platform project ID\").Action(c.ProjectID.Set).StringVar(&c.ProjectID.Value)\n\tlogflags.ResponseCondition(c.CmdClause, &c.ResponseCondition)\n\tc.CmdClause.Flag(\"secret-key\", \"Your Google Cloud Platform account secret key. The private_key field in your service account authentication JSON.\").Action(c.SecretKey.Set).StringVar(&c.SecretKey.Value)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.ServiceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.ServiceName.Value,\n\t})\n\tc.CmdClause.Flag(\"table\", \"Your BigQuery table\").Action(c.Table.Set).StringVar(&c.Table.Value)\n\tc.CmdClause.Flag(\"template-suffix\", \"BigQuery table name suffix template\").Action(c.Template.Set).StringVar(&c.Template.Value)\n\tc.CmdClause.Flag(\"user\", \"Your Google Cloud Platform service account email address. The client_email field in your service account authentication JSON.\").Action(c.User.Set).StringVar(&c.User.Value)\n\treturn &c\n}\n\n// ConstructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *UpdateCommand) ConstructInput(serviceID string, serviceVersion int) (*fastly.UpdateBigQueryInput, error) {\n\tinput := fastly.UpdateBigQueryInput{\n\t\tServiceID:      serviceID,\n\t\tServiceVersion: serviceVersion,\n\t\tName:           c.EndpointName,\n\t}\n\n\tif c.AccountName.WasSet {\n\t\tinput.AccountName = &c.AccountName.Value\n\t}\n\tif c.Dataset.WasSet {\n\t\tinput.Dataset = &c.Dataset.Value\n\t}\n\tif c.Format.WasSet {\n\t\tinput.Format = fastly.ToPointer(argparser.Content(c.Format.Value))\n\t}\n\tif c.FormatVersion.WasSet {\n\t\tinput.FormatVersion = &c.FormatVersion.Value\n\t}\n\tif c.NewName.WasSet {\n\t\tinput.NewName = &c.NewName.Value\n\t}\n\tif c.Placement.WasSet {\n\t\tinput.Placement = &c.Placement.Value\n\t}\n\tif c.ProcessingRegion.WasSet {\n\t\tinput.ProcessingRegion = &c.ProcessingRegion.Value\n\t}\n\tif c.ProjectID.WasSet {\n\t\tinput.ProjectID = &c.ProjectID.Value\n\t}\n\tif c.ResponseCondition.WasSet {\n\t\tinput.ResponseCondition = &c.ResponseCondition.Value\n\t}\n\tif c.SecretKey.WasSet {\n\t\tinput.SecretKey = &c.SecretKey.Value\n\t}\n\tif c.Table.WasSet {\n\t\tinput.Table = &c.Table.Value\n\t}\n\tif c.Template.WasSet {\n\t\tinput.Template = &c.Template.Value\n\t}\n\tif c.User.WasSet {\n\t\tinput.User = &c.User.Value\n\t}\n\n\treturn &input, nil\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *UpdateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(false),\n\t\tLocked:             optional.Of(false),\n\t\tAutoCloneFlag:      c.AutoClone,\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.ServiceName,\n\t\tServiceVersionFlag: c.ServiceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fsterr.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tinput, err := c.ConstructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fastly.ToValue(serviceVersion.Number),\n\t\t})\n\t\treturn err\n\t}\n\n\tbq, err := c.Globals.APIClient.UpdateBigQuery(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fastly.ToValue(serviceVersion.Number),\n\t\t})\n\t\treturn err\n\t}\n\n\ttext.Success(\n\t\tout,\n\t\t\"Updated BigQuery logging endpoint %s (service %s version %d)\",\n\t\tfastly.ToValue(bq.Name),\n\t\tfastly.ToValue(bq.ServiceID),\n\t\tfastly.ToValue(bq.ServiceVersion),\n\t)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/cloudfiles/cloudfiles_integration_test.go",
    "content": "package cloudfiles_test\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/mock\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n\n\troot \"github.com/fastly/cli/pkg/commands/service\"\n\tparent \"github.com/fastly/cli/pkg/commands/service/logging\"\n\tsub \"github.com/fastly/cli/pkg/commands/service/logging/cloudfiles\"\n)\n\nfunc TestCloudfilesCreate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name log --user username --bucket log --access-key foo --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:       testutil.GetVersion,\n\t\t\t\tCloneVersionFn:     testutil.CloneVersionResult(4),\n\t\t\t\tCreateCloudfilesFn: createCloudfilesOK,\n\t\t\t},\n\t\t\tWantOutput: \"Created Cloudfiles logging endpoint log (service 123 version 4)\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name log --user username --bucket log --access-key foo --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:       testutil.GetVersion,\n\t\t\t\tCloneVersionFn:     testutil.CloneVersionResult(4),\n\t\t\t\tCreateCloudfilesFn: createCloudfilesError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name log --user username --bucket log --access-key foo --compression-codec zstd --gzip-level 9 --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t},\n\t\t\tWantError: \"error parsing arguments: the --compression-codec flag is mutually exclusive with the --gzip-level flag\",\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"create\"}, scenarios)\n}\n\nfunc TestCloudfilesList(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:     testutil.GetVersion,\n\t\t\t\tListCloudfilesFn: listCloudfilesOK,\n\t\t\t},\n\t\t\tWantOutput: listCloudfilesShortOutput,\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --verbose\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:     testutil.GetVersion,\n\t\t\t\tListCloudfilesFn: listCloudfilesOK,\n\t\t\t},\n\t\t\tWantOutput: listCloudfilesVerboseOutput,\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 -v\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:     testutil.GetVersion,\n\t\t\t\tListCloudfilesFn: listCloudfilesOK,\n\t\t\t},\n\t\t\tWantOutput: listCloudfilesVerboseOutput,\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:     testutil.GetVersion,\n\t\t\t\tListCloudfilesFn: listCloudfilesError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"list\"}, scenarios)\n}\n\nfunc TestCloudfilesDescribe(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"--service-id 123 --version 1\",\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:    testutil.GetVersion,\n\t\t\t\tGetCloudfilesFn: getCloudfilesError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:    testutil.GetVersion,\n\t\t\t\tGetCloudfilesFn: getCloudfilesOK,\n\t\t\t},\n\t\t\tWantOutput: describeCloudfilesOutput,\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"describe\"}, scenarios)\n}\n\nfunc TestCloudfilesUpdate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"--service-id 123 --version 1 --new-name log\",\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs --new-name log --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:       testutil.GetVersion,\n\t\t\t\tCloneVersionFn:     testutil.CloneVersionResult(4),\n\t\t\t\tUpdateCloudfilesFn: updateCloudfilesError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs --new-name log --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:       testutil.GetVersion,\n\t\t\t\tCloneVersionFn:     testutil.CloneVersionResult(4),\n\t\t\t\tUpdateCloudfilesFn: updateCloudfilesOK,\n\t\t\t},\n\t\t\tWantOutput: \"Updated Cloudfiles logging endpoint log (service 123 version 4)\",\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"update\"}, scenarios)\n}\n\nfunc TestCloudfilesDelete(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"--service-id 123 --version 1\",\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:       testutil.GetVersion,\n\t\t\t\tCloneVersionFn:     testutil.CloneVersionResult(4),\n\t\t\t\tDeleteCloudfilesFn: deleteCloudfilesError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:       testutil.GetVersion,\n\t\t\t\tCloneVersionFn:     testutil.CloneVersionResult(4),\n\t\t\t\tDeleteCloudfilesFn: deleteCloudfilesOK,\n\t\t\t},\n\t\t\tWantOutput: \"Deleted Cloudfiles logging endpoint logs (service 123 version 4)\",\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"delete\"}, scenarios)\n}\n\nvar errTest = errors.New(\"fixture error\")\n\nfunc createCloudfilesOK(_ context.Context, i *fastly.CreateCloudfilesInput) (*fastly.Cloudfiles, error) {\n\ts := fastly.Cloudfiles{\n\t\tServiceID:      fastly.ToPointer(i.ServiceID),\n\t\tServiceVersion: fastly.ToPointer(i.ServiceVersion),\n\t}\n\n\tif i.Name != nil {\n\t\ts.Name = i.Name\n\t}\n\n\treturn &s, nil\n}\n\nfunc createCloudfilesError(_ context.Context, _ *fastly.CreateCloudfilesInput) (*fastly.Cloudfiles, error) {\n\treturn nil, errTest\n}\n\nfunc listCloudfilesOK(_ context.Context, i *fastly.ListCloudfilesInput) ([]*fastly.Cloudfiles, error) {\n\treturn []*fastly.Cloudfiles{\n\t\t{\n\t\t\tServiceID:         fastly.ToPointer(i.ServiceID),\n\t\t\tServiceVersion:    fastly.ToPointer(i.ServiceVersion),\n\t\t\tName:              fastly.ToPointer(\"logs\"),\n\t\t\tUser:              fastly.ToPointer(\"username\"),\n\t\t\tAccessKey:         fastly.ToPointer(\"1234\"),\n\t\t\tBucketName:        fastly.ToPointer(\"my-logs\"),\n\t\t\tPath:              fastly.ToPointer(\"logs/\"),\n\t\t\tRegion:            fastly.ToPointer(\"ORD\"),\n\t\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\t\tPeriod:            fastly.ToPointer(3600),\n\t\t\tGzipLevel:         fastly.ToPointer(9),\n\t\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\t\tFormatVersion:     fastly.ToPointer(2),\n\t\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\t\tMessageType:       fastly.ToPointer(\"classic\"),\n\t\t\tTimestampFormat:   fastly.ToPointer(\"%Y-%m-%dT%H:%M:%S.000\"),\n\t\t\tPublicKey:         fastly.ToPointer(pgpPublicKey()),\n\t\t\tProcessingRegion:  fastly.ToPointer(\"us\"),\n\t\t},\n\t\t{\n\t\t\tServiceID:         fastly.ToPointer(i.ServiceID),\n\t\t\tServiceVersion:    fastly.ToPointer(i.ServiceVersion),\n\t\t\tName:              fastly.ToPointer(\"analytics\"),\n\t\t\tUser:              fastly.ToPointer(\"username\"),\n\t\t\tAccessKey:         fastly.ToPointer(\"1234\"),\n\t\t\tBucketName:        fastly.ToPointer(\"analytics\"),\n\t\t\tPath:              fastly.ToPointer(\"logs/\"),\n\t\t\tRegion:            fastly.ToPointer(\"ORD\"),\n\t\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\t\tPeriod:            fastly.ToPointer(86400),\n\t\t\tGzipLevel:         fastly.ToPointer(9),\n\t\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\t\tFormatVersion:     fastly.ToPointer(2),\n\t\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\t\tMessageType:       fastly.ToPointer(\"classic\"),\n\t\t\tTimestampFormat:   fastly.ToPointer(\"%Y-%m-%dT%H:%M:%S.000\"),\n\t\t\tPublicKey:         fastly.ToPointer(pgpPublicKey()),\n\t\t\tProcessingRegion:  fastly.ToPointer(\"us\"),\n\t\t},\n\t}, nil\n}\n\nfunc listCloudfilesError(_ context.Context, _ *fastly.ListCloudfilesInput) ([]*fastly.Cloudfiles, error) {\n\treturn nil, errTest\n}\n\nvar listCloudfilesShortOutput = strings.TrimSpace(`\nSERVICE  VERSION  NAME\n123      1        logs\n123      1        analytics\n`) + \"\\n\"\n\nvar listCloudfilesVerboseOutput = strings.TrimSpace(`\nFastly API endpoint: https://api.fastly.com\nFastly API token provided via config file (auth: user)\n\nService ID (via --service-id): 123\n\nVersion: 1\n\tCloudfiles 1/2\n\t\tService ID: 123\n\t\tVersion: 1\n\t\tName: logs\n\t\tUser: username\n\t\tAccess key: 1234\n\t\tBucket: my-logs\n\t\tPath: logs/\n\t\tRegion: ORD\n\t\tPlacement: none\n\t\tPeriod: 3600\n\t\tGZip level: 9\n\t\tFormat: %h %l %u %t \"%r\" %>s %b\n\t\tFormat version: 2\n\t\tResponse condition: Prevent default logging\n\t\tMessage type: classic\n\t\tTimestamp format: %Y-%m-%dT%H:%M:%S.000\n\t\tPublic key: `+pgpPublicKey()+`\n\t\tProcessing region: us\n\tCloudfiles 2/2\n\t\tService ID: 123\n\t\tVersion: 1\n\t\tName: analytics\n\t\tUser: username\n\t\tAccess key: 1234\n\t\tBucket: analytics\n\t\tPath: logs/\n\t\tRegion: ORD\n\t\tPlacement: none\n\t\tPeriod: 86400\n\t\tGZip level: 9\n\t\tFormat: %h %l %u %t \"%r\" %>s %b\n\t\tFormat version: 2\n\t\tResponse condition: Prevent default logging\n\t\tMessage type: classic\n\t\tTimestamp format: %Y-%m-%dT%H:%M:%S.000\n\t\tPublic key: `+pgpPublicKey()+`\n\t\tProcessing region: us\n`) + \"\\n\\n\"\n\nfunc getCloudfilesOK(_ context.Context, i *fastly.GetCloudfilesInput) (*fastly.Cloudfiles, error) {\n\treturn &fastly.Cloudfiles{\n\t\tServiceID:         fastly.ToPointer(i.ServiceID),\n\t\tServiceVersion:    fastly.ToPointer(i.ServiceVersion),\n\t\tName:              fastly.ToPointer(\"logs\"),\n\t\tUser:              fastly.ToPointer(\"username\"),\n\t\tAccessKey:         fastly.ToPointer(\"1234\"),\n\t\tBucketName:        fastly.ToPointer(\"my-logs\"),\n\t\tPath:              fastly.ToPointer(\"logs/\"),\n\t\tRegion:            fastly.ToPointer(\"ORD\"),\n\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\tPeriod:            fastly.ToPointer(3600),\n\t\tProcessingRegion:  fastly.ToPointer(\"us\"),\n\t\tGzipLevel:         fastly.ToPointer(9),\n\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\tFormatVersion:     fastly.ToPointer(2),\n\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\tMessageType:       fastly.ToPointer(\"classic\"),\n\t\tTimestampFormat:   fastly.ToPointer(\"%Y-%m-%dT%H:%M:%S.000\"),\n\t\tPublicKey:         fastly.ToPointer(pgpPublicKey()),\n\t}, nil\n}\n\nfunc getCloudfilesError(_ context.Context, _ *fastly.GetCloudfilesInput) (*fastly.Cloudfiles, error) {\n\treturn nil, errTest\n}\n\nvar describeCloudfilesOutput = \"\\n\" + strings.TrimSpace(`\nAccess key: 1234\nBucket: my-logs\nFormat: %h %l %u %t \"%r\" %>s %b\nFormat version: 2\nGZip level: 9\nMessage type: classic\nName: logs\nPath: logs/\nPeriod: 3600\nPlacement: none\nProcessing region: us\nPublic key: `+pgpPublicKey()+`\nRegion: ORD\nResponse condition: Prevent default logging\nService ID: 123\nTimestamp format: %Y-%m-%dT%H:%M:%S.000\nUser: username\nVersion: 1\n`) + \"\\n\"\n\nfunc updateCloudfilesOK(_ context.Context, i *fastly.UpdateCloudfilesInput) (*fastly.Cloudfiles, error) {\n\treturn &fastly.Cloudfiles{\n\t\tServiceID:         fastly.ToPointer(i.ServiceID),\n\t\tServiceVersion:    fastly.ToPointer(i.ServiceVersion),\n\t\tName:              fastly.ToPointer(\"log\"),\n\t\tUser:              fastly.ToPointer(\"username\"),\n\t\tAccessKey:         fastly.ToPointer(\"1234\"),\n\t\tBucketName:        fastly.ToPointer(\"my-logs\"),\n\t\tPath:              fastly.ToPointer(\"logs/\"),\n\t\tRegion:            fastly.ToPointer(\"ORD\"),\n\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\tPeriod:            fastly.ToPointer(3600),\n\t\tGzipLevel:         fastly.ToPointer(9),\n\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\tFormatVersion:     fastly.ToPointer(2),\n\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\tMessageType:       fastly.ToPointer(\"classic\"),\n\t\tTimestampFormat:   fastly.ToPointer(\"%Y-%m-%dT%H:%M:%S.000\"),\n\t\tPublicKey:         fastly.ToPointer(pgpPublicKey()),\n\t}, nil\n}\n\nfunc updateCloudfilesError(_ context.Context, _ *fastly.UpdateCloudfilesInput) (*fastly.Cloudfiles, error) {\n\treturn nil, errTest\n}\n\nfunc deleteCloudfilesOK(_ context.Context, _ *fastly.DeleteCloudfilesInput) error {\n\treturn nil\n}\n\nfunc deleteCloudfilesError(_ context.Context, _ *fastly.DeleteCloudfilesInput) error {\n\treturn errTest\n}\n\n// pgpPublicKey returns a PEM encoded PGP public key suitable for testing.\nfunc pgpPublicKey() string {\n\treturn strings.TrimSpace(`-----BEGIN PGP PUBLIC KEY BLOCK-----\nmQENBFyUD8sBCACyFnB39AuuTygseek+eA4fo0cgwva6/FSjnWq7riouQee8GgQ/\nibXTRyv4iVlwI12GswvMTIy7zNvs1R54i0qvsLr+IZ4GVGJqs6ZJnvQcqe3xPoR4\n8AnBfw90o32r/LuHf6QCJXi+AEu35koNlNAvLJ2B+KACaNB7N0EeWmqpV/1V2k9p\nlDYk+th7LcCuaFNGqKS/PrMnnMqR6VDLCjHhNx4KR79b0Twm/2qp6an3hyNRu8Gn\ndwxpf1/BUu3JWf+LqkN4Y3mbOmSUL3MaJNvyQguUzTfS0P0uGuBDHrJCVkMZCzDB\n89ag55jCPHyGeHBTd02gHMWzsg3WMBWvCsrzABEBAAG0JXRlcnJhZm9ybSAodGVz\ndCkgPHRlc3RAdGVycmFmb3JtLmNvbT6JAU4EEwEIADgWIQSHYyc6Kj9l6HzQsau6\nvFFc9jxV/wUCXJQPywIbAwULCQgHAgYVCgkICwIEFgIDAQIeAQIXgAAKCRC6vFFc\n9jxV/815CAClb32OxV7wG01yF97TzlyTl8TnvjMtoG29Mw4nSyg+mjM3b8N7iXm9\nOLX59fbDAWtBSldSZE22RXd3CvlFOG/EnKBXSjBtEqfyxYSnyOPkMPBYWGL/ApkX\nSvPYJ4LKdvipYToKFh3y9kk2gk1DcDBDyaaHvR+3rv1u3aoy7/s2EltAfDS3ZQIq\n7/cWTLJml/lleeB/Y6rPj8xqeCYhE5ahw9gsV/Mdqatl24V9Tks30iijx0Hhw+Gx\nkATUikMGr2GDVqoIRga5kXI7CzYff4rkc0Twn47fMHHHe/KY9M2yVnMHUXmAZwbG\nM1cMI/NH1DjevCKdGBLcRJlhuLPKF/anuQENBFyUD8sBCADIpd7r7GuPd6n/Ikxe\nu6h7umV6IIPoAm88xCYpTbSZiaK30Svh6Ywra9jfE2KlU9o6Y/art8ip0VJ3m07L\n4RSfSpnzqgSwdjSq5hNour2Fo/BzYhK7yaz2AzVSbe33R0+RYhb4b/6N+bKbjwGF\nftCsqVFMH+PyvYkLbvxyQrHlA9woAZaNThI1ztO5rGSnGUR8xt84eup28WIFKg0K\nUEGUcTzz+8QGAwAra+0ewPXo/AkO+8BvZjDidP417u6gpBHOJ9qYIcO9FxHeqFyu\nYrjlrxowEgXn5wO8xuNz6Vu1vhHGDHGDsRbZF8pv1d5O+0F1G7ttZ2GRRgVBZPwi\nkiyRABEBAAGJATYEGAEIACAWIQSHYyc6Kj9l6HzQsau6vFFc9jxV/wUCXJQPywIb\nDAAKCRC6vFFc9jxV/9YOCACe8qmOSnKQpQfW+PqYOqo3dt7JyweTs3FkD6NT8Zml\ndYy/vkstbTjPpX6aTvUZjkb46BVi7AOneVHpD5GBqvRsZ9iVgDYHaehmLCdKiG5L\n3Tp90NN+QY5WDbsGmsyk6+6ZMYejb4qYfweQeduOj27aavCJdLkCYMoRKfcFYI8c\nFaNmEfKKy/r1PO20NXEG6t9t05K/frHy6ZG8bCNYdpagfFVot47r9JaQqWlTNtIR\n5+zkkSq/eG9BEtRij3a6cTdQbktdBzx2KBeI0PYc1vlZR0LpuFKZqY9vlE6vTGLR\nwMfrTEOvx0NxUM3rpaCgEmuWbB1G1Hu371oyr4srrr+N\n=28dr\n-----END PGP PUBLIC KEY BLOCK-----\n`)\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/cloudfiles/cloudfiles_test.go",
    "content": "package cloudfiles_test\n\nimport (\n\t\"bytes\"\n\t\"testing\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/service/logging/cloudfiles\"\n\t\"github.com/fastly/cli/pkg/config\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/manifest\"\n\t\"github.com/fastly/cli/pkg/mock\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n)\n\nfunc TestCreateCloudfilesInput(t *testing.T) {\n\tfor _, testcase := range []struct {\n\t\tname      string\n\t\tcmd       *cloudfiles.CreateCommand\n\t\twant      *fastly.CreateCloudfilesInput\n\t\twantError string\n\t}{\n\t\t{\n\t\t\tname: \"required values set flag serviceID\",\n\t\t\tcmd:  createCommandRequired(),\n\t\t\twant: &fastly.CreateCloudfilesInput{\n\t\t\t\tServiceID:      \"123\",\n\t\t\t\tServiceVersion: 4,\n\t\t\t\tName:           fastly.ToPointer(\"log\"),\n\t\t\t\tUser:           fastly.ToPointer(\"user\"),\n\t\t\t\tAccessKey:      fastly.ToPointer(\"key\"),\n\t\t\t\tBucketName:     fastly.ToPointer(\"bucket\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"all values set flag serviceID\",\n\t\t\tcmd:  createCommandAll(),\n\t\t\twant: &fastly.CreateCloudfilesInput{\n\t\t\t\tServiceID:         \"123\",\n\t\t\t\tServiceVersion:    4,\n\t\t\t\tName:              fastly.ToPointer(\"log\"),\n\t\t\t\tUser:              fastly.ToPointer(\"user\"),\n\t\t\t\tAccessKey:         fastly.ToPointer(\"key\"),\n\t\t\t\tBucketName:        fastly.ToPointer(\"bucket\"),\n\t\t\t\tPath:              fastly.ToPointer(\"/logs\"),\n\t\t\t\tRegion:            fastly.ToPointer(\"abc\"),\n\t\t\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\t\t\tPeriod:            fastly.ToPointer(3600),\n\t\t\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\t\t\tFormatVersion:     fastly.ToPointer(2),\n\t\t\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\t\t\tMessageType:       fastly.ToPointer(\"classic\"),\n\t\t\t\tTimestampFormat:   fastly.ToPointer(\"%Y-%m-%dT%H:%M:%S.000\"),\n\t\t\t\tPublicKey:         fastly.ToPointer(pgpPublicKey()),\n\t\t\t\tCompressionCodec:  fastly.ToPointer(\"zstd\"),\n\t\t\t\tProcessingRegion:  fastly.ToPointer(\"eu\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:      \"error missing serviceID\",\n\t\t\tcmd:       createCommandMissingServiceID(),\n\t\t\twant:      nil,\n\t\t\twantError: errors.ErrNoServiceID.Error(),\n\t\t},\n\t} {\n\t\tt.Run(testcase.name, func(t *testing.T) {\n\t\t\tif testcase.wantError == errors.ErrNoServiceID.Error() {\n\t\t\t\tt.Setenv(\"FASTLY_SERVICE_ID\", \"\")\n\t\t\t}\n\t\t\tvar bs []byte\n\t\t\tout := bytes.NewBuffer(bs)\n\t\t\tverboseMode := true\n\n\t\t\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\t\t\tAutoCloneFlag:      testcase.cmd.AutoClone,\n\t\t\t\tAPIClient:          testcase.cmd.Globals.APIClient,\n\t\t\t\tManifest:           testcase.cmd.Manifest,\n\t\t\t\tOut:                out,\n\t\t\t\tServiceVersionFlag: testcase.cmd.ServiceVersion,\n\t\t\t\tVerboseMode:        verboseMode,\n\t\t\t})\n\n\t\t\tswitch {\n\t\t\tcase err != nil && testcase.wantError == \"\":\n\t\t\t\tt.Fatalf(\"unexpected error getting service details: %v\", err)\n\t\t\t\treturn\n\t\t\tcase err != nil && testcase.wantError != \"\":\n\t\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\t\treturn\n\t\t\tcase err == nil && testcase.wantError != \"\":\n\t\t\t\tt.Fatalf(\"expected error, have nil (service details: %s, %d)\", serviceID, serviceVersion.Number)\n\t\t\tcase err == nil && testcase.wantError == \"\":\n\t\t\t\thave, err := testcase.cmd.ConstructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\t\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\t\ttestutil.AssertEqual(t, testcase.want, have)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestUpdateCloudfilesInput(t *testing.T) {\n\tscenarios := []struct {\n\t\tname      string\n\t\tcmd       *cloudfiles.UpdateCommand\n\t\tapi       mock.API\n\t\twant      *fastly.UpdateCloudfilesInput\n\t\twantError string\n\t}{\n\t\t{\n\t\t\tname: \"no update\",\n\t\t\tcmd:  updateCommandNoUpdate(),\n\t\t\tapi: mock.API{\n\t\t\t\tGetVersionFn:    testutil.GetVersion,\n\t\t\t\tCloneVersionFn:  testutil.CloneVersionResult(4),\n\t\t\t\tGetCloudfilesFn: getCloudfilesOK,\n\t\t\t},\n\t\t\twant: &fastly.UpdateCloudfilesInput{\n\t\t\t\tServiceID:      \"123\",\n\t\t\t\tServiceVersion: 4,\n\t\t\t\tName:           \"log\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"all values set flag serviceID\",\n\t\t\tcmd:  updateCommandAll(),\n\t\t\tapi: mock.API{\n\t\t\t\tGetVersionFn:    testutil.GetVersion,\n\t\t\t\tCloneVersionFn:  testutil.CloneVersionResult(4),\n\t\t\t\tGetCloudfilesFn: getCloudfilesOK,\n\t\t\t},\n\t\t\twant: &fastly.UpdateCloudfilesInput{\n\t\t\t\tServiceID:         \"123\",\n\t\t\t\tServiceVersion:    4,\n\t\t\t\tName:              \"log\",\n\t\t\t\tNewName:           fastly.ToPointer(\"new1\"),\n\t\t\t\tAccessKey:         fastly.ToPointer(\"new2\"),\n\t\t\t\tBucketName:        fastly.ToPointer(\"new3\"),\n\t\t\t\tPath:              fastly.ToPointer(\"new4\"),\n\t\t\t\tRegion:            fastly.ToPointer(\"new5\"),\n\t\t\t\tPlacement:         fastly.ToPointer(\"new6\"),\n\t\t\t\tPeriod:            fastly.ToPointer(3601),\n\t\t\t\tGzipLevel:         fastly.ToPointer(0),\n\t\t\t\tFormat:            fastly.ToPointer(\"new7\"),\n\t\t\t\tFormatVersion:     fastly.ToPointer(3),\n\t\t\t\tResponseCondition: fastly.ToPointer(\"new8\"),\n\t\t\t\tMessageType:       fastly.ToPointer(\"new9\"),\n\t\t\t\tTimestampFormat:   fastly.ToPointer(\"new10\"),\n\t\t\t\tPublicKey:         fastly.ToPointer(\"new11\"),\n\t\t\t\tUser:              fastly.ToPointer(\"new12\"),\n\t\t\t\tCompressionCodec:  fastly.ToPointer(\"new13\"),\n\t\t\t\tProcessingRegion:  fastly.ToPointer(\"eu\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:      \"error missing serviceID\",\n\t\t\tcmd:       updateCommandMissingServiceID(),\n\t\t\twant:      nil,\n\t\t\twantError: errors.ErrNoServiceID.Error(),\n\t\t},\n\t}\n\tfor testcaseIdx := range scenarios {\n\t\ttestcase := &scenarios[testcaseIdx]\n\t\tt.Run(testcase.name, func(t *testing.T) {\n\t\t\tif testcase.wantError == errors.ErrNoServiceID.Error() {\n\t\t\t\tt.Setenv(\"FASTLY_SERVICE_ID\", \"\")\n\t\t\t}\n\t\t\ttestcase.cmd.Globals.APIClient = testcase.api\n\n\t\t\tvar bs []byte\n\t\t\tout := bytes.NewBuffer(bs)\n\t\t\tverboseMode := true\n\n\t\t\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\t\t\tAutoCloneFlag:      testcase.cmd.AutoClone,\n\t\t\t\tAPIClient:          testcase.api,\n\t\t\t\tManifest:           testcase.cmd.Manifest,\n\t\t\t\tOut:                out,\n\t\t\t\tServiceVersionFlag: testcase.cmd.ServiceVersion,\n\t\t\t\tVerboseMode:        verboseMode,\n\t\t\t})\n\n\t\t\tswitch {\n\t\t\tcase err != nil && testcase.wantError == \"\":\n\t\t\t\tt.Fatalf(\"unexpected error getting service details: %v\", err)\n\t\t\t\treturn\n\t\t\tcase err != nil && testcase.wantError != \"\":\n\t\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\t\treturn\n\t\t\tcase err == nil && testcase.wantError != \"\":\n\t\t\t\tt.Fatalf(\"expected error, have nil (service details: %s, %d)\", serviceID, serviceVersion.Number)\n\t\t\tcase err == nil && testcase.wantError == \"\":\n\t\t\t\thave, err := testcase.cmd.ConstructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\t\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\t\ttestutil.AssertEqual(t, testcase.want, have)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc createCommandRequired() *cloudfiles.CreateCommand {\n\tvar b bytes.Buffer\n\n\tg := global.Data{\n\t\tConfig: config.File{},\n\t\tEnv:    config.Environment{},\n\t\tOutput: &b,\n\t}\n\tg.APIClient, _ = mock.APIClient(mock.API{\n\t\tGetVersionFn:   testutil.GetVersion,\n\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t})(\"token\", \"endpoint\", false)\n\n\treturn &cloudfiles.CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: &g,\n\t\t},\n\t\tManifest: manifest.Data{\n\t\t\tFlag: manifest.Flag{\n\t\t\t\tServiceID: \"123\",\n\t\t\t},\n\t\t},\n\t\tServiceVersion: argparser.OptionalServiceVersion{\n\t\t\tOptionalString: argparser.OptionalString{Value: \"1\"},\n\t\t},\n\t\tAutoClone: argparser.OptionalAutoClone{\n\t\t\tOptionalBool: argparser.OptionalBool{\n\t\t\t\tOptional: argparser.Optional{\n\t\t\t\t\tWasSet: true,\n\t\t\t\t},\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t},\n\t\tEndpointName: argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"log\"},\n\t\tUser:         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"user\"},\n\t\tAccessKey:    argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"key\"},\n\t\tBucketName:   argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"bucket\"},\n\t}\n}\n\nfunc createCommandAll() *cloudfiles.CreateCommand {\n\tvar b bytes.Buffer\n\n\tg := global.Data{\n\t\tConfig: config.File{},\n\t\tEnv:    config.Environment{},\n\t\tOutput: &b,\n\t}\n\tg.APIClient, _ = mock.APIClient(mock.API{\n\t\tGetVersionFn:   testutil.GetVersion,\n\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t})(\"token\", \"endpoint\", false)\n\n\treturn &cloudfiles.CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: &g,\n\t\t},\n\t\tManifest: manifest.Data{\n\t\t\tFlag: manifest.Flag{\n\t\t\t\tServiceID: \"123\",\n\t\t\t},\n\t\t},\n\t\tServiceVersion: argparser.OptionalServiceVersion{\n\t\t\tOptionalString: argparser.OptionalString{Value: \"1\"},\n\t\t},\n\t\tAutoClone: argparser.OptionalAutoClone{\n\t\t\tOptionalBool: argparser.OptionalBool{\n\t\t\t\tOptional: argparser.Optional{\n\t\t\t\t\tWasSet: true,\n\t\t\t\t},\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t},\n\t\tEndpointName:      argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"log\"},\n\t\tUser:              argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"user\"},\n\t\tAccessKey:         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"key\"},\n\t\tBucketName:        argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"bucket\"},\n\t\tPath:              argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"/logs\"},\n\t\tRegion:            argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"abc\"},\n\t\tPlacement:         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"none\"},\n\t\tPeriod:            argparser.OptionalInt{Optional: argparser.Optional{WasSet: true}, Value: 3600},\n\t\tFormat:            argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: `%h %l %u %t \"%r\" %>s %b`},\n\t\tFormatVersion:     argparser.OptionalInt{Optional: argparser.Optional{WasSet: true}, Value: 2},\n\t\tResponseCondition: argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"Prevent default logging\"},\n\t\tMessageType:       argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"classic\"},\n\t\tTimestampFormat:   argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"%Y-%m-%dT%H:%M:%S.000\"},\n\t\tPublicKey:         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: pgpPublicKey()},\n\t\tCompressionCodec:  argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"zstd\"},\n\t\tProcessingRegion:  argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"eu\"},\n\t}\n}\n\nfunc createCommandMissingServiceID() *cloudfiles.CreateCommand {\n\tres := createCommandAll()\n\tres.Manifest = manifest.Data{}\n\tres.ServiceVersion = argparser.OptionalServiceVersion{}\n\treturn res\n}\n\nfunc updateCommandNoUpdate() *cloudfiles.UpdateCommand {\n\tvar b bytes.Buffer\n\n\tg := global.Data{\n\t\tConfig: config.File{},\n\t\tEnv:    config.Environment{},\n\t\tOutput: &b,\n\t}\n\n\treturn &cloudfiles.UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: &g,\n\t\t},\n\t\tManifest: manifest.Data{\n\t\t\tFlag: manifest.Flag{\n\t\t\t\tServiceID: \"123\",\n\t\t\t},\n\t\t},\n\t\tServiceVersion: argparser.OptionalServiceVersion{\n\t\t\tOptionalString: argparser.OptionalString{Value: \"1\"},\n\t\t},\n\t\tAutoClone: argparser.OptionalAutoClone{\n\t\t\tOptionalBool: argparser.OptionalBool{\n\t\t\t\tOptional: argparser.Optional{\n\t\t\t\t\tWasSet: true,\n\t\t\t\t},\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t},\n\t\tEndpointName: \"log\",\n\t}\n}\n\nfunc updateCommandAll() *cloudfiles.UpdateCommand {\n\tvar b bytes.Buffer\n\n\tg := global.Data{\n\t\tConfig: config.File{},\n\t\tEnv:    config.Environment{},\n\t\tOutput: &b,\n\t}\n\n\treturn &cloudfiles.UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: &g,\n\t\t},\n\t\tManifest: manifest.Data{\n\t\t\tFlag: manifest.Flag{\n\t\t\t\tServiceID: \"123\",\n\t\t\t},\n\t\t},\n\t\tServiceVersion: argparser.OptionalServiceVersion{\n\t\t\tOptionalString: argparser.OptionalString{Value: \"1\"},\n\t\t},\n\t\tAutoClone: argparser.OptionalAutoClone{\n\t\t\tOptionalBool: argparser.OptionalBool{\n\t\t\t\tOptional: argparser.Optional{\n\t\t\t\t\tWasSet: true,\n\t\t\t\t},\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t},\n\t\tEndpointName:      \"log\",\n\t\tNewName:           argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new1\"},\n\t\tAccessKey:         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new2\"},\n\t\tBucketName:        argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new3\"},\n\t\tPath:              argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new4\"},\n\t\tRegion:            argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new5\"},\n\t\tPlacement:         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new6\"},\n\t\tPeriod:            argparser.OptionalInt{Optional: argparser.Optional{WasSet: true}, Value: 3601},\n\t\tGzipLevel:         argparser.OptionalInt{Optional: argparser.Optional{WasSet: true}, Value: 0},\n\t\tFormat:            argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new7\"},\n\t\tFormatVersion:     argparser.OptionalInt{Optional: argparser.Optional{WasSet: true}, Value: 3},\n\t\tResponseCondition: argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new8\"},\n\t\tMessageType:       argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new9\"},\n\t\tTimestampFormat:   argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new10\"},\n\t\tPublicKey:         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new11\"},\n\t\tUser:              argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new12\"},\n\t\tCompressionCodec:  argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new13\"},\n\t\tProcessingRegion:  argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"eu\"},\n\t}\n}\n\nfunc updateCommandMissingServiceID() *cloudfiles.UpdateCommand {\n\tres := updateCommandAll()\n\tres.Manifest = manifest.Data{}\n\tres.ServiceVersion = argparser.OptionalServiceVersion{}\n\treturn res\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/cloudfiles/create.go",
    "content": "package cloudfiles\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/service/logging/logflags\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/manifest\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// CreateCommand calls the Fastly API to create a Cloudfiles logging endpoint.\ntype CreateCommand struct {\n\targparser.Base\n\tManifest manifest.Data\n\n\t// Required.\n\tServiceName    argparser.OptionalServiceNameID\n\tServiceVersion argparser.OptionalServiceVersion\n\n\t// Optional.\n\tAccessKey         argparser.OptionalString\n\tAutoClone         argparser.OptionalAutoClone\n\tBucketName        argparser.OptionalString\n\tCompressionCodec  argparser.OptionalString\n\tEndpointName      argparser.OptionalString // Can't shadow argparser.Base method Name().\n\tFormat            argparser.OptionalString\n\tFormatVersion     argparser.OptionalInt\n\tGzipLevel         argparser.OptionalInt\n\tMessageType       argparser.OptionalString\n\tPath              argparser.OptionalString\n\tPeriod            argparser.OptionalInt\n\tPlacement         argparser.OptionalString\n\tProcessingRegion  argparser.OptionalString\n\tPublicKey         argparser.OptionalString\n\tRegion            argparser.OptionalString\n\tResponseCondition argparser.OptionalString\n\tTimestampFormat   argparser.OptionalString\n\tToken             argparser.OptionalString\n\tUser              argparser.OptionalString\n}\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"create\", \"Create a Cloudfiles logging endpoint on a Fastly service version\").Alias(\"add\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.ServiceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.CmdClause.Flag(\"access-key\", \"Your Cloudfile account access key\").Action(c.AccessKey.Set).StringVar(&c.AccessKey.Value)\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.AutoClone.Set,\n\t\tDst:    &c.AutoClone.Value,\n\t})\n\tc.CmdClause.Flag(\"bucket\", \"The name of your Cloudfiles container\").Action(c.BucketName.Set).StringVar(&c.BucketName.Value)\n\tlogflags.CompressionCodec(c.CmdClause, &c.CompressionCodec)\n\tlogflags.Format(c.CmdClause, &c.Format)\n\tlogflags.FormatVersion(c.CmdClause, &c.FormatVersion)\n\tlogflags.GzipLevel(c.CmdClause, &c.GzipLevel)\n\tlogflags.MessageType(c.CmdClause, &c.MessageType)\n\tc.CmdClause.Flag(\"name\", \"The name of the Cloudfiles logging object. Used as a primary key for API access\").Short('n').Action(c.EndpointName.Set).StringVar(&c.EndpointName.Value)\n\tlogflags.Path(c.CmdClause, &c.Path)\n\tlogflags.Period(c.CmdClause, &c.Period)\n\tlogflags.Placement(c.CmdClause, &c.Placement)\n\tlogflags.ProcessingRegion(c.CmdClause, &c.ProcessingRegion, \"Cloud Files\")\n\tlogflags.PublicKey(c.CmdClause, &c.PublicKey)\n\tc.CmdClause.Flag(\"region\", \"The region where logs are received and stored by Cloud Files. One of: DFW-Dallas, ORD-Chicago, IAD-Northern Virginia, LON-London, SYD-Sydney, HKG-Hong Kong\").Action(c.Region.Set).StringVar(&c.Region.Value)\n\tlogflags.ResponseCondition(c.CmdClause, &c.ResponseCondition)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.ServiceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.ServiceName.Value,\n\t})\n\tlogflags.TimestampFormat(c.CmdClause, &c.TimestampFormat)\n\tc.CmdClause.Flag(\"user\", \"The username for your Cloudfile account\").Action(c.User.Set).StringVar(&c.User.Value)\n\treturn &c\n}\n\n// ConstructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *CreateCommand) ConstructInput(serviceID string, serviceVersion int) (*fastly.CreateCloudfilesInput, error) {\n\tvar input fastly.CreateCloudfilesInput\n\n\tinput.ServiceID = serviceID\n\tinput.ServiceVersion = serviceVersion\n\tif c.EndpointName.WasSet {\n\t\tinput.Name = &c.EndpointName.Value\n\t}\n\tif c.User.WasSet {\n\t\tinput.User = &c.User.Value\n\t}\n\tif c.AccessKey.WasSet {\n\t\tinput.AccessKey = &c.AccessKey.Value\n\t}\n\tif c.BucketName.WasSet {\n\t\tinput.BucketName = &c.BucketName.Value\n\t}\n\n\t// The following blocks enforces the mutual exclusivity of the\n\t// CompressionCodec and GzipLevel flags.\n\tif c.CompressionCodec.WasSet && c.GzipLevel.WasSet {\n\t\treturn nil, fmt.Errorf(\"error parsing arguments: the --compression-codec flag is mutually exclusive with the --gzip-level flag\")\n\t}\n\n\tif c.Path.WasSet {\n\t\tinput.Path = &c.Path.Value\n\t}\n\tif c.Region.WasSet {\n\t\tinput.Region = &c.Region.Value\n\t}\n\tif c.Placement.WasSet {\n\t\tinput.Placement = &c.Placement.Value\n\t}\n\tif c.Period.WasSet {\n\t\tinput.Period = &c.Period.Value\n\t}\n\tif c.GzipLevel.WasSet {\n\t\tinput.GzipLevel = &c.GzipLevel.Value\n\t}\n\tif c.Format.WasSet {\n\t\tinput.Format = fastly.ToPointer(argparser.Content(c.Format.Value))\n\t}\n\tif c.FormatVersion.WasSet {\n\t\tinput.FormatVersion = &c.FormatVersion.Value\n\t}\n\tif c.ResponseCondition.WasSet {\n\t\tinput.ResponseCondition = &c.ResponseCondition.Value\n\t}\n\tif c.MessageType.WasSet {\n\t\tinput.MessageType = &c.MessageType.Value\n\t}\n\tif c.TimestampFormat.WasSet {\n\t\tinput.TimestampFormat = &c.TimestampFormat.Value\n\t}\n\tif c.PublicKey.WasSet {\n\t\tinput.PublicKey = &c.PublicKey.Value\n\t}\n\tif c.CompressionCodec.WasSet {\n\t\tinput.CompressionCodec = &c.CompressionCodec.Value\n\t}\n\tif c.ProcessingRegion.WasSet {\n\t\tinput.ProcessingRegion = &c.ProcessingRegion.Value\n\t}\n\n\treturn &input, nil\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *CreateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(false),\n\t\tLocked:             optional.Of(false),\n\t\tAutoCloneFlag:      c.AutoClone,\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.ServiceName,\n\t\tServiceVersionFlag: c.ServiceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fsterr.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tinput, err := c.ConstructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fastly.ToValue(serviceVersion.Number),\n\t\t})\n\t\treturn err\n\t}\n\n\td, err := c.Globals.APIClient.CreateCloudfiles(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fastly.ToValue(serviceVersion.Number),\n\t\t})\n\t\treturn err\n\t}\n\n\ttext.Success(\n\t\tout,\n\t\t\"Created Cloudfiles logging endpoint %s (service %s version %d)\",\n\t\tfastly.ToValue(d.Name),\n\t\tfastly.ToValue(d.ServiceID),\n\t\tfastly.ToValue(d.ServiceVersion),\n\t)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/cloudfiles/delete.go",
    "content": "package cloudfiles\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DeleteCommand calls the Fastly API to delete a Cloudfiles logging endpoint.\ntype DeleteCommand struct {\n\targparser.Base\n\tInput          fastly.DeleteCloudfilesInput\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n\tautoClone      argparser.OptionalAutoClone\n}\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"delete\", \"Delete a Cloudfiles logging endpoint on a Fastly service version\").Alias(\"remove\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"The name of the Cloudfiles logging object\").Short('n').Required().StringVar(&c.Input.Name)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.autoClone.Set,\n\t\tDst:    &c.autoClone.Value,\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DeleteCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(false),\n\t\tLocked:             optional.Of(false),\n\t\tAutoCloneFlag:      c.autoClone,\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": errors.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tc.Input.ServiceID = serviceID\n\tc.Input.ServiceVersion = fastly.ToValue(serviceVersion.Number)\n\n\tif err := c.Globals.APIClient.DeleteCloudfiles(context.TODO(), &c.Input); err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fastly.ToValue(serviceVersion.Number),\n\t\t})\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Deleted Cloudfiles logging endpoint %s (service %s version %d)\", c.Input.Name, c.Input.ServiceID, c.Input.ServiceVersion)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/cloudfiles/describe.go",
    "content": "package cloudfiles\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DescribeCommand calls the Fastly API to describe a Cloudfiles logging endpoint.\ntype DescribeCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tInput          fastly.GetCloudfilesInput\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n}\n\n// NewDescribeCommand returns a usable command registered under the parent.\nfunc NewDescribeCommand(parent argparser.Registerer, g *global.Data) *DescribeCommand {\n\tc := DescribeCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"describe\", \"Show detailed information about a Cloudfiles logging endpoint on a Fastly service version\").Alias(\"get\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"The name of the Cloudfiles logging object\").Short('n').Required().StringVar(&c.Input.Name)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DescribeCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fsterr.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tc.Input.ServiceID = serviceID\n\tc.Input.ServiceVersion = fastly.ToValue(serviceVersion.Number)\n\n\to, err := c.Globals.APIClient.GetCloudfiles(context.TODO(), &c.Input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fastly.ToValue(serviceVersion.Number),\n\t\t})\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\tlines := text.Lines{\n\t\t\"Access key\":         fastly.ToValue(o.AccessKey),\n\t\t\"Bucket\":             fastly.ToValue(o.BucketName),\n\t\t\"Format version\":     fastly.ToValue(o.FormatVersion),\n\t\t\"Format\":             fastly.ToValue(o.Format),\n\t\t\"GZip level\":         fastly.ToValue(o.GzipLevel),\n\t\t\"Message type\":       fastly.ToValue(o.MessageType),\n\t\t\"Name\":               fastly.ToValue(o.Name),\n\t\t\"Path\":               fastly.ToValue(o.Path),\n\t\t\"Period\":             fastly.ToValue(o.Period),\n\t\t\"Placement\":          fastly.ToValue(o.Placement),\n\t\t\"Processing region\":  fastly.ToValue(o.ProcessingRegion),\n\t\t\"Public key\":         fastly.ToValue(o.PublicKey),\n\t\t\"Region\":             fastly.ToValue(o.Region),\n\t\t\"Response condition\": fastly.ToValue(o.ResponseCondition),\n\t\t\"Timestamp format\":   fastly.ToValue(o.TimestampFormat),\n\t\t\"User\":               fastly.ToValue(o.User),\n\t\t\"Version\":            fastly.ToValue(o.ServiceVersion),\n\t}\n\tif !c.Globals.Verbose() {\n\t\tlines[\"Service ID\"] = fastly.ToValue(o.ServiceID)\n\t}\n\ttext.PrintLines(out, lines)\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/cloudfiles/doc.go",
    "content": "// Package cloudfiles contains commands to inspect and manipulate Fastly service Cloudfiles\n// logging endpoints.\npackage cloudfiles\n"
  },
  {
    "path": "pkg/commands/service/logging/cloudfiles/list.go",
    "content": "package cloudfiles\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// ListCommand calls the Fastly API to list Cloudfiles logging endpoints.\ntype ListCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tInput          fastly.ListCloudfilesInput\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n}\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"list\", \"List Cloudfiles endpoints on a Fastly service version\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *ListCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fsterr.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tc.Input.ServiceID = serviceID\n\tc.Input.ServiceVersion = fastly.ToValue(serviceVersion.Number)\n\n\to, err := c.Globals.APIClient.ListCloudfiles(context.TODO(), &c.Input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fastly.ToValue(serviceVersion.Number),\n\t\t})\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\tif !c.Globals.Verbose() {\n\t\ttw := text.NewTable(out)\n\t\ttw.AddHeader(\"SERVICE\", \"VERSION\", \"NAME\")\n\t\tfor _, cloudfile := range o {\n\t\t\ttw.AddLine(\n\t\t\t\tfastly.ToValue(cloudfile.ServiceID),\n\t\t\t\tfastly.ToValue(cloudfile.ServiceVersion),\n\t\t\t\tfastly.ToValue(cloudfile.Name),\n\t\t\t)\n\t\t}\n\t\ttw.Print()\n\t\treturn nil\n\t}\n\n\tfmt.Fprintf(out, \"Version: %d\\n\", c.Input.ServiceVersion)\n\tfor i, cloudfile := range o {\n\t\tfmt.Fprintf(out, \"\\tCloudfiles %d/%d\\n\", i+1, len(o))\n\t\tfmt.Fprintf(out, \"\\t\\tService ID: %s\\n\", fastly.ToValue(cloudfile.ServiceID))\n\t\tfmt.Fprintf(out, \"\\t\\tVersion: %d\\n\", fastly.ToValue(cloudfile.ServiceVersion))\n\t\tfmt.Fprintf(out, \"\\t\\tName: %s\\n\", fastly.ToValue(cloudfile.Name))\n\t\tfmt.Fprintf(out, \"\\t\\tUser: %s\\n\", fastly.ToValue(cloudfile.User))\n\t\tfmt.Fprintf(out, \"\\t\\tAccess key: %s\\n\", fastly.ToValue(cloudfile.AccessKey))\n\t\tfmt.Fprintf(out, \"\\t\\tBucket: %s\\n\", fastly.ToValue(cloudfile.BucketName))\n\t\tfmt.Fprintf(out, \"\\t\\tPath: %s\\n\", fastly.ToValue(cloudfile.Path))\n\t\tfmt.Fprintf(out, \"\\t\\tRegion: %s\\n\", fastly.ToValue(cloudfile.Region))\n\t\tfmt.Fprintf(out, \"\\t\\tPlacement: %s\\n\", fastly.ToValue(cloudfile.Placement))\n\t\tfmt.Fprintf(out, \"\\t\\tPeriod: %d\\n\", fastly.ToValue(cloudfile.Period))\n\t\tfmt.Fprintf(out, \"\\t\\tGZip level: %d\\n\", fastly.ToValue(cloudfile.GzipLevel))\n\t\tfmt.Fprintf(out, \"\\t\\tFormat: %s\\n\", fastly.ToValue(cloudfile.Format))\n\t\tfmt.Fprintf(out, \"\\t\\tFormat version: %d\\n\", fastly.ToValue(cloudfile.FormatVersion))\n\t\tfmt.Fprintf(out, \"\\t\\tResponse condition: %s\\n\", fastly.ToValue(cloudfile.ResponseCondition))\n\t\tfmt.Fprintf(out, \"\\t\\tMessage type: %s\\n\", fastly.ToValue(cloudfile.MessageType))\n\t\tfmt.Fprintf(out, \"\\t\\tTimestamp format: %s\\n\", fastly.ToValue(cloudfile.TimestampFormat))\n\t\tfmt.Fprintf(out, \"\\t\\tPublic key: %s\\n\", fastly.ToValue(cloudfile.PublicKey))\n\t\tfmt.Fprintf(out, \"\\t\\tProcessing region: %s\\n\", fastly.ToValue(cloudfile.ProcessingRegion))\n\t}\n\tfmt.Fprintln(out)\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/cloudfiles/root.go",
    "content": "package cloudfiles\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"cloudfiles\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manipulate Fastly service version Cloudfiles logging endpoints\")\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/cloudfiles/update.go",
    "content": "package cloudfiles\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/service/logging/logflags\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/manifest\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// UpdateCommand calls the Fastly API to update a Cloudfiles logging endpoint.\ntype UpdateCommand struct {\n\targparser.Base\n\tManifest manifest.Data\n\n\t// Required.\n\tEndpointName   string // Can't shadow argparser.Base method Name().\n\tServiceName    argparser.OptionalServiceNameID\n\tServiceVersion argparser.OptionalServiceVersion\n\n\t// Optional.\n\tAccessKey         argparser.OptionalString\n\tAutoClone         argparser.OptionalAutoClone\n\tBucketName        argparser.OptionalString\n\tCompressionCodec  argparser.OptionalString\n\tFormat            argparser.OptionalString\n\tFormatVersion     argparser.OptionalInt\n\tGzipLevel         argparser.OptionalInt\n\tMessageType       argparser.OptionalString\n\tNewName           argparser.OptionalString\n\tPath              argparser.OptionalString\n\tPeriod            argparser.OptionalInt\n\tPlacement         argparser.OptionalString\n\tProcessingRegion  argparser.OptionalString\n\tPublicKey         argparser.OptionalString\n\tRegion            argparser.OptionalString\n\tResponseCondition argparser.OptionalString\n\tTimestampFormat   argparser.OptionalString\n\tUser              argparser.OptionalString\n}\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"update\", \"Update a Cloudfiles logging endpoint on a Fastly service version\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"The name of the Cloudfiles logging object\").Short('n').Required().StringVar(&c.EndpointName)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.ServiceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.AutoClone.Set,\n\t\tDst:    &c.AutoClone.Value,\n\t})\n\tc.CmdClause.Flag(\"access-key\", \"Your Cloudfile account access key\").Action(c.AccessKey.Set).StringVar(&c.AccessKey.Value)\n\tc.CmdClause.Flag(\"bucket\", \"The name of your Cloudfiles container\").Action(c.BucketName.Set).StringVar(&c.BucketName.Value)\n\tlogflags.CompressionCodec(c.CmdClause, &c.CompressionCodec)\n\tlogflags.Format(c.CmdClause, &c.Format)\n\tlogflags.FormatVersion(c.CmdClause, &c.FormatVersion)\n\tlogflags.GzipLevel(c.CmdClause, &c.GzipLevel)\n\tlogflags.MessageType(c.CmdClause, &c.MessageType)\n\tc.CmdClause.Flag(\"new-name\", \"New name of the Cloudfiles logging object\").Action(c.NewName.Set).StringVar(&c.NewName.Value)\n\tlogflags.Path(c.CmdClause, &c.Path)\n\tlogflags.Period(c.CmdClause, &c.Period)\n\tlogflags.Placement(c.CmdClause, &c.Placement)\n\tlogflags.ProcessingRegion(c.CmdClause, &c.ProcessingRegion, \"Cloud Files\")\n\tlogflags.PublicKey(c.CmdClause, &c.PublicKey)\n\tc.CmdClause.Flag(\"region\", \"The region where logs are received and stored by Cloud Files. One of: DFW-Dallas, ORD-Chicago, IAD-Northern Virginia, LON-London, SYD-Sydney, HKG-Hong Kong\").Action(c.Region.Set).StringVar(&c.Region.Value)\n\tc.CmdClause.Flag(\"user\", \"The username for your Cloudfile account\").Action(c.User.Set).StringVar(&c.User.Value)\n\tlogflags.ResponseCondition(c.CmdClause, &c.ResponseCondition)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.ServiceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.ServiceName.Value,\n\t})\n\tlogflags.TimestampFormat(c.CmdClause, &c.TimestampFormat)\n\treturn &c\n}\n\n// ConstructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *UpdateCommand) ConstructInput(serviceID string, serviceVersion int) (*fastly.UpdateCloudfilesInput, error) {\n\tinput := fastly.UpdateCloudfilesInput{\n\t\tServiceID:      serviceID,\n\t\tServiceVersion: serviceVersion,\n\t\tName:           c.EndpointName,\n\t}\n\n\t// Set new values if set by user.\n\tif c.NewName.WasSet {\n\t\tinput.NewName = &c.NewName.Value\n\t}\n\n\tif c.User.WasSet {\n\t\tinput.User = &c.User.Value\n\t}\n\n\tif c.AccessKey.WasSet {\n\t\tinput.AccessKey = &c.AccessKey.Value\n\t}\n\n\tif c.BucketName.WasSet {\n\t\tinput.BucketName = &c.BucketName.Value\n\t}\n\n\tif c.Path.WasSet {\n\t\tinput.Path = &c.Path.Value\n\t}\n\n\tif c.Region.WasSet {\n\t\tinput.Region = &c.Region.Value\n\t}\n\n\tif c.Placement.WasSet {\n\t\tinput.Placement = &c.Placement.Value\n\t}\n\n\tif c.Period.WasSet {\n\t\tinput.Period = &c.Period.Value\n\t}\n\n\tif c.GzipLevel.WasSet {\n\t\tinput.GzipLevel = &c.GzipLevel.Value\n\t}\n\n\tif c.Format.WasSet {\n\t\tinput.Format = fastly.ToPointer(argparser.Content(c.Format.Value))\n\t}\n\n\tif c.FormatVersion.WasSet {\n\t\tinput.FormatVersion = &c.FormatVersion.Value\n\t}\n\n\tif c.ResponseCondition.WasSet {\n\t\tinput.ResponseCondition = &c.ResponseCondition.Value\n\t}\n\n\tif c.MessageType.WasSet {\n\t\tinput.MessageType = &c.MessageType.Value\n\t}\n\n\tif c.TimestampFormat.WasSet {\n\t\tinput.TimestampFormat = &c.TimestampFormat.Value\n\t}\n\n\tif c.PublicKey.WasSet {\n\t\tinput.PublicKey = &c.PublicKey.Value\n\t}\n\n\tif c.CompressionCodec.WasSet {\n\t\tinput.CompressionCodec = &c.CompressionCodec.Value\n\t}\n\n\tif c.ProcessingRegion.WasSet {\n\t\tinput.ProcessingRegion = &c.ProcessingRegion.Value\n\t}\n\n\treturn &input, nil\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *UpdateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(false),\n\t\tLocked:             optional.Of(false),\n\t\tAutoCloneFlag:      c.AutoClone,\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.ServiceName,\n\t\tServiceVersionFlag: c.ServiceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fsterr.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tinput, err := c.ConstructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fastly.ToValue(serviceVersion.Number),\n\t\t})\n\t\treturn err\n\t}\n\n\tcloudfiles, err := c.Globals.APIClient.UpdateCloudfiles(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fastly.ToValue(serviceVersion.Number),\n\t\t})\n\t\treturn err\n\t}\n\n\ttext.Success(\n\t\tout,\n\t\t\"Updated Cloudfiles logging endpoint %s (service %s version %d)\",\n\t\tfastly.ToValue(cloudfiles.Name),\n\t\tfastly.ToValue(cloudfiles.ServiceID),\n\t\tfastly.ToValue(cloudfiles.ServiceVersion),\n\t)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/datadog/create.go",
    "content": "package datadog\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/service/logging/logflags\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/manifest\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// CreateCommand calls the Fastly API to create a Datadog logging endpoint.\ntype CreateCommand struct {\n\targparser.Base\n\tManifest manifest.Data\n\n\t// Required.\n\tServiceName    argparser.OptionalServiceNameID\n\tServiceVersion argparser.OptionalServiceVersion\n\n\t// Optional.\n\tAutoClone         argparser.OptionalAutoClone\n\tEndpointName      argparser.OptionalString // Can't shadow argparser.Base method Name().\n\tFormat            argparser.OptionalString\n\tFormatVersion     argparser.OptionalInt\n\tPlacement         argparser.OptionalString\n\tProcessingRegion  argparser.OptionalString\n\tRegion            argparser.OptionalString\n\tResponseCondition argparser.OptionalString\n\tToken             argparser.OptionalString\n}\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"create\", \"Create a Datadog logging endpoint on a Fastly service version\").Alias(\"add\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"The name of the Datadog logging object. Used as a primary key for API access\").Short('n').Action(c.EndpointName.Set).StringVar(&c.EndpointName.Value)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.ServiceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.AutoClone.Set,\n\t\tDst:    &c.AutoClone.Value,\n\t})\n\tc.CmdClause.Flag(\"auth-token\", \"The API key from your Datadog account\").Action(c.Token.Set).StringVar(&c.Token.Value)\n\tlogflags.Format(c.CmdClause, &c.Format)\n\tlogflags.FormatVersion(c.CmdClause, &c.FormatVersion)\n\tlogflags.Placement(c.CmdClause, &c.Placement)\n\tlogflags.ProcessingRegion(c.CmdClause, &c.ProcessingRegion, \"Datadog\")\n\tc.CmdClause.Flag(\"region\", \"The region where logs are received and stored by Datadog. One of US, US3, US5, or EU. Defaults to US if undefined\").Action(c.Region.Set).StringVar(&c.Region.Value)\n\tlogflags.ResponseCondition(c.CmdClause, &c.ResponseCondition)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.ServiceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.ServiceName.Value,\n\t})\n\treturn &c\n}\n\n// ConstructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *CreateCommand) ConstructInput(serviceID string, serviceVersion int) (*fastly.CreateDatadogInput, error) {\n\tvar input fastly.CreateDatadogInput\n\n\tinput.ServiceID = serviceID\n\tinput.ServiceVersion = serviceVersion\n\tif c.EndpointName.WasSet {\n\t\tinput.Name = &c.EndpointName.Value\n\t}\n\tif c.Token.WasSet {\n\t\tinput.Token = &c.Token.Value\n\t}\n\n\tif c.Region.WasSet {\n\t\tinput.Region = &c.Region.Value\n\t}\n\n\tif c.Format.WasSet {\n\t\tinput.Format = fastly.ToPointer(argparser.Content(c.Format.Value))\n\t}\n\n\tif c.FormatVersion.WasSet {\n\t\tinput.FormatVersion = &c.FormatVersion.Value\n\t}\n\n\tif c.ResponseCondition.WasSet {\n\t\tinput.ResponseCondition = &c.ResponseCondition.Value\n\t}\n\n\tif c.Placement.WasSet {\n\t\tinput.Placement = &c.Placement.Value\n\t}\n\n\tif c.ProcessingRegion.WasSet {\n\t\tinput.ProcessingRegion = &c.ProcessingRegion.Value\n\t}\n\n\treturn &input, nil\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *CreateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(false),\n\t\tLocked:             optional.Of(false),\n\t\tAutoCloneFlag:      c.AutoClone,\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.ServiceName,\n\t\tServiceVersionFlag: c.ServiceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": errors.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tinput, err := c.ConstructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\td, err := c.Globals.APIClient.CreateDatadog(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\ttext.Success(\n\t\tout,\n\t\t\"Created Datadog logging endpoint %s (service %s version %d)\",\n\t\tfastly.ToValue(d.Name),\n\t\tfastly.ToValue(d.ServiceID),\n\t\tfastly.ToValue(d.ServiceVersion),\n\t)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/datadog/datadog_integration_test.go",
    "content": "package datadog_test\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\troot \"github.com/fastly/cli/pkg/commands/service\"\n\tparent \"github.com/fastly/cli/pkg/commands/service/logging\"\n\tsub \"github.com/fastly/cli/pkg/commands/service/logging/datadog\"\n\t\"github.com/fastly/cli/pkg/mock\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n)\n\nfunc TestDatadogCreate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name log --auth-token abc --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:    testutil.GetVersion,\n\t\t\t\tCloneVersionFn:  testutil.CloneVersionResult(4),\n\t\t\t\tCreateDatadogFn: createDatadogOK,\n\t\t\t},\n\t\t\tWantOutput: \"Created Datadog logging endpoint log (service 123 version 4)\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name log --auth-token abc --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:    testutil.GetVersion,\n\t\t\t\tCloneVersionFn:  testutil.CloneVersionResult(4),\n\t\t\t\tCreateDatadogFn: createDatadogError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"create\"}, scenarios)\n}\n\nfunc TestDatadogList(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:  testutil.GetVersion,\n\t\t\t\tListDatadogFn: listDatadogsOK,\n\t\t\t},\n\t\t\tWantOutput: listDatadogsShortOutput,\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --verbose\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:  testutil.GetVersion,\n\t\t\t\tListDatadogFn: listDatadogsOK,\n\t\t\t},\n\t\t\tWantOutput: listDatadogsVerboseOutput,\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 -v\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:  testutil.GetVersion,\n\t\t\t\tListDatadogFn: listDatadogsOK,\n\t\t\t},\n\t\t\tWantOutput: listDatadogsVerboseOutput,\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:  testutil.GetVersion,\n\t\t\t\tListDatadogFn: listDatadogsError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"list\"}, scenarios)\n}\n\nfunc TestDatadogDescribe(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"--service-id 123 --version 1\",\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tGetDatadogFn: getDatadogError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tGetDatadogFn: getDatadogOK,\n\t\t\t},\n\t\t\tWantOutput: describeDatadogOutput,\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"describe\"}, scenarios)\n}\n\nfunc TestDatadogUpdate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"--service-id 123 --version 1 --new-name log\",\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs --new-name log --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:    testutil.GetVersion,\n\t\t\t\tCloneVersionFn:  testutil.CloneVersionResult(4),\n\t\t\t\tUpdateDatadogFn: updateDatadogError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs --new-name log --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:    testutil.GetVersion,\n\t\t\t\tCloneVersionFn:  testutil.CloneVersionResult(4),\n\t\t\t\tUpdateDatadogFn: updateDatadogOK,\n\t\t\t},\n\t\t\tWantOutput: \"Updated Datadog logging endpoint log (service 123 version 4)\",\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"update\"}, scenarios)\n}\n\nfunc TestDatadogDelete(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"--service-id 123 --version 1\",\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:    testutil.GetVersion,\n\t\t\t\tCloneVersionFn:  testutil.CloneVersionResult(4),\n\t\t\t\tDeleteDatadogFn: deleteDatadogError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:    testutil.GetVersion,\n\t\t\t\tCloneVersionFn:  testutil.CloneVersionResult(4),\n\t\t\t\tDeleteDatadogFn: deleteDatadogOK,\n\t\t\t},\n\t\t\tWantOutput: \"Deleted Datadog logging endpoint logs (service 123 version 4)\",\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"delete\"}, scenarios)\n}\n\nvar errTest = errors.New(\"fixture error\")\n\nfunc createDatadogOK(_ context.Context, i *fastly.CreateDatadogInput) (*fastly.Datadog, error) {\n\ts := fastly.Datadog{\n\t\tServiceID:      fastly.ToPointer(i.ServiceID),\n\t\tServiceVersion: fastly.ToPointer(i.ServiceVersion),\n\t}\n\n\tif i.Name != nil {\n\t\ts.Name = i.Name\n\t}\n\n\treturn &s, nil\n}\n\nfunc createDatadogError(_ context.Context, _ *fastly.CreateDatadogInput) (*fastly.Datadog, error) {\n\treturn nil, errTest\n}\n\nfunc listDatadogsOK(_ context.Context, i *fastly.ListDatadogInput) ([]*fastly.Datadog, error) {\n\treturn []*fastly.Datadog{\n\t\t{\n\t\t\tServiceID:         fastly.ToPointer(i.ServiceID),\n\t\t\tServiceVersion:    fastly.ToPointer(i.ServiceVersion),\n\t\t\tName:              fastly.ToPointer(\"logs\"),\n\t\t\tToken:             fastly.ToPointer(\"abc\"),\n\t\t\tRegion:            fastly.ToPointer(\"US\"),\n\t\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\t\tFormatVersion:     fastly.ToPointer(2),\n\t\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\t\tProcessingRegion:  fastly.ToPointer(\"us\"),\n\t\t},\n\t\t{\n\t\t\tServiceID:         fastly.ToPointer(i.ServiceID),\n\t\t\tServiceVersion:    fastly.ToPointer(i.ServiceVersion),\n\t\t\tName:              fastly.ToPointer(\"analytics\"),\n\t\t\tToken:             fastly.ToPointer(\"abc\"),\n\t\t\tRegion:            fastly.ToPointer(\"US\"),\n\t\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\t\tFormatVersion:     fastly.ToPointer(2),\n\t\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\t\tProcessingRegion:  fastly.ToPointer(\"us\"),\n\t\t},\n\t}, nil\n}\n\nfunc listDatadogsError(_ context.Context, _ *fastly.ListDatadogInput) ([]*fastly.Datadog, error) {\n\treturn nil, errTest\n}\n\nvar listDatadogsShortOutput = strings.TrimSpace(`\nSERVICE  VERSION  NAME\n123      1        logs\n123      1        analytics\n`) + \"\\n\"\n\nvar listDatadogsVerboseOutput = strings.TrimSpace(`\nFastly API endpoint: https://api.fastly.com\nFastly API token provided via config file (auth: user)\n\nService ID (via --service-id): 123\n\nVersion: 1\n\tDatadog 1/2\n\t\tService ID: 123\n\t\tVersion: 1\n\t\tName: logs\n\t\tToken: abc\n\t\tRegion: US\n\t\tFormat: %h %l %u %t \"%r\" %>s %b\n\t\tFormat version: 2\n\t\tResponse condition: Prevent default logging\n\t\tPlacement: none\n\t\tProcessing region: us\n\tDatadog 2/2\n\t\tService ID: 123\n\t\tVersion: 1\n\t\tName: analytics\n\t\tToken: abc\n\t\tRegion: US\n\t\tFormat: %h %l %u %t \"%r\" %>s %b\n\t\tFormat version: 2\n\t\tResponse condition: Prevent default logging\n\t\tPlacement: none\n\t\tProcessing region: us\n`) + \"\\n\\n\"\n\nfunc getDatadogOK(_ context.Context, i *fastly.GetDatadogInput) (*fastly.Datadog, error) {\n\treturn &fastly.Datadog{\n\t\tServiceID:         fastly.ToPointer(i.ServiceID),\n\t\tServiceVersion:    fastly.ToPointer(i.ServiceVersion),\n\t\tName:              fastly.ToPointer(\"logs\"),\n\t\tToken:             fastly.ToPointer(\"abc\"),\n\t\tRegion:            fastly.ToPointer(\"US\"),\n\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\tFormatVersion:     fastly.ToPointer(2),\n\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\tProcessingRegion:  fastly.ToPointer(\"us\"),\n\t}, nil\n}\n\nfunc getDatadogError(_ context.Context, _ *fastly.GetDatadogInput) (*fastly.Datadog, error) {\n\treturn nil, errTest\n}\n\nvar describeDatadogOutput = \"\\n\" + strings.TrimSpace(`\nFormat: %h %l %u %t \"%r\" %>s %b\nFormat version: 2\nName: logs\nPlacement: none\nProcessing region: us\nRegion: US\nResponse condition: Prevent default logging\nService ID: 123\nToken: abc\nVersion: 1\n`) + \"\\n\"\n\nfunc updateDatadogOK(_ context.Context, i *fastly.UpdateDatadogInput) (*fastly.Datadog, error) {\n\treturn &fastly.Datadog{\n\t\tServiceID:         fastly.ToPointer(i.ServiceID),\n\t\tServiceVersion:    fastly.ToPointer(i.ServiceVersion),\n\t\tName:              fastly.ToPointer(\"log\"),\n\t\tToken:             fastly.ToPointer(\"abc\"),\n\t\tRegion:            fastly.ToPointer(\"US\"),\n\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\tFormatVersion:     fastly.ToPointer(2),\n\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t}, nil\n}\n\nfunc updateDatadogError(_ context.Context, _ *fastly.UpdateDatadogInput) (*fastly.Datadog, error) {\n\treturn nil, errTest\n}\n\nfunc deleteDatadogOK(_ context.Context, _ *fastly.DeleteDatadogInput) error {\n\treturn nil\n}\n\nfunc deleteDatadogError(_ context.Context, _ *fastly.DeleteDatadogInput) error {\n\treturn errTest\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/datadog/datadog_test.go",
    "content": "package datadog_test\n\nimport (\n\t\"bytes\"\n\t\"testing\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/service/logging/datadog\"\n\t\"github.com/fastly/cli/pkg/config\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/manifest\"\n\t\"github.com/fastly/cli/pkg/mock\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n)\n\nfunc TestCreateDatadogInput(t *testing.T) {\n\tfor _, testcase := range []struct {\n\t\tname      string\n\t\tcmd       *datadog.CreateCommand\n\t\twant      *fastly.CreateDatadogInput\n\t\twantError string\n\t}{\n\t\t{\n\t\t\tname: \"required values set flag serviceID\",\n\t\t\tcmd:  createCommandRequired(),\n\t\t\twant: &fastly.CreateDatadogInput{\n\t\t\t\tServiceID:      \"123\",\n\t\t\t\tServiceVersion: 4,\n\t\t\t\tName:           fastly.ToPointer(\"log\"),\n\t\t\t\tToken:          fastly.ToPointer(\"tkn\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"all values set flag serviceID\",\n\t\t\tcmd:  createCommandOK(),\n\t\t\twant: &fastly.CreateDatadogInput{\n\t\t\t\tServiceID:         \"123\",\n\t\t\t\tServiceVersion:    4,\n\t\t\t\tName:              fastly.ToPointer(\"log\"),\n\t\t\t\tRegion:            fastly.ToPointer(\"US\"),\n\t\t\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\t\t\tFormatVersion:     fastly.ToPointer(2),\n\t\t\t\tToken:             fastly.ToPointer(\"tkn\"),\n\t\t\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\t\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\t\t\tProcessingRegion:  fastly.ToPointer(\"eu\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:      \"error missing serviceID\",\n\t\t\tcmd:       createCommandMissingServiceID(),\n\t\t\twant:      nil,\n\t\t\twantError: errors.ErrNoServiceID.Error(),\n\t\t},\n\t} {\n\t\tt.Run(testcase.name, func(t *testing.T) {\n\t\t\tif testcase.wantError == errors.ErrNoServiceID.Error() {\n\t\t\t\tt.Setenv(\"FASTLY_SERVICE_ID\", \"\")\n\t\t\t}\n\t\t\tvar bs []byte\n\t\t\tout := bytes.NewBuffer(bs)\n\t\t\tverboseMode := true\n\n\t\t\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\t\t\tAutoCloneFlag:      testcase.cmd.AutoClone,\n\t\t\t\tAPIClient:          testcase.cmd.Globals.APIClient,\n\t\t\t\tManifest:           testcase.cmd.Manifest,\n\t\t\t\tOut:                out,\n\t\t\t\tServiceVersionFlag: testcase.cmd.ServiceVersion,\n\t\t\t\tVerboseMode:        verboseMode,\n\t\t\t})\n\n\t\t\tswitch {\n\t\t\tcase err != nil && testcase.wantError == \"\":\n\t\t\t\tt.Fatalf(\"unexpected error getting service details: %v\", err)\n\t\t\t\treturn\n\t\t\tcase err != nil && testcase.wantError != \"\":\n\t\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\t\treturn\n\t\t\tcase err == nil && testcase.wantError != \"\":\n\t\t\t\tt.Fatalf(\"expected error, have nil (service details: %s, %d)\", serviceID, serviceVersion.Number)\n\t\t\tcase err == nil && testcase.wantError == \"\":\n\t\t\t\thave, err := testcase.cmd.ConstructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\t\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\t\ttestutil.AssertEqual(t, testcase.want, have)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestUpdateDatadogInput(t *testing.T) {\n\tscenarios := []struct {\n\t\tname      string\n\t\tcmd       *datadog.UpdateCommand\n\t\tapi       mock.API\n\t\twant      *fastly.UpdateDatadogInput\n\t\twantError string\n\t}{\n\t\t{\n\t\t\tname: \"no updates\",\n\t\t\tcmd:  updateCommandNoUpdates(),\n\t\t\tapi: mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tGetDatadogFn:   getDatadogOK,\n\t\t\t},\n\t\t\twant: &fastly.UpdateDatadogInput{\n\t\t\t\tServiceID:      \"123\",\n\t\t\t\tServiceVersion: 4,\n\t\t\t\tName:           \"log\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"all values set flag serviceID\",\n\t\t\tcmd:  updateCommandAll(),\n\t\t\tapi: mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tGetDatadogFn:   getDatadogOK,\n\t\t\t},\n\t\t\twant: &fastly.UpdateDatadogInput{\n\t\t\t\tServiceID:         \"123\",\n\t\t\t\tServiceVersion:    4,\n\t\t\t\tName:              \"log\",\n\t\t\t\tNewName:           fastly.ToPointer(\"new1\"),\n\t\t\t\tRegion:            fastly.ToPointer(\"new2\"),\n\t\t\t\tFormat:            fastly.ToPointer(\"new3\"),\n\t\t\t\tFormatVersion:     fastly.ToPointer(3),\n\t\t\t\tToken:             fastly.ToPointer(\"new4\"),\n\t\t\t\tResponseCondition: fastly.ToPointer(\"new5\"),\n\t\t\t\tPlacement:         fastly.ToPointer(\"new6\"),\n\t\t\t\tProcessingRegion:  fastly.ToPointer(\"eu\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:      \"error missing serviceID\",\n\t\t\tcmd:       updateCommandMissingServiceID(),\n\t\t\twant:      nil,\n\t\t\twantError: errors.ErrNoServiceID.Error(),\n\t\t},\n\t}\n\tfor testcaseIdx := range scenarios {\n\t\ttestcase := &scenarios[testcaseIdx]\n\t\tt.Run(testcase.name, func(t *testing.T) {\n\t\t\tif testcase.wantError == errors.ErrNoServiceID.Error() {\n\t\t\t\tt.Setenv(\"FASTLY_SERVICE_ID\", \"\")\n\t\t\t}\n\t\t\ttestcase.cmd.Globals.APIClient = testcase.api\n\n\t\t\tvar bs []byte\n\t\t\tout := bytes.NewBuffer(bs)\n\t\t\tverboseMode := true\n\n\t\t\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\t\t\tAutoCloneFlag:      testcase.cmd.AutoClone,\n\t\t\t\tAPIClient:          testcase.api,\n\t\t\t\tManifest:           testcase.cmd.Manifest,\n\t\t\t\tOut:                out,\n\t\t\t\tServiceVersionFlag: testcase.cmd.ServiceVersion,\n\t\t\t\tVerboseMode:        verboseMode,\n\t\t\t})\n\n\t\t\tswitch {\n\t\t\tcase err != nil && testcase.wantError == \"\":\n\t\t\t\tt.Fatalf(\"unexpected error getting service details: %v\", err)\n\t\t\t\treturn\n\t\t\tcase err != nil && testcase.wantError != \"\":\n\t\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\t\treturn\n\t\t\tcase err == nil && testcase.wantError != \"\":\n\t\t\t\tt.Fatalf(\"expected error, have nil (service details: %s, %d)\", serviceID, serviceVersion.Number)\n\t\t\tcase err == nil && testcase.wantError == \"\":\n\t\t\t\thave, err := testcase.cmd.ConstructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\t\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\t\ttestutil.AssertEqual(t, testcase.want, have)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc createCommandOK() *datadog.CreateCommand {\n\tvar b bytes.Buffer\n\n\tg := global.Data{\n\t\tConfig: config.File{},\n\t\tEnv:    config.Environment{},\n\t\tOutput: &b,\n\t}\n\tg.APIClient, _ = mock.APIClient(mock.API{\n\t\tGetVersionFn:   testutil.GetVersion,\n\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t})(\"token\", \"endpoint\", false)\n\n\treturn &datadog.CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: &g,\n\t\t},\n\t\tManifest: manifest.Data{\n\t\t\tFlag: manifest.Flag{\n\t\t\t\tServiceID: \"123\",\n\t\t\t},\n\t\t},\n\t\tServiceVersion: argparser.OptionalServiceVersion{\n\t\t\tOptionalString: argparser.OptionalString{Value: \"1\"},\n\t\t},\n\t\tAutoClone: argparser.OptionalAutoClone{\n\t\t\tOptionalBool: argparser.OptionalBool{\n\t\t\t\tOptional: argparser.Optional{\n\t\t\t\t\tWasSet: true,\n\t\t\t\t},\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t},\n\t\tEndpointName:      argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"log\"},\n\t\tToken:             argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"tkn\"},\n\t\tRegion:            argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"US\"},\n\t\tFormat:            argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: `%h %l %u %t \"%r\" %>s %b`},\n\t\tFormatVersion:     argparser.OptionalInt{Optional: argparser.Optional{WasSet: true}, Value: 2},\n\t\tResponseCondition: argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"Prevent default logging\"},\n\t\tPlacement:         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"none\"},\n\t\tProcessingRegion:  argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"eu\"},\n\t}\n}\n\nfunc createCommandRequired() *datadog.CreateCommand {\n\tvar b bytes.Buffer\n\n\tg := global.Data{\n\t\tConfig: config.File{},\n\t\tEnv:    config.Environment{},\n\t\tOutput: &b,\n\t}\n\tg.APIClient, _ = mock.APIClient(mock.API{\n\t\tGetVersionFn:   testutil.GetVersion,\n\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t})(\"token\", \"endpoint\", false)\n\n\treturn &datadog.CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: &g,\n\t\t},\n\t\tManifest: manifest.Data{\n\t\t\tFlag: manifest.Flag{\n\t\t\t\tServiceID: \"123\",\n\t\t\t},\n\t\t},\n\t\tEndpointName: argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"log\"},\n\t\tToken:        argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"tkn\"},\n\t\tServiceVersion: argparser.OptionalServiceVersion{\n\t\t\tOptionalString: argparser.OptionalString{Value: \"1\"},\n\t\t},\n\t\tAutoClone: argparser.OptionalAutoClone{\n\t\t\tOptionalBool: argparser.OptionalBool{\n\t\t\t\tOptional: argparser.Optional{\n\t\t\t\t\tWasSet: true,\n\t\t\t\t},\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t},\n\t}\n}\n\nfunc createCommandMissingServiceID() *datadog.CreateCommand {\n\tres := createCommandOK()\n\tres.Manifest = manifest.Data{}\n\tres.ServiceVersion = argparser.OptionalServiceVersion{}\n\treturn res\n}\n\nfunc updateCommandNoUpdates() *datadog.UpdateCommand {\n\tvar b bytes.Buffer\n\n\tg := global.Data{\n\t\tConfig: config.File{},\n\t\tEnv:    config.Environment{},\n\t\tOutput: &b,\n\t}\n\n\treturn &datadog.UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: &g,\n\t\t},\n\t\tManifest: manifest.Data{\n\t\t\tFlag: manifest.Flag{\n\t\t\t\tServiceID: \"123\",\n\t\t\t},\n\t\t},\n\t\tEndpointName: \"log\",\n\t\tServiceVersion: argparser.OptionalServiceVersion{\n\t\t\tOptionalString: argparser.OptionalString{Value: \"1\"},\n\t\t},\n\t\tAutoClone: argparser.OptionalAutoClone{\n\t\t\tOptionalBool: argparser.OptionalBool{\n\t\t\t\tOptional: argparser.Optional{\n\t\t\t\t\tWasSet: true,\n\t\t\t\t},\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t},\n\t}\n}\n\nfunc updateCommandAll() *datadog.UpdateCommand {\n\tvar b bytes.Buffer\n\n\tg := global.Data{\n\t\tConfig: config.File{},\n\t\tEnv:    config.Environment{},\n\t\tOutput: &b,\n\t}\n\n\treturn &datadog.UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: &g,\n\t\t},\n\t\tManifest: manifest.Data{\n\t\t\tFlag: manifest.Flag{\n\t\t\t\tServiceID: \"123\",\n\t\t\t},\n\t\t},\n\t\tEndpointName: \"log\",\n\t\tServiceVersion: argparser.OptionalServiceVersion{\n\t\t\tOptionalString: argparser.OptionalString{Value: \"1\"},\n\t\t},\n\t\tAutoClone: argparser.OptionalAutoClone{\n\t\t\tOptionalBool: argparser.OptionalBool{\n\t\t\t\tOptional: argparser.Optional{\n\t\t\t\t\tWasSet: true,\n\t\t\t\t},\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t},\n\t\tNewName:           argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new1\"},\n\t\tRegion:            argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new2\"},\n\t\tFormat:            argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new3\"},\n\t\tFormatVersion:     argparser.OptionalInt{Optional: argparser.Optional{WasSet: true}, Value: 3},\n\t\tToken:             argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new4\"},\n\t\tResponseCondition: argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new5\"},\n\t\tPlacement:         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new6\"},\n\t\tProcessingRegion:  argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"eu\"},\n\t}\n}\n\nfunc updateCommandMissingServiceID() *datadog.UpdateCommand {\n\tres := updateCommandAll()\n\tres.Manifest = manifest.Data{}\n\tres.ServiceVersion = argparser.OptionalServiceVersion{}\n\treturn res\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/datadog/delete.go",
    "content": "package datadog\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DeleteCommand calls the Fastly API to delete a Datadog logging endpoint.\ntype DeleteCommand struct {\n\targparser.Base\n\tInput          fastly.DeleteDatadogInput\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n\tautoClone      argparser.OptionalAutoClone\n}\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"delete\", \"Delete a Datadog logging endpoint on a Fastly service version\").Alias(\"remove\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"The name of the Datadog logging object\").Short('n').Required().StringVar(&c.Input.Name)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.autoClone.Set,\n\t\tDst:    &c.autoClone.Value,\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DeleteCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(false),\n\t\tLocked:             optional.Of(false),\n\t\tAutoCloneFlag:      c.autoClone,\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": errors.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tc.Input.ServiceID = serviceID\n\tc.Input.ServiceVersion = fastly.ToValue(serviceVersion.Number)\n\n\tif err := c.Globals.APIClient.DeleteDatadog(context.TODO(), &c.Input); err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Deleted Datadog logging endpoint %s (service %s version %d)\", c.Input.Name, c.Input.ServiceID, c.Input.ServiceVersion)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/datadog/describe.go",
    "content": "package datadog\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DescribeCommand calls the Fastly API to describe a Datadog logging endpoint.\ntype DescribeCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tInput          fastly.GetDatadogInput\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n}\n\n// NewDescribeCommand returns a usable command registered under the parent.\nfunc NewDescribeCommand(parent argparser.Registerer, g *global.Data) *DescribeCommand {\n\tc := DescribeCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"describe\", \"Show detailed information about a Datadog logging endpoint on a Fastly service version\").Alias(\"get\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"The name of the Datadog logging object\").Short('n').Required().StringVar(&c.Input.Name)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DescribeCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fsterr.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tc.Input.ServiceID = serviceID\n\tc.Input.ServiceVersion = fastly.ToValue(serviceVersion.Number)\n\n\to, err := c.Globals.APIClient.GetDatadog(context.TODO(), &c.Input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\tlines := text.Lines{\n\t\t\"Format version\":     fastly.ToValue(o.FormatVersion),\n\t\t\"Format\":             fastly.ToValue(o.Format),\n\t\t\"Name\":               fastly.ToValue(o.Name),\n\t\t\"Placement\":          fastly.ToValue(o.Placement),\n\t\t\"Processing region\":  fastly.ToValue(o.ProcessingRegion),\n\t\t\"Region\":             fastly.ToValue(o.Region),\n\t\t\"Response condition\": fastly.ToValue(o.ResponseCondition),\n\t\t\"Token\":              fastly.ToValue(o.Token),\n\t\t\"Version\":            fastly.ToValue(o.ServiceVersion),\n\t}\n\tif !c.Globals.Verbose() {\n\t\tlines[\"Service ID\"] = fastly.ToValue(o.ServiceID)\n\t}\n\ttext.PrintLines(out, lines)\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/datadog/doc.go",
    "content": "// Package datadog contains commands to inspect and manipulate Fastly service Datadog\n// logging endpoints.\npackage datadog\n"
  },
  {
    "path": "pkg/commands/service/logging/datadog/list.go",
    "content": "package datadog\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// ListCommand calls the Fastly API to list Datadog logging endpoints.\ntype ListCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tInput          fastly.ListDatadogInput\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n}\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"list\", \"List Datadog endpoints on a Fastly service version\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *ListCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fsterr.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tc.Input.ServiceID = serviceID\n\tc.Input.ServiceVersion = fastly.ToValue(serviceVersion.Number)\n\n\to, err := c.Globals.APIClient.ListDatadog(context.TODO(), &c.Input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\tif !c.Globals.Verbose() {\n\t\ttw := text.NewTable(out)\n\t\ttw.AddHeader(\"SERVICE\", \"VERSION\", \"NAME\")\n\t\tfor _, datadog := range o {\n\t\t\ttw.AddLine(\n\t\t\t\tfastly.ToValue(datadog.ServiceID),\n\t\t\t\tfastly.ToValue(datadog.ServiceVersion),\n\t\t\t\tfastly.ToValue(datadog.Name),\n\t\t\t)\n\t\t}\n\t\ttw.Print()\n\t\treturn nil\n\t}\n\n\tfmt.Fprintf(out, \"Version: %d\\n\", c.Input.ServiceVersion)\n\tfor i, datadog := range o {\n\t\tfmt.Fprintf(out, \"\\tDatadog %d/%d\\n\", i+1, len(o))\n\t\tfmt.Fprintf(out, \"\\t\\tService ID: %s\\n\", fastly.ToValue(datadog.ServiceID))\n\t\tfmt.Fprintf(out, \"\\t\\tVersion: %d\\n\", fastly.ToValue(datadog.ServiceVersion))\n\t\tfmt.Fprintf(out, \"\\t\\tName: %s\\n\", fastly.ToValue(datadog.Name))\n\t\tfmt.Fprintf(out, \"\\t\\tToken: %s\\n\", fastly.ToValue(datadog.Token))\n\t\tfmt.Fprintf(out, \"\\t\\tRegion: %s\\n\", fastly.ToValue(datadog.Region))\n\t\tfmt.Fprintf(out, \"\\t\\tFormat: %s\\n\", fastly.ToValue(datadog.Format))\n\t\tfmt.Fprintf(out, \"\\t\\tFormat version: %d\\n\", fastly.ToValue(datadog.FormatVersion))\n\t\tfmt.Fprintf(out, \"\\t\\tResponse condition: %s\\n\", fastly.ToValue(datadog.ResponseCondition))\n\t\tfmt.Fprintf(out, \"\\t\\tPlacement: %s\\n\", fastly.ToValue(datadog.Placement))\n\t\tfmt.Fprintf(out, \"\\t\\tProcessing region: %s\\n\", fastly.ToValue(datadog.ProcessingRegion))\n\t}\n\tfmt.Fprintln(out)\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/datadog/root.go",
    "content": "package datadog\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"datadog\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manipulate Fastly service version Datadog logging endpoints\")\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/datadog/update.go",
    "content": "package datadog\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/service/logging/logflags\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/manifest\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// UpdateCommand calls the Fastly API to update a Datadog logging endpoint.\ntype UpdateCommand struct {\n\targparser.Base\n\tManifest manifest.Data\n\n\t// Required.\n\tEndpointName   string // Can't shadow argparser.Base method Name().\n\tServiceName    argparser.OptionalServiceNameID\n\tServiceVersion argparser.OptionalServiceVersion\n\n\t// Optional.\n\tAutoClone         argparser.OptionalAutoClone\n\tFormat            argparser.OptionalString\n\tFormatVersion     argparser.OptionalInt\n\tNewName           argparser.OptionalString\n\tPlacement         argparser.OptionalString\n\tProcessingRegion  argparser.OptionalString\n\tRegion            argparser.OptionalString\n\tResponseCondition argparser.OptionalString\n\tToken             argparser.OptionalString\n}\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"update\", \"Update a Datadog logging endpoint on a Fastly service version\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"The name of the Datadog logging object\").Short('n').Required().StringVar(&c.EndpointName)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.ServiceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.AutoClone.Set,\n\t\tDst:    &c.AutoClone.Value,\n\t})\n\tc.CmdClause.Flag(\"auth-token\", \"The API key from your Datadog account\").Action(c.Token.Set).StringVar(&c.Token.Value)\n\tlogflags.Format(c.CmdClause, &c.Format)\n\tlogflags.FormatVersion(c.CmdClause, &c.FormatVersion)\n\tc.CmdClause.Flag(\"new-name\", \"New name of the Datadog logging object\").Action(c.NewName.Set).StringVar(&c.NewName.Value)\n\tlogflags.Placement(c.CmdClause, &c.Placement)\n\tlogflags.ProcessingRegion(c.CmdClause, &c.ProcessingRegion, \"Datadog\")\n\tc.CmdClause.Flag(\"region\", \"The region where logs are received and stored by Datadog. One of US, US3, US5, or EU. Defaults to US if undefined\").Action(c.Region.Set).StringVar(&c.Region.Value)\n\tlogflags.ResponseCondition(c.CmdClause, &c.ResponseCondition)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.ServiceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.ServiceName.Value,\n\t})\n\treturn &c\n}\n\n// ConstructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *UpdateCommand) ConstructInput(serviceID string, serviceVersion int) (*fastly.UpdateDatadogInput, error) {\n\tinput := fastly.UpdateDatadogInput{\n\t\tServiceID:      serviceID,\n\t\tServiceVersion: serviceVersion,\n\t\tName:           c.EndpointName,\n\t}\n\n\tif c.NewName.WasSet {\n\t\tinput.NewName = &c.NewName.Value\n\t}\n\n\tif c.Token.WasSet {\n\t\tinput.Token = &c.Token.Value\n\t}\n\n\tif c.Region.WasSet {\n\t\tinput.Region = &c.Region.Value\n\t}\n\n\tif c.Format.WasSet {\n\t\tinput.Format = fastly.ToPointer(argparser.Content(c.Format.Value))\n\t}\n\n\tif c.FormatVersion.WasSet {\n\t\tinput.FormatVersion = &c.FormatVersion.Value\n\t}\n\n\tif c.ResponseCondition.WasSet {\n\t\tinput.ResponseCondition = &c.ResponseCondition.Value\n\t}\n\n\tif c.Placement.WasSet {\n\t\tinput.Placement = &c.Placement.Value\n\t}\n\n\tif c.ProcessingRegion.WasSet {\n\t\tinput.ProcessingRegion = &c.ProcessingRegion.Value\n\t}\n\n\treturn &input, nil\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *UpdateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(false),\n\t\tLocked:             optional.Of(false),\n\t\tAutoCloneFlag:      c.AutoClone,\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.ServiceName,\n\t\tServiceVersionFlag: c.ServiceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": errors.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tinput, err := c.ConstructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tdatadog, err := c.Globals.APIClient.UpdateDatadog(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\ttext.Success(\n\t\tout,\n\t\t\"Updated Datadog logging endpoint %s (service %s version %d)\",\n\t\tfastly.ToValue(datadog.Name),\n\t\tfastly.ToValue(datadog.ServiceID),\n\t\tfastly.ToValue(datadog.ServiceVersion),\n\t)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/debug/debug_test.go",
    "content": "package debug\n\nimport (\n\t\"encoding/json\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n)\n\n// TestParseLoggingError validates we're correctly decoding individual logging error JSON.\nfunc TestParseLoggingError(t *testing.T) {\n\tdata := []byte(`{\"sequence_number\":1,\"error_time_us\":1601645172164,\"stream\":\"logging_error\",\"message\":\"Failed to send log\",\"endpoint\":\"my-s3-endpoint\",\"details\":\"connection refused\"}`)\n\n\tvar got fastly.LoggingEndpointError\n\terr := json.Unmarshal(data, &got)\n\tif err != nil {\n\t\tt.Fatalf(\"error parsing response data: %v\", err)\n\t}\n\n\twant := fastly.LoggingEndpointError{\n\t\tSequenceNumber: 1,\n\t\tTimestamp:      1601645172164,\n\t\tStream:         \"logging_error\",\n\t\tMessage:        \"Failed to send log\",\n\t\tEndpoint:       \"my-s3-endpoint\",\n\t\tDetails:        \"connection refused\",\n\t}\n\n\tif diff := cmp.Diff(want, got); diff != \"\" {\n\t\tt.Errorf(\"JSON unmarshal mismatch (-want +got):\\n%s\", diff)\n\t}\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/debug/doc.go",
    "content": "// Package debug contains the command to stream live logging endpoint errors,\n// providing visibility into logging pipeline issues for troubleshooting and resolution.\npackage debug\n"
  },
  {
    "path": "pkg/commands/service/logging/debug/root.go",
    "content": "package debug\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"os/signal\"\n\t\"strconv\"\n\t\"strings\"\n\t\"syscall\"\n\t\"time\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// batch wraps errors for sending to the output loop.\ntype batch struct {\n\tErrors []fastly.LoggingEndpointError\n}\n\n// Command is the command for streaming logging endpoint errors.\ntype Command struct {\n\targparser.Base\n\n\tserviceName     argparser.OptionalServiceNameID\n\tserviceID       string\n\tfrom            uint64\n\tto              uint64\n\tfilter          string\n\tprintTimestamps bool\n\tjsonOutput      bool\n\tbatchCh         chan batch\n\tdieCh           chan struct{}\n\tdoneCh          chan struct{}\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"debug\"\n\n// NewDebugCommand returns a new command registered in the parent.\nfunc NewDebugCommand(parent argparser.Registerer, g *global.Data) *Command {\n\tvar c Command\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Stream live logging endpoint errors\")\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\tc.CmdClause.Flag(\"from\", \"From time, in Unix seconds\").Uint64Var(&c.from)\n\tc.CmdClause.Flag(\"to\", \"To time, in Unix seconds\").Uint64Var(&c.to)\n\tc.CmdClause.Flag(\"filter\", \"Filter errors by logging endpoint name\").StringVar(&c.filter)\n\tc.CmdClause.Flag(\"timestamps\", \"Print full timestamps instead of compact time\").BoolVar(&c.printTimestamps)\n\tc.CmdClause.Flag(\"json\", \"Output error stream as JSON\").BoolVar(&c.jsonOutput)\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *Command) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, source, flag, err := argparser.ServiceID(c.serviceName, *c.Globals.Manifest, c.Globals.APIClient, c.Globals.ErrLog)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif c.Globals.Verbose() {\n\t\targparser.DisplayServiceID(serviceID, flag, source, out)\n\t}\n\n\tc.serviceID = serviceID\n\n\tc.dieCh = make(chan struct{})\n\tc.batchCh = make(chan batch)\n\tc.doneCh = make(chan struct{})\n\n\ttext.Info(out, \"Streaming logging endpoint errors for service %s\\n\\n\", c.serviceID)\n\n\tfailure := make(chan error)\n\tsigs := make(chan os.Signal, 2)\n\tsignal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP)\n\n\t// Start the output loop.\n\tgo c.outputLoop(out)\n\n\t// Start streaming the errors.\n\tgo func() {\n\t\tfailure <- c.stream(out)\n\t}()\n\n\tselect {\n\tcase asyncErr := <-failure:\n\t\tclose(c.dieCh)\n\t\treturn asyncErr\n\tcase <-c.doneCh:\n\t\treturn nil\n\tcase <-sigs:\n\t\tclose(c.dieCh)\n\t}\n\n\treturn nil\n}\n\n// stream fetches error data from the API and sends it to the output loop.\nfunc (c *Command) stream(out io.Writer) error {\n\tvar curWindow *uint64\n\tif c.from != 0 {\n\t\tcurWindow = &c.from\n\t}\n\tvar toWindow *uint64\n\tif c.to != 0 {\n\t\ttoWindow = &c.to\n\t}\n\n\t// Prepare filter slice\n\tvar filter []string\n\tif c.filter != \"\" {\n\t\tfilter = []string{c.filter}\n\t}\n\n\tctx := context.Background()\n\n\tfor {\n\t\t// Check if we've passed the \"to\" requirement.\n\t\tif toWindow != nil && curWindow != nil && *curWindow > *toWindow {\n\t\t\ttext.Info(out, \"Reached window: %v which is newer than the requested 'to': %v\", *curWindow, *toWindow)\n\t\t\tclose(c.doneCh)\n\t\t\tbreak\n\t\t}\n\n\t\t// Use go-fastly to fetch logging endpoint errors\n\t\tresp, err := c.Globals.APIClient.GetLoggingEndpointErrors(ctx, &fastly.LoggingEndpointErrorsInput{\n\t\t\tServiceID: c.serviceID,\n\t\t\tFrom:      curWindow,\n\t\t\tTo:        toWindow,\n\t\t\tFilter:    filter,\n\t\t})\n\t\tif err != nil {\n\t\t\tc.Globals.ErrLog.Add(err)\n\t\t\treturn fmt.Errorf(\"unable to fetch logging endpoint errors: %w\", err)\n\t\t}\n\n\t\t// Send errors to the output loop\n\t\tif len(resp.Errors) > 0 {\n\t\t\tc.batchCh <- batch{Errors: resp.Errors}\n\t\t}\n\n\t\t// Check for next link to continue streaming\n\t\tif resp.NextFrom != \"\" {\n\t\t\t// Parse the next link value (it's already the from parameter value)\n\t\t\tnextFrom, err := strconv.ParseUint(resp.NextFrom, 10, 64)\n\t\t\tif err != nil {\n\t\t\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\t\t\"NextFrom\": resp.NextFrom,\n\t\t\t\t})\n\t\t\t\ttext.Error(out, \"error parsing next from\")\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tcurWindow = &nextFrom\n\t\t} else {\n\t\t\t// No next link, we're done\n\t\t\tclose(c.doneCh)\n\t\t\tbreak\n\t\t}\n\t}\n\treturn nil\n}\n\n// outputLoop processes the errors out of band from the request/response loop.\nfunc (c *Command) outputLoop(out io.Writer) {\n\tfor {\n\t\tselect {\n\t\tcase <-c.dieCh:\n\t\t\treturn\n\t\tcase batch := <-c.batchCh:\n\t\t\tc.printErrors(out, batch.Errors)\n\t\t}\n\t}\n}\n\n// printErrors prints error entries.\nfunc (c *Command) printErrors(out io.Writer, errors []fastly.LoggingEndpointError) {\n\tif len(errors) == 0 {\n\t\treturn\n\t}\n\n\tif c.jsonOutput {\n\t\t// Output as JSON array\n\t\tencoder := json.NewEncoder(out)\n\t\tfor _, e := range errors {\n\t\t\tif err := encoder.Encode(e); err != nil {\n\t\t\t\tc.Globals.ErrLog.Add(err)\n\t\t\t}\n\t\t}\n\t} else {\n\t\t// Find the longest endpoint name in this batch for dynamic width\n\t\tmaxEndpointLen := 0\n\t\tfor _, e := range errors {\n\t\t\tif len(e.Endpoint) > maxEndpointLen {\n\t\t\t\tmaxEndpointLen = len(e.Endpoint)\n\t\t\t}\n\t\t}\n\n\t\t// Human-readable format - match log-tail style\n\t\tfor _, e := range errors {\n\t\t\t// Format timestamp\n\t\t\t// #nosec G115 -- Timestamp is in microseconds, multiplication by 1000 for nanoseconds is safe for reasonable time values\n\t\t\ttimestamp := time.Unix(0, int64(e.Timestamp)*1000) // Convert microseconds to nanoseconds\n\t\t\tvar timeStr string\n\t\t\tif c.printTimestamps {\n\t\t\t\t// Full timestamp with --timestamps flag\n\t\t\t\ttimeStr = timestamp.UTC().Format(time.RFC3339)\n\t\t\t} else {\n\t\t\t\t// Compact time by default (HH:MM:SS)\n\t\t\t\ttimeStr = timestamp.UTC().Format(\"15:04:05\")\n\t\t\t}\n\n\t\t\t// Extract clean error message from details JSON if present\n\t\t\terrorSummary := e.Message\n\t\t\tif e.Details != \"\" {\n\t\t\t\tvar detailsJSON map[string]interface{}\n\t\t\t\tif err := json.Unmarshal([]byte(e.Details), &detailsJSON); err == nil {\n\t\t\t\t\t// Try to extract a cleaner error message\n\t\t\t\t\tif errorMsg, ok := detailsJSON[\"error\"].(string); ok {\n\t\t\t\t\t\t// Simplify common error patterns\n\t\t\t\t\t\terrorMsg = strings.TrimPrefix(errorMsg, \"non-temporary request err: \")\n\t\t\t\t\t\terrorMsg = strings.TrimPrefix(errorMsg, \"temporary request err: \")\n\t\t\t\t\t\terrorSummary = errorMsg\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Format: time | endpoint | message\n\t\t\tfmt.Fprintf(out, \"%s | %-*s | %s\\n\", timeStr, maxEndpointLen, e.Endpoint, errorSummary)\n\t\t}\n\t}\n\n\t// Flush output immediately\n\tif f, ok := out.(*os.File); ok {\n\t\t_ = f.Sync()\n\t}\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/digitalocean/create.go",
    "content": "package digitalocean\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/service/logging/logflags\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/manifest\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// CreateCommand calls the Fastly API to create a DigitalOcean Spaces logging endpoint.\ntype CreateCommand struct {\n\targparser.Base\n\tManifest manifest.Data\n\n\t// Required.\n\tServiceName    argparser.OptionalServiceNameID\n\tServiceVersion argparser.OptionalServiceVersion\n\n\t// Optional.\n\tAccessKey         argparser.OptionalString\n\tAutoClone         argparser.OptionalAutoClone\n\tBucketName        argparser.OptionalString\n\tCompressionCodec  argparser.OptionalString\n\tDomain            argparser.OptionalString\n\tEndpointName      argparser.OptionalString // Can't shadow argparser.Base method Name().\n\tFormat            argparser.OptionalString\n\tFormatVersion     argparser.OptionalInt\n\tGzipLevel         argparser.OptionalInt\n\tMessageType       argparser.OptionalString\n\tPath              argparser.OptionalString\n\tPeriod            argparser.OptionalInt\n\tPlacement         argparser.OptionalString\n\tProcessingRegion  argparser.OptionalString\n\tPublicKey         argparser.OptionalString\n\tResponseCondition argparser.OptionalString\n\tSecretKey         argparser.OptionalString\n\tTimestampFormat   argparser.OptionalString\n}\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"create\", \"Create a DigitalOcean Spaces logging endpoint on a Fastly service version\").Alias(\"add\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.ServiceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.AutoClone.Set,\n\t\tDst:    &c.AutoClone.Value,\n\t})\n\tc.CmdClause.Flag(\"access-key\", \"Your DigitalOcean Spaces account access key\").Action(c.AccessKey.Set).StringVar(&c.AccessKey.Value)\n\tc.CmdClause.Flag(\"bucket\", \"The name of the DigitalOcean Space\").Action(c.BucketName.Set).StringVar(&c.BucketName.Value)\n\tlogflags.CompressionCodec(c.CmdClause, &c.CompressionCodec)\n\tc.CmdClause.Flag(\"domain\", \"The domain of the DigitalOcean Spaces endpoint (default 'nyc3.digitaloceanspaces.com')\").Action(c.Domain.Set).StringVar(&c.Domain.Value)\n\tlogflags.Format(c.CmdClause, &c.Format)\n\tlogflags.FormatVersion(c.CmdClause, &c.FormatVersion)\n\tlogflags.GzipLevel(c.CmdClause, &c.GzipLevel)\n\tc.CmdClause.Flag(\"name\", \"The name of the DigitalOcean Spaces logging object. Used as a primary key for API access\").Short('n').Action(c.EndpointName.Set).StringVar(&c.EndpointName.Value)\n\tlogflags.MessageType(c.CmdClause, &c.MessageType)\n\tlogflags.Path(c.CmdClause, &c.Path)\n\tlogflags.Period(c.CmdClause, &c.Period)\n\tlogflags.Placement(c.CmdClause, &c.Placement)\n\tlogflags.ProcessingRegion(c.CmdClause, &c.ProcessingRegion, \"DigitalOcean Spaces\")\n\tlogflags.PublicKey(c.CmdClause, &c.PublicKey)\n\tlogflags.ResponseCondition(c.CmdClause, &c.ResponseCondition)\n\tc.CmdClause.Flag(\"secret-key\", \"Your DigitalOcean Spaces account secret key\").Action(c.SecretKey.Set).StringVar(&c.SecretKey.Value)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.ServiceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.ServiceName.Value,\n\t})\n\tlogflags.TimestampFormat(c.CmdClause, &c.TimestampFormat)\n\treturn &c\n}\n\n// ConstructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *CreateCommand) ConstructInput(serviceID string, serviceVersion int) (*fastly.CreateDigitalOceanInput, error) {\n\tvar input fastly.CreateDigitalOceanInput\n\n\tinput.ServiceID = serviceID\n\tinput.ServiceVersion = serviceVersion\n\tif c.EndpointName.WasSet {\n\t\tinput.Name = &c.EndpointName.Value\n\t}\n\tif c.BucketName.WasSet {\n\t\tinput.BucketName = &c.BucketName.Value\n\t}\n\tif c.AccessKey.WasSet {\n\t\tinput.AccessKey = &c.AccessKey.Value\n\t}\n\tif c.SecretKey.WasSet {\n\t\tinput.SecretKey = &c.SecretKey.Value\n\t}\n\n\t// The following blocks enforces the mutual exclusivity of the\n\t// CompressionCodec and GzipLevel flags.\n\tif c.CompressionCodec.WasSet && c.GzipLevel.WasSet {\n\t\treturn nil, fmt.Errorf(\"error parsing arguments: the --compression-codec flag is mutually exclusive with the --gzip-level flag\")\n\t}\n\n\tif c.Domain.WasSet {\n\t\tinput.Domain = &c.Domain.Value\n\t}\n\n\tif c.Path.WasSet {\n\t\tinput.Path = &c.Path.Value\n\t}\n\n\tif c.Period.WasSet {\n\t\tinput.Period = &c.Period.Value\n\t}\n\n\tif c.GzipLevel.WasSet {\n\t\tinput.GzipLevel = &c.GzipLevel.Value\n\t}\n\n\tif c.Format.WasSet {\n\t\tinput.Format = fastly.ToPointer(argparser.Content(c.Format.Value))\n\t}\n\n\tif c.FormatVersion.WasSet {\n\t\tinput.FormatVersion = &c.FormatVersion.Value\n\t}\n\n\tif c.ResponseCondition.WasSet {\n\t\tinput.ResponseCondition = &c.ResponseCondition.Value\n\t}\n\n\tif c.MessageType.WasSet {\n\t\tinput.MessageType = &c.MessageType.Value\n\t}\n\n\tif c.TimestampFormat.WasSet {\n\t\tinput.TimestampFormat = &c.TimestampFormat.Value\n\t}\n\n\tif c.Placement.WasSet {\n\t\tinput.Placement = &c.Placement.Value\n\t}\n\n\tif c.PublicKey.WasSet {\n\t\tinput.PublicKey = &c.PublicKey.Value\n\t}\n\n\tif c.CompressionCodec.WasSet {\n\t\tinput.CompressionCodec = &c.CompressionCodec.Value\n\t}\n\n\tif c.ProcessingRegion.WasSet {\n\t\tinput.ProcessingRegion = &c.ProcessingRegion.Value\n\t}\n\n\treturn &input, nil\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *CreateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(false),\n\t\tLocked:             optional.Of(false),\n\t\tAutoCloneFlag:      c.AutoClone,\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.ServiceName,\n\t\tServiceVersionFlag: c.ServiceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": errors.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tinput, err := c.ConstructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\td, err := c.Globals.APIClient.CreateDigitalOcean(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\ttext.Success(\n\t\tout,\n\t\t\"Created DigitalOcean Spaces logging endpoint %s (service %s version %d)\",\n\t\tfastly.ToValue(d.Name),\n\t\tfastly.ToValue(d.ServiceID),\n\t\tfastly.ToValue(d.ServiceVersion),\n\t)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/digitalocean/delete.go",
    "content": "package digitalocean\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DeleteCommand calls the Fastly API to delete a DigitalOcean Spaces logging endpoint.\ntype DeleteCommand struct {\n\targparser.Base\n\tInput          fastly.DeleteDigitalOceanInput\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n\tautoClone      argparser.OptionalAutoClone\n}\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"delete\", \"Delete a DigitalOcean Spaces logging endpoint on a Fastly service version\").Alias(\"remove\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"The name of the DigitalOcean Spaces logging object\").Short('n').Required().StringVar(&c.Input.Name)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.autoClone.Set,\n\t\tDst:    &c.autoClone.Value,\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DeleteCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(false),\n\t\tLocked:             optional.Of(false),\n\t\tAutoCloneFlag:      c.autoClone,\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": errors.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tc.Input.ServiceID = serviceID\n\tc.Input.ServiceVersion = fastly.ToValue(serviceVersion.Number)\n\n\tif err := c.Globals.APIClient.DeleteDigitalOcean(context.TODO(), &c.Input); err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Deleted DigitalOcean Spaces logging endpoint %s (service %s version %d)\", c.Input.Name, c.Input.ServiceID, c.Input.ServiceVersion)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/digitalocean/describe.go",
    "content": "package digitalocean\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DescribeCommand calls the Fastly API to describe a DigitalOcean Spaces logging endpoint.\ntype DescribeCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tInput          fastly.GetDigitalOceanInput\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n}\n\n// NewDescribeCommand returns a usable command registered under the parent.\nfunc NewDescribeCommand(parent argparser.Registerer, g *global.Data) *DescribeCommand {\n\tc := DescribeCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"describe\", \"Show detailed information about a DigitalOcean Spaces logging endpoint on a Fastly service version\").Alias(\"get\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"The name of the DigitalOcean Spaces logging object\").Short('n').Required().StringVar(&c.Input.Name)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DescribeCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fsterr.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tc.Input.ServiceID = serviceID\n\tc.Input.ServiceVersion = fastly.ToValue(serviceVersion.Number)\n\n\to, err := c.Globals.APIClient.GetDigitalOcean(context.TODO(), &c.Input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\tlines := text.Lines{\n\t\t\"Access key\":         fastly.ToValue(o.AccessKey),\n\t\t\"Bucket\":             fastly.ToValue(o.BucketName),\n\t\t\"Domain\":             fastly.ToValue(o.Domain),\n\t\t\"Format version\":     fastly.ToValue(o.FormatVersion),\n\t\t\"Format\":             fastly.ToValue(o.Format),\n\t\t\"GZip level\":         fastly.ToValue(o.GzipLevel),\n\t\t\"Message type\":       fastly.ToValue(o.MessageType),\n\t\t\"Name\":               fastly.ToValue(o.Name),\n\t\t\"Path\":               fastly.ToValue(o.Path),\n\t\t\"Period\":             fastly.ToValue(o.Period),\n\t\t\"Placement\":          fastly.ToValue(o.Placement),\n\t\t\"Processing region\":  fastly.ToValue(o.ProcessingRegion),\n\t\t\"Public key\":         fastly.ToValue(o.PublicKey),\n\t\t\"Response condition\": fastly.ToValue(o.ResponseCondition),\n\t\t\"Secret key\":         fastly.ToValue(o.SecretKey),\n\t\t\"Timestamp format\":   fastly.ToValue(o.TimestampFormat),\n\t\t\"Version\":            fastly.ToValue(o.ServiceVersion),\n\t}\n\tif !c.Globals.Verbose() {\n\t\tlines[\"Service ID\"] = fastly.ToValue(o.ServiceID)\n\t}\n\ttext.PrintLines(out, lines)\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/digitalocean/digitalocean_integration_test.go",
    "content": "package digitalocean_test\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/mock\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n\n\troot \"github.com/fastly/cli/pkg/commands/service\"\n\tparent \"github.com/fastly/cli/pkg/commands/service/logging\"\n\tsub \"github.com/fastly/cli/pkg/commands/service/logging/digitalocean\"\n)\n\nfunc TestDigitalOceanCreate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name log --bucket log --access-key foo --secret-key abc --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:         testutil.GetVersion,\n\t\t\t\tCloneVersionFn:       testutil.CloneVersionResult(4),\n\t\t\t\tCreateDigitalOceanFn: createDigitalOceanOK,\n\t\t\t},\n\t\t\tWantOutput: \"Created DigitalOcean Spaces logging endpoint log (service 123 version 4)\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name log --bucket log --access-key foo --secret-key abc --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:         testutil.GetVersion,\n\t\t\t\tCloneVersionFn:       testutil.CloneVersionResult(4),\n\t\t\t\tCreateDigitalOceanFn: createDigitalOceanError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name log --bucket log --access-key foo --secret-key abc --compression-codec zstd --gzip-level 9 --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t},\n\t\t\tWantError: \"error parsing arguments: the --compression-codec flag is mutually exclusive with the --gzip-level flag\",\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"create\"}, scenarios)\n}\n\nfunc TestDigitalOceanList(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:        testutil.GetVersion,\n\t\t\t\tListDigitalOceansFn: listDigitalOceansOK,\n\t\t\t},\n\t\t\tWantOutput: listDigitalOceansShortOutput,\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --verbose\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:        testutil.GetVersion,\n\t\t\t\tListDigitalOceansFn: listDigitalOceansOK,\n\t\t\t},\n\t\t\tWantOutput: listDigitalOceansVerboseOutput,\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 -v\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:        testutil.GetVersion,\n\t\t\t\tListDigitalOceansFn: listDigitalOceansOK,\n\t\t\t},\n\t\t\tWantOutput: listDigitalOceansVerboseOutput,\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:        testutil.GetVersion,\n\t\t\t\tListDigitalOceansFn: listDigitalOceansError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"list\"}, scenarios)\n}\n\nfunc TestDigitalOceanDescribe(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"--service-id 123 --version 1\",\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:      testutil.GetVersion,\n\t\t\t\tGetDigitalOceanFn: getDigitalOceanError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:      testutil.GetVersion,\n\t\t\t\tGetDigitalOceanFn: getDigitalOceanOK,\n\t\t\t},\n\t\t\tWantOutput: describeDigitalOceanOutput,\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"describe\"}, scenarios)\n}\n\nfunc TestDigitalOceanUpdate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"--service-id 123 --version 1 --new-name log\",\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs --new-name log --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:         testutil.GetVersion,\n\t\t\t\tCloneVersionFn:       testutil.CloneVersionResult(4),\n\t\t\t\tUpdateDigitalOceanFn: updateDigitalOceanError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs --new-name log --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:         testutil.GetVersion,\n\t\t\t\tCloneVersionFn:       testutil.CloneVersionResult(4),\n\t\t\t\tUpdateDigitalOceanFn: updateDigitalOceanOK,\n\t\t\t},\n\t\t\tWantOutput: \"Updated DigitalOcean Spaces logging endpoint log (service 123 version 4)\",\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"update\"}, scenarios)\n}\n\nfunc TestDigitalOceanDelete(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"--service-id 123 --version 1\",\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:         testutil.GetVersion,\n\t\t\t\tCloneVersionFn:       testutil.CloneVersionResult(4),\n\t\t\t\tDeleteDigitalOceanFn: deleteDigitalOceanError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:         testutil.GetVersion,\n\t\t\t\tCloneVersionFn:       testutil.CloneVersionResult(4),\n\t\t\t\tDeleteDigitalOceanFn: deleteDigitalOceanOK,\n\t\t\t},\n\t\t\tWantOutput: \"Deleted DigitalOcean Spaces logging endpoint logs (service 123 version 4)\",\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"delete\"}, scenarios)\n}\n\nvar errTest = errors.New(\"fixture error\")\n\nfunc createDigitalOceanOK(_ context.Context, i *fastly.CreateDigitalOceanInput) (*fastly.DigitalOcean, error) {\n\ts := fastly.DigitalOcean{\n\t\tServiceID:      fastly.ToPointer(i.ServiceID),\n\t\tServiceVersion: fastly.ToPointer(i.ServiceVersion),\n\t}\n\n\tif i.Name != nil {\n\t\ts.Name = i.Name\n\t}\n\n\treturn &s, nil\n}\n\nfunc createDigitalOceanError(_ context.Context, _ *fastly.CreateDigitalOceanInput) (*fastly.DigitalOcean, error) {\n\treturn nil, errTest\n}\n\nfunc listDigitalOceansOK(_ context.Context, i *fastly.ListDigitalOceansInput) ([]*fastly.DigitalOcean, error) {\n\treturn []*fastly.DigitalOcean{\n\t\t{\n\t\t\tServiceID:         fastly.ToPointer(i.ServiceID),\n\t\t\tServiceVersion:    fastly.ToPointer(i.ServiceVersion),\n\t\t\tName:              fastly.ToPointer(\"logs\"),\n\t\t\tBucketName:        fastly.ToPointer(\"my-logs\"),\n\t\t\tDomain:            fastly.ToPointer(\"https://digitalocean.us-east-1.amazonaws.com\"),\n\t\t\tAccessKey:         fastly.ToPointer(\"1234\"),\n\t\t\tSecretKey:         fastly.ToPointer(\"-----BEGIN RSA PRIVATE KEY-----MIIEogIBAAKCA\"),\n\t\t\tPath:              fastly.ToPointer(\"logs/\"),\n\t\t\tPeriod:            fastly.ToPointer(3600),\n\t\t\tGzipLevel:         fastly.ToPointer(9),\n\t\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\t\tFormatVersion:     fastly.ToPointer(2),\n\t\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\t\tMessageType:       fastly.ToPointer(\"classic\"),\n\t\t\tTimestampFormat:   fastly.ToPointer(\"%Y-%m-%dT%H:%M:%S.000\"),\n\t\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\t\tPublicKey:         fastly.ToPointer(pgpPublicKey()),\n\t\t},\n\t\t{\n\t\t\tServiceID:         fastly.ToPointer(i.ServiceID),\n\t\t\tServiceVersion:    fastly.ToPointer(i.ServiceVersion),\n\t\t\tName:              fastly.ToPointer(\"analytics\"),\n\t\t\tBucketName:        fastly.ToPointer(\"analytics\"),\n\t\t\tAccessKey:         fastly.ToPointer(\"1234\"),\n\t\t\tSecretKey:         fastly.ToPointer(\"-----BEGIN RSA PRIVATE KEY-----MIIEogIBAAKCA\"),\n\t\t\tDomain:            fastly.ToPointer(\"https://digitalocean.us-east-2.amazonaws.com\"),\n\t\t\tPath:              fastly.ToPointer(\"logs/\"),\n\t\t\tPeriod:            fastly.ToPointer(86400),\n\t\t\tGzipLevel:         fastly.ToPointer(9),\n\t\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\t\tFormatVersion:     fastly.ToPointer(2),\n\t\t\tMessageType:       fastly.ToPointer(\"classic\"),\n\t\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\t\tTimestampFormat:   fastly.ToPointer(\"%Y-%m-%dT%H:%M:%S.000\"),\n\t\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\t\tPublicKey:         fastly.ToPointer(pgpPublicKey()),\n\t\t},\n\t}, nil\n}\n\nfunc listDigitalOceansError(_ context.Context, _ *fastly.ListDigitalOceansInput) ([]*fastly.DigitalOcean, error) {\n\treturn nil, errTest\n}\n\nvar listDigitalOceansShortOutput = strings.TrimSpace(`\nSERVICE  VERSION  NAME\n123      1        logs\n123      1        analytics\n`) + \"\\n\"\n\nvar listDigitalOceansVerboseOutput = strings.TrimSpace(`\nFastly API endpoint: https://api.fastly.com\nFastly API token provided via config file (auth: user)\n\nService ID (via --service-id): 123\n\nVersion: 1\n\tDigitalOcean 1/2\n\t\tService ID: 123\n\t\tVersion: 1\n\t\tName: logs\n\t\tBucket: my-logs\n\t\tDomain: https://digitalocean.us-east-1.amazonaws.com\n\t\tAccess key: 1234\n\t\tSecret key: -----BEGIN RSA PRIVATE KEY-----MIIEogIBAAKCA\n\t\tPath: logs/\n\t\tPeriod: 3600\n\t\tGZip level: 9\n\t\tFormat: %h %l %u %t \"%r\" %>s %b\n\t\tFormat version: 2\n\t\tResponse condition: Prevent default logging\n\t\tMessage type: classic\n\t\tTimestamp format: %Y-%m-%dT%H:%M:%S.000\n\t\tPlacement: none\n\t\tPublic key: `+pgpPublicKey()+`\n\tDigitalOcean 2/2\n\t\tService ID: 123\n\t\tVersion: 1\n\t\tName: analytics\n\t\tBucket: analytics\n\t\tDomain: https://digitalocean.us-east-2.amazonaws.com\n\t\tAccess key: 1234\n\t\tSecret key: -----BEGIN RSA PRIVATE KEY-----MIIEogIBAAKCA\n\t\tPath: logs/\n\t\tPeriod: 86400\n\t\tGZip level: 9\n\t\tFormat: %h %l %u %t \"%r\" %>s %b\n\t\tFormat version: 2\n\t\tResponse condition: Prevent default logging\n\t\tMessage type: classic\n\t\tTimestamp format: %Y-%m-%dT%H:%M:%S.000\n\t\tPlacement: none\n\t\tPublic key: `+pgpPublicKey()+`\n`) + \"\\n\\n\"\n\nfunc getDigitalOceanOK(_ context.Context, i *fastly.GetDigitalOceanInput) (*fastly.DigitalOcean, error) {\n\treturn &fastly.DigitalOcean{\n\t\tServiceID:         fastly.ToPointer(i.ServiceID),\n\t\tServiceVersion:    fastly.ToPointer(i.ServiceVersion),\n\t\tName:              fastly.ToPointer(\"logs\"),\n\t\tBucketName:        fastly.ToPointer(\"my-logs\"),\n\t\tDomain:            fastly.ToPointer(\"https://digitalocean.us-east-1.amazonaws.com\"),\n\t\tAccessKey:         fastly.ToPointer(\"1234\"),\n\t\tSecretKey:         fastly.ToPointer(\"-----BEGIN RSA PRIVATE KEY-----MIIEogIBAAKCA\"),\n\t\tPath:              fastly.ToPointer(\"logs/\"),\n\t\tPeriod:            fastly.ToPointer(3600),\n\t\tGzipLevel:         fastly.ToPointer(9),\n\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\tFormatVersion:     fastly.ToPointer(2),\n\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\tMessageType:       fastly.ToPointer(\"classic\"),\n\t\tTimestampFormat:   fastly.ToPointer(\"%Y-%m-%dT%H:%M:%S.000\"),\n\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\tPublicKey:         fastly.ToPointer(pgpPublicKey()),\n\t\tProcessingRegion:  fastly.ToPointer(\"us\"),\n\t}, nil\n}\n\nfunc getDigitalOceanError(_ context.Context, _ *fastly.GetDigitalOceanInput) (*fastly.DigitalOcean, error) {\n\treturn nil, errTest\n}\n\nvar describeDigitalOceanOutput = \"\\n\" + strings.TrimSpace(`\nAccess key: 1234\nBucket: my-logs\nDomain: https://digitalocean.us-east-1.amazonaws.com\nFormat: %h %l %u %t \"%r\" %>s %b\nFormat version: 2\nGZip level: 9\nMessage type: classic\nName: logs\nPath: logs/\nPeriod: 3600\nPlacement: none\nProcessing region: us\nPublic key: `+pgpPublicKey()+`\nResponse condition: Prevent default logging\nSecret key: -----BEGIN RSA PRIVATE KEY-----MIIEogIBAAKCA\nService ID: 123\nTimestamp format: %Y-%m-%dT%H:%M:%S.000\nVersion: 1\n`) + \"\\n\"\n\nfunc updateDigitalOceanOK(_ context.Context, i *fastly.UpdateDigitalOceanInput) (*fastly.DigitalOcean, error) {\n\treturn &fastly.DigitalOcean{\n\t\tServiceID:         fastly.ToPointer(i.ServiceID),\n\t\tServiceVersion:    fastly.ToPointer(i.ServiceVersion),\n\t\tName:              fastly.ToPointer(\"log\"),\n\t\tBucketName:        fastly.ToPointer(\"my-logs\"),\n\t\tDomain:            fastly.ToPointer(\"https://digitalocean.us-east-1.amazonaws.com\"),\n\t\tAccessKey:         fastly.ToPointer(\"1234\"),\n\t\tSecretKey:         fastly.ToPointer(\"-----BEGIN RSA PRIVATE KEY-----MIIEogIBAAKCA\"),\n\t\tPath:              fastly.ToPointer(\"logs/\"),\n\t\tPeriod:            fastly.ToPointer(3600),\n\t\tGzipLevel:         fastly.ToPointer(9),\n\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\tFormatVersion:     fastly.ToPointer(2),\n\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\tMessageType:       fastly.ToPointer(\"classic\"),\n\t\tTimestampFormat:   fastly.ToPointer(\"%Y-%m-%dT%H:%M:%S.000\"),\n\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\tPublicKey:         fastly.ToPointer(pgpPublicKey()),\n\t}, nil\n}\n\nfunc updateDigitalOceanError(_ context.Context, _ *fastly.UpdateDigitalOceanInput) (*fastly.DigitalOcean, error) {\n\treturn nil, errTest\n}\n\nfunc deleteDigitalOceanOK(_ context.Context, _ *fastly.DeleteDigitalOceanInput) error {\n\treturn nil\n}\n\nfunc deleteDigitalOceanError(_ context.Context, _ *fastly.DeleteDigitalOceanInput) error {\n\treturn errTest\n}\n\n// pgpPublicKey returns a PEM encoded PGP public key suitable for testing.\nfunc pgpPublicKey() string {\n\treturn strings.TrimSpace(`-----BEGIN PGP PUBLIC KEY BLOCK-----\nmQENBFyUD8sBCACyFnB39AuuTygseek+eA4fo0cgwva6/FSjnWq7riouQee8GgQ/\nibXTRyv4iVlwI12GswvMTIy7zNvs1R54i0qvsLr+IZ4GVGJqs6ZJnvQcqe3xPoR4\n8AnBfw90o32r/LuHf6QCJXi+AEu35koNlNAvLJ2B+KACaNB7N0EeWmqpV/1V2k9p\nlDYk+th7LcCuaFNGqKS/PrMnnMqR6VDLCjHhNx4KR79b0Twm/2qp6an3hyNRu8Gn\ndwxpf1/BUu3JWf+LqkN4Y3mbOmSUL3MaJNvyQguUzTfS0P0uGuBDHrJCVkMZCzDB\n89ag55jCPHyGeHBTd02gHMWzsg3WMBWvCsrzABEBAAG0JXRlcnJhZm9ybSAodGVz\ndCkgPHRlc3RAdGVycmFmb3JtLmNvbT6JAU4EEwEIADgWIQSHYyc6Kj9l6HzQsau6\nvFFc9jxV/wUCXJQPywIbAwULCQgHAgYVCgkICwIEFgIDAQIeAQIXgAAKCRC6vFFc\n9jxV/815CAClb32OxV7wG01yF97TzlyTl8TnvjMtoG29Mw4nSyg+mjM3b8N7iXm9\nOLX59fbDAWtBSldSZE22RXd3CvlFOG/EnKBXSjBtEqfyxYSnyOPkMPBYWGL/ApkX\nSvPYJ4LKdvipYToKFh3y9kk2gk1DcDBDyaaHvR+3rv1u3aoy7/s2EltAfDS3ZQIq\n7/cWTLJml/lleeB/Y6rPj8xqeCYhE5ahw9gsV/Mdqatl24V9Tks30iijx0Hhw+Gx\nkATUikMGr2GDVqoIRga5kXI7CzYff4rkc0Twn47fMHHHe/KY9M2yVnMHUXmAZwbG\nM1cMI/NH1DjevCKdGBLcRJlhuLPKF/anuQENBFyUD8sBCADIpd7r7GuPd6n/Ikxe\nu6h7umV6IIPoAm88xCYpTbSZiaK30Svh6Ywra9jfE2KlU9o6Y/art8ip0VJ3m07L\n4RSfSpnzqgSwdjSq5hNour2Fo/BzYhK7yaz2AzVSbe33R0+RYhb4b/6N+bKbjwGF\nftCsqVFMH+PyvYkLbvxyQrHlA9woAZaNThI1ztO5rGSnGUR8xt84eup28WIFKg0K\nUEGUcTzz+8QGAwAra+0ewPXo/AkO+8BvZjDidP417u6gpBHOJ9qYIcO9FxHeqFyu\nYrjlrxowEgXn5wO8xuNz6Vu1vhHGDHGDsRbZF8pv1d5O+0F1G7ttZ2GRRgVBZPwi\nkiyRABEBAAGJATYEGAEIACAWIQSHYyc6Kj9l6HzQsau6vFFc9jxV/wUCXJQPywIb\nDAAKCRC6vFFc9jxV/9YOCACe8qmOSnKQpQfW+PqYOqo3dt7JyweTs3FkD6NT8Zml\ndYy/vkstbTjPpX6aTvUZjkb46BVi7AOneVHpD5GBqvRsZ9iVgDYHaehmLCdKiG5L\n3Tp90NN+QY5WDbsGmsyk6+6ZMYejb4qYfweQeduOj27aavCJdLkCYMoRKfcFYI8c\nFaNmEfKKy/r1PO20NXEG6t9t05K/frHy6ZG8bCNYdpagfFVot47r9JaQqWlTNtIR\n5+zkkSq/eG9BEtRij3a6cTdQbktdBzx2KBeI0PYc1vlZR0LpuFKZqY9vlE6vTGLR\nwMfrTEOvx0NxUM3rpaCgEmuWbB1G1Hu371oyr4srrr+N\n=28dr\n-----END PGP PUBLIC KEY BLOCK-----\n`)\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/digitalocean/digitalocean_test.go",
    "content": "package digitalocean_test\n\nimport (\n\t\"bytes\"\n\t\"testing\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/service/logging/digitalocean\"\n\t\"github.com/fastly/cli/pkg/config\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/manifest\"\n\t\"github.com/fastly/cli/pkg/mock\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n)\n\nfunc TestCreateDigitalOceanInput(t *testing.T) {\n\tfor _, testcase := range []struct {\n\t\tname      string\n\t\tcmd       *digitalocean.CreateCommand\n\t\twant      *fastly.CreateDigitalOceanInput\n\t\twantError string\n\t}{\n\t\t{\n\t\t\tname: \"required values set flag serviceID\",\n\t\t\tcmd:  createCommandRequired(),\n\t\t\twant: &fastly.CreateDigitalOceanInput{\n\t\t\t\tServiceID:      \"123\",\n\t\t\t\tServiceVersion: 4,\n\t\t\t\tName:           fastly.ToPointer(\"log\"),\n\t\t\t\tBucketName:     fastly.ToPointer(\"bucket\"),\n\t\t\t\tAccessKey:      fastly.ToPointer(\"access\"),\n\t\t\t\tSecretKey:      fastly.ToPointer(\"secret\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"all values set flag serviceID\",\n\t\t\tcmd:  createCommandAll(),\n\t\t\twant: &fastly.CreateDigitalOceanInput{\n\t\t\t\tServiceID:         \"123\",\n\t\t\t\tServiceVersion:    4,\n\t\t\t\tName:              fastly.ToPointer(\"log\"),\n\t\t\t\tBucketName:        fastly.ToPointer(\"bucket\"),\n\t\t\t\tDomain:            fastly.ToPointer(\"nyc3.digitaloceanspaces.com\"),\n\t\t\t\tAccessKey:         fastly.ToPointer(\"access\"),\n\t\t\t\tSecretKey:         fastly.ToPointer(\"secret\"),\n\t\t\t\tPath:              fastly.ToPointer(\"/log\"),\n\t\t\t\tPeriod:            fastly.ToPointer(3600),\n\t\t\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\t\t\tMessageType:       fastly.ToPointer(\"classic\"),\n\t\t\t\tFormatVersion:     fastly.ToPointer(2),\n\t\t\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\t\t\tTimestampFormat:   fastly.ToPointer(\"%Y-%m-%dT%H:%M:%S.000\"),\n\t\t\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\t\t\tPublicKey:         fastly.ToPointer(pgpPublicKey()),\n\t\t\t\tCompressionCodec:  fastly.ToPointer(\"zstd\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:      \"error missing serviceID\",\n\t\t\tcmd:       createCommandMissingServiceID(),\n\t\t\twant:      nil,\n\t\t\twantError: errors.ErrNoServiceID.Error(),\n\t\t},\n\t} {\n\t\tt.Run(testcase.name, func(t *testing.T) {\n\t\t\tif testcase.wantError == errors.ErrNoServiceID.Error() {\n\t\t\t\tt.Setenv(\"FASTLY_SERVICE_ID\", \"\")\n\t\t\t}\n\t\t\tvar bs []byte\n\t\t\tout := bytes.NewBuffer(bs)\n\t\t\tverboseMode := true\n\n\t\t\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\t\t\tAutoCloneFlag:      testcase.cmd.AutoClone,\n\t\t\t\tAPIClient:          testcase.cmd.Globals.APIClient,\n\t\t\t\tManifest:           testcase.cmd.Manifest,\n\t\t\t\tOut:                out,\n\t\t\t\tServiceVersionFlag: testcase.cmd.ServiceVersion,\n\t\t\t\tVerboseMode:        verboseMode,\n\t\t\t})\n\n\t\t\tswitch {\n\t\t\tcase err != nil && testcase.wantError == \"\":\n\t\t\t\tt.Fatalf(\"unexpected error getting service details: %v\", err)\n\t\t\t\treturn\n\t\t\tcase err != nil && testcase.wantError != \"\":\n\t\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\t\treturn\n\t\t\tcase err == nil && testcase.wantError != \"\":\n\t\t\t\tt.Fatalf(\"expected error, have nil (service details: %s, %d)\", serviceID, serviceVersion.Number)\n\t\t\tcase err == nil && testcase.wantError == \"\":\n\t\t\t\thave, err := testcase.cmd.ConstructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\t\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\t\ttestutil.AssertEqual(t, testcase.want, have)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestUpdateDigitalOceanInput(t *testing.T) {\n\tscenarios := []struct {\n\t\tname      string\n\t\tcmd       *digitalocean.UpdateCommand\n\t\tapi       mock.API\n\t\twant      *fastly.UpdateDigitalOceanInput\n\t\twantError string\n\t}{\n\t\t{\n\t\t\tname: \"all values set flag serviceID\",\n\t\t\tcmd:  updateCommandAll(),\n\t\t\tapi: mock.API{\n\t\t\t\tGetVersionFn:      testutil.GetVersion,\n\t\t\t\tCloneVersionFn:    testutil.CloneVersionResult(4),\n\t\t\t\tGetDigitalOceanFn: getDigitalOceanOK,\n\t\t\t},\n\t\t\twant: &fastly.UpdateDigitalOceanInput{\n\t\t\t\tServiceID:         \"123\",\n\t\t\t\tServiceVersion:    4,\n\t\t\t\tName:              \"log\",\n\t\t\t\tNewName:           fastly.ToPointer(\"new1\"),\n\t\t\t\tBucketName:        fastly.ToPointer(\"new2\"),\n\t\t\t\tDomain:            fastly.ToPointer(\"new3\"),\n\t\t\t\tAccessKey:         fastly.ToPointer(\"new4\"),\n\t\t\t\tSecretKey:         fastly.ToPointer(\"new5\"),\n\t\t\t\tPath:              fastly.ToPointer(\"new6\"),\n\t\t\t\tPeriod:            fastly.ToPointer(3601),\n\t\t\t\tGzipLevel:         fastly.ToPointer(0),\n\t\t\t\tFormat:            fastly.ToPointer(\"new7\"),\n\t\t\t\tFormatVersion:     fastly.ToPointer(3),\n\t\t\t\tResponseCondition: fastly.ToPointer(\"new8\"),\n\t\t\t\tMessageType:       fastly.ToPointer(\"new9\"),\n\t\t\t\tTimestampFormat:   fastly.ToPointer(\"new10\"),\n\t\t\t\tPlacement:         fastly.ToPointer(\"new11\"),\n\t\t\t\tPublicKey:         fastly.ToPointer(\"new12\"),\n\t\t\t\tCompressionCodec:  fastly.ToPointer(\"new13\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"no updates\",\n\t\t\tcmd:  updateCommandNoUpdates(),\n\t\t\tapi: mock.API{\n\t\t\t\tGetVersionFn:      testutil.GetVersion,\n\t\t\t\tCloneVersionFn:    testutil.CloneVersionResult(4),\n\t\t\t\tGetDigitalOceanFn: getDigitalOceanOK,\n\t\t\t},\n\t\t\twant: &fastly.UpdateDigitalOceanInput{\n\t\t\t\tServiceID:      \"123\",\n\t\t\t\tServiceVersion: 4,\n\t\t\t\tName:           \"log\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:      \"error missing serviceID\",\n\t\t\tcmd:       updateCommandMissingServiceID(),\n\t\t\twant:      nil,\n\t\t\twantError: errors.ErrNoServiceID.Error(),\n\t\t},\n\t}\n\tfor testcaseIdx := range scenarios {\n\t\ttestcase := &scenarios[testcaseIdx]\n\t\tt.Run(testcase.name, func(t *testing.T) {\n\t\t\tif testcase.wantError == errors.ErrNoServiceID.Error() {\n\t\t\t\tt.Setenv(\"FASTLY_SERVICE_ID\", \"\")\n\t\t\t}\n\t\t\ttestcase.cmd.Globals.APIClient = testcase.api\n\n\t\t\tvar bs []byte\n\t\t\tout := bytes.NewBuffer(bs)\n\t\t\tverboseMode := true\n\n\t\t\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\t\t\tAutoCloneFlag:      testcase.cmd.AutoClone,\n\t\t\t\tAPIClient:          testcase.api,\n\t\t\t\tManifest:           testcase.cmd.Manifest,\n\t\t\t\tOut:                out,\n\t\t\t\tServiceVersionFlag: testcase.cmd.ServiceVersion,\n\t\t\t\tVerboseMode:        verboseMode,\n\t\t\t})\n\n\t\t\tswitch {\n\t\t\tcase err != nil && testcase.wantError == \"\":\n\t\t\t\tt.Fatalf(\"unexpected error getting service details: %v\", err)\n\t\t\t\treturn\n\t\t\tcase err != nil && testcase.wantError != \"\":\n\t\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\t\treturn\n\t\t\tcase err == nil && testcase.wantError != \"\":\n\t\t\t\tt.Fatalf(\"expected error, have nil (service details: %s, %d)\", serviceID, serviceVersion.Number)\n\t\t\tcase err == nil && testcase.wantError == \"\":\n\t\t\t\thave, err := testcase.cmd.ConstructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\t\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\t\ttestutil.AssertEqual(t, testcase.want, have)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc createCommandRequired() *digitalocean.CreateCommand {\n\tvar b bytes.Buffer\n\n\tg := global.Data{\n\t\tConfig: config.File{},\n\t\tEnv:    config.Environment{},\n\t\tOutput: &b,\n\t}\n\tg.APIClient, _ = mock.APIClient(mock.API{\n\t\tGetVersionFn:   testutil.GetVersion,\n\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t})(\"token\", \"endpoint\", false)\n\n\treturn &digitalocean.CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: &g,\n\t\t},\n\t\tManifest: manifest.Data{\n\t\t\tFlag: manifest.Flag{\n\t\t\t\tServiceID: \"123\",\n\t\t\t},\n\t\t},\n\t\tServiceVersion: argparser.OptionalServiceVersion{\n\t\t\tOptionalString: argparser.OptionalString{Value: \"1\"},\n\t\t},\n\t\tAutoClone: argparser.OptionalAutoClone{\n\t\t\tOptionalBool: argparser.OptionalBool{\n\t\t\t\tOptional: argparser.Optional{\n\t\t\t\t\tWasSet: true,\n\t\t\t\t},\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t},\n\t\tEndpointName: argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"log\"},\n\t\tBucketName:   argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"bucket\"},\n\t\tAccessKey:    argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"access\"},\n\t\tSecretKey:    argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"secret\"},\n\t}\n}\n\nfunc createCommandAll() *digitalocean.CreateCommand {\n\tvar b bytes.Buffer\n\n\tg := global.Data{\n\t\tConfig: config.File{},\n\t\tEnv:    config.Environment{},\n\t\tOutput: &b,\n\t}\n\tg.APIClient, _ = mock.APIClient(mock.API{\n\t\tGetVersionFn:   testutil.GetVersion,\n\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t})(\"token\", \"endpoint\", false)\n\n\treturn &digitalocean.CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: &g,\n\t\t},\n\t\tManifest: manifest.Data{\n\t\t\tFlag: manifest.Flag{\n\t\t\t\tServiceID: \"123\",\n\t\t\t},\n\t\t},\n\t\tServiceVersion: argparser.OptionalServiceVersion{\n\t\t\tOptionalString: argparser.OptionalString{Value: \"1\"},\n\t\t},\n\t\tAutoClone: argparser.OptionalAutoClone{\n\t\t\tOptionalBool: argparser.OptionalBool{\n\t\t\t\tOptional: argparser.Optional{\n\t\t\t\t\tWasSet: true,\n\t\t\t\t},\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t},\n\t\tEndpointName:      argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"log\"},\n\t\tBucketName:        argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"bucket\"},\n\t\tAccessKey:         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"access\"},\n\t\tSecretKey:         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"secret\"},\n\t\tDomain:            argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"nyc3.digitaloceanspaces.com\"},\n\t\tPath:              argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"/log\"},\n\t\tPeriod:            argparser.OptionalInt{Optional: argparser.Optional{WasSet: true}, Value: 3600},\n\t\tFormat:            argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: `%h %l %u %t \"%r\" %>s %b`},\n\t\tFormatVersion:     argparser.OptionalInt{Optional: argparser.Optional{WasSet: true}, Value: 2},\n\t\tResponseCondition: argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"Prevent default logging\"},\n\t\tTimestampFormat:   argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"%Y-%m-%dT%H:%M:%S.000\"},\n\t\tPlacement:         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"none\"},\n\t\tMessageType:       argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"classic\"},\n\t\tPublicKey:         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: pgpPublicKey()},\n\t\tCompressionCodec:  argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"zstd\"},\n\t}\n}\n\nfunc createCommandMissingServiceID() *digitalocean.CreateCommand {\n\tres := createCommandAll()\n\tres.Manifest = manifest.Data{}\n\tres.ServiceVersion = argparser.OptionalServiceVersion{}\n\treturn res\n}\n\nfunc updateCommandNoUpdates() *digitalocean.UpdateCommand {\n\tvar b bytes.Buffer\n\n\tg := global.Data{\n\t\tConfig: config.File{},\n\t\tEnv:    config.Environment{},\n\t\tOutput: &b,\n\t}\n\n\treturn &digitalocean.UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: &g,\n\t\t},\n\t\tManifest: manifest.Data{\n\t\t\tFlag: manifest.Flag{\n\t\t\t\tServiceID: \"123\",\n\t\t\t},\n\t\t},\n\t\tEndpointName: \"log\",\n\t\tServiceVersion: argparser.OptionalServiceVersion{\n\t\t\tOptionalString: argparser.OptionalString{Value: \"1\"},\n\t\t},\n\t\tAutoClone: argparser.OptionalAutoClone{\n\t\t\tOptionalBool: argparser.OptionalBool{\n\t\t\t\tOptional: argparser.Optional{\n\t\t\t\t\tWasSet: true,\n\t\t\t\t},\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t},\n\t}\n}\n\nfunc updateCommandAll() *digitalocean.UpdateCommand {\n\tvar b bytes.Buffer\n\n\tg := global.Data{\n\t\tConfig: config.File{},\n\t\tEnv:    config.Environment{},\n\t\tOutput: &b,\n\t}\n\n\treturn &digitalocean.UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: &g,\n\t\t},\n\t\tManifest: manifest.Data{\n\t\t\tFlag: manifest.Flag{\n\t\t\t\tServiceID: \"123\",\n\t\t\t},\n\t\t},\n\t\tEndpointName: \"log\",\n\t\tServiceVersion: argparser.OptionalServiceVersion{\n\t\t\tOptionalString: argparser.OptionalString{Value: \"1\"},\n\t\t},\n\t\tAutoClone: argparser.OptionalAutoClone{\n\t\t\tOptionalBool: argparser.OptionalBool{\n\t\t\t\tOptional: argparser.Optional{\n\t\t\t\t\tWasSet: true,\n\t\t\t\t},\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t},\n\t\tNewName:           argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new1\"},\n\t\tBucketName:        argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new2\"},\n\t\tDomain:            argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new3\"},\n\t\tAccessKey:         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new4\"},\n\t\tSecretKey:         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new5\"},\n\t\tPath:              argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new6\"},\n\t\tPeriod:            argparser.OptionalInt{Optional: argparser.Optional{WasSet: true}, Value: 3601},\n\t\tGzipLevel:         argparser.OptionalInt{Optional: argparser.Optional{WasSet: true}, Value: 0},\n\t\tFormat:            argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new7\"},\n\t\tFormatVersion:     argparser.OptionalInt{Optional: argparser.Optional{WasSet: true}, Value: 3},\n\t\tResponseCondition: argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new8\"},\n\t\tMessageType:       argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new9\"},\n\t\tTimestampFormat:   argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new10\"},\n\t\tPlacement:         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new11\"},\n\t\tPublicKey:         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new12\"},\n\t\tCompressionCodec:  argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new13\"},\n\t}\n}\n\nfunc updateCommandMissingServiceID() *digitalocean.UpdateCommand {\n\tres := updateCommandAll()\n\tres.Manifest = manifest.Data{}\n\tres.ServiceVersion = argparser.OptionalServiceVersion{}\n\treturn res\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/digitalocean/doc.go",
    "content": "// Package digitalocean contains commands to inspect and manipulate Fastly service DigitalOcean\n// logging endpoints.\npackage digitalocean\n"
  },
  {
    "path": "pkg/commands/service/logging/digitalocean/list.go",
    "content": "package digitalocean\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// ListCommand calls the Fastly API to list DigitalOcean Spaces logging endpoints.\ntype ListCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tInput          fastly.ListDigitalOceansInput\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n}\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"list\", \"List DigitalOcean Spaces logging endpoints on a Fastly service version\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *ListCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fsterr.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tc.Input.ServiceID = serviceID\n\tc.Input.ServiceVersion = fastly.ToValue(serviceVersion.Number)\n\n\to, err := c.Globals.APIClient.ListDigitalOceans(context.TODO(), &c.Input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\tif !c.Globals.Verbose() {\n\t\ttw := text.NewTable(out)\n\t\ttw.AddHeader(\"SERVICE\", \"VERSION\", \"NAME\")\n\t\tfor _, digitalocean := range o {\n\t\t\ttw.AddLine(\n\t\t\t\tfastly.ToValue(digitalocean.ServiceID),\n\t\t\t\tfastly.ToValue(digitalocean.ServiceVersion),\n\t\t\t\tfastly.ToValue(digitalocean.Name),\n\t\t\t)\n\t\t}\n\t\ttw.Print()\n\t\treturn nil\n\t}\n\n\tfmt.Fprintf(out, \"Version: %d\\n\", c.Input.ServiceVersion)\n\tfor i, digitalocean := range o {\n\t\tfmt.Fprintf(out, \"\\tDigitalOcean %d/%d\\n\", i+1, len(o))\n\t\tfmt.Fprintf(out, \"\\t\\tService ID: %s\\n\", fastly.ToValue(digitalocean.ServiceID))\n\t\tfmt.Fprintf(out, \"\\t\\tVersion: %d\\n\", fastly.ToValue(digitalocean.ServiceVersion))\n\t\tfmt.Fprintf(out, \"\\t\\tName: %s\\n\", fastly.ToValue(digitalocean.Name))\n\t\tfmt.Fprintf(out, \"\\t\\tBucket: %s\\n\", fastly.ToValue(digitalocean.BucketName))\n\t\tfmt.Fprintf(out, \"\\t\\tDomain: %s\\n\", fastly.ToValue(digitalocean.Domain))\n\t\tfmt.Fprintf(out, \"\\t\\tAccess key: %s\\n\", fastly.ToValue(digitalocean.AccessKey))\n\t\tfmt.Fprintf(out, \"\\t\\tSecret key: %s\\n\", fastly.ToValue(digitalocean.SecretKey))\n\t\tfmt.Fprintf(out, \"\\t\\tPath: %s\\n\", fastly.ToValue(digitalocean.Path))\n\t\tfmt.Fprintf(out, \"\\t\\tPeriod: %d\\n\", fastly.ToValue(digitalocean.Period))\n\t\tfmt.Fprintf(out, \"\\t\\tGZip level: %d\\n\", fastly.ToValue(digitalocean.GzipLevel))\n\t\tfmt.Fprintf(out, \"\\t\\tFormat: %s\\n\", fastly.ToValue(digitalocean.Format))\n\t\tfmt.Fprintf(out, \"\\t\\tFormat version: %d\\n\", fastly.ToValue(digitalocean.FormatVersion))\n\t\tfmt.Fprintf(out, \"\\t\\tResponse condition: %s\\n\", fastly.ToValue(digitalocean.ResponseCondition))\n\t\tfmt.Fprintf(out, \"\\t\\tMessage type: %s\\n\", fastly.ToValue(digitalocean.MessageType))\n\t\tfmt.Fprintf(out, \"\\t\\tTimestamp format: %s\\n\", fastly.ToValue(digitalocean.TimestampFormat))\n\t\tfmt.Fprintf(out, \"\\t\\tPlacement: %s\\n\", fastly.ToValue(digitalocean.Placement))\n\t\tfmt.Fprintf(out, \"\\t\\tPublic key: %s\\n\", fastly.ToValue(digitalocean.PublicKey))\n\t}\n\tfmt.Fprintln(out)\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/digitalocean/root.go",
    "content": "package digitalocean\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"digitalocean\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manipulate Fastly service version DigitalOcean Spaces logging endpoints\")\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/digitalocean/update.go",
    "content": "package digitalocean\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/service/logging/logflags\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/manifest\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// UpdateCommand calls the Fastly API to update a DigitalOcean Spaces logging endpoint.\ntype UpdateCommand struct {\n\targparser.Base\n\tManifest manifest.Data\n\n\t// Required.\n\tEndpointName   string\n\tServiceName    argparser.OptionalServiceNameID\n\tServiceVersion argparser.OptionalServiceVersion\n\n\t// Optional.\n\tAccessKey         argparser.OptionalString\n\tAutoClone         argparser.OptionalAutoClone\n\tBucketName        argparser.OptionalString\n\tCompressionCodec  argparser.OptionalString\n\tDomain            argparser.OptionalString\n\tFormat            argparser.OptionalString\n\tFormatVersion     argparser.OptionalInt\n\tGzipLevel         argparser.OptionalInt\n\tMessageType       argparser.OptionalString\n\tNewName           argparser.OptionalString\n\tPath              argparser.OptionalString\n\tPeriod            argparser.OptionalInt\n\tPlacement         argparser.OptionalString\n\tProcessingRegion  argparser.OptionalString\n\tPublicKey         argparser.OptionalString\n\tResponseCondition argparser.OptionalString\n\tSecretKey         argparser.OptionalString\n\tTimestampFormat   argparser.OptionalString\n}\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"update\", \"Update a DigitalOcean Spaces logging endpoint on a Fastly service version\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"The name of the DigitalOcean Spaces logging object\").Short('n').Required().StringVar(&c.EndpointName)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.ServiceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.CmdClause.Flag(\"access-key\", \"Your DigitalOcean Spaces account access key\").Action(c.AccessKey.Set).StringVar(&c.AccessKey.Value)\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.AutoClone.Set,\n\t\tDst:    &c.AutoClone.Value,\n\t})\n\tc.CmdClause.Flag(\"bucket\", \"The name of the DigitalOcean Space\").Action(c.BucketName.Set).StringVar(&c.BucketName.Value)\n\tlogflags.CompressionCodec(c.CmdClause, &c.CompressionCodec)\n\tc.CmdClause.Flag(\"domain\", \"The domain of the DigitalOcean Spaces endpoint (default 'nyc3.digitaloceanspaces.com')\").Action(c.Domain.Set).StringVar(&c.Domain.Value)\n\tlogflags.Format(c.CmdClause, &c.Format)\n\tlogflags.FormatVersion(c.CmdClause, &c.FormatVersion)\n\tlogflags.GzipLevel(c.CmdClause, &c.GzipLevel)\n\tlogflags.MessageType(c.CmdClause, &c.MessageType)\n\tc.CmdClause.Flag(\"new-name\", \"New name of the DigitalOcean Spaces logging object\").Action(c.NewName.Set).StringVar(&c.NewName.Value)\n\tlogflags.Path(c.CmdClause, &c.Path)\n\tlogflags.Period(c.CmdClause, &c.Period)\n\tlogflags.Placement(c.CmdClause, &c.Placement)\n\tlogflags.ProcessingRegion(c.CmdClause, &c.ProcessingRegion, \"DigitalOcean Spaces\")\n\tlogflags.PublicKey(c.CmdClause, &c.PublicKey)\n\tlogflags.ResponseCondition(c.CmdClause, &c.ResponseCondition)\n\tc.CmdClause.Flag(\"secret-key\", \"Your DigitalOcean Spaces account secret key\").Action(c.SecretKey.Set).StringVar(&c.SecretKey.Value)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.ServiceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.ServiceName.Value,\n\t})\n\tlogflags.TimestampFormat(c.CmdClause, &c.TimestampFormat)\n\treturn &c\n}\n\n// ConstructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *UpdateCommand) ConstructInput(serviceID string, serviceVersion int) (*fastly.UpdateDigitalOceanInput, error) {\n\tinput := fastly.UpdateDigitalOceanInput{\n\t\tServiceID:      serviceID,\n\t\tServiceVersion: serviceVersion,\n\t\tName:           c.EndpointName,\n\t}\n\n\t// Set new values if set by user.\n\tif c.NewName.WasSet {\n\t\tinput.NewName = &c.NewName.Value\n\t}\n\n\tif c.BucketName.WasSet {\n\t\tinput.BucketName = &c.BucketName.Value\n\t}\n\n\tif c.Domain.WasSet {\n\t\tinput.Domain = &c.Domain.Value\n\t}\n\n\tif c.AccessKey.WasSet {\n\t\tinput.AccessKey = &c.AccessKey.Value\n\t}\n\n\tif c.SecretKey.WasSet {\n\t\tinput.SecretKey = &c.SecretKey.Value\n\t}\n\n\tif c.Path.WasSet {\n\t\tinput.Path = &c.Path.Value\n\t}\n\n\tif c.Period.WasSet {\n\t\tinput.Period = &c.Period.Value\n\t}\n\n\tif c.GzipLevel.WasSet {\n\t\tinput.GzipLevel = &c.GzipLevel.Value\n\t}\n\n\tif c.Format.WasSet {\n\t\tinput.Format = fastly.ToPointer(argparser.Content(c.Format.Value))\n\t}\n\n\tif c.FormatVersion.WasSet {\n\t\tinput.FormatVersion = &c.FormatVersion.Value\n\t}\n\n\tif c.ResponseCondition.WasSet {\n\t\tinput.ResponseCondition = &c.ResponseCondition.Value\n\t}\n\n\tif c.MessageType.WasSet {\n\t\tinput.MessageType = &c.MessageType.Value\n\t}\n\n\tif c.TimestampFormat.WasSet {\n\t\tinput.TimestampFormat = &c.TimestampFormat.Value\n\t}\n\n\tif c.Placement.WasSet {\n\t\tinput.Placement = &c.Placement.Value\n\t}\n\n\tif c.PublicKey.WasSet {\n\t\tinput.PublicKey = &c.PublicKey.Value\n\t}\n\n\tif c.CompressionCodec.WasSet {\n\t\tinput.CompressionCodec = &c.CompressionCodec.Value\n\t}\n\n\tif c.ProcessingRegion.WasSet {\n\t\tinput.ProcessingRegion = &c.ProcessingRegion.Value\n\t}\n\n\treturn &input, nil\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *UpdateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(false),\n\t\tLocked:             optional.Of(false),\n\t\tAutoCloneFlag:      c.AutoClone,\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.ServiceName,\n\t\tServiceVersionFlag: c.ServiceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": errors.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tinput, err := c.ConstructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tdigitalocean, err := c.Globals.APIClient.UpdateDigitalOcean(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\ttext.Success(\n\t\tout,\n\t\t\"Updated DigitalOcean Spaces logging endpoint %s (service %s version %d)\",\n\t\tfastly.ToValue(digitalocean.Name),\n\t\tfastly.ToValue(digitalocean.ServiceID),\n\t\tfastly.ToValue(digitalocean.ServiceVersion),\n\t)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/doc.go",
    "content": "// Package logging contains commands to inspect and manipulate Fastly service\n// logging endpoints.\npackage logging\n"
  },
  {
    "path": "pkg/commands/service/logging/elasticsearch/create.go",
    "content": "package elasticsearch\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/service/logging/logflags\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/manifest\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// CreateCommand calls the Fastly API to create an Elasticsearch logging endpoint.\ntype CreateCommand struct {\n\targparser.Base\n\tManifest manifest.Data\n\n\t// Required.\n\tServiceName    argparser.OptionalServiceNameID\n\tServiceVersion argparser.OptionalServiceVersion\n\n\t// Optional.\n\tAutoClone         argparser.OptionalAutoClone\n\tEndpointName      argparser.OptionalString // Can't shadow argparser.Base method Name().\n\tFormat            argparser.OptionalString\n\tFormatVersion     argparser.OptionalInt\n\tIndex             argparser.OptionalString\n\tPassword          argparser.OptionalString\n\tPipeline          argparser.OptionalString\n\tPlacement         argparser.OptionalString\n\tProcessingRegion  argparser.OptionalString\n\tRequestMaxBytes   argparser.OptionalInt\n\tRequestMaxEntries argparser.OptionalInt\n\tResponseCondition argparser.OptionalString\n\tTLSCACert         argparser.OptionalString\n\tTLSClientCert     argparser.OptionalString\n\tTLSClientKey      argparser.OptionalString\n\tTLSHostname       argparser.OptionalString\n\tURL               argparser.OptionalString\n\tUser              argparser.OptionalString\n}\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"create\", \"Create an Elasticsearch logging endpoint on a Fastly service version\").Alias(\"add\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"The name of the Elasticsearch logging object. Used as a primary key for API access\").Short('n').Action(c.EndpointName.Set).StringVar(&c.EndpointName.Value)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.ServiceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.AutoClone.Set,\n\t\tDst:    &c.AutoClone.Value,\n\t})\n\tlogflags.Format(c.CmdClause, &c.Format)\n\tlogflags.FormatVersion(c.CmdClause, &c.FormatVersion)\n\tc.CmdClause.Flag(\"index\", `The name of the Elasticsearch index to send documents (logs) to. The index must follow the Elasticsearch index format rules (https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-create-index.html). We support strftime (http://man7.org/linux/man-pages/man3/strftime.3.html) interpolated variables inside braces prefixed with a pound symbol. For example, #{%F} will interpolate as YYYY-MM-DD with today's date`).Action(c.Index.Set).StringVar(&c.Index.Value)\n\tlogflags.Placement(c.CmdClause, &c.Placement)\n\tc.CmdClause.Flag(\"pipeline\", \"The ID of the Elasticsearch ingest pipeline to apply pre-process transformations to before indexing. For example my_pipeline_id. Learn more about creating a pipeline in the Elasticsearch docs (https://www.elastic.co/guide/en/elasticsearch/reference/current/ingest.html)\").Action(c.Password.Set).StringVar(&c.Pipeline.Value)\n\tlogflags.ProcessingRegion(c.CmdClause, &c.ProcessingRegion, \"Elasticsearch\")\n\tc.CmdClause.Flag(\"request-max-bytes\", \"Maximum size of log batch, if non-zero. Defaults to 100MB\").Action(c.RequestMaxBytes.Set).IntVar(&c.RequestMaxBytes.Value)\n\tc.CmdClause.Flag(\"request-max-entries\", \"Maximum number of logs to append to a batch, if non-zero. Defaults to 10k\").Action(c.RequestMaxEntries.Set).IntVar(&c.RequestMaxEntries.Value)\n\tlogflags.ResponseCondition(c.CmdClause, &c.ResponseCondition)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.ServiceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.ServiceName.Value,\n\t})\n\tlogflags.TLSCACert(c.CmdClause, &c.TLSCACert)\n\tlogflags.TLSClientCert(c.CmdClause, &c.TLSClientCert)\n\tlogflags.TLSClientKey(c.CmdClause, &c.TLSClientKey)\n\tlogflags.TLSHostname(c.CmdClause, &c.TLSHostname)\n\tc.CmdClause.Flag(\"url\", \"The URL to stream logs to. Must use HTTPS.\").Action(c.URL.Set).StringVar(&c.URL.Value)\n\treturn &c\n}\n\n// ConstructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *CreateCommand) ConstructInput(serviceID string, serviceVersion int) (*fastly.CreateElasticsearchInput, error) {\n\tvar input fastly.CreateElasticsearchInput\n\n\tinput.ServiceID = serviceID\n\tinput.ServiceVersion = serviceVersion\n\tif c.EndpointName.WasSet {\n\t\tinput.Name = &c.EndpointName.Value\n\t}\n\tif c.Index.WasSet {\n\t\tinput.Index = &c.Index.Value\n\t}\n\tif c.URL.WasSet {\n\t\tinput.URL = &c.URL.Value\n\t}\n\n\tif c.Pipeline.WasSet {\n\t\tinput.Pipeline = &c.Pipeline.Value\n\t}\n\n\tif c.RequestMaxEntries.WasSet {\n\t\tinput.RequestMaxEntries = &c.RequestMaxEntries.Value\n\t}\n\n\tif c.RequestMaxBytes.WasSet {\n\t\tinput.RequestMaxBytes = &c.RequestMaxBytes.Value\n\t}\n\n\tif c.User.WasSet {\n\t\tinput.User = &c.User.Value\n\t}\n\n\tif c.Password.WasSet {\n\t\tinput.Password = &c.Password.Value\n\t}\n\n\tif c.TLSCACert.WasSet {\n\t\tinput.TLSCACert = &c.TLSCACert.Value\n\t}\n\n\tif c.TLSClientCert.WasSet {\n\t\tinput.TLSClientCert = &c.TLSClientCert.Value\n\t}\n\n\tif c.TLSClientKey.WasSet {\n\t\tinput.TLSClientKey = &c.TLSClientKey.Value\n\t}\n\n\tif c.TLSHostname.WasSet {\n\t\tinput.TLSHostname = &c.TLSHostname.Value\n\t}\n\n\tif c.Format.WasSet {\n\t\tinput.Format = fastly.ToPointer(argparser.Content(c.Format.Value))\n\t}\n\n\tif c.FormatVersion.WasSet {\n\t\tinput.FormatVersion = &c.FormatVersion.Value\n\t}\n\n\tif c.ResponseCondition.WasSet {\n\t\tinput.ResponseCondition = &c.ResponseCondition.Value\n\t}\n\n\tif c.Placement.WasSet {\n\t\tinput.Placement = &c.Placement.Value\n\t}\n\n\tif c.ProcessingRegion.WasSet {\n\t\tinput.ProcessingRegion = &c.ProcessingRegion.Value\n\t}\n\n\treturn &input, nil\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *CreateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(false),\n\t\tLocked:             optional.Of(false),\n\t\tAutoCloneFlag:      c.AutoClone,\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.ServiceName,\n\t\tServiceVersionFlag: c.ServiceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": errors.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tinput, err := c.ConstructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\td, err := c.Globals.APIClient.CreateElasticsearch(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\ttext.Success(\n\t\tout,\n\t\t\"Created Elasticsearch logging endpoint %s (service %s version %d)\",\n\t\tfastly.ToValue(d.Name),\n\t\tfastly.ToValue(d.ServiceID),\n\t\tfastly.ToValue(d.ServiceVersion),\n\t)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/elasticsearch/delete.go",
    "content": "package elasticsearch\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DeleteCommand calls the Fastly API to delete an Elasticsearch logging endpoint.\ntype DeleteCommand struct {\n\targparser.Base\n\tInput          fastly.DeleteElasticsearchInput\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n\tautoClone      argparser.OptionalAutoClone\n}\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"delete\", \"Delete an Elasticsearch logging endpoint on a Fastly service version\").Alias(\"remove\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"The name of the Elasticsearch logging object\").Short('n').Required().StringVar(&c.Input.Name)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.autoClone.Set,\n\t\tDst:    &c.autoClone.Value,\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DeleteCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(false),\n\t\tLocked:             optional.Of(false),\n\t\tAutoCloneFlag:      c.autoClone,\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": errors.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tc.Input.ServiceID = serviceID\n\tc.Input.ServiceVersion = fastly.ToValue(serviceVersion.Number)\n\n\tif err := c.Globals.APIClient.DeleteElasticsearch(context.TODO(), &c.Input); err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Deleted Elasticsearch logging endpoint %s (service %s version %d)\", c.Input.Name, c.Input.ServiceID, c.Input.ServiceVersion)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/elasticsearch/describe.go",
    "content": "package elasticsearch\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DescribeCommand calls the Fastly API to describe an Elasticsearch logging endpoint.\ntype DescribeCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tInput          fastly.GetElasticsearchInput\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n}\n\n// NewDescribeCommand returns a usable command registered under the parent.\nfunc NewDescribeCommand(parent argparser.Registerer, g *global.Data) *DescribeCommand {\n\tc := DescribeCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"describe\", \"Show detailed information about an Elasticsearch logging endpoint on a Fastly service version\").Alias(\"get\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"The name of the Elasticsearch logging object\").Short('n').Required().StringVar(&c.Input.Name)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DescribeCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fsterr.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tc.Input.ServiceID = serviceID\n\tc.Input.ServiceVersion = fastly.ToValue(serviceVersion.Number)\n\n\to, err := c.Globals.APIClient.GetElasticsearch(context.TODO(), &c.Input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\tlines := text.Lines{\n\t\t\"Format version\":         fastly.ToValue(o.FormatVersion),\n\t\t\"Format\":                 fastly.ToValue(o.Format),\n\t\t\"Index\":                  fastly.ToValue(o.Index),\n\t\t\"Name\":                   fastly.ToValue(o.Name),\n\t\t\"Password\":               fastly.ToValue(o.Password),\n\t\t\"Pipeline\":               fastly.ToValue(o.Pipeline),\n\t\t\"Placement\":              fastly.ToValue(o.Placement),\n\t\t\"Processing region\":      fastly.ToValue(o.ProcessingRegion),\n\t\t\"Response condition\":     fastly.ToValue(o.ResponseCondition),\n\t\t\"TLS CA certificate\":     fastly.ToValue(o.TLSCACert),\n\t\t\"TLS client certificate\": fastly.ToValue(o.TLSClientCert),\n\t\t\"TLS client key\":         fastly.ToValue(o.TLSClientKey),\n\t\t\"TLS hostname\":           fastly.ToValue(o.TLSHostname),\n\t\t\"URL\":                    fastly.ToValue(o.URL),\n\t\t\"User\":                   fastly.ToValue(o.User),\n\t\t\"Version\":                fastly.ToValue(o.ServiceVersion),\n\t}\n\tif !c.Globals.Verbose() {\n\t\tlines[\"Service ID\"] = fastly.ToValue(o.ServiceID)\n\t}\n\ttext.PrintLines(out, lines)\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/elasticsearch/doc.go",
    "content": "// Package elasticsearch contains commands to inspect and manipulate Fastly service Elasticsearch\n// logging endpoints.\npackage elasticsearch\n"
  },
  {
    "path": "pkg/commands/service/logging/elasticsearch/elasticsearch_integration_test.go",
    "content": "package elasticsearch_test\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/mock\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n\n\troot \"github.com/fastly/cli/pkg/commands/service\"\n\tparent \"github.com/fastly/cli/pkg/commands/service/logging\"\n\tsub \"github.com/fastly/cli/pkg/commands/service/logging/elasticsearch\"\n)\n\nfunc TestElasticsearchCreate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name log --index logs --url example.com --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:          testutil.GetVersion,\n\t\t\t\tCloneVersionFn:        testutil.CloneVersionResult(4),\n\t\t\t\tCreateElasticsearchFn: createElasticsearchOK,\n\t\t\t},\n\t\t\tWantOutput: \"Created Elasticsearch logging endpoint log (service 123 version 4)\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name log --index logs --url example.com --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:          testutil.GetVersion,\n\t\t\t\tCloneVersionFn:        testutil.CloneVersionResult(4),\n\t\t\t\tCreateElasticsearchFn: createElasticsearchError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"create\"}, scenarios)\n}\n\nfunc TestElasticsearchList(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:        testutil.GetVersion,\n\t\t\t\tListElasticsearchFn: listElasticsearchsOK,\n\t\t\t},\n\t\t\tWantOutput: listElasticsearchsShortOutput,\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --verbose\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:        testutil.GetVersion,\n\t\t\t\tListElasticsearchFn: listElasticsearchsOK,\n\t\t\t},\n\t\t\tWantOutput: listElasticsearchsVerboseOutput,\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 -v\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:        testutil.GetVersion,\n\t\t\t\tListElasticsearchFn: listElasticsearchsOK,\n\t\t\t},\n\t\t\tWantOutput: listElasticsearchsVerboseOutput,\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:        testutil.GetVersion,\n\t\t\t\tListElasticsearchFn: listElasticsearchsError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"list\"}, scenarios)\n}\n\nfunc TestElasticsearchDescribe(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"--service-id 123 --version 1\",\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:       testutil.GetVersion,\n\t\t\t\tGetElasticsearchFn: getElasticsearchError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:       testutil.GetVersion,\n\t\t\t\tGetElasticsearchFn: getElasticsearchOK,\n\t\t\t},\n\t\t\tWantOutput: describeElasticsearchOutput,\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"describe\"}, scenarios)\n}\n\nfunc TestElasticsearchUpdate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"--service-id 123 --version 1 --new-name log\",\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs --new-name log --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:          testutil.GetVersion,\n\t\t\t\tCloneVersionFn:        testutil.CloneVersionResult(4),\n\t\t\t\tUpdateElasticsearchFn: updateElasticsearchError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs --new-name log --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:          testutil.GetVersion,\n\t\t\t\tCloneVersionFn:        testutil.CloneVersionResult(4),\n\t\t\t\tUpdateElasticsearchFn: updateElasticsearchOK,\n\t\t\t},\n\t\t\tWantOutput: \"Updated Elasticsearch logging endpoint log (service 123 version 4)\",\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"update\"}, scenarios)\n}\n\nfunc TestElasticsearchDelete(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"--service-id 123 --version 1\",\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:          testutil.GetVersion,\n\t\t\t\tCloneVersionFn:        testutil.CloneVersionResult(4),\n\t\t\t\tDeleteElasticsearchFn: deleteElasticsearchError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:          testutil.GetVersion,\n\t\t\t\tCloneVersionFn:        testutil.CloneVersionResult(4),\n\t\t\t\tDeleteElasticsearchFn: deleteElasticsearchOK,\n\t\t\t},\n\t\t\tWantOutput: \"Deleted Elasticsearch logging endpoint logs (service 123 version 4)\",\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"delete\"}, scenarios)\n}\n\nvar errTest = errors.New(\"fixture error\")\n\nfunc createElasticsearchOK(_ context.Context, i *fastly.CreateElasticsearchInput) (*fastly.Elasticsearch, error) {\n\treturn &fastly.Elasticsearch{\n\t\tServiceID:         fastly.ToPointer(i.ServiceID),\n\t\tServiceVersion:    fastly.ToPointer(i.ServiceVersion),\n\t\tName:              fastly.ToPointer(\"log\"),\n\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\tIndex:             fastly.ToPointer(\"logs\"),\n\t\tURL:               fastly.ToPointer(\"example.com\"),\n\t\tPipeline:          fastly.ToPointer(\"logs\"),\n\t\tUser:              fastly.ToPointer(\"user\"),\n\t\tPassword:          fastly.ToPointer(\"password\"),\n\t\tRequestMaxEntries: fastly.ToPointer(2),\n\t\tRequestMaxBytes:   fastly.ToPointer(2),\n\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\tTLSCACert:         fastly.ToPointer(\"-----BEGIN CERTIFICATE-----foo\"),\n\t\tTLSHostname:       fastly.ToPointer(\"example.com\"),\n\t\tTLSClientCert:     fastly.ToPointer(\"-----BEGIN CERTIFICATE-----bar\"),\n\t\tTLSClientKey:      fastly.ToPointer(\"-----BEGIN PRIVATE KEY-----bar\"),\n\t\tFormatVersion:     fastly.ToPointer(2),\n\t}, nil\n}\n\nfunc createElasticsearchError(_ context.Context, _ *fastly.CreateElasticsearchInput) (*fastly.Elasticsearch, error) {\n\treturn nil, errTest\n}\n\nfunc listElasticsearchsOK(_ context.Context, i *fastly.ListElasticsearchInput) ([]*fastly.Elasticsearch, error) {\n\treturn []*fastly.Elasticsearch{\n\t\t{\n\t\t\tServiceID:         fastly.ToPointer(i.ServiceID),\n\t\t\tServiceVersion:    fastly.ToPointer(i.ServiceVersion),\n\t\t\tName:              fastly.ToPointer(\"logs\"),\n\t\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\t\tIndex:             fastly.ToPointer(\"logs\"),\n\t\t\tURL:               fastly.ToPointer(\"example.com\"),\n\t\t\tPipeline:          fastly.ToPointer(\"logs\"),\n\t\t\tUser:              fastly.ToPointer(\"user\"),\n\t\t\tPassword:          fastly.ToPointer(\"password\"),\n\t\t\tRequestMaxEntries: fastly.ToPointer(2),\n\t\t\tRequestMaxBytes:   fastly.ToPointer(2),\n\t\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\t\tTLSCACert:         fastly.ToPointer(\"-----BEGIN CERTIFICATE-----foo\"),\n\t\t\tTLSHostname:       fastly.ToPointer(\"example.com\"),\n\t\t\tTLSClientCert:     fastly.ToPointer(\"-----BEGIN CERTIFICATE-----bar\"),\n\t\t\tTLSClientKey:      fastly.ToPointer(\"-----BEGIN PRIVATE KEY-----bar\"),\n\t\t\tFormatVersion:     fastly.ToPointer(2),\n\t\t\tProcessingRegion:  fastly.ToPointer(\"us\"),\n\t\t},\n\t\t{\n\t\t\tServiceID:         fastly.ToPointer(i.ServiceID),\n\t\t\tServiceVersion:    fastly.ToPointer(i.ServiceVersion),\n\t\t\tName:              fastly.ToPointer(\"analytics\"),\n\t\t\tIndex:             fastly.ToPointer(\"analytics\"),\n\t\t\tURL:               fastly.ToPointer(\"example.com\"),\n\t\t\tPipeline:          fastly.ToPointer(\"analytics\"),\n\t\t\tUser:              fastly.ToPointer(\"user\"),\n\t\t\tPassword:          fastly.ToPointer(\"password\"),\n\t\t\tRequestMaxEntries: fastly.ToPointer(2),\n\t\t\tRequestMaxBytes:   fastly.ToPointer(2),\n\t\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\t\tTLSCACert:         fastly.ToPointer(\"-----BEGIN CERTIFICATE-----foo\"),\n\t\t\tTLSHostname:       fastly.ToPointer(\"example.com\"),\n\t\t\tTLSClientCert:     fastly.ToPointer(\"-----BEGIN CERTIFICATE-----bar\"),\n\t\t\tTLSClientKey:      fastly.ToPointer(\"-----BEGIN PRIVATE KEY-----bar\"),\n\t\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\t\tFormatVersion:     fastly.ToPointer(2),\n\t\t\tProcessingRegion:  fastly.ToPointer(\"us\"),\n\t\t},\n\t}, nil\n}\n\nfunc listElasticsearchsError(_ context.Context, _ *fastly.ListElasticsearchInput) ([]*fastly.Elasticsearch, error) {\n\treturn nil, errTest\n}\n\nvar listElasticsearchsShortOutput = strings.TrimSpace(`\nSERVICE  VERSION  NAME\n123      1        logs\n123      1        analytics\n`) + \"\\n\"\n\nvar listElasticsearchsVerboseOutput = strings.TrimSpace(`\nFastly API endpoint: https://api.fastly.com\nFastly API token provided via config file (auth: user)\n\nService ID (via --service-id): 123\n\nVersion: 1\n\tElasticsearch 1/2\n\t\tService ID: 123\n\t\tVersion: 1\n\t\tName: logs\n\t\tIndex: logs\n\t\tURL: example.com\n\t\tPipeline: logs\n\t\tTLS CA certificate: -----BEGIN CERTIFICATE-----foo\n\t\tTLS client certificate: -----BEGIN CERTIFICATE-----bar\n\t\tTLS client key: -----BEGIN PRIVATE KEY-----bar\n\t\tTLS hostname: example.com\n\t\tUser: user\n\t\tPassword: password\n\t\tFormat: %h %l %u %t \"%r\" %>s %b\n\t\tFormat version: 2\n\t\tResponse condition: Prevent default logging\n\t\tPlacement: none\n\t\tProcessing region: us\n\tElasticsearch 2/2\n\t\tService ID: 123\n\t\tVersion: 1\n\t\tName: analytics\n\t\tIndex: analytics\n\t\tURL: example.com\n\t\tPipeline: analytics\n\t\tTLS CA certificate: -----BEGIN CERTIFICATE-----foo\n\t\tTLS client certificate: -----BEGIN CERTIFICATE-----bar\n\t\tTLS client key: -----BEGIN PRIVATE KEY-----bar\n\t\tTLS hostname: example.com\n\t\tUser: user\n\t\tPassword: password\n\t\tFormat: %h %l %u %t \"%r\" %>s %b\n\t\tFormat version: 2\n\t\tResponse condition: Prevent default logging\n\t\tPlacement: none\n\t\tProcessing region: us\n`) + \"\\n\\n\"\n\nfunc getElasticsearchOK(_ context.Context, i *fastly.GetElasticsearchInput) (*fastly.Elasticsearch, error) {\n\treturn &fastly.Elasticsearch{\n\t\tServiceID:         fastly.ToPointer(i.ServiceID),\n\t\tServiceVersion:    fastly.ToPointer(i.ServiceVersion),\n\t\tName:              fastly.ToPointer(\"logs\"),\n\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\tIndex:             fastly.ToPointer(\"logs\"),\n\t\tURL:               fastly.ToPointer(\"example.com\"),\n\t\tPipeline:          fastly.ToPointer(\"logs\"),\n\t\tUser:              fastly.ToPointer(\"user\"),\n\t\tPassword:          fastly.ToPointer(\"password\"),\n\t\tRequestMaxEntries: fastly.ToPointer(2),\n\t\tRequestMaxBytes:   fastly.ToPointer(2),\n\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\tTLSCACert:         fastly.ToPointer(\"-----BEGIN CERTIFICATE-----foo\"),\n\t\tTLSHostname:       fastly.ToPointer(\"example.com\"),\n\t\tTLSClientCert:     fastly.ToPointer(\"-----BEGIN CERTIFICATE-----bar\"),\n\t\tTLSClientKey:      fastly.ToPointer(\"-----BEGIN PRIVATE KEY-----bar\"),\n\t\tFormatVersion:     fastly.ToPointer(2),\n\t\tProcessingRegion:  fastly.ToPointer(\"us\"),\n\t}, nil\n}\n\nfunc getElasticsearchError(_ context.Context, _ *fastly.GetElasticsearchInput) (*fastly.Elasticsearch, error) {\n\treturn nil, errTest\n}\n\nvar describeElasticsearchOutput = \"\\n\" + strings.TrimSpace(`\nFormat: %h %l %u %t \"%r\" %>s %b\nFormat version: 2\nIndex: logs\nName: logs\nPassword: password\nPipeline: logs\nPlacement: none\nProcessing region: us\nResponse condition: Prevent default logging\nService ID: 123\nTLS CA certificate: -----BEGIN CERTIFICATE-----foo\nTLS client certificate: -----BEGIN CERTIFICATE-----bar\nTLS client key: -----BEGIN PRIVATE KEY-----bar\nTLS hostname: example.com\nURL: example.com\nUser: user\nVersion: 1\n`) + \"\\n\"\n\nfunc updateElasticsearchOK(_ context.Context, i *fastly.UpdateElasticsearchInput) (*fastly.Elasticsearch, error) {\n\treturn &fastly.Elasticsearch{\n\t\tServiceID:         fastly.ToPointer(i.ServiceID),\n\t\tServiceVersion:    fastly.ToPointer(i.ServiceVersion),\n\t\tName:              fastly.ToPointer(\"log\"),\n\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\tIndex:             fastly.ToPointer(\"logs\"),\n\t\tURL:               fastly.ToPointer(\"example.com\"),\n\t\tPipeline:          fastly.ToPointer(\"logs\"),\n\t\tUser:              fastly.ToPointer(\"user\"),\n\t\tPassword:          fastly.ToPointer(\"password\"),\n\t\tRequestMaxEntries: fastly.ToPointer(2),\n\t\tRequestMaxBytes:   fastly.ToPointer(2),\n\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\tTLSCACert:         fastly.ToPointer(\"-----BEGIN CERTIFICATE-----foo\"),\n\t\tTLSHostname:       fastly.ToPointer(\"example.com\"),\n\t\tTLSClientCert:     fastly.ToPointer(\"-----BEGIN CERTIFICATE-----bar\"),\n\t\tTLSClientKey:      fastly.ToPointer(\"-----BEGIN PRIVATE KEY-----bar\"),\n\t\tFormatVersion:     fastly.ToPointer(2),\n\t}, nil\n}\n\nfunc updateElasticsearchError(_ context.Context, _ *fastly.UpdateElasticsearchInput) (*fastly.Elasticsearch, error) {\n\treturn nil, errTest\n}\n\nfunc deleteElasticsearchOK(_ context.Context, _ *fastly.DeleteElasticsearchInput) error {\n\treturn nil\n}\n\nfunc deleteElasticsearchError(_ context.Context, _ *fastly.DeleteElasticsearchInput) error {\n\treturn errTest\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/elasticsearch/elasticsearch_test.go",
    "content": "package elasticsearch_test\n\nimport (\n\t\"bytes\"\n\t\"testing\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/service/logging/elasticsearch\"\n\t\"github.com/fastly/cli/pkg/config\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/manifest\"\n\t\"github.com/fastly/cli/pkg/mock\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n)\n\nfunc TestCreateElasticsearchInput(t *testing.T) {\n\tfor _, testcase := range []struct {\n\t\tname      string\n\t\tcmd       *elasticsearch.CreateCommand\n\t\twant      *fastly.CreateElasticsearchInput\n\t\twantError string\n\t}{\n\t\t{\n\t\t\tname: \"required values set flag serviceID\",\n\t\t\tcmd:  createCommandRequired(),\n\t\t\twant: &fastly.CreateElasticsearchInput{\n\t\t\t\tServiceID:      \"123\",\n\t\t\t\tServiceVersion: 4,\n\t\t\t\tName:           fastly.ToPointer(\"log\"),\n\t\t\t\tIndex:          fastly.ToPointer(\"logs\"),\n\t\t\t\tURL:            fastly.ToPointer(\"example.com\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"all values set flag serviceID\",\n\t\t\tcmd:  createCommandAll(),\n\t\t\twant: &fastly.CreateElasticsearchInput{\n\t\t\t\tServiceID:         \"123\",\n\t\t\t\tServiceVersion:    4,\n\t\t\t\tName:              fastly.ToPointer(\"logs\"),\n\t\t\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\t\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\t\t\tIndex:             fastly.ToPointer(\"logs\"),\n\t\t\t\tURL:               fastly.ToPointer(\"example.com\"),\n\t\t\t\tPipeline:          fastly.ToPointer(\"my_pipeline_id\"),\n\t\t\t\tUser:              fastly.ToPointer(\"user\"),\n\t\t\t\tPassword:          fastly.ToPointer(\"password\"),\n\t\t\t\tProcessingRegion:  fastly.ToPointer(\"eu\"),\n\t\t\t\tRequestMaxEntries: fastly.ToPointer(2),\n\t\t\t\tRequestMaxBytes:   fastly.ToPointer(2),\n\t\t\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\t\t\tTLSCACert:         fastly.ToPointer(\"-----BEGIN CERTIFICATE-----foo\"),\n\t\t\t\tTLSHostname:       fastly.ToPointer(\"example.com\"),\n\t\t\t\tTLSClientCert:     fastly.ToPointer(\"-----BEGIN CERTIFICATE-----bar\"),\n\t\t\t\tTLSClientKey:      fastly.ToPointer(\"-----BEGIN PRIVATE KEY-----bar\"),\n\t\t\t\tFormatVersion:     fastly.ToPointer(2),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:      \"error missing serviceID\",\n\t\t\tcmd:       createCommandMissingServiceID(),\n\t\t\twant:      nil,\n\t\t\twantError: errors.ErrNoServiceID.Error(),\n\t\t},\n\t} {\n\t\tt.Run(testcase.name, func(t *testing.T) {\n\t\t\tif testcase.wantError == errors.ErrNoServiceID.Error() {\n\t\t\t\tt.Setenv(\"FASTLY_SERVICE_ID\", \"\")\n\t\t\t}\n\t\t\tvar bs []byte\n\t\t\tout := bytes.NewBuffer(bs)\n\t\t\tverboseMode := true\n\n\t\t\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\t\t\tAutoCloneFlag:      testcase.cmd.AutoClone,\n\t\t\t\tAPIClient:          testcase.cmd.Globals.APIClient,\n\t\t\t\tManifest:           testcase.cmd.Manifest,\n\t\t\t\tOut:                out,\n\t\t\t\tServiceVersionFlag: testcase.cmd.ServiceVersion,\n\t\t\t\tVerboseMode:        verboseMode,\n\t\t\t})\n\n\t\t\tswitch {\n\t\t\tcase err != nil && testcase.wantError == \"\":\n\t\t\t\tt.Fatalf(\"unexpected error getting service details: %v\", err)\n\t\t\t\treturn\n\t\t\tcase err != nil && testcase.wantError != \"\":\n\t\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\t\treturn\n\t\t\tcase err == nil && testcase.wantError != \"\":\n\t\t\t\tt.Fatalf(\"expected error, have nil (service details: %s, %d)\", serviceID, serviceVersion.Number)\n\t\t\tcase err == nil && testcase.wantError == \"\":\n\t\t\t\thave, err := testcase.cmd.ConstructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\t\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\t\ttestutil.AssertEqual(t, testcase.want, have)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestUpdateElasticsearchInput(t *testing.T) {\n\tscenarios := []struct {\n\t\tname      string\n\t\tcmd       *elasticsearch.UpdateCommand\n\t\tapi       mock.API\n\t\twant      *fastly.UpdateElasticsearchInput\n\t\twantError string\n\t}{\n\t\t{\n\t\t\tname: \"all values set flag serviceID\",\n\t\t\tcmd:  updateCommandAll(),\n\t\t\tapi: mock.API{\n\t\t\t\tGetVersionFn:       testutil.GetVersion,\n\t\t\t\tCloneVersionFn:     testutil.CloneVersionResult(4),\n\t\t\t\tGetElasticsearchFn: getElasticsearchOK,\n\t\t\t},\n\t\t\twant: &fastly.UpdateElasticsearchInput{\n\t\t\t\tServiceID:         \"123\",\n\t\t\t\tServiceVersion:    4,\n\t\t\t\tName:              \"log\",\n\t\t\t\tNewName:           fastly.ToPointer(\"new1\"),\n\t\t\t\tIndex:             fastly.ToPointer(\"new2\"),\n\t\t\t\tURL:               fastly.ToPointer(\"new3\"),\n\t\t\t\tPipeline:          fastly.ToPointer(\"new4\"),\n\t\t\t\tUser:              fastly.ToPointer(\"new5\"),\n\t\t\t\tPassword:          fastly.ToPointer(\"new6\"),\n\t\t\t\tRequestMaxEntries: fastly.ToPointer(3),\n\t\t\t\tRequestMaxBytes:   fastly.ToPointer(3),\n\t\t\t\tPlacement:         fastly.ToPointer(\"new7\"),\n\t\t\t\tFormat:            fastly.ToPointer(\"new8\"),\n\t\t\t\tFormatVersion:     fastly.ToPointer(3),\n\t\t\t\tProcessingRegion:  fastly.ToPointer(\"eu\"),\n\t\t\t\tResponseCondition: fastly.ToPointer(\"new9\"),\n\t\t\t\tTLSCACert:         fastly.ToPointer(\"new10\"),\n\t\t\t\tTLSClientCert:     fastly.ToPointer(\"new11\"),\n\t\t\t\tTLSClientKey:      fastly.ToPointer(\"new12\"),\n\t\t\t\tTLSHostname:       fastly.ToPointer(\"new13\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"no updates\",\n\t\t\tcmd:  updateCommandNoUpdates(),\n\t\t\tapi: mock.API{\n\t\t\t\tGetVersionFn:       testutil.GetVersion,\n\t\t\t\tCloneVersionFn:     testutil.CloneVersionResult(4),\n\t\t\t\tGetElasticsearchFn: getElasticsearchOK,\n\t\t\t},\n\t\t\twant: &fastly.UpdateElasticsearchInput{\n\t\t\t\tServiceID:      \"123\",\n\t\t\t\tServiceVersion: 4,\n\t\t\t\tName:           \"log\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:      \"error missing serviceID\",\n\t\t\tcmd:       updateCommandMissingServiceID(),\n\t\t\twant:      nil,\n\t\t\twantError: errors.ErrNoServiceID.Error(),\n\t\t},\n\t}\n\tfor testcaseIdx := range scenarios {\n\t\ttestcase := &scenarios[testcaseIdx]\n\t\tt.Run(testcase.name, func(t *testing.T) {\n\t\t\tif testcase.wantError == errors.ErrNoServiceID.Error() {\n\t\t\t\tt.Setenv(\"FASTLY_SERVICE_ID\", \"\")\n\t\t\t}\n\t\t\ttestcase.cmd.Globals.APIClient = testcase.api\n\n\t\t\tvar bs []byte\n\t\t\tout := bytes.NewBuffer(bs)\n\t\t\tverboseMode := true\n\n\t\t\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\t\t\tAutoCloneFlag:      testcase.cmd.AutoClone,\n\t\t\t\tAPIClient:          testcase.api,\n\t\t\t\tManifest:           testcase.cmd.Manifest,\n\t\t\t\tOut:                out,\n\t\t\t\tServiceVersionFlag: testcase.cmd.ServiceVersion,\n\t\t\t\tVerboseMode:        verboseMode,\n\t\t\t})\n\n\t\t\tswitch {\n\t\t\tcase err != nil && testcase.wantError == \"\":\n\t\t\t\tt.Fatalf(\"unexpected error getting service details: %v\", err)\n\t\t\t\treturn\n\t\t\tcase err != nil && testcase.wantError != \"\":\n\t\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\t\treturn\n\t\t\tcase err == nil && testcase.wantError != \"\":\n\t\t\t\tt.Fatalf(\"expected error, have nil (service details: %s, %d)\", serviceID, serviceVersion.Number)\n\t\t\tcase err == nil && testcase.wantError == \"\":\n\t\t\t\thave, err := testcase.cmd.ConstructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\t\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\t\ttestutil.AssertEqual(t, testcase.want, have)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc createCommandRequired() *elasticsearch.CreateCommand {\n\tvar b bytes.Buffer\n\n\tglobals := global.Data{\n\t\tConfig: config.File{},\n\t\tEnv:    config.Environment{},\n\t\tOutput: &b,\n\t}\n\tglobals.APIClient, _ = mock.APIClient(mock.API{\n\t\tGetVersionFn:   testutil.GetVersion,\n\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t})(\"token\", \"endpoint\", false)\n\n\treturn &elasticsearch.CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: &globals,\n\t\t},\n\t\tManifest: manifest.Data{\n\t\t\tFlag: manifest.Flag{\n\t\t\t\tServiceID: \"123\",\n\t\t\t},\n\t\t},\n\t\tServiceVersion: argparser.OptionalServiceVersion{\n\t\t\tOptionalString: argparser.OptionalString{Value: \"1\"},\n\t\t},\n\t\tAutoClone: argparser.OptionalAutoClone{\n\t\t\tOptionalBool: argparser.OptionalBool{\n\t\t\t\tOptional: argparser.Optional{\n\t\t\t\t\tWasSet: true,\n\t\t\t\t},\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t},\n\t\tEndpointName: argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"log\"},\n\t\tIndex:        argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"logs\"},\n\t\tURL:          argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"example.com\"},\n\t}\n}\n\nfunc createCommandAll() *elasticsearch.CreateCommand {\n\tvar b bytes.Buffer\n\n\tg := global.Data{\n\t\tConfig: config.File{},\n\t\tEnv:    config.Environment{},\n\t\tOutput: &b,\n\t}\n\tg.APIClient, _ = mock.APIClient(mock.API{\n\t\tGetVersionFn:   testutil.GetVersion,\n\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t})(\"token\", \"endpoint\", false)\n\n\treturn &elasticsearch.CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: &g,\n\t\t},\n\t\tManifest: manifest.Data{\n\t\t\tFlag: manifest.Flag{\n\t\t\t\tServiceID: \"123\",\n\t\t\t},\n\t\t},\n\t\tServiceVersion: argparser.OptionalServiceVersion{\n\t\t\tOptionalString: argparser.OptionalString{Value: \"1\"},\n\t\t},\n\t\tAutoClone: argparser.OptionalAutoClone{\n\t\t\tOptionalBool: argparser.OptionalBool{\n\t\t\t\tOptional: argparser.Optional{\n\t\t\t\t\tWasSet: true,\n\t\t\t\t},\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t},\n\t\tEndpointName:      argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"logs\"},\n\t\tIndex:             argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"logs\"},\n\t\tURL:               argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"example.com\"},\n\t\tPipeline:          argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"my_pipeline_id\"},\n\t\tProcessingRegion:  argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"eu\"},\n\t\tRequestMaxEntries: argparser.OptionalInt{Optional: argparser.Optional{WasSet: true}, Value: 2},\n\t\tRequestMaxBytes:   argparser.OptionalInt{Optional: argparser.Optional{WasSet: true}, Value: 2},\n\t\tFormat:            argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: `%h %l %u %t \"%r\" %>s %b`},\n\t\tFormatVersion:     argparser.OptionalInt{Optional: argparser.Optional{WasSet: true}, Value: 2},\n\t\tResponseCondition: argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"Prevent default logging\"},\n\t\tPlacement:         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"none\"},\n\t\tUser:              argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"user\"},\n\t\tPassword:          argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"password\"},\n\t\tTLSCACert:         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"-----BEGIN CERTIFICATE-----foo\"},\n\t\tTLSHostname:       argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"example.com\"},\n\t\tTLSClientCert:     argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"-----BEGIN CERTIFICATE-----bar\"},\n\t\tTLSClientKey:      argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"-----BEGIN PRIVATE KEY-----bar\"},\n\t}\n}\n\nfunc createCommandMissingServiceID() *elasticsearch.CreateCommand {\n\tres := createCommandAll()\n\tres.Manifest = manifest.Data{}\n\tres.ServiceVersion = argparser.OptionalServiceVersion{}\n\treturn res\n}\n\nfunc updateCommandNoUpdates() *elasticsearch.UpdateCommand {\n\tvar b bytes.Buffer\n\n\tg := global.Data{\n\t\tConfig: config.File{},\n\t\tEnv:    config.Environment{},\n\t\tOutput: &b,\n\t}\n\n\treturn &elasticsearch.UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: &g,\n\t\t},\n\t\tManifest: manifest.Data{\n\t\t\tFlag: manifest.Flag{\n\t\t\t\tServiceID: \"123\",\n\t\t\t},\n\t\t},\n\t\tEndpointName: \"log\",\n\t\tServiceVersion: argparser.OptionalServiceVersion{\n\t\t\tOptionalString: argparser.OptionalString{Value: \"1\"},\n\t\t},\n\t\tAutoClone: argparser.OptionalAutoClone{\n\t\t\tOptionalBool: argparser.OptionalBool{\n\t\t\t\tOptional: argparser.Optional{\n\t\t\t\t\tWasSet: true,\n\t\t\t\t},\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t},\n\t}\n}\n\nfunc updateCommandAll() *elasticsearch.UpdateCommand {\n\tvar b bytes.Buffer\n\n\tg := global.Data{\n\t\tConfig: config.File{},\n\t\tEnv:    config.Environment{},\n\t\tOutput: &b,\n\t}\n\n\treturn &elasticsearch.UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: &g,\n\t\t},\n\t\tManifest: manifest.Data{\n\t\t\tFlag: manifest.Flag{\n\t\t\t\tServiceID: \"123\",\n\t\t\t},\n\t\t},\n\t\tEndpointName: \"log\",\n\t\tServiceVersion: argparser.OptionalServiceVersion{\n\t\t\tOptionalString: argparser.OptionalString{Value: \"1\"},\n\t\t},\n\t\tAutoClone: argparser.OptionalAutoClone{\n\t\t\tOptionalBool: argparser.OptionalBool{\n\t\t\t\tOptional: argparser.Optional{\n\t\t\t\t\tWasSet: true,\n\t\t\t\t},\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t},\n\t\tNewName:           argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new1\"},\n\t\tIndex:             argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new2\"},\n\t\tURL:               argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new3\"},\n\t\tPipeline:          argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new4\"},\n\t\tRequestMaxEntries: argparser.OptionalInt{Optional: argparser.Optional{WasSet: true}, Value: 3},\n\t\tRequestMaxBytes:   argparser.OptionalInt{Optional: argparser.Optional{WasSet: true}, Value: 3},\n\t\tUser:              argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new5\"},\n\t\tPassword:          argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new6\"},\n\t\tPlacement:         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new7\"},\n\t\tProcessingRegion:  argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"eu\"},\n\t\tFormat:            argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new8\"},\n\t\tFormatVersion:     argparser.OptionalInt{Optional: argparser.Optional{WasSet: true}, Value: 3},\n\t\tResponseCondition: argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new9\"},\n\t\tTLSCACert:         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new10\"},\n\t\tTLSClientCert:     argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new11\"},\n\t\tTLSClientKey:      argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new12\"},\n\t\tTLSHostname:       argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new13\"},\n\t}\n}\n\nfunc updateCommandMissingServiceID() *elasticsearch.UpdateCommand {\n\tres := updateCommandAll()\n\tres.Manifest = manifest.Data{}\n\tres.ServiceVersion = argparser.OptionalServiceVersion{}\n\treturn res\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/elasticsearch/list.go",
    "content": "package elasticsearch\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// ListCommand calls the Fastly API to list Elasticsearch logging endpoints.\ntype ListCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tInput          fastly.ListElasticsearchInput\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n}\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"list\", \"List Elasticsearch endpoints on a Fastly service version\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *ListCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fsterr.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tc.Input.ServiceID = serviceID\n\tc.Input.ServiceVersion = fastly.ToValue(serviceVersion.Number)\n\n\to, err := c.Globals.APIClient.ListElasticsearch(context.TODO(), &c.Input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\tif !c.Globals.Verbose() {\n\t\ttw := text.NewTable(out)\n\t\ttw.AddHeader(\"SERVICE\", \"VERSION\", \"NAME\")\n\t\tfor _, elasticsearch := range o {\n\t\t\ttw.AddLine(\n\t\t\t\tfastly.ToValue(elasticsearch.ServiceID),\n\t\t\t\tfastly.ToValue(elasticsearch.ServiceVersion),\n\t\t\t\tfastly.ToValue(elasticsearch.Name),\n\t\t\t)\n\t\t}\n\t\ttw.Print()\n\t\treturn nil\n\t}\n\n\tfmt.Fprintf(out, \"Version: %d\\n\", c.Input.ServiceVersion)\n\tfor i, elasticsearch := range o {\n\t\tfmt.Fprintf(out, \"\\tElasticsearch %d/%d\\n\", i+1, len(o))\n\t\tfmt.Fprintf(out, \"\\t\\tService ID: %s\\n\", fastly.ToValue(elasticsearch.ServiceID))\n\t\tfmt.Fprintf(out, \"\\t\\tVersion: %d\\n\", fastly.ToValue(elasticsearch.ServiceVersion))\n\t\tfmt.Fprintf(out, \"\\t\\tName: %s\\n\", fastly.ToValue(elasticsearch.Name))\n\t\tfmt.Fprintf(out, \"\\t\\tIndex: %s\\n\", fastly.ToValue(elasticsearch.Index))\n\t\tfmt.Fprintf(out, \"\\t\\tURL: %s\\n\", fastly.ToValue(elasticsearch.URL))\n\t\tfmt.Fprintf(out, \"\\t\\tPipeline: %s\\n\", fastly.ToValue(elasticsearch.Pipeline))\n\t\tfmt.Fprintf(out, \"\\t\\tTLS CA certificate: %s\\n\", fastly.ToValue(elasticsearch.TLSCACert))\n\t\tfmt.Fprintf(out, \"\\t\\tTLS client certificate: %s\\n\", fastly.ToValue(elasticsearch.TLSClientCert))\n\t\tfmt.Fprintf(out, \"\\t\\tTLS client key: %s\\n\", fastly.ToValue(elasticsearch.TLSClientKey))\n\t\tfmt.Fprintf(out, \"\\t\\tTLS hostname: %s\\n\", fastly.ToValue(elasticsearch.TLSHostname))\n\t\tfmt.Fprintf(out, \"\\t\\tUser: %s\\n\", fastly.ToValue(elasticsearch.User))\n\t\tfmt.Fprintf(out, \"\\t\\tPassword: %s\\n\", fastly.ToValue(elasticsearch.Password))\n\t\tfmt.Fprintf(out, \"\\t\\tFormat: %s\\n\", fastly.ToValue(elasticsearch.Format))\n\t\tfmt.Fprintf(out, \"\\t\\tFormat version: %d\\n\", fastly.ToValue(elasticsearch.FormatVersion))\n\t\tfmt.Fprintf(out, \"\\t\\tResponse condition: %s\\n\", fastly.ToValue(elasticsearch.ResponseCondition))\n\t\tfmt.Fprintf(out, \"\\t\\tPlacement: %s\\n\", fastly.ToValue(elasticsearch.Placement))\n\t\tfmt.Fprintf(out, \"\\t\\tProcessing region: %s\\n\", fastly.ToValue(elasticsearch.ProcessingRegion))\n\t}\n\tfmt.Fprintln(out)\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/elasticsearch/root.go",
    "content": "package elasticsearch\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"elasticsearch\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manipulate Fastly service version Elasticsearch logging endpoints\")\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/elasticsearch/update.go",
    "content": "package elasticsearch\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/service/logging/logflags\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/manifest\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// UpdateCommand calls the Fastly API to update an Elasticsearch logging endpoint.\ntype UpdateCommand struct {\n\targparser.Base\n\tManifest manifest.Data\n\n\t// Required.\n\tEndpointName   string // Can't shadow argparser.Base method Name().\n\tServiceName    argparser.OptionalServiceNameID\n\tServiceVersion argparser.OptionalServiceVersion\n\n\t// Optional.\n\tAutoClone         argparser.OptionalAutoClone\n\tFormat            argparser.OptionalString\n\tFormatVersion     argparser.OptionalInt\n\tIndex             argparser.OptionalString\n\tNewName           argparser.OptionalString\n\tPassword          argparser.OptionalString\n\tPipeline          argparser.OptionalString\n\tPlacement         argparser.OptionalString\n\tProcessingRegion  argparser.OptionalString\n\tRequestMaxBytes   argparser.OptionalInt\n\tRequestMaxEntries argparser.OptionalInt\n\tResponseCondition argparser.OptionalString\n\tTLSCACert         argparser.OptionalString\n\tTLSClientCert     argparser.OptionalString\n\tTLSClientKey      argparser.OptionalString\n\tTLSHostname       argparser.OptionalString\n\tURL               argparser.OptionalString\n\tUser              argparser.OptionalString\n}\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"update\", \"Update an Elasticsearch logging endpoint on a Fastly service version\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"The name of the Elasticsearch logging object\").Short('n').Required().StringVar(&c.EndpointName)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.ServiceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.AutoClone.Set,\n\t\tDst:    &c.AutoClone.Value,\n\t})\n\tlogflags.Format(c.CmdClause, &c.Format)\n\tlogflags.FormatVersion(c.CmdClause, &c.FormatVersion)\n\tc.CmdClause.Flag(\"index\", `The name of the Elasticsearch index to send documents (logs) to. The index must follow the Elasticsearch index format rules (https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-create-index.html). We support strftime (http://man7.org/linux/man-pages/man3/strftime.3.html) interpolated variables inside braces prefixed with a pound symbol. For example, #{%F} will interpolate as YYYY-MM-DD with today's date`).Action(c.Index.Set).StringVar(&c.Index.Value)\n\tc.CmdClause.Flag(\"new-name\", \"New name of the Elasticsearch logging object\").Action(c.NewName.Set).StringVar(&c.NewName.Value)\n\tc.CmdClause.Flag(\"pipeline\", \"The ID of the Elasticsearch ingest pipeline to apply pre-process transformations to before indexing. For example my_pipeline_id. Learn more about creating a pipeline in the Elasticsearch docs (https://www.elastic.co/guide/en/elasticsearch/reference/current/ingest.html)\").Action(c.Password.Set).StringVar(&c.Pipeline.Value)\n\tlogflags.Placement(c.CmdClause, &c.Placement)\n\tlogflags.ProcessingRegion(c.CmdClause, &c.ProcessingRegion, \"Elasticsearch\")\n\tc.CmdClause.Flag(\"request-max-bytes\", \"Maximum size of log batch, if non-zero. Defaults to 100MB\").Action(c.RequestMaxBytes.Set).IntVar(&c.RequestMaxBytes.Value)\n\tc.CmdClause.Flag(\"request-max-entries\", \"Maximum number of logs to append to a batch, if non-zero. Defaults to 10k\").Action(c.RequestMaxEntries.Set).IntVar(&c.RequestMaxEntries.Value)\n\tlogflags.ResponseCondition(c.CmdClause, &c.ResponseCondition)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.ServiceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.ServiceName.Value,\n\t})\n\tlogflags.TLSCACert(c.CmdClause, &c.TLSCACert)\n\tlogflags.TLSClientCert(c.CmdClause, &c.TLSClientCert)\n\tlogflags.TLSClientKey(c.CmdClause, &c.TLSClientKey)\n\tlogflags.TLSHostname(c.CmdClause, &c.TLSHostname)\n\tc.CmdClause.Flag(\"url\", \"The URL to stream logs to. Must use HTTPS.\").Action(c.URL.Set).StringVar(&c.URL.Value)\n\treturn &c\n}\n\n// ConstructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *UpdateCommand) ConstructInput(serviceID string, serviceVersion int) (*fastly.UpdateElasticsearchInput, error) {\n\tinput := fastly.UpdateElasticsearchInput{\n\t\tServiceID:      serviceID,\n\t\tServiceVersion: serviceVersion,\n\t\tName:           c.EndpointName,\n\t}\n\n\tif c.NewName.WasSet {\n\t\tinput.NewName = &c.NewName.Value\n\t}\n\n\tif c.Index.WasSet {\n\t\tinput.Index = &c.Index.Value\n\t}\n\n\tif c.URL.WasSet {\n\t\tinput.URL = &c.URL.Value\n\t}\n\n\tif c.Pipeline.WasSet {\n\t\tinput.Pipeline = &c.Pipeline.Value\n\t}\n\n\tif c.RequestMaxEntries.WasSet {\n\t\tinput.RequestMaxEntries = &c.RequestMaxEntries.Value\n\t}\n\n\tif c.RequestMaxBytes.WasSet {\n\t\tinput.RequestMaxBytes = &c.RequestMaxBytes.Value\n\t}\n\n\tif c.User.WasSet {\n\t\tinput.User = &c.User.Value\n\t}\n\n\tif c.Password.WasSet {\n\t\tinput.Password = &c.Password.Value\n\t}\n\n\tif c.TLSCACert.WasSet {\n\t\tinput.TLSCACert = &c.TLSCACert.Value\n\t}\n\n\tif c.TLSClientCert.WasSet {\n\t\tinput.TLSClientCert = &c.TLSClientCert.Value\n\t}\n\n\tif c.TLSClientKey.WasSet {\n\t\tinput.TLSClientKey = &c.TLSClientKey.Value\n\t}\n\n\tif c.TLSHostname.WasSet {\n\t\tinput.TLSHostname = &c.TLSHostname.Value\n\t}\n\n\tif c.Format.WasSet {\n\t\tinput.Format = fastly.ToPointer(argparser.Content(c.Format.Value))\n\t}\n\n\tif c.FormatVersion.WasSet {\n\t\tinput.FormatVersion = &c.FormatVersion.Value\n\t}\n\n\tif c.ResponseCondition.WasSet {\n\t\tinput.ResponseCondition = &c.ResponseCondition.Value\n\t}\n\n\tif c.Placement.WasSet {\n\t\tinput.Placement = &c.Placement.Value\n\t}\n\n\tif c.ProcessingRegion.WasSet {\n\t\tinput.ProcessingRegion = &c.ProcessingRegion.Value\n\t}\n\n\treturn &input, nil\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *UpdateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(false),\n\t\tLocked:             optional.Of(false),\n\t\tAutoCloneFlag:      c.AutoClone,\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.ServiceName,\n\t\tServiceVersionFlag: c.ServiceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": errors.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tinput, err := c.ConstructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\telasticsearch, err := c.Globals.APIClient.UpdateElasticsearch(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\ttext.Success(\n\t\tout,\n\t\t\"Updated Elasticsearch logging endpoint %s (service %s version %d)\",\n\t\tfastly.ToValue(elasticsearch.Name),\n\t\tfastly.ToValue(elasticsearch.ServiceID),\n\t\tfastly.ToValue(elasticsearch.ServiceVersion),\n\t)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/ftp/create.go",
    "content": "package ftp\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/service/logging/logflags\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/manifest\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// CreateCommand calls the Fastly API to create an FTP logging endpoint.\ntype CreateCommand struct {\n\targparser.Base\n\tManifest manifest.Data\n\n\t// Required.\n\tServiceName    argparser.OptionalServiceNameID\n\tServiceVersion argparser.OptionalServiceVersion\n\n\t// Optional.\n\tAddress           argparser.OptionalString\n\tAutoClone         argparser.OptionalAutoClone\n\tCompressionCodec  argparser.OptionalString\n\tEndpointName      argparser.OptionalString // Can't shadow argparser.Base method Name().\n\tFormat            argparser.OptionalString\n\tFormatVersion     argparser.OptionalInt\n\tGzipLevel         argparser.OptionalInt\n\tPassword          argparser.OptionalString\n\tPath              argparser.OptionalString\n\tPeriod            argparser.OptionalInt\n\tPlacement         argparser.OptionalString\n\tPort              argparser.OptionalInt\n\tProcessingRegion  argparser.OptionalString\n\tResponseCondition argparser.OptionalString\n\tTimestampFormat   argparser.OptionalString\n\tUsername          argparser.OptionalString\n}\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"create\", \"Create an FTP logging endpoint on a Fastly service version\").Alias(\"add\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.ServiceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.AutoClone.Set,\n\t\tDst:    &c.AutoClone.Value,\n\t})\n\tc.CmdClause.Flag(\"address\", \"An hostname or IPv4 address\").Action(c.Address.Set).StringVar(&c.Address.Value)\n\tlogflags.CompressionCodec(c.CmdClause, &c.CompressionCodec)\n\tc.CmdClause.Flag(\"name\", \"The name of the FTP logging object. Used as a primary key for API access\").Short('n').Action(c.EndpointName.Set).StringVar(&c.EndpointName.Value)\n\tlogflags.Format(c.CmdClause, &c.Format)\n\tlogflags.FormatVersion(c.CmdClause, &c.FormatVersion)\n\tlogflags.GzipLevel(c.CmdClause, &c.GzipLevel)\n\tc.CmdClause.Flag(\"password\", \"The password for the server (for anonymous use an email address)\").Action(c.Password.Set).StringVar(&c.Password.Value)\n\tc.CmdClause.Flag(\"path\", \"The path to upload log files to. If the path ends in / then it is treated as a directory\").Action(c.Path.Set).StringVar(&c.Path.Value)\n\tlogflags.Period(c.CmdClause, &c.Period)\n\tlogflags.Placement(c.CmdClause, &c.Placement)\n\tc.CmdClause.Flag(\"port\", \"The port number\").Action(c.Port.Set).IntVar(&c.Port.Value)\n\tlogflags.ProcessingRegion(c.CmdClause, &c.ProcessingRegion, \"FTP\")\n\tlogflags.ResponseCondition(c.CmdClause, &c.ResponseCondition)\n\tlogflags.TimestampFormat(c.CmdClause, &c.TimestampFormat)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.ServiceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.ServiceName.Value,\n\t})\n\tc.CmdClause.Flag(\"user\", \"The username for the server (can be anonymous)\").Action(c.Username.Set).StringVar(&c.Username.Value)\n\treturn &c\n}\n\n// ConstructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *CreateCommand) ConstructInput(serviceID string, serviceVersion int) (*fastly.CreateFTPInput, error) {\n\tvar input fastly.CreateFTPInput\n\n\tinput.ServiceID = serviceID\n\tinput.ServiceVersion = serviceVersion\n\tif c.EndpointName.WasSet {\n\t\tinput.Name = &c.EndpointName.Value\n\t}\n\tif c.Address.WasSet {\n\t\tinput.Address = &c.Address.Value\n\t}\n\tif c.Username.WasSet {\n\t\tinput.Username = &c.Username.Value\n\t}\n\tif c.Password.WasSet {\n\t\tinput.Password = &c.Password.Value\n\t}\n\n\t// The following blocks enforces the mutual exclusivity of the\n\t// CompressionCodec and GzipLevel flags.\n\tif c.CompressionCodec.WasSet && c.GzipLevel.WasSet {\n\t\treturn nil, fmt.Errorf(\"error parsing arguments: the --compression-codec flag is mutually exclusive with the --gzip-level flag\")\n\t}\n\n\tif c.Port.WasSet {\n\t\tinput.Port = &c.Port.Value\n\t}\n\n\tif c.Path.WasSet {\n\t\tinput.Path = &c.Path.Value\n\t}\n\n\tif c.Period.WasSet {\n\t\tinput.Period = &c.Period.Value\n\t}\n\n\tif c.Format.WasSet {\n\t\tinput.Format = fastly.ToPointer(argparser.Content(c.Format.Value))\n\t}\n\n\tif c.FormatVersion.WasSet {\n\t\tinput.FormatVersion = &c.FormatVersion.Value\n\t}\n\n\tif c.GzipLevel.WasSet {\n\t\tinput.GzipLevel = &c.GzipLevel.Value\n\t}\n\n\tif c.ResponseCondition.WasSet {\n\t\tinput.ResponseCondition = &c.ResponseCondition.Value\n\t}\n\n\tif c.TimestampFormat.WasSet {\n\t\tinput.TimestampFormat = &c.TimestampFormat.Value\n\t}\n\n\tif c.Placement.WasSet {\n\t\tinput.Placement = &c.Placement.Value\n\t}\n\n\tif c.CompressionCodec.WasSet {\n\t\tinput.CompressionCodec = &c.CompressionCodec.Value\n\t}\n\n\tif c.ProcessingRegion.WasSet {\n\t\tinput.ProcessingRegion = &c.ProcessingRegion.Value\n\t}\n\n\treturn &input, nil\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *CreateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(false),\n\t\tLocked:             optional.Of(false),\n\t\tAutoCloneFlag:      c.AutoClone,\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.ServiceName,\n\t\tServiceVersionFlag: c.ServiceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": errors.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tinput, err := c.ConstructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\td, err := c.Globals.APIClient.CreateFTP(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\ttext.Success(\n\t\tout,\n\t\t\"Created FTP logging endpoint %s (service %s version %d)\",\n\t\tfastly.ToValue(d.Name),\n\t\tfastly.ToValue(d.ServiceID),\n\t\tfastly.ToValue(d.ServiceVersion),\n\t)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/ftp/delete.go",
    "content": "package ftp\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DeleteCommand calls the Fastly API to delete an FTP logging endpoint.\ntype DeleteCommand struct {\n\targparser.Base\n\tInput          fastly.DeleteFTPInput\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n\tautoClone      argparser.OptionalAutoClone\n}\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"delete\", \"Delete an FTP logging endpoint on a Fastly service version\").Alias(\"remove\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"The name of the FTP logging object\").Short('n').Required().StringVar(&c.Input.Name)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.autoClone.Set,\n\t\tDst:    &c.autoClone.Value,\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DeleteCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(false),\n\t\tLocked:             optional.Of(false),\n\t\tAutoCloneFlag:      c.autoClone,\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": errors.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tc.Input.ServiceID = serviceID\n\tc.Input.ServiceVersion = fastly.ToValue(serviceVersion.Number)\n\n\tif err := c.Globals.APIClient.DeleteFTP(context.TODO(), &c.Input); err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Deleted FTP logging endpoint %s (service %s version %d)\", c.Input.Name, c.Input.ServiceID, c.Input.ServiceVersion)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/ftp/describe.go",
    "content": "package ftp\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DescribeCommand calls the Fastly API to describe an FTP logging endpoint.\ntype DescribeCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tInput          fastly.GetFTPInput\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n}\n\n// NewDescribeCommand returns a usable command registered under the parent.\nfunc NewDescribeCommand(parent argparser.Registerer, g *global.Data) *DescribeCommand {\n\tc := DescribeCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"describe\", \"Show detailed information about an FTP logging endpoint on a Fastly service version\").Alias(\"get\")\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\tc.CmdClause.Flag(\"name\", \"The name of the FTP logging object\").Short('n').Required().StringVar(&c.Input.Name)\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DescribeCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fsterr.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tc.Input.ServiceID = serviceID\n\tc.Input.ServiceVersion = fastly.ToValue(serviceVersion.Number)\n\n\to, err := c.Globals.APIClient.GetFTP(context.TODO(), &c.Input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\tlines := text.Lines{\n\t\t\"Address\":            fastly.ToValue(o.Address),\n\t\t\"Compression codec\":  fastly.ToValue(o.CompressionCodec),\n\t\t\"Format version\":     fastly.ToValue(o.FormatVersion),\n\t\t\"Format\":             fastly.ToValue(o.Format),\n\t\t\"GZip level\":         fastly.ToValue(o.GzipLevel),\n\t\t\"Name\":               fastly.ToValue(o.Name),\n\t\t\"Password\":           fastly.ToValue(o.Password),\n\t\t\"Path\":               fastly.ToValue(o.Path),\n\t\t\"Period\":             fastly.ToValue(o.Period),\n\t\t\"Placement\":          fastly.ToValue(o.Placement),\n\t\t\"Port\":               fastly.ToValue(o.Port),\n\t\t\"Processing region\":  fastly.ToValue(o.ProcessingRegion),\n\t\t\"Public key\":         fastly.ToValue(o.PublicKey),\n\t\t\"Response condition\": fastly.ToValue(o.ResponseCondition),\n\t\t\"Timestamp format\":   fastly.ToValue(o.TimestampFormat),\n\t\t\"Username\":           fastly.ToValue(o.Username),\n\t\t\"Version\":            fastly.ToValue(o.ServiceVersion),\n\t}\n\tif !c.Globals.Verbose() {\n\t\tlines[\"Service ID\"] = fastly.ToValue(o.ServiceID)\n\t}\n\ttext.PrintLines(out, lines)\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/ftp/doc.go",
    "content": "// Package ftp contains commands to inspect and manipulate Fastly service FTP\n// logging endpoints.\npackage ftp\n"
  },
  {
    "path": "pkg/commands/service/logging/ftp/ftp_integration_test.go",
    "content": "package ftp_test\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/mock\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n\n\troot \"github.com/fastly/cli/pkg/commands/service\"\n\tparent \"github.com/fastly/cli/pkg/commands/service/logging\"\n\tsub \"github.com/fastly/cli/pkg/commands/service/logging/ftp\"\n)\n\nfunc TestFTPCreate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name log --address example.com --user anonymous --password foo@example.com --compression-codec zstd --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tCreateFTPFn:    createFTPOK,\n\t\t\t},\n\t\t\tWantOutput: \"Created FTP logging endpoint log (service 123 version 4)\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name log --address example.com --user anonymous --password foo@example.com --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tCreateFTPFn:    createFTPError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name log --address example.com --user anonymous --password foo@example.com --compression-codec zstd --gzip-level 9 --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t},\n\t\t\tWantError: \"error parsing arguments: the --compression-codec flag is mutually exclusive with the --gzip-level flag\",\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"create\"}, scenarios)\n}\n\nfunc TestFTPList(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tListFTPsFn:   listFTPsOK,\n\t\t\t},\n\t\t\tWantOutput: listFTPsShortOutput,\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --verbose\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tListFTPsFn:   listFTPsOK,\n\t\t\t},\n\t\t\tWantOutput: listFTPsVerboseOutput,\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 -v\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tListFTPsFn:   listFTPsOK,\n\t\t\t},\n\t\t\tWantOutput: listFTPsVerboseOutput,\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tListFTPsFn:   listFTPsError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"list\"}, scenarios)\n}\n\nfunc TestFTPDescribe(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"--service-id 123 --version 1\",\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tGetFTPFn:     getFTPError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tGetFTPFn:     getFTPOK,\n\t\t\t},\n\t\t\tWantOutput: describeFTPOutput,\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"describe\"}, scenarios)\n}\n\nfunc TestFTPUpdate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"--service-id 123 --version 1 --new-name log\",\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs --new-name log --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tUpdateFTPFn:    updateFTPError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs --new-name log --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tUpdateFTPFn:    updateFTPOK,\n\t\t\t},\n\t\t\tWantOutput: \"Updated FTP logging endpoint log (service 123 version 4)\",\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"update\"}, scenarios)\n}\n\nfunc TestFTPDelete(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"--service-id 123 --version 1\",\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tDeleteFTPFn:    deleteFTPError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tDeleteFTPFn:    deleteFTPOK,\n\t\t\t},\n\t\t\tWantOutput: \"Deleted FTP logging endpoint logs (service 123 version 4)\",\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"delete\"}, scenarios)\n}\n\nvar errTest = errors.New(\"fixture error\")\n\nfunc createFTPOK(_ context.Context, i *fastly.CreateFTPInput) (*fastly.FTP, error) {\n\treturn &fastly.FTP{\n\t\tServiceID:        fastly.ToPointer(i.ServiceID),\n\t\tServiceVersion:   fastly.ToPointer(i.ServiceVersion),\n\t\tName:             i.Name,\n\t\tCompressionCodec: i.CompressionCodec,\n\t}, nil\n}\n\nfunc createFTPError(_ context.Context, _ *fastly.CreateFTPInput) (*fastly.FTP, error) {\n\treturn nil, errTest\n}\n\nfunc listFTPsOK(_ context.Context, i *fastly.ListFTPsInput) ([]*fastly.FTP, error) {\n\treturn []*fastly.FTP{\n\t\t{\n\t\t\tServiceID:         fastly.ToPointer(i.ServiceID),\n\t\t\tServiceVersion:    fastly.ToPointer(i.ServiceVersion),\n\t\t\tName:              fastly.ToPointer(\"logs\"),\n\t\t\tAddress:           fastly.ToPointer(\"example.com\"),\n\t\t\tPort:              fastly.ToPointer(123),\n\t\t\tUsername:          fastly.ToPointer(\"anonymous\"),\n\t\t\tPassword:          fastly.ToPointer(\"foo@example.com\"),\n\t\t\tPublicKey:         fastly.ToPointer(pgpPublicKey()),\n\t\t\tPath:              fastly.ToPointer(\"logs/\"),\n\t\t\tPeriod:            fastly.ToPointer(3600),\n\t\t\tGzipLevel:         fastly.ToPointer(9),\n\t\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\t\tFormatVersion:     fastly.ToPointer(2),\n\t\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\t\tTimestampFormat:   fastly.ToPointer(\"%Y-%m-%dT%H:%M:%S.000\"),\n\t\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\t\tCompressionCodec:  fastly.ToPointer(\"zstd\"),\n\t\t\tProcessingRegion:  fastly.ToPointer(\"us\"),\n\t\t},\n\t\t{\n\t\t\tServiceID:         fastly.ToPointer(i.ServiceID),\n\t\t\tServiceVersion:    fastly.ToPointer(i.ServiceVersion),\n\t\t\tName:              fastly.ToPointer(\"analytics\"),\n\t\t\tAddress:           fastly.ToPointer(\"127.0.0.1\"),\n\t\t\tPort:              fastly.ToPointer(456),\n\t\t\tUsername:          fastly.ToPointer(\"foo\"),\n\t\t\tPassword:          fastly.ToPointer(\"password\"),\n\t\t\tPublicKey:         fastly.ToPointer(pgpPublicKey()),\n\t\t\tPath:              fastly.ToPointer(\"logs/\"),\n\t\t\tPeriod:            fastly.ToPointer(86400),\n\t\t\tGzipLevel:         fastly.ToPointer(9),\n\t\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\t\tFormatVersion:     fastly.ToPointer(2),\n\t\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\t\tTimestampFormat:   fastly.ToPointer(\"%Y-%m-%dT%H:%M:%S.000\"),\n\t\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\t\tCompressionCodec:  fastly.ToPointer(\"zstd\"),\n\t\t\tProcessingRegion:  fastly.ToPointer(\"us\"),\n\t\t},\n\t}, nil\n}\n\nfunc listFTPsError(_ context.Context, _ *fastly.ListFTPsInput) ([]*fastly.FTP, error) {\n\treturn nil, errTest\n}\n\nvar listFTPsShortOutput = strings.TrimSpace(`\nSERVICE  VERSION  NAME\n123      1        logs\n123      1        analytics\n`) + \"\\n\"\n\nvar listFTPsVerboseOutput = strings.TrimSpace(`\nFastly API endpoint: https://api.fastly.com\nFastly API token provided via config file (auth: user)\n\nService ID (via --service-id): 123\n\nVersion: 1\n\tFTP 1/2\n\t\tService ID: 123\n\t\tVersion: 1\n\t\tName: logs\n\t\tAddress: example.com\n\t\tPort: 123\n\t\tUsername: anonymous\n\t\tPassword: foo@example.com\n\t\tPublic key: `+pgpPublicKey()+`\n\t\tPath: logs/\n\t\tPeriod: 3600\n\t\tGZip level: 9\n\t\tFormat: %h %l %u %t \"%r\" %>s %b\n\t\tFormat version: 2\n\t\tResponse condition: Prevent default logging\n\t\tTimestamp format: %Y-%m-%dT%H:%M:%S.000\n\t\tPlacement: none\n\t\tCompression codec: zstd\n\t\tProcessing region: us\n\tFTP 2/2\n\t\tService ID: 123\n\t\tVersion: 1\n\t\tName: analytics\n\t\tAddress: 127.0.0.1\n\t\tPort: 456\n\t\tUsername: foo\n\t\tPassword: password\n\t\tPublic key: `+pgpPublicKey()+`\n\t\tPath: logs/\n\t\tPeriod: 86400\n\t\tGZip level: 9\n\t\tFormat: %h %l %u %t \"%r\" %>s %b\n\t\tFormat version: 2\n\t\tResponse condition: Prevent default logging\n\t\tTimestamp format: %Y-%m-%dT%H:%M:%S.000\n\t\tPlacement: none\n\t\tCompression codec: zstd\n\t\tProcessing region: us\n`) + \"\\n\\n\"\n\nfunc getFTPOK(_ context.Context, i *fastly.GetFTPInput) (*fastly.FTP, error) {\n\treturn &fastly.FTP{\n\t\tServiceID:         fastly.ToPointer(i.ServiceID),\n\t\tServiceVersion:    fastly.ToPointer(i.ServiceVersion),\n\t\tName:              fastly.ToPointer(\"logs\"),\n\t\tAddress:           fastly.ToPointer(\"example.com\"),\n\t\tPort:              fastly.ToPointer(123),\n\t\tUsername:          fastly.ToPointer(\"anonymous\"),\n\t\tPassword:          fastly.ToPointer(\"foo@example.com\"),\n\t\tPublicKey:         fastly.ToPointer(pgpPublicKey()),\n\t\tPath:              fastly.ToPointer(\"logs/\"),\n\t\tPeriod:            fastly.ToPointer(3600),\n\t\tGzipLevel:         fastly.ToPointer(9),\n\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\tFormatVersion:     fastly.ToPointer(2),\n\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\tTimestampFormat:   fastly.ToPointer(\"%Y-%m-%dT%H:%M:%S.000\"),\n\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\tCompressionCodec:  fastly.ToPointer(\"zstd\"),\n\t\tProcessingRegion:  fastly.ToPointer(\"us\"),\n\t}, nil\n}\n\nfunc getFTPError(_ context.Context, _ *fastly.GetFTPInput) (*fastly.FTP, error) {\n\treturn nil, errTest\n}\n\nvar describeFTPOutput = \"\\n\" + strings.TrimSpace(`\nAddress: example.com\nCompression codec: zstd\nFormat: %h %l %u %t \"%r\" %>s %b\nFormat version: 2\nGZip level: 9\nName: logs\nPassword: foo@example.com\nPath: logs/\nPeriod: 3600\nPlacement: none\nPort: 123\nProcessing region: us\nPublic key: `+pgpPublicKey()+`\nResponse condition: Prevent default logging\nService ID: 123\nTimestamp format: %Y-%m-%dT%H:%M:%S.000\nUsername: anonymous\nVersion: 1\n`) + \"\\n\"\n\nfunc updateFTPOK(_ context.Context, i *fastly.UpdateFTPInput) (*fastly.FTP, error) {\n\treturn &fastly.FTP{\n\t\tServiceID:         fastly.ToPointer(i.ServiceID),\n\t\tServiceVersion:    fastly.ToPointer(i.ServiceVersion),\n\t\tName:              fastly.ToPointer(\"log\"),\n\t\tAddress:           fastly.ToPointer(\"example.com\"),\n\t\tPort:              fastly.ToPointer(123),\n\t\tUsername:          fastly.ToPointer(\"anonymous\"),\n\t\tPassword:          fastly.ToPointer(\"foo@example.com\"),\n\t\tPublicKey:         fastly.ToPointer(pgpPublicKey()),\n\t\tPath:              fastly.ToPointer(\"logs/\"),\n\t\tPeriod:            fastly.ToPointer(3600),\n\t\tGzipLevel:         fastly.ToPointer(9),\n\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\tFormatVersion:     fastly.ToPointer(2),\n\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\tTimestampFormat:   fastly.ToPointer(\"%Y-%m-%dT%H:%M:%S.000\"),\n\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\tCompressionCodec:  fastly.ToPointer(\"zstd\"),\n\t}, nil\n}\n\nfunc updateFTPError(_ context.Context, _ *fastly.UpdateFTPInput) (*fastly.FTP, error) {\n\treturn nil, errTest\n}\n\nfunc deleteFTPOK(_ context.Context, _ *fastly.DeleteFTPInput) error {\n\treturn nil\n}\n\nfunc deleteFTPError(_ context.Context, _ *fastly.DeleteFTPInput) error {\n\treturn errTest\n}\n\n// pgpPublicKey returns a PEM encoded PGP public key suitable for testing.\nfunc pgpPublicKey() string {\n\treturn strings.TrimSpace(`-----BEGIN PGP PUBLIC KEY BLOCK-----\nmQENBFyUD8sBCACyFnB39AuuTygseek+eA4fo0cgwva6/FSjnWq7riouQee8GgQ/\nibXTRyv4iVlwI12GswvMTIy7zNvs1R54i0qvsLr+IZ4GVGJqs6ZJnvQcqe3xPoR4\n8AnBfw90o32r/LuHf6QCJXi+AEu35koNlNAvLJ2B+KACaNB7N0EeWmqpV/1V2k9p\nlDYk+th7LcCuaFNGqKS/PrMnnMqR6VDLCjHhNx4KR79b0Twm/2qp6an3hyNRu8Gn\ndwxpf1/BUu3JWf+LqkN4Y3mbOmSUL3MaJNvyQguUzTfS0P0uGuBDHrJCVkMZCzDB\n89ag55jCPHyGeHBTd02gHMWzsg3WMBWvCsrzABEBAAG0JXRlcnJhZm9ybSAodGVz\ndCkgPHRlc3RAdGVycmFmb3JtLmNvbT6JAU4EEwEIADgWIQSHYyc6Kj9l6HzQsau6\nvFFc9jxV/wUCXJQPywIbAwULCQgHAgYVCgkICwIEFgIDAQIeAQIXgAAKCRC6vFFc\n9jxV/815CAClb32OxV7wG01yF97TzlyTl8TnvjMtoG29Mw4nSyg+mjM3b8N7iXm9\nOLX59fbDAWtBSldSZE22RXd3CvlFOG/EnKBXSjBtEqfyxYSnyOPkMPBYWGL/ApkX\nSvPYJ4LKdvipYToKFh3y9kk2gk1DcDBDyaaHvR+3rv1u3aoy7/s2EltAfDS3ZQIq\n7/cWTLJml/lleeB/Y6rPj8xqeCYhE5ahw9gsV/Mdqatl24V9Tks30iijx0Hhw+Gx\nkATUikMGr2GDVqoIRga5kXI7CzYff4rkc0Twn47fMHHHe/KY9M2yVnMHUXmAZwbG\nM1cMI/NH1DjevCKdGBLcRJlhuLPKF/anuQENBFyUD8sBCADIpd7r7GuPd6n/Ikxe\nu6h7umV6IIPoAm88xCYpTbSZiaK30Svh6Ywra9jfE2KlU9o6Y/art8ip0VJ3m07L\n4RSfSpnzqgSwdjSq5hNour2Fo/BzYhK7yaz2AzVSbe33R0+RYhb4b/6N+bKbjwGF\nftCsqVFMH+PyvYkLbvxyQrHlA9woAZaNThI1ztO5rGSnGUR8xt84eup28WIFKg0K\nUEGUcTzz+8QGAwAra+0ewPXo/AkO+8BvZjDidP417u6gpBHOJ9qYIcO9FxHeqFyu\nYrjlrxowEgXn5wO8xuNz6Vu1vhHGDHGDsRbZF8pv1d5O+0F1G7ttZ2GRRgVBZPwi\nkiyRABEBAAGJATYEGAEIACAWIQSHYyc6Kj9l6HzQsau6vFFc9jxV/wUCXJQPywIb\nDAAKCRC6vFFc9jxV/9YOCACe8qmOSnKQpQfW+PqYOqo3dt7JyweTs3FkD6NT8Zml\ndYy/vkstbTjPpX6aTvUZjkb46BVi7AOneVHpD5GBqvRsZ9iVgDYHaehmLCdKiG5L\n3Tp90NN+QY5WDbsGmsyk6+6ZMYejb4qYfweQeduOj27aavCJdLkCYMoRKfcFYI8c\nFaNmEfKKy/r1PO20NXEG6t9t05K/frHy6ZG8bCNYdpagfFVot47r9JaQqWlTNtIR\n5+zkkSq/eG9BEtRij3a6cTdQbktdBzx2KBeI0PYc1vlZR0LpuFKZqY9vlE6vTGLR\nwMfrTEOvx0NxUM3rpaCgEmuWbB1G1Hu371oyr4srrr+N\n=28dr\n-----END PGP PUBLIC KEY BLOCK-----\n`)\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/ftp/ftp_test.go",
    "content": "package ftp_test\n\nimport (\n\t\"bytes\"\n\t\"testing\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/service/logging/ftp\"\n\t\"github.com/fastly/cli/pkg/config\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/manifest\"\n\t\"github.com/fastly/cli/pkg/mock\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n)\n\nfunc TestCreateFTPInput(t *testing.T) {\n\tfor _, testcase := range []struct {\n\t\tname      string\n\t\tcmd       *ftp.CreateCommand\n\t\twant      *fastly.CreateFTPInput\n\t\twantError string\n\t}{\n\t\t{\n\t\t\tname: \"required values set flag serviceID\",\n\t\t\tcmd:  createCommandRequired(),\n\t\t\twant: &fastly.CreateFTPInput{\n\t\t\t\tServiceID:      \"123\",\n\t\t\t\tServiceVersion: 4,\n\t\t\t\tName:           fastly.ToPointer(\"log\"),\n\t\t\t\tAddress:        fastly.ToPointer(\"example.com\"),\n\t\t\t\tUsername:       fastly.ToPointer(\"user\"),\n\t\t\t\tPassword:       fastly.ToPointer(\"password\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"all values set flag serviceID\",\n\t\t\tcmd:  createCommandAll(),\n\t\t\twant: &fastly.CreateFTPInput{\n\t\t\t\tServiceID:         \"123\",\n\t\t\t\tServiceVersion:    4,\n\t\t\t\tName:              fastly.ToPointer(\"log\"),\n\t\t\t\tAddress:           fastly.ToPointer(\"example.com\"),\n\t\t\t\tPort:              fastly.ToPointer(22),\n\t\t\t\tUsername:          fastly.ToPointer(\"user\"),\n\t\t\t\tPassword:          fastly.ToPointer(\"password\"),\n\t\t\t\tPath:              fastly.ToPointer(\"/logs\"),\n\t\t\t\tPeriod:            fastly.ToPointer(3600),\n\t\t\t\tProcessingRegion:  fastly.ToPointer(\"eu\"),\n\t\t\t\tFormatVersion:     fastly.ToPointer(2),\n\t\t\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\t\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\t\t\tTimestampFormat:   fastly.ToPointer(\"%Y-%m-%dT%H:%M:%S.000\"),\n\t\t\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\t\t\tCompressionCodec:  fastly.ToPointer(\"zstd\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:      \"error missing serviceID\",\n\t\t\tcmd:       createCommandMissingServiceID(),\n\t\t\twant:      nil,\n\t\t\twantError: errors.ErrNoServiceID.Error(),\n\t\t},\n\t} {\n\t\tt.Run(testcase.name, func(t *testing.T) {\n\t\t\tif testcase.wantError == errors.ErrNoServiceID.Error() {\n\t\t\t\tt.Setenv(\"FASTLY_SERVICE_ID\", \"\")\n\t\t\t}\n\t\t\tvar bs []byte\n\t\t\tout := bytes.NewBuffer(bs)\n\t\t\tverboseMode := true\n\n\t\t\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\t\t\tAutoCloneFlag:      testcase.cmd.AutoClone,\n\t\t\t\tAPIClient:          testcase.cmd.Globals.APIClient,\n\t\t\t\tManifest:           testcase.cmd.Manifest,\n\t\t\t\tOut:                out,\n\t\t\t\tServiceVersionFlag: testcase.cmd.ServiceVersion,\n\t\t\t\tVerboseMode:        verboseMode,\n\t\t\t})\n\n\t\t\tswitch {\n\t\t\tcase err != nil && testcase.wantError == \"\":\n\t\t\t\tt.Fatalf(\"unexpected error getting service details: %v\", err)\n\t\t\t\treturn\n\t\t\tcase err != nil && testcase.wantError != \"\":\n\t\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\t\treturn\n\t\t\tcase err == nil && testcase.wantError != \"\":\n\t\t\t\tt.Fatalf(\"expected error, have nil (service details: %s, %d)\", serviceID, serviceVersion.Number)\n\t\t\tcase err == nil && testcase.wantError == \"\":\n\t\t\t\thave, err := testcase.cmd.ConstructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\t\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\t\ttestutil.AssertEqual(t, testcase.want, have)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestUpdateFTPInput(t *testing.T) {\n\tscenarios := []struct {\n\t\tname      string\n\t\tcmd       *ftp.UpdateCommand\n\t\tapi       mock.API\n\t\twant      *fastly.UpdateFTPInput\n\t\twantError string\n\t}{\n\t\t{\n\t\t\tname: \"no updates\",\n\t\t\tcmd:  updateCommandNoUpdates(),\n\t\t\tapi: mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tGetFTPFn:       getFTPOK,\n\t\t\t},\n\t\t\twant: &fastly.UpdateFTPInput{\n\t\t\t\tServiceID:      \"123\",\n\t\t\t\tServiceVersion: 4,\n\t\t\t\tName:           \"log\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"all values set flag serviceID\",\n\t\t\tcmd:  updateCommandAll(),\n\t\t\tapi: mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tGetFTPFn:       getFTPOK,\n\t\t\t},\n\t\t\twant: &fastly.UpdateFTPInput{\n\t\t\t\tServiceID:         \"123\",\n\t\t\t\tServiceVersion:    4,\n\t\t\t\tName:              \"log\",\n\t\t\t\tNewName:           fastly.ToPointer(\"new1\"),\n\t\t\t\tAddress:           fastly.ToPointer(\"new2\"),\n\t\t\t\tPort:              fastly.ToPointer(23),\n\t\t\t\tPublicKey:         fastly.ToPointer(\"new10\"),\n\t\t\t\tUsername:          fastly.ToPointer(\"new3\"),\n\t\t\t\tPassword:          fastly.ToPointer(\"new4\"),\n\t\t\t\tPath:              fastly.ToPointer(\"new5\"),\n\t\t\t\tPeriod:            fastly.ToPointer(3601),\n\t\t\t\tProcessingRegion:  fastly.ToPointer(\"eu\"),\n\t\t\t\tFormatVersion:     fastly.ToPointer(3),\n\t\t\t\tGzipLevel:         fastly.ToPointer(0),\n\t\t\t\tFormat:            fastly.ToPointer(\"new6\"),\n\t\t\t\tResponseCondition: fastly.ToPointer(\"new7\"),\n\t\t\t\tTimestampFormat:   fastly.ToPointer(\"new8\"),\n\t\t\t\tPlacement:         fastly.ToPointer(\"new9\"),\n\t\t\t\tCompressionCodec:  fastly.ToPointer(\"new11\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:      \"error missing serviceID\",\n\t\t\tcmd:       updateCommandMissingServiceID(),\n\t\t\twant:      nil,\n\t\t\twantError: errors.ErrNoServiceID.Error(),\n\t\t},\n\t}\n\tfor testcaseIdx := range scenarios {\n\t\ttestcase := &scenarios[testcaseIdx]\n\t\tt.Run(testcase.name, func(t *testing.T) {\n\t\t\tif testcase.wantError == errors.ErrNoServiceID.Error() {\n\t\t\t\tt.Setenv(\"FASTLY_SERVICE_ID\", \"\")\n\t\t\t}\n\t\t\ttestcase.cmd.Globals.APIClient = testcase.api\n\n\t\t\tvar bs []byte\n\t\t\tout := bytes.NewBuffer(bs)\n\t\t\tverboseMode := true\n\n\t\t\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\t\t\tAutoCloneFlag:      testcase.cmd.AutoClone,\n\t\t\t\tAPIClient:          testcase.api,\n\t\t\t\tManifest:           testcase.cmd.Manifest,\n\t\t\t\tOut:                out,\n\t\t\t\tServiceVersionFlag: testcase.cmd.ServiceVersion,\n\t\t\t\tVerboseMode:        verboseMode,\n\t\t\t})\n\n\t\t\tswitch {\n\t\t\tcase err != nil && testcase.wantError == \"\":\n\t\t\t\tt.Fatalf(\"unexpected error getting service details: %v\", err)\n\t\t\t\treturn\n\t\t\tcase err != nil && testcase.wantError != \"\":\n\t\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\t\treturn\n\t\t\tcase err == nil && testcase.wantError != \"\":\n\t\t\t\tt.Fatalf(\"expected error, have nil (service details: %s, %d)\", serviceID, serviceVersion.Number)\n\t\t\tcase err == nil && testcase.wantError == \"\":\n\t\t\t\thave, err := testcase.cmd.ConstructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\t\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\t\ttestutil.AssertEqual(t, testcase.want, have)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc createCommandRequired() *ftp.CreateCommand {\n\tvar b bytes.Buffer\n\n\tg := global.Data{\n\t\tConfig: config.File{},\n\t\tEnv:    config.Environment{},\n\t\tOutput: &b,\n\t}\n\tg.APIClient, _ = mock.APIClient(mock.API{\n\t\tGetVersionFn:   testutil.GetVersion,\n\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t})(\"token\", \"endpoint\", false)\n\n\treturn &ftp.CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: &g,\n\t\t},\n\t\tManifest: manifest.Data{\n\t\t\tFlag: manifest.Flag{\n\t\t\t\tServiceID: \"123\",\n\t\t\t},\n\t\t},\n\t\tEndpointName: argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"log\"},\n\t\tAddress:      argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"example.com\"},\n\t\tUsername:     argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"user\"},\n\t\tPassword:     argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"password\"},\n\t\tServiceVersion: argparser.OptionalServiceVersion{\n\t\t\tOptionalString: argparser.OptionalString{Value: \"1\"},\n\t\t},\n\t\tAutoClone: argparser.OptionalAutoClone{\n\t\t\tOptionalBool: argparser.OptionalBool{\n\t\t\t\tOptional: argparser.Optional{\n\t\t\t\t\tWasSet: true,\n\t\t\t\t},\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t},\n\t}\n}\n\nfunc createCommandAll() *ftp.CreateCommand {\n\tvar b bytes.Buffer\n\n\tg := global.Data{\n\t\tConfig: config.File{},\n\t\tEnv:    config.Environment{},\n\t\tOutput: &b,\n\t}\n\tg.APIClient, _ = mock.APIClient(mock.API{\n\t\tGetVersionFn:   testutil.GetVersion,\n\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t})(\"token\", \"endpoint\", false)\n\n\treturn &ftp.CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: &g,\n\t\t},\n\t\tManifest: manifest.Data{\n\t\t\tFlag: manifest.Flag{\n\t\t\t\tServiceID: \"123\",\n\t\t\t},\n\t\t},\n\t\tServiceVersion: argparser.OptionalServiceVersion{\n\t\t\tOptionalString: argparser.OptionalString{Value: \"1\"},\n\t\t},\n\t\tAutoClone: argparser.OptionalAutoClone{\n\t\t\tOptionalBool: argparser.OptionalBool{\n\t\t\t\tOptional: argparser.Optional{\n\t\t\t\t\tWasSet: true,\n\t\t\t\t},\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t},\n\t\tEndpointName:      argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"log\"},\n\t\tAddress:           argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"example.com\"},\n\t\tUsername:          argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"user\"},\n\t\tPassword:          argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"password\"},\n\t\tPort:              argparser.OptionalInt{Optional: argparser.Optional{WasSet: true}, Value: 22},\n\t\tPath:              argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"/logs\"},\n\t\tPeriod:            argparser.OptionalInt{Optional: argparser.Optional{WasSet: true}, Value: 3600},\n\t\tProcessingRegion:  argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"eu\"},\n\t\tFormat:            argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: `%h %l %u %t \"%r\" %>s %b`},\n\t\tFormatVersion:     argparser.OptionalInt{Optional: argparser.Optional{WasSet: true}, Value: 2},\n\t\tTimestampFormat:   argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"%Y-%m-%dT%H:%M:%S.000\"},\n\t\tResponseCondition: argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"Prevent default logging\"},\n\t\tPlacement:         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"none\"},\n\t\tCompressionCodec:  argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"zstd\"},\n\t}\n}\n\nfunc createCommandMissingServiceID() *ftp.CreateCommand {\n\tres := createCommandAll()\n\tres.Manifest = manifest.Data{}\n\tres.ServiceVersion = argparser.OptionalServiceVersion{}\n\treturn res\n}\n\nfunc updateCommandNoUpdates() *ftp.UpdateCommand {\n\tvar b bytes.Buffer\n\n\tg := global.Data{\n\t\tConfig: config.File{},\n\t\tEnv:    config.Environment{},\n\t\tOutput: &b,\n\t}\n\n\treturn &ftp.UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: &g,\n\t\t},\n\t\tManifest: manifest.Data{\n\t\t\tFlag: manifest.Flag{\n\t\t\t\tServiceID: \"123\",\n\t\t\t},\n\t\t},\n\t\tEndpointName: \"log\",\n\t\tServiceVersion: argparser.OptionalServiceVersion{\n\t\t\tOptionalString: argparser.OptionalString{Value: \"1\"},\n\t\t},\n\t\tAutoClone: argparser.OptionalAutoClone{\n\t\t\tOptionalBool: argparser.OptionalBool{\n\t\t\t\tOptional: argparser.Optional{\n\t\t\t\t\tWasSet: true,\n\t\t\t\t},\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t},\n\t}\n}\n\nfunc updateCommandAll() *ftp.UpdateCommand {\n\tvar b bytes.Buffer\n\n\tg := global.Data{\n\t\tConfig: config.File{},\n\t\tEnv:    config.Environment{},\n\t\tOutput: &b,\n\t}\n\n\treturn &ftp.UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: &g,\n\t\t},\n\t\tManifest: manifest.Data{\n\t\t\tFlag: manifest.Flag{\n\t\t\t\tServiceID: \"123\",\n\t\t\t},\n\t\t},\n\t\tEndpointName: \"log\",\n\t\tServiceVersion: argparser.OptionalServiceVersion{\n\t\t\tOptionalString: argparser.OptionalString{Value: \"1\"},\n\t\t},\n\t\tAutoClone: argparser.OptionalAutoClone{\n\t\t\tOptionalBool: argparser.OptionalBool{\n\t\t\t\tOptional: argparser.Optional{\n\t\t\t\t\tWasSet: true,\n\t\t\t\t},\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t},\n\t\tNewName:           argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new1\"},\n\t\tAddress:           argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new2\"},\n\t\tPort:              argparser.OptionalInt{Optional: argparser.Optional{WasSet: true}, Value: 23},\n\t\tUsername:          argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new3\"},\n\t\tPassword:          argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new4\"},\n\t\tPublicKey:         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new10\"},\n\t\tPath:              argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new5\"},\n\t\tPeriod:            argparser.OptionalInt{Optional: argparser.Optional{WasSet: true}, Value: 3601},\n\t\tGzipLevel:         argparser.OptionalInt{Optional: argparser.Optional{WasSet: true}, Value: 0},\n\t\tProcessingRegion:  argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"eu\"},\n\t\tFormat:            argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new6\"},\n\t\tFormatVersion:     argparser.OptionalInt{Optional: argparser.Optional{WasSet: true}, Value: 3},\n\t\tResponseCondition: argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new7\"},\n\t\tTimestampFormat:   argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new8\"},\n\t\tPlacement:         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new9\"},\n\t\tCompressionCodec:  argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new11\"},\n\t}\n}\n\nfunc updateCommandMissingServiceID() *ftp.UpdateCommand {\n\tres := updateCommandAll()\n\tres.Manifest = manifest.Data{}\n\tres.ServiceVersion = argparser.OptionalServiceVersion{}\n\treturn res\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/ftp/list.go",
    "content": "package ftp\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// ListCommand calls the Fastly API to list FTP logging endpoints.\ntype ListCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tInput          fastly.ListFTPsInput\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n}\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"list\", \"List FTP endpoints on a Fastly service version\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *ListCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fsterr.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tc.Input.ServiceID = serviceID\n\tc.Input.ServiceVersion = fastly.ToValue(serviceVersion.Number)\n\n\to, err := c.Globals.APIClient.ListFTPs(context.TODO(), &c.Input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\tif !c.Globals.Verbose() {\n\t\ttw := text.NewTable(out)\n\t\ttw.AddHeader(\"SERVICE\", \"VERSION\", \"NAME\")\n\t\tfor _, ftp := range o {\n\t\t\ttw.AddLine(\n\t\t\t\tfastly.ToValue(ftp.ServiceID),\n\t\t\t\tfastly.ToValue(ftp.ServiceVersion),\n\t\t\t\tfastly.ToValue(ftp.Name),\n\t\t\t)\n\t\t}\n\t\ttw.Print()\n\t\treturn nil\n\t}\n\n\tfmt.Fprintf(out, \"Version: %d\\n\", c.Input.ServiceVersion)\n\tfor i, ftp := range o {\n\t\tfmt.Fprintf(out, \"\\tFTP %d/%d\\n\", i+1, len(o))\n\t\tfmt.Fprintf(out, \"\\t\\tService ID: %s\\n\", fastly.ToValue(ftp.ServiceID))\n\t\tfmt.Fprintf(out, \"\\t\\tVersion: %d\\n\", fastly.ToValue(ftp.ServiceVersion))\n\t\tfmt.Fprintf(out, \"\\t\\tName: %s\\n\", fastly.ToValue(ftp.Name))\n\t\tfmt.Fprintf(out, \"\\t\\tAddress: %s\\n\", fastly.ToValue(ftp.Address))\n\t\tfmt.Fprintf(out, \"\\t\\tPort: %d\\n\", fastly.ToValue(ftp.Port))\n\t\tfmt.Fprintf(out, \"\\t\\tUsername: %s\\n\", fastly.ToValue(ftp.Username))\n\t\tfmt.Fprintf(out, \"\\t\\tPassword: %s\\n\", fastly.ToValue(ftp.Password))\n\t\tfmt.Fprintf(out, \"\\t\\tPublic key: %s\\n\", fastly.ToValue(ftp.PublicKey))\n\t\tfmt.Fprintf(out, \"\\t\\tPath: %s\\n\", fastly.ToValue(ftp.Path))\n\t\tfmt.Fprintf(out, \"\\t\\tPeriod: %d\\n\", fastly.ToValue(ftp.Period))\n\t\tfmt.Fprintf(out, \"\\t\\tGZip level: %d\\n\", fastly.ToValue(ftp.GzipLevel))\n\t\tfmt.Fprintf(out, \"\\t\\tFormat: %s\\n\", fastly.ToValue(ftp.Format))\n\t\tfmt.Fprintf(out, \"\\t\\tFormat version: %d\\n\", fastly.ToValue(ftp.FormatVersion))\n\t\tfmt.Fprintf(out, \"\\t\\tResponse condition: %s\\n\", fastly.ToValue(ftp.ResponseCondition))\n\t\tfmt.Fprintf(out, \"\\t\\tTimestamp format: %s\\n\", fastly.ToValue(ftp.TimestampFormat))\n\t\tfmt.Fprintf(out, \"\\t\\tPlacement: %s\\n\", fastly.ToValue(ftp.Placement))\n\t\tfmt.Fprintf(out, \"\\t\\tCompression codec: %s\\n\", fastly.ToValue(ftp.CompressionCodec))\n\t\tfmt.Fprintf(out, \"\\t\\tProcessing region: %s\\n\", fastly.ToValue(ftp.ProcessingRegion))\n\t}\n\tfmt.Fprintln(out)\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/ftp/root.go",
    "content": "package ftp\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"ftp\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manipulate Fastly service version FTP logging endpoints\")\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/ftp/update.go",
    "content": "package ftp\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/service/logging/logflags\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/manifest\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// UpdateCommand calls the Fastly API to update an FTP logging endpoint.\ntype UpdateCommand struct {\n\targparser.Base\n\tManifest manifest.Data\n\n\t// Required.\n\tEndpointName   string // Can't shadow argparser.Base method Name().\n\tServiceName    argparser.OptionalServiceNameID\n\tServiceVersion argparser.OptionalServiceVersion\n\n\t// Optional.\n\tAddress           argparser.OptionalString\n\tAutoClone         argparser.OptionalAutoClone\n\tCompressionCodec  argparser.OptionalString\n\tFormat            argparser.OptionalString\n\tFormatVersion     argparser.OptionalInt\n\tGzipLevel         argparser.OptionalInt\n\tNewName           argparser.OptionalString\n\tPassword          argparser.OptionalString\n\tPath              argparser.OptionalString\n\tPeriod            argparser.OptionalInt\n\tPlacement         argparser.OptionalString\n\tPort              argparser.OptionalInt\n\tProcessingRegion  argparser.OptionalString\n\tPublicKey         argparser.OptionalString\n\tResponseCondition argparser.OptionalString\n\tTimestampFormat   argparser.OptionalString\n\tUsername          argparser.OptionalString\n}\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"update\", \"Update an FTP logging endpoint on a Fastly service version\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"The name of the FTP logging object\").Short('n').Required().StringVar(&c.EndpointName)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.ServiceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.CmdClause.Flag(\"address\", \"An hostname or IPv4 address\").Action(c.Address.Set).StringVar(&c.Address.Value)\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.AutoClone.Set,\n\t\tDst:    &c.AutoClone.Value,\n\t})\n\tlogflags.CompressionCodec(c.CmdClause, &c.CompressionCodec)\n\tlogflags.Format(c.CmdClause, &c.Format)\n\tlogflags.FormatVersion(c.CmdClause, &c.FormatVersion)\n\tlogflags.GzipLevel(c.CmdClause, &c.GzipLevel)\n\tc.CmdClause.Flag(\"new-name\", \"New name of the FTP logging object\").Action(c.NewName.Set).StringVar(&c.NewName.Value)\n\tc.CmdClause.Flag(\"password\", \"The password for the server (for anonymous use an email address)\").Action(c.Password.Set).StringVar(&c.Password.Value)\n\tc.CmdClause.Flag(\"path\", \"The path to upload log files to. If the path ends in / then it is treated as a directory\").Action(c.Path.Set).StringVar(&c.Path.Value)\n\tlogflags.Period(c.CmdClause, &c.Period)\n\tlogflags.Placement(c.CmdClause, &c.Placement)\n\tc.CmdClause.Flag(\"port\", \"The port number\").Action(c.Port.Set).IntVar(&c.Port.Value)\n\tlogflags.ProcessingRegion(c.CmdClause, &c.ProcessingRegion, \"FTP\")\n\tlogflags.PublicKey(c.CmdClause, &c.PublicKey)\n\tlogflags.ResponseCondition(c.CmdClause, &c.ResponseCondition)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.ServiceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.ServiceName.Value,\n\t})\n\tlogflags.TimestampFormat(c.CmdClause, &c.TimestampFormat)\n\tc.CmdClause.Flag(\"username\", \"The username for the server (can be anonymous)\").Action(c.Username.Set).StringVar(&c.Username.Value)\n\treturn &c\n}\n\n// ConstructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *UpdateCommand) ConstructInput(serviceID string, serviceVersion int) (*fastly.UpdateFTPInput, error) {\n\tinput := fastly.UpdateFTPInput{\n\t\tServiceID:      serviceID,\n\t\tServiceVersion: serviceVersion,\n\t\tName:           c.EndpointName,\n\t}\n\n\t// Set new values if set by user.\n\tif c.NewName.WasSet {\n\t\tinput.NewName = &c.NewName.Value\n\t}\n\n\tif c.Address.WasSet {\n\t\tinput.Address = &c.Address.Value\n\t}\n\n\tif c.Port.WasSet {\n\t\tinput.Port = &c.Port.Value\n\t}\n\n\tif c.Username.WasSet {\n\t\tinput.Username = &c.Username.Value\n\t}\n\n\tif c.Password.WasSet {\n\t\tinput.Password = &c.Password.Value\n\t}\n\n\tif c.PublicKey.WasSet {\n\t\tinput.PublicKey = &c.PublicKey.Value\n\t}\n\n\tif c.Path.WasSet {\n\t\tinput.Path = &c.Path.Value\n\t}\n\n\tif c.Period.WasSet {\n\t\tinput.Period = &c.Period.Value\n\t}\n\n\tif c.FormatVersion.WasSet {\n\t\tinput.FormatVersion = &c.FormatVersion.Value\n\t}\n\n\tif c.GzipLevel.WasSet {\n\t\tinput.GzipLevel = &c.GzipLevel.Value\n\t}\n\n\tif c.Format.WasSet {\n\t\tinput.Format = fastly.ToPointer(argparser.Content(c.Format.Value))\n\t}\n\n\tif c.ResponseCondition.WasSet {\n\t\tinput.ResponseCondition = &c.ResponseCondition.Value\n\t}\n\n\tif c.TimestampFormat.WasSet {\n\t\tinput.TimestampFormat = &c.TimestampFormat.Value\n\t}\n\n\tif c.Placement.WasSet {\n\t\tinput.Placement = &c.Placement.Value\n\t}\n\n\tif c.CompressionCodec.WasSet {\n\t\tinput.CompressionCodec = &c.CompressionCodec.Value\n\t}\n\n\tif c.ProcessingRegion.WasSet {\n\t\tinput.ProcessingRegion = &c.ProcessingRegion.Value\n\t}\n\n\treturn &input, nil\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *UpdateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(false),\n\t\tLocked:             optional.Of(false),\n\t\tAutoCloneFlag:      c.AutoClone,\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.ServiceName,\n\t\tServiceVersionFlag: c.ServiceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": errors.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tinput, err := c.ConstructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tftp, err := c.Globals.APIClient.UpdateFTP(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\ttext.Success(\n\t\tout,\n\t\t\"Updated FTP logging endpoint %s (service %s version %d)\",\n\t\tfastly.ToValue(ftp.Name),\n\t\tfastly.ToValue(ftp.ServiceID),\n\t\tfastly.ToValue(ftp.ServiceVersion),\n\t)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/gcs/create.go",
    "content": "package gcs\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/service/logging/logflags\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/manifest\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// CreateCommand calls the Fastly API to create a GCS logging endpoint.\ntype CreateCommand struct {\n\targparser.Base\n\tManifest manifest.Data\n\n\t// Required.\n\tServiceName    argparser.OptionalServiceNameID\n\tServiceVersion argparser.OptionalServiceVersion\n\n\t// Optional.\n\tAccountName       argparser.OptionalString\n\tAutoClone         argparser.OptionalAutoClone\n\tBucket            argparser.OptionalString\n\tCompressionCodec  argparser.OptionalString\n\tEndpointName      argparser.OptionalString // Can't shadow argparser.Base method Name().\n\tFormat            argparser.OptionalString\n\tFormatVersion     argparser.OptionalInt\n\tGzipLevel         argparser.OptionalInt\n\tMessageType       argparser.OptionalString\n\tPath              argparser.OptionalString\n\tPeriod            argparser.OptionalInt\n\tPlacement         argparser.OptionalString\n\tProcessingRegion  argparser.OptionalString\n\tProjectID         argparser.OptionalString\n\tResponseCondition argparser.OptionalString\n\tSecretKey         argparser.OptionalString\n\tTimestampFormat   argparser.OptionalString\n\tUser              argparser.OptionalString\n}\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"create\", \"Create a GCS logging endpoint on a Fastly service version\").Alias(\"add\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"The name of the GCS logging object. Used as a primary key for API access\").Short('n').Action(c.EndpointName.Set).StringVar(&c.EndpointName.Value)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.ServiceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tlogflags.AccountName(c.CmdClause, &c.AccountName)\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.AutoClone.Set,\n\t\tDst:    &c.AutoClone.Value,\n\t})\n\tc.CmdClause.Flag(\"bucket\", \"The bucket of the GCS bucket\").Action(c.Bucket.Set).StringVar(&c.Bucket.Value)\n\tlogflags.CompressionCodec(c.CmdClause, &c.CompressionCodec)\n\tlogflags.Format(c.CmdClause, &c.Format)\n\tlogflags.FormatVersion(c.CmdClause, &c.FormatVersion)\n\tlogflags.GzipLevel(c.CmdClause, &c.GzipLevel)\n\tlogflags.MessageType(c.CmdClause, &c.MessageType)\n\tlogflags.Path(c.CmdClause, &c.Path)\n\tlogflags.Period(c.CmdClause, &c.Period)\n\tlogflags.Placement(c.CmdClause, &c.Placement)\n\tlogflags.ProcessingRegion(c.CmdClause, &c.ProcessingRegion, \"GCS\")\n\tc.CmdClause.Flag(\"project-id\", \"The google project ID\").Action(c.ProjectID.Set).StringVar(&c.ProjectID.Value)\n\tlogflags.ResponseCondition(c.CmdClause, &c.ResponseCondition)\n\tc.CmdClause.Flag(\"secret-key\", \"Your GCS account secret key. The private_key field in your service account authentication JSON\").Action(c.SecretKey.Set).StringVar(&c.SecretKey.Value)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.ServiceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.ServiceName.Value,\n\t})\n\tlogflags.TimestampFormat(c.CmdClause, &c.TimestampFormat)\n\tc.CmdClause.Flag(\"user\", \"Your GCS service account email address. The client_email field in your service account authentication JSON\").Action(c.User.Set).StringVar(&c.User.Value)\n\treturn &c\n}\n\n// ConstructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *CreateCommand) ConstructInput(serviceID string, serviceVersion int) (*fastly.CreateGCSInput, error) {\n\tinput := fastly.CreateGCSInput{\n\t\tServiceID:      serviceID,\n\t\tServiceVersion: serviceVersion,\n\t}\n\n\t// The following blocks enforces the mutual exclusivity of the\n\t// CompressionCodec and GzipLevel flags.\n\tif c.CompressionCodec.WasSet && c.GzipLevel.WasSet {\n\t\treturn nil, fmt.Errorf(\"error parsing arguments: the --compression-codec flag is mutually exclusive with the --gzip-level flag\")\n\t}\n\n\tif c.AccountName.WasSet {\n\t\tinput.AccountName = &c.AccountName.Value\n\t}\n\tif c.Bucket.WasSet {\n\t\tinput.Bucket = &c.Bucket.Value\n\t}\n\tif c.CompressionCodec.WasSet {\n\t\tinput.CompressionCodec = &c.CompressionCodec.Value\n\t}\n\tif c.EndpointName.WasSet {\n\t\tinput.Name = &c.EndpointName.Value\n\t}\n\tif c.Format.WasSet {\n\t\tinput.Format = fastly.ToPointer(argparser.Content(c.Format.Value))\n\t}\n\tif c.FormatVersion.WasSet {\n\t\tinput.FormatVersion = &c.FormatVersion.Value\n\t}\n\tif c.GzipLevel.WasSet {\n\t\tinput.GzipLevel = &c.GzipLevel.Value\n\t}\n\tif c.MessageType.WasSet {\n\t\tinput.MessageType = &c.MessageType.Value\n\t}\n\tif c.Path.WasSet {\n\t\tinput.Path = &c.Path.Value\n\t}\n\tif c.Period.WasSet {\n\t\tinput.Period = &c.Period.Value\n\t}\n\tif c.Placement.WasSet {\n\t\tinput.Placement = &c.Placement.Value\n\t}\n\tif c.ProcessingRegion.WasSet {\n\t\tinput.ProcessingRegion = &c.ProcessingRegion.Value\n\t}\n\tif c.ProjectID.WasSet {\n\t\tinput.ProjectID = &c.ProjectID.Value\n\t}\n\tif c.ResponseCondition.WasSet {\n\t\tinput.ResponseCondition = &c.ResponseCondition.Value\n\t}\n\tif c.SecretKey.WasSet {\n\t\tinput.SecretKey = &c.SecretKey.Value\n\t}\n\tif c.TimestampFormat.WasSet {\n\t\tinput.TimestampFormat = &c.TimestampFormat.Value\n\t}\n\tif c.User.WasSet {\n\t\tinput.User = &c.User.Value\n\t}\n\n\treturn &input, nil\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *CreateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(false),\n\t\tLocked:             optional.Of(false),\n\t\tAutoCloneFlag:      c.AutoClone,\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.ServiceName,\n\t\tServiceVersionFlag: c.ServiceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": errors.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tinput, err := c.ConstructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\td, err := c.Globals.APIClient.CreateGCS(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\ttext.Success(\n\t\tout,\n\t\t\"Created GCS logging endpoint %s (service %s version %d)\",\n\t\tfastly.ToValue(d.Name),\n\t\tfastly.ToValue(d.ServiceID),\n\t\tfastly.ToValue(d.ServiceVersion),\n\t)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/gcs/delete.go",
    "content": "package gcs\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DeleteCommand calls the Fastly API to delete a GCS logging endpoint.\ntype DeleteCommand struct {\n\targparser.Base\n\tInput          fastly.DeleteGCSInput\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n\tautoClone      argparser.OptionalAutoClone\n}\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"delete\", \"Delete a GCS logging endpoint on a Fastly service version\").Alias(\"remove\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"The name of the GCS logging object\").Short('n').Required().StringVar(&c.Input.Name)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.autoClone.Set,\n\t\tDst:    &c.autoClone.Value,\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DeleteCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(false),\n\t\tLocked:             optional.Of(false),\n\t\tAutoCloneFlag:      c.autoClone,\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": errors.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tc.Input.ServiceID = serviceID\n\tc.Input.ServiceVersion = fastly.ToValue(serviceVersion.Number)\n\n\tif err := c.Globals.APIClient.DeleteGCS(context.TODO(), &c.Input); err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Deleted GCS logging endpoint %s (service %s version %d)\", c.Input.Name, c.Input.ServiceID, c.Input.ServiceVersion)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/gcs/describe.go",
    "content": "package gcs\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DescribeCommand calls the Fastly API to describe a GCS logging endpoint.\ntype DescribeCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tInput          fastly.GetGCSInput\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n}\n\n// NewDescribeCommand returns a usable command registered under the parent.\nfunc NewDescribeCommand(parent argparser.Registerer, g *global.Data) *DescribeCommand {\n\tc := DescribeCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"describe\", \"Show detailed information about a GCS logging endpoint on a Fastly service version\").Alias(\"get\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"The name of the GCS logging object\").Short('n').Required().StringVar(&c.Input.Name)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DescribeCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fsterr.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tc.Input.ServiceID = serviceID\n\tc.Input.ServiceVersion = fastly.ToValue(serviceVersion.Number)\n\n\to, err := c.Globals.APIClient.GetGCS(context.TODO(), &c.Input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\tlines := text.Lines{\n\t\t\"Account name\":       fastly.ToValue(o.AccountName),\n\t\t\"Bucket\":             fastly.ToValue(o.Bucket),\n\t\t\"Compression codec\":  fastly.ToValue(o.CompressionCodec),\n\t\t\"Format version\":     fastly.ToValue(o.FormatVersion),\n\t\t\"Format\":             fastly.ToValue(o.Format),\n\t\t\"GZip level\":         fastly.ToValue(o.GzipLevel),\n\t\t\"Message type\":       fastly.ToValue(o.MessageType),\n\t\t\"Name\":               fastly.ToValue(o.Name),\n\t\t\"Path\":               fastly.ToValue(o.Path),\n\t\t\"Period\":             fastly.ToValue(o.Period),\n\t\t\"Processing region\":  fastly.ToValue(o.ProcessingRegion),\n\t\t\"Project ID\":         fastly.ToValue(o.ProjectID),\n\t\t\"Placement\":          fastly.ToValue(o.Placement),\n\t\t\"Response condition\": fastly.ToValue(o.ResponseCondition),\n\t\t\"Secret key\":         fastly.ToValue(o.SecretKey),\n\t\t\"Timestamp format\":   fastly.ToValue(o.TimestampFormat),\n\t\t\"User\":               fastly.ToValue(o.User),\n\t\t\"Version\":            fastly.ToValue(o.ServiceVersion),\n\t}\n\tif !c.Globals.Verbose() {\n\t\tlines[\"Service ID\"] = fastly.ToValue(o.ServiceID)\n\t}\n\ttext.PrintLines(out, lines)\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/gcs/doc.go",
    "content": "// Package gcs contains commands to inspect and manipulate Fastly service GCS\n// logging endpoints.\npackage gcs\n"
  },
  {
    "path": "pkg/commands/service/logging/gcs/gcs_integration_test.go",
    "content": "package gcs_test\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/mock\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n\n\troot \"github.com/fastly/cli/pkg/commands/service\"\n\tparent \"github.com/fastly/cli/pkg/commands/service/logging\"\n\tsub \"github.com/fastly/cli/pkg/commands/service/logging/gcs\"\n)\n\nfunc TestGCSCreate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name log --bucket log --user foo@example.com --secret-key foo --period 86400 --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tCreateGCSFn:    createGCSOK,\n\t\t\t},\n\t\t\tWantOutput: \"Created GCS logging endpoint log (service 123 version 4)\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name log --bucket log --account-name service-account-id --project-id gcp-prj-id --period 86400 --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tCreateGCSFn:    createGCSOK,\n\t\t\t},\n\t\t\tWantOutput: \"Created GCS logging endpoint log (service 123 version 4)\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name log --bucket log --user foo@example.com --secret-key foo --period 86400 --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tCreateGCSFn:    createGCSError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name log --bucket log --user foo@example.com --secret-key foo --period 86400 --compression-codec zstd --gzip-level 9 --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t},\n\t\t\tWantError: \"error parsing arguments: the --compression-codec flag is mutually exclusive with the --gzip-level flag\",\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"create\"}, scenarios)\n}\n\nfunc TestGCSList(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tListGCSsFn:   listGCSsOK,\n\t\t\t},\n\t\t\tWantOutput: listGCSsShortOutput,\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --verbose\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tListGCSsFn:   listGCSsOK,\n\t\t\t},\n\t\t\tWantOutput: listGCSsVerboseOutput,\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 -v\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tListGCSsFn:   listGCSsOK,\n\t\t\t},\n\t\t\tWantOutput: listGCSsVerboseOutput,\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tListGCSsFn:   listGCSsError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"list\"}, scenarios)\n}\n\nfunc TestGCSDescribe(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"--service-id 123 --version 1\",\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tGetGCSFn:     getGCSError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tGetGCSFn:     getGCSOK,\n\t\t\t},\n\t\t\tWantOutput: describeGCSOutput,\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"describe\"}, scenarios)\n}\n\nfunc TestGCSUpdate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"--service-id 123 --version 1 --new-name log\",\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs --new-name log --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tUpdateGCSFn:    updateGCSError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs --new-name log --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tUpdateGCSFn:    updateGCSOK,\n\t\t\t},\n\t\t\tWantOutput: \"Updated GCS logging endpoint log (service 123 version 4)\",\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"update\"}, scenarios)\n}\n\nfunc TestGCSDelete(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"--service-id 123 --version 1\",\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tDeleteGCSFn:    deleteGCSError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tDeleteGCSFn:    deleteGCSOK,\n\t\t\t},\n\t\t\tWantOutput: \"Deleted GCS logging endpoint logs (service 123 version 4)\",\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"delete\"}, scenarios)\n}\n\nvar errTest = errors.New(\"fixture error\")\n\nfunc createGCSOK(_ context.Context, i *fastly.CreateGCSInput) (*fastly.GCS, error) {\n\treturn &fastly.GCS{\n\t\tServiceID:      fastly.ToPointer(i.ServiceID),\n\t\tServiceVersion: fastly.ToPointer(i.ServiceVersion),\n\t\tName:           i.Name,\n\t}, nil\n}\n\nfunc createGCSError(_ context.Context, _ *fastly.CreateGCSInput) (*fastly.GCS, error) {\n\treturn nil, errTest\n}\n\nfunc listGCSsOK(_ context.Context, i *fastly.ListGCSsInput) ([]*fastly.GCS, error) {\n\treturn []*fastly.GCS{\n\t\t{\n\t\t\tServiceID:         fastly.ToPointer(i.ServiceID),\n\t\t\tServiceVersion:    fastly.ToPointer(i.ServiceVersion),\n\t\t\tName:              fastly.ToPointer(\"logs\"),\n\t\t\tBucket:            fastly.ToPointer(\"my-logs\"),\n\t\t\tUser:              fastly.ToPointer(\"foo@example.com\"),\n\t\t\tAccountName:       fastly.ToPointer(\"me@fastly.com\"),\n\t\t\tSecretKey:         fastly.ToPointer(\"-----BEGIN RSA PRIVATE KEY-----foo\"),\n\t\t\tPath:              fastly.ToPointer(\"logs/\"),\n\t\t\tPeriod:            fastly.ToPointer(3600),\n\t\t\tGzipLevel:         fastly.ToPointer(0),\n\t\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\t\tFormatVersion:     fastly.ToPointer(2),\n\t\t\tMessageType:       fastly.ToPointer(\"classic\"),\n\t\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\t\tTimestampFormat:   fastly.ToPointer(\"%Y-%m-%dT%H:%M:%S.000\"),\n\t\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\t\tCompressionCodec:  fastly.ToPointer(\"zstd\"),\n\t\t\tProcessingRegion:  fastly.ToPointer(\"us\"),\n\t\t},\n\t\t{\n\t\t\tServiceID:         fastly.ToPointer(i.ServiceID),\n\t\t\tServiceVersion:    fastly.ToPointer(i.ServiceVersion),\n\t\t\tName:              fastly.ToPointer(\"analytics\"),\n\t\t\tBucket:            fastly.ToPointer(\"analytics\"),\n\t\t\tUser:              fastly.ToPointer(\"foo@example.com\"),\n\t\t\tAccountName:       fastly.ToPointer(\"me@fastly.com\"),\n\t\t\tSecretKey:         fastly.ToPointer(\"-----BEGIN RSA PRIVATE KEY-----foo\"),\n\t\t\tPath:              fastly.ToPointer(\"logs/\"),\n\t\t\tPeriod:            fastly.ToPointer(86400),\n\t\t\tGzipLevel:         fastly.ToPointer(0),\n\t\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\t\tFormatVersion:     fastly.ToPointer(2),\n\t\t\tMessageType:       fastly.ToPointer(\"classic\"),\n\t\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\t\tTimestampFormat:   fastly.ToPointer(\"%Y-%m-%dT%H:%M:%S.000\"),\n\t\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\t\tCompressionCodec:  fastly.ToPointer(\"zstd\"),\n\t\t\tProcessingRegion:  fastly.ToPointer(\"us\"),\n\t\t},\n\t}, nil\n}\n\nfunc listGCSsError(_ context.Context, _ *fastly.ListGCSsInput) ([]*fastly.GCS, error) {\n\treturn nil, errTest\n}\n\nvar listGCSsShortOutput = strings.TrimSpace(`\nSERVICE  VERSION  NAME\n123      1        logs\n123      1        analytics\n`) + \"\\n\"\n\nvar listGCSsVerboseOutput = strings.TrimSpace(`\nFastly API endpoint: https://api.fastly.com\nFastly API token provided via config file (auth: user)\n\nService ID (via --service-id): 123\n\nVersion: 1\n\tGCS 1/2\n\t\tService ID: 123\n\t\tVersion: 1\n\t\tName: logs\n\t\tBucket: my-logs\n\t\tUser: foo@example.com\n\t\tAccount name: me@fastly.com\n\t\tSecret key: -----BEGIN RSA PRIVATE KEY-----foo\n\t\tPath: logs/\n\t\tPeriod: 3600\n\t\tGZip level: 0\n\t\tFormat: %h %l %u %t \"%r\" %>s %b\n\t\tFormat version: 2\n\t\tResponse condition: Prevent default logging\n\t\tMessage type: classic\n\t\tTimestamp format: %Y-%m-%dT%H:%M:%S.000\n\t\tPlacement: none\n\t\tCompression codec: zstd\n\t\tProcessing region: us\n\tGCS 2/2\n\t\tService ID: 123\n\t\tVersion: 1\n\t\tName: analytics\n\t\tBucket: analytics\n\t\tUser: foo@example.com\n\t\tAccount name: me@fastly.com\n\t\tSecret key: -----BEGIN RSA PRIVATE KEY-----foo\n\t\tPath: logs/\n\t\tPeriod: 86400\n\t\tGZip level: 0\n\t\tFormat: %h %l %u %t \"%r\" %>s %b\n\t\tFormat version: 2\n\t\tResponse condition: Prevent default logging\n\t\tMessage type: classic\n\t\tTimestamp format: %Y-%m-%dT%H:%M:%S.000\n\t\tPlacement: none\n\t\tCompression codec: zstd\n\t\tProcessing region: us\n`) + \"\\n\\n\"\n\nfunc getGCSOK(_ context.Context, i *fastly.GetGCSInput) (*fastly.GCS, error) {\n\treturn &fastly.GCS{\n\t\tServiceID:         fastly.ToPointer(i.ServiceID),\n\t\tServiceVersion:    fastly.ToPointer(i.ServiceVersion),\n\t\tName:              fastly.ToPointer(\"logs\"),\n\t\tBucket:            fastly.ToPointer(\"my-logs\"),\n\t\tUser:              fastly.ToPointer(\"foo@example.com\"),\n\t\tSecretKey:         fastly.ToPointer(\"-----BEGIN RSA PRIVATE KEY-----foo\"),\n\t\tAccountName:       fastly.ToPointer(\"me@fastly.com\"),\n\t\tPath:              fastly.ToPointer(\"logs/\"),\n\t\tPeriod:            fastly.ToPointer(3600),\n\t\tGzipLevel:         fastly.ToPointer(0),\n\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\tFormatVersion:     fastly.ToPointer(2),\n\t\tMessageType:       fastly.ToPointer(\"classic\"),\n\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\tTimestampFormat:   fastly.ToPointer(\"%Y-%m-%dT%H:%M:%S.000\"),\n\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\tCompressionCodec:  fastly.ToPointer(\"zstd\"),\n\t\tProcessingRegion:  fastly.ToPointer(\"us\"),\n\t}, nil\n}\n\nfunc getGCSError(_ context.Context, _ *fastly.GetGCSInput) (*fastly.GCS, error) {\n\treturn nil, errTest\n}\n\nvar describeGCSOutput = \"\\n\" + strings.TrimSpace(`\nAccount name: me@fastly.com\nBucket: my-logs\nCompression codec: zstd\nFormat: %h %l %u %t \"%r\" %>s %b\nFormat version: 2\nGZip level: 0\nMessage type: classic\nName: logs\nPath: logs/\nPeriod: 3600\nPlacement: none\nProcessing region: us\nProject ID: \nResponse condition: Prevent default logging\nSecret key: -----BEGIN RSA PRIVATE KEY-----foo\nService ID: 123\nTimestamp format: %Y-%m-%dT%H:%M:%S.000\nUser: foo@example.com\nVersion: 1\n`) + \"\\n\"\n\nfunc updateGCSOK(_ context.Context, i *fastly.UpdateGCSInput) (*fastly.GCS, error) {\n\treturn &fastly.GCS{\n\t\tServiceID:         fastly.ToPointer(i.ServiceID),\n\t\tServiceVersion:    fastly.ToPointer(i.ServiceVersion),\n\t\tName:              fastly.ToPointer(\"log\"),\n\t\tBucket:            fastly.ToPointer(\"logs\"),\n\t\tUser:              fastly.ToPointer(\"foo@example.com\"),\n\t\tSecretKey:         fastly.ToPointer(\"-----BEGIN RSA PRIVATE KEY-----foo\"),\n\t\tPath:              fastly.ToPointer(\"logs/\"),\n\t\tPeriod:            fastly.ToPointer(3600),\n\t\tGzipLevel:         fastly.ToPointer(0),\n\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\tFormatVersion:     fastly.ToPointer(2),\n\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\tMessageType:       fastly.ToPointer(\"classic\"),\n\t\tTimestampFormat:   fastly.ToPointer(\"%Y-%m-%dT%H:%M:%S.000\"),\n\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\tCompressionCodec:  fastly.ToPointer(\"zstd\"),\n\t}, nil\n}\n\nfunc updateGCSError(_ context.Context, _ *fastly.UpdateGCSInput) (*fastly.GCS, error) {\n\treturn nil, errTest\n}\n\nfunc deleteGCSOK(_ context.Context, _ *fastly.DeleteGCSInput) error {\n\treturn nil\n}\n\nfunc deleteGCSError(_ context.Context, _ *fastly.DeleteGCSInput) error {\n\treturn errTest\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/gcs/gcs_test.go",
    "content": "package gcs_test\n\nimport (\n\t\"bytes\"\n\t\"testing\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/service/logging/gcs\"\n\t\"github.com/fastly/cli/pkg/config\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/manifest\"\n\t\"github.com/fastly/cli/pkg/mock\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n)\n\nfunc TestCreateGCSInput(t *testing.T) {\n\tfor _, testcase := range []struct {\n\t\tname      string\n\t\tcmd       *gcs.CreateCommand\n\t\twant      *fastly.CreateGCSInput\n\t\twantError string\n\t}{\n\t\t{\n\t\t\tname: \"required values set flag serviceID\",\n\t\t\tcmd:  createCommandRequired(),\n\t\t\twant: &fastly.CreateGCSInput{\n\t\t\t\tServiceID:      \"123\",\n\t\t\t\tServiceVersion: 4,\n\t\t\t\tName:           fastly.ToPointer(\"log\"),\n\t\t\t\tBucket:         fastly.ToPointer(\"bucket\"),\n\t\t\t\tUser:           fastly.ToPointer(\"user\"),\n\t\t\t\tSecretKey:      fastly.ToPointer(\"-----BEGIN PRIVATE KEY-----foo\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"all values set flag serviceID\",\n\t\t\tcmd:  createCommandAll(),\n\t\t\twant: &fastly.CreateGCSInput{\n\t\t\t\tServiceID:         \"123\",\n\t\t\t\tServiceVersion:    4,\n\t\t\t\tName:              fastly.ToPointer(\"log\"),\n\t\t\t\tBucket:            fastly.ToPointer(\"bucket\"),\n\t\t\t\tUser:              fastly.ToPointer(\"user\"),\n\t\t\t\tSecretKey:         fastly.ToPointer(\"-----BEGIN PRIVATE KEY-----foo\"),\n\t\t\t\tPath:              fastly.ToPointer(\"/logs\"),\n\t\t\t\tPeriod:            fastly.ToPointer(3600),\n\t\t\t\tFormatVersion:     fastly.ToPointer(2),\n\t\t\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\t\t\tMessageType:       fastly.ToPointer(\"classic\"),\n\t\t\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\t\t\tTimestampFormat:   fastly.ToPointer(\"%Y-%m-%dT%H:%M:%S.000\"),\n\t\t\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\t\t\tCompressionCodec:  fastly.ToPointer(\"zstd\"),\n\t\t\t\tProcessingRegion:  fastly.ToPointer(\"eu\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:      \"error missing serviceID\",\n\t\t\tcmd:       createCommandMissingServiceID(),\n\t\t\twant:      nil,\n\t\t\twantError: errors.ErrNoServiceID.Error(),\n\t\t},\n\t} {\n\t\tt.Run(testcase.name, func(t *testing.T) {\n\t\t\tif testcase.wantError == errors.ErrNoServiceID.Error() {\n\t\t\t\tt.Setenv(\"FASTLY_SERVICE_ID\", \"\")\n\t\t\t}\n\t\t\tvar bs []byte\n\t\t\tout := bytes.NewBuffer(bs)\n\t\t\tverboseMode := true\n\n\t\t\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\t\t\tAutoCloneFlag:      testcase.cmd.AutoClone,\n\t\t\t\tAPIClient:          testcase.cmd.Globals.APIClient,\n\t\t\t\tManifest:           testcase.cmd.Manifest,\n\t\t\t\tOut:                out,\n\t\t\t\tServiceVersionFlag: testcase.cmd.ServiceVersion,\n\t\t\t\tVerboseMode:        verboseMode,\n\t\t\t})\n\n\t\t\tswitch {\n\t\t\tcase err != nil && testcase.wantError == \"\":\n\t\t\t\tt.Fatalf(\"unexpected error getting service details: %v\", err)\n\t\t\t\treturn\n\t\t\tcase err != nil && testcase.wantError != \"\":\n\t\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\t\treturn\n\t\t\tcase err == nil && testcase.wantError != \"\":\n\t\t\t\tt.Fatalf(\"expected error, have nil (service details: %s, %d)\", serviceID, serviceVersion.Number)\n\t\t\tcase err == nil && testcase.wantError == \"\":\n\t\t\t\thave, err := testcase.cmd.ConstructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\t\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\t\ttestutil.AssertEqual(t, testcase.want, have)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestUpdateGCSInput(t *testing.T) {\n\tscenarios := []struct {\n\t\tname      string\n\t\tcmd       *gcs.UpdateCommand\n\t\tapi       mock.API\n\t\twant      *fastly.UpdateGCSInput\n\t\twantError string\n\t}{\n\t\t{\n\t\t\tname: \"no updates\",\n\t\t\tcmd:  updateCommandNoUpdates(),\n\t\t\tapi: mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tGetGCSFn:       getGCSOK,\n\t\t\t},\n\t\t\twant: &fastly.UpdateGCSInput{\n\t\t\t\tServiceID:      \"123\",\n\t\t\t\tServiceVersion: 4,\n\t\t\t\tName:           \"log\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"all values set flag serviceID\",\n\t\t\tcmd:  updateCommandAll(),\n\t\t\tapi: mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tGetGCSFn:       getGCSOK,\n\t\t\t},\n\t\t\twant: &fastly.UpdateGCSInput{\n\t\t\t\tServiceID:         \"123\",\n\t\t\t\tServiceVersion:    4,\n\t\t\t\tName:              \"log\",\n\t\t\t\tNewName:           fastly.ToPointer(\"new1\"),\n\t\t\t\tBucket:            fastly.ToPointer(\"new2\"),\n\t\t\t\tUser:              fastly.ToPointer(\"new3\"),\n\t\t\t\tSecretKey:         fastly.ToPointer(\"new4\"),\n\t\t\t\tPath:              fastly.ToPointer(\"new5\"),\n\t\t\t\tPeriod:            fastly.ToPointer(3601),\n\t\t\t\tFormatVersion:     fastly.ToPointer(3),\n\t\t\t\tGzipLevel:         fastly.ToPointer(0),\n\t\t\t\tFormat:            fastly.ToPointer(\"new6\"),\n\t\t\t\tResponseCondition: fastly.ToPointer(\"new7\"),\n\t\t\t\tTimestampFormat:   fastly.ToPointer(\"new8\"),\n\t\t\t\tPlacement:         fastly.ToPointer(\"new9\"),\n\t\t\t\tMessageType:       fastly.ToPointer(\"new10\"),\n\t\t\t\tCompressionCodec:  fastly.ToPointer(\"new11\"),\n\t\t\t\tProcessingRegion:  fastly.ToPointer(\"eu\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:      \"error missing serviceID\",\n\t\t\tcmd:       updateCommandMissingServiceID(),\n\t\t\twant:      nil,\n\t\t\twantError: errors.ErrNoServiceID.Error(),\n\t\t},\n\t}\n\tfor testcaseIdx := range scenarios {\n\t\ttestcase := &scenarios[testcaseIdx]\n\t\tt.Run(testcase.name, func(t *testing.T) {\n\t\t\tif testcase.wantError == errors.ErrNoServiceID.Error() {\n\t\t\t\tt.Setenv(\"FASTLY_SERVICE_ID\", \"\")\n\t\t\t}\n\t\t\ttestcase.cmd.Globals.APIClient = testcase.api\n\n\t\t\tvar bs []byte\n\t\t\tout := bytes.NewBuffer(bs)\n\t\t\tverboseMode := true\n\n\t\t\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\t\t\tAutoCloneFlag:      testcase.cmd.AutoClone,\n\t\t\t\tAPIClient:          testcase.api,\n\t\t\t\tManifest:           testcase.cmd.Manifest,\n\t\t\t\tOut:                out,\n\t\t\t\tServiceVersionFlag: testcase.cmd.ServiceVersion,\n\t\t\t\tVerboseMode:        verboseMode,\n\t\t\t})\n\n\t\t\tswitch {\n\t\t\tcase err != nil && testcase.wantError == \"\":\n\t\t\t\tt.Fatalf(\"unexpected error getting service details: %v\", err)\n\t\t\t\treturn\n\t\t\tcase err != nil && testcase.wantError != \"\":\n\t\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\t\treturn\n\t\t\tcase err == nil && testcase.wantError != \"\":\n\t\t\t\tt.Fatalf(\"expected error, have nil (service details: %s, %d)\", serviceID, serviceVersion.Number)\n\t\t\tcase err == nil && testcase.wantError == \"\":\n\t\t\t\thave, err := testcase.cmd.ConstructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\t\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\t\ttestutil.AssertEqual(t, testcase.want, have)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc createCommandRequired() *gcs.CreateCommand {\n\tvar b bytes.Buffer\n\n\tg := global.Data{\n\t\tConfig: config.File{},\n\t\tEnv:    config.Environment{},\n\t\tOutput: &b,\n\t}\n\tg.APIClient, _ = mock.APIClient(mock.API{\n\t\tGetVersionFn:   testutil.GetVersion,\n\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t})(\"token\", \"endpoint\", false)\n\n\treturn &gcs.CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: &g,\n\t\t},\n\t\tManifest: manifest.Data{\n\t\t\tFlag: manifest.Flag{\n\t\t\t\tServiceID: \"123\",\n\t\t\t},\n\t\t},\n\t\tServiceVersion: argparser.OptionalServiceVersion{\n\t\t\tOptionalString: argparser.OptionalString{Value: \"1\"},\n\t\t},\n\t\tAutoClone: argparser.OptionalAutoClone{\n\t\t\tOptionalBool: argparser.OptionalBool{\n\t\t\t\tOptional: argparser.Optional{\n\t\t\t\t\tWasSet: true,\n\t\t\t\t},\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t},\n\t\tEndpointName: argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"log\"},\n\t\tBucket:       argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"bucket\"},\n\t\tUser:         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"user\"},\n\t\tSecretKey:    argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"-----BEGIN PRIVATE KEY-----foo\"},\n\t}\n}\n\nfunc createCommandAll() *gcs.CreateCommand {\n\tvar b bytes.Buffer\n\n\tg := global.Data{\n\t\tConfig: config.File{},\n\t\tEnv:    config.Environment{},\n\t\tOutput: &b,\n\t}\n\tg.APIClient, _ = mock.APIClient(mock.API{\n\t\tGetVersionFn:   testutil.GetVersion,\n\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t})(\"token\", \"endpoint\", false)\n\n\treturn &gcs.CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: &g,\n\t\t},\n\t\tManifest: manifest.Data{\n\t\t\tFlag: manifest.Flag{\n\t\t\t\tServiceID: \"123\",\n\t\t\t},\n\t\t},\n\t\tServiceVersion: argparser.OptionalServiceVersion{\n\t\t\tOptionalString: argparser.OptionalString{Value: \"1\"},\n\t\t},\n\t\tAutoClone: argparser.OptionalAutoClone{\n\t\t\tOptionalBool: argparser.OptionalBool{\n\t\t\t\tOptional: argparser.Optional{\n\t\t\t\t\tWasSet: true,\n\t\t\t\t},\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t},\n\t\tEndpointName:      argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"log\"},\n\t\tBucket:            argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"bucket\"},\n\t\tUser:              argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"user\"},\n\t\tSecretKey:         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"-----BEGIN PRIVATE KEY-----foo\"},\n\t\tPath:              argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"/logs\"},\n\t\tPeriod:            argparser.OptionalInt{Optional: argparser.Optional{WasSet: true}, Value: 3600},\n\t\tFormat:            argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: `%h %l %u %t \"%r\" %>s %b`},\n\t\tFormatVersion:     argparser.OptionalInt{Optional: argparser.Optional{WasSet: true}, Value: 2},\n\t\tTimestampFormat:   argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"%Y-%m-%dT%H:%M:%S.000\"},\n\t\tMessageType:       argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"classic\"},\n\t\tResponseCondition: argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"Prevent default logging\"},\n\t\tPlacement:         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"none\"},\n\t\tCompressionCodec:  argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"zstd\"},\n\t\tProcessingRegion:  argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"eu\"},\n\t}\n}\n\nfunc createCommandMissingServiceID() *gcs.CreateCommand {\n\tres := createCommandAll()\n\tres.Manifest = manifest.Data{}\n\tres.ServiceVersion = argparser.OptionalServiceVersion{}\n\treturn res\n}\n\nfunc updateCommandNoUpdates() *gcs.UpdateCommand {\n\tvar b bytes.Buffer\n\n\tg := global.Data{\n\t\tConfig: config.File{},\n\t\tEnv:    config.Environment{},\n\t\tOutput: &b,\n\t}\n\n\treturn &gcs.UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: &g,\n\t\t},\n\t\tManifest: manifest.Data{\n\t\t\tFlag: manifest.Flag{\n\t\t\t\tServiceID: \"123\",\n\t\t\t},\n\t\t},\n\t\tEndpointName: \"log\",\n\t\tServiceVersion: argparser.OptionalServiceVersion{\n\t\t\tOptionalString: argparser.OptionalString{Value: \"1\"},\n\t\t},\n\t\tAutoClone: argparser.OptionalAutoClone{\n\t\t\tOptionalBool: argparser.OptionalBool{\n\t\t\t\tOptional: argparser.Optional{\n\t\t\t\t\tWasSet: true,\n\t\t\t\t},\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t},\n\t}\n}\n\nfunc updateCommandAll() *gcs.UpdateCommand {\n\tvar b bytes.Buffer\n\n\tg := global.Data{\n\t\tConfig: config.File{},\n\t\tEnv:    config.Environment{},\n\t\tOutput: &b,\n\t}\n\n\treturn &gcs.UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: &g,\n\t\t},\n\t\tManifest: manifest.Data{\n\t\t\tFlag: manifest.Flag{\n\t\t\t\tServiceID: \"123\",\n\t\t\t},\n\t\t},\n\t\tEndpointName: \"log\",\n\t\tServiceVersion: argparser.OptionalServiceVersion{\n\t\t\tOptionalString: argparser.OptionalString{Value: \"1\"},\n\t\t},\n\t\tAutoClone: argparser.OptionalAutoClone{\n\t\t\tOptionalBool: argparser.OptionalBool{\n\t\t\t\tOptional: argparser.Optional{\n\t\t\t\t\tWasSet: true,\n\t\t\t\t},\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t},\n\t\tNewName:           argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new1\"},\n\t\tBucket:            argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new2\"},\n\t\tUser:              argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new3\"},\n\t\tSecretKey:         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new4\"},\n\t\tPath:              argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new5\"},\n\t\tPeriod:            argparser.OptionalInt{Optional: argparser.Optional{WasSet: true}, Value: 3601},\n\t\tGzipLevel:         argparser.OptionalInt{Optional: argparser.Optional{WasSet: true}, Value: 0},\n\t\tFormat:            argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new6\"},\n\t\tFormatVersion:     argparser.OptionalInt{Optional: argparser.Optional{WasSet: true}, Value: 3},\n\t\tResponseCondition: argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new7\"},\n\t\tTimestampFormat:   argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new8\"},\n\t\tPlacement:         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new9\"},\n\t\tMessageType:       argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new10\"},\n\t\tCompressionCodec:  argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new11\"},\n\t\tProcessingRegion:  argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"eu\"},\n\t}\n}\n\nfunc updateCommandMissingServiceID() *gcs.UpdateCommand {\n\tres := updateCommandAll()\n\tres.Manifest = manifest.Data{}\n\tres.ServiceVersion = argparser.OptionalServiceVersion{}\n\treturn res\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/gcs/list.go",
    "content": "package gcs\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// ListCommand calls the Fastly API to list GCS logging endpoints.\ntype ListCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tInput          fastly.ListGCSsInput\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n}\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"list\", \"List GCS endpoints on a Fastly service version\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *ListCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fsterr.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tc.Input.ServiceID = serviceID\n\tc.Input.ServiceVersion = fastly.ToValue(serviceVersion.Number)\n\n\to, err := c.Globals.APIClient.ListGCSs(context.TODO(), &c.Input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\tif !c.Globals.Verbose() {\n\t\ttw := text.NewTable(out)\n\t\ttw.AddHeader(\"SERVICE\", \"VERSION\", \"NAME\")\n\t\tfor _, gcs := range o {\n\t\t\ttw.AddLine(\n\t\t\t\tfastly.ToValue(gcs.ServiceID),\n\t\t\t\tfastly.ToValue(gcs.ServiceVersion),\n\t\t\t\tfastly.ToValue(gcs.Name),\n\t\t\t)\n\t\t}\n\t\ttw.Print()\n\t\treturn nil\n\t}\n\n\tfmt.Fprintf(out, \"Version: %d\\n\", c.Input.ServiceVersion)\n\tfor i, gcs := range o {\n\t\tfmt.Fprintf(out, \"\\tGCS %d/%d\\n\", i+1, len(o))\n\t\tfmt.Fprintf(out, \"\\t\\tService ID: %s\\n\", fastly.ToValue(gcs.ServiceID))\n\t\tfmt.Fprintf(out, \"\\t\\tVersion: %d\\n\", fastly.ToValue(gcs.ServiceVersion))\n\t\tfmt.Fprintf(out, \"\\t\\tName: %s\\n\", fastly.ToValue(gcs.Name))\n\t\tfmt.Fprintf(out, \"\\t\\tBucket: %s\\n\", fastly.ToValue(gcs.Bucket))\n\t\tfmt.Fprintf(out, \"\\t\\tUser: %s\\n\", fastly.ToValue(gcs.User))\n\t\tfmt.Fprintf(out, \"\\t\\tAccount name: %s\\n\", fastly.ToValue(gcs.AccountName))\n\t\tfmt.Fprintf(out, \"\\t\\tSecret key: %s\\n\", fastly.ToValue(gcs.SecretKey))\n\t\tfmt.Fprintf(out, \"\\t\\tPath: %s\\n\", fastly.ToValue(gcs.Path))\n\t\tfmt.Fprintf(out, \"\\t\\tPeriod: %d\\n\", fastly.ToValue(gcs.Period))\n\t\tfmt.Fprintf(out, \"\\t\\tGZip level: %d\\n\", fastly.ToValue(gcs.GzipLevel))\n\t\tfmt.Fprintf(out, \"\\t\\tFormat: %s\\n\", fastly.ToValue(gcs.Format))\n\t\tfmt.Fprintf(out, \"\\t\\tFormat version: %d\\n\", fastly.ToValue(gcs.FormatVersion))\n\t\tfmt.Fprintf(out, \"\\t\\tResponse condition: %s\\n\", fastly.ToValue(gcs.ResponseCondition))\n\t\tfmt.Fprintf(out, \"\\t\\tMessage type: %s\\n\", fastly.ToValue(gcs.MessageType))\n\t\tfmt.Fprintf(out, \"\\t\\tTimestamp format: %s\\n\", fastly.ToValue(gcs.TimestampFormat))\n\t\tfmt.Fprintf(out, \"\\t\\tPlacement: %s\\n\", fastly.ToValue(gcs.Placement))\n\t\tfmt.Fprintf(out, \"\\t\\tCompression codec: %s\\n\", fastly.ToValue(gcs.CompressionCodec))\n\t\tfmt.Fprintf(out, \"\\t\\tProcessing region: %s\\n\", fastly.ToValue(gcs.ProcessingRegion))\n\t}\n\tfmt.Fprintln(out)\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/gcs/root.go",
    "content": "package gcs\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"gcs\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manipulate Fastly service version GCS logging endpoints\")\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/gcs/update.go",
    "content": "package gcs\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/service/logging/logflags\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/manifest\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// UpdateCommand calls the Fastly API to update a GCS logging endpoint.\ntype UpdateCommand struct {\n\targparser.Base\n\tManifest manifest.Data\n\n\t// Required.\n\tEndpointName   string // Can't shadow argparser.Base method Name().\n\tServiceName    argparser.OptionalServiceNameID\n\tServiceVersion argparser.OptionalServiceVersion\n\n\t// Optional.\n\tAccountName       argparser.OptionalString\n\tAutoClone         argparser.OptionalAutoClone\n\tBucket            argparser.OptionalString\n\tCompressionCodec  argparser.OptionalString\n\tFormat            argparser.OptionalString\n\tFormatVersion     argparser.OptionalInt\n\tGzipLevel         argparser.OptionalInt\n\tMessageType       argparser.OptionalString\n\tNewName           argparser.OptionalString\n\tPath              argparser.OptionalString\n\tPeriod            argparser.OptionalInt\n\tPlacement         argparser.OptionalString\n\tProcessingRegion  argparser.OptionalString\n\tProjectID         argparser.OptionalString\n\tResponseCondition argparser.OptionalString\n\tSecretKey         argparser.OptionalString\n\tTimestampFormat   argparser.OptionalString\n\tUser              argparser.OptionalString\n}\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"update\", \"Update a GCS logging endpoint on a Fastly service version\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"The name of the GCS logging object\").Short('n').Required().StringVar(&c.EndpointName)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.ServiceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tlogflags.AccountName(c.CmdClause, &c.AccountName)\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.AutoClone.Set,\n\t\tDst:    &c.AutoClone.Value,\n\t})\n\tc.CmdClause.Flag(\"bucket\", \"The bucket of the GCS bucket\").Action(c.Bucket.Set).StringVar(&c.Bucket.Value)\n\tlogflags.CompressionCodec(c.CmdClause, &c.CompressionCodec)\n\tlogflags.Format(c.CmdClause, &c.Format)\n\tlogflags.FormatVersion(c.CmdClause, &c.FormatVersion)\n\tlogflags.GzipLevel(c.CmdClause, &c.GzipLevel)\n\tlogflags.MessageType(c.CmdClause, &c.MessageType)\n\tc.CmdClause.Flag(\"new-name\", \"New name of the GCS logging object\").Action(c.NewName.Set).StringVar(&c.NewName.Value)\n\tc.CmdClause.Flag(\"path\", \"The path to upload logs to (default '/')\").Action(c.Path.Set).StringVar(&c.Path.Value)\n\tlogflags.Period(c.CmdClause, &c.Period)\n\tlogflags.Placement(c.CmdClause, &c.Placement)\n\tlogflags.ProcessingRegion(c.CmdClause, &c.ProcessingRegion, \"GCS\")\n\tc.CmdClause.Flag(\"project-id\", \"The google project ID\").Action(c.ProjectID.Set).StringVar(&c.ProjectID.Value)\n\tlogflags.ResponseCondition(c.CmdClause, &c.ResponseCondition)\n\tc.CmdClause.Flag(\"secret-key\", \"Your GCS account secret key. The private_key field in your service account authentication JSON\").Action(c.SecretKey.Set).StringVar(&c.SecretKey.Value)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.ServiceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.ServiceName.Value,\n\t})\n\tc.CmdClause.Flag(\"user\", \"Your GCS service account email address. The client_email field in your service account authentication JSON\").Action(c.User.Set).StringVar(&c.User.Value)\n\tlogflags.TimestampFormat(c.CmdClause, &c.TimestampFormat)\n\treturn &c\n}\n\n// ConstructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *UpdateCommand) ConstructInput(serviceID string, serviceVersion int) (*fastly.UpdateGCSInput, error) {\n\tinput := fastly.UpdateGCSInput{\n\t\tServiceID:      serviceID,\n\t\tServiceVersion: serviceVersion,\n\t\tName:           c.EndpointName,\n\t}\n\n\tif c.AccountName.WasSet {\n\t\tinput.AccountName = &c.AccountName.Value\n\t}\n\tif c.Bucket.WasSet {\n\t\tinput.Bucket = &c.Bucket.Value\n\t}\n\tif c.CompressionCodec.WasSet {\n\t\tinput.CompressionCodec = &c.CompressionCodec.Value\n\t}\n\tif c.Format.WasSet {\n\t\tinput.Format = fastly.ToPointer(argparser.Content(c.Format.Value))\n\t}\n\tif c.FormatVersion.WasSet {\n\t\tinput.FormatVersion = &c.FormatVersion.Value\n\t}\n\tif c.GzipLevel.WasSet {\n\t\tinput.GzipLevel = &c.GzipLevel.Value\n\t}\n\tif c.MessageType.WasSet {\n\t\tinput.MessageType = &c.MessageType.Value\n\t}\n\tif c.NewName.WasSet {\n\t\tinput.NewName = &c.NewName.Value\n\t}\n\tif c.Path.WasSet {\n\t\tinput.Path = &c.Path.Value\n\t}\n\tif c.Period.WasSet {\n\t\tinput.Period = &c.Period.Value\n\t}\n\tif c.Placement.WasSet {\n\t\tinput.Placement = &c.Placement.Value\n\t}\n\tif c.ProcessingRegion.WasSet {\n\t\tinput.ProcessingRegion = &c.ProcessingRegion.Value\n\t}\n\tif c.ProjectID.WasSet {\n\t\tinput.ProjectID = &c.ProjectID.Value\n\t}\n\tif c.ResponseCondition.WasSet {\n\t\tinput.ResponseCondition = &c.ResponseCondition.Value\n\t}\n\tif c.SecretKey.WasSet {\n\t\tinput.SecretKey = &c.SecretKey.Value\n\t}\n\tif c.TimestampFormat.WasSet {\n\t\tinput.TimestampFormat = &c.TimestampFormat.Value\n\t}\n\tif c.User.WasSet {\n\t\tinput.User = &c.User.Value\n\t}\n\n\treturn &input, nil\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *UpdateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(false),\n\t\tLocked:             optional.Of(false),\n\t\tAutoCloneFlag:      c.AutoClone,\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.ServiceName,\n\t\tServiceVersionFlag: c.ServiceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": errors.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tinput, err := c.ConstructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tgcs, err := c.Globals.APIClient.UpdateGCS(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\ttext.Success(\n\t\tout,\n\t\t\"Updated GCS logging endpoint %s (service %s version %d)\",\n\t\tfastly.ToValue(gcs.Name),\n\t\tfastly.ToValue(gcs.ServiceID),\n\t\tfastly.ToValue(gcs.ServiceVersion),\n\t)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/googlepubsub/create.go",
    "content": "package googlepubsub\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/service/logging/logflags\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/manifest\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// CreateCommand calls the Fastly API to create a Google Cloud Pub/Sub logging endpoint.\ntype CreateCommand struct {\n\targparser.Base\n\tManifest manifest.Data\n\n\t// Required.\n\tServiceName    argparser.OptionalServiceNameID\n\tServiceVersion argparser.OptionalServiceVersion\n\n\t// Optional.\n\tAccountName       argparser.OptionalString\n\tAutoClone         argparser.OptionalAutoClone\n\tEndpointName      argparser.OptionalString // Can't shadow argparser.Base method Name().\n\tFormat            argparser.OptionalString\n\tFormatVersion     argparser.OptionalInt\n\tPlacement         argparser.OptionalString\n\tProcessingRegion  argparser.OptionalString\n\tProjectID         argparser.OptionalString\n\tResponseCondition argparser.OptionalString\n\tSecretKey         argparser.OptionalString\n\tTopic             argparser.OptionalString\n\tUser              argparser.OptionalString\n}\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"create\", \"Create a Google Cloud Pub/Sub logging endpoint on a Fastly service version\").Alias(\"add\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"The name of the Google Cloud Pub/Sub logging object. Used as a primary key for API access\").Short('n').Action(c.EndpointName.Set).StringVar(&c.EndpointName.Value)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.ServiceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tlogflags.AccountName(c.CmdClause, &c.AccountName)\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.AutoClone.Set,\n\t\tDst:    &c.AutoClone.Value,\n\t})\n\tlogflags.Format(c.CmdClause, &c.Format)\n\tlogflags.FormatVersion(c.CmdClause, &c.FormatVersion)\n\tlogflags.Placement(c.CmdClause, &c.Placement)\n\tlogflags.ProcessingRegion(c.CmdClause, &c.ProcessingRegion, \"Google Cloud Pub/Sub\")\n\tc.CmdClause.Flag(\"project-id\", \"The ID of your Google Cloud Platform project\").Action(c.ProjectID.Set).StringVar(&c.ProjectID.Value)\n\tlogflags.ResponseCondition(c.CmdClause, &c.ResponseCondition)\n\tc.CmdClause.Flag(\"secret-key\", \"Your Google Cloud Platform account secret key. The private_key field in your service account authentication JSON\").Action(c.SecretKey.Set).StringVar(&c.SecretKey.Value)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.ServiceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.ServiceName.Value,\n\t})\n\tc.CmdClause.Flag(\"topic\", \"The Google Cloud Pub/Sub topic to which logs will be published\").Action(c.Topic.Set).StringVar(&c.Topic.Value)\n\tc.CmdClause.Flag(\"user\", \"Your Google Cloud Platform service account email address. The client_email field in your service account authentication JSON\").Action(c.User.Set).StringVar(&c.User.Value)\n\treturn &c\n}\n\n// ConstructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *CreateCommand) ConstructInput(serviceID string, serviceVersion int) (*fastly.CreatePubsubInput, error) {\n\tinput := fastly.CreatePubsubInput{\n\t\tServiceID:      serviceID,\n\t\tServiceVersion: serviceVersion,\n\t}\n\n\tif c.AccountName.WasSet {\n\t\tinput.AccountName = &c.AccountName.Value\n\t}\n\tif c.EndpointName.WasSet {\n\t\tinput.Name = &c.EndpointName.Value\n\t}\n\tif c.Format.WasSet {\n\t\tinput.Format = fastly.ToPointer(argparser.Content(c.Format.Value))\n\t}\n\tif c.FormatVersion.WasSet {\n\t\tinput.FormatVersion = &c.FormatVersion.Value\n\t}\n\tif c.Placement.WasSet {\n\t\tinput.Placement = &c.Placement.Value\n\t}\n\tif c.ProcessingRegion.WasSet {\n\t\tinput.ProcessingRegion = &c.ProcessingRegion.Value\n\t}\n\tif c.ProjectID.WasSet {\n\t\tinput.ProjectID = &c.ProjectID.Value\n\t}\n\tif c.ResponseCondition.WasSet {\n\t\tinput.ResponseCondition = &c.ResponseCondition.Value\n\t}\n\tif c.SecretKey.WasSet {\n\t\tinput.SecretKey = &c.SecretKey.Value\n\t}\n\tif c.Topic.WasSet {\n\t\tinput.Topic = &c.Topic.Value\n\t}\n\tif c.User.WasSet {\n\t\tinput.User = &c.User.Value\n\t}\n\n\treturn &input, nil\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *CreateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(false),\n\t\tLocked:             optional.Of(false),\n\t\tAutoCloneFlag:      c.AutoClone,\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.ServiceName,\n\t\tServiceVersionFlag: c.ServiceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": errors.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tinput, err := c.ConstructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\td, err := c.Globals.APIClient.CreatePubsub(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\ttext.Success(\n\t\tout,\n\t\t\"Created Google Cloud Pub/Sub logging endpoint %s (service %s version %d)\",\n\t\tfastly.ToValue(d.Name),\n\t\tfastly.ToValue(d.ServiceID),\n\t\tfastly.ToValue(d.ServiceVersion),\n\t)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/googlepubsub/delete.go",
    "content": "package googlepubsub\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DeleteCommand calls the Fastly API to delete a Google Cloud Pub/Sub logging endpoint.\ntype DeleteCommand struct {\n\targparser.Base\n\tInput          fastly.DeletePubsubInput\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n\tautoClone      argparser.OptionalAutoClone\n}\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"delete\", \"Delete a Google Cloud Pub/Sub logging endpoint on a Fastly service version\").Alias(\"remove\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"The name of the Google Cloud Pub/Sub logging object\").Short('n').Required().StringVar(&c.Input.Name)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.autoClone.Set,\n\t\tDst:    &c.autoClone.Value,\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DeleteCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(false),\n\t\tLocked:             optional.Of(false),\n\t\tAutoCloneFlag:      c.autoClone,\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": errors.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tc.Input.ServiceID = serviceID\n\tc.Input.ServiceVersion = fastly.ToValue(serviceVersion.Number)\n\n\tif err := c.Globals.APIClient.DeletePubsub(context.TODO(), &c.Input); err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Deleted Google Cloud Pub/Sub logging endpoint %s (service %s version %d)\", c.Input.Name, c.Input.ServiceID, c.Input.ServiceVersion)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/googlepubsub/describe.go",
    "content": "package googlepubsub\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DescribeCommand calls the Fastly API to describe a Google Cloud Pub/Sub logging endpoint.\ntype DescribeCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tInput          fastly.GetPubsubInput\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n}\n\n// NewDescribeCommand returns a usable command registered under the parent.\nfunc NewDescribeCommand(parent argparser.Registerer, g *global.Data) *DescribeCommand {\n\tc := DescribeCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"describe\", \"Show detailed information about a Google Cloud Pub/Sub logging endpoint on a Fastly service version\").Alias(\"get\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"The name of the Google Cloud Pub/Sub logging object\").Short('n').Required().StringVar(&c.Input.Name)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DescribeCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fsterr.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tc.Input.ServiceID = serviceID\n\tc.Input.ServiceVersion = fastly.ToValue(serviceVersion.Number)\n\n\to, err := c.Globals.APIClient.GetPubsub(context.TODO(), &c.Input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\tlines := text.Lines{\n\t\t\"Account name\":       fastly.ToValue(o.AccountName),\n\t\t\"Format version\":     fastly.ToValue(o.FormatVersion),\n\t\t\"Format\":             fastly.ToValue(o.Format),\n\t\t\"Name\":               fastly.ToValue(o.Name),\n\t\t\"Placement\":          fastly.ToValue(o.Placement),\n\t\t\"Processing region\":  fastly.ToValue(o.ProcessingRegion),\n\t\t\"Project ID\":         fastly.ToValue(o.ProjectID),\n\t\t\"Response condition\": fastly.ToValue(o.ResponseCondition),\n\t\t\"Secret key\":         fastly.ToValue(o.SecretKey),\n\t\t\"Topic\":              fastly.ToValue(o.Topic),\n\t\t\"User\":               fastly.ToValue(o.User),\n\t\t\"Version\":            fastly.ToValue(o.ServiceVersion),\n\t}\n\tif !c.Globals.Verbose() {\n\t\tlines[\"Service ID\"] = fastly.ToValue(o.ServiceID)\n\t}\n\ttext.PrintLines(out, lines)\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/googlepubsub/doc.go",
    "content": "// Package googlepubsub contains commands to inspect and manipulate Fastly service Google Cloud Pub/Sub\n// logging endpoints.\npackage googlepubsub\n"
  },
  {
    "path": "pkg/commands/service/logging/googlepubsub/googlepubsub_integration_test.go",
    "content": "package googlepubsub_test\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/mock\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n\n\troot \"github.com/fastly/cli/pkg/commands/service\"\n\tparent \"github.com/fastly/cli/pkg/commands/service/logging\"\n\tsub \"github.com/fastly/cli/pkg/commands/service/logging/googlepubsub\"\n)\n\nfunc TestGooglePubSubCreate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name log --user user@example.com --secret-key secret --project-id project --topic topic --account-name=me@fastly.com --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tCreatePubsubFn: createGooglePubSubOK,\n\t\t\t},\n\t\t\tWantOutput: \"Created Google Cloud Pub/Sub logging endpoint log (service 123 version 4)\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name log --user user@example.com --secret-key secret --project-id project --topic topic --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tCreatePubsubFn: createGooglePubSubError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"create\"}, scenarios)\n}\n\nfunc TestGooglePubSubList(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:  testutil.GetVersion,\n\t\t\t\tListPubsubsFn: listGooglePubSubsOK,\n\t\t\t},\n\t\t\tWantOutput: listGooglePubSubsShortOutput,\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --verbose\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:  testutil.GetVersion,\n\t\t\t\tListPubsubsFn: listGooglePubSubsOK,\n\t\t\t},\n\t\t\tWantOutput: listGooglePubSubsVerboseOutput,\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 -v\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:  testutil.GetVersion,\n\t\t\t\tListPubsubsFn: listGooglePubSubsOK,\n\t\t\t},\n\t\t\tWantOutput: listGooglePubSubsVerboseOutput,\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:  testutil.GetVersion,\n\t\t\t\tListPubsubsFn: listGooglePubSubsError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"list\"}, scenarios)\n}\n\nfunc TestGooglePubSubDescribe(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"--service-id 123 --version 1\",\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tGetPubsubFn:  getGooglePubSubError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tGetPubsubFn:  getGooglePubSubOK,\n\t\t\t},\n\t\t\tWantOutput: describeGooglePubSubOutput,\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"describe\"}, scenarios)\n}\n\nfunc TestGooglePubSubUpdate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"--service-id 123 --version 1 --new-name log\",\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs --new-name log --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tUpdatePubsubFn: updateGooglePubSubError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs --new-name log --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tUpdatePubsubFn: updateGooglePubSubOK,\n\t\t\t},\n\t\t\tWantOutput: \"Updated Google Cloud Pub/Sub logging endpoint log (service 123 version 4)\",\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"update\"}, scenarios)\n}\n\nfunc TestGooglePubSubDelete(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"--service-id 123 --version 1\",\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tDeletePubsubFn: deleteGooglePubSubError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tDeletePubsubFn: deleteGooglePubSubOK,\n\t\t\t},\n\t\t\tWantOutput: \"Deleted Google Cloud Pub/Sub logging endpoint logs (service 123 version 4)\",\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"delete\"}, scenarios)\n}\n\nvar errTest = errors.New(\"fixture error\")\n\nfunc createGooglePubSubOK(_ context.Context, i *fastly.CreatePubsubInput) (*fastly.Pubsub, error) {\n\treturn &fastly.Pubsub{\n\t\tServiceID:         fastly.ToPointer(i.ServiceID),\n\t\tServiceVersion:    fastly.ToPointer(i.ServiceVersion),\n\t\tName:              fastly.ToPointer(\"log\"),\n\t\tTopic:             fastly.ToPointer(\"topic\"),\n\t\tUser:              fastly.ToPointer(\"user\"),\n\t\tSecretKey:         fastly.ToPointer(\"secret\"),\n\t\tProjectID:         fastly.ToPointer(\"project\"),\n\t\tAccountName:       fastly.ToPointer(\"me@fastly.com\"),\n\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\tFormatVersion:     fastly.ToPointer(2),\n\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t}, nil\n}\n\nfunc createGooglePubSubError(_ context.Context, _ *fastly.CreatePubsubInput) (*fastly.Pubsub, error) {\n\treturn nil, errTest\n}\n\nfunc listGooglePubSubsOK(_ context.Context, i *fastly.ListPubsubsInput) ([]*fastly.Pubsub, error) {\n\treturn []*fastly.Pubsub{\n\t\t{\n\t\t\tServiceID:         fastly.ToPointer(i.ServiceID),\n\t\t\tServiceVersion:    fastly.ToPointer(i.ServiceVersion),\n\t\t\tName:              fastly.ToPointer(\"logs\"),\n\t\t\tUser:              fastly.ToPointer(\"user@example.com\"),\n\t\t\tAccountName:       fastly.ToPointer(\"none\"),\n\t\t\tSecretKey:         fastly.ToPointer(\"secret\"),\n\t\t\tProjectID:         fastly.ToPointer(\"project\"),\n\t\t\tTopic:             fastly.ToPointer(\"topic\"),\n\t\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\t\tFormatVersion:     fastly.ToPointer(2),\n\t\t\tProcessingRegion:  fastly.ToPointer(\"us\"),\n\t\t},\n\t\t{\n\t\t\tServiceID:         fastly.ToPointer(i.ServiceID),\n\t\t\tServiceVersion:    fastly.ToPointer(i.ServiceVersion),\n\t\t\tName:              fastly.ToPointer(\"analytics\"),\n\t\t\tUser:              fastly.ToPointer(\"user@example.com\"),\n\t\t\tAccountName:       fastly.ToPointer(\"none\"),\n\t\t\tSecretKey:         fastly.ToPointer(\"secret\"),\n\t\t\tProjectID:         fastly.ToPointer(\"project\"),\n\t\t\tTopic:             fastly.ToPointer(\"analytics\"),\n\t\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\t\tFormatVersion:     fastly.ToPointer(2),\n\t\t\tProcessingRegion:  fastly.ToPointer(\"us\"),\n\t\t},\n\t}, nil\n}\n\nfunc listGooglePubSubsError(_ context.Context, _ *fastly.ListPubsubsInput) ([]*fastly.Pubsub, error) {\n\treturn nil, errTest\n}\n\nvar listGooglePubSubsShortOutput = strings.TrimSpace(`\nSERVICE  VERSION  NAME\n123      1        logs\n123      1        analytics\n`) + \"\\n\"\n\nvar listGooglePubSubsVerboseOutput = strings.TrimSpace(`\nFastly API endpoint: https://api.fastly.com\nFastly API token provided via config file (auth: user)\n\nService ID (via --service-id): 123\n\nVersion: 1\n\tGoogle Cloud Pub/Sub 1/2\n\t\tService ID: 123\n\t\tVersion: 1\n\t\tName: logs\n\t\tUser: user@example.com\n\t\tAccount name: none\n\t\tSecret key: secret\n\t\tProject ID: project\n\t\tTopic: topic\n\t\tFormat: %h %l %u %t \"%r\" %>s %b\n\t\tFormat version: 2\n\t\tResponse condition: Prevent default logging\n\t\tPlacement: none\n\t\tProcessing region: us\n\tGoogle Cloud Pub/Sub 2/2\n\t\tService ID: 123\n\t\tVersion: 1\n\t\tName: analytics\n\t\tUser: user@example.com\n\t\tAccount name: none\n\t\tSecret key: secret\n\t\tProject ID: project\n\t\tTopic: analytics\n\t\tFormat: %h %l %u %t \"%r\" %>s %b\n\t\tFormat version: 2\n\t\tResponse condition: Prevent default logging\n\t\tPlacement: none\n\t\tProcessing region: us\n`) + \"\\n\\n\"\n\nfunc getGooglePubSubOK(_ context.Context, i *fastly.GetPubsubInput) (*fastly.Pubsub, error) {\n\treturn &fastly.Pubsub{\n\t\tServiceID:         fastly.ToPointer(i.ServiceID),\n\t\tServiceVersion:    fastly.ToPointer(i.ServiceVersion),\n\t\tName:              fastly.ToPointer(\"logs\"),\n\t\tTopic:             fastly.ToPointer(\"topic\"),\n\t\tUser:              fastly.ToPointer(\"user@example.com\"),\n\t\tAccountName:       fastly.ToPointer(\"none\"),\n\t\tSecretKey:         fastly.ToPointer(\"secret\"),\n\t\tProjectID:         fastly.ToPointer(\"project\"),\n\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\tFormatVersion:     fastly.ToPointer(2),\n\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\tProcessingRegion:  fastly.ToPointer(\"us\"),\n\t}, nil\n}\n\nfunc getGooglePubSubError(_ context.Context, _ *fastly.GetPubsubInput) (*fastly.Pubsub, error) {\n\treturn nil, errTest\n}\n\nvar describeGooglePubSubOutput = \"\\n\" + strings.TrimSpace(`\nAccount name: none\nFormat: %h %l %u %t \"%r\" %>s %b\nFormat version: 2\nName: logs\nPlacement: none\nProcessing region: us\nProject ID: project\nResponse condition: Prevent default logging\nSecret key: secret\nService ID: 123\nTopic: topic\nUser: user@example.com\nVersion: 1\n`) + \"\\n\"\n\nfunc updateGooglePubSubOK(_ context.Context, i *fastly.UpdatePubsubInput) (*fastly.Pubsub, error) {\n\treturn &fastly.Pubsub{\n\t\tServiceID:         fastly.ToPointer(i.ServiceID),\n\t\tServiceVersion:    fastly.ToPointer(i.ServiceVersion),\n\t\tName:              fastly.ToPointer(\"log\"),\n\t\tTopic:             fastly.ToPointer(\"topic\"),\n\t\tUser:              fastly.ToPointer(\"user@example.com\"),\n\t\tSecretKey:         fastly.ToPointer(\"secret\"),\n\t\tProjectID:         fastly.ToPointer(\"project\"),\n\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\tFormatVersion:     fastly.ToPointer(2),\n\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t}, nil\n}\n\nfunc updateGooglePubSubError(_ context.Context, _ *fastly.UpdatePubsubInput) (*fastly.Pubsub, error) {\n\treturn nil, errTest\n}\n\nfunc deleteGooglePubSubOK(_ context.Context, _ *fastly.DeletePubsubInput) error {\n\treturn nil\n}\n\nfunc deleteGooglePubSubError(_ context.Context, _ *fastly.DeletePubsubInput) error {\n\treturn errTest\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/googlepubsub/googlepubsub_test.go",
    "content": "package googlepubsub_test\n\nimport (\n\t\"bytes\"\n\t\"testing\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/service/logging/googlepubsub\"\n\t\"github.com/fastly/cli/pkg/config\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/manifest\"\n\t\"github.com/fastly/cli/pkg/mock\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n)\n\nfunc TestCreateGooglePubSubInput(t *testing.T) {\n\tfor _, testcase := range []struct {\n\t\tname      string\n\t\tcmd       *googlepubsub.CreateCommand\n\t\twant      *fastly.CreatePubsubInput\n\t\twantError string\n\t}{\n\t\t{\n\t\t\tname: \"required values set flag serviceID\",\n\t\t\tcmd:  createCommandRequired(),\n\t\t\twant: &fastly.CreatePubsubInput{\n\t\t\t\tServiceID:      \"123\",\n\t\t\t\tServiceVersion: 4,\n\t\t\t\tName:           fastly.ToPointer(\"log\"),\n\t\t\t\tUser:           fastly.ToPointer(\"user@example.com\"),\n\t\t\t\tSecretKey:      fastly.ToPointer(\"secret\"),\n\t\t\t\tProjectID:      fastly.ToPointer(\"project\"),\n\t\t\t\tTopic:          fastly.ToPointer(\"topic\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"all values set flag serviceID\",\n\t\t\tcmd:  createCommandAll(),\n\t\t\twant: &fastly.CreatePubsubInput{\n\t\t\t\tServiceID:         \"123\",\n\t\t\t\tServiceVersion:    4,\n\t\t\t\tName:              fastly.ToPointer(\"logs\"),\n\t\t\t\tTopic:             fastly.ToPointer(\"topic\"),\n\t\t\t\tUser:              fastly.ToPointer(\"user@example.com\"),\n\t\t\t\tSecretKey:         fastly.ToPointer(\"secret\"),\n\t\t\t\tProjectID:         fastly.ToPointer(\"project\"),\n\t\t\t\tFormatVersion:     fastly.ToPointer(2),\n\t\t\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\t\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\t\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\t\t\tProcessingRegion:  fastly.ToPointer(\"eu\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:      \"error missing serviceID\",\n\t\t\tcmd:       createCommandMissingServiceID(),\n\t\t\twant:      nil,\n\t\t\twantError: errors.ErrNoServiceID.Error(),\n\t\t},\n\t} {\n\t\tt.Run(testcase.name, func(t *testing.T) {\n\t\t\tif testcase.wantError == errors.ErrNoServiceID.Error() {\n\t\t\t\tt.Setenv(\"FASTLY_SERVICE_ID\", \"\")\n\t\t\t}\n\t\t\tvar bs []byte\n\t\t\tout := bytes.NewBuffer(bs)\n\t\t\tverboseMode := true\n\n\t\t\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\t\t\tAutoCloneFlag:      testcase.cmd.AutoClone,\n\t\t\t\tAPIClient:          testcase.cmd.Globals.APIClient,\n\t\t\t\tManifest:           testcase.cmd.Manifest,\n\t\t\t\tOut:                out,\n\t\t\t\tServiceVersionFlag: testcase.cmd.ServiceVersion,\n\t\t\t\tVerboseMode:        verboseMode,\n\t\t\t})\n\n\t\t\tswitch {\n\t\t\tcase err != nil && testcase.wantError == \"\":\n\t\t\t\tt.Fatalf(\"unexpected error getting service details: %v\", err)\n\t\t\t\treturn\n\t\t\tcase err != nil && testcase.wantError != \"\":\n\t\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\t\treturn\n\t\t\tcase err == nil && testcase.wantError != \"\":\n\t\t\t\tt.Fatalf(\"expected error, have nil (service details: %s, %d)\", serviceID, serviceVersion.Number)\n\t\t\tcase err == nil && testcase.wantError == \"\":\n\t\t\t\thave, err := testcase.cmd.ConstructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\t\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\t\ttestutil.AssertEqual(t, testcase.want, have)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestUpdateGooglePubSubInput(t *testing.T) {\n\tscenarios := []struct {\n\t\tname      string\n\t\tcmd       *googlepubsub.UpdateCommand\n\t\tapi       mock.API\n\t\twant      *fastly.UpdatePubsubInput\n\t\twantError string\n\t}{\n\t\t{\n\t\t\tname: \"all values set flag serviceID\",\n\t\t\tcmd:  updateCommandAll(),\n\t\t\tapi: mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tGetPubsubFn:    getGooglePubSubOK,\n\t\t\t},\n\t\t\twant: &fastly.UpdatePubsubInput{\n\t\t\t\tServiceID:         \"123\",\n\t\t\t\tServiceVersion:    4,\n\t\t\t\tName:              \"log\",\n\t\t\t\tNewName:           fastly.ToPointer(\"new1\"),\n\t\t\t\tUser:              fastly.ToPointer(\"new2\"),\n\t\t\t\tSecretKey:         fastly.ToPointer(\"new3\"),\n\t\t\t\tProjectID:         fastly.ToPointer(\"new4\"),\n\t\t\t\tTopic:             fastly.ToPointer(\"new5\"),\n\t\t\t\tPlacement:         fastly.ToPointer(\"new6\"),\n\t\t\t\tFormat:            fastly.ToPointer(\"new7\"),\n\t\t\t\tFormatVersion:     fastly.ToPointer(3),\n\t\t\t\tResponseCondition: fastly.ToPointer(\"new8\"),\n\t\t\t\tProcessingRegion:  fastly.ToPointer(\"eu\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"no updates\",\n\t\t\tcmd:  updateCommandNoUpdates(),\n\t\t\tapi: mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tGetPubsubFn:    getGooglePubSubOK,\n\t\t\t},\n\t\t\twant: &fastly.UpdatePubsubInput{\n\t\t\t\tServiceID:      \"123\",\n\t\t\t\tServiceVersion: 4,\n\t\t\t\tName:           \"log\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:      \"error missing serviceID\",\n\t\t\tcmd:       updateCommandMissingServiceID(),\n\t\t\twant:      nil,\n\t\t\twantError: errors.ErrNoServiceID.Error(),\n\t\t},\n\t}\n\tfor testcaseIdx := range scenarios {\n\t\ttestcase := &scenarios[testcaseIdx]\n\t\tt.Run(testcase.name, func(t *testing.T) {\n\t\t\tif testcase.wantError == errors.ErrNoServiceID.Error() {\n\t\t\t\tt.Setenv(\"FASTLY_SERVICE_ID\", \"\")\n\t\t\t}\n\t\t\ttestcase.cmd.Globals.APIClient = testcase.api\n\n\t\t\tvar bs []byte\n\t\t\tout := bytes.NewBuffer(bs)\n\t\t\tverboseMode := true\n\n\t\t\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\t\t\tAutoCloneFlag:      testcase.cmd.AutoClone,\n\t\t\t\tAPIClient:          testcase.api,\n\t\t\t\tManifest:           testcase.cmd.Manifest,\n\t\t\t\tOut:                out,\n\t\t\t\tServiceVersionFlag: testcase.cmd.ServiceVersion,\n\t\t\t\tVerboseMode:        verboseMode,\n\t\t\t})\n\n\t\t\tswitch {\n\t\t\tcase err != nil && testcase.wantError == \"\":\n\t\t\t\tt.Fatalf(\"unexpected error getting service details: %v\", err)\n\t\t\t\treturn\n\t\t\tcase err != nil && testcase.wantError != \"\":\n\t\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\t\treturn\n\t\t\tcase err == nil && testcase.wantError != \"\":\n\t\t\t\tt.Fatalf(\"expected error, have nil (service details: %s, %d)\", serviceID, serviceVersion.Number)\n\t\t\tcase err == nil && testcase.wantError == \"\":\n\t\t\t\thave, err := testcase.cmd.ConstructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\t\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\t\ttestutil.AssertEqual(t, testcase.want, have)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc createCommandRequired() *googlepubsub.CreateCommand {\n\tvar b bytes.Buffer\n\n\tg := global.Data{\n\t\tConfig: config.File{},\n\t\tEnv:    config.Environment{},\n\t\tOutput: &b,\n\t}\n\tg.APIClient, _ = mock.APIClient(mock.API{\n\t\tGetVersionFn:   testutil.GetVersion,\n\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t})(\"token\", \"endpoint\", false)\n\n\treturn &googlepubsub.CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: &g,\n\t\t},\n\t\tManifest: manifest.Data{\n\t\t\tFlag: manifest.Flag{\n\t\t\t\tServiceID: \"123\",\n\t\t\t},\n\t\t},\n\t\tServiceVersion: argparser.OptionalServiceVersion{\n\t\t\tOptionalString: argparser.OptionalString{Value: \"1\"},\n\t\t},\n\t\tAutoClone: argparser.OptionalAutoClone{\n\t\t\tOptionalBool: argparser.OptionalBool{\n\t\t\t\tOptional: argparser.Optional{\n\t\t\t\t\tWasSet: true,\n\t\t\t\t},\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t},\n\t\tEndpointName: argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"log\"},\n\t\tUser:         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"user@example.com\"},\n\t\tSecretKey:    argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"secret\"},\n\t\tProjectID:    argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"project\"},\n\t\tTopic:        argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"topic\"},\n\t}\n}\n\nfunc createCommandAll() *googlepubsub.CreateCommand {\n\tvar b bytes.Buffer\n\n\tg := global.Data{\n\t\tConfig: config.File{},\n\t\tEnv:    config.Environment{},\n\t\tOutput: &b,\n\t}\n\tg.APIClient, _ = mock.APIClient(mock.API{\n\t\tGetVersionFn:   testutil.GetVersion,\n\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t})(\"token\", \"endpoint\", false)\n\n\treturn &googlepubsub.CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: &g,\n\t\t},\n\t\tManifest: manifest.Data{\n\t\t\tFlag: manifest.Flag{\n\t\t\t\tServiceID: \"123\",\n\t\t\t},\n\t\t},\n\t\tServiceVersion: argparser.OptionalServiceVersion{\n\t\t\tOptionalString: argparser.OptionalString{Value: \"1\"},\n\t\t},\n\t\tAutoClone: argparser.OptionalAutoClone{\n\t\t\tOptionalBool: argparser.OptionalBool{\n\t\t\t\tOptional: argparser.Optional{\n\t\t\t\t\tWasSet: true,\n\t\t\t\t},\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t},\n\t\tEndpointName:      argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"logs\"},\n\t\tUser:              argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"user@example.com\"},\n\t\tProjectID:         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"project\"},\n\t\tTopic:             argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"topic\"},\n\t\tSecretKey:         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"secret\"},\n\t\tFormat:            argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: `%h %l %u %t \"%r\" %>s %b`},\n\t\tFormatVersion:     argparser.OptionalInt{Optional: argparser.Optional{WasSet: true}, Value: 2},\n\t\tResponseCondition: argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"Prevent default logging\"},\n\t\tPlacement:         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"none\"},\n\t\tProcessingRegion:  argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"eu\"},\n\t}\n}\n\nfunc createCommandMissingServiceID() *googlepubsub.CreateCommand {\n\tres := createCommandAll()\n\tres.Manifest = manifest.Data{}\n\tres.ServiceVersion = argparser.OptionalServiceVersion{}\n\treturn res\n}\n\nfunc updateCommandNoUpdates() *googlepubsub.UpdateCommand {\n\tvar b bytes.Buffer\n\n\tg := global.Data{\n\t\tConfig: config.File{},\n\t\tEnv:    config.Environment{},\n\t\tOutput: &b,\n\t}\n\n\treturn &googlepubsub.UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: &g,\n\t\t},\n\t\tManifest: manifest.Data{\n\t\t\tFlag: manifest.Flag{\n\t\t\t\tServiceID: \"123\",\n\t\t\t},\n\t\t},\n\t\tEndpointName: \"log\",\n\t\tServiceVersion: argparser.OptionalServiceVersion{\n\t\t\tOptionalString: argparser.OptionalString{Value: \"1\"},\n\t\t},\n\t\tAutoClone: argparser.OptionalAutoClone{\n\t\t\tOptionalBool: argparser.OptionalBool{\n\t\t\t\tOptional: argparser.Optional{\n\t\t\t\t\tWasSet: true,\n\t\t\t\t},\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t},\n\t}\n}\n\nfunc updateCommandAll() *googlepubsub.UpdateCommand {\n\tvar b bytes.Buffer\n\n\tg := global.Data{\n\t\tConfig: config.File{},\n\t\tEnv:    config.Environment{},\n\t\tOutput: &b,\n\t}\n\n\treturn &googlepubsub.UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: &g,\n\t\t},\n\t\tManifest: manifest.Data{\n\t\t\tFlag: manifest.Flag{\n\t\t\t\tServiceID: \"123\",\n\t\t\t},\n\t\t},\n\t\tEndpointName: \"log\",\n\t\tServiceVersion: argparser.OptionalServiceVersion{\n\t\t\tOptionalString: argparser.OptionalString{Value: \"1\"},\n\t\t},\n\t\tAutoClone: argparser.OptionalAutoClone{\n\t\t\tOptionalBool: argparser.OptionalBool{\n\t\t\t\tOptional: argparser.Optional{\n\t\t\t\t\tWasSet: true,\n\t\t\t\t},\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t},\n\t\tNewName:           argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new1\"},\n\t\tUser:              argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new2\"},\n\t\tSecretKey:         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new3\"},\n\t\tProjectID:         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new4\"},\n\t\tTopic:             argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new5\"},\n\t\tPlacement:         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new6\"},\n\t\tFormat:            argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new7\"},\n\t\tFormatVersion:     argparser.OptionalInt{Optional: argparser.Optional{WasSet: true}, Value: 3},\n\t\tResponseCondition: argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new8\"},\n\t\tProcessingRegion:  argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"eu\"},\n\t}\n}\n\nfunc updateCommandMissingServiceID() *googlepubsub.UpdateCommand {\n\tres := updateCommandAll()\n\tres.Manifest = manifest.Data{}\n\tres.ServiceVersion = argparser.OptionalServiceVersion{}\n\treturn res\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/googlepubsub/list.go",
    "content": "package googlepubsub\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// ListCommand calls the Fastly API to list Google Cloud Pub/Sub logging endpoints.\ntype ListCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tInput          fastly.ListPubsubsInput\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n}\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"list\", \"List Google Cloud Pub/Sub endpoints on a Fastly service version\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *ListCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fsterr.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tc.Input.ServiceID = serviceID\n\tc.Input.ServiceVersion = fastly.ToValue(serviceVersion.Number)\n\n\to, err := c.Globals.APIClient.ListPubsubs(context.TODO(), &c.Input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\tif !c.Globals.Verbose() {\n\t\ttw := text.NewTable(out)\n\t\ttw.AddHeader(\"SERVICE\", \"VERSION\", \"NAME\")\n\t\tfor _, googlepubsub := range o {\n\t\t\ttw.AddLine(\n\t\t\t\tfastly.ToValue(googlepubsub.ServiceID),\n\t\t\t\tfastly.ToValue(googlepubsub.ServiceVersion),\n\t\t\t\tfastly.ToValue(googlepubsub.Name),\n\t\t\t)\n\t\t}\n\t\ttw.Print()\n\t\treturn nil\n\t}\n\n\tfmt.Fprintf(out, \"Version: %d\\n\", c.Input.ServiceVersion)\n\tfor i, googlepubsub := range o {\n\t\tfmt.Fprintf(out, \"\\tGoogle Cloud Pub/Sub %d/%d\\n\", i+1, len(o))\n\t\tfmt.Fprintf(out, \"\\t\\tService ID: %s\\n\", fastly.ToValue(googlepubsub.ServiceID))\n\t\tfmt.Fprintf(out, \"\\t\\tVersion: %d\\n\", fastly.ToValue(googlepubsub.ServiceVersion))\n\t\tfmt.Fprintf(out, \"\\t\\tName: %s\\n\", fastly.ToValue(googlepubsub.Name))\n\t\tfmt.Fprintf(out, \"\\t\\tUser: %s\\n\", fastly.ToValue(googlepubsub.User))\n\t\tfmt.Fprintf(out, \"\\t\\tAccount name: %s\\n\", fastly.ToValue(googlepubsub.AccountName))\n\t\tfmt.Fprintf(out, \"\\t\\tSecret key: %s\\n\", fastly.ToValue(googlepubsub.SecretKey))\n\t\tfmt.Fprintf(out, \"\\t\\tProject ID: %s\\n\", fastly.ToValue(googlepubsub.ProjectID))\n\t\tfmt.Fprintf(out, \"\\t\\tTopic: %s\\n\", fastly.ToValue(googlepubsub.Topic))\n\t\tfmt.Fprintf(out, \"\\t\\tFormat: %s\\n\", fastly.ToValue(googlepubsub.Format))\n\t\tfmt.Fprintf(out, \"\\t\\tFormat version: %d\\n\", fastly.ToValue(googlepubsub.FormatVersion))\n\t\tfmt.Fprintf(out, \"\\t\\tResponse condition: %s\\n\", fastly.ToValue(googlepubsub.ResponseCondition))\n\t\tfmt.Fprintf(out, \"\\t\\tPlacement: %s\\n\", fastly.ToValue(googlepubsub.Placement))\n\t\tfmt.Fprintf(out, \"\\t\\tProcessing region: %s\\n\", fastly.ToValue(googlepubsub.ProcessingRegion))\n\t}\n\tfmt.Fprintln(out)\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/googlepubsub/root.go",
    "content": "package googlepubsub\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"googlepubsub\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manipulate Fastly service version Google Cloud Pub/Sub logging endpoints\")\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/googlepubsub/update.go",
    "content": "package googlepubsub\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/service/logging/logflags\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/manifest\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// UpdateCommand calls the Fastly API to update a Google Cloud Pub/Sub logging endpoint.\ntype UpdateCommand struct {\n\targparser.Base\n\tManifest manifest.Data\n\n\t// Required.\n\tEndpointName   string // Can't shadow argparser.Base method Name().\n\tServiceName    argparser.OptionalServiceNameID\n\tServiceVersion argparser.OptionalServiceVersion\n\n\t// Optional.\n\tAccountName       argparser.OptionalString\n\tAutoClone         argparser.OptionalAutoClone\n\tFormat            argparser.OptionalString\n\tFormatVersion     argparser.OptionalInt\n\tNewName           argparser.OptionalString\n\tPlacement         argparser.OptionalString\n\tProcessingRegion  argparser.OptionalString\n\tProjectID         argparser.OptionalString\n\tResponseCondition argparser.OptionalString\n\tSecretKey         argparser.OptionalString\n\tTopic             argparser.OptionalString\n\tUser              argparser.OptionalString\n}\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"update\", \"Update a Google Cloud Pub/Sub logging endpoint on a Fastly service version\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"The name of the Google Cloud Pub/Sub logging object\").Short('n').Required().StringVar(&c.EndpointName)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.ServiceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tlogflags.AccountName(c.CmdClause, &c.AccountName)\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.AutoClone.Set,\n\t\tDst:    &c.AutoClone.Value,\n\t})\n\tlogflags.Format(c.CmdClause, &c.Format)\n\tlogflags.FormatVersion(c.CmdClause, &c.FormatVersion)\n\tc.CmdClause.Flag(\"new-name\", \"New name of the Google Cloud Pub/Sub logging object\").Action(c.NewName.Set).StringVar(&c.NewName.Value)\n\tlogflags.Placement(c.CmdClause, &c.Placement)\n\tlogflags.ProcessingRegion(c.CmdClause, &c.ProcessingRegion, \"Google Cloud Pub/Sub\")\n\tc.CmdClause.Flag(\"project-id\", \"The ID of your Google Cloud Platform project\").Action(c.ProjectID.Set).StringVar(&c.ProjectID.Value)\n\tc.CmdClause.Flag(\"secret-key\", \"Your Google Cloud Platform account secret key. The private_key field in your service account authentication JSON\").Action(c.SecretKey.Set).StringVar(&c.SecretKey.Value)\n\tlogflags.ResponseCondition(c.CmdClause, &c.ResponseCondition)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.ServiceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.ServiceName.Value,\n\t})\n\tc.CmdClause.Flag(\"topic\", \"The Google Cloud Pub/Sub topic to which logs will be published\").Action(c.Topic.Set).StringVar(&c.Topic.Value)\n\tc.CmdClause.Flag(\"user\", \"Your Google Cloud Platform service account email address. The client_email field in your service account authentication JSON\").Action(c.User.Set).StringVar(&c.User.Value)\n\treturn &c\n}\n\n// ConstructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *UpdateCommand) ConstructInput(serviceID string, serviceVersion int) (*fastly.UpdatePubsubInput, error) {\n\tinput := fastly.UpdatePubsubInput{\n\t\tServiceID:      serviceID,\n\t\tServiceVersion: serviceVersion,\n\t\tName:           c.EndpointName,\n\t}\n\n\tif c.AccountName.WasSet {\n\t\tinput.AccountName = &c.AccountName.Value\n\t}\n\tif c.Format.WasSet {\n\t\tinput.Format = fastly.ToPointer(argparser.Content(c.Format.Value))\n\t}\n\tif c.FormatVersion.WasSet {\n\t\tinput.FormatVersion = &c.FormatVersion.Value\n\t}\n\tif c.NewName.WasSet {\n\t\tinput.NewName = &c.NewName.Value\n\t}\n\tif c.Placement.WasSet {\n\t\tinput.Placement = &c.Placement.Value\n\t}\n\tif c.ProcessingRegion.WasSet {\n\t\tinput.ProcessingRegion = &c.ProcessingRegion.Value\n\t}\n\tif c.ProjectID.WasSet {\n\t\tinput.ProjectID = &c.ProjectID.Value\n\t}\n\tif c.ResponseCondition.WasSet {\n\t\tinput.ResponseCondition = &c.ResponseCondition.Value\n\t}\n\tif c.SecretKey.WasSet {\n\t\tinput.SecretKey = &c.SecretKey.Value\n\t}\n\tif c.Topic.WasSet {\n\t\tinput.Topic = &c.Topic.Value\n\t}\n\tif c.User.WasSet {\n\t\tinput.User = &c.User.Value\n\t}\n\n\treturn &input, nil\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *UpdateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(false),\n\t\tLocked:             optional.Of(false),\n\t\tAutoCloneFlag:      c.AutoClone,\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.ServiceName,\n\t\tServiceVersionFlag: c.ServiceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": errors.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tinput, err := c.ConstructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tgooglepubsub, err := c.Globals.APIClient.UpdatePubsub(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\ttext.Success(\n\t\tout,\n\t\t\"Updated Google Cloud Pub/Sub logging endpoint %s (service %s version %d)\",\n\t\tfastly.ToValue(googlepubsub.Name),\n\t\tfastly.ToValue(googlepubsub.ServiceID),\n\t\tfastly.ToValue(googlepubsub.ServiceVersion),\n\t)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/grafanacloudlogs/create.go",
    "content": "package grafanacloudlogs\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/service/logging/logflags\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/manifest\"\n\t\"github.com/fastly/cli/pkg/text\"\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n)\n\n// CreateCommand calls the Fastly API to create a GrafanaCloudLogs logging endpoint.\ntype CreateCommand struct {\n\targparser.Base\n\tManifest manifest.Data\n\n\t// Required.\n\tServiceName    argparser.OptionalServiceNameID\n\tServiceVersion argparser.OptionalServiceVersion\n\tUser           argparser.OptionalString\n\tURL            argparser.OptionalString\n\tIndex          argparser.OptionalString\n\tToken          argparser.OptionalString\n\n\t// Optional.\n\tAutoClone         argparser.OptionalAutoClone\n\tEndpointName      argparser.OptionalString // Can't shadow argparser.Base method Name().\n\tFormat            argparser.OptionalString\n\tFormatVersion     argparser.OptionalInt\n\tMessageType       argparser.OptionalString\n\tPlacement         argparser.OptionalString\n\tProcessingRegion  argparser.OptionalString\n\tResponseCondition argparser.OptionalString\n\tTimestampFormat   argparser.OptionalString\n}\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"create\", \"Create a Grafana Cloud Logs logging endpoint on a Fastly service version\").Alias(\"add\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"The name of the Grafana Cloud Logs logging endpoint. Used as a primary key for API access\").Short('n').Action(c.EndpointName.Set).StringVar(&c.EndpointName.Value)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.ServiceVersion.Value,\n\t\tRequired:    true,\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.ServiceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.ServiceName.Value,\n\t})\n\n\t// Optional.\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.AutoClone.Set,\n\t\tDst:    &c.AutoClone.Value,\n\t})\n\tlogflags.Format(c.CmdClause, &c.Format)\n\tlogflags.FormatVersion(c.CmdClause, &c.FormatVersion)\n\tlogflags.MessageType(c.CmdClause, &c.MessageType)\n\tlogflags.Placement(c.CmdClause, &c.Placement)\n\tlogflags.ProcessingRegion(c.CmdClause, &c.ProcessingRegion, \"Grafana Cloud Logs\")\n\tlogflags.ResponseCondition(c.CmdClause, &c.ResponseCondition)\n\n\tc.CmdClause.Flag(\"index\", `The stream identifier`).Action(c.Index.Set).StringVar(&c.Index.Value)\n\tc.CmdClause.Flag(\"url\", \"The URL of your Grafana instance\").Action(c.URL.Set).StringVar(&c.URL.Value)\n\tc.CmdClause.Flag(\"user\", \"Your Grafana User ID.\").Action(c.User.Set).StringVar(&c.User.Value)\n\tc.CmdClause.Flag(\"auth-token\", \"Your Grafana Access Policy Token\").Action(c.Token.Set).StringVar(&c.Token.Value)\n\n\treturn &c\n}\n\n// ConstructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *CreateCommand) ConstructInput(serviceID string, serviceVersion int) (*fastly.CreateGrafanaCloudLogsInput, error) {\n\tinput := fastly.CreateGrafanaCloudLogsInput{\n\t\tServiceID:      serviceID,\n\t\tServiceVersion: serviceVersion,\n\t}\n\n\tif c.EndpointName.WasSet {\n\t\tinput.Name = &c.EndpointName.Value\n\t}\n\tif c.Format.WasSet {\n\t\tinput.Format = fastly.ToPointer(argparser.Content(c.Format.Value))\n\t}\n\tif c.FormatVersion.WasSet {\n\t\tinput.FormatVersion = &c.FormatVersion.Value\n\t}\n\tif c.MessageType.WasSet {\n\t\tinput.MessageType = &c.MessageType.Value\n\t}\n\tif c.Index.WasSet {\n\t\tinput.Index = &c.Index.Value\n\t}\n\tif c.URL.WasSet {\n\t\tinput.URL = &c.URL.Value\n\t}\n\tif c.User.WasSet {\n\t\tinput.User = &c.User.Value\n\t}\n\tif c.Token.WasSet {\n\t\tinput.Token = &c.Token.Value\n\t}\n\tif c.Placement.WasSet {\n\t\tinput.Placement = &c.Placement.Value\n\t}\n\tif c.ProcessingRegion.WasSet {\n\t\tinput.ProcessingRegion = &c.ProcessingRegion.Value\n\t}\n\tif c.ResponseCondition.WasSet {\n\t\tinput.ResponseCondition = &c.ResponseCondition.Value\n\t}\n\treturn &input, nil\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *CreateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(false),\n\t\tLocked:             optional.Of(false),\n\t\tAutoCloneFlag:      c.AutoClone,\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.ServiceName,\n\t\tServiceVersionFlag: c.ServiceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": errors.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tinput, err := c.ConstructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\td, err := c.Globals.APIClient.CreateGrafanaCloudLogs(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\ttext.Success(\n\t\tout,\n\t\t\"Created Grafana Cloud Logs logging endpoint %s (service %s version %d)\",\n\t\tfastly.ToValue(d.Name),\n\t\tfastly.ToValue(d.ServiceID),\n\t\tfastly.ToValue(d.ServiceVersion),\n\t)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/grafanacloudlogs/delete.go",
    "content": "package grafanacloudlogs\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DeleteCommand calls the Fastly API to delete a Grafana Cloud Logs logging endpoint.\ntype DeleteCommand struct {\n\targparser.Base\n\tInput          fastly.DeleteGrafanaCloudLogsInput\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n\tautoClone      argparser.OptionalAutoClone\n}\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"delete\", \"Delete a GrafanaCloudLogs logging endpoint on a Fastly service version\").Alias(\"remove\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"The name of the Grafana Cloud Logs logging object\").Short('n').Required().StringVar(&c.Input.Name)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.autoClone.Set,\n\t\tDst:    &c.autoClone.Value,\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DeleteCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(false),\n\t\tLocked:             optional.Of(false),\n\t\tAutoCloneFlag:      c.autoClone,\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": errors.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tc.Input.ServiceID = serviceID\n\tc.Input.ServiceVersion = fastly.ToValue(serviceVersion.Number)\n\n\tif err := c.Globals.APIClient.DeleteGrafanaCloudLogs(context.TODO(), &c.Input); err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Deleted Grafana Cloud Logs logging endpoint %s (service %s version %d)\", c.Input.Name, c.Input.ServiceID, c.Input.ServiceVersion)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/grafanacloudlogs/describe.go",
    "content": "package grafanacloudlogs\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DescribeCommand calls the Fastly API to describe a Grafana Cloud Logs logging endpoint.\ntype DescribeCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tInput          fastly.GetGrafanaCloudLogsInput\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n}\n\n// NewDescribeCommand returns a usable command registered under the parent.\nfunc NewDescribeCommand(parent argparser.Registerer, g *global.Data) *DescribeCommand {\n\tc := DescribeCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"describe\", \"Show detailed information about a Grafana Cloud Logs logging endpoint on a Fastly service version\").Alias(\"get\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"The name of the Grafana Cloud Logs logging object\").Short('n').Required().StringVar(&c.Input.Name)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DescribeCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fsterr.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tc.Input.ServiceID = serviceID\n\tc.Input.ServiceVersion = fastly.ToValue(serviceVersion.Number)\n\n\to, err := c.Globals.APIClient.GetGrafanaCloudLogs(context.TODO(), &c.Input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\tlines := text.Lines{\n\t\t\"Format version\":     fastly.ToValue(o.FormatVersion),\n\t\t\"Format\":             fastly.ToValue(o.Format),\n\t\t\"Message type\":       fastly.ToValue(o.MessageType),\n\t\t\"Name\":               fastly.ToValue(o.Name),\n\t\t\"Placement\":          fastly.ToValue(o.Placement),\n\t\t\"Processing region\":  fastly.ToValue(o.ProcessingRegion),\n\t\t\"Response condition\": fastly.ToValue(o.ResponseCondition),\n\t\t\"Version\":            fastly.ToValue(o.ServiceVersion),\n\t\t\"User\":               fastly.ToValue(o.User),\n\t\t\"URL\":                fastly.ToValue(o.URL),\n\t\t\"Token\":              fastly.ToValue(o.Token),\n\t\t\"Index\":              fastly.ToValue(o.Index),\n\t}\n\tif !c.Globals.Verbose() {\n\t\tlines[\"Service ID\"] = fastly.ToValue(o.ServiceID)\n\t}\n\ttext.PrintLines(out, lines)\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/grafanacloudlogs/doc.go",
    "content": "// Package grafanacloudlogs contains commands to inspect and manipulate Fastly service Grafana Cloud Logs\n// logging endpoints.\npackage grafanacloudlogs\n"
  },
  {
    "path": "pkg/commands/service/logging/grafanacloudlogs/grafanacloud_logs_integration_test.go",
    "content": "package grafanacloudlogs_test\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\troot \"github.com/fastly/cli/pkg/commands/service\"\n\tparent \"github.com/fastly/cli/pkg/commands/service/logging\"\n\tsub \"github.com/fastly/cli/pkg/commands/service/logging/grafanacloudlogs\"\n\t\"github.com/fastly/cli/pkg/mock\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n)\n\nfunc TestGrafanaCloudLogsCreate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name log --user 123456 --url https://test123.grafana.net --auth-token testtoken --index `{\\\"label\\\": \\\"value\\\" }` --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:             testutil.GetVersion,\n\t\t\t\tCloneVersionFn:           testutil.CloneVersionResult(4),\n\t\t\t\tCreateGrafanaCloudLogsFn: createGrafanaCloudLogsOK,\n\t\t\t},\n\t\t\tWantOutput: \"Created Grafana Cloud Logs logging endpoint log (service 123 version 4)\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name log --url https://test123.grafana.net --auth-token testtoken --index `{\\\"label\\\": \\\"value\\\" }` --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:             testutil.GetVersion,\n\t\t\t\tCloneVersionFn:           testutil.CloneVersionResult(4),\n\t\t\t\tCreateGrafanaCloudLogsFn: createGrafanaCloudLogsError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"create\"}, scenarios)\n}\n\nfunc TestGrafanaCloudLogsList(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:           testutil.GetVersion,\n\t\t\t\tListGrafanaCloudLogsFn: listGrafanaCloudLogsOK,\n\t\t\t},\n\t\t\tWantOutput: listGrafanaCloudLogsShortOutput,\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --verbose\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:           testutil.GetVersion,\n\t\t\t\tListGrafanaCloudLogsFn: listGrafanaCloudLogsOK,\n\t\t\t},\n\t\t\tWantOutput: listGrafanaCloudLogsVerboseOutput,\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 -v\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:           testutil.GetVersion,\n\t\t\t\tListGrafanaCloudLogsFn: listGrafanaCloudLogsOK,\n\t\t\t},\n\t\t\tWantOutput: listGrafanaCloudLogsVerboseOutput,\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:           testutil.GetVersion,\n\t\t\t\tListGrafanaCloudLogsFn: listGrafanaCloudLogsError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"list\"}, scenarios)\n}\n\nfunc TestGrafanaCloudLogsDescribe(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"--service-id 123 --version 1\",\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:          testutil.GetVersion,\n\t\t\t\tGetGrafanaCloudLogsFn: getGrafanaCloudLogsError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:          testutil.GetVersion,\n\t\t\t\tGetGrafanaCloudLogsFn: getGrafanaCloudLogsOK,\n\t\t\t},\n\t\t\tWantOutput: describeGrafanaCloudLogsOutput,\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"describe\"}, scenarios)\n}\n\nfunc TestGrafanaCloudLogsUpdate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"--service-id 123 --version 1 --new-name log\",\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs --new-name log --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:             testutil.GetVersion,\n\t\t\t\tCloneVersionFn:           testutil.CloneVersionResult(4),\n\t\t\t\tUpdateGrafanaCloudLogsFn: updateGrafanaCloudLogsError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs --new-name log --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:             testutil.GetVersion,\n\t\t\t\tCloneVersionFn:           testutil.CloneVersionResult(4),\n\t\t\t\tUpdateGrafanaCloudLogsFn: updateGrafanaCloudLogsOK,\n\t\t\t},\n\t\t\tWantOutput: \"Updated Grafana Cloud Logs logging endpoint log (service 123 version 4)\",\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"update\"}, scenarios)\n}\n\nfunc TestGrafanaCloudLogsDelete(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"--service-id 123 --version 1\",\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:             testutil.GetVersion,\n\t\t\t\tCloneVersionFn:           testutil.CloneVersionResult(4),\n\t\t\t\tDeleteGrafanaCloudLogsFn: deleteGrafanaCloudLogsError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:             testutil.GetVersion,\n\t\t\t\tCloneVersionFn:           testutil.CloneVersionResult(4),\n\t\t\t\tDeleteGrafanaCloudLogsFn: deleteGrafanaCloudLogsOK,\n\t\t\t},\n\t\t\tWantOutput: \"Deleted Grafana Cloud Logs logging endpoint logs (service 123 version 4)\",\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"delete\"}, scenarios)\n}\n\nvar errTest = errors.New(\"fixture error\")\n\nfunc createGrafanaCloudLogsOK(_ context.Context, i *fastly.CreateGrafanaCloudLogsInput) (*fastly.GrafanaCloudLogs, error) {\n\treturn &fastly.GrafanaCloudLogs{\n\t\tServiceID:      fastly.ToPointer(i.ServiceID),\n\t\tServiceVersion: fastly.ToPointer(i.ServiceVersion),\n\t\tName:           i.Name,\n\t}, nil\n}\n\nfunc createGrafanaCloudLogsError(_ context.Context, _ *fastly.CreateGrafanaCloudLogsInput) (*fastly.GrafanaCloudLogs, error) {\n\treturn nil, errTest\n}\n\nfunc listGrafanaCloudLogsOK(_ context.Context, i *fastly.ListGrafanaCloudLogsInput) ([]*fastly.GrafanaCloudLogs, error) {\n\treturn []*fastly.GrafanaCloudLogs{\n\t\t{\n\t\t\tServiceID:         fastly.ToPointer(i.ServiceID),\n\t\t\tServiceVersion:    fastly.ToPointer(i.ServiceVersion),\n\t\t\tName:              fastly.ToPointer(\"logs\"),\n\t\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\t\tFormatVersion:     fastly.ToPointer(2),\n\t\t\tMessageType:       fastly.ToPointer(\"classic\"),\n\t\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\t\tUser:              fastly.ToPointer(\"123456\"),\n\t\t\tToken:             fastly.ToPointer(\"testtoken\"),\n\t\t\tURL:               fastly.ToPointer(\"https://test123.grafana.net\"),\n\t\t\tIndex:             fastly.ToPointer(\"{\\\"label\\\": \\\"value\\\"}\"),\n\t\t\tProcessingRegion:  fastly.ToPointer(\"us\"),\n\t\t},\n\t\t{\n\t\t\tServiceID:         fastly.ToPointer(i.ServiceID),\n\t\t\tServiceVersion:    fastly.ToPointer(i.ServiceVersion),\n\t\t\tName:              fastly.ToPointer(\"analytics\"),\n\t\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\t\tFormatVersion:     fastly.ToPointer(2),\n\t\t\tMessageType:       fastly.ToPointer(\"classic\"),\n\t\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\t\tUser:              fastly.ToPointer(\"123456\"),\n\t\t\tToken:             fastly.ToPointer(\"testtoken\"),\n\t\t\tURL:               fastly.ToPointer(\"https://test123.grafana.net\"),\n\t\t\tIndex:             fastly.ToPointer(\"{\\\"label\\\": \\\"value\\\"}\"),\n\t\t\tProcessingRegion:  fastly.ToPointer(\"us\"),\n\t\t},\n\t}, nil\n}\n\nfunc listGrafanaCloudLogsError(_ context.Context, _ *fastly.ListGrafanaCloudLogsInput) ([]*fastly.GrafanaCloudLogs, error) {\n\treturn nil, errTest\n}\n\nvar listGrafanaCloudLogsShortOutput = strings.TrimSpace(`\nSERVICE  VERSION  NAME\n123      1        logs\n123      1        analytics\n`) + \"\\n\"\n\nvar listGrafanaCloudLogsVerboseOutput = strings.TrimSpace(`\nFastly API endpoint: https://api.fastly.com\nFastly API token provided via config file (auth: user)\n\nService ID (via --service-id): 123\n\nVersion: 1\n\tGrafanaCloudLogs 1/2\n\t\tService ID: 123\n\t\tVersion: 1\n\t\tName: logs\n\t\tFormat: %h %l %u %t \"%r\" %>s %b\n\t\tFormat version: 2\n\t\tResponse condition: Prevent default logging\n\t\tMessage type: classic\n\t\tPlacement: none\n\t\tUser: 123456\n\t\tURL: https://test123.grafana.net\n\t\tToken: testtoken\n\t\tIndex: {\"label\": \"value\"}\n\t\tProcessing region: us\n\tGrafanaCloudLogs 2/2\n\t\tService ID: 123\n\t\tVersion: 1\n\t\tName: analytics\n\t\tFormat: %h %l %u %t \"%r\" %>s %b\n\t\tFormat version: 2\n\t\tResponse condition: Prevent default logging\n\t\tMessage type: classic\n\t\tPlacement: none\n\t\tUser: 123456\n\t\tURL: https://test123.grafana.net\n\t\tToken: testtoken\n\t\tIndex: {\"label\": \"value\"}\n\t\tProcessing region: us\n`) + \"\\n\\n\"\n\nfunc getGrafanaCloudLogsOK(_ context.Context, i *fastly.GetGrafanaCloudLogsInput) (*fastly.GrafanaCloudLogs, error) {\n\treturn &fastly.GrafanaCloudLogs{\n\t\tServiceID:         fastly.ToPointer(i.ServiceID),\n\t\tServiceVersion:    fastly.ToPointer(i.ServiceVersion),\n\t\tName:              fastly.ToPointer(\"logs\"),\n\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\tFormatVersion:     fastly.ToPointer(2),\n\t\tMessageType:       fastly.ToPointer(\"classic\"),\n\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\tUser:              fastly.ToPointer(\"123456\"),\n\t\tURL:               fastly.ToPointer(\"https://test123.grafana.net\"),\n\t\tToken:             fastly.ToPointer(\"testtoken\"),\n\t\tIndex:             fastly.ToPointer(\"{\\\"label\\\": \\\"value\\\"}\"),\n\t\tProcessingRegion:  fastly.ToPointer(\"us\"),\n\t}, nil\n}\n\nfunc getGrafanaCloudLogsError(_ context.Context, _ *fastly.GetGrafanaCloudLogsInput) (*fastly.GrafanaCloudLogs, error) {\n\treturn nil, errTest\n}\n\nvar describeGrafanaCloudLogsOutput = \"\\n\" + strings.TrimSpace(`\nFormat: %h %l %u %t \"%r\" %>s %b\nFormat version: 2\nIndex: {\"label\": \"value\"}\nMessage type: classic\nName: logs\nPlacement: none\nProcessing region: us\nResponse condition: Prevent default logging\nService ID: 123\nToken: testtoken\nURL: https://test123.grafana.net\nUser: 123456\nVersion: 1\n`) + \"\\n\"\n\nfunc updateGrafanaCloudLogsOK(_ context.Context, i *fastly.UpdateGrafanaCloudLogsInput) (*fastly.GrafanaCloudLogs, error) {\n\treturn &fastly.GrafanaCloudLogs{\n\t\tServiceID:         fastly.ToPointer(i.ServiceID),\n\t\tServiceVersion:    fastly.ToPointer(i.ServiceVersion),\n\t\tName:              fastly.ToPointer(\"log\"),\n\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\tFormatVersion:     fastly.ToPointer(2),\n\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\tMessageType:       fastly.ToPointer(\"classic\"),\n\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\tUser:              fastly.ToPointer(\"123456\"),\n\t\tURL:               fastly.ToPointer(\"https://test123.grafana.net\"),\n\t\tToken:             fastly.ToPointer(\"testtoken\"),\n\t\tIndex:             fastly.ToPointer(\"{\\\"label\\\": \\\"value\\\"}\"),\n\t}, nil\n}\n\nfunc updateGrafanaCloudLogsError(_ context.Context, _ *fastly.UpdateGrafanaCloudLogsInput) (*fastly.GrafanaCloudLogs, error) {\n\treturn nil, errTest\n}\n\nfunc deleteGrafanaCloudLogsOK(_ context.Context, _ *fastly.DeleteGrafanaCloudLogsInput) error {\n\treturn nil\n}\n\nfunc deleteGrafanaCloudLogsError(_ context.Context, _ *fastly.DeleteGrafanaCloudLogsInput) error {\n\treturn errTest\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/grafanacloudlogs/grafanacloudlogs_test.go",
    "content": "package grafanacloudlogs_test\n\nimport (\n\t\"bytes\"\n\t\"testing\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/service/logging/grafanacloudlogs\"\n\t\"github.com/fastly/cli/pkg/config\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/manifest\"\n\t\"github.com/fastly/cli/pkg/mock\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n)\n\nfunc TestCreateGrafanaCloudLogsInput(t *testing.T) {\n\tfor _, testcase := range []struct {\n\t\tname      string\n\t\tcmd       *grafanacloudlogs.CreateCommand\n\t\twant      *fastly.CreateGrafanaCloudLogsInput\n\t\twantError string\n\t}{\n\t\t{\n\t\t\tname: \"required values set flag serviceID\",\n\t\t\tcmd:  createCommandRequired(),\n\t\t\twant: &fastly.CreateGrafanaCloudLogsInput{\n\t\t\t\tServiceID:      \"123\",\n\t\t\t\tServiceVersion: 4,\n\t\t\t\tName:           fastly.ToPointer(\"log\"),\n\t\t\t\tUser:           fastly.ToPointer(\"123456\"),\n\t\t\t\tIndex:          fastly.ToPointer(\"{\\\"label\\\": \\\"value\\\"}\"),\n\t\t\t\tURL:            fastly.ToPointer(\"https://test123.grafana.net\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"all values set flag serviceID\",\n\t\t\tcmd:  createCommandAll(),\n\t\t\twant: &fastly.CreateGrafanaCloudLogsInput{\n\t\t\t\tServiceID:         \"123\",\n\t\t\t\tServiceVersion:    4,\n\t\t\t\tFormatVersion:     fastly.ToPointer(2),\n\t\t\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\t\t\tMessageType:       fastly.ToPointer(\"classic\"),\n\t\t\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\t\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\t\t\tName:              fastly.ToPointer(\"log\"),\n\t\t\t\tUser:              fastly.ToPointer(\"123456\"),\n\t\t\t\tIndex:             fastly.ToPointer(\"{\\\"label\\\": \\\"value\\\"}\"),\n\t\t\t\tURL:               fastly.ToPointer(\"https://test123.grafana.net\"),\n\t\t\t\tProcessingRegion:  fastly.ToPointer(\"eu\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:      \"error missing serviceID\",\n\t\t\tcmd:       createCommandMissingServiceID(),\n\t\t\twant:      nil,\n\t\t\twantError: errors.ErrNoServiceID.Error(),\n\t\t},\n\t} {\n\t\tt.Run(testcase.name, func(t *testing.T) {\n\t\t\tif testcase.wantError == errors.ErrNoServiceID.Error() {\n\t\t\t\tt.Setenv(\"FASTLY_SERVICE_ID\", \"\")\n\t\t\t}\n\t\t\tvar bs []byte\n\t\t\tout := bytes.NewBuffer(bs)\n\t\t\tverboseMode := true\n\n\t\t\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\t\t\tAutoCloneFlag:      testcase.cmd.AutoClone,\n\t\t\t\tAPIClient:          testcase.cmd.Globals.APIClient,\n\t\t\t\tManifest:           testcase.cmd.Manifest,\n\t\t\t\tOut:                out,\n\t\t\t\tServiceVersionFlag: testcase.cmd.ServiceVersion,\n\t\t\t\tVerboseMode:        verboseMode,\n\t\t\t})\n\n\t\t\tswitch {\n\t\t\tcase err != nil && testcase.wantError == \"\":\n\t\t\t\tt.Fatalf(\"unexpected error getting service details: %v\", err)\n\t\t\t\treturn\n\t\t\tcase err != nil && testcase.wantError != \"\":\n\t\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\t\treturn\n\t\t\tcase err == nil && testcase.wantError != \"\":\n\t\t\t\tt.Fatalf(\"expected error, have nil (service details: %s, %d)\", serviceID, serviceVersion.Number)\n\t\t\tcase err == nil && testcase.wantError == \"\":\n\t\t\t\thave, err := testcase.cmd.ConstructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\t\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\t\ttestutil.AssertEqual(t, testcase.want, have)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestUpdateGrafanaCloudLogsInput(t *testing.T) {\n\tscenarios := []struct {\n\t\tname      string\n\t\tcmd       *grafanacloudlogs.UpdateCommand\n\t\tapi       mock.API\n\t\twant      *fastly.UpdateGrafanaCloudLogsInput\n\t\twantError string\n\t}{\n\t\t{\n\t\t\tname: \"no updates\",\n\t\t\tcmd:  updateCommandNoUpdates(),\n\t\t\tapi: mock.API{\n\t\t\t\tGetVersionFn:          testutil.GetVersion,\n\t\t\t\tCloneVersionFn:        testutil.CloneVersionResult(4),\n\t\t\t\tGetGrafanaCloudLogsFn: getGrafanaCloudLogsOK,\n\t\t\t},\n\t\t\twant: &fastly.UpdateGrafanaCloudLogsInput{\n\t\t\t\tServiceID:      \"123\",\n\t\t\t\tServiceVersion: 4,\n\t\t\t\tName:           \"log\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"all values set flag serviceID\",\n\t\t\tcmd:  updateCommandAll(),\n\t\t\tapi: mock.API{\n\t\t\t\tGetVersionFn:          testutil.GetVersion,\n\t\t\t\tCloneVersionFn:        testutil.CloneVersionResult(4),\n\t\t\t\tGetGrafanaCloudLogsFn: getGrafanaCloudLogsOK,\n\t\t\t},\n\t\t\twant: &fastly.UpdateGrafanaCloudLogsInput{\n\t\t\t\tServiceID:         \"123\",\n\t\t\t\tServiceVersion:    4,\n\t\t\t\tName:              \"log\",\n\t\t\t\tNewName:           fastly.ToPointer(\"new1\"),\n\t\t\t\tUser:              fastly.ToPointer(\"new3\"),\n\t\t\t\tFormatVersion:     fastly.ToPointer(3),\n\t\t\t\tFormat:            fastly.ToPointer(\"new6\"),\n\t\t\t\tResponseCondition: fastly.ToPointer(\"new7\"),\n\t\t\t\tPlacement:         fastly.ToPointer(\"new9\"),\n\t\t\t\tMessageType:       fastly.ToPointer(\"new10\"),\n\t\t\t\tProcessingRegion:  fastly.ToPointer(\"eu\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:      \"error missing serviceID\",\n\t\t\tcmd:       updateCommandMissingServiceID(),\n\t\t\twant:      nil,\n\t\t\twantError: errors.ErrNoServiceID.Error(),\n\t\t},\n\t}\n\tfor testcaseIdx := range scenarios {\n\t\ttestcase := &scenarios[testcaseIdx]\n\t\tt.Run(testcase.name, func(t *testing.T) {\n\t\t\tif testcase.wantError == errors.ErrNoServiceID.Error() {\n\t\t\t\tt.Setenv(\"FASTLY_SERVICE_ID\", \"\")\n\t\t\t}\n\t\t\ttestcase.cmd.Globals.APIClient = testcase.api\n\n\t\t\tvar bs []byte\n\t\t\tout := bytes.NewBuffer(bs)\n\t\t\tverboseMode := true\n\n\t\t\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\t\t\tAutoCloneFlag:      testcase.cmd.AutoClone,\n\t\t\t\tAPIClient:          testcase.api,\n\t\t\t\tManifest:           testcase.cmd.Manifest,\n\t\t\t\tOut:                out,\n\t\t\t\tServiceVersionFlag: testcase.cmd.ServiceVersion,\n\t\t\t\tVerboseMode:        verboseMode,\n\t\t\t})\n\n\t\t\tswitch {\n\t\t\tcase err != nil && testcase.wantError == \"\":\n\t\t\t\tt.Fatalf(\"unexpected error getting service details: %v\", err)\n\t\t\t\treturn\n\t\t\tcase err != nil && testcase.wantError != \"\":\n\t\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\t\treturn\n\t\t\tcase err == nil && testcase.wantError != \"\":\n\t\t\t\tt.Fatalf(\"expected error, have nil (service details: %s, %d)\", serviceID, serviceVersion.Number)\n\t\t\tcase err == nil && testcase.wantError == \"\":\n\t\t\t\thave, err := testcase.cmd.ConstructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\t\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\t\ttestutil.AssertEqual(t, testcase.want, have)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc createCommandRequired() *grafanacloudlogs.CreateCommand {\n\tvar b bytes.Buffer\n\n\tg := global.Data{\n\t\tConfig: config.File{},\n\t\tEnv:    config.Environment{},\n\t\tOutput: &b,\n\t}\n\tg.APIClient, _ = mock.APIClient(mock.API{\n\t\tGetVersionFn:   testutil.GetVersion,\n\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t})(\"token\", \"endpoint\", false)\n\n\treturn &grafanacloudlogs.CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: &g,\n\t\t},\n\t\tManifest: manifest.Data{\n\t\t\tFlag: manifest.Flag{\n\t\t\t\tServiceID: \"123\",\n\t\t\t},\n\t\t},\n\t\tServiceVersion: argparser.OptionalServiceVersion{\n\t\t\tOptionalString: argparser.OptionalString{Value: \"1\"},\n\t\t},\n\t\tAutoClone: argparser.OptionalAutoClone{\n\t\t\tOptionalBool: argparser.OptionalBool{\n\t\t\t\tOptional: argparser.Optional{\n\t\t\t\t\tWasSet: true,\n\t\t\t\t},\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t},\n\t\tEndpointName: argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"log\"},\n\t\tUser:         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"123456\"},\n\t\tIndex:        argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"{\\\"label\\\": \\\"value\\\"}\"},\n\t\tURL:          argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"https://test123.grafana.net\"},\n\t}\n}\n\nfunc createCommandAll() *grafanacloudlogs.CreateCommand {\n\tvar b bytes.Buffer\n\n\tg := global.Data{\n\t\tConfig: config.File{},\n\t\tEnv:    config.Environment{},\n\t\tOutput: &b,\n\t}\n\tg.APIClient, _ = mock.APIClient(mock.API{\n\t\tGetVersionFn:   testutil.GetVersion,\n\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t})(\"token\", \"endpoint\", false)\n\n\treturn &grafanacloudlogs.CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: &g,\n\t\t},\n\t\tManifest: manifest.Data{\n\t\t\tFlag: manifest.Flag{\n\t\t\t\tServiceID: \"123\",\n\t\t\t},\n\t\t},\n\t\tServiceVersion: argparser.OptionalServiceVersion{\n\t\t\tOptionalString: argparser.OptionalString{Value: \"1\"},\n\t\t},\n\t\tAutoClone: argparser.OptionalAutoClone{\n\t\t\tOptionalBool: argparser.OptionalBool{\n\t\t\t\tOptional: argparser.Optional{\n\t\t\t\t\tWasSet: true,\n\t\t\t\t},\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t},\n\t\tEndpointName:      argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"log\"},\n\t\tFormat:            argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: `%h %l %u %t \"%r\" %>s %b`},\n\t\tFormatVersion:     argparser.OptionalInt{Optional: argparser.Optional{WasSet: true}, Value: 2},\n\t\tTimestampFormat:   argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"%Y-%m-%dT%H:%M:%S.000\"},\n\t\tMessageType:       argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"classic\"},\n\t\tResponseCondition: argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"Prevent default logging\"},\n\t\tPlacement:         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"none\"},\n\t\tUser:              argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"123456\"},\n\t\tIndex:             argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"{\\\"label\\\": \\\"value\\\"}\"},\n\t\tURL:               argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"https://test123.grafana.net\"},\n\t\tProcessingRegion:  argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"eu\"},\n\t}\n}\n\nfunc createCommandMissingServiceID() *grafanacloudlogs.CreateCommand {\n\tres := createCommandAll()\n\tres.Manifest = manifest.Data{}\n\tres.ServiceVersion = argparser.OptionalServiceVersion{}\n\treturn res\n}\n\nfunc updateCommandNoUpdates() *grafanacloudlogs.UpdateCommand {\n\tvar b bytes.Buffer\n\n\tg := global.Data{\n\t\tConfig: config.File{},\n\t\tEnv:    config.Environment{},\n\t\tOutput: &b,\n\t}\n\n\treturn &grafanacloudlogs.UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: &g,\n\t\t},\n\t\tManifest: manifest.Data{\n\t\t\tFlag: manifest.Flag{\n\t\t\t\tServiceID: \"123\",\n\t\t\t},\n\t\t},\n\t\tEndpointName: \"log\",\n\t\tServiceVersion: argparser.OptionalServiceVersion{\n\t\t\tOptionalString: argparser.OptionalString{Value: \"1\"},\n\t\t},\n\t\tAutoClone: argparser.OptionalAutoClone{\n\t\t\tOptionalBool: argparser.OptionalBool{\n\t\t\t\tOptional: argparser.Optional{\n\t\t\t\t\tWasSet: true,\n\t\t\t\t},\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t},\n\t}\n}\n\nfunc updateCommandAll() *grafanacloudlogs.UpdateCommand {\n\tvar b bytes.Buffer\n\n\tg := global.Data{\n\t\tConfig: config.File{},\n\t\tEnv:    config.Environment{},\n\t\tOutput: &b,\n\t}\n\n\treturn &grafanacloudlogs.UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: &g,\n\t\t},\n\t\tManifest: manifest.Data{\n\t\t\tFlag: manifest.Flag{\n\t\t\t\tServiceID: \"123\",\n\t\t\t},\n\t\t},\n\t\tEndpointName: \"log\",\n\t\tServiceVersion: argparser.OptionalServiceVersion{\n\t\t\tOptionalString: argparser.OptionalString{Value: \"1\"},\n\t\t},\n\t\tAutoClone: argparser.OptionalAutoClone{\n\t\t\tOptionalBool: argparser.OptionalBool{\n\t\t\t\tOptional: argparser.Optional{\n\t\t\t\t\tWasSet: true,\n\t\t\t\t},\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t},\n\t\tNewName:           argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new1\"},\n\t\tUser:              argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new3\"},\n\t\tFormat:            argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new6\"},\n\t\tFormatVersion:     argparser.OptionalInt{Optional: argparser.Optional{WasSet: true}, Value: 3},\n\t\tResponseCondition: argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new7\"},\n\t\tPlacement:         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new9\"},\n\t\tMessageType:       argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new10\"},\n\t\tProcessingRegion:  argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"eu\"},\n\t}\n}\n\nfunc updateCommandMissingServiceID() *grafanacloudlogs.UpdateCommand {\n\tres := updateCommandAll()\n\tres.Manifest = manifest.Data{}\n\tres.ServiceVersion = argparser.OptionalServiceVersion{}\n\treturn res\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/grafanacloudlogs/list.go",
    "content": "package grafanacloudlogs\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// ListCommand calls the Fastly API to list Grafana Cloud Logs logging endpoints.\ntype ListCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tInput          fastly.ListGrafanaCloudLogsInput\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n}\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"list\", \"List Grafana Cloud Logs endpoints on a Fastly service version\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *ListCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fsterr.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tc.Input.ServiceID = serviceID\n\tc.Input.ServiceVersion = fastly.ToValue(serviceVersion.Number)\n\n\to, err := c.Globals.APIClient.ListGrafanaCloudLogs(context.TODO(), &c.Input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\tif !c.Globals.Verbose() {\n\t\ttw := text.NewTable(out)\n\t\ttw.AddHeader(\"SERVICE\", \"VERSION\", \"NAME\")\n\t\tfor _, gcs := range o {\n\t\t\ttw.AddLine(\n\t\t\t\tfastly.ToValue(gcs.ServiceID),\n\t\t\t\tfastly.ToValue(gcs.ServiceVersion),\n\t\t\t\tfastly.ToValue(gcs.Name),\n\t\t\t)\n\t\t}\n\t\ttw.Print()\n\t\treturn nil\n\t}\n\n\tfmt.Fprintf(out, \"Version: %d\\n\", c.Input.ServiceVersion)\n\tfor i, grafanacloudlogs := range o {\n\t\tfmt.Fprintf(out, \"\\tGrafanaCloudLogs %d/%d\\n\", i+1, len(o))\n\t\tfmt.Fprintf(out, \"\\t\\tService ID: %s\\n\", fastly.ToValue(grafanacloudlogs.ServiceID))\n\t\tfmt.Fprintf(out, \"\\t\\tVersion: %d\\n\", fastly.ToValue(grafanacloudlogs.ServiceVersion))\n\t\tfmt.Fprintf(out, \"\\t\\tName: %s\\n\", fastly.ToValue(grafanacloudlogs.Name))\n\t\tfmt.Fprintf(out, \"\\t\\tFormat: %s\\n\", fastly.ToValue(grafanacloudlogs.Format))\n\t\tfmt.Fprintf(out, \"\\t\\tFormat version: %d\\n\", fastly.ToValue(grafanacloudlogs.FormatVersion))\n\t\tfmt.Fprintf(out, \"\\t\\tResponse condition: %s\\n\", fastly.ToValue(grafanacloudlogs.ResponseCondition))\n\t\tfmt.Fprintf(out, \"\\t\\tMessage type: %s\\n\", fastly.ToValue(grafanacloudlogs.MessageType))\n\t\tfmt.Fprintf(out, \"\\t\\tPlacement: %s\\n\", fastly.ToValue(grafanacloudlogs.Placement))\n\t\tfmt.Fprintf(out, \"\\t\\tUser: %s\\n\", fastly.ToValue(grafanacloudlogs.User))\n\t\tfmt.Fprintf(out, \"\\t\\tURL: %s\\n\", fastly.ToValue(grafanacloudlogs.URL))\n\t\tfmt.Fprintf(out, \"\\t\\tToken: %s\\n\", fastly.ToValue(grafanacloudlogs.Token))\n\t\tfmt.Fprintf(out, \"\\t\\tIndex: %s\\n\", fastly.ToValue(grafanacloudlogs.Index))\n\t\tfmt.Fprintf(out, \"\\t\\tProcessing region: %s\\n\", fastly.ToValue(grafanacloudlogs.ProcessingRegion))\n\t}\n\tfmt.Fprintln(out)\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/grafanacloudlogs/root.go",
    "content": "package grafanacloudlogs\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"grafanacloudlogs\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manipulate Fastly service version Grafana Cloud Logs logging endpoints\")\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/grafanacloudlogs/update.go",
    "content": "package grafanacloudlogs\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/service/logging/logflags\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/manifest\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// UpdateCommand calls the Fastly API to update a Grafana Cloud Logs logging endpoint.\ntype UpdateCommand struct {\n\targparser.Base\n\tManifest manifest.Data\n\n\t// Required.\n\tEndpointName   string // Can't shadow argparser.Base method Name().\n\tServiceName    argparser.OptionalServiceNameID\n\tServiceVersion argparser.OptionalServiceVersion\n\tUser           argparser.OptionalString\n\tURL            argparser.OptionalString\n\tIndex          argparser.OptionalString\n\tToken          argparser.OptionalString\n\n\t// Optional.\n\tAutoClone         argparser.OptionalAutoClone\n\tFormat            argparser.OptionalString\n\tFormatVersion     argparser.OptionalInt\n\tMessageType       argparser.OptionalString\n\tNewName           argparser.OptionalString\n\tPlacement         argparser.OptionalString\n\tProcessingRegion  argparser.OptionalString\n\tResponseCondition argparser.OptionalString\n}\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"update\", \"Update a Grafana Cloud Logs logging endpoint on a Fastly service version\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"The name of the Grafana Cloud Logs logging object\").Short('n').Required().StringVar(&c.EndpointName)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.ServiceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.AutoClone.Set,\n\t\tDst:    &c.AutoClone.Value,\n\t})\n\tlogflags.Format(c.CmdClause, &c.Format)\n\tlogflags.FormatVersion(c.CmdClause, &c.FormatVersion)\n\tlogflags.MessageType(c.CmdClause, &c.MessageType)\n\tc.CmdClause.Flag(\"new-name\", \"New name of the Grafana Cloud Logs logging object\").Action(c.NewName.Set).StringVar(&c.NewName.Value)\n\tlogflags.Placement(c.CmdClause, &c.Placement)\n\tlogflags.ProcessingRegion(c.CmdClause, &c.ProcessingRegion, \"Grafana Cloud Logs\")\n\tlogflags.ResponseCondition(c.CmdClause, &c.ResponseCondition)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.ServiceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.ServiceName.Value,\n\t})\n\tc.CmdClause.Flag(\"user\", \"Your Grafana Cloud Logs User ID.\").Action(c.User.Set).StringVar(&c.User.Value)\n\tc.CmdClause.Flag(\"auth-token\", \"Your Grafana Access Policy Token\").Action(c.Token.Set).StringVar(&c.Token.Value)\n\tc.CmdClause.Flag(\"url\", \"URL of your Grafana Instance\").Action(c.URL.Set).StringVar(&c.URL.Value)\n\tc.CmdClause.Flag(\"index\", \"Stream identifier\").Action(c.Index.Set).StringVar(&c.Index.Value)\n\treturn &c\n}\n\n// ConstructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *UpdateCommand) ConstructInput(serviceID string, serviceVersion int) (*fastly.UpdateGrafanaCloudLogsInput, error) {\n\tinput := fastly.UpdateGrafanaCloudLogsInput{\n\t\tServiceID:      serviceID,\n\t\tServiceVersion: serviceVersion,\n\t\tName:           c.EndpointName,\n\t}\n\n\tif c.Format.WasSet {\n\t\tinput.Format = fastly.ToPointer(argparser.Content(c.Format.Value))\n\t}\n\tif c.FormatVersion.WasSet {\n\t\tinput.FormatVersion = &c.FormatVersion.Value\n\t}\n\tif c.MessageType.WasSet {\n\t\tinput.MessageType = &c.MessageType.Value\n\t}\n\tif c.NewName.WasSet {\n\t\tinput.NewName = &c.NewName.Value\n\t}\n\tif c.Placement.WasSet {\n\t\tinput.Placement = &c.Placement.Value\n\t}\n\tif c.ProcessingRegion.WasSet {\n\t\tinput.ProcessingRegion = &c.ProcessingRegion.Value\n\t}\n\tif c.ResponseCondition.WasSet {\n\t\tinput.ResponseCondition = &c.ResponseCondition.Value\n\t}\n\tif c.User.WasSet {\n\t\tinput.User = &c.User.Value\n\t}\n\tif c.URL.WasSet {\n\t\tinput.URL = &c.URL.Value\n\t}\n\tif c.Token.WasSet {\n\t\tinput.Token = &c.Token.Value\n\t}\n\tif c.Index.WasSet {\n\t\tinput.Index = &c.Index.Value\n\t}\n\n\treturn &input, nil\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *UpdateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(false),\n\t\tLocked:             optional.Of(false),\n\t\tAutoCloneFlag:      c.AutoClone,\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.ServiceName,\n\t\tServiceVersionFlag: c.ServiceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": errors.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tinput, err := c.ConstructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tgrafanacloudlogs, err := c.Globals.APIClient.UpdateGrafanaCloudLogs(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\ttext.Success(\n\t\tout,\n\t\t\"Updated Grafana Cloud Logs logging endpoint %s (service %s version %d)\",\n\t\tfastly.ToValue(grafanacloudlogs.Name),\n\t\tfastly.ToValue(grafanacloudlogs.ServiceID),\n\t\tfastly.ToValue(grafanacloudlogs.ServiceVersion),\n\t)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/heroku/create.go",
    "content": "package heroku\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/service/logging/logflags\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/manifest\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// CreateCommand calls the Fastly API to create a Heroku logging endpoint.\ntype CreateCommand struct {\n\targparser.Base\n\tManifest manifest.Data\n\n\t// Required.\n\tServiceName    argparser.OptionalServiceNameID\n\tServiceVersion argparser.OptionalServiceVersion\n\n\t// Optional.\n\tAutoClone         argparser.OptionalAutoClone\n\tEndpointName      argparser.OptionalString // Can't shadow argparser.Base method Name().\n\tFormat            argparser.OptionalString\n\tFormatVersion     argparser.OptionalInt\n\tPlacement         argparser.OptionalString\n\tProcessingRegion  argparser.OptionalString\n\tResponseCondition argparser.OptionalString\n\tToken             argparser.OptionalString\n\tURL               argparser.OptionalString\n}\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"create\", \"Create a Heroku logging endpoint on a Fastly service version\").Alias(\"add\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"The name of the Heroku logging object. Used as a primary key for API access\").Short('n').Action(c.EndpointName.Set).StringVar(&c.EndpointName.Value)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.ServiceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.CmdClause.Flag(\"auth-token\", \"The token to use for authentication (https://devcenter.heroku.com/articles/add-on-partner-log-integration)\").Action(c.Token.Set).StringVar(&c.Token.Value)\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.AutoClone.Set,\n\t\tDst:    &c.AutoClone.Value,\n\t})\n\tlogflags.Format(c.CmdClause, &c.Format)\n\tlogflags.FormatVersion(c.CmdClause, &c.FormatVersion)\n\tlogflags.Placement(c.CmdClause, &c.Placement)\n\tlogflags.ProcessingRegion(c.CmdClause, &c.ProcessingRegion, \"Heroku\")\n\tlogflags.ResponseCondition(c.CmdClause, &c.ResponseCondition)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.ServiceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.ServiceName.Value,\n\t})\n\tc.CmdClause.Flag(\"url\", \"The url to stream logs to\").Action(c.URL.Set).StringVar(&c.URL.Value)\n\treturn &c\n}\n\n// ConstructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *CreateCommand) ConstructInput(serviceID string, serviceVersion int) (*fastly.CreateHerokuInput, error) {\n\tvar input fastly.CreateHerokuInput\n\n\tinput.ServiceID = serviceID\n\tinput.ServiceVersion = serviceVersion\n\tif c.EndpointName.WasSet {\n\t\tinput.Name = &c.EndpointName.Value\n\t}\n\tif c.Token.WasSet {\n\t\tinput.Token = &c.Token.Value\n\t}\n\tif c.URL.WasSet {\n\t\tinput.URL = &c.URL.Value\n\t}\n\n\tif c.Format.WasSet {\n\t\tinput.Format = fastly.ToPointer(argparser.Content(c.Format.Value))\n\t}\n\n\tif c.FormatVersion.WasSet {\n\t\tinput.FormatVersion = &c.FormatVersion.Value\n\t}\n\n\tif c.ResponseCondition.WasSet {\n\t\tinput.ResponseCondition = &c.ResponseCondition.Value\n\t}\n\n\tif c.Placement.WasSet {\n\t\tinput.Placement = &c.Placement.Value\n\t}\n\n\tif c.ProcessingRegion.WasSet {\n\t\tinput.ProcessingRegion = &c.ProcessingRegion.Value\n\t}\n\n\treturn &input, nil\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *CreateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(false),\n\t\tLocked:             optional.Of(false),\n\t\tAutoCloneFlag:      c.AutoClone,\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.ServiceName,\n\t\tServiceVersionFlag: c.ServiceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": errors.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tinput, err := c.ConstructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\td, err := c.Globals.APIClient.CreateHeroku(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\ttext.Success(\n\t\tout,\n\t\t\"Created Heroku logging endpoint %s (service %s version %d)\",\n\t\tfastly.ToValue(d.Name),\n\t\tfastly.ToValue(d.ServiceID),\n\t\tfastly.ToValue(d.ServiceVersion),\n\t)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/heroku/delete.go",
    "content": "package heroku\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DeleteCommand calls the Fastly API to delete a Heroku logging endpoint.\ntype DeleteCommand struct {\n\targparser.Base\n\tInput          fastly.DeleteHerokuInput\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n\tautoClone      argparser.OptionalAutoClone\n}\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"delete\", \"Delete a Heroku logging endpoint on a Fastly service version\").Alias(\"remove\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"The name of the Heroku logging object\").Short('n').Required().StringVar(&c.Input.Name)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.autoClone.Set,\n\t\tDst:    &c.autoClone.Value,\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DeleteCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(false),\n\t\tLocked:             optional.Of(false),\n\t\tAutoCloneFlag:      c.autoClone,\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": errors.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tc.Input.ServiceID = serviceID\n\tc.Input.ServiceVersion = fastly.ToValue(serviceVersion.Number)\n\n\tif err := c.Globals.APIClient.DeleteHeroku(context.TODO(), &c.Input); err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Deleted Heroku logging endpoint %s (service %s version %d)\", c.Input.Name, c.Input.ServiceID, c.Input.ServiceVersion)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/heroku/describe.go",
    "content": "package heroku\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DescribeCommand calls the Fastly API to describe a Heroku logging endpoint.\ntype DescribeCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tInput          fastly.GetHerokuInput\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n}\n\n// NewDescribeCommand returns a usable command registered under the parent.\nfunc NewDescribeCommand(parent argparser.Registerer, g *global.Data) *DescribeCommand {\n\tc := DescribeCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"describe\", \"Show detailed information about a Heroku logging endpoint on a Fastly service version\").Alias(\"get\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"The name of the Heroku logging object\").Short('n').Required().StringVar(&c.Input.Name)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DescribeCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fsterr.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tc.Input.ServiceID = serviceID\n\tc.Input.ServiceVersion = fastly.ToValue(serviceVersion.Number)\n\n\to, err := c.Globals.APIClient.GetHeroku(context.TODO(), &c.Input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\tlines := text.Lines{\n\t\t\"Format version\":     fastly.ToValue(o.FormatVersion),\n\t\t\"Format\":             fastly.ToValue(o.Format),\n\t\t\"Name\":               fastly.ToValue(o.Name),\n\t\t\"Placement\":          fastly.ToValue(o.Placement),\n\t\t\"Processing region\":  fastly.ToValue(o.ProcessingRegion),\n\t\t\"Response condition\": fastly.ToValue(o.ResponseCondition),\n\t\t\"Token\":              fastly.ToValue(o.Token),\n\t\t\"URL\":                fastly.ToValue(o.URL),\n\t\t\"Version\":            fastly.ToValue(o.ServiceVersion),\n\t}\n\tif !c.Globals.Verbose() {\n\t\tlines[\"Service ID\"] = fastly.ToValue(o.ServiceID)\n\t}\n\ttext.PrintLines(out, lines)\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/heroku/doc.go",
    "content": "// Package heroku contains commands to inspect and manipulate Fastly service Heroku\n// logging endpoints.\npackage heroku\n"
  },
  {
    "path": "pkg/commands/service/logging/heroku/heroku_integration_test.go",
    "content": "package heroku_test\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/mock\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n\n\troot \"github.com/fastly/cli/pkg/commands/service\"\n\tparent \"github.com/fastly/cli/pkg/commands/service/logging\"\n\tsub \"github.com/fastly/cli/pkg/commands/service/logging/heroku\"\n)\n\nfunc TestHerokuCreate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name log --auth-token abc --url example.com --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tCreateHerokuFn: createHerokuOK,\n\t\t\t},\n\t\t\tWantOutput: \"Created Heroku logging endpoint log (service 123 version 4)\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name log --auth-token abc --url example.com --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tCreateHerokuFn: createHerokuError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"create\"}, scenarios)\n}\n\nfunc TestHerokuList(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:  testutil.GetVersion,\n\t\t\t\tListHerokusFn: listHerokusOK,\n\t\t\t},\n\t\t\tWantOutput: listHerokusShortOutput,\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --verbose\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:  testutil.GetVersion,\n\t\t\t\tListHerokusFn: listHerokusOK,\n\t\t\t},\n\t\t\tWantOutput: listHerokusVerboseOutput,\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 -v\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:  testutil.GetVersion,\n\t\t\t\tListHerokusFn: listHerokusOK,\n\t\t\t},\n\t\t\tWantOutput: listHerokusVerboseOutput,\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:  testutil.GetVersion,\n\t\t\t\tListHerokusFn: listHerokusError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"list\"}, scenarios)\n}\n\nfunc TestHerokuDescribe(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"--service-id 123 --version 1\",\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tGetHerokuFn:  getHerokuError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tGetHerokuFn:  getHerokuOK,\n\t\t\t},\n\t\t\tWantOutput: describeHerokuOutput,\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"describe\"}, scenarios)\n}\n\nfunc TestHerokuUpdate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"--service-id 123 --version 1 --new-name log\",\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs --new-name log --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tUpdateHerokuFn: updateHerokuError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs --new-name log --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tUpdateHerokuFn: updateHerokuOK,\n\t\t\t},\n\t\t\tWantOutput: \"Updated Heroku logging endpoint log (service 123 version 4)\",\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"update\"}, scenarios)\n}\n\nfunc TestHerokuDelete(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"--service-id 123 --version 1\",\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tDeleteHerokuFn: deleteHerokuError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tDeleteHerokuFn: deleteHerokuOK,\n\t\t\t},\n\t\t\tWantOutput: \"Deleted Heroku logging endpoint logs (service 123 version 4)\",\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"delete\"}, scenarios)\n}\n\nvar errTest = errors.New(\"fixture error\")\n\nfunc createHerokuOK(_ context.Context, i *fastly.CreateHerokuInput) (*fastly.Heroku, error) {\n\ts := fastly.Heroku{\n\t\tServiceID:      fastly.ToPointer(i.ServiceID),\n\t\tServiceVersion: fastly.ToPointer(i.ServiceVersion),\n\t}\n\n\tif i.Name != nil {\n\t\ts.Name = i.Name\n\t}\n\n\treturn &s, nil\n}\n\nfunc createHerokuError(_ context.Context, _ *fastly.CreateHerokuInput) (*fastly.Heroku, error) {\n\treturn nil, errTest\n}\n\nfunc listHerokusOK(_ context.Context, i *fastly.ListHerokusInput) ([]*fastly.Heroku, error) {\n\treturn []*fastly.Heroku{\n\t\t{\n\t\t\tServiceID:         fastly.ToPointer(i.ServiceID),\n\t\t\tServiceVersion:    fastly.ToPointer(i.ServiceVersion),\n\t\t\tName:              fastly.ToPointer(\"logs\"),\n\t\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\t\tFormatVersion:     fastly.ToPointer(2),\n\t\t\tURL:               fastly.ToPointer(\"example.com\"),\n\t\t\tToken:             fastly.ToPointer(\"abc\"),\n\t\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\t\tProcessingRegion:  fastly.ToPointer(\"us\"),\n\t\t},\n\t\t{\n\t\t\tServiceID:         fastly.ToPointer(i.ServiceID),\n\t\t\tServiceVersion:    fastly.ToPointer(i.ServiceVersion),\n\t\t\tName:              fastly.ToPointer(\"analytics\"),\n\t\t\tURL:               fastly.ToPointer(\"bar.com\"),\n\t\t\tToken:             fastly.ToPointer(\"abc\"),\n\t\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\t\tFormatVersion:     fastly.ToPointer(2),\n\t\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\t\tProcessingRegion:  fastly.ToPointer(\"us\"),\n\t\t},\n\t}, nil\n}\n\nfunc listHerokusError(_ context.Context, _ *fastly.ListHerokusInput) ([]*fastly.Heroku, error) {\n\treturn nil, errTest\n}\n\nvar listHerokusShortOutput = strings.TrimSpace(`\nSERVICE  VERSION  NAME\n123      1        logs\n123      1        analytics\n`) + \"\\n\"\n\nvar listHerokusVerboseOutput = strings.TrimSpace(`\nFastly API endpoint: https://api.fastly.com\nFastly API token provided via config file (auth: user)\n\nService ID (via --service-id): 123\n\nVersion: 1\n\tHeroku 1/2\n\t\tService ID: 123\n\t\tVersion: 1\n\t\tName: logs\n\t\tURL: example.com\n\t\tToken: abc\n\t\tFormat: %h %l %u %t \"%r\" %>s %b\n\t\tFormat version: 2\n\t\tResponse condition: Prevent default logging\n\t\tPlacement: none\n\t\tProcessing region: us\n\tHeroku 2/2\n\t\tService ID: 123\n\t\tVersion: 1\n\t\tName: analytics\n\t\tURL: bar.com\n\t\tToken: abc\n\t\tFormat: %h %l %u %t \"%r\" %>s %b\n\t\tFormat version: 2\n\t\tResponse condition: Prevent default logging\n\t\tPlacement: none\n\t\tProcessing region: us\n`) + \"\\n\\n\"\n\nfunc getHerokuOK(_ context.Context, i *fastly.GetHerokuInput) (*fastly.Heroku, error) {\n\treturn &fastly.Heroku{\n\t\tServiceID:         fastly.ToPointer(i.ServiceID),\n\t\tServiceVersion:    fastly.ToPointer(i.ServiceVersion),\n\t\tName:              fastly.ToPointer(\"logs\"),\n\t\tURL:               fastly.ToPointer(\"example.com\"),\n\t\tToken:             fastly.ToPointer(\"abc\"),\n\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\tFormatVersion:     fastly.ToPointer(2),\n\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\tProcessingRegion:  fastly.ToPointer(\"us\"),\n\t}, nil\n}\n\nfunc getHerokuError(_ context.Context, _ *fastly.GetHerokuInput) (*fastly.Heroku, error) {\n\treturn nil, errTest\n}\n\nvar describeHerokuOutput = \"\\n\" + strings.TrimSpace(`\nFormat: %h %l %u %t \"%r\" %>s %b\nFormat version: 2\nName: logs\nPlacement: none\nProcessing region: us\nResponse condition: Prevent default logging\nService ID: 123\nToken: abc\nURL: example.com\nVersion: 1\n`) + \"\\n\"\n\nfunc updateHerokuOK(_ context.Context, i *fastly.UpdateHerokuInput) (*fastly.Heroku, error) {\n\treturn &fastly.Heroku{\n\t\tServiceID:         fastly.ToPointer(i.ServiceID),\n\t\tServiceVersion:    fastly.ToPointer(i.ServiceVersion),\n\t\tName:              fastly.ToPointer(\"log\"),\n\t\tURL:               fastly.ToPointer(\"example.com\"),\n\t\tToken:             fastly.ToPointer(\"abc\"),\n\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\tFormatVersion:     fastly.ToPointer(2),\n\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t}, nil\n}\n\nfunc updateHerokuError(_ context.Context, _ *fastly.UpdateHerokuInput) (*fastly.Heroku, error) {\n\treturn nil, errTest\n}\n\nfunc deleteHerokuOK(_ context.Context, _ *fastly.DeleteHerokuInput) error {\n\treturn nil\n}\n\nfunc deleteHerokuError(_ context.Context, _ *fastly.DeleteHerokuInput) error {\n\treturn errTest\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/heroku/heroku_test.go",
    "content": "package heroku_test\n\nimport (\n\t\"bytes\"\n\t\"testing\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/service/logging/heroku\"\n\t\"github.com/fastly/cli/pkg/config\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/manifest\"\n\t\"github.com/fastly/cli/pkg/mock\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n)\n\nfunc TestCreateHerokuInput(t *testing.T) {\n\tfor _, testcase := range []struct {\n\t\tname      string\n\t\tcmd       *heroku.CreateCommand\n\t\twant      *fastly.CreateHerokuInput\n\t\twantError string\n\t}{\n\t\t{\n\t\t\tname: \"required values set flag serviceID\",\n\t\t\tcmd:  createCommandRequired(),\n\t\t\twant: &fastly.CreateHerokuInput{\n\t\t\t\tServiceID:      \"123\",\n\t\t\t\tServiceVersion: 4,\n\t\t\t\tName:           fastly.ToPointer(\"log\"),\n\t\t\t\tToken:          fastly.ToPointer(\"tkn\"),\n\t\t\t\tURL:            fastly.ToPointer(\"example.com\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"all values set flag serviceID\",\n\t\t\tcmd:  createCommandAll(),\n\t\t\twant: &fastly.CreateHerokuInput{\n\t\t\t\tServiceID:         \"123\",\n\t\t\t\tServiceVersion:    4,\n\t\t\t\tName:              fastly.ToPointer(\"log\"),\n\t\t\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\t\t\tFormatVersion:     fastly.ToPointer(2),\n\t\t\t\tToken:             fastly.ToPointer(\"tkn\"),\n\t\t\t\tURL:               fastly.ToPointer(\"example.com\"),\n\t\t\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\t\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\t\t\tProcessingRegion:  fastly.ToPointer(\"eu\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:      \"error missing serviceID\",\n\t\t\tcmd:       createCommandMissingServiceID(),\n\t\t\twant:      nil,\n\t\t\twantError: errors.ErrNoServiceID.Error(),\n\t\t},\n\t} {\n\t\tt.Run(testcase.name, func(t *testing.T) {\n\t\t\tif testcase.wantError == errors.ErrNoServiceID.Error() {\n\t\t\t\tt.Setenv(\"FASTLY_SERVICE_ID\", \"\")\n\t\t\t}\n\t\t\tvar bs []byte\n\t\t\tout := bytes.NewBuffer(bs)\n\t\t\tverboseMode := true\n\n\t\t\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\t\t\tAutoCloneFlag:      testcase.cmd.AutoClone,\n\t\t\t\tAPIClient:          testcase.cmd.Globals.APIClient,\n\t\t\t\tManifest:           testcase.cmd.Manifest,\n\t\t\t\tOut:                out,\n\t\t\t\tServiceVersionFlag: testcase.cmd.ServiceVersion,\n\t\t\t\tVerboseMode:        verboseMode,\n\t\t\t})\n\n\t\t\tswitch {\n\t\t\tcase err != nil && testcase.wantError == \"\":\n\t\t\t\tt.Fatalf(\"unexpected error getting service details: %v\", err)\n\t\t\t\treturn\n\t\t\tcase err != nil && testcase.wantError != \"\":\n\t\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\t\treturn\n\t\t\tcase err == nil && testcase.wantError != \"\":\n\t\t\t\tt.Fatalf(\"expected error, have nil (service details: %s, %d)\", serviceID, serviceVersion.Number)\n\t\t\tcase err == nil && testcase.wantError == \"\":\n\t\t\t\thave, err := testcase.cmd.ConstructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\t\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\t\ttestutil.AssertEqual(t, testcase.want, have)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestUpdateHerokuInput(t *testing.T) {\n\tscenarios := []struct {\n\t\tname      string\n\t\tcmd       *heroku.UpdateCommand\n\t\tapi       mock.API\n\t\twant      *fastly.UpdateHerokuInput\n\t\twantError string\n\t}{\n\t\t{\n\t\t\tname: \"no updates\",\n\t\t\tcmd:  updateCommandNoUpdates(),\n\t\t\tapi: mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tGetHerokuFn:    getHerokuOK,\n\t\t\t},\n\t\t\twant: &fastly.UpdateHerokuInput{\n\t\t\t\tServiceID:      \"123\",\n\t\t\t\tServiceVersion: 4,\n\t\t\t\tName:           \"log\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"all values set flag serviceID\",\n\t\t\tcmd:  updateCommandAll(),\n\t\t\tapi: mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tGetHerokuFn:    getHerokuOK,\n\t\t\t},\n\t\t\twant: &fastly.UpdateHerokuInput{\n\t\t\t\tServiceID:         \"123\",\n\t\t\t\tServiceVersion:    4,\n\t\t\t\tName:              \"log\",\n\t\t\t\tNewName:           fastly.ToPointer(\"new1\"),\n\t\t\t\tFormat:            fastly.ToPointer(\"new2\"),\n\t\t\t\tFormatVersion:     fastly.ToPointer(3),\n\t\t\t\tToken:             fastly.ToPointer(\"new3\"),\n\t\t\t\tURL:               fastly.ToPointer(\"new4\"),\n\t\t\t\tResponseCondition: fastly.ToPointer(\"new5\"),\n\t\t\t\tPlacement:         fastly.ToPointer(\"new6\"),\n\t\t\t\tProcessingRegion:  fastly.ToPointer(\"eu\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:      \"error missing serviceID\",\n\t\t\tcmd:       updateCommandMissingServiceID(),\n\t\t\twant:      nil,\n\t\t\twantError: errors.ErrNoServiceID.Error(),\n\t\t},\n\t}\n\tfor testcaseIdx := range scenarios {\n\t\ttestcase := &scenarios[testcaseIdx]\n\t\tt.Run(testcase.name, func(t *testing.T) {\n\t\t\tif testcase.wantError == errors.ErrNoServiceID.Error() {\n\t\t\t\tt.Setenv(\"FASTLY_SERVICE_ID\", \"\")\n\t\t\t}\n\t\t\ttestcase.cmd.Globals.APIClient = testcase.api\n\n\t\t\tvar bs []byte\n\t\t\tout := bytes.NewBuffer(bs)\n\t\t\tverboseMode := true\n\n\t\t\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\t\t\tAutoCloneFlag:      testcase.cmd.AutoClone,\n\t\t\t\tAPIClient:          testcase.api,\n\t\t\t\tManifest:           testcase.cmd.Manifest,\n\t\t\t\tOut:                out,\n\t\t\t\tServiceVersionFlag: testcase.cmd.ServiceVersion,\n\t\t\t\tVerboseMode:        verboseMode,\n\t\t\t})\n\n\t\t\tswitch {\n\t\t\tcase err != nil && testcase.wantError == \"\":\n\t\t\t\tt.Fatalf(\"unexpected error getting service details: %v\", err)\n\t\t\t\treturn\n\t\t\tcase err != nil && testcase.wantError != \"\":\n\t\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\t\treturn\n\t\t\tcase err == nil && testcase.wantError != \"\":\n\t\t\t\tt.Fatalf(\"expected error, have nil (service details: %s, %d)\", serviceID, serviceVersion.Number)\n\t\t\tcase err == nil && testcase.wantError == \"\":\n\t\t\t\thave, err := testcase.cmd.ConstructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\t\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\t\ttestutil.AssertEqual(t, testcase.want, have)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc createCommandRequired() *heroku.CreateCommand {\n\tvar b bytes.Buffer\n\n\tg := global.Data{\n\t\tConfig: config.File{},\n\t\tEnv:    config.Environment{},\n\t\tOutput: &b,\n\t}\n\tg.APIClient, _ = mock.APIClient(mock.API{\n\t\tGetVersionFn:   testutil.GetVersion,\n\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t})(\"token\", \"endpoint\", false)\n\n\treturn &heroku.CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: &g,\n\t\t},\n\t\tManifest: manifest.Data{\n\t\t\tFlag: manifest.Flag{\n\t\t\t\tServiceID: \"123\",\n\t\t\t},\n\t\t},\n\t\tEndpointName: argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"log\"},\n\t\tToken:        argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"tkn\"},\n\t\tURL:          argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"example.com\"},\n\t\tServiceVersion: argparser.OptionalServiceVersion{\n\t\t\tOptionalString: argparser.OptionalString{Value: \"1\"},\n\t\t},\n\t\tAutoClone: argparser.OptionalAutoClone{\n\t\t\tOptionalBool: argparser.OptionalBool{\n\t\t\t\tOptional: argparser.Optional{\n\t\t\t\t\tWasSet: true,\n\t\t\t\t},\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t},\n\t}\n}\n\nfunc createCommandAll() *heroku.CreateCommand {\n\tvar b bytes.Buffer\n\n\tg := global.Data{\n\t\tConfig: config.File{},\n\t\tEnv:    config.Environment{},\n\t\tOutput: &b,\n\t}\n\tg.APIClient, _ = mock.APIClient(mock.API{\n\t\tGetVersionFn:   testutil.GetVersion,\n\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t})(\"token\", \"endpoint\", false)\n\n\treturn &heroku.CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: &g,\n\t\t},\n\t\tManifest: manifest.Data{\n\t\t\tFlag: manifest.Flag{\n\t\t\t\tServiceID: \"123\",\n\t\t\t},\n\t\t},\n\t\tEndpointName: argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"log\"},\n\t\tToken:        argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"tkn\"},\n\t\tURL:          argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"example.com\"},\n\t\tServiceVersion: argparser.OptionalServiceVersion{\n\t\t\tOptionalString: argparser.OptionalString{Value: \"1\"},\n\t\t},\n\t\tAutoClone: argparser.OptionalAutoClone{\n\t\t\tOptionalBool: argparser.OptionalBool{\n\t\t\t\tOptional: argparser.Optional{\n\t\t\t\t\tWasSet: true,\n\t\t\t\t},\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t},\n\t\tFormat:            argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: `%h %l %u %t \"%r\" %>s %b`},\n\t\tFormatVersion:     argparser.OptionalInt{Optional: argparser.Optional{WasSet: true}, Value: 2},\n\t\tResponseCondition: argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"Prevent default logging\"},\n\t\tPlacement:         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"none\"},\n\t\tProcessingRegion:  argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"eu\"},\n\t}\n}\n\nfunc createCommandMissingServiceID() *heroku.CreateCommand {\n\tres := createCommandAll()\n\tres.Manifest = manifest.Data{}\n\tres.ServiceVersion = argparser.OptionalServiceVersion{}\n\treturn res\n}\n\nfunc updateCommandNoUpdates() *heroku.UpdateCommand {\n\tvar b bytes.Buffer\n\n\tg := global.Data{\n\t\tConfig: config.File{},\n\t\tEnv:    config.Environment{},\n\t\tOutput: &b,\n\t}\n\n\treturn &heroku.UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: &g,\n\t\t},\n\t\tManifest: manifest.Data{\n\t\t\tFlag: manifest.Flag{\n\t\t\t\tServiceID: \"123\",\n\t\t\t},\n\t\t},\n\t\tEndpointName: \"log\",\n\t\tServiceVersion: argparser.OptionalServiceVersion{\n\t\t\tOptionalString: argparser.OptionalString{Value: \"1\"},\n\t\t},\n\t\tAutoClone: argparser.OptionalAutoClone{\n\t\t\tOptionalBool: argparser.OptionalBool{\n\t\t\t\tOptional: argparser.Optional{\n\t\t\t\t\tWasSet: true,\n\t\t\t\t},\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t},\n\t}\n}\n\nfunc updateCommandAll() *heroku.UpdateCommand {\n\tvar b bytes.Buffer\n\n\tg := global.Data{\n\t\tConfig: config.File{},\n\t\tEnv:    config.Environment{},\n\t\tOutput: &b,\n\t}\n\n\treturn &heroku.UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: &g,\n\t\t},\n\t\tManifest: manifest.Data{\n\t\t\tFlag: manifest.Flag{\n\t\t\t\tServiceID: \"123\",\n\t\t\t},\n\t\t},\n\t\tEndpointName: \"log\",\n\t\tServiceVersion: argparser.OptionalServiceVersion{\n\t\t\tOptionalString: argparser.OptionalString{Value: \"1\"},\n\t\t},\n\t\tAutoClone: argparser.OptionalAutoClone{\n\t\t\tOptionalBool: argparser.OptionalBool{\n\t\t\t\tOptional: argparser.Optional{\n\t\t\t\t\tWasSet: true,\n\t\t\t\t},\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t},\n\t\tNewName:           argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new1\"},\n\t\tFormat:            argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new2\"},\n\t\tFormatVersion:     argparser.OptionalInt{Optional: argparser.Optional{WasSet: true}, Value: 3},\n\t\tToken:             argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new3\"},\n\t\tURL:               argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new4\"},\n\t\tResponseCondition: argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new5\"},\n\t\tPlacement:         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new6\"},\n\t\tProcessingRegion:  argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"eu\"},\n\t}\n}\n\nfunc updateCommandMissingServiceID() *heroku.UpdateCommand {\n\tres := updateCommandAll()\n\tres.Manifest = manifest.Data{}\n\tres.ServiceVersion = argparser.OptionalServiceVersion{}\n\treturn res\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/heroku/list.go",
    "content": "package heroku\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// ListCommand calls the Fastly API to list Heroku logging endpoints.\ntype ListCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tInput          fastly.ListHerokusInput\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n}\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"list\", \"List Heroku endpoints on a Fastly service version\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *ListCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fsterr.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tc.Input.ServiceID = serviceID\n\tc.Input.ServiceVersion = fastly.ToValue(serviceVersion.Number)\n\n\to, err := c.Globals.APIClient.ListHerokus(context.TODO(), &c.Input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\tif !c.Globals.Verbose() {\n\t\ttw := text.NewTable(out)\n\t\ttw.AddHeader(\"SERVICE\", \"VERSION\", \"NAME\")\n\t\tfor _, heroku := range o {\n\t\t\ttw.AddLine(\n\t\t\t\tfastly.ToValue(heroku.ServiceID),\n\t\t\t\tfastly.ToValue(heroku.ServiceVersion),\n\t\t\t\tfastly.ToValue(heroku.Name),\n\t\t\t)\n\t\t}\n\t\ttw.Print()\n\t\treturn nil\n\t}\n\n\tfmt.Fprintf(out, \"Version: %d\\n\", c.Input.ServiceVersion)\n\tfor i, heroku := range o {\n\t\tfmt.Fprintf(out, \"\\tHeroku %d/%d\\n\", i+1, len(o))\n\t\tfmt.Fprintf(out, \"\\t\\tService ID: %s\\n\", fastly.ToValue(heroku.ServiceID))\n\t\tfmt.Fprintf(out, \"\\t\\tVersion: %d\\n\", fastly.ToValue(heroku.ServiceVersion))\n\t\tfmt.Fprintf(out, \"\\t\\tName: %s\\n\", fastly.ToValue(heroku.Name))\n\t\tfmt.Fprintf(out, \"\\t\\tURL: %s\\n\", fastly.ToValue(heroku.URL))\n\t\tfmt.Fprintf(out, \"\\t\\tToken: %s\\n\", fastly.ToValue(heroku.Token))\n\t\tfmt.Fprintf(out, \"\\t\\tFormat: %s\\n\", fastly.ToValue(heroku.Format))\n\t\tfmt.Fprintf(out, \"\\t\\tFormat version: %d\\n\", fastly.ToValue(heroku.FormatVersion))\n\t\tfmt.Fprintf(out, \"\\t\\tResponse condition: %s\\n\", fastly.ToValue(heroku.ResponseCondition))\n\t\tfmt.Fprintf(out, \"\\t\\tPlacement: %s\\n\", fastly.ToValue(heroku.Placement))\n\t\tfmt.Fprintf(out, \"\\t\\tProcessing region: %s\\n\", fastly.ToValue(heroku.ProcessingRegion))\n\t}\n\tfmt.Fprintln(out)\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/heroku/root.go",
    "content": "package heroku\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"heroku\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manipulate Fastly service version Heroku logging endpoints\")\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/heroku/update.go",
    "content": "package heroku\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/service/logging/logflags\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/manifest\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// UpdateCommand calls the Fastly API to update a Heroku logging endpoint.\ntype UpdateCommand struct {\n\targparser.Base\n\tManifest manifest.Data\n\n\t// Required.\n\tEndpointName   string // Can't shadow argparser.Base method Name().\n\tServiceName    argparser.OptionalServiceNameID\n\tServiceVersion argparser.OptionalServiceVersion\n\n\t// Optional.\n\tAutoClone         argparser.OptionalAutoClone\n\tFormat            argparser.OptionalString\n\tFormatVersion     argparser.OptionalInt\n\tNewName           argparser.OptionalString\n\tPlacement         argparser.OptionalString\n\tProcessingRegion  argparser.OptionalString\n\tResponseCondition argparser.OptionalString\n\tToken             argparser.OptionalString\n\tURL               argparser.OptionalString\n}\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"update\", \"Update a Heroku logging endpoint on a Fastly service version\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"The name of the Heroku logging object\").Short('n').Required().StringVar(&c.EndpointName)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.ServiceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.CmdClause.Flag(\"auth-token\", \"The token to use for authentication (https://devcenter.heroku.com/articles/add-on-partner-log-integration)\").Action(c.Token.Set).StringVar(&c.Token.Value)\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.AutoClone.Set,\n\t\tDst:    &c.AutoClone.Value,\n\t})\n\tlogflags.Format(c.CmdClause, &c.Format)\n\tlogflags.FormatVersion(c.CmdClause, &c.FormatVersion)\n\tc.CmdClause.Flag(\"new-name\", \"New name of the Heroku logging object\").Action(c.NewName.Set).StringVar(&c.NewName.Value)\n\tlogflags.Placement(c.CmdClause, &c.Placement)\n\tlogflags.ProcessingRegion(c.CmdClause, &c.ProcessingRegion, \"Heroku\")\n\tlogflags.ResponseCondition(c.CmdClause, &c.ResponseCondition)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.ServiceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.ServiceName.Value,\n\t})\n\tc.CmdClause.Flag(\"url\", \"The url to stream logs to\").Action(c.URL.Set).StringVar(&c.URL.Value)\n\treturn &c\n}\n\n// ConstructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *UpdateCommand) ConstructInput(serviceID string, serviceVersion int) (*fastly.UpdateHerokuInput, error) {\n\tinput := fastly.UpdateHerokuInput{\n\t\tServiceID:      serviceID,\n\t\tServiceVersion: serviceVersion,\n\t\tName:           c.EndpointName,\n\t}\n\n\tif c.NewName.WasSet {\n\t\tinput.NewName = &c.NewName.Value\n\t}\n\n\tif c.Format.WasSet {\n\t\tinput.Format = fastly.ToPointer(argparser.Content(c.Format.Value))\n\t}\n\n\tif c.FormatVersion.WasSet {\n\t\tinput.FormatVersion = &c.FormatVersion.Value\n\t}\n\n\tif c.Token.WasSet {\n\t\tinput.Token = &c.Token.Value\n\t}\n\n\tif c.URL.WasSet {\n\t\tinput.URL = &c.URL.Value\n\t}\n\n\tif c.ResponseCondition.WasSet {\n\t\tinput.ResponseCondition = &c.ResponseCondition.Value\n\t}\n\n\tif c.Placement.WasSet {\n\t\tinput.Placement = &c.Placement.Value\n\t}\n\n\tif c.ProcessingRegion.WasSet {\n\t\tinput.ProcessingRegion = &c.ProcessingRegion.Value\n\t}\n\n\treturn &input, nil\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *UpdateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(false),\n\t\tLocked:             optional.Of(false),\n\t\tAutoCloneFlag:      c.AutoClone,\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.ServiceName,\n\t\tServiceVersionFlag: c.ServiceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": errors.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tinput, err := c.ConstructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\theroku, err := c.Globals.APIClient.UpdateHeroku(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\ttext.Success(\n\t\tout,\n\t\t\"Updated Heroku logging endpoint %s (service %s version %d)\",\n\t\tfastly.ToValue(heroku.Name),\n\t\tfastly.ToValue(heroku.ServiceID),\n\t\tfastly.ToValue(heroku.ServiceVersion),\n\t)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/honeycomb/create.go",
    "content": "package honeycomb\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/service/logging/logflags\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/manifest\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// CreateCommand calls the Fastly API to create a Honeycomb logging endpoint.\ntype CreateCommand struct {\n\targparser.Base\n\tManifest manifest.Data\n\n\t// Required.\n\tServiceName    argparser.OptionalServiceNameID\n\tServiceVersion argparser.OptionalServiceVersion\n\n\t// Optional.\n\tAutoClone         argparser.OptionalAutoClone\n\tDataset           argparser.OptionalString\n\tEndpointName      argparser.OptionalString // Can't shadow argparser.Base method Name().\n\tFormat            argparser.OptionalString\n\tFormatVersion     argparser.OptionalInt\n\tPlacement         argparser.OptionalString\n\tProcessingRegion  argparser.OptionalString\n\tResponseCondition argparser.OptionalString\n\tToken             argparser.OptionalString\n}\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"create\", \"Create a Honeycomb logging endpoint on a Fastly service version\").Alias(\"add\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"The name of the Honeycomb logging object. Used as a primary key for API access\").Short('n').Action(c.EndpointName.Set).StringVar(&c.EndpointName.Value)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.ServiceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.CmdClause.Flag(\"auth-token\", \"The Write Key from the Account page of your Honeycomb account\").Action(c.Token.Set).StringVar(&c.Token.Value)\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.AutoClone.Set,\n\t\tDst:    &c.AutoClone.Value,\n\t})\n\tc.CmdClause.Flag(\"dataset\", \"The Honeycomb Dataset you want to log to\").Action(c.Dataset.Set).StringVar(&c.Dataset.Value)\n\tlogflags.Format(c.CmdClause, &c.Format)\n\tlogflags.FormatVersion(c.CmdClause, &c.FormatVersion)\n\tlogflags.ResponseCondition(c.CmdClause, &c.ResponseCondition)\n\tlogflags.Placement(c.CmdClause, &c.Placement)\n\tlogflags.ProcessingRegion(c.CmdClause, &c.ProcessingRegion, \"Honeycomb\")\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.ServiceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.ServiceName.Value,\n\t})\n\treturn &c\n}\n\n// ConstructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *CreateCommand) ConstructInput(serviceID string, serviceVersion int) (*fastly.CreateHoneycombInput, error) {\n\tvar input fastly.CreateHoneycombInput\n\n\tinput.ServiceID = serviceID\n\tinput.ServiceVersion = serviceVersion\n\tif c.EndpointName.WasSet {\n\t\tinput.Name = &c.EndpointName.Value\n\t}\n\tif c.Token.WasSet {\n\t\tinput.Token = &c.Token.Value\n\t}\n\tif c.Dataset.WasSet {\n\t\tinput.Dataset = &c.Dataset.Value\n\t}\n\n\tif c.Format.WasSet {\n\t\tinput.Format = fastly.ToPointer(argparser.Content(c.Format.Value))\n\t}\n\n\tif c.FormatVersion.WasSet {\n\t\tinput.FormatVersion = &c.FormatVersion.Value\n\t}\n\n\tif c.ResponseCondition.WasSet {\n\t\tinput.ResponseCondition = &c.ResponseCondition.Value\n\t}\n\n\tif c.Placement.WasSet {\n\t\tinput.Placement = &c.Placement.Value\n\t}\n\n\tif c.ProcessingRegion.WasSet {\n\t\tinput.ProcessingRegion = &c.ProcessingRegion.Value\n\t}\n\n\treturn &input, nil\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *CreateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(false),\n\t\tLocked:             optional.Of(false),\n\t\tAutoCloneFlag:      c.AutoClone,\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.ServiceName,\n\t\tServiceVersionFlag: c.ServiceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": errors.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tinput, err := c.ConstructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\td, err := c.Globals.APIClient.CreateHoneycomb(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\ttext.Success(\n\t\tout,\n\t\t\"Created Honeycomb logging endpoint %s (service %s version %d)\",\n\t\tfastly.ToValue(d.Name),\n\t\tfastly.ToValue(d.ServiceID),\n\t\tfastly.ToValue(d.ServiceVersion),\n\t)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/honeycomb/delete.go",
    "content": "package honeycomb\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DeleteCommand calls the Fastly API to delete a Honeycomb logging endpoint.\ntype DeleteCommand struct {\n\targparser.Base\n\tInput          fastly.DeleteHoneycombInput\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n\tautoClone      argparser.OptionalAutoClone\n}\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"delete\", \"Delete a Honeycomb logging endpoint on a Fastly service version\").Alias(\"remove\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"The name of the Honeycomb logging object\").Short('n').Required().StringVar(&c.Input.Name)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.autoClone.Set,\n\t\tDst:    &c.autoClone.Value,\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DeleteCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(false),\n\t\tLocked:             optional.Of(false),\n\t\tAutoCloneFlag:      c.autoClone,\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": errors.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tc.Input.ServiceID = serviceID\n\tc.Input.ServiceVersion = fastly.ToValue(serviceVersion.Number)\n\n\tif err := c.Globals.APIClient.DeleteHoneycomb(context.TODO(), &c.Input); err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Deleted Honeycomb logging endpoint %s (service %s version %d)\", c.Input.Name, c.Input.ServiceID, c.Input.ServiceVersion)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/honeycomb/describe.go",
    "content": "package honeycomb\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DescribeCommand calls the Fastly API to describe a Honeycomb logging endpoint.\ntype DescribeCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tInput          fastly.GetHoneycombInput\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n}\n\n// NewDescribeCommand returns a usable command registered under the parent.\nfunc NewDescribeCommand(parent argparser.Registerer, g *global.Data) *DescribeCommand {\n\tc := DescribeCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"describe\", \"Show detailed information about a Honeycomb logging endpoint on a Fastly service version\").Alias(\"get\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"The name of the Honeycomb logging object\").Short('n').Required().StringVar(&c.Input.Name)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DescribeCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fsterr.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tc.Input.ServiceID = serviceID\n\tc.Input.ServiceVersion = fastly.ToValue(serviceVersion.Number)\n\n\to, err := c.Globals.APIClient.GetHoneycomb(context.TODO(), &c.Input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\tlines := text.Lines{\n\t\t\"Dataset\":            fastly.ToValue(o.Dataset),\n\t\t\"Format version\":     fastly.ToValue(o.FormatVersion),\n\t\t\"Format\":             fastly.ToValue(o.Format),\n\t\t\"Name\":               fastly.ToValue(o.Name),\n\t\t\"Placement\":          fastly.ToValue(o.Placement),\n\t\t\"Processing region\":  fastly.ToValue(o.ProcessingRegion),\n\t\t\"Response condition\": fastly.ToValue(o.ResponseCondition),\n\t\t\"Token\":              fastly.ToValue(o.Token),\n\t\t\"Version\":            fastly.ToValue(o.ServiceVersion),\n\t}\n\tif !c.Globals.Verbose() {\n\t\tlines[\"Service ID\"] = fastly.ToValue(o.ServiceID)\n\t}\n\ttext.PrintLines(out, lines)\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/honeycomb/doc.go",
    "content": "// Package honeycomb contains commands to inspect and manipulate Fastly service Honeycomb\n// logging endpoints.\npackage honeycomb\n"
  },
  {
    "path": "pkg/commands/service/logging/honeycomb/honeycomb_integration_test.go",
    "content": "package honeycomb_test\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\troot \"github.com/fastly/cli/pkg/commands/service\"\n\tparent \"github.com/fastly/cli/pkg/commands/service/logging\"\n\tsub \"github.com/fastly/cli/pkg/commands/service/logging/honeycomb\"\n\t\"github.com/fastly/cli/pkg/mock\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n)\n\nfunc TestHoneycombCreate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name log --auth-token abc --dataset log --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:      testutil.GetVersion,\n\t\t\t\tCloneVersionFn:    testutil.CloneVersionResult(4),\n\t\t\t\tCreateHoneycombFn: createHoneycombOK,\n\t\t\t},\n\t\t\tWantOutput: \"Created Honeycomb logging endpoint log (service 123 version 4)\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name log --auth-token abc --dataset log --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:      testutil.GetVersion,\n\t\t\t\tCloneVersionFn:    testutil.CloneVersionResult(4),\n\t\t\t\tCreateHoneycombFn: createHoneycombError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"create\"}, scenarios)\n}\n\nfunc TestHoneycombList(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:     testutil.GetVersion,\n\t\t\t\tListHoneycombsFn: listHoneycombsOK,\n\t\t\t},\n\t\t\tWantOutput: listHoneycombsShortOutput,\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --verbose\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:     testutil.GetVersion,\n\t\t\t\tListHoneycombsFn: listHoneycombsOK,\n\t\t\t},\n\t\t\tWantOutput: listHoneycombsVerboseOutput,\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 -v\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:     testutil.GetVersion,\n\t\t\t\tListHoneycombsFn: listHoneycombsOK,\n\t\t\t},\n\t\t\tWantOutput: listHoneycombsVerboseOutput,\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:     testutil.GetVersion,\n\t\t\t\tListHoneycombsFn: listHoneycombsError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"list\"}, scenarios)\n}\n\nfunc TestHoneycombDescribe(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"--service-id 123 --version 1\",\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tGetHoneycombFn: getHoneycombError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tGetHoneycombFn: getHoneycombOK,\n\t\t\t},\n\t\t\tWantOutput: describeHoneycombOutput,\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"describe\"}, scenarios)\n}\n\nfunc TestHoneycombUpdate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"--service-id 123 --version 1 --new-name log\",\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs --new-name log --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:      testutil.GetVersion,\n\t\t\t\tCloneVersionFn:    testutil.CloneVersionResult(4),\n\t\t\t\tUpdateHoneycombFn: updateHoneycombError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs --new-name log --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:      testutil.GetVersion,\n\t\t\t\tCloneVersionFn:    testutil.CloneVersionResult(4),\n\t\t\t\tUpdateHoneycombFn: updateHoneycombOK,\n\t\t\t},\n\t\t\tWantOutput: \"Updated Honeycomb logging endpoint log (service 123 version 4)\",\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"update\"}, scenarios)\n}\n\nfunc TestHoneycombDelete(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"--service-id 123 --version 1\",\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:      testutil.GetVersion,\n\t\t\t\tCloneVersionFn:    testutil.CloneVersionResult(4),\n\t\t\t\tDeleteHoneycombFn: deleteHoneycombError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:      testutil.GetVersion,\n\t\t\t\tCloneVersionFn:    testutil.CloneVersionResult(4),\n\t\t\t\tDeleteHoneycombFn: deleteHoneycombOK,\n\t\t\t},\n\t\t\tWantOutput: \"Deleted Honeycomb logging endpoint logs (service 123 version 4)\",\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"delete\"}, scenarios)\n}\n\nvar errTest = errors.New(\"fixture error\")\n\nfunc createHoneycombOK(_ context.Context, i *fastly.CreateHoneycombInput) (*fastly.Honeycomb, error) {\n\ts := fastly.Honeycomb{\n\t\tServiceID:      fastly.ToPointer(i.ServiceID),\n\t\tServiceVersion: fastly.ToPointer(i.ServiceVersion),\n\t}\n\n\tif i.Name != nil {\n\t\ts.Name = i.Name\n\t}\n\n\treturn &s, nil\n}\n\nfunc createHoneycombError(_ context.Context, _ *fastly.CreateHoneycombInput) (*fastly.Honeycomb, error) {\n\treturn nil, errTest\n}\n\nfunc listHoneycombsOK(_ context.Context, i *fastly.ListHoneycombsInput) ([]*fastly.Honeycomb, error) {\n\treturn []*fastly.Honeycomb{\n\t\t{\n\t\t\tServiceID:         fastly.ToPointer(i.ServiceID),\n\t\t\tServiceVersion:    fastly.ToPointer(i.ServiceVersion),\n\t\t\tName:              fastly.ToPointer(\"logs\"),\n\t\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\t\tFormatVersion:     fastly.ToPointer(2),\n\t\t\tDataset:           fastly.ToPointer(\"log\"),\n\t\t\tToken:             fastly.ToPointer(\"tkn\"),\n\t\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\t\tProcessingRegion:  fastly.ToPointer(\"us\"),\n\t\t},\n\t\t{\n\t\t\tServiceID:         fastly.ToPointer(i.ServiceID),\n\t\t\tServiceVersion:    fastly.ToPointer(i.ServiceVersion),\n\t\t\tName:              fastly.ToPointer(\"analytics\"),\n\t\t\tDataset:           fastly.ToPointer(\"log\"),\n\t\t\tToken:             fastly.ToPointer(\"tkn\"),\n\t\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\t\tFormatVersion:     fastly.ToPointer(2),\n\t\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\t\tProcessingRegion:  fastly.ToPointer(\"us\"),\n\t\t},\n\t}, nil\n}\n\nfunc listHoneycombsError(_ context.Context, _ *fastly.ListHoneycombsInput) ([]*fastly.Honeycomb, error) {\n\treturn nil, errTest\n}\n\nvar listHoneycombsShortOutput = strings.TrimSpace(`\nSERVICE  VERSION  NAME\n123      1        logs\n123      1        analytics\n`) + \"\\n\"\n\nvar listHoneycombsVerboseOutput = strings.TrimSpace(`\nFastly API endpoint: https://api.fastly.com\nFastly API token provided via config file (auth: user)\n\nService ID (via --service-id): 123\n\nVersion: 1\n\tHoneycomb 1/2\n\t\tService ID: 123\n\t\tVersion: 1\n\t\tName: logs\n\t\tDataset: log\n\t\tToken: tkn\n\t\tFormat: %h %l %u %t \"%r\" %>s %b\n\t\tFormat version: 2\n\t\tResponse condition: Prevent default logging\n\t\tPlacement: none\n\t\tProcessing region: us\n\tHoneycomb 2/2\n\t\tService ID: 123\n\t\tVersion: 1\n\t\tName: analytics\n\t\tDataset: log\n\t\tToken: tkn\n\t\tFormat: %h %l %u %t \"%r\" %>s %b\n\t\tFormat version: 2\n\t\tResponse condition: Prevent default logging\n\t\tPlacement: none\n\t\tProcessing region: us\n`) + \"\\n\\n\"\n\nfunc getHoneycombOK(_ context.Context, i *fastly.GetHoneycombInput) (*fastly.Honeycomb, error) {\n\treturn &fastly.Honeycomb{\n\t\tServiceID:         fastly.ToPointer(i.ServiceID),\n\t\tServiceVersion:    fastly.ToPointer(i.ServiceVersion),\n\t\tName:              fastly.ToPointer(\"logs\"),\n\t\tDataset:           fastly.ToPointer(\"log\"),\n\t\tToken:             fastly.ToPointer(\"tkn\"),\n\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\tFormatVersion:     fastly.ToPointer(2),\n\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\tProcessingRegion:  fastly.ToPointer(\"us\"),\n\t}, nil\n}\n\nfunc getHoneycombError(_ context.Context, _ *fastly.GetHoneycombInput) (*fastly.Honeycomb, error) {\n\treturn nil, errTest\n}\n\nvar describeHoneycombOutput = \"\\n\" + strings.TrimSpace(`\nDataset: log\nFormat: %h %l %u %t \"%r\" %>s %b\nFormat version: 2\nName: logs\nPlacement: none\nProcessing region: us\nResponse condition: Prevent default logging\nService ID: 123\nToken: tkn\nVersion: 1\n`) + \"\\n\"\n\nfunc updateHoneycombOK(_ context.Context, i *fastly.UpdateHoneycombInput) (*fastly.Honeycomb, error) {\n\treturn &fastly.Honeycomb{\n\t\tServiceID:         fastly.ToPointer(i.ServiceID),\n\t\tServiceVersion:    fastly.ToPointer(i.ServiceVersion),\n\t\tName:              fastly.ToPointer(\"log\"),\n\t\tDataset:           fastly.ToPointer(\"log\"),\n\t\tToken:             fastly.ToPointer(\"tkn\"),\n\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\tFormatVersion:     fastly.ToPointer(2),\n\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t}, nil\n}\n\nfunc updateHoneycombError(_ context.Context, _ *fastly.UpdateHoneycombInput) (*fastly.Honeycomb, error) {\n\treturn nil, errTest\n}\n\nfunc deleteHoneycombOK(_ context.Context, _ *fastly.DeleteHoneycombInput) error {\n\treturn nil\n}\n\nfunc deleteHoneycombError(_ context.Context, _ *fastly.DeleteHoneycombInput) error {\n\treturn errTest\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/honeycomb/honeycomb_test.go",
    "content": "package honeycomb_test\n\nimport (\n\t\"bytes\"\n\t\"testing\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/service/logging/honeycomb\"\n\t\"github.com/fastly/cli/pkg/config\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/manifest\"\n\t\"github.com/fastly/cli/pkg/mock\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n)\n\nfunc TestCreateHoneycombInput(t *testing.T) {\n\tfor _, testcase := range []struct {\n\t\tname      string\n\t\tcmd       *honeycomb.CreateCommand\n\t\twant      *fastly.CreateHoneycombInput\n\t\twantError string\n\t}{\n\t\t{\n\t\t\tname: \"required values set flag serviceID\",\n\t\t\tcmd:  createCommandRequired(),\n\t\t\twant: &fastly.CreateHoneycombInput{\n\t\t\t\tServiceID:      \"123\",\n\t\t\t\tServiceVersion: 4,\n\t\t\t\tName:           fastly.ToPointer(\"log\"),\n\t\t\t\tToken:          fastly.ToPointer(\"tkn\"),\n\t\t\t\tDataset:        fastly.ToPointer(\"logs\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"all values set flag serviceID\",\n\t\t\tcmd:  createCommandAll(),\n\t\t\twant: &fastly.CreateHoneycombInput{\n\t\t\t\tServiceID:         \"123\",\n\t\t\t\tServiceVersion:    4,\n\t\t\t\tName:              fastly.ToPointer(\"log\"),\n\t\t\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\t\t\tFormatVersion:     fastly.ToPointer(2),\n\t\t\t\tToken:             fastly.ToPointer(\"tkn\"),\n\t\t\t\tDataset:           fastly.ToPointer(\"logs\"),\n\t\t\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\t\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\t\t\tProcessingRegion:  fastly.ToPointer(\"eu\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:      \"error missing serviceID\",\n\t\t\tcmd:       createCommandMissingServiceID(),\n\t\t\twant:      nil,\n\t\t\twantError: errors.ErrNoServiceID.Error(),\n\t\t},\n\t} {\n\t\tt.Run(testcase.name, func(t *testing.T) {\n\t\t\tif testcase.wantError == errors.ErrNoServiceID.Error() {\n\t\t\t\tt.Setenv(\"FASTLY_SERVICE_ID\", \"\")\n\t\t\t}\n\t\t\tvar bs []byte\n\t\t\tout := bytes.NewBuffer(bs)\n\t\t\tverboseMode := true\n\n\t\t\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\t\t\tAutoCloneFlag:      testcase.cmd.AutoClone,\n\t\t\t\tAPIClient:          testcase.cmd.Globals.APIClient,\n\t\t\t\tManifest:           testcase.cmd.Manifest,\n\t\t\t\tOut:                out,\n\t\t\t\tServiceVersionFlag: testcase.cmd.ServiceVersion,\n\t\t\t\tVerboseMode:        verboseMode,\n\t\t\t})\n\n\t\t\tswitch {\n\t\t\tcase err != nil && testcase.wantError == \"\":\n\t\t\t\tt.Fatalf(\"unexpected error getting service details: %v\", err)\n\t\t\t\treturn\n\t\t\tcase err != nil && testcase.wantError != \"\":\n\t\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\t\treturn\n\t\t\tcase err == nil && testcase.wantError != \"\":\n\t\t\t\tt.Fatalf(\"expected error, have nil (service details: %s, %d)\", serviceID, serviceVersion.Number)\n\t\t\tcase err == nil && testcase.wantError == \"\":\n\t\t\t\thave, err := testcase.cmd.ConstructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\t\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\t\ttestutil.AssertEqual(t, testcase.want, have)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestUpdateHoneycombInput(t *testing.T) {\n\tscenarios := []struct {\n\t\tname      string\n\t\tcmd       *honeycomb.UpdateCommand\n\t\tapi       mock.API\n\t\twant      *fastly.UpdateHoneycombInput\n\t\twantError string\n\t}{\n\t\t{\n\t\t\tname: \"no updates\",\n\t\t\tcmd:  updateCommandNoUpdates(),\n\t\t\tapi: mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tGetHoneycombFn: getHoneycombOK,\n\t\t\t},\n\t\t\twant: &fastly.UpdateHoneycombInput{\n\t\t\t\tServiceID:      \"123\",\n\t\t\t\tServiceVersion: 4,\n\t\t\t\tName:           \"log\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"all values set flag serviceID\",\n\t\t\tcmd:  updateCommandAll(),\n\t\t\tapi: mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tGetHoneycombFn: getHoneycombOK,\n\t\t\t},\n\t\t\twant: &fastly.UpdateHoneycombInput{\n\t\t\t\tServiceID:         \"123\",\n\t\t\t\tServiceVersion:    4,\n\t\t\t\tName:              \"log\",\n\t\t\t\tNewName:           fastly.ToPointer(\"new1\"),\n\t\t\t\tFormat:            fastly.ToPointer(\"new2\"),\n\t\t\t\tFormatVersion:     fastly.ToPointer(3),\n\t\t\t\tToken:             fastly.ToPointer(\"new3\"),\n\t\t\t\tDataset:           fastly.ToPointer(\"new4\"),\n\t\t\t\tResponseCondition: fastly.ToPointer(\"new5\"),\n\t\t\t\tPlacement:         fastly.ToPointer(\"new6\"),\n\t\t\t\tProcessingRegion:  fastly.ToPointer(\"eu\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:      \"error missing serviceID\",\n\t\t\tcmd:       updateCommandMissingServiceID(),\n\t\t\twant:      nil,\n\t\t\twantError: errors.ErrNoServiceID.Error(),\n\t\t},\n\t}\n\tfor testcaseIdx := range scenarios {\n\t\ttestcase := &scenarios[testcaseIdx]\n\t\tt.Run(testcase.name, func(t *testing.T) {\n\t\t\tif testcase.wantError == errors.ErrNoServiceID.Error() {\n\t\t\t\tt.Setenv(\"FASTLY_SERVICE_ID\", \"\")\n\t\t\t}\n\t\t\ttestcase.cmd.Globals.APIClient = testcase.api\n\n\t\t\tvar bs []byte\n\t\t\tout := bytes.NewBuffer(bs)\n\t\t\tverboseMode := true\n\n\t\t\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\t\t\tAutoCloneFlag:      testcase.cmd.AutoClone,\n\t\t\t\tAPIClient:          testcase.api,\n\t\t\t\tManifest:           testcase.cmd.Manifest,\n\t\t\t\tOut:                out,\n\t\t\t\tServiceVersionFlag: testcase.cmd.ServiceVersion,\n\t\t\t\tVerboseMode:        verboseMode,\n\t\t\t})\n\n\t\t\tswitch {\n\t\t\tcase err != nil && testcase.wantError == \"\":\n\t\t\t\tt.Fatalf(\"unexpected error getting service details: %v\", err)\n\t\t\t\treturn\n\t\t\tcase err != nil && testcase.wantError != \"\":\n\t\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\t\treturn\n\t\t\tcase err == nil && testcase.wantError != \"\":\n\t\t\t\tt.Fatalf(\"expected error, have nil (service details: %s, %d)\", serviceID, serviceVersion.Number)\n\t\t\tcase err == nil && testcase.wantError == \"\":\n\t\t\t\thave, err := testcase.cmd.ConstructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\t\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\t\ttestutil.AssertEqual(t, testcase.want, have)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc createCommandRequired() *honeycomb.CreateCommand {\n\tvar b bytes.Buffer\n\n\tg := global.Data{\n\t\tConfig: config.File{},\n\t\tEnv:    config.Environment{},\n\t\tOutput: &b,\n\t}\n\tg.APIClient, _ = mock.APIClient(mock.API{\n\t\tGetVersionFn:   testutil.GetVersion,\n\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t})(\"token\", \"endpoint\", false)\n\n\treturn &honeycomb.CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: &g,\n\t\t},\n\t\tManifest: manifest.Data{\n\t\t\tFlag: manifest.Flag{\n\t\t\t\tServiceID: \"123\",\n\t\t\t},\n\t\t},\n\t\tEndpointName: argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"log\"},\n\t\tToken:        argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"tkn\"},\n\t\tDataset:      argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"logs\"},\n\t\tServiceVersion: argparser.OptionalServiceVersion{\n\t\t\tOptionalString: argparser.OptionalString{Value: \"1\"},\n\t\t},\n\t\tAutoClone: argparser.OptionalAutoClone{\n\t\t\tOptionalBool: argparser.OptionalBool{\n\t\t\t\tOptional: argparser.Optional{\n\t\t\t\t\tWasSet: true,\n\t\t\t\t},\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t},\n\t}\n}\n\nfunc createCommandAll() *honeycomb.CreateCommand {\n\tvar b bytes.Buffer\n\n\tg := global.Data{\n\t\tConfig: config.File{},\n\t\tEnv:    config.Environment{},\n\t\tOutput: &b,\n\t}\n\tg.APIClient, _ = mock.APIClient(mock.API{\n\t\tGetVersionFn:   testutil.GetVersion,\n\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t})(\"token\", \"endpoint\", false)\n\n\treturn &honeycomb.CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: &g,\n\t\t},\n\t\tManifest: manifest.Data{\n\t\t\tFlag: manifest.Flag{\n\t\t\t\tServiceID: \"123\",\n\t\t\t},\n\t\t},\n\t\tEndpointName: argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"log\"},\n\t\tToken:        argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"tkn\"},\n\t\tDataset:      argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"logs\"},\n\t\tServiceVersion: argparser.OptionalServiceVersion{\n\t\t\tOptionalString: argparser.OptionalString{Value: \"1\"},\n\t\t},\n\t\tAutoClone: argparser.OptionalAutoClone{\n\t\t\tOptionalBool: argparser.OptionalBool{\n\t\t\t\tOptional: argparser.Optional{\n\t\t\t\t\tWasSet: true,\n\t\t\t\t},\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t},\n\t\tFormat:            argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: `%h %l %u %t \"%r\" %>s %b`},\n\t\tFormatVersion:     argparser.OptionalInt{Optional: argparser.Optional{WasSet: true}, Value: 2},\n\t\tResponseCondition: argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"Prevent default logging\"},\n\t\tPlacement:         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"none\"},\n\t\tProcessingRegion:  argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"eu\"},\n\t}\n}\n\nfunc createCommandMissingServiceID() *honeycomb.CreateCommand {\n\tres := createCommandAll()\n\tres.Manifest = manifest.Data{}\n\tres.ServiceVersion = argparser.OptionalServiceVersion{}\n\treturn res\n}\n\nfunc updateCommandNoUpdates() *honeycomb.UpdateCommand {\n\tvar b bytes.Buffer\n\n\tg := global.Data{\n\t\tConfig: config.File{},\n\t\tEnv:    config.Environment{},\n\t\tOutput: &b,\n\t}\n\n\treturn &honeycomb.UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: &g,\n\t\t},\n\t\tManifest: manifest.Data{\n\t\t\tFlag: manifest.Flag{\n\t\t\t\tServiceID: \"123\",\n\t\t\t},\n\t\t},\n\t\tEndpointName: \"log\",\n\t\tServiceVersion: argparser.OptionalServiceVersion{\n\t\t\tOptionalString: argparser.OptionalString{Value: \"1\"},\n\t\t},\n\t\tAutoClone: argparser.OptionalAutoClone{\n\t\t\tOptionalBool: argparser.OptionalBool{\n\t\t\t\tOptional: argparser.Optional{\n\t\t\t\t\tWasSet: true,\n\t\t\t\t},\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t},\n\t}\n}\n\nfunc updateCommandAll() *honeycomb.UpdateCommand {\n\tvar b bytes.Buffer\n\n\tg := global.Data{\n\t\tConfig: config.File{},\n\t\tEnv:    config.Environment{},\n\t\tOutput: &b,\n\t}\n\n\treturn &honeycomb.UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: &g,\n\t\t},\n\t\tManifest: manifest.Data{\n\t\t\tFlag: manifest.Flag{\n\t\t\t\tServiceID: \"123\",\n\t\t\t},\n\t\t},\n\t\tEndpointName: \"log\",\n\t\tServiceVersion: argparser.OptionalServiceVersion{\n\t\t\tOptionalString: argparser.OptionalString{Value: \"1\"},\n\t\t},\n\t\tAutoClone: argparser.OptionalAutoClone{\n\t\t\tOptionalBool: argparser.OptionalBool{\n\t\t\t\tOptional: argparser.Optional{\n\t\t\t\t\tWasSet: true,\n\t\t\t\t},\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t},\n\t\tNewName:           argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new1\"},\n\t\tFormat:            argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new2\"},\n\t\tFormatVersion:     argparser.OptionalInt{Optional: argparser.Optional{WasSet: true}, Value: 3},\n\t\tToken:             argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new3\"},\n\t\tDataset:           argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new4\"},\n\t\tResponseCondition: argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new5\"},\n\t\tPlacement:         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new6\"},\n\t\tProcessingRegion:  argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"eu\"},\n\t}\n}\n\nfunc updateCommandMissingServiceID() *honeycomb.UpdateCommand {\n\tres := updateCommandAll()\n\tres.Manifest = manifest.Data{}\n\tres.ServiceVersion = argparser.OptionalServiceVersion{}\n\treturn res\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/honeycomb/list.go",
    "content": "package honeycomb\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// ListCommand calls the Fastly API to list Honeycomb logging endpoints.\ntype ListCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tInput          fastly.ListHoneycombsInput\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n}\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"list\", \"List Honeycomb endpoints on a Fastly service version\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *ListCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fsterr.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tc.Input.ServiceID = serviceID\n\tc.Input.ServiceVersion = fastly.ToValue(serviceVersion.Number)\n\n\to, err := c.Globals.APIClient.ListHoneycombs(context.TODO(), &c.Input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\tif !c.Globals.Verbose() {\n\t\ttw := text.NewTable(out)\n\t\ttw.AddHeader(\"SERVICE\", \"VERSION\", \"NAME\")\n\t\tfor _, honeycomb := range o {\n\t\t\ttw.AddLine(\n\t\t\t\tfastly.ToValue(honeycomb.ServiceID),\n\t\t\t\tfastly.ToValue(honeycomb.ServiceVersion),\n\t\t\t\tfastly.ToValue(honeycomb.Name),\n\t\t\t)\n\t\t}\n\t\ttw.Print()\n\t\treturn nil\n\t}\n\n\tfmt.Fprintf(out, \"Version: %d\\n\", c.Input.ServiceVersion)\n\tfor i, honeycomb := range o {\n\t\tfmt.Fprintf(out, \"\\tHoneycomb %d/%d\\n\", i+1, len(o))\n\t\tfmt.Fprintf(out, \"\\t\\tService ID: %s\\n\", fastly.ToValue(honeycomb.ServiceID))\n\t\tfmt.Fprintf(out, \"\\t\\tVersion: %d\\n\", fastly.ToValue(honeycomb.ServiceVersion))\n\t\tfmt.Fprintf(out, \"\\t\\tName: %s\\n\", fastly.ToValue(honeycomb.Name))\n\t\tfmt.Fprintf(out, \"\\t\\tDataset: %s\\n\", fastly.ToValue(honeycomb.Dataset))\n\t\tfmt.Fprintf(out, \"\\t\\tToken: %s\\n\", fastly.ToValue(honeycomb.Token))\n\t\tfmt.Fprintf(out, \"\\t\\tFormat: %s\\n\", fastly.ToValue(honeycomb.Format))\n\t\tfmt.Fprintf(out, \"\\t\\tFormat version: %d\\n\", fastly.ToValue(honeycomb.FormatVersion))\n\t\tfmt.Fprintf(out, \"\\t\\tResponse condition: %s\\n\", fastly.ToValue(honeycomb.ResponseCondition))\n\t\tfmt.Fprintf(out, \"\\t\\tPlacement: %s\\n\", fastly.ToValue(honeycomb.Placement))\n\t\tfmt.Fprintf(out, \"\\t\\tProcessing region: %s\\n\", fastly.ToValue(honeycomb.ProcessingRegion))\n\t}\n\tfmt.Fprintln(out)\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/honeycomb/root.go",
    "content": "package honeycomb\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"honeycomb\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manipulate Fastly service version Honeycomb logging endpoints\")\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/honeycomb/update.go",
    "content": "package honeycomb\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/service/logging/logflags\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/manifest\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// UpdateCommand calls the Fastly API to update a Honeycomb logging endpoint.\ntype UpdateCommand struct {\n\targparser.Base\n\tManifest manifest.Data\n\n\t// Required.\n\tEndpointName   string // Can't shadow argparser.Base method Name().\n\tServiceName    argparser.OptionalServiceNameID\n\tServiceVersion argparser.OptionalServiceVersion\n\n\t// Optional.\n\tAutoClone         argparser.OptionalAutoClone\n\tDataset           argparser.OptionalString\n\tFormat            argparser.OptionalString\n\tFormatVersion     argparser.OptionalInt\n\tNewName           argparser.OptionalString\n\tPlacement         argparser.OptionalString\n\tProcessingRegion  argparser.OptionalString\n\tResponseCondition argparser.OptionalString\n\tToken             argparser.OptionalString\n}\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"update\", \"Update a Honeycomb logging endpoint on a Fastly service version\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"The name of the Honeycomb logging object\").Short('n').Required().StringVar(&c.EndpointName)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.ServiceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.CmdClause.Flag(\"auth-token\", \"The Write Key from the Account page of your Honeycomb account\").Action(c.Token.Set).StringVar(&c.Token.Value)\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.AutoClone.Set,\n\t\tDst:    &c.AutoClone.Value,\n\t})\n\tc.CmdClause.Flag(\"dataset\", \"The Honeycomb Dataset you want to log to\").Action(c.Dataset.Set).StringVar(&c.Dataset.Value)\n\tlogflags.Format(c.CmdClause, &c.Format)\n\tlogflags.FormatVersion(c.CmdClause, &c.FormatVersion)\n\tc.CmdClause.Flag(\"new-name\", \"New name of the Honeycomb logging object\").Action(c.NewName.Set).StringVar(&c.NewName.Value)\n\tlogflags.Placement(c.CmdClause, &c.Placement)\n\tlogflags.ProcessingRegion(c.CmdClause, &c.ProcessingRegion, \"Honeycomb\")\n\tlogflags.ResponseCondition(c.CmdClause, &c.ResponseCondition)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.ServiceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.ServiceName.Value,\n\t})\n\treturn &c\n}\n\n// ConstructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *UpdateCommand) ConstructInput(serviceID string, serviceVersion int) (*fastly.UpdateHoneycombInput, error) {\n\tinput := fastly.UpdateHoneycombInput{\n\t\tServiceID:      serviceID,\n\t\tServiceVersion: serviceVersion,\n\t\tName:           c.EndpointName,\n\t}\n\n\tif c.NewName.WasSet {\n\t\tinput.NewName = &c.NewName.Value\n\t}\n\n\tif c.Format.WasSet {\n\t\tinput.Format = fastly.ToPointer(argparser.Content(c.Format.Value))\n\t}\n\n\tif c.FormatVersion.WasSet {\n\t\tinput.FormatVersion = &c.FormatVersion.Value\n\t}\n\n\tif c.Token.WasSet {\n\t\tinput.Token = &c.Token.Value\n\t}\n\n\tif c.Dataset.WasSet {\n\t\tinput.Dataset = &c.Dataset.Value\n\t}\n\n\tif c.ResponseCondition.WasSet {\n\t\tinput.ResponseCondition = &c.ResponseCondition.Value\n\t}\n\n\tif c.Placement.WasSet {\n\t\tinput.Placement = &c.Placement.Value\n\t}\n\n\tif c.ProcessingRegion.WasSet {\n\t\tinput.ProcessingRegion = &c.ProcessingRegion.Value\n\t}\n\n\treturn &input, nil\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *UpdateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(false),\n\t\tLocked:             optional.Of(false),\n\t\tAutoCloneFlag:      c.AutoClone,\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.ServiceName,\n\t\tServiceVersionFlag: c.ServiceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": errors.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tinput, err := c.ConstructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\thoneycomb, err := c.Globals.APIClient.UpdateHoneycomb(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\ttext.Success(\n\t\tout,\n\t\t\"Updated Honeycomb logging endpoint %s (service %s version %d)\",\n\t\tfastly.ToValue(honeycomb.Name),\n\t\tfastly.ToValue(honeycomb.ServiceID),\n\t\tfastly.ToValue(honeycomb.ServiceVersion),\n\t)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/https/create.go",
    "content": "package https\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/service/logging/logflags\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/manifest\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// CreateCommand calls the Fastly API to create an HTTPS logging endpoint.\ntype CreateCommand struct {\n\targparser.Base\n\tManifest manifest.Data\n\n\t// Required.\n\tServiceName    argparser.OptionalServiceNameID\n\tServiceVersion argparser.OptionalServiceVersion\n\n\t// Optional.\n\tAutoClone         argparser.OptionalAutoClone\n\tCompressionCodec  argparser.OptionalString\n\tContentType       argparser.OptionalString\n\tEndpointName      argparser.OptionalString // Can't shadow argparser.Base method Name().\n\tFormat            argparser.OptionalString\n\tFormatVersion     argparser.OptionalInt\n\tGzipLevel         argparser.OptionalInt\n\tHeaderName        argparser.OptionalString\n\tHeaderValue       argparser.OptionalString\n\tJSONFormat        argparser.OptionalString\n\tMessageType       argparser.OptionalString\n\tMethod            argparser.OptionalString\n\tPeriod            argparser.OptionalInt\n\tPlacement         argparser.OptionalString\n\tProcessingRegion  argparser.OptionalString\n\tRequestMaxBytes   argparser.OptionalInt\n\tRequestMaxEntries argparser.OptionalInt\n\tResponseCondition argparser.OptionalString\n\tTLSCACert         argparser.OptionalString\n\tTLSClientCert     argparser.OptionalString\n\tTLSClientKey      argparser.OptionalString\n\tTLSHostname       argparser.OptionalString\n\tURL               argparser.OptionalString\n}\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"create\", \"Create an HTTPS logging endpoint on a Fastly service version\").Alias(\"add\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"The name of the HTTPS logging object. Used as a primary key for API access\").Short('n').Action(c.EndpointName.Set).StringVar(&c.EndpointName.Value)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.ServiceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.AutoClone.Set,\n\t\tDst:    &c.AutoClone.Value,\n\t})\n\tc.CmdClause.Flag(\"content-type\", \"Content type of the header sent with the request\").Action(c.ContentType.Set).StringVar(&c.ContentType.Value)\n\tlogflags.CompressionCodec(c.CmdClause, &c.CompressionCodec)\n\tlogflags.Format(c.CmdClause, &c.Format)\n\tlogflags.FormatVersion(c.CmdClause, &c.FormatVersion)\n\tlogflags.GzipLevel(c.CmdClause, &c.GzipLevel)\n\tc.CmdClause.Flag(\"header-name\", \"Name of the custom header sent with the request\").Action(c.HeaderName.Set).StringVar(&c.HeaderName.Value)\n\tc.CmdClause.Flag(\"header-value\", \"Value of the custom header sent with the request\").Action(c.HeaderValue.Set).StringVar(&c.HeaderValue.Value)\n\tc.CmdClause.Flag(\"json-format\", \"Enforces valid JSON formatting for log entries. Can be disabled 0, array of json (wraps JSON log batches in an array) 1, or newline delimited json (places each JSON log entry onto a new line in a batch) 2\").Action(c.JSONFormat.Set).StringVar(&c.JSONFormat.Value)\n\tlogflags.MessageType(c.CmdClause, &c.MessageType)\n\tc.CmdClause.Flag(\"method\", \"HTTP method used for request. Can be POST or PUT. Defaults to POST if not specified\").Action(c.Method.Set).StringVar(&c.Method.Value)\n\tlogflags.Period(c.CmdClause, &c.Period)\n\tlogflags.Placement(c.CmdClause, &c.Placement)\n\tlogflags.ProcessingRegion(c.CmdClause, &c.ProcessingRegion, \"HTTPS\")\n\tc.CmdClause.Flag(\"request-max-bytes\", \"Maximum size of log batch, if non-zero. Defaults to 100MB\").Action(c.RequestMaxBytes.Set).IntVar(&c.RequestMaxBytes.Value)\n\tc.CmdClause.Flag(\"request-max-entries\", \"Maximum number of logs to append to a batch, if non-zero. Defaults to 10k\").Action(c.RequestMaxEntries.Set).IntVar(&c.RequestMaxEntries.Value)\n\tlogflags.ResponseCondition(c.CmdClause, &c.ResponseCondition)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.ServiceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.ServiceName.Value,\n\t})\n\tlogflags.TLSCACert(c.CmdClause, &c.TLSCACert)\n\tlogflags.TLSClientCert(c.CmdClause, &c.TLSClientCert)\n\tlogflags.TLSClientKey(c.CmdClause, &c.TLSClientKey)\n\tlogflags.TLSHostname(c.CmdClause, &c.TLSHostname)\n\tc.CmdClause.Flag(\"url\", \"URL that log data will be sent to. Must use the https protocol\").Action(c.URL.Set).StringVar(&c.URL.Value)\n\treturn &c\n}\n\n// ConstructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *CreateCommand) ConstructInput(serviceID string, serviceVersion int) (*fastly.CreateHTTPSInput, error) {\n\tvar input fastly.CreateHTTPSInput\n\n\t// The following blocks enforces the mutual exclusivity of the\n\t// CompressionCodec and GzipLevel flags.\n\tif c.CompressionCodec.WasSet && c.GzipLevel.WasSet {\n\t\treturn nil, fmt.Errorf(\"error parsing arguments: the --compression-codec flag is mutually exclusive with the --gzip-level flag\")\n\t}\n\n\tinput.ServiceID = serviceID\n\tif c.EndpointName.WasSet {\n\t\tinput.Name = &c.EndpointName.Value\n\t}\n\tif c.URL.WasSet {\n\t\tinput.URL = &c.URL.Value\n\t}\n\tinput.ServiceVersion = serviceVersion\n\n\tif c.CompressionCodec.WasSet {\n\t\tinput.CompressionCodec = &c.CompressionCodec.Value\n\t}\n\n\tif c.ContentType.WasSet {\n\t\tinput.ContentType = &c.ContentType.Value\n\t}\n\tif c.GzipLevel.WasSet {\n\t\tinput.GzipLevel = &c.GzipLevel.Value\n\t}\n\n\tif c.HeaderName.WasSet {\n\t\tinput.HeaderName = &c.HeaderName.Value\n\t}\n\n\tif c.HeaderValue.WasSet {\n\t\tinput.HeaderValue = &c.HeaderValue.Value\n\t}\n\n\tif c.Method.WasSet {\n\t\tinput.Method = &c.Method.Value\n\t}\n\n\tif c.JSONFormat.WasSet {\n\t\tinput.JSONFormat = &c.JSONFormat.Value\n\t}\n\n\tif c.RequestMaxEntries.WasSet {\n\t\tinput.RequestMaxEntries = &c.RequestMaxEntries.Value\n\t}\n\n\tif c.RequestMaxBytes.WasSet {\n\t\tinput.RequestMaxBytes = &c.RequestMaxBytes.Value\n\t}\n\n\tif c.TLSCACert.WasSet {\n\t\tinput.TLSCACert = &c.TLSCACert.Value\n\t}\n\n\tif c.TLSClientCert.WasSet {\n\t\tinput.TLSClientCert = &c.TLSClientCert.Value\n\t}\n\n\tif c.TLSClientKey.WasSet {\n\t\tinput.TLSClientKey = &c.TLSClientKey.Value\n\t}\n\n\tif c.TLSHostname.WasSet {\n\t\tinput.TLSHostname = &c.TLSHostname.Value\n\t}\n\n\tif c.Format.WasSet {\n\t\tinput.Format = fastly.ToPointer(argparser.Content(c.Format.Value))\n\t}\n\n\tif c.FormatVersion.WasSet {\n\t\tinput.FormatVersion = &c.FormatVersion.Value\n\t}\n\n\tif c.ResponseCondition.WasSet {\n\t\tinput.ResponseCondition = &c.ResponseCondition.Value\n\t}\n\n\tif c.Period.WasSet {\n\t\tinput.Period = &c.Period.Value\n\t}\n\tif c.Placement.WasSet {\n\t\tinput.Placement = &c.Placement.Value\n\t}\n\n\tif c.ProcessingRegion.WasSet {\n\t\tinput.ProcessingRegion = &c.ProcessingRegion.Value\n\t}\n\n\tif c.MessageType.WasSet {\n\t\tinput.MessageType = &c.MessageType.Value\n\t}\n\n\treturn &input, nil\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *CreateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(false),\n\t\tLocked:             optional.Of(false),\n\t\tAutoCloneFlag:      c.AutoClone,\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.ServiceName,\n\t\tServiceVersionFlag: c.ServiceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": errors.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tinput, err := c.ConstructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\td, err := c.Globals.APIClient.CreateHTTPS(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\ttext.Success(\n\t\tout,\n\t\t\"Created HTTPS logging endpoint %s (service %s version %d)\",\n\t\tfastly.ToValue(d.Name),\n\t\tfastly.ToValue(d.ServiceID),\n\t\tfastly.ToValue(d.ServiceVersion),\n\t)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/https/delete.go",
    "content": "package https\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DeleteCommand calls the Fastly API to delete an HTTPS logging endpoint.\ntype DeleteCommand struct {\n\targparser.Base\n\tInput          fastly.DeleteHTTPSInput\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n\tautoClone      argparser.OptionalAutoClone\n}\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"delete\", \"Delete an HTTPS logging endpoint on a Fastly service version\").Alias(\"remove\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"The name of the HTTPS logging object\").Short('n').Required().StringVar(&c.Input.Name)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.autoClone.Set,\n\t\tDst:    &c.autoClone.Value,\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DeleteCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(false),\n\t\tLocked:             optional.Of(false),\n\t\tAutoCloneFlag:      c.autoClone,\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": errors.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tc.Input.ServiceID = serviceID\n\tc.Input.ServiceVersion = fastly.ToValue(serviceVersion.Number)\n\n\tif err := c.Globals.APIClient.DeleteHTTPS(context.TODO(), &c.Input); err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Deleted HTTPS logging endpoint %s (service %s version %d)\", c.Input.Name, c.Input.ServiceID, c.Input.ServiceVersion)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/https/describe.go",
    "content": "package https\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DescribeCommand calls the Fastly API to describe an HTTPS logging endpoint.\ntype DescribeCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tInput          fastly.GetHTTPSInput\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n}\n\n// NewDescribeCommand returns a usable command registered under the parent.\nfunc NewDescribeCommand(parent argparser.Registerer, g *global.Data) *DescribeCommand {\n\tc := DescribeCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"describe\", \"Show detailed information about an HTTPS logging endpoint on a Fastly service version\").Alias(\"get\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"The name of the HTTPS logging object\").Short('n').Required().StringVar(&c.Input.Name)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DescribeCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fsterr.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tc.Input.ServiceID = serviceID\n\tc.Input.ServiceVersion = fastly.ToValue(serviceVersion.Number)\n\n\to, err := c.Globals.APIClient.GetHTTPS(context.TODO(), &c.Input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\tlines := text.Lines{\n\t\t\"Compression codec\":      fastly.ToValue(o.CompressionCodec),\n\t\t\"Content type\":           fastly.ToValue(o.ContentType),\n\t\t\"Format version\":         fastly.ToValue(o.FormatVersion),\n\t\t\"Format\":                 fastly.ToValue(o.Format),\n\t\t\"GZip level\":             fastly.ToValue(o.GzipLevel),\n\t\t\"Header name\":            fastly.ToValue(o.HeaderName),\n\t\t\"Header value\":           fastly.ToValue(o.HeaderValue),\n\t\t\"JSON format\":            fastly.ToValue(o.JSONFormat),\n\t\t\"Message type\":           fastly.ToValue(o.MessageType),\n\t\t\"Method\":                 fastly.ToValue(o.Method),\n\t\t\"Name\":                   fastly.ToValue(o.Name),\n\t\t\"Period\":                 fastly.ToValue(o.Period),\n\t\t\"Placement\":              fastly.ToValue(o.Placement),\n\t\t\"Processing region\":      fastly.ToValue(o.ProcessingRegion),\n\t\t\"Request max bytes\":      fastly.ToValue(o.RequestMaxBytes),\n\t\t\"Request max entries\":    fastly.ToValue(o.RequestMaxEntries),\n\t\t\"Response condition\":     fastly.ToValue(o.ResponseCondition),\n\t\t\"TLS CA certificate\":     fastly.ToValue(o.TLSCACert),\n\t\t\"TLS client certificate\": fastly.ToValue(o.TLSClientCert),\n\t\t\"TLS client key\":         fastly.ToValue(o.TLSClientKey),\n\t\t\"TLS hostname\":           fastly.ToValue(o.TLSHostname),\n\t\t\"URL\":                    fastly.ToValue(o.URL),\n\t\t\"Version\":                fastly.ToValue(o.ServiceVersion),\n\t}\n\tif !c.Globals.Verbose() {\n\t\tlines[\"Service ID\"] = fastly.ToValue(o.ServiceID)\n\t}\n\ttext.PrintLines(out, lines)\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/https/doc.go",
    "content": "// Package https contains commands to inspect and manipulate Fastly service HTTPS\n// logging endpoints.\npackage https\n"
  },
  {
    "path": "pkg/commands/service/logging/https/https_integration_test.go",
    "content": "package https_test\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"net/http\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/mock\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n\n\troot \"github.com/fastly/cli/pkg/commands/service\"\n\tparent \"github.com/fastly/cli/pkg/commands/service/logging\"\n\tsub \"github.com/fastly/cli/pkg/commands/service/logging/https\"\n)\n\nfunc TestHTTPSCreate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name log --url example.com --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tCreateHTTPSFn:  createHTTPSOK,\n\t\t\t},\n\t\t\tWantOutput: \"Created HTTPS logging endpoint log (service 123 version 4)\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name log --url example.com --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tCreateHTTPSFn:  createHTTPSError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name log --url example.com --compression-codec zstd --gzip-level 9 --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t},\n\t\t\tWantError: \"error parsing arguments: the --compression-codec flag is mutually exclusive with the --gzip-level flag\",\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"create\"}, scenarios)\n}\n\nfunc TestHTTPSList(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tListHTTPSFn:  listHTTPSsOK,\n\t\t\t},\n\t\t\tWantOutput: listHTTPSsShortOutput,\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --verbose\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tListHTTPSFn:  listHTTPSsOK,\n\t\t\t},\n\t\t\tWantOutput: listHTTPSsVerboseOutput,\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 -v\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tListHTTPSFn:  listHTTPSsOK,\n\t\t\t},\n\t\t\tWantOutput: listHTTPSsVerboseOutput,\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tListHTTPSFn:  listHTTPSsError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"list\"}, scenarios)\n}\n\nfunc TestHTTPSDescribe(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"--service-id 123 --version 1\",\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tGetHTTPSFn:   getHTTPSError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tGetHTTPSFn:   getHTTPSOK,\n\t\t\t},\n\t\t\tWantOutput: describeHTTPSOutput,\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"describe\"}, scenarios)\n}\n\nfunc TestHTTPSUpdate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"--service-id 123 --version 1 --new-name log\",\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs --new-name log --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tUpdateHTTPSFn:  updateHTTPSError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs --new-name log --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tUpdateHTTPSFn:  updateHTTPSOK,\n\t\t\t},\n\t\t\tWantOutput: \"Updated HTTPS logging endpoint log (service 123 version 4)\",\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"update\"}, scenarios)\n}\n\nfunc TestHTTPSDelete(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"--service-id 123 --version 1\",\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tDeleteHTTPSFn:  deleteHTTPSError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tDeleteHTTPSFn:  deleteHTTPSOK,\n\t\t\t},\n\t\t\tWantOutput: \"Deleted HTTPS logging endpoint logs (service 123 version 4)\",\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"delete\"}, scenarios)\n}\n\nvar errTest = errors.New(\"fixture error\")\n\nfunc createHTTPSOK(_ context.Context, i *fastly.CreateHTTPSInput) (*fastly.HTTPS, error) {\n\treturn &fastly.HTTPS{\n\t\tServiceID:         fastly.ToPointer(i.ServiceID),\n\t\tServiceVersion:    fastly.ToPointer(i.ServiceVersion),\n\t\tName:              fastly.ToPointer(\"log\"),\n\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\tURL:               fastly.ToPointer(\"example.com\"),\n\t\tRequestMaxEntries: fastly.ToPointer(2),\n\t\tRequestMaxBytes:   fastly.ToPointer(2),\n\t\tCompressionCodec:  fastly.ToPointer(\"\"),\n\t\tContentType:       fastly.ToPointer(\"application/json\"),\n\t\tGzipLevel:         fastly.ToPointer(0),\n\t\tHeaderName:        fastly.ToPointer(\"name\"),\n\t\tHeaderValue:       fastly.ToPointer(\"value\"),\n\t\tMethod:            fastly.ToPointer(http.MethodGet),\n\t\tJSONFormat:        fastly.ToPointer(\"1\"),\n\t\tPeriod:            fastly.ToPointer(0),\n\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\tTLSCACert:         fastly.ToPointer(\"-----BEGIN CERTIFICATE-----foo\"),\n\t\tTLSClientCert:     fastly.ToPointer(\"-----BEGIN CERTIFICATE-----bar\"),\n\t\tTLSClientKey:      fastly.ToPointer(\"-----BEGIN PRIVATE KEY-----bar\"),\n\t\tTLSHostname:       fastly.ToPointer(\"example.com\"),\n\t\tMessageType:       fastly.ToPointer(\"classic\"),\n\t\tFormatVersion:     fastly.ToPointer(2),\n\t}, nil\n}\n\nfunc createHTTPSError(_ context.Context, _ *fastly.CreateHTTPSInput) (*fastly.HTTPS, error) {\n\treturn nil, errTest\n}\n\nfunc listHTTPSsOK(_ context.Context, i *fastly.ListHTTPSInput) ([]*fastly.HTTPS, error) {\n\treturn []*fastly.HTTPS{\n\t\t{\n\t\t\tServiceID:         fastly.ToPointer(i.ServiceID),\n\t\t\tServiceVersion:    fastly.ToPointer(i.ServiceVersion),\n\t\t\tName:              fastly.ToPointer(\"logs\"),\n\t\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\t\tURL:               fastly.ToPointer(\"example.com\"),\n\t\t\tRequestMaxEntries: fastly.ToPointer(2),\n\t\t\tRequestMaxBytes:   fastly.ToPointer(2),\n\t\t\tCompressionCodec:  fastly.ToPointer(\"\"),\n\t\t\tContentType:       fastly.ToPointer(\"application/json\"),\n\t\t\tGzipLevel:         fastly.ToPointer(0),\n\t\t\tHeaderName:        fastly.ToPointer(\"name\"),\n\t\t\tHeaderValue:       fastly.ToPointer(\"value\"),\n\t\t\tMethod:            fastly.ToPointer(http.MethodGet),\n\t\t\tJSONFormat:        fastly.ToPointer(\"1\"),\n\t\t\tPeriod:            fastly.ToPointer(0),\n\t\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\t\tTLSCACert:         fastly.ToPointer(\"-----BEGIN CERTIFICATE-----foo\"),\n\t\t\tTLSClientCert:     fastly.ToPointer(\"-----BEGIN CERTIFICATE-----bar\"),\n\t\t\tTLSClientKey:      fastly.ToPointer(\"-----BEGIN PRIVATE KEY-----bar\"),\n\t\t\tTLSHostname:       fastly.ToPointer(\"example.com\"),\n\t\t\tMessageType:       fastly.ToPointer(\"classic\"),\n\t\t\tFormatVersion:     fastly.ToPointer(2),\n\t\t\tProcessingRegion:  fastly.ToPointer(\"us\"),\n\t\t},\n\t\t{\n\t\t\tServiceID:         fastly.ToPointer(i.ServiceID),\n\t\t\tServiceVersion:    fastly.ToPointer(i.ServiceVersion),\n\t\t\tName:              fastly.ToPointer(\"analytics\"),\n\t\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\t\tURL:               fastly.ToPointer(\"analytics.example.com\"),\n\t\t\tRequestMaxEntries: fastly.ToPointer(2),\n\t\t\tRequestMaxBytes:   fastly.ToPointer(2),\n\t\t\tCompressionCodec:  fastly.ToPointer(\"\"),\n\t\t\tContentType:       fastly.ToPointer(\"application/json\"),\n\t\t\tGzipLevel:         fastly.ToPointer(0),\n\t\t\tHeaderName:        fastly.ToPointer(\"name\"),\n\t\t\tHeaderValue:       fastly.ToPointer(\"value\"),\n\t\t\tMethod:            fastly.ToPointer(http.MethodGet),\n\t\t\tJSONFormat:        fastly.ToPointer(\"1\"),\n\t\t\tPeriod:            fastly.ToPointer(0),\n\t\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\t\tTLSCACert:         fastly.ToPointer(\"-----BEGIN CERTIFICATE-----foo\"),\n\t\t\tTLSClientCert:     fastly.ToPointer(\"-----BEGIN CERTIFICATE-----bar\"),\n\t\t\tTLSClientKey:      fastly.ToPointer(\"-----BEGIN PRIVATE KEY-----bar\"),\n\t\t\tTLSHostname:       fastly.ToPointer(\"example.com\"),\n\t\t\tMessageType:       fastly.ToPointer(\"classic\"),\n\t\t\tFormatVersion:     fastly.ToPointer(2),\n\t\t\tProcessingRegion:  fastly.ToPointer(\"us\"),\n\t\t},\n\t}, nil\n}\n\nfunc listHTTPSsError(_ context.Context, _ *fastly.ListHTTPSInput) ([]*fastly.HTTPS, error) {\n\treturn nil, errTest\n}\n\nvar listHTTPSsShortOutput = strings.TrimSpace(`\nSERVICE  VERSION  NAME\n123      1        logs\n123      1        analytics\n`) + \"\\n\"\n\nvar listHTTPSsVerboseOutput = strings.TrimSpace(`\nFastly API endpoint: https://api.fastly.com\nFastly API token provided via config file (auth: user)\n\nService ID (via --service-id): 123\n\nVersion: 1\n\tHTTPS 1/2\n\t\tService ID: 123\n\t\tVersion: 1\n\t\tName: logs\n\t\tURL: example.com\n\t\tCompression codec: \n\t\tContent type: application/json\n\t\tGZip level: 0\n\t\tHeader name: name\n\t\tHeader value: value\n\t\tMethod: GET\n\t\tJSON format: 1\n\t\tTLS CA certificate: -----BEGIN CERTIFICATE-----foo\n\t\tTLS client certificate: -----BEGIN CERTIFICATE-----bar\n\t\tTLS client key: -----BEGIN PRIVATE KEY-----bar\n\t\tTLS hostname: example.com\n\t\tRequest max entries: 2\n\t\tRequest max bytes: 2\n\t\tMessage type: classic\n\t\tFormat: %h %l %u %t \"%r\" %>s %b\n\t\tFormat version: 2\n\t\tResponse condition: Prevent default logging\n\t\tPeriod: 0\n\t\tPlacement: none\n\t\tProcessing region: us\n\tHTTPS 2/2\n\t\tService ID: 123\n\t\tVersion: 1\n\t\tName: analytics\n\t\tURL: analytics.example.com\n\t\tCompression codec: \n\t\tContent type: application/json\n\t\tGZip level: 0\n\t\tHeader name: name\n\t\tHeader value: value\n\t\tMethod: GET\n\t\tJSON format: 1\n\t\tTLS CA certificate: -----BEGIN CERTIFICATE-----foo\n\t\tTLS client certificate: -----BEGIN CERTIFICATE-----bar\n\t\tTLS client key: -----BEGIN PRIVATE KEY-----bar\n\t\tTLS hostname: example.com\n\t\tRequest max entries: 2\n\t\tRequest max bytes: 2\n\t\tMessage type: classic\n\t\tFormat: %h %l %u %t \"%r\" %>s %b\n\t\tFormat version: 2\n\t\tResponse condition: Prevent default logging\n\t\tPeriod: 0\n\t\tPlacement: none\n\t\tProcessing region: us\n`) + \"\\n\\n\"\n\nfunc getHTTPSOK(_ context.Context, i *fastly.GetHTTPSInput) (*fastly.HTTPS, error) {\n\treturn &fastly.HTTPS{\n\t\tServiceID:         fastly.ToPointer(i.ServiceID),\n\t\tServiceVersion:    fastly.ToPointer(i.ServiceVersion),\n\t\tName:              fastly.ToPointer(\"log\"),\n\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\tURL:               fastly.ToPointer(\"example.com\"),\n\t\tRequestMaxEntries: fastly.ToPointer(2),\n\t\tRequestMaxBytes:   fastly.ToPointer(2),\n\t\tCompressionCodec:  fastly.ToPointer(\"\"),\n\t\tContentType:       fastly.ToPointer(\"application/json\"),\n\t\tGzipLevel:         fastly.ToPointer(0),\n\t\tHeaderName:        fastly.ToPointer(\"name\"),\n\t\tHeaderValue:       fastly.ToPointer(\"value\"),\n\t\tMethod:            fastly.ToPointer(http.MethodGet),\n\t\tJSONFormat:        fastly.ToPointer(\"1\"),\n\t\tPeriod:            fastly.ToPointer(0),\n\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\tTLSCACert:         fastly.ToPointer(\"-----BEGIN CERTIFICATE-----foo\"),\n\t\tTLSClientCert:     fastly.ToPointer(\"-----BEGIN CERTIFICATE-----bar\"),\n\t\tTLSClientKey:      fastly.ToPointer(\"-----BEGIN PRIVATE KEY-----bar\"),\n\t\tTLSHostname:       fastly.ToPointer(\"example.com\"),\n\t\tMessageType:       fastly.ToPointer(\"classic\"),\n\t\tFormatVersion:     fastly.ToPointer(2),\n\t\tProcessingRegion:  fastly.ToPointer(\"us\"),\n\t}, nil\n}\n\nfunc getHTTPSError(_ context.Context, _ *fastly.GetHTTPSInput) (*fastly.HTTPS, error) {\n\treturn nil, errTest\n}\n\nvar describeHTTPSOutput = \"\\n\" + strings.TrimSpace(`\nCompression codec: \nContent type: application/json\nFormat: %h %l %u %t \"%r\" %>s %b\nFormat version: 2\nGZip level: 0\nHeader name: name\nHeader value: value\nJSON format: 1\nMessage type: classic\nMethod: GET\nName: log\nPeriod: 0\nPlacement: none\nProcessing region: us\nRequest max bytes: 2\nRequest max entries: 2\nResponse condition: Prevent default logging\nService ID: 123\nTLS CA certificate: -----BEGIN CERTIFICATE-----foo\nTLS client certificate: -----BEGIN CERTIFICATE-----bar\nTLS client key: -----BEGIN PRIVATE KEY-----bar\nTLS hostname: example.com\nURL: example.com\nVersion: 1\n`) + \"\\n\"\n\nfunc updateHTTPSOK(_ context.Context, i *fastly.UpdateHTTPSInput) (*fastly.HTTPS, error) {\n\treturn &fastly.HTTPS{\n\t\tServiceID:         fastly.ToPointer(i.ServiceID),\n\t\tServiceVersion:    fastly.ToPointer(i.ServiceVersion),\n\t\tName:              fastly.ToPointer(\"log\"),\n\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\tURL:               fastly.ToPointer(\"example.com\"),\n\t\tRequestMaxEntries: fastly.ToPointer(2),\n\t\tRequestMaxBytes:   fastly.ToPointer(2),\n\t\tCompressionCodec:  fastly.ToPointer(\"\"),\n\t\tContentType:       fastly.ToPointer(\"application/json\"),\n\t\tGzipLevel:         fastly.ToPointer(7),\n\t\tHeaderName:        fastly.ToPointer(\"name\"),\n\t\tHeaderValue:       fastly.ToPointer(\"value\"),\n\t\tMethod:            fastly.ToPointer(http.MethodGet),\n\t\tJSONFormat:        fastly.ToPointer(\"1\"),\n\t\tPeriod:            fastly.ToPointer(0),\n\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\tTLSCACert:         fastly.ToPointer(\"-----BEGIN CERTIFICATE-----foo\"),\n\t\tTLSClientCert:     fastly.ToPointer(\"-----BEGIN CERTIFICATE-----bar\"),\n\t\tTLSClientKey:      fastly.ToPointer(\"-----BEGIN PRIVATE KEY-----bar\"),\n\t\tTLSHostname:       fastly.ToPointer(\"example.com\"),\n\t\tMessageType:       fastly.ToPointer(\"classic\"),\n\t\tFormatVersion:     fastly.ToPointer(2),\n\t}, nil\n}\n\nfunc updateHTTPSError(_ context.Context, _ *fastly.UpdateHTTPSInput) (*fastly.HTTPS, error) {\n\treturn nil, errTest\n}\n\nfunc deleteHTTPSOK(_ context.Context, _ *fastly.DeleteHTTPSInput) error {\n\treturn nil\n}\n\nfunc deleteHTTPSError(_ context.Context, _ *fastly.DeleteHTTPSInput) error {\n\treturn errTest\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/https/https_test.go",
    "content": "package https_test\n\nimport (\n\t\"bytes\"\n\t\"net/http\"\n\t\"testing\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/service/logging/https\"\n\t\"github.com/fastly/cli/pkg/config\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/manifest\"\n\t\"github.com/fastly/cli/pkg/mock\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n)\n\nfunc TestCreateHTTPSInput(t *testing.T) {\n\tfor _, testcase := range []struct {\n\t\tname      string\n\t\tcmd       *https.CreateCommand\n\t\twant      *fastly.CreateHTTPSInput\n\t\twantError string\n\t}{\n\t\t{\n\t\t\tname: \"required values set flag serviceID\",\n\t\t\tcmd:  createCommandRequired(),\n\t\t\twant: &fastly.CreateHTTPSInput{\n\t\t\t\tServiceID:      \"123\",\n\t\t\t\tServiceVersion: 4,\n\t\t\t\tName:           fastly.ToPointer(\"log\"),\n\t\t\t\tURL:            fastly.ToPointer(\"example.com\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"all values set flag serviceID\",\n\t\t\tcmd:  createCommandAll(),\n\t\t\twant: &fastly.CreateHTTPSInput{\n\t\t\t\tServiceID:         \"123\",\n\t\t\t\tServiceVersion:    4,\n\t\t\t\tName:              fastly.ToPointer(\"logs\"),\n\t\t\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\t\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\t\t\tURL:               fastly.ToPointer(\"example.com\"),\n\t\t\t\tRequestMaxEntries: fastly.ToPointer(2),\n\t\t\t\tRequestMaxBytes:   fastly.ToPointer(2),\n\t\t\t\tContentType:       fastly.ToPointer(\"application/json\"),\n\t\t\t\tHeaderName:        fastly.ToPointer(\"name\"),\n\t\t\t\tHeaderValue:       fastly.ToPointer(\"value\"),\n\t\t\t\tMethod:            fastly.ToPointer(http.MethodGet),\n\t\t\t\tJSONFormat:        fastly.ToPointer(\"1\"),\n\t\t\t\tPeriod:            fastly.ToPointer(5),\n\t\t\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\t\t\tTLSCACert:         fastly.ToPointer(\"-----BEGIN CERTIFICATE-----foo\"),\n\t\t\t\tTLSClientCert:     fastly.ToPointer(\"-----BEGIN CERTIFICATE-----bar\"),\n\t\t\t\tTLSClientKey:      fastly.ToPointer(\"-----BEGIN PRIVATE KEY-----bar\"),\n\t\t\t\tTLSHostname:       fastly.ToPointer(\"example.com\"),\n\t\t\t\tMessageType:       fastly.ToPointer(\"classic\"),\n\t\t\t\tFormatVersion:     fastly.ToPointer(2),\n\t\t\t\tProcessingRegion:  fastly.ToPointer(\"eu\"),\n\t\t\t\tCompressionCodec:  fastly.ToPointer(\"zstd\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:      \"error missing serviceID\",\n\t\t\tcmd:       createCommandMissingServiceID(),\n\t\t\twant:      nil,\n\t\t\twantError: errors.ErrNoServiceID.Error(),\n\t\t},\n\t} {\n\t\tt.Run(testcase.name, func(t *testing.T) {\n\t\t\tif testcase.wantError == errors.ErrNoServiceID.Error() {\n\t\t\t\tt.Setenv(\"FASTLY_SERVICE_ID\", \"\")\n\t\t\t}\n\t\t\tvar bs []byte\n\t\t\tout := bytes.NewBuffer(bs)\n\t\t\tverboseMode := true\n\n\t\t\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\t\t\tAutoCloneFlag:      testcase.cmd.AutoClone,\n\t\t\t\tAPIClient:          testcase.cmd.Globals.APIClient,\n\t\t\t\tManifest:           testcase.cmd.Manifest,\n\t\t\t\tOut:                out,\n\t\t\t\tServiceVersionFlag: testcase.cmd.ServiceVersion,\n\t\t\t\tVerboseMode:        verboseMode,\n\t\t\t})\n\n\t\t\tswitch {\n\t\t\tcase err != nil && testcase.wantError == \"\":\n\t\t\t\tt.Fatalf(\"unexpected error getting service details: %v\", err)\n\t\t\t\treturn\n\t\t\tcase err != nil && testcase.wantError != \"\":\n\t\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\t\treturn\n\t\t\tcase err == nil && testcase.wantError != \"\":\n\t\t\t\tt.Fatalf(\"expected error, have nil (service details: %s, %d)\", serviceID, serviceVersion.Number)\n\t\t\tcase err == nil && testcase.wantError == \"\":\n\t\t\t\thave, err := testcase.cmd.ConstructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\t\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\t\ttestutil.AssertEqual(t, testcase.want, have)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestUpdateHTTPSInput(t *testing.T) {\n\tscenarios := []struct {\n\t\tname      string\n\t\tcmd       *https.UpdateCommand\n\t\tapi       mock.API\n\t\twant      *fastly.UpdateHTTPSInput\n\t\twantError string\n\t}{\n\t\t{\n\t\t\tname: \"all values set flag serviceID\",\n\t\t\tcmd:  updateCommandAll(),\n\t\t\tapi: mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tGetHTTPSFn:     getHTTPSOK,\n\t\t\t},\n\t\t\twant: &fastly.UpdateHTTPSInput{\n\t\t\t\tServiceID:         \"123\",\n\t\t\t\tServiceVersion:    4,\n\t\t\t\tName:              \"log\",\n\t\t\t\tNewName:           fastly.ToPointer(\"new1\"),\n\t\t\t\tResponseCondition: fastly.ToPointer(\"new2\"),\n\t\t\t\tFormat:            fastly.ToPointer(\"new3\"),\n\t\t\t\tURL:               fastly.ToPointer(\"new4\"),\n\t\t\t\tRequestMaxEntries: fastly.ToPointer(3),\n\t\t\t\tRequestMaxBytes:   fastly.ToPointer(3),\n\t\t\t\tContentType:       fastly.ToPointer(\"new5\"),\n\t\t\t\tHeaderName:        fastly.ToPointer(\"new6\"),\n\t\t\t\tHeaderValue:       fastly.ToPointer(\"new7\"),\n\t\t\t\tMethod:            fastly.ToPointer(\"new8\"),\n\t\t\t\tJSONFormat:        fastly.ToPointer(\"new9\"),\n\t\t\t\tPeriod:            fastly.ToPointer(5),\n\t\t\t\tPlacement:         fastly.ToPointer(\"new10\"),\n\t\t\t\tTLSCACert:         fastly.ToPointer(\"new11\"),\n\t\t\t\tTLSClientCert:     fastly.ToPointer(\"new12\"),\n\t\t\t\tTLSClientKey:      fastly.ToPointer(\"new13\"),\n\t\t\t\tTLSHostname:       fastly.ToPointer(\"new14\"),\n\t\t\t\tMessageType:       fastly.ToPointer(\"new15\"),\n\t\t\t\tFormatVersion:     fastly.ToPointer(3),\n\t\t\t\tProcessingRegion:  fastly.ToPointer(\"eu\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"no updates\",\n\t\t\tcmd:  updateCommandNoUpdates(),\n\t\t\tapi: mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tGetHTTPSFn:     getHTTPSOK,\n\t\t\t},\n\t\t\twant: &fastly.UpdateHTTPSInput{\n\t\t\t\tServiceID:      \"123\",\n\t\t\t\tServiceVersion: 4,\n\t\t\t\tName:           \"log\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:      \"error missing serviceID\",\n\t\t\tcmd:       updateCommandMissingServiceID(),\n\t\t\twant:      nil,\n\t\t\twantError: errors.ErrNoServiceID.Error(),\n\t\t},\n\t}\n\tfor testcaseIdx := range scenarios {\n\t\ttestcase := &scenarios[testcaseIdx]\n\t\tt.Run(testcase.name, func(t *testing.T) {\n\t\t\tif testcase.wantError == errors.ErrNoServiceID.Error() {\n\t\t\t\tt.Setenv(\"FASTLY_SERVICE_ID\", \"\")\n\t\t\t}\n\t\t\ttestcase.cmd.Globals.APIClient = testcase.api\n\n\t\t\tvar bs []byte\n\t\t\tout := bytes.NewBuffer(bs)\n\t\t\tverboseMode := true\n\n\t\t\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\t\t\tAutoCloneFlag:      testcase.cmd.AutoClone,\n\t\t\t\tAPIClient:          testcase.api,\n\t\t\t\tManifest:           testcase.cmd.Manifest,\n\t\t\t\tOut:                out,\n\t\t\t\tServiceVersionFlag: testcase.cmd.ServiceVersion,\n\t\t\t\tVerboseMode:        verboseMode,\n\t\t\t})\n\n\t\t\tswitch {\n\t\t\tcase err != nil && testcase.wantError == \"\":\n\t\t\t\tt.Fatalf(\"unexpected error getting service details: %v\", err)\n\t\t\t\treturn\n\t\t\tcase err != nil && testcase.wantError != \"\":\n\t\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\t\treturn\n\t\t\tcase err == nil && testcase.wantError != \"\":\n\t\t\t\tt.Fatalf(\"expected error, have nil (service details: %s, %d)\", serviceID, serviceVersion.Number)\n\t\t\tcase err == nil && testcase.wantError == \"\":\n\t\t\t\thave, err := testcase.cmd.ConstructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\t\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\t\ttestutil.AssertEqual(t, testcase.want, have)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc createCommandRequired() *https.CreateCommand {\n\tvar b bytes.Buffer\n\n\tg := global.Data{\n\t\tConfig: config.File{},\n\t\tEnv:    config.Environment{},\n\t\tOutput: &b,\n\t}\n\tg.APIClient, _ = mock.APIClient(mock.API{\n\t\tGetVersionFn:   testutil.GetVersion,\n\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t})(\"token\", \"endpoint\", false)\n\n\treturn &https.CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: &g,\n\t\t},\n\t\tManifest: manifest.Data{\n\t\t\tFlag: manifest.Flag{\n\t\t\t\tServiceID: \"123\",\n\t\t\t},\n\t\t},\n\t\tServiceVersion: argparser.OptionalServiceVersion{\n\t\t\tOptionalString: argparser.OptionalString{Value: \"1\"},\n\t\t},\n\t\tAutoClone: argparser.OptionalAutoClone{\n\t\t\tOptionalBool: argparser.OptionalBool{\n\t\t\t\tOptional: argparser.Optional{\n\t\t\t\t\tWasSet: true,\n\t\t\t\t},\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t},\n\t\tEndpointName: argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"log\"},\n\t\tURL:          argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"example.com\"},\n\t}\n}\n\nfunc createCommandAll() *https.CreateCommand {\n\tvar b bytes.Buffer\n\n\tg := global.Data{\n\t\tConfig: config.File{},\n\t\tEnv:    config.Environment{},\n\t\tOutput: &b,\n\t}\n\tg.APIClient, _ = mock.APIClient(mock.API{\n\t\tGetVersionFn:   testutil.GetVersion,\n\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t})(\"token\", \"endpoint\", false)\n\n\treturn &https.CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: &g,\n\t\t},\n\t\tManifest: manifest.Data{\n\t\t\tFlag: manifest.Flag{\n\t\t\t\tServiceID: \"123\",\n\t\t\t},\n\t\t},\n\t\tServiceVersion: argparser.OptionalServiceVersion{\n\t\t\tOptionalString: argparser.OptionalString{Value: \"1\"},\n\t\t},\n\t\tAutoClone: argparser.OptionalAutoClone{\n\t\t\tOptionalBool: argparser.OptionalBool{\n\t\t\t\tOptional: argparser.Optional{\n\t\t\t\t\tWasSet: true,\n\t\t\t\t},\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t},\n\t\tEndpointName:      argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"logs\"},\n\t\tURL:               argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"example.com\"},\n\t\tContentType:       argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"application/json\"},\n\t\tHeaderName:        argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"name\"},\n\t\tHeaderValue:       argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"value\"},\n\t\tMethod:            argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: http.MethodGet},\n\t\tJSONFormat:        argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"1\"},\n\t\tMessageType:       argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"classic\"},\n\t\tRequestMaxEntries: argparser.OptionalInt{Optional: argparser.Optional{WasSet: true}, Value: 2},\n\t\tRequestMaxBytes:   argparser.OptionalInt{Optional: argparser.Optional{WasSet: true}, Value: 2},\n\t\tFormat:            argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: `%h %l %u %t \"%r\" %>s %b`},\n\t\tFormatVersion:     argparser.OptionalInt{Optional: argparser.Optional{WasSet: true}, Value: 2},\n\t\tResponseCondition: argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"Prevent default logging\"},\n\t\tPeriod:            argparser.OptionalInt{Optional: argparser.Optional{WasSet: true}, Value: 5},\n\t\tPlacement:         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"none\"},\n\t\tTLSCACert:         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"-----BEGIN CERTIFICATE-----foo\"},\n\t\tTLSHostname:       argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"example.com\"},\n\t\tTLSClientCert:     argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"-----BEGIN CERTIFICATE-----bar\"},\n\t\tTLSClientKey:      argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"-----BEGIN PRIVATE KEY-----bar\"},\n\t\tProcessingRegion:  argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"eu\"},\n\t\tCompressionCodec:  argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"zstd\"},\n\t}\n}\n\nfunc createCommandMissingServiceID() *https.CreateCommand {\n\tres := createCommandAll()\n\tres.Manifest = manifest.Data{}\n\tres.ServiceVersion = argparser.OptionalServiceVersion{}\n\treturn res\n}\n\nfunc updateCommandNoUpdates() *https.UpdateCommand {\n\tvar b bytes.Buffer\n\n\tg := global.Data{\n\t\tConfig: config.File{},\n\t\tEnv:    config.Environment{},\n\t\tOutput: &b,\n\t}\n\n\treturn &https.UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: &g,\n\t\t},\n\t\tManifest: manifest.Data{\n\t\t\tFlag: manifest.Flag{\n\t\t\t\tServiceID: \"123\",\n\t\t\t},\n\t\t},\n\t\tEndpointName: \"log\",\n\t\tServiceVersion: argparser.OptionalServiceVersion{\n\t\t\tOptionalString: argparser.OptionalString{Value: \"1\"},\n\t\t},\n\t\tAutoClone: argparser.OptionalAutoClone{\n\t\t\tOptionalBool: argparser.OptionalBool{\n\t\t\t\tOptional: argparser.Optional{\n\t\t\t\t\tWasSet: true,\n\t\t\t\t},\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t},\n\t}\n}\n\nfunc updateCommandAll() *https.UpdateCommand {\n\tvar b bytes.Buffer\n\n\tg := global.Data{\n\t\tConfig: config.File{},\n\t\tEnv:    config.Environment{},\n\t\tOutput: &b,\n\t}\n\n\treturn &https.UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: &g,\n\t\t},\n\t\tManifest: manifest.Data{\n\t\t\tFlag: manifest.Flag{\n\t\t\t\tServiceID: \"123\",\n\t\t\t},\n\t\t},\n\t\tEndpointName: \"log\",\n\t\tServiceVersion: argparser.OptionalServiceVersion{\n\t\t\tOptionalString: argparser.OptionalString{Value: \"1\"},\n\t\t},\n\t\tAutoClone: argparser.OptionalAutoClone{\n\t\t\tOptionalBool: argparser.OptionalBool{\n\t\t\t\tOptional: argparser.Optional{\n\t\t\t\t\tWasSet: true,\n\t\t\t\t},\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t},\n\t\tNewName:           argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new1\"},\n\t\tResponseCondition: argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new2\"},\n\t\tFormat:            argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new3\"},\n\t\tURL:               argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new4\"},\n\t\tContentType:       argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new5\"},\n\t\tHeaderName:        argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new6\"},\n\t\tHeaderValue:       argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new7\"},\n\t\tMethod:            argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new8\"},\n\t\tJSONFormat:        argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new9\"},\n\t\tPeriod:            argparser.OptionalInt{Optional: argparser.Optional{WasSet: true}, Value: 5},\n\t\tPlacement:         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new10\"},\n\t\tRequestMaxEntries: argparser.OptionalInt{Optional: argparser.Optional{WasSet: true}, Value: 3},\n\t\tRequestMaxBytes:   argparser.OptionalInt{Optional: argparser.Optional{WasSet: true}, Value: 3},\n\t\tTLSCACert:         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new11\"},\n\t\tTLSClientCert:     argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new12\"},\n\t\tTLSClientKey:      argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new13\"},\n\t\tTLSHostname:       argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new14\"},\n\t\tMessageType:       argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new15\"},\n\t\tFormatVersion:     argparser.OptionalInt{Optional: argparser.Optional{WasSet: true}, Value: 3},\n\t\tProcessingRegion:  argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"eu\"},\n\t\tCompressionCodec:  argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"zstd\"},\n\t}\n}\n\nfunc updateCommandMissingServiceID() *https.UpdateCommand {\n\tres := updateCommandAll()\n\tres.Manifest = manifest.Data{}\n\tres.ServiceVersion = argparser.OptionalServiceVersion{}\n\treturn res\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/https/list.go",
    "content": "package https\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// ListCommand calls the Fastly API to list HTTPS logging endpoints.\ntype ListCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tInput          fastly.ListHTTPSInput\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n}\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"list\", \"List HTTPS endpoints on a Fastly service version\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *ListCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fsterr.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tc.Input.ServiceID = serviceID\n\tc.Input.ServiceVersion = fastly.ToValue(serviceVersion.Number)\n\n\to, err := c.Globals.APIClient.ListHTTPS(context.TODO(), &c.Input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\tif !c.Globals.Verbose() {\n\t\ttw := text.NewTable(out)\n\t\ttw.AddHeader(\"SERVICE\", \"VERSION\", \"NAME\")\n\t\tfor _, https := range o {\n\t\t\ttw.AddLine(\n\t\t\t\tfastly.ToValue(https.ServiceID),\n\t\t\t\tfastly.ToValue(https.ServiceVersion),\n\t\t\t\tfastly.ToValue(https.Name),\n\t\t\t)\n\t\t}\n\t\ttw.Print()\n\t\treturn nil\n\t}\n\n\tfmt.Fprintf(out, \"Version: %d\\n\", c.Input.ServiceVersion)\n\tfor i, https := range o {\n\t\tfmt.Fprintf(out, \"\\tHTTPS %d/%d\\n\", i+1, len(o))\n\t\tfmt.Fprintf(out, \"\\t\\tService ID: %s\\n\", fastly.ToValue(https.ServiceID))\n\t\tfmt.Fprintf(out, \"\\t\\tVersion: %d\\n\", fastly.ToValue(https.ServiceVersion))\n\t\tfmt.Fprintf(out, \"\\t\\tName: %s\\n\", fastly.ToValue(https.Name))\n\t\tfmt.Fprintf(out, \"\\t\\tURL: %s\\n\", fastly.ToValue(https.URL))\n\t\tfmt.Fprintf(out, \"\\t\\tCompression codec: %s\\n\", fastly.ToValue(https.CompressionCodec))\n\t\tfmt.Fprintf(out, \"\\t\\tContent type: %s\\n\", fastly.ToValue(https.ContentType))\n\t\tfmt.Fprintf(out, \"\\t\\tGZip level: %d\\n\", fastly.ToValue(https.GzipLevel))\n\t\tfmt.Fprintf(out, \"\\t\\tHeader name: %s\\n\", fastly.ToValue(https.HeaderName))\n\t\tfmt.Fprintf(out, \"\\t\\tHeader value: %s\\n\", fastly.ToValue(https.HeaderValue))\n\t\tfmt.Fprintf(out, \"\\t\\tMethod: %s\\n\", fastly.ToValue(https.Method))\n\t\tfmt.Fprintf(out, \"\\t\\tJSON format: %s\\n\", fastly.ToValue(https.JSONFormat))\n\t\tfmt.Fprintf(out, \"\\t\\tTLS CA certificate: %s\\n\", fastly.ToValue(https.TLSCACert))\n\t\tfmt.Fprintf(out, \"\\t\\tTLS client certificate: %s\\n\", fastly.ToValue(https.TLSClientCert))\n\t\tfmt.Fprintf(out, \"\\t\\tTLS client key: %s\\n\", fastly.ToValue(https.TLSClientKey))\n\t\tfmt.Fprintf(out, \"\\t\\tTLS hostname: %s\\n\", fastly.ToValue(https.TLSHostname))\n\t\tfmt.Fprintf(out, \"\\t\\tRequest max entries: %d\\n\", fastly.ToValue(https.RequestMaxEntries))\n\t\tfmt.Fprintf(out, \"\\t\\tRequest max bytes: %d\\n\", fastly.ToValue(https.RequestMaxBytes))\n\t\tfmt.Fprintf(out, \"\\t\\tMessage type: %s\\n\", fastly.ToValue(https.MessageType))\n\t\tfmt.Fprintf(out, \"\\t\\tFormat: %s\\n\", fastly.ToValue(https.Format))\n\t\tfmt.Fprintf(out, \"\\t\\tFormat version: %d\\n\", fastly.ToValue(https.FormatVersion))\n\t\tfmt.Fprintf(out, \"\\t\\tResponse condition: %s\\n\", fastly.ToValue(https.ResponseCondition))\n\t\tfmt.Fprintf(out, \"\\t\\tPeriod: %d\\n\", fastly.ToValue(https.Period))\n\t\tfmt.Fprintf(out, \"\\t\\tPlacement: %s\\n\", fastly.ToValue(https.Placement))\n\t\tfmt.Fprintf(out, \"\\t\\tProcessing region: %s\\n\", fastly.ToValue(https.ProcessingRegion))\n\t}\n\tfmt.Fprintln(out)\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/https/root.go",
    "content": "package https\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"https\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manipulate Fastly service version HTTPS logging endpoints\")\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/https/update.go",
    "content": "package https\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/service/logging/logflags\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/manifest\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// UpdateCommand calls the Fastly API to update an HTTPS logging endpoint.\ntype UpdateCommand struct {\n\targparser.Base\n\tManifest manifest.Data\n\n\t// Required.\n\tEndpointName   string // Can't shadow argparser.Base method Name().\n\tServiceName    argparser.OptionalServiceNameID\n\tServiceVersion argparser.OptionalServiceVersion\n\n\t// Optional.\n\tAutoClone         argparser.OptionalAutoClone\n\tCompressionCodec  argparser.OptionalString\n\tContentType       argparser.OptionalString\n\tFormat            argparser.OptionalString\n\tFormatVersion     argparser.OptionalInt\n\tGzipLevel         argparser.OptionalInt\n\tHeaderName        argparser.OptionalString\n\tHeaderValue       argparser.OptionalString\n\tJSONFormat        argparser.OptionalString\n\tMessageType       argparser.OptionalString\n\tMethod            argparser.OptionalString\n\tNewName           argparser.OptionalString\n\tPeriod            argparser.OptionalInt\n\tPlacement         argparser.OptionalString\n\tProcessingRegion  argparser.OptionalString\n\tRequestMaxBytes   argparser.OptionalInt\n\tRequestMaxEntries argparser.OptionalInt\n\tResponseCondition argparser.OptionalString\n\tTLSCACert         argparser.OptionalString\n\tTLSClientCert     argparser.OptionalString\n\tTLSClientKey      argparser.OptionalString\n\tTLSHostname       argparser.OptionalString\n\tURL               argparser.OptionalString\n}\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"update\", \"Update an HTTPS logging endpoint on a Fastly service version\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"The name of the HTTPS logging object\").Short('n').Required().StringVar(&c.EndpointName)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.ServiceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.AutoClone.Set,\n\t\tDst:    &c.AutoClone.Value,\n\t})\n\tc.CmdClause.Flag(\"content-type\", \"Content type of the header sent with the request\").Action(c.ContentType.Set).StringVar(&c.ContentType.Value)\n\tlogflags.Format(c.CmdClause, &c.Format)\n\tlogflags.FormatVersion(c.CmdClause, &c.FormatVersion)\n\tc.CmdClause.Flag(\"header-name\", \"Name of the custom header sent with the request\").Action(c.HeaderName.Set).StringVar(&c.HeaderName.Value)\n\tc.CmdClause.Flag(\"header-value\", \"Value of the custom header sent with the request\").Action(c.HeaderValue.Set).StringVar(&c.HeaderValue.Value)\n\tc.CmdClause.Flag(\"json-format\", \"Enforces valid JSON formatting for log entries. Can be disabled 0, array of json (wraps JSON log batches in an array) 1, or newline delimited json (places each JSON log entry onto a new line in a batch) 2\").Action(c.JSONFormat.Set).StringVar(&c.JSONFormat.Value)\n\tlogflags.MessageType(c.CmdClause, &c.MessageType)\n\tc.CmdClause.Flag(\"method\", \"HTTP method used for request. Can be POST or PUT. Defaults to POST if not specified\").Action(c.Method.Set).StringVar(&c.Method.Value)\n\tc.CmdClause.Flag(\"new-name\", \"New name of the HTTPS logging object\").Action(c.NewName.Set).StringVar(&c.NewName.Value)\n\tlogflags.Period(c.CmdClause, &c.Period)\n\tlogflags.Placement(c.CmdClause, &c.Placement)\n\tlogflags.ProcessingRegion(c.CmdClause, &c.ProcessingRegion, \"HTTPS\")\n\tc.CmdClause.Flag(\"request-max-bytes\", \"Maximum size of log batch, if non-zero. Defaults to 100MB\").Action(c.RequestMaxBytes.Set).IntVar(&c.RequestMaxBytes.Value)\n\tc.CmdClause.Flag(\"request-max-entries\", \"Maximum number of logs to append to a batch, if non-zero. Defaults to 10k\").Action(c.RequestMaxEntries.Set).IntVar(&c.RequestMaxEntries.Value)\n\tlogflags.ResponseCondition(c.CmdClause, &c.ResponseCondition)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.ServiceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.ServiceName.Value,\n\t})\n\tlogflags.TLSCACert(c.CmdClause, &c.TLSCACert)\n\tlogflags.TLSClientCert(c.CmdClause, &c.TLSClientCert)\n\tlogflags.TLSClientKey(c.CmdClause, &c.TLSClientKey)\n\tlogflags.TLSHostname(c.CmdClause, &c.TLSHostname)\n\tc.CmdClause.Flag(\"url\", \"URL that log data will be sent to. Must use the https protocol\").Action(c.URL.Set).StringVar(&c.URL.Value)\n\treturn &c\n}\n\n// ConstructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *UpdateCommand) ConstructInput(serviceID string, serviceVersion int) (*fastly.UpdateHTTPSInput, error) {\n\tinput := fastly.UpdateHTTPSInput{\n\t\tServiceID:      serviceID,\n\t\tServiceVersion: serviceVersion,\n\t\tName:           c.EndpointName,\n\t}\n\n\tif c.NewName.WasSet {\n\t\tinput.NewName = &c.NewName.Value\n\t}\n\n\tif c.URL.WasSet {\n\t\tinput.URL = &c.URL.Value\n\t}\n\n\tif c.ContentType.WasSet {\n\t\tinput.ContentType = &c.ContentType.Value\n\t}\n\n\tif c.JSONFormat.WasSet {\n\t\tinput.JSONFormat = &c.JSONFormat.Value\n\t}\n\n\tif c.HeaderName.WasSet {\n\t\tinput.HeaderName = &c.HeaderName.Value\n\t}\n\n\tif c.HeaderValue.WasSet {\n\t\tinput.HeaderValue = &c.HeaderValue.Value\n\t}\n\n\tif c.Method.WasSet {\n\t\tinput.Method = &c.Method.Value\n\t}\n\n\tif c.RequestMaxEntries.WasSet {\n\t\tinput.RequestMaxEntries = &c.RequestMaxEntries.Value\n\t}\n\n\tif c.RequestMaxBytes.WasSet {\n\t\tinput.RequestMaxBytes = &c.RequestMaxBytes.Value\n\t}\n\n\tif c.TLSCACert.WasSet {\n\t\tinput.TLSCACert = &c.TLSCACert.Value\n\t}\n\n\tif c.TLSClientCert.WasSet {\n\t\tinput.TLSClientCert = &c.TLSClientCert.Value\n\t}\n\n\tif c.TLSClientKey.WasSet {\n\t\tinput.TLSClientKey = &c.TLSClientKey.Value\n\t}\n\n\tif c.TLSHostname.WasSet {\n\t\tinput.TLSHostname = &c.TLSHostname.Value\n\t}\n\n\tif c.Format.WasSet {\n\t\tinput.Format = fastly.ToPointer(argparser.Content(c.Format.Value))\n\t}\n\n\tif c.FormatVersion.WasSet {\n\t\tinput.FormatVersion = &c.FormatVersion.Value\n\t}\n\n\tif c.ResponseCondition.WasSet {\n\t\tinput.ResponseCondition = &c.ResponseCondition.Value\n\t}\n\n\tif c.Period.WasSet {\n\t\tinput.Period = &c.Period.Value\n\t}\n\n\tif c.Placement.WasSet {\n\t\tinput.Placement = &c.Placement.Value\n\t}\n\n\tif c.MessageType.WasSet {\n\t\tinput.MessageType = &c.MessageType.Value\n\t}\n\n\tif c.ProcessingRegion.WasSet {\n\t\tinput.ProcessingRegion = &c.ProcessingRegion.Value\n\t}\n\n\treturn &input, nil\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *UpdateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(false),\n\t\tLocked:             optional.Of(false),\n\t\tAutoCloneFlag:      c.AutoClone,\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.ServiceName,\n\t\tServiceVersionFlag: c.ServiceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": errors.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tinput, err := c.ConstructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\thttps, err := c.Globals.APIClient.UpdateHTTPS(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\ttext.Success(\n\t\tout,\n\t\t\"Updated HTTPS logging endpoint %s (service %s version %d)\",\n\t\tfastly.ToValue(https.Name),\n\t\tfastly.ToValue(https.ServiceID),\n\t\tfastly.ToValue(https.ServiceVersion),\n\t)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/kafka/create.go",
    "content": "package kafka\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/service/logging/logflags\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/manifest\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// CreateCommand calls the Fastly API to create a Kafka logging endpoint.\ntype CreateCommand struct {\n\targparser.Base\n\tManifest manifest.Data\n\n\t// Required.\n\tServiceName    argparser.OptionalServiceNameID\n\tServiceVersion argparser.OptionalServiceVersion\n\n\t// Optional.\n\tAuthMethod        argparser.OptionalString\n\tAutoClone         argparser.OptionalAutoClone\n\tBrokers           argparser.OptionalString\n\tCompressionCodec  argparser.OptionalString\n\tEndpointName      argparser.OptionalString // Can't shadow argparser.Base method Name().\n\tFormat            argparser.OptionalString\n\tFormatVersion     argparser.OptionalInt\n\tParseLogKeyvals   argparser.OptionalBool\n\tPassword          argparser.OptionalString\n\tPlacement         argparser.OptionalString\n\tProcessingRegion  argparser.OptionalString\n\tRequestMaxBytes   argparser.OptionalInt\n\tRequiredACKs      argparser.OptionalString\n\tResponseCondition argparser.OptionalString\n\tTLSCACert         argparser.OptionalString\n\tTLSClientCert     argparser.OptionalString\n\tTLSClientKey      argparser.OptionalString\n\tTLSHostname       argparser.OptionalString\n\tTopic             argparser.OptionalString\n\tUser              argparser.OptionalString\n\tUseSASL           argparser.OptionalBool\n\tUseTLS            argparser.OptionalBool\n}\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"create\", \"Create a Kafka logging endpoint on a Fastly service version\").Alias(\"add\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"The name of the Kafka logging object. Used as a primary key for API access\").Short('n').Action(c.EndpointName.Set).StringVar(&c.EndpointName.Value)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.ServiceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.AutoClone.Set,\n\t\tDst:    &c.AutoClone.Value,\n\t})\n\tc.CmdClause.Flag(\"auth-method\", \"SASL authentication method. Valid values are: plain, scram-sha-256, scram-sha-512\").Action(c.AuthMethod.Set).HintOptions(\"plain\", \"scram-sha-256\", \"scram-sha-512\").EnumVar(&c.AuthMethod.Value, \"plain\", \"scram-sha-256\", \"scram-sha-512\")\n\tc.CmdClause.Flag(\"brokers\", \"A comma-separated list of IP addresses or hostnames of Kafka brokers\").Action(c.Brokers.Set).StringVar(&c.Brokers.Value)\n\tc.CmdClause.Flag(\"compression-codec\", \"The codec used for compression of your logs. One of: gzip, snappy, lz4\").Action(c.CompressionCodec.Set).StringVar(&c.CompressionCodec.Value)\n\tlogflags.Format(c.CmdClause, &c.Format)\n\tlogflags.FormatVersion(c.CmdClause, &c.FormatVersion)\n\tc.CmdClause.Flag(\"max-batch-size\", \"The maximum size of the log batch in bytes\").Action(c.RequestMaxBytes.Set).IntVar(&c.RequestMaxBytes.Value)\n\tc.CmdClause.Flag(\"parse-log-keyvals\", \"Parse key-value pairs within the log format\").Action(c.ParseLogKeyvals.Set).BoolVar(&c.ParseLogKeyvals.Value)\n\tc.CmdClause.Flag(\"password\", \"SASL authentication password. Required if --auth-method is specified\").Action(c.Password.Set).StringVar(&c.Password.Value)\n\tlogflags.Placement(c.CmdClause, &c.Placement)\n\tlogflags.ProcessingRegion(c.CmdClause, &c.ProcessingRegion, \"Kafka\")\n\tc.CmdClause.Flag(\"required-acks\", \"The Number of acknowledgements a leader must receive before a write is considered successful. One of: 1 (default) One server needs to respond. 0\tNo servers need to respond. -1\tWait for all in-sync replicas to respond\").Action(c.RequiredACKs.Set).StringVar(&c.RequiredACKs.Value)\n\tlogflags.ResponseCondition(c.CmdClause, &c.ResponseCondition)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.ServiceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.ServiceName.Value,\n\t})\n\tlogflags.TLSCACert(c.CmdClause, &c.TLSCACert)\n\tlogflags.TLSClientCert(c.CmdClause, &c.TLSClientCert)\n\tlogflags.TLSClientKey(c.CmdClause, &c.TLSClientKey)\n\tlogflags.TLSHostname(c.CmdClause, &c.TLSHostname)\n\tc.CmdClause.Flag(\"topic\", \"The Kafka topic to send logs to\").Action(c.Topic.Set).StringVar(&c.Topic.Value)\n\tc.CmdClause.Flag(\"use-sasl\", \"Enable SASL authentication. Requires --auth-method, --username, and --password to be specified\").Action(c.UseSASL.Set).BoolVar(&c.UseSASL.Value)\n\tc.CmdClause.Flag(\"use-tls\", \"Whether to use TLS for secure logging. Can be either true or false\").Action(c.UseTLS.Set).BoolVar(&c.UseTLS.Value)\n\tc.CmdClause.Flag(\"username\", \"SASL authentication username. Required if --auth-method is specified\").Action(c.User.Set).StringVar(&c.User.Value)\n\treturn &c\n}\n\n// ConstructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *CreateCommand) ConstructInput(serviceID string, serviceVersion int) (*fastly.CreateKafkaInput, error) {\n\tvar input fastly.CreateKafkaInput\n\n\tif c.UseSASL.WasSet && c.UseSASL.Value && (c.AuthMethod.Value == \"\" || c.User.Value == \"\" || c.Password.Value == \"\") {\n\t\treturn nil, fmt.Errorf(\"the --auth-method, --username, and --password flags must be present when using the --use-sasl flag\")\n\t}\n\n\tif !c.UseSASL.Value && (c.AuthMethod.Value != \"\" || c.User.Value != \"\" || c.Password.Value != \"\") {\n\t\treturn nil, fmt.Errorf(\"the --auth-method, --username, and --password options are only valid when the --use-sasl flag is specified\")\n\t}\n\n\tinput.ServiceID = serviceID\n\tinput.ServiceVersion = serviceVersion\n\tif c.EndpointName.WasSet {\n\t\tinput.Name = &c.EndpointName.Value\n\t}\n\tif c.Topic.WasSet {\n\t\tinput.Topic = &c.Topic.Value\n\t}\n\tif c.Brokers.WasSet {\n\t\tinput.Brokers = &c.Brokers.Value\n\t}\n\n\tif c.CompressionCodec.WasSet {\n\t\tinput.CompressionCodec = &c.CompressionCodec.Value\n\t}\n\n\tif c.RequiredACKs.WasSet {\n\t\tinput.RequiredACKs = &c.RequiredACKs.Value\n\t}\n\n\tif c.UseTLS.WasSet {\n\t\tinput.UseTLS = fastly.ToPointer(fastly.Compatibool(c.UseTLS.Value))\n\t}\n\n\tif c.TLSCACert.WasSet {\n\t\tinput.TLSCACert = &c.TLSCACert.Value\n\t}\n\n\tif c.TLSClientCert.WasSet {\n\t\tinput.TLSClientCert = &c.TLSClientCert.Value\n\t}\n\n\tif c.TLSClientKey.WasSet {\n\t\tinput.TLSClientKey = &c.TLSClientKey.Value\n\t}\n\n\tif c.TLSHostname.WasSet {\n\t\tinput.TLSHostname = &c.TLSHostname.Value\n\t}\n\n\tif c.Format.WasSet {\n\t\tinput.Format = fastly.ToPointer(argparser.Content(c.Format.Value))\n\t}\n\n\tif c.FormatVersion.WasSet {\n\t\tinput.FormatVersion = &c.FormatVersion.Value\n\t}\n\n\tif c.ResponseCondition.WasSet {\n\t\tinput.ResponseCondition = &c.ResponseCondition.Value\n\t}\n\n\tif c.Placement.WasSet {\n\t\tinput.Placement = &c.Placement.Value\n\t}\n\n\tif c.ParseLogKeyvals.WasSet {\n\t\tinput.ParseLogKeyvals = fastly.ToPointer(fastly.Compatibool(c.ParseLogKeyvals.Value))\n\t}\n\n\tif c.ProcessingRegion.WasSet {\n\t\tinput.ProcessingRegion = &c.ProcessingRegion.Value\n\t}\n\n\tif c.RequestMaxBytes.WasSet {\n\t\tinput.RequestMaxBytes = &c.RequestMaxBytes.Value\n\t}\n\n\tif c.AuthMethod.WasSet {\n\t\tinput.AuthMethod = &c.AuthMethod.Value\n\t}\n\n\tif c.User.WasSet {\n\t\tinput.User = &c.User.Value\n\t}\n\n\tif c.Password.WasSet {\n\t\tinput.Password = &c.Password.Value\n\t}\n\n\treturn &input, nil\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *CreateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(false),\n\t\tLocked:             optional.Of(false),\n\t\tAutoCloneFlag:      c.AutoClone,\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.ServiceName,\n\t\tServiceVersionFlag: c.ServiceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": errors.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tinput, err := c.ConstructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\td, err := c.Globals.APIClient.CreateKafka(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\ttext.Success(\n\t\tout,\n\t\t\"Created Kafka logging endpoint %s (service %s version %d)\",\n\t\tfastly.ToValue(d.Name),\n\t\tfastly.ToValue(d.ServiceID),\n\t\tfastly.ToValue(d.ServiceVersion),\n\t)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/kafka/delete.go",
    "content": "package kafka\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DeleteCommand calls the Fastly API to delete a Kafka logging endpoint.\ntype DeleteCommand struct {\n\targparser.Base\n\tInput          fastly.DeleteKafkaInput\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n\tautoClone      argparser.OptionalAutoClone\n}\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"delete\", \"Delete a Kafka logging endpoint on a Fastly service version\").Alias(\"remove\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"The name of the Kafka logging object\").Short('n').Required().StringVar(&c.Input.Name)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.autoClone.Set,\n\t\tDst:    &c.autoClone.Value,\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DeleteCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(false),\n\t\tLocked:             optional.Of(false),\n\t\tAutoCloneFlag:      c.autoClone,\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": errors.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tc.Input.ServiceID = serviceID\n\tc.Input.ServiceVersion = fastly.ToValue(serviceVersion.Number)\n\n\tif err := c.Globals.APIClient.DeleteKafka(context.TODO(), &c.Input); err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Deleted Kafka logging endpoint %s (service %s version %d)\", c.Input.Name, c.Input.ServiceID, c.Input.ServiceVersion)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/kafka/describe.go",
    "content": "package kafka\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DescribeCommand calls the Fastly API to describe a Kafka logging endpoint.\ntype DescribeCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tInput          fastly.GetKafkaInput\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n}\n\n// NewDescribeCommand returns a usable command registered under the parent.\nfunc NewDescribeCommand(parent argparser.Registerer, g *global.Data) *DescribeCommand {\n\tc := DescribeCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"describe\", \"Show detailed information about a Kafka logging endpoint on a Fastly service version\").Alias(\"get\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"The name of the Kafka logging object\").Short('n').Required().StringVar(&c.Input.Name)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DescribeCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fsterr.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tc.Input.ServiceID = serviceID\n\tc.Input.ServiceVersion = fastly.ToValue(serviceVersion.Number)\n\n\to, err := c.Globals.APIClient.GetKafka(context.TODO(), &c.Input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\tlines := text.Lines{\n\t\t\"Brokers\":                      fastly.ToValue(o.Brokers),\n\t\t\"Compression codec\":            fastly.ToValue(o.CompressionCodec),\n\t\t\"Format version\":               fastly.ToValue(o.FormatVersion),\n\t\t\"Format\":                       fastly.ToValue(o.Format),\n\t\t\"Max batch size\":               fastly.ToValue(o.RequestMaxBytes),\n\t\t\"Name\":                         fastly.ToValue(o.Name),\n\t\t\"Parse log key-values\":         fastly.ToValue(o.ParseLogKeyvals),\n\t\t\"Placement\":                    fastly.ToValue(o.Placement),\n\t\t\"Processing region\":            fastly.ToValue(o.ProcessingRegion),\n\t\t\"Required acks\":                fastly.ToValue(o.RequiredACKs),\n\t\t\"Response condition\":           fastly.ToValue(o.ResponseCondition),\n\t\t\"SASL authentication method\":   fastly.ToValue(o.AuthMethod),\n\t\t\"SASL authentication password\": fastly.ToValue(o.Password),\n\t\t\"SASL authentication username\": fastly.ToValue(o.User),\n\t\t\"TLS CA certificate\":           fastly.ToValue(o.TLSCACert),\n\t\t\"TLS client certificate\":       fastly.ToValue(o.TLSClientCert),\n\t\t\"TLS client key\":               fastly.ToValue(o.TLSClientKey),\n\t\t\"TLS hostname\":                 fastly.ToValue(o.TLSHostname),\n\t\t\"Topic\":                        fastly.ToValue(o.Topic),\n\t\t\"Use TLS\":                      fastly.ToValue(o.UseTLS),\n\t\t\"Version\":                      fastly.ToValue(o.ServiceVersion),\n\t}\n\tif !c.Globals.Verbose() {\n\t\tlines[\"Service ID\"] = fastly.ToValue(o.ServiceID)\n\t}\n\ttext.PrintLines(out, lines)\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/kafka/doc.go",
    "content": "// Package kafka contains commands to inspect and manipulate Fastly service Kafka\n// logging endpoints.\npackage kafka\n"
  },
  {
    "path": "pkg/commands/service/logging/kafka/kafka_integration_test.go",
    "content": "package kafka_test\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/mock\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n\n\troot \"github.com/fastly/cli/pkg/commands/service\"\n\tparent \"github.com/fastly/cli/pkg/commands/service/logging\"\n\tsub \"github.com/fastly/cli/pkg/commands/service/logging/kafka\"\n)\n\nfunc TestKafkaCreate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name log --topic logs --brokers 127.0.0.1127.0.0.2 --parse-log-keyvals --max-batch-size 1024 --use-sasl --auth-method plain --username user --password password --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tCreateKafkaFn:  createKafkaOK,\n\t\t\t},\n\t\t\tWantOutput: \"Created Kafka logging endpoint log (service 123 version 4)\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name log --topic logs --brokers 127.0.0.1127.0.0.2 --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tCreateKafkaFn:  createKafkaError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"create\"}, scenarios)\n}\n\nfunc TestKafkaList(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tListKafkasFn: listKafkasOK,\n\t\t\t},\n\t\t\tWantOutput: listKafkasShortOutput,\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --verbose\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tListKafkasFn: listKafkasOK,\n\t\t\t},\n\t\t\tWantOutput: listKafkasVerboseOutput,\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 -v\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tListKafkasFn: listKafkasOK,\n\t\t\t},\n\t\t\tWantOutput: listKafkasVerboseOutput,\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tListKafkasFn: listKafkasError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"list\"}, scenarios)\n}\n\nfunc TestKafkaDescribe(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"--service-id 123 --version 1\",\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tGetKafkaFn:   getKafkaError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tGetKafkaFn:   getKafkaOK,\n\t\t\t},\n\t\t\tWantOutput: describeKafkaOutput,\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"describe\"}, scenarios)\n}\n\nfunc TestKafkaUpdate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"--service-id 123 --version 1 --new-name log\",\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs --new-name log --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tUpdateKafkaFn:  updateKafkaError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs --new-name log --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tUpdateKafkaFn:  updateKafkaOK,\n\t\t\t},\n\t\t\tWantOutput: \"Updated Kafka logging endpoint log (service 123 version 4)\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs --new-name log --parse-log-keyvals --max-batch-size 1024 --use-sasl --auth-method plain --username user --password password --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tUpdateKafkaFn:  updateKafkaSASL,\n\t\t\t},\n\t\t\tWantOutput: \"Updated Kafka logging endpoint log (service 123 version 4)\",\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"update\"}, scenarios)\n}\n\nfunc TestKafkaDelete(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"--service-id 123 --version 1\",\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tDeleteKafkaFn:  deleteKafkaError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tDeleteKafkaFn:  deleteKafkaOK,\n\t\t\t},\n\t\t\tWantOutput: \"Deleted Kafka logging endpoint logs (service 123 version 4)\",\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"delete\"}, scenarios)\n}\n\nvar errTest = errors.New(\"fixture error\")\n\nfunc createKafkaOK(_ context.Context, i *fastly.CreateKafkaInput) (*fastly.Kafka, error) {\n\treturn &fastly.Kafka{\n\t\tServiceID:         fastly.ToPointer(i.ServiceID),\n\t\tServiceVersion:    fastly.ToPointer(i.ServiceVersion),\n\t\tName:              fastly.ToPointer(\"log\"),\n\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\tTopic:             fastly.ToPointer(\"logs\"),\n\t\tBrokers:           fastly.ToPointer(\"127.0.0.1,127.0.0.2\"),\n\t\tRequiredACKs:      fastly.ToPointer(\"-1\"),\n\t\tCompressionCodec:  fastly.ToPointer(\"zippy\"),\n\t\tUseTLS:            fastly.ToPointer(true),\n\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\tTLSCACert:         fastly.ToPointer(\"-----BEGIN CERTIFICATE-----foo\"),\n\t\tTLSHostname:       fastly.ToPointer(\"127.0.0.1,127.0.0.2\"),\n\t\tTLSClientCert:     fastly.ToPointer(\"-----BEGIN CERTIFICATE-----bar\"),\n\t\tTLSClientKey:      fastly.ToPointer(\"-----BEGIN PRIVATE KEY-----bar\"),\n\t\tFormatVersion:     fastly.ToPointer(2),\n\t\tParseLogKeyvals:   fastly.ToPointer(true),\n\t\tRequestMaxBytes:   fastly.ToPointer(1024),\n\t\tAuthMethod:        fastly.ToPointer(\"plain\"),\n\t\tUser:              fastly.ToPointer(\"user\"),\n\t\tPassword:          fastly.ToPointer(\"password\"),\n\t}, nil\n}\n\nfunc createKafkaError(_ context.Context, _ *fastly.CreateKafkaInput) (*fastly.Kafka, error) {\n\treturn nil, errTest\n}\n\nfunc listKafkasOK(_ context.Context, i *fastly.ListKafkasInput) ([]*fastly.Kafka, error) {\n\treturn []*fastly.Kafka{\n\t\t{\n\t\t\tServiceID:         fastly.ToPointer(i.ServiceID),\n\t\t\tServiceVersion:    fastly.ToPointer(i.ServiceVersion),\n\t\t\tName:              fastly.ToPointer(\"logs\"),\n\t\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\t\tTopic:             fastly.ToPointer(\"logs\"),\n\t\t\tBrokers:           fastly.ToPointer(\"127.0.0.1,127.0.0.2\"),\n\t\t\tRequiredACKs:      fastly.ToPointer(\"-1\"),\n\t\t\tCompressionCodec:  fastly.ToPointer(\"zippy\"),\n\t\t\tUseTLS:            fastly.ToPointer(true),\n\t\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\t\tTLSCACert:         fastly.ToPointer(\"-----BEGIN CERTIFICATE-----foo\"),\n\t\t\tTLSHostname:       fastly.ToPointer(\"127.0.0.1,127.0.0.2\"),\n\t\t\tTLSClientCert:     fastly.ToPointer(\"-----BEGIN CERTIFICATE-----bar\"),\n\t\t\tTLSClientKey:      fastly.ToPointer(\"-----BEGIN PRIVATE KEY-----bar\"),\n\t\t\tFormatVersion:     fastly.ToPointer(2),\n\t\t\tParseLogKeyvals:   fastly.ToPointer(false),\n\t\t\tRequestMaxBytes:   fastly.ToPointer(0),\n\t\t\tAuthMethod:        fastly.ToPointer(\"plain\"),\n\t\t\tUser:              fastly.ToPointer(\"user\"),\n\t\t\tPassword:          fastly.ToPointer(\"password\"),\n\t\t\tProcessingRegion:  fastly.ToPointer(\"us\"),\n\t\t},\n\t\t{\n\t\t\tServiceID:         fastly.ToPointer(i.ServiceID),\n\t\t\tServiceVersion:    fastly.ToPointer(i.ServiceVersion),\n\t\t\tName:              fastly.ToPointer(\"analytics\"),\n\t\t\tTopic:             fastly.ToPointer(\"analytics\"),\n\t\t\tBrokers:           fastly.ToPointer(\"127.0.0.1,127.0.0.2\"),\n\t\t\tRequiredACKs:      fastly.ToPointer(\"-1\"),\n\t\t\tCompressionCodec:  fastly.ToPointer(\"zippy\"),\n\t\t\tUseTLS:            fastly.ToPointer(true),\n\t\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\t\tTLSCACert:         fastly.ToPointer(\"-----BEGIN CERTIFICATE-----foo\"),\n\t\t\tTLSHostname:       fastly.ToPointer(\"127.0.0.1,127.0.0.2\"),\n\t\t\tTLSClientCert:     fastly.ToPointer(\"-----BEGIN CERTIFICATE-----bar\"),\n\t\t\tTLSClientKey:      fastly.ToPointer(\"-----BEGIN PRIVATE KEY-----bar\"),\n\t\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\t\tFormatVersion:     fastly.ToPointer(2),\n\t\t\tParseLogKeyvals:   fastly.ToPointer(false),\n\t\t\tRequestMaxBytes:   fastly.ToPointer(0),\n\t\t\tAuthMethod:        fastly.ToPointer(\"plain\"),\n\t\t\tUser:              fastly.ToPointer(\"user\"),\n\t\t\tPassword:          fastly.ToPointer(\"password\"),\n\t\t\tProcessingRegion:  fastly.ToPointer(\"us\"),\n\t\t},\n\t}, nil\n}\n\nfunc listKafkasError(_ context.Context, _ *fastly.ListKafkasInput) ([]*fastly.Kafka, error) {\n\treturn nil, errTest\n}\n\nvar listKafkasShortOutput = strings.TrimSpace(`\nSERVICE  VERSION  NAME\n123      1        logs\n123      1        analytics\n`) + \"\\n\"\n\nvar listKafkasVerboseOutput = strings.TrimSpace(`\nFastly API endpoint: https://api.fastly.com\nFastly API token provided via config file (auth: user)\n\nService ID (via --service-id): 123\n\nVersion: 1\n\tKafka 1/2\n\t\tService ID: 123\n\t\tVersion: 1\n\t\tName: logs\n\t\tTopic: logs\n\t\tBrokers: 127.0.0.1,127.0.0.2\n\t\tRequired acks: -1\n\t\tCompression codec: zippy\n\t\tUse TLS: true\n\t\tTLS CA certificate: -----BEGIN CERTIFICATE-----foo\n\t\tTLS client certificate: -----BEGIN CERTIFICATE-----bar\n\t\tTLS client key: -----BEGIN PRIVATE KEY-----bar\n\t\tTLS hostname: 127.0.0.1,127.0.0.2\n\t\tFormat: %h %l %u %t \"%r\" %>s %b\n\t\tFormat version: 2\n\t\tResponse condition: Prevent default logging\n\t\tPlacement: none\n\t\tParse log key-values: false\n\t\tMax batch size: 0\n\t\tSASL authentication method: plain\n\t\tSASL authentication username: user\n\t\tSASL authentication password: password\n\t\tProcessing region: us\n\tKafka 2/2\n\t\tService ID: 123\n\t\tVersion: 1\n\t\tName: analytics\n\t\tTopic: analytics\n\t\tBrokers: 127.0.0.1,127.0.0.2\n\t\tRequired acks: -1\n\t\tCompression codec: zippy\n\t\tUse TLS: true\n\t\tTLS CA certificate: -----BEGIN CERTIFICATE-----foo\n\t\tTLS client certificate: -----BEGIN CERTIFICATE-----bar\n\t\tTLS client key: -----BEGIN PRIVATE KEY-----bar\n\t\tTLS hostname: 127.0.0.1,127.0.0.2\n\t\tFormat: %h %l %u %t \"%r\" %>s %b\n\t\tFormat version: 2\n\t\tResponse condition: Prevent default logging\n\t\tPlacement: none\n\t\tParse log key-values: false\n\t\tMax batch size: 0\n\t\tSASL authentication method: plain\n\t\tSASL authentication username: user\n\t\tSASL authentication password: password\n\t\tProcessing region: us\n  `) + \"\\n\\n\"\n\nfunc getKafkaOK(_ context.Context, i *fastly.GetKafkaInput) (*fastly.Kafka, error) {\n\treturn &fastly.Kafka{\n\t\tServiceID:         fastly.ToPointer(i.ServiceID),\n\t\tServiceVersion:    fastly.ToPointer(i.ServiceVersion),\n\t\tName:              fastly.ToPointer(\"log\"),\n\t\tBrokers:           fastly.ToPointer(\"127.0.0.1,127.0.0.2\"),\n\t\tTopic:             fastly.ToPointer(\"logs\"),\n\t\tRequiredACKs:      fastly.ToPointer(\"-1\"),\n\t\tUseTLS:            fastly.ToPointer(true),\n\t\tCompressionCodec:  fastly.ToPointer(\"zippy\"),\n\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\tFormatVersion:     fastly.ToPointer(2),\n\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\tProcessingRegion:  fastly.ToPointer(\"us\"),\n\t\tTLSCACert:         fastly.ToPointer(\"-----BEGIN CERTIFICATE-----foo\"),\n\t\tTLSHostname:       fastly.ToPointer(\"127.0.0.1,127.0.0.2\"),\n\t\tTLSClientCert:     fastly.ToPointer(\"-----BEGIN CERTIFICATE-----bar\"),\n\t\tTLSClientKey:      fastly.ToPointer(\"-----BEGIN PRIVATE KEY-----bar\"),\n\t}, nil\n}\n\nfunc getKafkaError(_ context.Context, _ *fastly.GetKafkaInput) (*fastly.Kafka, error) {\n\treturn nil, errTest\n}\n\nvar describeKafkaOutput = `\nBrokers: 127.0.0.1,127.0.0.2\nCompression codec: zippy\nFormat: %h %l %u %t \"%r\" %>s %b\nFormat version: 2\nMax batch size: 0\nName: log\nParse log key-values: false\nPlacement: none\nProcessing region: us\nRequired acks: -1\nResponse condition: Prevent default logging\nSASL authentication method: ` + `\nSASL authentication password: ` + `\nSASL authentication username: ` + `\nService ID: 123\nTLS CA certificate: -----BEGIN CERTIFICATE-----foo\nTLS client certificate: -----BEGIN CERTIFICATE-----bar\nTLS client key: -----BEGIN PRIVATE KEY-----bar\nTLS hostname: 127.0.0.1,127.0.0.2\nTopic: logs\nUse TLS: true\nVersion: 1\n`\n\nfunc updateKafkaOK(_ context.Context, i *fastly.UpdateKafkaInput) (*fastly.Kafka, error) {\n\treturn &fastly.Kafka{\n\t\tServiceID:         fastly.ToPointer(i.ServiceID),\n\t\tServiceVersion:    fastly.ToPointer(i.ServiceVersion),\n\t\tName:              fastly.ToPointer(\"log\"),\n\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\tTopic:             fastly.ToPointer(\"logs\"),\n\t\tBrokers:           fastly.ToPointer(\"127.0.0.1,127.0.0.2\"),\n\t\tRequiredACKs:      fastly.ToPointer(\"-1\"),\n\t\tCompressionCodec:  fastly.ToPointer(\"zippy\"),\n\t\tUseTLS:            fastly.ToPointer(true),\n\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\tTLSCACert:         fastly.ToPointer(\"-----BEGIN CERTIFICATE-----foo\"),\n\t\tTLSHostname:       fastly.ToPointer(\"127.0.0.1,127.0.0.2\"),\n\t\tTLSClientCert:     fastly.ToPointer(\"-----BEGIN CERTIFICATE-----bar\"),\n\t\tTLSClientKey:      fastly.ToPointer(\"-----BEGIN PRIVATE KEY-----bar\"),\n\t\tFormatVersion:     fastly.ToPointer(2),\n\t}, nil\n}\n\nfunc updateKafkaSASL(_ context.Context, i *fastly.UpdateKafkaInput) (*fastly.Kafka, error) {\n\treturn &fastly.Kafka{\n\t\tServiceID:         fastly.ToPointer(i.ServiceID),\n\t\tServiceVersion:    fastly.ToPointer(i.ServiceVersion),\n\t\tName:              fastly.ToPointer(\"log\"),\n\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\tTopic:             fastly.ToPointer(\"logs\"),\n\t\tBrokers:           fastly.ToPointer(\"127.0.0.1,127.0.0.2\"),\n\t\tRequiredACKs:      fastly.ToPointer(\"-1\"),\n\t\tCompressionCodec:  fastly.ToPointer(\"zippy\"),\n\t\tUseTLS:            fastly.ToPointer(true),\n\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\tTLSCACert:         fastly.ToPointer(\"-----BEGIN CERTIFICATE-----foo\"),\n\t\tTLSHostname:       fastly.ToPointer(\"127.0.0.1,127.0.0.2\"),\n\t\tTLSClientCert:     fastly.ToPointer(\"-----BEGIN CERTIFICATE-----bar\"),\n\t\tTLSClientKey:      fastly.ToPointer(\"-----BEGIN PRIVATE KEY-----bar\"),\n\t\tFormatVersion:     fastly.ToPointer(2),\n\t\tParseLogKeyvals:   fastly.ToPointer(true),\n\t\tRequestMaxBytes:   fastly.ToPointer(1024),\n\t\tAuthMethod:        fastly.ToPointer(\"plain\"),\n\t\tUser:              fastly.ToPointer(\"user\"),\n\t\tPassword:          fastly.ToPointer(\"password\"),\n\t}, nil\n}\n\nfunc updateKafkaError(_ context.Context, _ *fastly.UpdateKafkaInput) (*fastly.Kafka, error) {\n\treturn nil, errTest\n}\n\nfunc deleteKafkaOK(_ context.Context, _ *fastly.DeleteKafkaInput) error {\n\treturn nil\n}\n\nfunc deleteKafkaError(_ context.Context, _ *fastly.DeleteKafkaInput) error {\n\treturn errTest\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/kafka/kafka_test.go",
    "content": "package kafka_test\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/service/logging/kafka\"\n\t\"github.com/fastly/cli/pkg/config\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/manifest\"\n\t\"github.com/fastly/cli/pkg/mock\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n)\n\nfunc TestCreateKafkaInput(t *testing.T) {\n\tfor _, testcase := range []struct {\n\t\tname      string\n\t\tcmd       *kafka.CreateCommand\n\t\twant      *fastly.CreateKafkaInput\n\t\twantError string\n\t}{\n\t\t{\n\t\t\tname: \"required values set flag serviceID\",\n\t\t\tcmd:  createCommandRequired(),\n\t\t\twant: &fastly.CreateKafkaInput{\n\t\t\t\tServiceID:      \"123\",\n\t\t\t\tServiceVersion: 4,\n\t\t\t\tName:           fastly.ToPointer(\"log\"),\n\t\t\t\tTopic:          fastly.ToPointer(\"logs\"),\n\t\t\t\tBrokers:        fastly.ToPointer(\"127.0.0.1,127.0.0.2\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"all values set flag serviceID\",\n\t\t\tcmd:  createCommandAll(),\n\t\t\twant: &fastly.CreateKafkaInput{\n\t\t\t\tServiceID:         \"123\",\n\t\t\t\tServiceVersion:    4,\n\t\t\t\tName:              fastly.ToPointer(\"logs\"),\n\t\t\t\tBrokers:           fastly.ToPointer(\"127.0.0.1,127.0.0.2\"),\n\t\t\t\tTopic:             fastly.ToPointer(\"logs\"),\n\t\t\t\tRequiredACKs:      fastly.ToPointer(\"-1\"),\n\t\t\t\tUseTLS:            fastly.ToPointer(fastly.Compatibool(true)),\n\t\t\t\tCompressionCodec:  fastly.ToPointer(\"zippy\"),\n\t\t\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\t\t\tFormatVersion:     fastly.ToPointer(2),\n\t\t\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\t\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\t\t\tTLSCACert:         fastly.ToPointer(\"-----BEGIN CERTIFICATE-----foo\"),\n\t\t\t\tTLSHostname:       fastly.ToPointer(\"example.com\"),\n\t\t\t\tTLSClientCert:     fastly.ToPointer(\"-----BEGIN CERTIFICATE-----bar\"),\n\t\t\t\tTLSClientKey:      fastly.ToPointer(\"-----BEGIN PRIVATE KEY-----bar\"),\n\t\t\t\tProcessingRegion:  fastly.ToPointer(\"eu\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:      \"error missing serviceID\",\n\t\t\tcmd:       createCommandMissingServiceID(),\n\t\t\twantError: errors.ErrNoServiceID.Error(),\n\t\t},\n\t\t{\n\t\t\tname: \"verify SASL fields\",\n\t\t\tcmd:  createCommandSASL(\"scram-sha-512\", \"user1\", \"12345\"),\n\t\t\twant: &fastly.CreateKafkaInput{\n\t\t\t\tServiceID:       \"123\",\n\t\t\t\tServiceVersion:  4,\n\t\t\t\tName:            fastly.ToPointer(\"log\"),\n\t\t\t\tTopic:           fastly.ToPointer(\"logs\"),\n\t\t\t\tBrokers:         fastly.ToPointer(\"127.0.0.1,127.0.0.2\"),\n\t\t\t\tParseLogKeyvals: fastly.ToPointer(fastly.Compatibool(true)),\n\t\t\t\tRequestMaxBytes: fastly.ToPointer(11111),\n\t\t\t\tAuthMethod:      fastly.ToPointer(\"scram-sha-512\"),\n\t\t\t\tUser:            fastly.ToPointer(\"user1\"),\n\t\t\t\tPassword:        fastly.ToPointer(\"12345\"),\n\t\t\t},\n\t\t},\n\t} {\n\t\tt.Run(testcase.name, func(t *testing.T) {\n\t\t\tif testcase.wantError == errors.ErrNoServiceID.Error() {\n\t\t\t\tt.Setenv(\"FASTLY_SERVICE_ID\", \"\")\n\t\t\t}\n\t\t\tvar bs []byte\n\t\t\tout := bytes.NewBuffer(bs)\n\t\t\tverboseMode := true\n\n\t\t\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\t\t\tAutoCloneFlag:      testcase.cmd.AutoClone,\n\t\t\t\tAPIClient:          testcase.cmd.Globals.APIClient,\n\t\t\t\tManifest:           testcase.cmd.Manifest,\n\t\t\t\tOut:                out,\n\t\t\t\tServiceVersionFlag: testcase.cmd.ServiceVersion,\n\t\t\t\tVerboseMode:        verboseMode,\n\t\t\t})\n\n\t\t\tswitch {\n\t\t\tcase err != nil && testcase.wantError == \"\":\n\t\t\t\tt.Fatalf(\"unexpected error getting service details: %v\", err)\n\t\t\t\treturn\n\t\t\tcase err != nil && testcase.wantError != \"\":\n\t\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\t\treturn\n\t\t\tcase err == nil && testcase.wantError != \"\":\n\t\t\t\tt.Fatalf(\"expected error, have nil (service details: %s, %d)\", serviceID, serviceVersion.Number)\n\t\t\tcase err == nil && testcase.wantError == \"\":\n\t\t\t\thave, err := testcase.cmd.ConstructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\t\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\t\ttestutil.AssertEqual(t, testcase.want, have)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestUpdateKafkaInput(t *testing.T) {\n\tscenarios := []struct {\n\t\tname      string\n\t\tcmd       *kafka.UpdateCommand\n\t\tapi       mock.API\n\t\twant      *fastly.UpdateKafkaInput\n\t\twantError string\n\t}{\n\t\t{\n\t\t\tname: \"all values set flag serviceID\",\n\t\t\tcmd:  updateCommandAll(),\n\t\t\tapi: mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tGetKafkaFn:     getKafkaOK,\n\t\t\t},\n\t\t\twant: &fastly.UpdateKafkaInput{\n\t\t\t\tServiceID:         \"123\",\n\t\t\t\tServiceVersion:    4,\n\t\t\t\tName:              \"log\",\n\t\t\t\tNewName:           fastly.ToPointer(\"new1\"),\n\t\t\t\tTopic:             fastly.ToPointer(\"new2\"),\n\t\t\t\tBrokers:           fastly.ToPointer(\"new3\"),\n\t\t\t\tRequiredACKs:      fastly.ToPointer(\"new4\"),\n\t\t\t\tUseTLS:            fastly.ToPointer(fastly.Compatibool(false)),\n\t\t\t\tCompressionCodec:  fastly.ToPointer(\"new5\"),\n\t\t\t\tPlacement:         fastly.ToPointer(\"new6\"),\n\t\t\t\tFormat:            fastly.ToPointer(\"new7\"),\n\t\t\t\tFormatVersion:     fastly.ToPointer(3),\n\t\t\t\tResponseCondition: fastly.ToPointer(\"new8\"),\n\t\t\t\tTLSCACert:         fastly.ToPointer(\"new9\"),\n\t\t\t\tTLSClientCert:     fastly.ToPointer(\"new10\"),\n\t\t\t\tTLSClientKey:      fastly.ToPointer(\"new11\"),\n\t\t\t\tTLSHostname:       fastly.ToPointer(\"new12\"),\n\t\t\t\tParseLogKeyvals:   fastly.ToPointer(fastly.Compatibool(false)),\n\t\t\t\tRequestMaxBytes:   fastly.ToPointer(22222),\n\t\t\t\tAuthMethod:        fastly.ToPointer(\"plain\"),\n\t\t\t\tUser:              fastly.ToPointer(\"new13\"),\n\t\t\t\tPassword:          fastly.ToPointer(\"new14\"),\n\t\t\t\tProcessingRegion:  fastly.ToPointer(\"eu\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"no updates\",\n\t\t\tcmd:  updateCommandNoUpdates(),\n\t\t\tapi: mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tGetKafkaFn:     getKafkaOK,\n\t\t\t},\n\t\t\twant: &fastly.UpdateKafkaInput{\n\t\t\t\tServiceID:      \"123\",\n\t\t\t\tServiceVersion: 4,\n\t\t\t\tName:           \"log\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:      \"error missing serviceID\",\n\t\t\tcmd:       updateCommandMissingServiceID(),\n\t\t\twant:      nil,\n\t\t\twantError: errors.ErrNoServiceID.Error(),\n\t\t},\n\t\t{\n\t\t\tname: \"verify SASL fields\",\n\t\t\tapi: mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tGetKafkaFn:     getKafkaOK,\n\t\t\t},\n\t\t\tcmd: updateCommandSASL(\"scram-sha-512\", \"user1\", \"12345\"),\n\t\t\twant: &fastly.UpdateKafkaInput{\n\t\t\t\tServiceID:       \"123\",\n\t\t\t\tServiceVersion:  4,\n\t\t\t\tName:            \"log\",\n\t\t\t\tTopic:           fastly.ToPointer(\"logs\"),\n\t\t\t\tBrokers:         fastly.ToPointer(\"127.0.0.1,127.0.0.2\"),\n\t\t\t\tParseLogKeyvals: fastly.ToPointer(fastly.Compatibool(true)),\n\t\t\t\tRequestMaxBytes: fastly.ToPointer(11111),\n\t\t\t\tAuthMethod:      fastly.ToPointer(\"scram-sha-512\"),\n\t\t\t\tUser:            fastly.ToPointer(\"user1\"),\n\t\t\t\tPassword:        fastly.ToPointer(\"12345\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"verify disabling SASL\",\n\t\t\tapi: mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tGetKafkaFn:     getKafkaSASL,\n\t\t\t},\n\t\t\tcmd: updateCommandNoSASL(),\n\t\t\twant: &fastly.UpdateKafkaInput{\n\t\t\t\tServiceID:       \"123\",\n\t\t\t\tServiceVersion:  4,\n\t\t\t\tName:            \"log\",\n\t\t\t\tTopic:           fastly.ToPointer(\"logs\"),\n\t\t\t\tBrokers:         fastly.ToPointer(\"127.0.0.1,127.0.0.2\"),\n\t\t\t\tParseLogKeyvals: fastly.ToPointer(fastly.Compatibool(true)),\n\t\t\t\tRequestMaxBytes: fastly.ToPointer(11111),\n\t\t\t\tAuthMethod:      fastly.ToPointer(\"\"),\n\t\t\t\tUser:            fastly.ToPointer(\"\"),\n\t\t\t\tPassword:        fastly.ToPointer(\"\"),\n\t\t\t},\n\t\t},\n\t}\n\tfor testcaseIdx := range scenarios {\n\t\ttestcase := &scenarios[testcaseIdx]\n\t\tt.Run(testcase.name, func(t *testing.T) {\n\t\t\tif testcase.wantError == errors.ErrNoServiceID.Error() {\n\t\t\t\tt.Setenv(\"FASTLY_SERVICE_ID\", \"\")\n\t\t\t}\n\t\t\ttestcase.cmd.Globals.APIClient = testcase.api\n\n\t\t\tvar bs []byte\n\t\t\tout := bytes.NewBuffer(bs)\n\t\t\tverboseMode := true\n\n\t\t\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\t\t\tAutoCloneFlag:      testcase.cmd.AutoClone,\n\t\t\t\tAPIClient:          testcase.api,\n\t\t\t\tManifest:           testcase.cmd.Manifest,\n\t\t\t\tOut:                out,\n\t\t\t\tServiceVersionFlag: testcase.cmd.ServiceVersion,\n\t\t\t\tVerboseMode:        verboseMode,\n\t\t\t})\n\n\t\t\tswitch {\n\t\t\tcase err != nil && testcase.wantError == \"\":\n\t\t\t\tt.Fatalf(\"unexpected error getting service details: %v\", err)\n\t\t\t\treturn\n\t\t\tcase err != nil && testcase.wantError != \"\":\n\t\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\t\treturn\n\t\t\tcase err == nil && testcase.wantError != \"\":\n\t\t\t\tt.Fatalf(\"expected error, have nil (service details: %s, %d)\", serviceID, serviceVersion.Number)\n\t\t\tcase err == nil && testcase.wantError == \"\":\n\t\t\t\thave, err := testcase.cmd.ConstructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\t\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\t\ttestutil.AssertEqual(t, testcase.want, have)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc createCommandRequired() *kafka.CreateCommand {\n\tvar b bytes.Buffer\n\n\tg := global.Data{\n\t\tConfig: config.File{},\n\t\tEnv:    config.Environment{},\n\t\tOutput: &b,\n\t}\n\tg.APIClient, _ = mock.APIClient(mock.API{\n\t\tGetVersionFn:   testutil.GetVersion,\n\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t})(\"token\", \"endpoint\", false)\n\n\treturn &kafka.CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: &g,\n\t\t},\n\t\tManifest: manifest.Data{\n\t\t\tFlag: manifest.Flag{\n\t\t\t\tServiceID: \"123\",\n\t\t\t},\n\t\t},\n\t\tServiceVersion: argparser.OptionalServiceVersion{\n\t\t\tOptionalString: argparser.OptionalString{Value: \"1\"},\n\t\t},\n\t\tAutoClone: argparser.OptionalAutoClone{\n\t\t\tOptionalBool: argparser.OptionalBool{\n\t\t\t\tOptional: argparser.Optional{\n\t\t\t\t\tWasSet: true,\n\t\t\t\t},\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t},\n\t\tEndpointName: argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"log\"},\n\t\tTopic:        argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"logs\"},\n\t\tBrokers:      argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"127.0.0.1,127.0.0.2\"},\n\t}\n}\n\nfunc createCommandAll() *kafka.CreateCommand {\n\tvar b bytes.Buffer\n\n\tg := global.Data{\n\t\tConfig: config.File{},\n\t\tEnv:    config.Environment{},\n\t\tOutput: &b,\n\t}\n\tg.APIClient, _ = mock.APIClient(mock.API{\n\t\tGetVersionFn:   testutil.GetVersion,\n\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t})(\"token\", \"endpoint\", false)\n\n\treturn &kafka.CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: &g,\n\t\t},\n\t\tManifest: manifest.Data{\n\t\t\tFlag: manifest.Flag{\n\t\t\t\tServiceID: \"123\",\n\t\t\t},\n\t\t},\n\t\tServiceVersion: argparser.OptionalServiceVersion{\n\t\t\tOptionalString: argparser.OptionalString{Value: \"1\"},\n\t\t},\n\t\tAutoClone: argparser.OptionalAutoClone{\n\t\t\tOptionalBool: argparser.OptionalBool{\n\t\t\t\tOptional: argparser.Optional{\n\t\t\t\t\tWasSet: true,\n\t\t\t\t},\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t},\n\t\tEndpointName:      argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"logs\"},\n\t\tTopic:             argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"logs\"},\n\t\tBrokers:           argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"127.0.0.1,127.0.0.2\"},\n\t\tUseTLS:            argparser.OptionalBool{Optional: argparser.Optional{WasSet: true}, Value: true},\n\t\tRequiredACKs:      argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"-1\"},\n\t\tCompressionCodec:  argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"zippy\"},\n\t\tFormat:            argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: `%h %l %u %t \"%r\" %>s %b`},\n\t\tFormatVersion:     argparser.OptionalInt{Optional: argparser.Optional{WasSet: true}, Value: 2},\n\t\tResponseCondition: argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"Prevent default logging\"},\n\t\tPlacement:         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"none\"},\n\t\tTLSCACert:         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"-----BEGIN CERTIFICATE-----foo\"},\n\t\tTLSHostname:       argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"example.com\"},\n\t\tTLSClientCert:     argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"-----BEGIN CERTIFICATE-----bar\"},\n\t\tTLSClientKey:      argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"-----BEGIN PRIVATE KEY-----bar\"},\n\t\tProcessingRegion:  argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"eu\"},\n\t}\n}\n\nfunc createCommandSASL(authMethod, user, password string) *kafka.CreateCommand {\n\tvar b bytes.Buffer\n\n\tg := global.Data{\n\t\tConfig: config.File{},\n\t\tEnv:    config.Environment{},\n\t\tOutput: &b,\n\t}\n\tg.APIClient, _ = mock.APIClient(mock.API{\n\t\tGetVersionFn:   testutil.GetVersion,\n\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t})(\"token\", \"endpoint\", false)\n\n\treturn &kafka.CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: &g,\n\t\t},\n\t\tManifest: manifest.Data{\n\t\t\tFlag: manifest.Flag{\n\t\t\t\tServiceID: \"123\",\n\t\t\t},\n\t\t},\n\t\tServiceVersion: argparser.OptionalServiceVersion{\n\t\t\tOptionalString: argparser.OptionalString{Value: \"1\"},\n\t\t},\n\t\tAutoClone: argparser.OptionalAutoClone{\n\t\t\tOptionalBool: argparser.OptionalBool{\n\t\t\t\tOptional: argparser.Optional{\n\t\t\t\t\tWasSet: true,\n\t\t\t\t},\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t},\n\t\tEndpointName:    argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"log\"},\n\t\tTopic:           argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"logs\"},\n\t\tBrokers:         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"127.0.0.1,127.0.0.2\"},\n\t\tParseLogKeyvals: argparser.OptionalBool{Optional: argparser.Optional{WasSet: true}, Value: true},\n\t\tRequestMaxBytes: argparser.OptionalInt{Optional: argparser.Optional{WasSet: true}, Value: 11111},\n\t\tUseSASL:         argparser.OptionalBool{Optional: argparser.Optional{WasSet: true}, Value: true},\n\t\tAuthMethod:      argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: authMethod},\n\t\tUser:            argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: user},\n\t\tPassword:        argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: password},\n\t}\n}\n\nfunc createCommandMissingServiceID() *kafka.CreateCommand {\n\tres := createCommandAll()\n\tres.Manifest = manifest.Data{}\n\tres.ServiceVersion = argparser.OptionalServiceVersion{}\n\treturn res\n}\n\nfunc updateCommandNoUpdates() *kafka.UpdateCommand {\n\tvar b bytes.Buffer\n\n\tg := global.Data{\n\t\tConfig: config.File{},\n\t\tEnv:    config.Environment{},\n\t\tOutput: &b,\n\t}\n\n\treturn &kafka.UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: &g,\n\t\t},\n\t\tManifest: manifest.Data{\n\t\t\tFlag: manifest.Flag{\n\t\t\t\tServiceID: \"123\",\n\t\t\t},\n\t\t},\n\t\tEndpointName: \"log\",\n\t\tServiceVersion: argparser.OptionalServiceVersion{\n\t\t\tOptionalString: argparser.OptionalString{Value: \"1\"},\n\t\t},\n\t\tAutoClone: argparser.OptionalAutoClone{\n\t\t\tOptionalBool: argparser.OptionalBool{\n\t\t\t\tOptional: argparser.Optional{\n\t\t\t\t\tWasSet: true,\n\t\t\t\t},\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t},\n\t}\n}\n\nfunc updateCommandAll() *kafka.UpdateCommand {\n\tvar b bytes.Buffer\n\n\tg := global.Data{\n\t\tConfig: config.File{},\n\t\tEnv:    config.Environment{},\n\t\tOutput: &b,\n\t}\n\n\treturn &kafka.UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: &g,\n\t\t},\n\t\tManifest: manifest.Data{\n\t\t\tFlag: manifest.Flag{\n\t\t\t\tServiceID: \"123\",\n\t\t\t},\n\t\t},\n\t\tEndpointName: \"log\",\n\t\tServiceVersion: argparser.OptionalServiceVersion{\n\t\t\tOptionalString: argparser.OptionalString{Value: \"1\"},\n\t\t},\n\t\tAutoClone: argparser.OptionalAutoClone{\n\t\t\tOptionalBool: argparser.OptionalBool{\n\t\t\t\tOptional: argparser.Optional{\n\t\t\t\t\tWasSet: true,\n\t\t\t\t},\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t},\n\t\tNewName:           argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new1\"},\n\t\tTopic:             argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new2\"},\n\t\tBrokers:           argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new3\"},\n\t\tUseTLS:            argparser.OptionalBool{Optional: argparser.Optional{WasSet: true}, Value: false},\n\t\tRequiredACKs:      argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new4\"},\n\t\tCompressionCodec:  argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new5\"},\n\t\tPlacement:         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new6\"},\n\t\tFormat:            argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new7\"},\n\t\tFormatVersion:     argparser.OptionalInt{Optional: argparser.Optional{WasSet: true}, Value: 3},\n\t\tResponseCondition: argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new8\"},\n\t\tTLSCACert:         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new9\"},\n\t\tTLSClientCert:     argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new10\"},\n\t\tTLSClientKey:      argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new11\"},\n\t\tTLSHostname:       argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new12\"},\n\t\tParseLogKeyvals:   argparser.OptionalBool{Optional: argparser.Optional{WasSet: true}, Value: false},\n\t\tRequestMaxBytes:   argparser.OptionalInt{Optional: argparser.Optional{WasSet: true}, Value: 22222},\n\t\tUseSASL:           argparser.OptionalBool{Optional: argparser.Optional{WasSet: true}, Value: true},\n\t\tAuthMethod:        argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"plain\"},\n\t\tUser:              argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new13\"},\n\t\tPassword:          argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new14\"},\n\t\tProcessingRegion:  argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"eu\"},\n\t}\n}\n\nfunc updateCommandSASL(authMethod, user, password string) *kafka.UpdateCommand {\n\tvar b bytes.Buffer\n\n\tg := global.Data{\n\t\tConfig: config.File{},\n\t\tEnv:    config.Environment{},\n\t\tOutput: &b,\n\t}\n\n\treturn &kafka.UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: &g,\n\t\t},\n\t\tManifest: manifest.Data{\n\t\t\tFlag: manifest.Flag{\n\t\t\t\tServiceID: \"123\",\n\t\t\t},\n\t\t},\n\t\tEndpointName: \"log\",\n\t\tServiceVersion: argparser.OptionalServiceVersion{\n\t\t\tOptionalString: argparser.OptionalString{Value: \"1\"},\n\t\t},\n\t\tAutoClone: argparser.OptionalAutoClone{\n\t\t\tOptionalBool: argparser.OptionalBool{\n\t\t\t\tOptional: argparser.Optional{\n\t\t\t\t\tWasSet: true,\n\t\t\t\t},\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t},\n\t\tTopic:           argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"logs\"},\n\t\tBrokers:         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"127.0.0.1,127.0.0.2\"},\n\t\tParseLogKeyvals: argparser.OptionalBool{Optional: argparser.Optional{WasSet: true}, Value: true},\n\t\tRequestMaxBytes: argparser.OptionalInt{Optional: argparser.Optional{WasSet: true}, Value: 11111},\n\t\tUseSASL:         argparser.OptionalBool{Optional: argparser.Optional{WasSet: true}, Value: true},\n\t\tAuthMethod:      argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: authMethod},\n\t\tUser:            argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: user},\n\t\tPassword:        argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: password},\n\t}\n}\n\nfunc updateCommandNoSASL() *kafka.UpdateCommand {\n\tvar b bytes.Buffer\n\n\tg := global.Data{\n\t\tConfig: config.File{},\n\t\tEnv:    config.Environment{},\n\t\tOutput: &b,\n\t}\n\n\treturn &kafka.UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: &g,\n\t\t},\n\t\tManifest: manifest.Data{\n\t\t\tFlag: manifest.Flag{\n\t\t\t\tServiceID: \"123\",\n\t\t\t},\n\t\t},\n\t\tEndpointName: \"log\",\n\t\tServiceVersion: argparser.OptionalServiceVersion{\n\t\t\tOptionalString: argparser.OptionalString{Value: \"1\"},\n\t\t},\n\t\tAutoClone: argparser.OptionalAutoClone{\n\t\t\tOptionalBool: argparser.OptionalBool{\n\t\t\t\tOptional: argparser.Optional{\n\t\t\t\t\tWasSet: true,\n\t\t\t\t},\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t},\n\t\tTopic:           argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"logs\"},\n\t\tBrokers:         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"127.0.0.1,127.0.0.2\"},\n\t\tParseLogKeyvals: argparser.OptionalBool{Optional: argparser.Optional{WasSet: true}, Value: true},\n\t\tRequestMaxBytes: argparser.OptionalInt{Optional: argparser.Optional{WasSet: true}, Value: 11111},\n\t\tUseSASL:         argparser.OptionalBool{Optional: argparser.Optional{WasSet: true}, Value: false},\n\t\tAuthMethod:      argparser.OptionalString{Optional: argparser.Optional{WasSet: false}, Value: \"\"},\n\t\tUser:            argparser.OptionalString{Optional: argparser.Optional{WasSet: false}, Value: \"\"},\n\t\tPassword:        argparser.OptionalString{Optional: argparser.Optional{WasSet: false}, Value: \"\"},\n\t}\n}\n\nfunc updateCommandMissingServiceID() *kafka.UpdateCommand {\n\tres := updateCommandAll()\n\tres.Manifest = manifest.Data{}\n\tres.ServiceVersion = argparser.OptionalServiceVersion{}\n\treturn res\n}\n\nfunc getKafkaSASL(_ context.Context, i *fastly.GetKafkaInput) (*fastly.Kafka, error) {\n\treturn &fastly.Kafka{\n\t\tServiceID:         fastly.ToPointer(i.ServiceID),\n\t\tServiceVersion:    fastly.ToPointer(i.ServiceVersion),\n\t\tName:              fastly.ToPointer(\"log\"),\n\t\tBrokers:           fastly.ToPointer(\"127.0.0.1,127.0.0.2\"),\n\t\tTopic:             fastly.ToPointer(\"logs\"),\n\t\tRequiredACKs:      fastly.ToPointer(\"-1\"),\n\t\tUseTLS:            fastly.ToPointer(true),\n\t\tCompressionCodec:  fastly.ToPointer(\"zippy\"),\n\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\tFormatVersion:     fastly.ToPointer(2),\n\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\tTLSCACert:         fastly.ToPointer(\"-----BEGIN CERTIFICATE-----foo\"),\n\t\tTLSHostname:       fastly.ToPointer(\"example.com\"),\n\t\tTLSClientCert:     fastly.ToPointer(\"-----BEGIN CERTIFICATE-----bar\"),\n\t\tTLSClientKey:      fastly.ToPointer(\"-----BEGIN PRIVATE KEY-----bar\"),\n\t\tParseLogKeyvals:   fastly.ToPointer(false),\n\t\tRequestMaxBytes:   fastly.ToPointer(0),\n\t\tAuthMethod:        fastly.ToPointer(\"plain\"),\n\t\tUser:              fastly.ToPointer(\"user\"),\n\t\tPassword:          fastly.ToPointer(\"password\"),\n\t}, nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/kafka/list.go",
    "content": "package kafka\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// ListCommand calls the Fastly API to list Kafka logging endpoints.\ntype ListCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tInput          fastly.ListKafkasInput\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n}\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"list\", \"List Kafka endpoints on a Fastly service version\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *ListCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fsterr.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tc.Input.ServiceID = serviceID\n\tc.Input.ServiceVersion = fastly.ToValue(serviceVersion.Number)\n\n\to, err := c.Globals.APIClient.ListKafkas(context.TODO(), &c.Input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\tif !c.Globals.Verbose() {\n\t\ttw := text.NewTable(out)\n\t\ttw.AddHeader(\"SERVICE\", \"VERSION\", \"NAME\")\n\t\tfor _, kafka := range o {\n\t\t\ttw.AddLine(\n\t\t\t\tfastly.ToValue(kafka.ServiceID),\n\t\t\t\tfastly.ToValue(kafka.ServiceVersion),\n\t\t\t\tfastly.ToValue(kafka.Name),\n\t\t\t)\n\t\t}\n\t\ttw.Print()\n\t\treturn nil\n\t}\n\n\tfmt.Fprintf(out, \"Version: %d\\n\", c.Input.ServiceVersion)\n\tfor i, kafka := range o {\n\t\tfmt.Fprintf(out, \"\\tKafka %d/%d\\n\", i+1, len(o))\n\t\tfmt.Fprintf(out, \"\\t\\tService ID: %s\\n\", fastly.ToValue(kafka.ServiceID))\n\t\tfmt.Fprintf(out, \"\\t\\tVersion: %d\\n\", fastly.ToValue(kafka.ServiceVersion))\n\t\tfmt.Fprintf(out, \"\\t\\tName: %s\\n\", fastly.ToValue(kafka.Name))\n\t\tfmt.Fprintf(out, \"\\t\\tTopic: %s\\n\", fastly.ToValue(kafka.Topic))\n\t\tfmt.Fprintf(out, \"\\t\\tBrokers: %s\\n\", fastly.ToValue(kafka.Brokers))\n\t\tfmt.Fprintf(out, \"\\t\\tRequired acks: %s\\n\", fastly.ToValue(kafka.RequiredACKs))\n\t\tfmt.Fprintf(out, \"\\t\\tCompression codec: %s\\n\", fastly.ToValue(kafka.CompressionCodec))\n\t\tfmt.Fprintf(out, \"\\t\\tUse TLS: %t\\n\", fastly.ToValue(kafka.UseTLS))\n\t\tfmt.Fprintf(out, \"\\t\\tTLS CA certificate: %s\\n\", fastly.ToValue(kafka.TLSCACert))\n\t\tfmt.Fprintf(out, \"\\t\\tTLS client certificate: %s\\n\", fastly.ToValue(kafka.TLSClientCert))\n\t\tfmt.Fprintf(out, \"\\t\\tTLS client key: %s\\n\", fastly.ToValue(kafka.TLSClientKey))\n\t\tfmt.Fprintf(out, \"\\t\\tTLS hostname: %s\\n\", fastly.ToValue(kafka.TLSHostname))\n\t\tfmt.Fprintf(out, \"\\t\\tFormat: %s\\n\", fastly.ToValue(kafka.Format))\n\t\tfmt.Fprintf(out, \"\\t\\tFormat version: %d\\n\", fastly.ToValue(kafka.FormatVersion))\n\t\tfmt.Fprintf(out, \"\\t\\tResponse condition: %s\\n\", fastly.ToValue(kafka.ResponseCondition))\n\t\tfmt.Fprintf(out, \"\\t\\tPlacement: %s\\n\", fastly.ToValue(kafka.Placement))\n\t\tfmt.Fprintf(out, \"\\t\\tParse log key-values: %t\\n\", fastly.ToValue(kafka.ParseLogKeyvals))\n\t\tfmt.Fprintf(out, \"\\t\\tMax batch size: %d\\n\", fastly.ToValue(kafka.RequestMaxBytes))\n\t\tfmt.Fprintf(out, \"\\t\\tSASL authentication method: %s\\n\", fastly.ToValue(kafka.AuthMethod))\n\t\tfmt.Fprintf(out, \"\\t\\tSASL authentication username: %s\\n\", fastly.ToValue(kafka.User))\n\t\tfmt.Fprintf(out, \"\\t\\tSASL authentication password: %s\\n\", fastly.ToValue(kafka.Password))\n\t\tfmt.Fprintf(out, \"\\t\\tProcessing region: %s\\n\", fastly.ToValue(kafka.ProcessingRegion))\n\t}\n\tfmt.Fprintln(out)\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/kafka/root.go",
    "content": "package kafka\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"kafka\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manipulate Fastly service version Kafka logging endpoints\")\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/kafka/update.go",
    "content": "package kafka\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/service/logging/logflags\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/manifest\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// UpdateCommand calls the Fastly API to update a Kafka logging endpoint.\ntype UpdateCommand struct {\n\targparser.Base\n\tManifest manifest.Data\n\n\t// Required.\n\tEndpointName   string // Can't shadow argparser.Base method Name().\n\tServiceName    argparser.OptionalServiceNameID\n\tServiceVersion argparser.OptionalServiceVersion\n\n\t// Optional.\n\tAuthMethod        argparser.OptionalString\n\tAutoClone         argparser.OptionalAutoClone\n\tBrokers           argparser.OptionalString\n\tCompressionCodec  argparser.OptionalString\n\tFormat            argparser.OptionalString\n\tFormatVersion     argparser.OptionalInt\n\tIndex             argparser.OptionalString\n\tNewName           argparser.OptionalString\n\tParseLogKeyvals   argparser.OptionalBool\n\tPassword          argparser.OptionalString\n\tPlacement         argparser.OptionalString\n\tProcessingRegion  argparser.OptionalString\n\tRequestMaxBytes   argparser.OptionalInt\n\tRequiredACKs      argparser.OptionalString\n\tResponseCondition argparser.OptionalString\n\tTLSCACert         argparser.OptionalString\n\tTLSClientCert     argparser.OptionalString\n\tTLSClientKey      argparser.OptionalString\n\tTLSHostname       argparser.OptionalString\n\tTopic             argparser.OptionalString\n\tUseSASL           argparser.OptionalBool\n\tUseTLS            argparser.OptionalBool\n\tUser              argparser.OptionalString\n}\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"update\", \"Update a Kafka logging endpoint on a Fastly service version\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"The name of the Kafka logging object\").Short('n').Required().StringVar(&c.EndpointName)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.ServiceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.AutoClone.Set,\n\t\tDst:    &c.AutoClone.Value,\n\t})\n\tc.CmdClause.Flag(\"auth-method\", \"SASL authentication method. Valid values are: plain, scram-sha-256, scram-sha-512\").Action(c.AuthMethod.Set).HintOptions(\"plain\", \"scram-sha-256\", \"scram-sha-512\").EnumVar(&c.AuthMethod.Value, \"plain\", \"scram-sha-256\", \"scram-sha-512\")\n\tc.CmdClause.Flag(\"brokers\", \"A comma-separated list of IP addresses or hostnames of Kafka brokers\").Action(c.Brokers.Set).StringVar(&c.Brokers.Value)\n\tc.CmdClause.Flag(\"compression-codec\", \"The codec used for compression of your logs. One of: gzip, snappy, lz4\").Action(c.CompressionCodec.Set).StringVar(&c.CompressionCodec.Value)\n\tlogflags.Format(c.CmdClause, &c.Format)\n\tlogflags.FormatVersion(c.CmdClause, &c.FormatVersion)\n\tc.CmdClause.Flag(\"max-batch-size\", \"The maximum size of the log batch in bytes\").Action(c.RequestMaxBytes.Set).IntVar(&c.RequestMaxBytes.Value)\n\tc.CmdClause.Flag(\"new-name\", \"New name of the Kafka logging object\").Action(c.NewName.Set).StringVar(&c.NewName.Value)\n\tc.CmdClause.Flag(\"parse-log-keyvals\", \"Parse key-value pairs within the log format\").Action(c.ParseLogKeyvals.Set).NegatableBoolVar(&c.ParseLogKeyvals.Value)\n\tc.CmdClause.Flag(\"password\", \"SASL authentication password. Required if --auth-method is specified\").Action(c.Password.Set).StringVar(&c.Password.Value)\n\tlogflags.Placement(c.CmdClause, &c.Placement)\n\tlogflags.ProcessingRegion(c.CmdClause, &c.ProcessingRegion, \"Kafka\")\n\tc.CmdClause.Flag(\"required-acks\", \"The Number of acknowledgements a leader must receive before a write is considered successful. One of: 1 (default) One server needs to respond. 0\tNo servers need to respond. -1\tWait for all in-sync replicas to respond\").Action(c.RequiredACKs.Set).StringVar(&c.RequiredACKs.Value)\n\tlogflags.ResponseCondition(c.CmdClause, &c.ResponseCondition)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.ServiceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.ServiceName.Value,\n\t})\n\tlogflags.TLSCACert(c.CmdClause, &c.TLSCACert)\n\tlogflags.TLSClientCert(c.CmdClause, &c.TLSClientCert)\n\tlogflags.TLSClientKey(c.CmdClause, &c.TLSClientKey)\n\tlogflags.TLSHostname(c.CmdClause, &c.TLSHostname)\n\tc.CmdClause.Flag(\"topic\", \"The Kafka topic to send logs to\").Action(c.Topic.Set).StringVar(&c.Topic.Value)\n\tc.CmdClause.Flag(\"use-sasl\", \"Enable SASL authentication. Requires --auth-method, --username, and --password to be specified\").Action(c.UseSASL.Set).BoolVar(&c.UseSASL.Value)\n\tc.CmdClause.Flag(\"use-tls\", \"Whether to use TLS for secure logging. Can be either true or false\").Action(c.UseTLS.Set).BoolVar(&c.UseTLS.Value)\n\tc.CmdClause.Flag(\"username\", \"SASL authentication username. Required if --auth-method is specified\").Action(c.User.Set).StringVar(&c.User.Value)\n\treturn &c\n}\n\n// ConstructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *UpdateCommand) ConstructInput(serviceID string, serviceVersion int) (*fastly.UpdateKafkaInput, error) {\n\tif c.UseSASL.WasSet && c.UseSASL.Value && (c.AuthMethod.Value == \"\" || c.User.Value == \"\" || c.Password.Value == \"\") {\n\t\treturn nil, fmt.Errorf(\"the --auth-method, --username, and --password flags must be present when using the --use-sasl flag\")\n\t}\n\n\tif !c.UseSASL.Value && (c.AuthMethod.Value != \"\" || c.User.Value != \"\" || c.Password.Value != \"\") {\n\t\treturn nil, fmt.Errorf(\"the --auth-method, --username, and --password options are only valid when the --use-sasl flag is specified\")\n\t}\n\n\tinput := fastly.UpdateKafkaInput{\n\t\tServiceID:      serviceID,\n\t\tServiceVersion: serviceVersion,\n\t\tName:           c.EndpointName,\n\t}\n\n\tif c.NewName.WasSet {\n\t\tinput.NewName = &c.NewName.Value\n\t}\n\n\tif c.Topic.WasSet {\n\t\tinput.Topic = &c.Topic.Value\n\t}\n\n\tif c.Brokers.WasSet {\n\t\tinput.Brokers = &c.Brokers.Value\n\t}\n\n\tif c.CompressionCodec.WasSet {\n\t\tinput.CompressionCodec = &c.CompressionCodec.Value\n\t}\n\n\tif c.RequiredACKs.WasSet {\n\t\tinput.RequiredACKs = &c.RequiredACKs.Value\n\t}\n\n\tif c.UseTLS.WasSet {\n\t\tinput.UseTLS = fastly.ToPointer(fastly.Compatibool(c.UseTLS.Value))\n\t}\n\n\tif c.TLSCACert.WasSet {\n\t\tinput.TLSCACert = &c.TLSCACert.Value\n\t}\n\n\tif c.TLSClientCert.WasSet {\n\t\tinput.TLSClientCert = &c.TLSClientCert.Value\n\t}\n\n\tif c.TLSClientKey.WasSet {\n\t\tinput.TLSClientKey = &c.TLSClientKey.Value\n\t}\n\n\tif c.TLSHostname.WasSet {\n\t\tinput.TLSHostname = &c.TLSHostname.Value\n\t}\n\n\tif c.Format.WasSet {\n\t\tinput.Format = fastly.ToPointer(argparser.Content(c.Format.Value))\n\t}\n\n\tif c.FormatVersion.WasSet {\n\t\tinput.FormatVersion = &c.FormatVersion.Value\n\t}\n\n\tif c.ResponseCondition.WasSet {\n\t\tinput.ResponseCondition = &c.ResponseCondition.Value\n\t}\n\n\tif c.Placement.WasSet {\n\t\tinput.Placement = &c.Placement.Value\n\t}\n\n\tif c.ParseLogKeyvals.WasSet {\n\t\tinput.ParseLogKeyvals = fastly.ToPointer(fastly.Compatibool(c.ParseLogKeyvals.Value))\n\t}\n\n\tif c.RequestMaxBytes.WasSet {\n\t\tinput.RequestMaxBytes = &c.RequestMaxBytes.Value\n\t}\n\n\tif c.UseSASL.WasSet && !c.UseSASL.Value {\n\t\tinput.AuthMethod = fastly.ToPointer(\"\")\n\t\tinput.User = fastly.ToPointer(\"\")\n\t\tinput.Password = fastly.ToPointer(\"\")\n\t}\n\n\tif c.AuthMethod.WasSet {\n\t\tinput.AuthMethod = &c.AuthMethod.Value\n\t}\n\n\tif c.User.WasSet {\n\t\tinput.User = &c.User.Value\n\t}\n\n\tif c.Password.WasSet {\n\t\tinput.Password = &c.Password.Value\n\t}\n\n\tif c.ProcessingRegion.WasSet {\n\t\tinput.ProcessingRegion = &c.ProcessingRegion.Value\n\t}\n\n\treturn &input, nil\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *UpdateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(false),\n\t\tLocked:             optional.Of(false),\n\t\tAutoCloneFlag:      c.AutoClone,\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.ServiceName,\n\t\tServiceVersionFlag: c.ServiceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": errors.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tinput, err := c.ConstructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tkafka, err := c.Globals.APIClient.UpdateKafka(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\ttext.Success(\n\t\tout,\n\t\t\"Updated Kafka logging endpoint %s (service %s version %d)\",\n\t\tfastly.ToValue(kafka.Name),\n\t\tfastly.ToValue(kafka.ServiceID),\n\t\tfastly.ToValue(kafka.ServiceVersion),\n\t)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/kinesis/create.go",
    "content": "package kinesis\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/service/logging/logflags\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/manifest\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// CreateCommand calls the Fastly API to create an Amazon Kinesis logging endpoint.\ntype CreateCommand struct {\n\targparser.Base\n\tManifest manifest.Data\n\n\t// Required.\n\tServiceName    argparser.OptionalServiceNameID\n\tServiceVersion argparser.OptionalServiceVersion\n\n\t// mutual exclusions\n\t// AccessKey + SecretKey or IAMRole must be provided\n\tAccessKey argparser.OptionalString\n\tSecretKey argparser.OptionalString\n\tIAMRole   argparser.OptionalString\n\n\t// Optional.\n\tAutoClone         argparser.OptionalAutoClone\n\tEndpointName      argparser.OptionalString // Can't shadow argparser.Base method Name().\n\tFormat            argparser.OptionalString\n\tFormatVersion     argparser.OptionalInt\n\tPlacement         argparser.OptionalString\n\tProcessingRegion  argparser.OptionalString\n\tRegion            argparser.OptionalString\n\tResponseCondition argparser.OptionalString\n\tStreamName        argparser.OptionalString\n}\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"create\", \"Create an Amazon Kinesis logging endpoint on a Fastly service version\").Alias(\"add\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"The name of the Kinesis logging object. Used as a primary key for API access\").Short('n').Action(c.EndpointName.Set).StringVar(&c.EndpointName.Value)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.ServiceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// required, but mutually exclusive\n\tc.CmdClause.Flag(\"access-key\", \"The access key associated with the target Amazon Kinesis stream\").Action(c.AccessKey.Set).StringVar(&c.AccessKey.Value)\n\tc.CmdClause.Flag(\"secret-key\", \"The secret key associated with the target Amazon Kinesis stream\").Action(c.SecretKey.Set).StringVar(&c.SecretKey.Value)\n\tc.CmdClause.Flag(\"iam-role\", \"The IAM role ARN for logging\").Action(c.IAMRole.Set).StringVar(&c.IAMRole.Value)\n\n\t// Optional.\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.AutoClone.Set,\n\t\tDst:    &c.AutoClone.Value,\n\t})\n\tlogflags.Format(c.CmdClause, &c.Format)\n\tlogflags.FormatVersion(c.CmdClause, &c.FormatVersion)\n\tc.CmdClause.Flag(\"region\", \"The region where logs are received and stored by Kinesis\").Action(c.Region.Set).StringVar(&c.Region.Value)\n\tlogflags.ResponseCondition(c.CmdClause, &c.ResponseCondition)\n\tlogflags.Placement(c.CmdClause, &c.Placement)\n\tlogflags.ProcessingRegion(c.CmdClause, &c.ProcessingRegion, \"Kinesis\")\n\tc.CmdClause.Flag(\"stream-name\", \"The Amazon Kinesis stream to send logs to\").Action(c.StreamName.Set).StringVar(&c.StreamName.Value)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.ServiceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.ServiceName.Value,\n\t})\n\n\treturn &c\n}\n\n// ConstructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *CreateCommand) ConstructInput(serviceID string, serviceVersion int) (*fastly.CreateKinesisInput, error) {\n\tvar input fastly.CreateKinesisInput\n\n\tinput.ServiceID = serviceID\n\tif c.EndpointName.WasSet {\n\t\tinput.Name = &c.EndpointName.Value\n\t}\n\tif c.StreamName.WasSet {\n\t\tinput.StreamName = &c.StreamName.Value\n\t}\n\tif c.Region.WasSet {\n\t\tinput.Region = &c.Region.Value\n\t}\n\tinput.ServiceVersion = serviceVersion\n\n\t// The following block checks for invalid permutations of the ways in\n\t// which the AccessKey + SecretKey and IAMRole flags can be\n\t// provided. This is necessary because either the AccessKey and\n\t// SecretKey or the IAMRole is required, but they are mutually\n\t// exclusive. The kingpin library lacks a way to express this constraint\n\t// via the flag specification API so we enforce it manually here.\n\tswitch {\n\tcase !c.AccessKey.WasSet && !c.SecretKey.WasSet && !c.IAMRole.WasSet:\n\t\treturn nil, fmt.Errorf(\"error parsing arguments: the --access-key and --secret-key flags or the --iam-role flag must be provided\")\n\tcase (c.AccessKey.WasSet || c.SecretKey.WasSet) && c.IAMRole.WasSet:\n\t\t// Enforce mutual exclusion\n\t\treturn nil, fmt.Errorf(\"error parsing arguments: the --access-key and --secret-key flags are mutually exclusive with the --iam-role flag\")\n\tcase c.AccessKey.WasSet && !c.SecretKey.WasSet:\n\t\treturn nil, fmt.Errorf(\"error parsing arguments: required flag --secret-key not provided\")\n\tcase !c.AccessKey.WasSet && c.SecretKey.WasSet:\n\t\treturn nil, fmt.Errorf(\"error parsing arguments: required flag --access-key not provided\")\n\t}\n\n\tif c.AccessKey.WasSet {\n\t\tinput.AccessKey = &c.AccessKey.Value\n\t}\n\n\tif c.SecretKey.WasSet {\n\t\tinput.SecretKey = &c.SecretKey.Value\n\t}\n\n\tif c.IAMRole.WasSet {\n\t\tinput.IAMRole = &c.IAMRole.Value\n\t}\n\n\tif c.Format.WasSet {\n\t\tinput.Format = fastly.ToPointer(argparser.Content(c.Format.Value))\n\t}\n\n\tif c.FormatVersion.WasSet {\n\t\tinput.FormatVersion = &c.FormatVersion.Value\n\t}\n\n\tif c.ResponseCondition.WasSet {\n\t\tinput.ResponseCondition = &c.ResponseCondition.Value\n\t}\n\n\tif c.Placement.WasSet {\n\t\tinput.Placement = &c.Placement.Value\n\t}\n\n\tif c.ProcessingRegion.WasSet {\n\t\tinput.ProcessingRegion = &c.ProcessingRegion.Value\n\t}\n\n\treturn &input, nil\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *CreateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(false),\n\t\tLocked:             optional.Of(false),\n\t\tAutoCloneFlag:      c.AutoClone,\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.ServiceName,\n\t\tServiceVersionFlag: c.ServiceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": errors.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tinput, err := c.ConstructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\td, err := c.Globals.APIClient.CreateKinesis(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\ttext.Success(\n\t\tout,\n\t\t\"Created Kinesis logging endpoint %s (service %s version %d)\",\n\t\tfastly.ToValue(d.Name),\n\t\tfastly.ToValue(d.ServiceID),\n\t\tfastly.ToValue(d.ServiceVersion),\n\t)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/kinesis/delete.go",
    "content": "package kinesis\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DeleteCommand calls the Fastly API to delete an Amazon Kinesis logging endpoint.\ntype DeleteCommand struct {\n\targparser.Base\n\tInput          fastly.DeleteKinesisInput\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n\tautoClone      argparser.OptionalAutoClone\n}\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"delete\", \"Delete a Kinesis logging endpoint on a Fastly service version\").Alias(\"remove\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"The name of the Kinesis logging object\").Short('n').Required().StringVar(&c.Input.Name)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.autoClone.Set,\n\t\tDst:    &c.autoClone.Value,\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DeleteCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(false),\n\t\tLocked:             optional.Of(false),\n\t\tAutoCloneFlag:      c.autoClone,\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": errors.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tc.Input.ServiceID = serviceID\n\tc.Input.ServiceVersion = fastly.ToValue(serviceVersion.Number)\n\n\tif err := c.Globals.APIClient.DeleteKinesis(context.TODO(), &c.Input); err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Deleted Kinesis logging endpoint %s (service %s version %d)\", c.Input.Name, c.Input.ServiceID, c.Input.ServiceVersion)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/kinesis/describe.go",
    "content": "package kinesis\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DescribeCommand calls the Fastly API to describe an Amazon Kinesis logging endpoint.\ntype DescribeCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tInput          fastly.GetKinesisInput\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n}\n\n// NewDescribeCommand returns a usable command registered under the parent.\nfunc NewDescribeCommand(parent argparser.Registerer, g *global.Data) *DescribeCommand {\n\tc := DescribeCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"describe\", \"Show detailed information about a Kinesis logging endpoint on a Fastly service version\").Alias(\"get\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"The name of the Kinesis logging object\").Short('n').Required().StringVar(&c.Input.Name)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DescribeCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fsterr.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tc.Input.ServiceID = serviceID\n\tc.Input.ServiceVersion = fastly.ToValue(serviceVersion.Number)\n\n\to, err := c.Globals.APIClient.GetKinesis(context.TODO(), &c.Input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\tlines := text.Lines{\n\t\t\"Format version\":     fastly.ToValue(o.FormatVersion),\n\t\t\"Format\":             fastly.ToValue(o.Format),\n\t\t\"Name\":               fastly.ToValue(o.Name),\n\t\t\"Placement\":          fastly.ToValue(o.Placement),\n\t\t\"Processing region\":  fastly.ToValue(o.ProcessingRegion),\n\t\t\"Region\":             fastly.ToValue(o.Region),\n\t\t\"Response condition\": fastly.ToValue(o.ResponseCondition),\n\t\t\"Stream name\":        fastly.ToValue(o.StreamName),\n\t\t\"Version\":            fastly.ToValue(o.ServiceVersion),\n\t}\n\n\tif o.AccessKey != nil || o.SecretKey != nil {\n\t\tlines[\"Access key\"] = fastly.ToValue(o.AccessKey)\n\t\tlines[\"Secret key\"] = fastly.ToValue(o.SecretKey)\n\t}\n\tif o.IAMRole != nil {\n\t\tlines[\"IAM role\"] = fastly.ToValue(o.IAMRole)\n\t}\n\tif !c.Globals.Verbose() {\n\t\tlines[\"Service ID\"] = fastly.ToValue(o.ServiceID)\n\t}\n\ttext.PrintLines(out, lines)\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/kinesis/doc.go",
    "content": "// Package kinesis contains commands to inspect and manipulate Fastly service Kinesis\n// logging endpoints.\npackage kinesis\n"
  },
  {
    "path": "pkg/commands/service/logging/kinesis/kinesis_integration_test.go",
    "content": "package kinesis_test\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/mock\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n\n\troot \"github.com/fastly/cli/pkg/commands/service\"\n\tparent \"github.com/fastly/cli/pkg/commands/service/logging\"\n\tsub \"github.com/fastly/cli/pkg/commands/service/logging/kinesis\"\n)\n\nfunc TestKinesisCreate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name log --stream-name log --region us-east-1 --secret-key bar --iam-role arn:aws:iam::123456789012:role/KinesisAccess --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t},\n\t\t\tWantError: \"error parsing arguments: the --access-key and --secret-key flags are mutually exclusive with the --iam-role flag\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name log --stream-name log --region us-east-1 --access-key foo --iam-role arn:aws:iam::123456789012:role/KinesisAccess --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t},\n\t\t\tWantError: \"error parsing arguments: the --access-key and --secret-key flags are mutually exclusive with the --iam-role flag\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name log --stream-name log --region us-east-1 --access-key foo --secret-key bar --iam-role arn:aws:iam::123456789012:role/KinesisAccess --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t},\n\t\t\tWantError: \"error parsing arguments: the --access-key and --secret-key flags are mutually exclusive with the --iam-role flag\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name log --stream-name log --access-key foo --secret-key bar --region us-east-1 --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:    testutil.GetVersion,\n\t\t\t\tCloneVersionFn:  testutil.CloneVersionResult(4),\n\t\t\t\tCreateKinesisFn: createKinesisOK,\n\t\t\t},\n\t\t\tWantOutput: \"Created Kinesis logging endpoint log (service 123 version 4)\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name log --stream-name log --access-key foo --secret-key bar --region us-east-1 --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:    testutil.GetVersion,\n\t\t\t\tCloneVersionFn:  testutil.CloneVersionResult(4),\n\t\t\t\tCreateKinesisFn: createKinesisError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name log2 --stream-name log --region us-east-1 --iam-role arn:aws:iam::123456789012:role/KinesisAccess --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:    testutil.GetVersion,\n\t\t\t\tCloneVersionFn:  testutil.CloneVersionResult(4),\n\t\t\t\tCreateKinesisFn: createKinesisOK,\n\t\t\t},\n\t\t\tWantOutput: \"Created Kinesis logging endpoint log2 (service 123 version 4)\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name log2 --stream-name log --region us-east-1 --iam-role arn:aws:iam::123456789012:role/KinesisAccess --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:    testutil.GetVersion,\n\t\t\t\tCloneVersionFn:  testutil.CloneVersionResult(4),\n\t\t\t\tCreateKinesisFn: createKinesisError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"create\"}, scenarios)\n}\n\nfunc TestKinesisList(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:  testutil.GetVersion,\n\t\t\t\tListKinesisFn: listKinesesOK,\n\t\t\t},\n\t\t\tWantOutput: listKinesesShortOutput,\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --verbose\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:  testutil.GetVersion,\n\t\t\t\tListKinesisFn: listKinesesOK,\n\t\t\t},\n\t\t\tWantOutput: listKinesesVerboseOutput,\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 -v\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:  testutil.GetVersion,\n\t\t\t\tListKinesisFn: listKinesesOK,\n\t\t\t},\n\t\t\tWantOutput: listKinesesVerboseOutput,\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:  testutil.GetVersion,\n\t\t\t\tListKinesisFn: listKinesesError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"list\"}, scenarios)\n}\n\nfunc TestKinesisDescribe(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"--service-id 123 --version 1\",\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tGetKinesisFn: getKinesisError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tGetKinesisFn: getKinesisOK,\n\t\t\t},\n\t\t\tWantOutput: describeKinesisOutput,\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"describe\"}, scenarios)\n}\n\nfunc TestKinesisUpdate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"--service-id 123 --version 1 --new-name log\",\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs --new-name log --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:    testutil.GetVersion,\n\t\t\t\tCloneVersionFn:  testutil.CloneVersionResult(4),\n\t\t\t\tUpdateKinesisFn: updateKinesisError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs --new-name log --region us-west-1 --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:    testutil.GetVersion,\n\t\t\t\tCloneVersionFn:  testutil.CloneVersionResult(4),\n\t\t\t\tUpdateKinesisFn: updateKinesisOK,\n\t\t\t},\n\t\t\tWantOutput: \"Updated Kinesis logging endpoint log (service 123 version 4)\",\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"update\"}, scenarios)\n}\n\nfunc TestKinesisDelete(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"--service-id 123 --version 1\",\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:    testutil.GetVersion,\n\t\t\t\tCloneVersionFn:  testutil.CloneVersionResult(4),\n\t\t\t\tDeleteKinesisFn: deleteKinesisError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:    testutil.GetVersion,\n\t\t\t\tCloneVersionFn:  testutil.CloneVersionResult(4),\n\t\t\t\tDeleteKinesisFn: deleteKinesisOK,\n\t\t\t},\n\t\t\tWantOutput: \"Deleted Kinesis logging endpoint logs (service 123 version 4)\",\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"delete\"}, scenarios)\n}\n\nvar errTest = errors.New(\"fixture error\")\n\nfunc createKinesisOK(_ context.Context, i *fastly.CreateKinesisInput) (*fastly.Kinesis, error) {\n\treturn &fastly.Kinesis{\n\t\tServiceID:      fastly.ToPointer(i.ServiceID),\n\t\tServiceVersion: fastly.ToPointer(i.ServiceVersion),\n\t\tName:           i.Name,\n\t}, nil\n}\n\nfunc createKinesisError(_ context.Context, _ *fastly.CreateKinesisInput) (*fastly.Kinesis, error) {\n\treturn nil, errTest\n}\n\nfunc listKinesesOK(_ context.Context, i *fastly.ListKinesisInput) ([]*fastly.Kinesis, error) {\n\treturn []*fastly.Kinesis{\n\t\t{\n\t\t\tServiceID:         fastly.ToPointer(i.ServiceID),\n\t\t\tServiceVersion:    fastly.ToPointer(i.ServiceVersion),\n\t\t\tName:              fastly.ToPointer(\"logs\"),\n\t\t\tStreamName:        fastly.ToPointer(\"my-logs\"),\n\t\t\tAccessKey:         fastly.ToPointer(\"1234\"),\n\t\t\tSecretKey:         fastly.ToPointer(\"-----BEGIN RSA PRIVATE KEY-----MIIEogIBAAKCA\"),\n\t\t\tRegion:            fastly.ToPointer(\"us-east-1\"),\n\t\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\t\tFormatVersion:     fastly.ToPointer(2),\n\t\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\t\tProcessingRegion:  fastly.ToPointer(\"us\"),\n\t\t},\n\t\t{\n\t\t\tServiceID:         fastly.ToPointer(i.ServiceID),\n\t\t\tServiceVersion:    fastly.ToPointer(i.ServiceVersion),\n\t\t\tName:              fastly.ToPointer(\"analytics\"),\n\t\t\tStreamName:        fastly.ToPointer(\"analytics\"),\n\t\t\tAccessKey:         fastly.ToPointer(\"1234\"),\n\t\t\tSecretKey:         fastly.ToPointer(\"-----BEGIN RSA PRIVATE KEY-----MIIEogIBAAKCA\"),\n\t\t\tRegion:            fastly.ToPointer(\"us-east-1\"),\n\t\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\t\tFormatVersion:     fastly.ToPointer(2),\n\t\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\t\tProcessingRegion:  fastly.ToPointer(\"us\"),\n\t\t},\n\t}, nil\n}\n\nfunc listKinesesError(_ context.Context, _ *fastly.ListKinesisInput) ([]*fastly.Kinesis, error) {\n\treturn nil, errTest\n}\n\nvar listKinesesShortOutput = strings.TrimSpace(`\nSERVICE  VERSION  NAME\n123      1        logs\n123      1        analytics\n`) + \"\\n\"\n\nvar listKinesesVerboseOutput = strings.TrimSpace(`\nFastly API endpoint: https://api.fastly.com\nFastly API token provided via config file (auth: user)\n\nService ID (via --service-id): 123\n\nVersion: 1\n\tKinesis 1/2\n\t\tService ID: 123\n\t\tVersion: 1\n\t\tName: logs\n\t\tStream name: my-logs\n\t\tRegion: us-east-1\n\t\tAccess key: 1234\n\t\tSecret key: -----BEGIN RSA PRIVATE KEY-----MIIEogIBAAKCA\n\t\tFormat: %h %l %u %t \"%r\" %>s %b\n\t\tFormat version: 2\n\t\tResponse condition: Prevent default logging\n\t\tPlacement: none\n\t\tProcessing region: us\n\tKinesis 2/2\n\t\tService ID: 123\n\t\tVersion: 1\n\t\tName: analytics\n\t\tStream name: analytics\n\t\tRegion: us-east-1\n\t\tAccess key: 1234\n\t\tSecret key: -----BEGIN RSA PRIVATE KEY-----MIIEogIBAAKCA\n\t\tFormat: %h %l %u %t \"%r\" %>s %b\n\t\tFormat version: 2\n\t\tResponse condition: Prevent default logging\n\t\tPlacement: none\n\t\tProcessing region: us\n`) + \"\\n\\n\"\n\nfunc getKinesisOK(_ context.Context, i *fastly.GetKinesisInput) (*fastly.Kinesis, error) {\n\treturn &fastly.Kinesis{\n\t\tServiceID:         fastly.ToPointer(i.ServiceID),\n\t\tServiceVersion:    fastly.ToPointer(i.ServiceVersion),\n\t\tName:              fastly.ToPointer(\"logs\"),\n\t\tStreamName:        fastly.ToPointer(\"my-logs\"),\n\t\tAccessKey:         fastly.ToPointer(\"1234\"),\n\t\tSecretKey:         fastly.ToPointer(\"-----BEGIN RSA PRIVATE KEY-----MIIEogIBAAKCA\"),\n\t\tRegion:            fastly.ToPointer(\"us-east-1\"),\n\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\tFormatVersion:     fastly.ToPointer(2),\n\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\tProcessingRegion:  fastly.ToPointer(\"us\"),\n\t}, nil\n}\n\nfunc getKinesisError(_ context.Context, _ *fastly.GetKinesisInput) (*fastly.Kinesis, error) {\n\treturn nil, errTest\n}\n\nvar describeKinesisOutput = \"\\n\" + strings.TrimSpace(`\nAccess key: 1234\nFormat: %h %l %u %t \"%r\" %>s %b\nFormat version: 2\nName: logs\nPlacement: none\nProcessing region: us\nRegion: us-east-1\nResponse condition: Prevent default logging\nSecret key: -----BEGIN RSA PRIVATE KEY-----MIIEogIBAAKCA\nService ID: 123\nStream name: my-logs\nVersion: 1\n`) + \"\\n\"\n\nfunc updateKinesisOK(_ context.Context, i *fastly.UpdateKinesisInput) (*fastly.Kinesis, error) {\n\treturn &fastly.Kinesis{\n\t\tServiceID:         fastly.ToPointer(i.ServiceID),\n\t\tServiceVersion:    fastly.ToPointer(i.ServiceVersion),\n\t\tName:              fastly.ToPointer(\"log\"),\n\t\tStreamName:        fastly.ToPointer(\"my-logs\"),\n\t\tAccessKey:         fastly.ToPointer(\"1234\"),\n\t\tSecretKey:         fastly.ToPointer(\"-----BEGIN RSA PRIVATE KEY-----MIIEogIBAAKCA\"),\n\t\tRegion:            fastly.ToPointer(\"us-west-1\"),\n\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\tFormatVersion:     fastly.ToPointer(2),\n\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t}, nil\n}\n\nfunc updateKinesisError(_ context.Context, _ *fastly.UpdateKinesisInput) (*fastly.Kinesis, error) {\n\treturn nil, errTest\n}\n\nfunc deleteKinesisOK(_ context.Context, _ *fastly.DeleteKinesisInput) error {\n\treturn nil\n}\n\nfunc deleteKinesisError(_ context.Context, _ *fastly.DeleteKinesisInput) error {\n\treturn errTest\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/kinesis/kinesis_test.go",
    "content": "package kinesis_test\n\nimport (\n\t\"bytes\"\n\t\"testing\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/service/logging/kinesis\"\n\t\"github.com/fastly/cli/pkg/config\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/manifest\"\n\t\"github.com/fastly/cli/pkg/mock\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n)\n\nfunc TestCreateKinesisInput(t *testing.T) {\n\tfor _, testcase := range []struct {\n\t\tname      string\n\t\tcmd       *kinesis.CreateCommand\n\t\twant      *fastly.CreateKinesisInput\n\t\twantError string\n\t}{\n\t\t{\n\t\t\tname: \"required values set flag serviceID\",\n\t\t\tcmd:  createCommandRequired(),\n\t\t\twant: &fastly.CreateKinesisInput{\n\t\t\t\tServiceID:      \"123\",\n\t\t\t\tServiceVersion: 4,\n\t\t\t\tName:           fastly.ToPointer(\"log\"),\n\t\t\t\tStreamName:     fastly.ToPointer(\"stream\"),\n\t\t\t\tRegion:         fastly.ToPointer(\"us-east-1\"),\n\t\t\t\tAccessKey:      fastly.ToPointer(\"access\"),\n\t\t\t\tSecretKey:      fastly.ToPointer(\"secret\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"required values set flag serviceID using IAM role\",\n\t\t\tcmd:  createCommandRequiredIAMRole(),\n\t\t\twant: &fastly.CreateKinesisInput{\n\t\t\t\tServiceID:      \"123\",\n\t\t\t\tServiceVersion: 4,\n\t\t\t\tName:           fastly.ToPointer(\"log\"),\n\t\t\t\tRegion:         fastly.ToPointer(\"us-east-1\"),\n\t\t\t\tStreamName:     fastly.ToPointer(\"stream\"),\n\t\t\t\tIAMRole:        fastly.ToPointer(\"arn:aws:iam::123456789012:role/KinesisAccess\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"all values set flag serviceID\",\n\t\t\tcmd:  createCommandAll(),\n\t\t\twant: &fastly.CreateKinesisInput{\n\t\t\t\tServiceID:         \"123\",\n\t\t\t\tServiceVersion:    4,\n\t\t\t\tName:              fastly.ToPointer(\"logs\"),\n\t\t\t\tStreamName:        fastly.ToPointer(\"stream\"),\n\t\t\t\tRegion:            fastly.ToPointer(\"us-east-1\"),\n\t\t\t\tAccessKey:         fastly.ToPointer(\"access\"),\n\t\t\t\tSecretKey:         fastly.ToPointer(\"secret\"),\n\t\t\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\t\t\tFormatVersion:     fastly.ToPointer(2),\n\t\t\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\t\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\t\t\tProcessingRegion:  fastly.ToPointer(\"eu\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:      \"error missing serviceID\",\n\t\t\tcmd:       createCommandMissingServiceID(),\n\t\t\twant:      nil,\n\t\t\twantError: errors.ErrNoServiceID.Error(),\n\t\t},\n\t} {\n\t\tt.Run(testcase.name, func(t *testing.T) {\n\t\t\tif testcase.wantError == errors.ErrNoServiceID.Error() {\n\t\t\t\tt.Setenv(\"FASTLY_SERVICE_ID\", \"\")\n\t\t\t}\n\t\t\tvar bs []byte\n\t\t\tout := bytes.NewBuffer(bs)\n\t\t\tverboseMode := true\n\n\t\t\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\t\t\tAutoCloneFlag:      testcase.cmd.AutoClone,\n\t\t\t\tAPIClient:          testcase.cmd.Globals.APIClient,\n\t\t\t\tManifest:           testcase.cmd.Manifest,\n\t\t\t\tOut:                out,\n\t\t\t\tServiceVersionFlag: testcase.cmd.ServiceVersion,\n\t\t\t\tVerboseMode:        verboseMode,\n\t\t\t})\n\n\t\t\tswitch {\n\t\t\tcase err != nil && testcase.wantError == \"\":\n\t\t\t\tt.Fatalf(\"unexpected error getting service details: %v\", err)\n\t\t\t\treturn\n\t\t\tcase err != nil && testcase.wantError != \"\":\n\t\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\t\treturn\n\t\t\tcase err == nil && testcase.wantError != \"\":\n\t\t\t\tt.Fatalf(\"expected error, have nil (service details: %s, %d)\", serviceID, serviceVersion.Number)\n\t\t\tcase err == nil && testcase.wantError == \"\":\n\t\t\t\thave, err := testcase.cmd.ConstructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\t\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\t\ttestutil.AssertEqual(t, testcase.want, have)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestUpdateKinesisInput(t *testing.T) {\n\tscenarios := []struct {\n\t\tname      string\n\t\tcmd       *kinesis.UpdateCommand\n\t\tapi       mock.API\n\t\twant      *fastly.UpdateKinesisInput\n\t\twantError string\n\t}{\n\t\t{\n\t\t\tname: \"no updates\",\n\t\t\tcmd:  updateCommandNoUpdates(),\n\t\t\tapi: mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tGetKinesisFn:   getKinesisOK,\n\t\t\t},\n\t\t\twant: &fastly.UpdateKinesisInput{\n\t\t\t\tServiceID:      \"123\",\n\t\t\t\tServiceVersion: 4,\n\t\t\t\tName:           \"log\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"all values set flag serviceID\",\n\t\t\tcmd:  updateCommandAll(),\n\t\t\tapi: mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tGetKinesisFn:   getKinesisOK,\n\t\t\t},\n\t\t\twant: &fastly.UpdateKinesisInput{\n\t\t\t\tServiceID:         \"123\",\n\t\t\t\tServiceVersion:    4,\n\t\t\t\tName:              \"log\",\n\t\t\t\tNewName:           fastly.ToPointer(\"new1\"),\n\t\t\t\tStreamName:        fastly.ToPointer(\"new2\"),\n\t\t\t\tAccessKey:         fastly.ToPointer(\"new3\"),\n\t\t\t\tSecretKey:         fastly.ToPointer(\"new4\"),\n\t\t\t\tIAMRole:           fastly.ToPointer(\"\"),\n\t\t\t\tRegion:            fastly.ToPointer(\"new5\"),\n\t\t\t\tFormat:            fastly.ToPointer(\"new7\"),\n\t\t\t\tFormatVersion:     fastly.ToPointer(3),\n\t\t\t\tResponseCondition: fastly.ToPointer(\"new9\"),\n\t\t\t\tPlacement:         fastly.ToPointer(\"new11\"),\n\t\t\t\tProcessingRegion:  fastly.ToPointer(\"eu\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:      \"error missing serviceID\",\n\t\t\tcmd:       updateCommandMissingServiceID(),\n\t\t\twant:      nil,\n\t\t\twantError: errors.ErrNoServiceID.Error(),\n\t\t},\n\t}\n\tfor testcaseIdx := range scenarios {\n\t\ttestcase := &scenarios[testcaseIdx]\n\t\tt.Run(testcase.name, func(t *testing.T) {\n\t\t\tif testcase.wantError == errors.ErrNoServiceID.Error() {\n\t\t\t\tt.Setenv(\"FASTLY_SERVICE_ID\", \"\")\n\t\t\t}\n\t\t\ttestcase.cmd.Globals.APIClient = testcase.api\n\n\t\t\tvar bs []byte\n\t\t\tout := bytes.NewBuffer(bs)\n\t\t\tverboseMode := true\n\n\t\t\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\t\t\tAutoCloneFlag:      testcase.cmd.AutoClone,\n\t\t\t\tAPIClient:          testcase.api,\n\t\t\t\tManifest:           testcase.cmd.Manifest,\n\t\t\t\tOut:                out,\n\t\t\t\tServiceVersionFlag: testcase.cmd.ServiceVersion,\n\t\t\t\tVerboseMode:        verboseMode,\n\t\t\t})\n\n\t\t\tswitch {\n\t\t\tcase err != nil && testcase.wantError == \"\":\n\t\t\t\tt.Fatalf(\"unexpected error getting service details: %v\", err)\n\t\t\t\treturn\n\t\t\tcase err != nil && testcase.wantError != \"\":\n\t\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\t\treturn\n\t\t\tcase err == nil && testcase.wantError != \"\":\n\t\t\t\tt.Fatalf(\"expected error, have nil (service details: %s, %d)\", serviceID, serviceVersion.Number)\n\t\t\tcase err == nil && testcase.wantError == \"\":\n\t\t\t\thave, err := testcase.cmd.ConstructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\t\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\t\ttestutil.AssertEqual(t, testcase.want, have)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc createCommandRequired() *kinesis.CreateCommand {\n\tvar b bytes.Buffer\n\n\tg := global.Data{\n\t\tConfig: config.File{},\n\t\tEnv:    config.Environment{},\n\t\tOutput: &b,\n\t}\n\tg.APIClient, _ = mock.APIClient(mock.API{\n\t\tGetVersionFn:   testutil.GetVersion,\n\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t})(\"token\", \"endpoint\", false)\n\n\treturn &kinesis.CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: &g,\n\t\t},\n\t\tManifest: manifest.Data{\n\t\t\tFlag: manifest.Flag{\n\t\t\t\tServiceID: \"123\",\n\t\t\t},\n\t\t},\n\t\tServiceVersion: argparser.OptionalServiceVersion{\n\t\t\tOptionalString: argparser.OptionalString{Value: \"1\"},\n\t\t},\n\t\tAutoClone: argparser.OptionalAutoClone{\n\t\t\tOptionalBool: argparser.OptionalBool{\n\t\t\t\tOptional: argparser.Optional{\n\t\t\t\t\tWasSet: true,\n\t\t\t\t},\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t},\n\t\tEndpointName: argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"log\"},\n\t\tRegion:       argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"us-east-1\"},\n\t\tStreamName:   argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"stream\"},\n\t\tAccessKey:    argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"access\"},\n\t\tSecretKey:    argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"secret\"},\n\t}\n}\n\nfunc createCommandRequiredIAMRole() *kinesis.CreateCommand {\n\tvar b bytes.Buffer\n\n\tg := global.Data{\n\t\tConfig: config.File{},\n\t\tEnv:    config.Environment{},\n\t\tOutput: &b,\n\t}\n\tg.APIClient, _ = mock.APIClient(mock.API{\n\t\tGetVersionFn:   testutil.GetVersion,\n\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t})(\"token\", \"endpoint\", false)\n\n\treturn &kinesis.CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: &g,\n\t\t},\n\t\tManifest: manifest.Data{\n\t\t\tFlag: manifest.Flag{\n\t\t\t\tServiceID: \"123\",\n\t\t\t},\n\t\t},\n\t\tServiceVersion: argparser.OptionalServiceVersion{\n\t\t\tOptionalString: argparser.OptionalString{Value: \"1\"},\n\t\t},\n\t\tEndpointName: argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"log\"},\n\t\tRegion:       argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"us-east-1\"},\n\t\tStreamName:   argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"stream\"},\n\t\tAutoClone: argparser.OptionalAutoClone{\n\t\t\tOptionalBool: argparser.OptionalBool{\n\t\t\t\tOptional: argparser.Optional{\n\t\t\t\t\tWasSet: true,\n\t\t\t\t},\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t},\n\t\tIAMRole: argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"arn:aws:iam::123456789012:role/KinesisAccess\"},\n\t}\n}\n\nfunc createCommandAll() *kinesis.CreateCommand {\n\tvar b bytes.Buffer\n\n\tg := global.Data{\n\t\tConfig: config.File{},\n\t\tEnv:    config.Environment{},\n\t\tOutput: &b,\n\t}\n\tg.APIClient, _ = mock.APIClient(mock.API{\n\t\tGetVersionFn:   testutil.GetVersion,\n\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t})(\"token\", \"endpoint\", false)\n\n\treturn &kinesis.CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: &g,\n\t\t},\n\t\tManifest: manifest.Data{\n\t\t\tFlag: manifest.Flag{\n\t\t\t\tServiceID: \"123\",\n\t\t\t},\n\t\t},\n\t\tServiceVersion: argparser.OptionalServiceVersion{\n\t\t\tOptionalString: argparser.OptionalString{Value: \"1\"},\n\t\t},\n\t\tAutoClone: argparser.OptionalAutoClone{\n\t\t\tOptionalBool: argparser.OptionalBool{\n\t\t\t\tOptional: argparser.Optional{\n\t\t\t\t\tWasSet: true,\n\t\t\t\t},\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t},\n\t\tEndpointName:      argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"logs\"},\n\t\tStreamName:        argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"stream\"},\n\t\tRegion:            argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"us-east-1\"},\n\t\tAccessKey:         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"access\"},\n\t\tSecretKey:         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"secret\"},\n\t\tFormat:            argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: `%h %l %u %t \"%r\" %>s %b`},\n\t\tFormatVersion:     argparser.OptionalInt{Optional: argparser.Optional{WasSet: true}, Value: 2},\n\t\tResponseCondition: argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"Prevent default logging\"},\n\t\tPlacement:         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"none\"},\n\t\tProcessingRegion:  argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"eu\"},\n\t}\n}\n\nfunc createCommandMissingServiceID() *kinesis.CreateCommand {\n\tres := createCommandAll()\n\tres.Manifest = manifest.Data{}\n\tres.ServiceVersion = argparser.OptionalServiceVersion{}\n\treturn res\n}\n\nfunc updateCommandNoUpdates() *kinesis.UpdateCommand {\n\tvar b bytes.Buffer\n\n\tg := global.Data{\n\t\tConfig: config.File{},\n\t\tEnv:    config.Environment{},\n\t\tOutput: &b,\n\t}\n\n\treturn &kinesis.UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: &g,\n\t\t},\n\t\tManifest: manifest.Data{\n\t\t\tFlag: manifest.Flag{\n\t\t\t\tServiceID: \"123\",\n\t\t\t},\n\t\t},\n\t\tEndpointName: \"log\",\n\t\tServiceVersion: argparser.OptionalServiceVersion{\n\t\t\tOptionalString: argparser.OptionalString{Value: \"1\"},\n\t\t},\n\t\tAutoClone: argparser.OptionalAutoClone{\n\t\t\tOptionalBool: argparser.OptionalBool{\n\t\t\t\tOptional: argparser.Optional{\n\t\t\t\t\tWasSet: true,\n\t\t\t\t},\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t},\n\t}\n}\n\nfunc updateCommandAll() *kinesis.UpdateCommand {\n\tvar b bytes.Buffer\n\n\tg := global.Data{\n\t\tConfig: config.File{},\n\t\tEnv:    config.Environment{},\n\t\tOutput: &b,\n\t}\n\n\treturn &kinesis.UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: &g,\n\t\t},\n\t\tManifest: manifest.Data{\n\t\t\tFlag: manifest.Flag{\n\t\t\t\tServiceID: \"123\",\n\t\t\t},\n\t\t},\n\t\tEndpointName: \"log\",\n\t\tServiceVersion: argparser.OptionalServiceVersion{\n\t\t\tOptionalString: argparser.OptionalString{Value: \"1\"},\n\t\t},\n\t\tAutoClone: argparser.OptionalAutoClone{\n\t\t\tOptionalBool: argparser.OptionalBool{\n\t\t\t\tOptional: argparser.Optional{\n\t\t\t\t\tWasSet: true,\n\t\t\t\t},\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t},\n\t\tNewName:           argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new1\"},\n\t\tStreamName:        argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new2\"},\n\t\tAccessKey:         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new3\"},\n\t\tSecretKey:         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new4\"},\n\t\tIAMRole:           argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"\"},\n\t\tRegion:            argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new5\"},\n\t\tFormat:            argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new7\"},\n\t\tFormatVersion:     argparser.OptionalInt{Optional: argparser.Optional{WasSet: true}, Value: 3},\n\t\tResponseCondition: argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new9\"},\n\t\tPlacement:         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new11\"},\n\t\tProcessingRegion:  argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"eu\"},\n\t}\n}\n\nfunc updateCommandMissingServiceID() *kinesis.UpdateCommand {\n\tres := updateCommandAll()\n\tres.Manifest = manifest.Data{}\n\tres.ServiceVersion = argparser.OptionalServiceVersion{}\n\treturn res\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/kinesis/list.go",
    "content": "package kinesis\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// ListCommand calls the Fastly API to list Amazon Kinesis logging endpoints.\ntype ListCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tInput          fastly.ListKinesisInput\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n}\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"list\", \"List Kinesis endpoints on a Fastly service version\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *ListCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fsterr.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tc.Input.ServiceID = serviceID\n\tc.Input.ServiceVersion = fastly.ToValue(serviceVersion.Number)\n\n\to, err := c.Globals.APIClient.ListKinesis(context.TODO(), &c.Input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\tif !c.Globals.Verbose() {\n\t\ttw := text.NewTable(out)\n\t\ttw.AddHeader(\"SERVICE\", \"VERSION\", \"NAME\")\n\t\tfor _, kinesis := range o {\n\t\t\ttw.AddLine(\n\t\t\t\tfastly.ToValue(kinesis.ServiceID),\n\t\t\t\tfastly.ToValue(kinesis.ServiceVersion),\n\t\t\t\tfastly.ToValue(kinesis.Name),\n\t\t\t)\n\t\t}\n\t\ttw.Print()\n\t\treturn nil\n\t}\n\n\tfmt.Fprintf(out, \"Version: %d\\n\", c.Input.ServiceVersion)\n\tfor i, kinesis := range o {\n\t\tfmt.Fprintf(out, \"\\tKinesis %d/%d\\n\", i+1, len(o))\n\t\tfmt.Fprintf(out, \"\\t\\tService ID: %s\\n\", fastly.ToValue(kinesis.ServiceID))\n\t\tfmt.Fprintf(out, \"\\t\\tVersion: %d\\n\", fastly.ToValue(kinesis.ServiceVersion))\n\t\tfmt.Fprintf(out, \"\\t\\tName: %s\\n\", fastly.ToValue(kinesis.Name))\n\t\tfmt.Fprintf(out, \"\\t\\tStream name: %s\\n\", fastly.ToValue(kinesis.StreamName))\n\t\tfmt.Fprintf(out, \"\\t\\tRegion: %s\\n\", fastly.ToValue(kinesis.Region))\n\t\tif kinesis.AccessKey != nil || kinesis.SecretKey != nil {\n\t\t\tfmt.Fprintf(out, \"\\t\\tAccess key: %s\\n\", fastly.ToValue(kinesis.AccessKey))\n\t\t\tfmt.Fprintf(out, \"\\t\\tSecret key: %s\\n\", fastly.ToValue(kinesis.SecretKey))\n\t\t}\n\t\tif kinesis.IAMRole != nil {\n\t\t\tfmt.Fprintf(out, \"\\t\\tIAM role: %s\\n\", fastly.ToValue(kinesis.IAMRole))\n\t\t}\n\t\tfmt.Fprintf(out, \"\\t\\tFormat: %s\\n\", fastly.ToValue(kinesis.Format))\n\t\tfmt.Fprintf(out, \"\\t\\tFormat version: %d\\n\", fastly.ToValue(kinesis.FormatVersion))\n\t\tfmt.Fprintf(out, \"\\t\\tResponse condition: %s\\n\", fastly.ToValue(kinesis.ResponseCondition))\n\t\tfmt.Fprintf(out, \"\\t\\tPlacement: %s\\n\", fastly.ToValue(kinesis.Placement))\n\t\tfmt.Fprintf(out, \"\\t\\tProcessing region: %s\\n\", fastly.ToValue(kinesis.ProcessingRegion))\n\t}\n\tfmt.Fprintln(out)\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/kinesis/root.go",
    "content": "package kinesis\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"kinesis\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manipulate a Kinesis logging endpoint for a specific Fastly service version\")\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/kinesis/update.go",
    "content": "package kinesis\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/service/logging/logflags\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/manifest\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// UpdateCommand calls the Fastly API to update an Amazon Kinesis logging endpoint.\ntype UpdateCommand struct {\n\targparser.Base\n\tManifest manifest.Data\n\n\t// Required.\n\tEndpointName   string // Can't shadow argparser.Base method Name().\n\tServiceName    argparser.OptionalServiceNameID\n\tServiceVersion argparser.OptionalServiceVersion\n\n\t// Optional.\n\tAccessKey         argparser.OptionalString\n\tAutoClone         argparser.OptionalAutoClone\n\tFormat            argparser.OptionalString\n\tFormatVersion     argparser.OptionalInt\n\tIAMRole           argparser.OptionalString\n\tNewName           argparser.OptionalString\n\tPlacement         argparser.OptionalString\n\tProcessingRegion  argparser.OptionalString\n\tRegion            argparser.OptionalString\n\tResponseCondition argparser.OptionalString\n\tSecretKey         argparser.OptionalString\n\tStreamName        argparser.OptionalString\n}\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"update\", \"Update a Kinesis logging endpoint on a Fastly service version\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"The name of the Kinesis logging object\").Short('n').Required().StringVar(&c.EndpointName)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.ServiceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.AutoClone.Set,\n\t\tDst:    &c.AutoClone.Value,\n\t})\n\tc.CmdClause.Flag(\"access-key\", \"Your Kinesis account access key\").Action(c.AccessKey.Set).StringVar(&c.AccessKey.Value)\n\tlogflags.Format(c.CmdClause, &c.Format)\n\tlogflags.FormatVersion(c.CmdClause, &c.FormatVersion)\n\tc.CmdClause.Flag(\"iam-role\", \"The IAM role ARN for logging\").Action(c.IAMRole.Set).StringVar(&c.IAMRole.Value)\n\tc.CmdClause.Flag(\"new-name\", \"New name of the Kinesis logging object\").Action(c.NewName.Set).StringVar(&c.NewName.Value)\n\tlogflags.Placement(c.CmdClause, &c.Placement)\n\tlogflags.ProcessingRegion(c.CmdClause, &c.ProcessingRegion, \"Kinesis\")\n\tc.CmdClause.Flag(\"region\", \"The region where logs are received and stored by Kinesis\").Action(c.Region.Set).StringVar(&c.Region.Value)\n\tlogflags.ResponseCondition(c.CmdClause, &c.ResponseCondition)\n\tc.CmdClause.Flag(\"secret-key\", \"Your Kinesis account secret key\").Action(c.SecretKey.Set).StringVar(&c.SecretKey.Value)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.ServiceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.ServiceName.Value,\n\t})\n\tc.CmdClause.Flag(\"stream-name\", \"Your Kinesis stream name\").Action(c.StreamName.Set).StringVar(&c.StreamName.Value)\n\treturn &c\n}\n\n// ConstructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *UpdateCommand) ConstructInput(serviceID string, serviceVersion int) (*fastly.UpdateKinesisInput, error) {\n\tinput := fastly.UpdateKinesisInput{\n\t\tServiceID:      serviceID,\n\t\tServiceVersion: serviceVersion,\n\t\tName:           c.EndpointName,\n\t}\n\n\tif c.NewName.WasSet {\n\t\tinput.NewName = &c.NewName.Value\n\t}\n\n\tif c.StreamName.WasSet {\n\t\tinput.StreamName = &c.StreamName.Value\n\t}\n\n\tif c.AccessKey.WasSet {\n\t\tinput.AccessKey = &c.AccessKey.Value\n\t}\n\n\tif c.SecretKey.WasSet {\n\t\tinput.SecretKey = &c.SecretKey.Value\n\t}\n\n\tif c.IAMRole.WasSet {\n\t\tinput.IAMRole = &c.IAMRole.Value\n\t}\n\n\tif c.Region.WasSet {\n\t\tinput.Region = &c.Region.Value\n\t}\n\n\tif c.Format.WasSet {\n\t\tinput.Format = fastly.ToPointer(argparser.Content(c.Format.Value))\n\t}\n\n\tif c.FormatVersion.WasSet {\n\t\tinput.FormatVersion = &c.FormatVersion.Value\n\t}\n\n\tif c.ResponseCondition.WasSet {\n\t\tinput.ResponseCondition = &c.ResponseCondition.Value\n\t}\n\n\tif c.Placement.WasSet {\n\t\tinput.Placement = &c.Placement.Value\n\t}\n\n\tif c.ProcessingRegion.WasSet {\n\t\tinput.ProcessingRegion = &c.ProcessingRegion.Value\n\t}\n\n\treturn &input, nil\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *UpdateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(false),\n\t\tLocked:             optional.Of(false),\n\t\tAutoCloneFlag:      c.AutoClone,\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.ServiceName,\n\t\tServiceVersionFlag: c.ServiceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": errors.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tinput, err := c.ConstructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tkinesis, err := c.Globals.APIClient.UpdateKinesis(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\ttext.Success(\n\t\tout,\n\t\t\"Updated Kinesis logging endpoint %s (service %s version %d)\",\n\t\tfastly.ToValue(kinesis.Name),\n\t\tfastly.ToValue(kinesis.ServiceID),\n\t\tfastly.ToValue(kinesis.ServiceVersion),\n\t)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/logflags/doc.go",
    "content": "// Package logflags contains common flags used in the logging commands\npackage logflags\n"
  },
  {
    "path": "pkg/commands/service/logging/logflags/flags.go",
    "content": "package logflags\n\nimport (\n\t\"github.com/fastly/kingpin\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n)\n\n// AccountName defines the account-name flag.\nfunc AccountName(command *kingpin.CmdClause, c *argparser.OptionalString) {\n\tcommand.Flag(\"account-name\", \"The google account name used to obtain temporary credentials (default none)\").Action(c.Set).StringVar(&c.Value)\n}\n\n// Format defines the format flag.\nfunc Format(command *kingpin.CmdClause, c *argparser.OptionalString) {\n\tcommand.Flag(\"format\", \"Apache style log formatting. Your log must produce valid JSON. Can be a string or a file path to a file containing formatting\").Action(c.Set).StringVar(&c.Value)\n}\n\n// GzipLevel defines the gzip flag.\nfunc GzipLevel(command *kingpin.CmdClause, c *argparser.OptionalInt) {\n\tcommand.Flag(\"gzip-level\", \"What level of GZIP encoding to have when dumping logs (default 0, no compression)\").Action(c.Set).IntVar(&c.Value)\n}\n\n// Path defines the path flag.\nfunc Path(command *kingpin.CmdClause, c *argparser.OptionalString) {\n\tcommand.Flag(\"path\", \"The path to upload logs to\").Action(c.Set).StringVar(&c.Value)\n}\n\n// MessageType defines the path flag.\nfunc MessageType(command *kingpin.CmdClause, c *argparser.OptionalString) {\n\tcommand.Flag(\"message-type\", \"How the message should be formatted. One of: classic (default), loggly, logplex or blank\").Action(c.Set).StringVar(&c.Value)\n}\n\n// Period defines the period flag.\nfunc Period(command *kingpin.CmdClause, c *argparser.OptionalInt) {\n\tcommand.Flag(\"period\", \"How frequently log files are finalized so they can be available for reading (in seconds, default 3600)\").Action(c.Set).IntVar(&c.Value)\n}\n\n// FormatVersion defines the format-version flag.\nfunc FormatVersion(command *kingpin.CmdClause, c *argparser.OptionalInt) {\n\tcommand.Flag(\"format-version\", \"The version of the custom logging format used for the configured endpoint. Can be either 2 (the default, version 2 log format) or 1 (the version 1 log format). The logging call gets placed by default in vcl_log if format_version is set to 2 and in vcl_deliver if format_version is set to 1\").Action(c.Set).IntVar(&c.Value)\n}\n\n// CompressionCodec defines the compression-codec flag.\nfunc CompressionCodec(command *kingpin.CmdClause, c *argparser.OptionalString) {\n\tcommand.Flag(\"compression-codec\", `The codec used for compression of your logs. Valid values are zstd, snappy, and gzip. If the specified codec is \"gzip\", gzip_level will default to 3. To specify a different level, leave compression_codec blank and explicitly set the level using gzip_level. Specifying both compression_codec and gzip_level in the same API request will result in an error.`).Action(c.Set).StringVar(&c.Value)\n}\n\n// Placement defines the placement flag.\nfunc Placement(command *kingpin.CmdClause, c *argparser.OptionalString) {\n\tcommand.Flag(\"placement\", \"Where in the generated VCL the logging call should be placed, overriding any format_version default. Can be none or waf_debug. This field is not required and has no default value\").Action(c.Set).StringVar(&c.Value)\n}\n\n// ProcessingRegion defines the processing-region flag.\nfunc ProcessingRegion(command *kingpin.CmdClause, c *argparser.OptionalString, endpoint string) {\n\tcommand.Flag(\"processing-region\", \"The region where logs will be processed before streaming to \"+endpoint+\". One of 'none', 'eu', or 'us'. Defaults to 'none' for no specific region\").Action(c.Set).StringVar(&c.Value)\n}\n\n// ResponseCondition defines the response-condition flag.\nfunc ResponseCondition(command *kingpin.CmdClause, c *argparser.OptionalString) {\n\tcommand.Flag(\"response-condition\", \"The name of an existing condition in the configured endpoint, or leave blank to always execute\").Action(c.Set).StringVar(&c.Value)\n}\n\n// TimestampFormat defines the timestamp-format flag.\nfunc TimestampFormat(command *kingpin.CmdClause, c *argparser.OptionalString) {\n\tcommand.Flag(\"timestamp-format\", `strftime specified timestamp formatting (default \"%Y-%m-%dT%H:%M:%S.000\")`).Action(c.Set).StringVar(&c.Value)\n}\n\n// PublicKey defines the public-key flag.\nfunc PublicKey(command *kingpin.CmdClause, c *argparser.OptionalString) {\n\tcommand.Flag(\"public-key\", \"A PGP public key that Fastly will use to encrypt your log files before writing them to disk\").Action(c.Set).StringVar(&c.Value)\n}\n\n// TLSCACert defines the tls-ca-cert flag.\nfunc TLSCACert(command *kingpin.CmdClause, c *argparser.OptionalString) {\n\tcommand.Flag(\"tls-ca-cert\", \"A secure certificate to authenticate the server with. Must be in PEM format\").Action(c.Set).StringVar(&c.Value)\n}\n\n// TLSHostname defines the tls-hostname flag.\nfunc TLSHostname(command *kingpin.CmdClause, c *argparser.OptionalString) {\n\tcommand.Flag(\"tls-hostname\", \"Used during the TLS handshake to validate the certificate\").Action(c.Set).StringVar(&c.Value)\n}\n\n// TLSClientCert defines the tls-client-cert flag.\nfunc TLSClientCert(command *kingpin.CmdClause, c *argparser.OptionalString) {\n\tcommand.Flag(\"tls-client-cert\", \"The client certificate used to make authenticated requests. Must be in PEM format\").Action(c.Set).StringVar(&c.Value)\n}\n\n// TLSClientKey defines the tls-client-key flag.\nfunc TLSClientKey(command *kingpin.CmdClause, c *argparser.OptionalString) {\n\tcommand.Flag(\"tls-client-key\", \"The client private key used to make authenticated requests. Must be in PEM format\").Action(c.Set).StringVar(&c.Value)\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/loggly/create.go",
    "content": "package loggly\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/service/logging/logflags\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/manifest\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// CreateCommand calls the Fastly API to create a Loggly logging endpoint.\ntype CreateCommand struct {\n\targparser.Base\n\tManifest manifest.Data\n\n\t// Required.\n\tServiceName    argparser.OptionalServiceNameID\n\tServiceVersion argparser.OptionalServiceVersion\n\n\t// Optional.\n\tAutoClone         argparser.OptionalAutoClone\n\tEndpointName      argparser.OptionalString // Can't shadow argparser.Base method Name().\n\tFormat            argparser.OptionalString\n\tFormatVersion     argparser.OptionalInt\n\tPlacement         argparser.OptionalString\n\tProcessingRegion  argparser.OptionalString\n\tResponseCondition argparser.OptionalString\n\tToken             argparser.OptionalString\n}\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"create\", \"Create a Loggly logging endpoint on a Fastly service version\").Alias(\"add\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"The name of the Loggly logging object. Used as a primary key for API access\").Short('n').Action(c.EndpointName.Set).StringVar(&c.EndpointName.Value)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.ServiceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.CmdClause.Flag(\"auth-token\", \"The token to use for authentication (https://www.loggly.com/docs/customer-token-authentication-token/)\").Action(c.Token.Set).StringVar(&c.Token.Value)\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.AutoClone.Set,\n\t\tDst:    &c.AutoClone.Value,\n\t})\n\tlogflags.Format(c.CmdClause, &c.Format)\n\tlogflags.FormatVersion(c.CmdClause, &c.FormatVersion)\n\tlogflags.ResponseCondition(c.CmdClause, &c.ResponseCondition)\n\tlogflags.Placement(c.CmdClause, &c.Placement)\n\tlogflags.ProcessingRegion(c.CmdClause, &c.ProcessingRegion, \"Loggly\")\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.ServiceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.ServiceName.Value,\n\t})\n\treturn &c\n}\n\n// ConstructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *CreateCommand) ConstructInput(serviceID string, serviceVersion int) (*fastly.CreateLogglyInput, error) {\n\tvar input fastly.CreateLogglyInput\n\n\tinput.ServiceID = serviceID\n\tinput.ServiceVersion = serviceVersion\n\tif c.EndpointName.WasSet {\n\t\tinput.Name = &c.EndpointName.Value\n\t}\n\tif c.Token.WasSet {\n\t\tinput.Token = &c.Token.Value\n\t}\n\n\tif c.Format.WasSet {\n\t\tinput.Format = fastly.ToPointer(argparser.Content(c.Format.Value))\n\t}\n\n\tif c.FormatVersion.WasSet {\n\t\tinput.FormatVersion = &c.FormatVersion.Value\n\t}\n\n\tif c.ResponseCondition.WasSet {\n\t\tinput.ResponseCondition = &c.ResponseCondition.Value\n\t}\n\n\tif c.Placement.WasSet {\n\t\tinput.Placement = &c.Placement.Value\n\t}\n\n\tif c.ProcessingRegion.WasSet {\n\t\tinput.ProcessingRegion = &c.ProcessingRegion.Value\n\t}\n\n\treturn &input, nil\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *CreateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(false),\n\t\tLocked:             optional.Of(false),\n\t\tAutoCloneFlag:      c.AutoClone,\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.ServiceName,\n\t\tServiceVersionFlag: c.ServiceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": errors.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tinput, err := c.ConstructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\td, err := c.Globals.APIClient.CreateLoggly(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\ttext.Success(\n\t\tout,\n\t\t\"Created Loggly logging endpoint %s (service %s version %d)\",\n\t\tfastly.ToValue(d.Name),\n\t\tfastly.ToValue(d.ServiceID),\n\t\tfastly.ToValue(d.ServiceVersion),\n\t)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/loggly/delete.go",
    "content": "package loggly\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DeleteCommand calls the Fastly API to delete a Loggly logging endpoint.\ntype DeleteCommand struct {\n\targparser.Base\n\tInput          fastly.DeleteLogglyInput\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n\tautoClone      argparser.OptionalAutoClone\n}\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"delete\", \"Delete a Loggly logging endpoint on a Fastly service version\").Alias(\"remove\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"The name of the Loggly logging object\").Short('n').Required().StringVar(&c.Input.Name)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.autoClone.Set,\n\t\tDst:    &c.autoClone.Value,\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DeleteCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(false),\n\t\tLocked:             optional.Of(false),\n\t\tAutoCloneFlag:      c.autoClone,\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": errors.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tc.Input.ServiceID = serviceID\n\tc.Input.ServiceVersion = fastly.ToValue(serviceVersion.Number)\n\n\tif err := c.Globals.APIClient.DeleteLoggly(context.TODO(), &c.Input); err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Deleted Loggly logging endpoint %s (service %s version %d)\", c.Input.Name, c.Input.ServiceID, c.Input.ServiceVersion)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/loggly/describe.go",
    "content": "package loggly\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DescribeCommand calls the Fastly API to describe a Loggly logging endpoint.\ntype DescribeCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tInput          fastly.GetLogglyInput\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n}\n\n// NewDescribeCommand returns a usable command registered under the parent.\nfunc NewDescribeCommand(parent argparser.Registerer, g *global.Data) *DescribeCommand {\n\tc := DescribeCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"describe\", \"Show detailed information about a Loggly logging endpoint on a Fastly service version\").Alias(\"get\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"The name of the Loggly logging object\").Short('n').Required().StringVar(&c.Input.Name)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DescribeCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fsterr.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tc.Input.ServiceID = serviceID\n\tc.Input.ServiceVersion = fastly.ToValue(serviceVersion.Number)\n\n\to, err := c.Globals.APIClient.GetLoggly(context.TODO(), &c.Input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\tlines := text.Lines{\n\t\t\"Format version\":     fastly.ToValue(o.FormatVersion),\n\t\t\"Format\":             fastly.ToValue(o.Format),\n\t\t\"Name\":               fastly.ToValue(o.Name),\n\t\t\"Placement\":          fastly.ToValue(o.Placement),\n\t\t\"Processing region\":  fastly.ToValue(o.ProcessingRegion),\n\t\t\"Response condition\": fastly.ToValue(o.ResponseCondition),\n\t\t\"Token\":              fastly.ToValue(o.Token),\n\t\t\"Version\":            fastly.ToValue(o.ServiceVersion),\n\t}\n\tif !c.Globals.Verbose() {\n\t\tlines[\"Service ID\"] = fastly.ToValue(o.ServiceID)\n\t}\n\ttext.PrintLines(out, lines)\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/loggly/doc.go",
    "content": "// Package loggly contains commands to inspect and manipulate Fastly service Loggly\n// logging endpoints.\npackage loggly\n"
  },
  {
    "path": "pkg/commands/service/logging/loggly/list.go",
    "content": "package loggly\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// ListCommand calls the Fastly API to list Loggly logging endpoints.\ntype ListCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tInput          fastly.ListLogglyInput\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n}\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"list\", \"List Loggly endpoints on a Fastly service version\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *ListCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fsterr.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tc.Input.ServiceID = serviceID\n\tc.Input.ServiceVersion = fastly.ToValue(serviceVersion.Number)\n\n\to, err := c.Globals.APIClient.ListLoggly(context.TODO(), &c.Input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\tif !c.Globals.Verbose() {\n\t\ttw := text.NewTable(out)\n\t\ttw.AddHeader(\"SERVICE\", \"VERSION\", \"NAME\")\n\t\tfor _, loggly := range o {\n\t\t\ttw.AddLine(\n\t\t\t\tfastly.ToValue(loggly.ServiceID),\n\t\t\t\tfastly.ToValue(loggly.ServiceVersion),\n\t\t\t\tfastly.ToValue(loggly.Name),\n\t\t\t)\n\t\t}\n\t\ttw.Print()\n\t\treturn nil\n\t}\n\n\tfmt.Fprintf(out, \"Version: %d\\n\", c.Input.ServiceVersion)\n\tfor i, loggly := range o {\n\t\tfmt.Fprintf(out, \"\\tLoggly %d/%d\\n\", i+1, len(o))\n\t\tfmt.Fprintf(out, \"\\t\\tService ID: %s\\n\", fastly.ToValue(loggly.ServiceID))\n\t\tfmt.Fprintf(out, \"\\t\\tVersion: %d\\n\", fastly.ToValue(loggly.ServiceVersion))\n\t\tfmt.Fprintf(out, \"\\t\\tName: %s\\n\", fastly.ToValue(loggly.Name))\n\t\tfmt.Fprintf(out, \"\\t\\tToken: %s\\n\", fastly.ToValue(loggly.Token))\n\t\tfmt.Fprintf(out, \"\\t\\tFormat: %s\\n\", fastly.ToValue(loggly.Format))\n\t\tfmt.Fprintf(out, \"\\t\\tFormat version: %d\\n\", fastly.ToValue(loggly.FormatVersion))\n\t\tfmt.Fprintf(out, \"\\t\\tResponse condition: %s\\n\", fastly.ToValue(loggly.ResponseCondition))\n\t\tfmt.Fprintf(out, \"\\t\\tPlacement: %s\\n\", fastly.ToValue(loggly.Placement))\n\t\tfmt.Fprintf(out, \"\\t\\tProcessing region: %s\\n\", fastly.ToValue(loggly.ProcessingRegion))\n\t}\n\tfmt.Fprintln(out)\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/loggly/loggly_integration_test.go",
    "content": "package loggly_test\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/mock\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n\n\troot \"github.com/fastly/cli/pkg/commands/service\"\n\tparent \"github.com/fastly/cli/pkg/commands/service/logging\"\n\tsub \"github.com/fastly/cli/pkg/commands/service/logging/loggly\"\n)\n\nfunc TestLogglyCreate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name log --auth-token abc --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tCreateLogglyFn: createLogglyOK,\n\t\t\t},\n\t\t\tWantOutput: \"Created Loggly logging endpoint log (service 123 version 4)\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name log --auth-token abc --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tCreateLogglyFn: createLogglyError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"create\"}, scenarios)\n}\n\nfunc TestLogglyList(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tListLogglyFn: listLogglysOK,\n\t\t\t},\n\t\t\tWantOutput: listLogglysShortOutput,\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --verbose\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tListLogglyFn: listLogglysOK,\n\t\t\t},\n\t\t\tWantOutput: listLogglysVerboseOutput,\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 -v\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tListLogglyFn: listLogglysOK,\n\t\t\t},\n\t\t\tWantOutput: listLogglysVerboseOutput,\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tListLogglyFn: listLogglysError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"list\"}, scenarios)\n}\n\nfunc TestLogglyDescribe(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"--service-id 123 --version 1\",\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tGetLogglyFn:  getLogglyError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tGetLogglyFn:  getLogglyOK,\n\t\t\t},\n\t\t\tWantOutput: describeLogglyOutput,\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"describe\"}, scenarios)\n}\n\nfunc TestLogglyUpdate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"--service-id 123 --version 1 --new-name log\",\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs --new-name log --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tUpdateLogglyFn: updateLogglyError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs --new-name log --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tUpdateLogglyFn: updateLogglyOK,\n\t\t\t},\n\t\t\tWantOutput: \"Updated Loggly logging endpoint log (service 123 version 4)\",\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"update\"}, scenarios)\n}\n\nfunc TestLogglyDelete(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"--service-id 123 --version 1\",\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tDeleteLogglyFn: deleteLogglyError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tDeleteLogglyFn: deleteLogglyOK,\n\t\t\t},\n\t\t\tWantOutput: \"Deleted Loggly logging endpoint logs (service 123 version 4)\",\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"delete\"}, scenarios)\n}\n\nvar errTest = errors.New(\"fixture error\")\n\nfunc createLogglyOK(_ context.Context, i *fastly.CreateLogglyInput) (*fastly.Loggly, error) {\n\ts := fastly.Loggly{\n\t\tServiceID:      fastly.ToPointer(i.ServiceID),\n\t\tServiceVersion: fastly.ToPointer(i.ServiceVersion),\n\t}\n\n\tif i.Name != nil {\n\t\ts.Name = i.Name\n\t}\n\n\treturn &s, nil\n}\n\nfunc createLogglyError(_ context.Context, _ *fastly.CreateLogglyInput) (*fastly.Loggly, error) {\n\treturn nil, errTest\n}\n\nfunc listLogglysOK(_ context.Context, i *fastly.ListLogglyInput) ([]*fastly.Loggly, error) {\n\treturn []*fastly.Loggly{\n\t\t{\n\t\t\tServiceID:         fastly.ToPointer(i.ServiceID),\n\t\t\tServiceVersion:    fastly.ToPointer(i.ServiceVersion),\n\t\t\tName:              fastly.ToPointer(\"logs\"),\n\t\t\tToken:             fastly.ToPointer(\"abc\"),\n\t\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\t\tFormatVersion:     fastly.ToPointer(2),\n\t\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\t\tProcessingRegion:  fastly.ToPointer(\"us\"),\n\t\t},\n\t\t{\n\t\t\tServiceID:         fastly.ToPointer(i.ServiceID),\n\t\t\tServiceVersion:    fastly.ToPointer(i.ServiceVersion),\n\t\t\tName:              fastly.ToPointer(\"analytics\"),\n\t\t\tToken:             fastly.ToPointer(\"abc\"),\n\t\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\t\tFormatVersion:     fastly.ToPointer(2),\n\t\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\t\tProcessingRegion:  fastly.ToPointer(\"us\"),\n\t\t},\n\t}, nil\n}\n\nfunc listLogglysError(_ context.Context, _ *fastly.ListLogglyInput) ([]*fastly.Loggly, error) {\n\treturn nil, errTest\n}\n\nvar listLogglysShortOutput = strings.TrimSpace(`\nSERVICE  VERSION  NAME\n123      1        logs\n123      1        analytics\n`) + \"\\n\"\n\nvar listLogglysVerboseOutput = strings.TrimSpace(`\nFastly API endpoint: https://api.fastly.com\nFastly API token provided via config file (auth: user)\n\nService ID (via --service-id): 123\n\nVersion: 1\n\tLoggly 1/2\n\t\tService ID: 123\n\t\tVersion: 1\n\t\tName: logs\n\t\tToken: abc\n\t\tFormat: %h %l %u %t \"%r\" %>s %b\n\t\tFormat version: 2\n\t\tResponse condition: Prevent default logging\n\t\tPlacement: none\n\t\tProcessing region: us\n\tLoggly 2/2\n\t\tService ID: 123\n\t\tVersion: 1\n\t\tName: analytics\n\t\tToken: abc\n\t\tFormat: %h %l %u %t \"%r\" %>s %b\n\t\tFormat version: 2\n\t\tResponse condition: Prevent default logging\n\t\tPlacement: none\n\t\tProcessing region: us\n`) + \"\\n\\n\"\n\nfunc getLogglyOK(_ context.Context, i *fastly.GetLogglyInput) (*fastly.Loggly, error) {\n\treturn &fastly.Loggly{\n\t\tServiceID:         fastly.ToPointer(i.ServiceID),\n\t\tServiceVersion:    fastly.ToPointer(i.ServiceVersion),\n\t\tName:              fastly.ToPointer(\"logs\"),\n\t\tToken:             fastly.ToPointer(\"abc\"),\n\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\tFormatVersion:     fastly.ToPointer(2),\n\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\tProcessingRegion:  fastly.ToPointer(\"us\"),\n\t}, nil\n}\n\nfunc getLogglyError(_ context.Context, _ *fastly.GetLogglyInput) (*fastly.Loggly, error) {\n\treturn nil, errTest\n}\n\nvar describeLogglyOutput = \"\\n\" + strings.TrimSpace(`\nFormat: %h %l %u %t \"%r\" %>s %b\nFormat version: 2\nName: logs\nPlacement: none\nProcessing region: us\nResponse condition: Prevent default logging\nService ID: 123\nToken: abc\nVersion: 1\n`) + \"\\n\"\n\nfunc updateLogglyOK(_ context.Context, i *fastly.UpdateLogglyInput) (*fastly.Loggly, error) {\n\treturn &fastly.Loggly{\n\t\tServiceID:         fastly.ToPointer(i.ServiceID),\n\t\tServiceVersion:    fastly.ToPointer(i.ServiceVersion),\n\t\tName:              fastly.ToPointer(\"log\"),\n\t\tToken:             fastly.ToPointer(\"abc\"),\n\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\tFormatVersion:     fastly.ToPointer(2),\n\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t}, nil\n}\n\nfunc updateLogglyError(_ context.Context, _ *fastly.UpdateLogglyInput) (*fastly.Loggly, error) {\n\treturn nil, errTest\n}\n\nfunc deleteLogglyOK(_ context.Context, _ *fastly.DeleteLogglyInput) error {\n\treturn nil\n}\n\nfunc deleteLogglyError(_ context.Context, _ *fastly.DeleteLogglyInput) error {\n\treturn errTest\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/loggly/loggly_test.go",
    "content": "package loggly_test\n\nimport (\n\t\"bytes\"\n\t\"testing\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/service/logging/loggly\"\n\t\"github.com/fastly/cli/pkg/config\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/manifest\"\n\t\"github.com/fastly/cli/pkg/mock\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n)\n\nfunc TestCreateLogglyInput(t *testing.T) {\n\tfor _, testcase := range []struct {\n\t\tname      string\n\t\tcmd       *loggly.CreateCommand\n\t\twant      *fastly.CreateLogglyInput\n\t\twantError string\n\t}{\n\t\t{\n\t\t\tname: \"required values set flag serviceID\",\n\t\t\tcmd:  createCommandRequired(),\n\t\t\twant: &fastly.CreateLogglyInput{\n\t\t\t\tServiceID:      \"123\",\n\t\t\t\tServiceVersion: 4,\n\t\t\t\tName:           fastly.ToPointer(\"log\"),\n\t\t\t\tToken:          fastly.ToPointer(\"tkn\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"all values set flag serviceID\",\n\t\t\tcmd:  createCommandOK(),\n\t\t\twant: &fastly.CreateLogglyInput{\n\t\t\t\tServiceID:         \"123\",\n\t\t\t\tServiceVersion:    4,\n\t\t\t\tName:              fastly.ToPointer(\"log\"),\n\t\t\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\t\t\tFormatVersion:     fastly.ToPointer(2),\n\t\t\t\tToken:             fastly.ToPointer(\"tkn\"),\n\t\t\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\t\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\t\t\tProcessingRegion:  fastly.ToPointer(\"eu\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:      \"error missing serviceID\",\n\t\t\tcmd:       createCommandMissingServiceID(),\n\t\t\twant:      nil,\n\t\t\twantError: errors.ErrNoServiceID.Error(),\n\t\t},\n\t} {\n\t\tt.Run(testcase.name, func(t *testing.T) {\n\t\t\tif testcase.wantError == errors.ErrNoServiceID.Error() {\n\t\t\t\tt.Setenv(\"FASTLY_SERVICE_ID\", \"\")\n\t\t\t}\n\t\t\tvar bs []byte\n\t\t\tout := bytes.NewBuffer(bs)\n\t\t\tverboseMode := true\n\n\t\t\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\t\t\tAutoCloneFlag:      testcase.cmd.AutoClone,\n\t\t\t\tAPIClient:          testcase.cmd.Globals.APIClient,\n\t\t\t\tManifest:           testcase.cmd.Manifest,\n\t\t\t\tOut:                out,\n\t\t\t\tServiceVersionFlag: testcase.cmd.ServiceVersion,\n\t\t\t\tVerboseMode:        verboseMode,\n\t\t\t})\n\n\t\t\tswitch {\n\t\t\tcase err != nil && testcase.wantError == \"\":\n\t\t\t\tt.Fatalf(\"unexpected error getting service details: %v\", err)\n\t\t\t\treturn\n\t\t\tcase err != nil && testcase.wantError != \"\":\n\t\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\t\treturn\n\t\t\tcase err == nil && testcase.wantError != \"\":\n\t\t\t\tt.Fatalf(\"expected error, have nil (service details: %s, %d)\", serviceID, serviceVersion.Number)\n\t\t\tcase err == nil && testcase.wantError == \"\":\n\t\t\t\thave, err := testcase.cmd.ConstructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\t\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\t\ttestutil.AssertEqual(t, testcase.want, have)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestUpdateLogglyInput(t *testing.T) {\n\tscenarios := []struct {\n\t\tname      string\n\t\tcmd       *loggly.UpdateCommand\n\t\tapi       mock.API\n\t\twant      *fastly.UpdateLogglyInput\n\t\twantError string\n\t}{\n\t\t{\n\t\t\tname: \"no updates\",\n\t\t\tcmd:  updateCommandNoUpdates(),\n\t\t\tapi: mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tGetLogglyFn:    getLogglyOK,\n\t\t\t},\n\t\t\twant: &fastly.UpdateLogglyInput{\n\t\t\t\tServiceID:      \"123\",\n\t\t\t\tServiceVersion: 4,\n\t\t\t\tName:           \"log\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"all values set flag serviceID\",\n\t\t\tcmd:  updateCommandAll(),\n\t\t\tapi: mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tGetLogglyFn:    getLogglyOK,\n\t\t\t},\n\t\t\twant: &fastly.UpdateLogglyInput{\n\t\t\t\tServiceID:         \"123\",\n\t\t\t\tServiceVersion:    4,\n\t\t\t\tName:              \"log\",\n\t\t\t\tNewName:           fastly.ToPointer(\"new1\"),\n\t\t\t\tFormat:            fastly.ToPointer(\"new2\"),\n\t\t\t\tFormatVersion:     fastly.ToPointer(3),\n\t\t\t\tToken:             fastly.ToPointer(\"new3\"),\n\t\t\t\tResponseCondition: fastly.ToPointer(\"new4\"),\n\t\t\t\tPlacement:         fastly.ToPointer(\"new5\"),\n\t\t\t\tProcessingRegion:  fastly.ToPointer(\"eu\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:      \"error missing serviceID\",\n\t\t\tcmd:       updateCommandMissingServiceID(),\n\t\t\twant:      nil,\n\t\t\twantError: errors.ErrNoServiceID.Error(),\n\t\t},\n\t}\n\tfor testcaseIdx := range scenarios {\n\t\ttestcase := &scenarios[testcaseIdx]\n\t\tt.Run(testcase.name, func(t *testing.T) {\n\t\t\tif testcase.wantError == errors.ErrNoServiceID.Error() {\n\t\t\t\tt.Setenv(\"FASTLY_SERVICE_ID\", \"\")\n\t\t\t}\n\t\t\ttestcase.cmd.Globals.APIClient = testcase.api\n\n\t\t\tvar bs []byte\n\t\t\tout := bytes.NewBuffer(bs)\n\t\t\tverboseMode := true\n\n\t\t\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\t\t\tAutoCloneFlag:      testcase.cmd.AutoClone,\n\t\t\t\tAPIClient:          testcase.api,\n\t\t\t\tManifest:           testcase.cmd.Manifest,\n\t\t\t\tOut:                out,\n\t\t\t\tServiceVersionFlag: testcase.cmd.ServiceVersion,\n\t\t\t\tVerboseMode:        verboseMode,\n\t\t\t})\n\n\t\t\tswitch {\n\t\t\tcase err != nil && testcase.wantError == \"\":\n\t\t\t\tt.Fatalf(\"unexpected error getting service details: %v\", err)\n\t\t\t\treturn\n\t\t\tcase err != nil && testcase.wantError != \"\":\n\t\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\t\treturn\n\t\t\tcase err == nil && testcase.wantError != \"\":\n\t\t\t\tt.Fatalf(\"expected error, have nil (service details: %s, %d)\", serviceID, serviceVersion.Number)\n\t\t\tcase err == nil && testcase.wantError == \"\":\n\t\t\t\thave, err := testcase.cmd.ConstructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\t\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\t\ttestutil.AssertEqual(t, testcase.want, have)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc createCommandOK() *loggly.CreateCommand {\n\tvar b bytes.Buffer\n\n\tg := global.Data{\n\t\tConfig: config.File{},\n\t\tEnv:    config.Environment{},\n\t\tOutput: &b,\n\t}\n\tg.APIClient, _ = mock.APIClient(mock.API{\n\t\tGetVersionFn:   testutil.GetVersion,\n\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t})(\"token\", \"endpoint\", false)\n\n\treturn &loggly.CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: &g,\n\t\t},\n\t\tManifest: manifest.Data{\n\t\t\tFlag: manifest.Flag{\n\t\t\t\tServiceID: \"123\",\n\t\t\t},\n\t\t},\n\t\tEndpointName: argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"log\"},\n\t\tToken:        argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"tkn\"},\n\t\tServiceVersion: argparser.OptionalServiceVersion{\n\t\t\tOptionalString: argparser.OptionalString{Value: \"1\"},\n\t\t},\n\t\tAutoClone: argparser.OptionalAutoClone{\n\t\t\tOptionalBool: argparser.OptionalBool{\n\t\t\t\tOptional: argparser.Optional{\n\t\t\t\t\tWasSet: true,\n\t\t\t\t},\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t},\n\t\tFormat:            argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: `%h %l %u %t \"%r\" %>s %b`},\n\t\tFormatVersion:     argparser.OptionalInt{Optional: argparser.Optional{WasSet: true}, Value: 2},\n\t\tResponseCondition: argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"Prevent default logging\"},\n\t\tPlacement:         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"none\"},\n\t\tProcessingRegion:  argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"eu\"},\n\t}\n}\n\nfunc createCommandRequired() *loggly.CreateCommand {\n\tvar b bytes.Buffer\n\n\tg := global.Data{\n\t\tConfig: config.File{},\n\t\tEnv:    config.Environment{},\n\t\tOutput: &b,\n\t}\n\tg.APIClient, _ = mock.APIClient(mock.API{\n\t\tGetVersionFn:   testutil.GetVersion,\n\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t})(\"token\", \"endpoint\", false)\n\n\treturn &loggly.CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: &g,\n\t\t},\n\t\tManifest: manifest.Data{\n\t\t\tFlag: manifest.Flag{\n\t\t\t\tServiceID: \"123\",\n\t\t\t},\n\t\t},\n\t\tEndpointName: argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"log\"},\n\t\tToken:        argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"tkn\"},\n\t\tServiceVersion: argparser.OptionalServiceVersion{\n\t\t\tOptionalString: argparser.OptionalString{Value: \"1\"},\n\t\t},\n\t\tAutoClone: argparser.OptionalAutoClone{\n\t\t\tOptionalBool: argparser.OptionalBool{\n\t\t\t\tOptional: argparser.Optional{\n\t\t\t\t\tWasSet: true,\n\t\t\t\t},\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t},\n\t}\n}\n\nfunc createCommandMissingServiceID() *loggly.CreateCommand {\n\tres := createCommandOK()\n\tres.Manifest = manifest.Data{}\n\tres.ServiceVersion = argparser.OptionalServiceVersion{}\n\treturn res\n}\n\nfunc updateCommandNoUpdates() *loggly.UpdateCommand {\n\tvar b bytes.Buffer\n\n\tg := global.Data{\n\t\tConfig: config.File{},\n\t\tEnv:    config.Environment{},\n\t\tOutput: &b,\n\t}\n\n\treturn &loggly.UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: &g,\n\t\t},\n\t\tManifest: manifest.Data{\n\t\t\tFlag: manifest.Flag{\n\t\t\t\tServiceID: \"123\",\n\t\t\t},\n\t\t},\n\t\tEndpointName: \"log\",\n\t\tServiceVersion: argparser.OptionalServiceVersion{\n\t\t\tOptionalString: argparser.OptionalString{Value: \"1\"},\n\t\t},\n\t\tAutoClone: argparser.OptionalAutoClone{\n\t\t\tOptionalBool: argparser.OptionalBool{\n\t\t\t\tOptional: argparser.Optional{\n\t\t\t\t\tWasSet: true,\n\t\t\t\t},\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t},\n\t}\n}\n\nfunc updateCommandAll() *loggly.UpdateCommand {\n\tvar b bytes.Buffer\n\n\tg := global.Data{\n\t\tConfig: config.File{},\n\t\tEnv:    config.Environment{},\n\t\tOutput: &b,\n\t}\n\n\treturn &loggly.UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: &g,\n\t\t},\n\t\tManifest: manifest.Data{\n\t\t\tFlag: manifest.Flag{\n\t\t\t\tServiceID: \"123\",\n\t\t\t},\n\t\t},\n\t\tEndpointName: \"log\",\n\t\tServiceVersion: argparser.OptionalServiceVersion{\n\t\t\tOptionalString: argparser.OptionalString{Value: \"1\"},\n\t\t},\n\t\tAutoClone: argparser.OptionalAutoClone{\n\t\t\tOptionalBool: argparser.OptionalBool{\n\t\t\t\tOptional: argparser.Optional{\n\t\t\t\t\tWasSet: true,\n\t\t\t\t},\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t},\n\t\tNewName:           argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new1\"},\n\t\tFormat:            argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new2\"},\n\t\tFormatVersion:     argparser.OptionalInt{Optional: argparser.Optional{WasSet: true}, Value: 3},\n\t\tToken:             argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new3\"},\n\t\tResponseCondition: argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new4\"},\n\t\tPlacement:         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new5\"},\n\t\tProcessingRegion:  argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"eu\"},\n\t}\n}\n\nfunc updateCommandMissingServiceID() *loggly.UpdateCommand {\n\tres := updateCommandAll()\n\tres.Manifest = manifest.Data{}\n\tres.ServiceVersion = argparser.OptionalServiceVersion{}\n\treturn res\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/loggly/root.go",
    "content": "package loggly\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"loggly\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manipulate Fastly service version Loggly logging endpoints\")\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/loggly/update.go",
    "content": "package loggly\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/service/logging/logflags\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/manifest\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// UpdateCommand calls the Fastly API to update a Loggly logging endpoint.\ntype UpdateCommand struct {\n\targparser.Base\n\tManifest manifest.Data\n\n\t// Required.\n\tEndpointName   string // Can't shadow argparser.Base method Name().\n\tServiceName    argparser.OptionalServiceNameID\n\tServiceVersion argparser.OptionalServiceVersion\n\n\t// Optional.\n\tAutoClone         argparser.OptionalAutoClone\n\tFormat            argparser.OptionalString\n\tFormatVersion     argparser.OptionalInt\n\tNewName           argparser.OptionalString\n\tPlacement         argparser.OptionalString\n\tProcessingRegion  argparser.OptionalString\n\tResponseCondition argparser.OptionalString\n\tToken             argparser.OptionalString\n}\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"update\", \"Update a Loggly logging endpoint on a Fastly service version\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"The name of the Loggly logging object\").Short('n').Required().StringVar(&c.EndpointName)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.ServiceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.AutoClone.Set,\n\t\tDst:    &c.AutoClone.Value,\n\t})\n\tc.CmdClause.Flag(\"auth-token\", \"The token to use for authentication (https://www.loggly.com/docs/customer-token-authentication-token/)\").Action(c.Token.Set).StringVar(&c.Token.Value)\n\tlogflags.Format(c.CmdClause, &c.Format)\n\tlogflags.FormatVersion(c.CmdClause, &c.FormatVersion)\n\tc.CmdClause.Flag(\"new-name\", \"New name of the Loggly logging object\").Action(c.NewName.Set).StringVar(&c.NewName.Value)\n\tlogflags.Placement(c.CmdClause, &c.Placement)\n\tlogflags.ProcessingRegion(c.CmdClause, &c.ProcessingRegion, \"Loggly\")\n\tlogflags.ResponseCondition(c.CmdClause, &c.ResponseCondition)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.ServiceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.ServiceName.Value,\n\t})\n\treturn &c\n}\n\n// ConstructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *UpdateCommand) ConstructInput(serviceID string, serviceVersion int) (*fastly.UpdateLogglyInput, error) {\n\tinput := fastly.UpdateLogglyInput{\n\t\tServiceID:      serviceID,\n\t\tServiceVersion: serviceVersion,\n\t\tName:           c.EndpointName,\n\t}\n\n\tif c.NewName.WasSet {\n\t\tinput.NewName = &c.NewName.Value\n\t}\n\n\tif c.Format.WasSet {\n\t\tinput.Format = fastly.ToPointer(argparser.Content(c.Format.Value))\n\t}\n\n\tif c.FormatVersion.WasSet {\n\t\tinput.FormatVersion = &c.FormatVersion.Value\n\t}\n\n\tif c.Token.WasSet {\n\t\tinput.Token = &c.Token.Value\n\t}\n\n\tif c.ResponseCondition.WasSet {\n\t\tinput.ResponseCondition = &c.ResponseCondition.Value\n\t}\n\n\tif c.Placement.WasSet {\n\t\tinput.Placement = &c.Placement.Value\n\t}\n\n\tif c.ProcessingRegion.WasSet {\n\t\tinput.ProcessingRegion = &c.ProcessingRegion.Value\n\t}\n\n\treturn &input, nil\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *UpdateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(false),\n\t\tLocked:             optional.Of(false),\n\t\tAutoCloneFlag:      c.AutoClone,\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.ServiceName,\n\t\tServiceVersionFlag: c.ServiceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": errors.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tinput, err := c.ConstructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tloggly, err := c.Globals.APIClient.UpdateLoggly(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\ttext.Success(\n\t\tout,\n\t\t\"Updated Loggly logging endpoint %s (service %s version %d)\",\n\t\tfastly.ToValue(loggly.Name),\n\t\tfastly.ToValue(loggly.ServiceID),\n\t\tfastly.ToValue(loggly.ServiceVersion),\n\t)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/logshuttle/create.go",
    "content": "package logshuttle\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/service/logging/logflags\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/manifest\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// CreateCommand calls the Fastly API to create a Logshuttle logging endpoint.\ntype CreateCommand struct {\n\targparser.Base\n\tManifest manifest.Data\n\n\t// Required.\n\tServiceName    argparser.OptionalServiceNameID\n\tServiceVersion argparser.OptionalServiceVersion\n\n\t// Optional.\n\tAutoClone         argparser.OptionalAutoClone\n\tEndpointName      argparser.OptionalString // Can't shadow argparser.Base method Name().\n\tFormat            argparser.OptionalString\n\tFormatVersion     argparser.OptionalInt\n\tPlacement         argparser.OptionalString\n\tProcessingRegion  argparser.OptionalString\n\tResponseCondition argparser.OptionalString\n\tToken             argparser.OptionalString\n\tURL               argparser.OptionalString\n}\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"create\", \"Create a Logshuttle logging endpoint on a Fastly service version\").Alias(\"add\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"The name of the Logshuttle logging object. Used as a primary key for API access\").Short('n').Action(c.EndpointName.Set).StringVar(&c.EndpointName.Value)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.ServiceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.CmdClause.Flag(\"auth-token\", \"The data authentication token associated with this endpoint\").Action(c.Token.Set).StringVar(&c.Token.Value)\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.AutoClone.Set,\n\t\tDst:    &c.AutoClone.Value,\n\t})\n\tlogflags.Format(c.CmdClause, &c.Format)\n\tlogflags.FormatVersion(c.CmdClause, &c.FormatVersion)\n\tlogflags.ResponseCondition(c.CmdClause, &c.ResponseCondition)\n\tlogflags.Placement(c.CmdClause, &c.Placement)\n\tlogflags.ProcessingRegion(c.CmdClause, &c.ProcessingRegion, \"Logshuttle\")\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.ServiceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.ServiceName.Value,\n\t})\n\tc.CmdClause.Flag(\"url\", \"Your Logshuttle endpoint url\").Action(c.URL.Set).StringVar(&c.URL.Value)\n\treturn &c\n}\n\n// ConstructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *CreateCommand) ConstructInput(serviceID string, serviceVersion int) (*fastly.CreateLogshuttleInput, error) {\n\tvar input fastly.CreateLogshuttleInput\n\n\tinput.ServiceID = serviceID\n\tinput.ServiceVersion = serviceVersion\n\tif c.EndpointName.WasSet {\n\t\tinput.Name = &c.EndpointName.Value\n\t}\n\tif c.Token.WasSet {\n\t\tinput.Token = &c.Token.Value\n\t}\n\tif c.URL.WasSet {\n\t\tinput.URL = &c.URL.Value\n\t}\n\n\tif c.Format.WasSet {\n\t\tinput.Format = fastly.ToPointer(argparser.Content(c.Format.Value))\n\t}\n\n\tif c.FormatVersion.WasSet {\n\t\tinput.FormatVersion = &c.FormatVersion.Value\n\t}\n\n\tif c.ResponseCondition.WasSet {\n\t\tinput.ResponseCondition = &c.ResponseCondition.Value\n\t}\n\n\tif c.Placement.WasSet {\n\t\tinput.Placement = &c.Placement.Value\n\t}\n\n\tif c.ProcessingRegion.WasSet {\n\t\tinput.ProcessingRegion = &c.ProcessingRegion.Value\n\t}\n\n\treturn &input, nil\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *CreateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(false),\n\t\tLocked:             optional.Of(false),\n\t\tAutoCloneFlag:      c.AutoClone,\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.ServiceName,\n\t\tServiceVersionFlag: c.ServiceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": errors.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tinput, err := c.ConstructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\td, err := c.Globals.APIClient.CreateLogshuttle(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\ttext.Success(\n\t\tout,\n\t\t\"Created Logshuttle logging endpoint %s (service %s version %d)\",\n\t\tfastly.ToValue(d.Name),\n\t\tfastly.ToValue(d.ServiceID),\n\t\tfastly.ToValue(d.ServiceVersion),\n\t)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/logshuttle/delete.go",
    "content": "package logshuttle\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DeleteCommand calls the Fastly API to delete a Logshuttle logging endpoint.\ntype DeleteCommand struct {\n\targparser.Base\n\tInput          fastly.DeleteLogshuttleInput\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n\tautoClone      argparser.OptionalAutoClone\n}\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"delete\", \"Delete a Logshuttle logging endpoint on a Fastly service version\").Alias(\"remove\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"The name of the Logshuttle logging object\").Short('n').Required().StringVar(&c.Input.Name)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.autoClone.Set,\n\t\tDst:    &c.autoClone.Value,\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DeleteCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(false),\n\t\tLocked:             optional.Of(false),\n\t\tAutoCloneFlag:      c.autoClone,\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": errors.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tc.Input.ServiceID = serviceID\n\tc.Input.ServiceVersion = fastly.ToValue(serviceVersion.Number)\n\n\tif err := c.Globals.APIClient.DeleteLogshuttle(context.TODO(), &c.Input); err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Deleted Logshuttle logging endpoint %s (service %s version %d)\", c.Input.Name, c.Input.ServiceID, c.Input.ServiceVersion)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/logshuttle/describe.go",
    "content": "package logshuttle\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DescribeCommand calls the Fastly API to describe a Logshuttle logging endpoint.\ntype DescribeCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tInput          fastly.GetLogshuttleInput\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n}\n\n// NewDescribeCommand returns a usable command registered under the parent.\nfunc NewDescribeCommand(parent argparser.Registerer, g *global.Data) *DescribeCommand {\n\tc := DescribeCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"describe\", \"Show detailed information about a Logshuttle logging endpoint on a Fastly service version\").Alias(\"get\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"The name of the Logshuttle logging object\").Short('n').Required().StringVar(&c.Input.Name)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DescribeCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fsterr.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tc.Input.ServiceID = serviceID\n\tc.Input.ServiceVersion = fastly.ToValue(serviceVersion.Number)\n\n\to, err := c.Globals.APIClient.GetLogshuttle(context.TODO(), &c.Input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\tlines := text.Lines{\n\t\t\"Format version\":     fastly.ToValue(o.FormatVersion),\n\t\t\"Format\":             fastly.ToValue(o.Format),\n\t\t\"Name\":               fastly.ToValue(o.Name),\n\t\t\"Placement\":          fastly.ToValue(o.Placement),\n\t\t\"Processing region\":  fastly.ToValue(o.ProcessingRegion),\n\t\t\"Response condition\": fastly.ToValue(o.ResponseCondition),\n\t\t\"Token\":              fastly.ToValue(o.Token),\n\t\t\"URL\":                fastly.ToValue(o.URL),\n\t\t\"Version\":            fastly.ToValue(o.ServiceVersion),\n\t}\n\tif !c.Globals.Verbose() {\n\t\tlines[\"Service ID\"] = fastly.ToValue(o.ServiceID)\n\t}\n\ttext.PrintLines(out, lines)\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/logshuttle/doc.go",
    "content": "// Package logshuttle contains commands to inspect and manipulate Fastly service Logshuttle\n// logging endpoints.\npackage logshuttle\n"
  },
  {
    "path": "pkg/commands/service/logging/logshuttle/list.go",
    "content": "package logshuttle\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// ListCommand calls the Fastly API to list Logshuttle logging endpoints.\ntype ListCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tInput          fastly.ListLogshuttlesInput\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n}\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"list\", \"List Logshuttle endpoints on a Fastly service version\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *ListCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fsterr.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tc.Input.ServiceID = serviceID\n\tc.Input.ServiceVersion = fastly.ToValue(serviceVersion.Number)\n\n\to, err := c.Globals.APIClient.ListLogshuttles(context.TODO(), &c.Input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\tif !c.Globals.Verbose() {\n\t\ttw := text.NewTable(out)\n\t\ttw.AddHeader(\"SERVICE\", \"VERSION\", \"NAME\")\n\t\tfor _, logshuttle := range o {\n\t\t\ttw.AddLine(\n\t\t\t\tfastly.ToValue(logshuttle.ServiceID),\n\t\t\t\tfastly.ToValue(logshuttle.ServiceVersion),\n\t\t\t\tfastly.ToValue(logshuttle.Name),\n\t\t\t)\n\t\t}\n\t\ttw.Print()\n\t\treturn nil\n\t}\n\n\tfmt.Fprintf(out, \"Version: %d\\n\", c.Input.ServiceVersion)\n\tfor i, logshuttle := range o {\n\t\tfmt.Fprintf(out, \"\\tLogshuttle %d/%d\\n\", i+1, len(o))\n\t\tfmt.Fprintf(out, \"\\t\\tService ID: %s\\n\", fastly.ToValue(logshuttle.ServiceID))\n\t\tfmt.Fprintf(out, \"\\t\\tVersion: %d\\n\", fastly.ToValue(logshuttle.ServiceVersion))\n\t\tfmt.Fprintf(out, \"\\t\\tName: %s\\n\", fastly.ToValue(logshuttle.Name))\n\t\tfmt.Fprintf(out, \"\\t\\tURL: %s\\n\", fastly.ToValue(logshuttle.URL))\n\t\tfmt.Fprintf(out, \"\\t\\tToken: %s\\n\", fastly.ToValue(logshuttle.Token))\n\t\tfmt.Fprintf(out, \"\\t\\tFormat: %s\\n\", fastly.ToValue(logshuttle.Format))\n\t\tfmt.Fprintf(out, \"\\t\\tFormat version: %d\\n\", fastly.ToValue(logshuttle.FormatVersion))\n\t\tfmt.Fprintf(out, \"\\t\\tResponse condition: %s\\n\", fastly.ToValue(logshuttle.ResponseCondition))\n\t\tfmt.Fprintf(out, \"\\t\\tPlacement: %s\\n\", fastly.ToValue(logshuttle.Placement))\n\t\tfmt.Fprintf(out, \"\\t\\tProcessing region: %s\\n\", fastly.ToValue(logshuttle.ProcessingRegion))\n\t}\n\tfmt.Fprintln(out)\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/logshuttle/logshuttle_integration_test.go",
    "content": "package logshuttle_test\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/mock\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n\n\troot \"github.com/fastly/cli/pkg/commands/service\"\n\tparent \"github.com/fastly/cli/pkg/commands/service/logging\"\n\tsub \"github.com/fastly/cli/pkg/commands/service/logging/logshuttle\"\n)\n\nfunc TestLogshuttleCreate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name log --url example.com --auth-token abc --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:       testutil.GetVersion,\n\t\t\t\tCloneVersionFn:     testutil.CloneVersionResult(4),\n\t\t\t\tCreateLogshuttleFn: createLogshuttleOK,\n\t\t\t},\n\t\t\tWantOutput: \"Created Logshuttle logging endpoint log (service 123 version 4)\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name log --url example.com --auth-token abc --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:       testutil.GetVersion,\n\t\t\t\tCloneVersionFn:     testutil.CloneVersionResult(4),\n\t\t\t\tCreateLogshuttleFn: createLogshuttleError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"create\"}, scenarios)\n}\n\nfunc TestLogshuttleList(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:      testutil.GetVersion,\n\t\t\t\tListLogshuttlesFn: listLogshuttlesOK,\n\t\t\t},\n\t\t\tWantOutput: listLogshuttlesShortOutput,\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --verbose\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:      testutil.GetVersion,\n\t\t\t\tListLogshuttlesFn: listLogshuttlesOK,\n\t\t\t},\n\t\t\tWantOutput: listLogshuttlesVerboseOutput,\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 -v\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:      testutil.GetVersion,\n\t\t\t\tListLogshuttlesFn: listLogshuttlesOK,\n\t\t\t},\n\t\t\tWantOutput: listLogshuttlesVerboseOutput,\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:      testutil.GetVersion,\n\t\t\t\tListLogshuttlesFn: listLogshuttlesError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"list\"}, scenarios)\n}\n\nfunc TestLogshuttleDescribe(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"--service-id 123 --version 1\",\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:    testutil.GetVersion,\n\t\t\t\tGetLogshuttleFn: getLogshuttleError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:    testutil.GetVersion,\n\t\t\t\tGetLogshuttleFn: getLogshuttleOK,\n\t\t\t},\n\t\t\tWantOutput: describeLogshuttleOutput,\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"describe\"}, scenarios)\n}\n\nfunc TestLogshuttleUpdate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"--service-id 123 --version 1 --new-name log\",\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs --new-name log --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:       testutil.GetVersion,\n\t\t\t\tCloneVersionFn:     testutil.CloneVersionResult(4),\n\t\t\t\tUpdateLogshuttleFn: updateLogshuttleError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs --new-name log --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:       testutil.GetVersion,\n\t\t\t\tCloneVersionFn:     testutil.CloneVersionResult(4),\n\t\t\t\tUpdateLogshuttleFn: updateLogshuttleOK,\n\t\t\t},\n\t\t\tWantOutput: \"Updated Logshuttle logging endpoint log (service 123 version 4)\",\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"update\"}, scenarios)\n}\n\nfunc TestLogshuttleDelete(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"--service-id 123 --version 1\",\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:       testutil.GetVersion,\n\t\t\t\tCloneVersionFn:     testutil.CloneVersionResult(4),\n\t\t\t\tDeleteLogshuttleFn: deleteLogshuttleError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:       testutil.GetVersion,\n\t\t\t\tCloneVersionFn:     testutil.CloneVersionResult(4),\n\t\t\t\tDeleteLogshuttleFn: deleteLogshuttleOK,\n\t\t\t},\n\t\t\tWantOutput: \"Deleted Logshuttle logging endpoint logs (service 123 version 4)\",\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"delete\"}, scenarios)\n}\n\nvar errTest = errors.New(\"fixture error\")\n\nfunc createLogshuttleOK(_ context.Context, i *fastly.CreateLogshuttleInput) (*fastly.Logshuttle, error) {\n\ts := fastly.Logshuttle{\n\t\tServiceID:      fastly.ToPointer(i.ServiceID),\n\t\tServiceVersion: fastly.ToPointer(i.ServiceVersion),\n\t}\n\n\tif i.Name != nil {\n\t\ts.Name = i.Name\n\t}\n\n\treturn &s, nil\n}\n\nfunc createLogshuttleError(_ context.Context, _ *fastly.CreateLogshuttleInput) (*fastly.Logshuttle, error) {\n\treturn nil, errTest\n}\n\nfunc listLogshuttlesOK(_ context.Context, i *fastly.ListLogshuttlesInput) ([]*fastly.Logshuttle, error) {\n\treturn []*fastly.Logshuttle{\n\t\t{\n\t\t\tServiceID:         fastly.ToPointer(i.ServiceID),\n\t\t\tServiceVersion:    fastly.ToPointer(i.ServiceVersion),\n\t\t\tName:              fastly.ToPointer(\"logs\"),\n\t\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\t\tFormatVersion:     fastly.ToPointer(2),\n\t\t\tURL:               fastly.ToPointer(\"example.com\"),\n\t\t\tToken:             fastly.ToPointer(\"abc\"),\n\t\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\t\tProcessingRegion:  fastly.ToPointer(\"us\"),\n\t\t},\n\t\t{\n\t\t\tServiceID:         fastly.ToPointer(i.ServiceID),\n\t\t\tServiceVersion:    fastly.ToPointer(i.ServiceVersion),\n\t\t\tName:              fastly.ToPointer(\"analytics\"),\n\t\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\t\tFormatVersion:     fastly.ToPointer(2),\n\t\t\tURL:               fastly.ToPointer(\"example.com\"),\n\t\t\tToken:             fastly.ToPointer(\"abc\"),\n\t\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\t\tProcessingRegion:  fastly.ToPointer(\"us\"),\n\t\t},\n\t}, nil\n}\n\nfunc listLogshuttlesError(_ context.Context, _ *fastly.ListLogshuttlesInput) ([]*fastly.Logshuttle, error) {\n\treturn nil, errTest\n}\n\nvar listLogshuttlesShortOutput = strings.TrimSpace(`\nSERVICE  VERSION  NAME\n123      1        logs\n123      1        analytics\n`) + \"\\n\"\n\nvar listLogshuttlesVerboseOutput = strings.TrimSpace(`\nFastly API endpoint: https://api.fastly.com\nFastly API token provided via config file (auth: user)\n\nService ID (via --service-id): 123\n\nVersion: 1\n\tLogshuttle 1/2\n\t\tService ID: 123\n\t\tVersion: 1\n\t\tName: logs\n\t\tURL: example.com\n\t\tToken: abc\n\t\tFormat: %h %l %u %t \"%r\" %>s %b\n\t\tFormat version: 2\n\t\tResponse condition: Prevent default logging\n\t\tPlacement: none\n\t\tProcessing region: us\n\tLogshuttle 2/2\n\t\tService ID: 123\n\t\tVersion: 1\n\t\tName: analytics\n\t\tURL: example.com\n\t\tToken: abc\n\t\tFormat: %h %l %u %t \"%r\" %>s %b\n\t\tFormat version: 2\n\t\tResponse condition: Prevent default logging\n\t\tPlacement: none\n\t\tProcessing region: us\n`) + \"\\n\\n\"\n\nfunc getLogshuttleOK(_ context.Context, i *fastly.GetLogshuttleInput) (*fastly.Logshuttle, error) {\n\treturn &fastly.Logshuttle{\n\t\tServiceID:         fastly.ToPointer(i.ServiceID),\n\t\tServiceVersion:    fastly.ToPointer(i.ServiceVersion),\n\t\tName:              fastly.ToPointer(\"logs\"),\n\t\tURL:               fastly.ToPointer(\"example.com\"),\n\t\tToken:             fastly.ToPointer(\"abc\"),\n\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\tFormatVersion:     fastly.ToPointer(2),\n\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\tProcessingRegion:  fastly.ToPointer(\"us\"),\n\t}, nil\n}\n\nfunc getLogshuttleError(_ context.Context, _ *fastly.GetLogshuttleInput) (*fastly.Logshuttle, error) {\n\treturn nil, errTest\n}\n\nvar describeLogshuttleOutput = \"\\n\" + strings.TrimSpace(`\nFormat: %h %l %u %t \"%r\" %>s %b\nFormat version: 2\nName: logs\nPlacement: none\nProcessing region: us\nResponse condition: Prevent default logging\nService ID: 123\nToken: abc\nURL: example.com\nVersion: 1\n`) + \"\\n\"\n\nfunc updateLogshuttleOK(_ context.Context, i *fastly.UpdateLogshuttleInput) (*fastly.Logshuttle, error) {\n\treturn &fastly.Logshuttle{\n\t\tServiceID:         fastly.ToPointer(i.ServiceID),\n\t\tServiceVersion:    fastly.ToPointer(i.ServiceVersion),\n\t\tName:              fastly.ToPointer(\"log\"),\n\t\tURL:               fastly.ToPointer(\"example.com\"),\n\t\tToken:             fastly.ToPointer(\"abc\"),\n\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\tFormatVersion:     fastly.ToPointer(2),\n\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t}, nil\n}\n\nfunc updateLogshuttleError(_ context.Context, _ *fastly.UpdateLogshuttleInput) (*fastly.Logshuttle, error) {\n\treturn nil, errTest\n}\n\nfunc deleteLogshuttleOK(_ context.Context, _ *fastly.DeleteLogshuttleInput) error {\n\treturn nil\n}\n\nfunc deleteLogshuttleError(_ context.Context, _ *fastly.DeleteLogshuttleInput) error {\n\treturn errTest\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/logshuttle/logshuttle_test.go",
    "content": "package logshuttle_test\n\nimport (\n\t\"bytes\"\n\t\"testing\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/service/logging/logshuttle\"\n\t\"github.com/fastly/cli/pkg/config\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/manifest\"\n\t\"github.com/fastly/cli/pkg/mock\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n)\n\nfunc TestCreateLogshuttleInput(t *testing.T) {\n\tfor _, testcase := range []struct {\n\t\tname      string\n\t\tcmd       *logshuttle.CreateCommand\n\t\twant      *fastly.CreateLogshuttleInput\n\t\twantError string\n\t}{\n\t\t{\n\t\t\tname: \"required values set flag serviceID\",\n\t\t\tcmd:  createCommandRequired(),\n\t\t\twant: &fastly.CreateLogshuttleInput{\n\t\t\t\tServiceID:      \"123\",\n\t\t\t\tServiceVersion: 4,\n\t\t\t\tName:           fastly.ToPointer(\"log\"),\n\t\t\t\tToken:          fastly.ToPointer(\"tkn\"),\n\t\t\t\tURL:            fastly.ToPointer(\"example.com\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"all values set flag serviceID\",\n\t\t\tcmd:  createCommandAll(),\n\t\t\twant: &fastly.CreateLogshuttleInput{\n\t\t\t\tServiceID:         \"123\",\n\t\t\t\tServiceVersion:    4,\n\t\t\t\tName:              fastly.ToPointer(\"log\"),\n\t\t\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\t\t\tFormatVersion:     fastly.ToPointer(2),\n\t\t\t\tURL:               fastly.ToPointer(\"example.com\"),\n\t\t\t\tToken:             fastly.ToPointer(\"tkn\"),\n\t\t\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\t\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\t\t\tProcessingRegion:  fastly.ToPointer(\"eu\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:      \"error missing serviceID\",\n\t\t\tcmd:       createCommandMissingServiceID(),\n\t\t\twant:      nil,\n\t\t\twantError: errors.ErrNoServiceID.Error(),\n\t\t},\n\t} {\n\t\tt.Run(testcase.name, func(t *testing.T) {\n\t\t\tif testcase.wantError == errors.ErrNoServiceID.Error() {\n\t\t\t\tt.Setenv(\"FASTLY_SERVICE_ID\", \"\")\n\t\t\t}\n\t\t\tvar bs []byte\n\t\t\tout := bytes.NewBuffer(bs)\n\t\t\tverboseMode := true\n\n\t\t\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\t\t\tAutoCloneFlag:      testcase.cmd.AutoClone,\n\t\t\t\tAPIClient:          testcase.cmd.Globals.APIClient,\n\t\t\t\tManifest:           testcase.cmd.Manifest,\n\t\t\t\tOut:                out,\n\t\t\t\tServiceVersionFlag: testcase.cmd.ServiceVersion,\n\t\t\t\tVerboseMode:        verboseMode,\n\t\t\t})\n\n\t\t\tswitch {\n\t\t\tcase err != nil && testcase.wantError == \"\":\n\t\t\t\tt.Fatalf(\"unexpected error getting service details: %v\", err)\n\t\t\t\treturn\n\t\t\tcase err != nil && testcase.wantError != \"\":\n\t\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\t\treturn\n\t\t\tcase err == nil && testcase.wantError != \"\":\n\t\t\t\tt.Fatalf(\"expected error, have nil (service details: %s, %d)\", serviceID, serviceVersion.Number)\n\t\t\tcase err == nil && testcase.wantError == \"\":\n\t\t\t\thave, err := testcase.cmd.ConstructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\t\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\t\ttestutil.AssertEqual(t, testcase.want, have)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestUpdateLogshuttleInput(t *testing.T) {\n\tscenarios := []struct {\n\t\tname      string\n\t\tcmd       *logshuttle.UpdateCommand\n\t\tapi       mock.API\n\t\twant      *fastly.UpdateLogshuttleInput\n\t\twantError string\n\t}{\n\t\t{\n\t\t\tname: \"no update\",\n\t\t\tcmd:  updateCommandNoUpdate(),\n\t\t\tapi: mock.API{\n\t\t\t\tGetVersionFn:    testutil.GetVersion,\n\t\t\t\tCloneVersionFn:  testutil.CloneVersionResult(4),\n\t\t\t\tGetLogshuttleFn: getLogshuttleOK,\n\t\t\t},\n\t\t\twant: &fastly.UpdateLogshuttleInput{\n\t\t\t\tServiceID:      \"123\",\n\t\t\t\tServiceVersion: 4,\n\t\t\t\tName:           \"log\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"all values set flag serviceID\",\n\t\t\tcmd:  updateCommandAll(),\n\t\t\tapi: mock.API{\n\t\t\t\tGetVersionFn:    testutil.GetVersion,\n\t\t\t\tCloneVersionFn:  testutil.CloneVersionResult(4),\n\t\t\t\tGetLogshuttleFn: getLogshuttleOK,\n\t\t\t},\n\t\t\twant: &fastly.UpdateLogshuttleInput{\n\t\t\t\tServiceID:         \"123\",\n\t\t\t\tServiceVersion:    4,\n\t\t\t\tName:              \"log\",\n\t\t\t\tNewName:           fastly.ToPointer(\"new1\"),\n\t\t\t\tFormat:            fastly.ToPointer(\"new2\"),\n\t\t\t\tFormatVersion:     fastly.ToPointer(3),\n\t\t\t\tToken:             fastly.ToPointer(\"new3\"),\n\t\t\t\tURL:               fastly.ToPointer(\"new4\"),\n\t\t\t\tResponseCondition: fastly.ToPointer(\"new5\"),\n\t\t\t\tPlacement:         fastly.ToPointer(\"new6\"),\n\t\t\t\tProcessingRegion:  fastly.ToPointer(\"eu\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:      \"error missing serviceID\",\n\t\t\tcmd:       updateCommandMissingServiceID(),\n\t\t\twant:      nil,\n\t\t\twantError: errors.ErrNoServiceID.Error(),\n\t\t},\n\t}\n\tfor testcaseIdx := range scenarios {\n\t\ttestcase := &scenarios[testcaseIdx]\n\t\tt.Run(testcase.name, func(t *testing.T) {\n\t\t\tif testcase.wantError == errors.ErrNoServiceID.Error() {\n\t\t\t\tt.Setenv(\"FASTLY_SERVICE_ID\", \"\")\n\t\t\t}\n\t\t\ttestcase.cmd.Globals.APIClient = testcase.api\n\n\t\t\tvar bs []byte\n\t\t\tout := bytes.NewBuffer(bs)\n\t\t\tverboseMode := true\n\n\t\t\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\t\t\tAutoCloneFlag:      testcase.cmd.AutoClone,\n\t\t\t\tAPIClient:          testcase.api,\n\t\t\t\tManifest:           testcase.cmd.Manifest,\n\t\t\t\tOut:                out,\n\t\t\t\tServiceVersionFlag: testcase.cmd.ServiceVersion,\n\t\t\t\tVerboseMode:        verboseMode,\n\t\t\t})\n\n\t\t\tswitch {\n\t\t\tcase err != nil && testcase.wantError == \"\":\n\t\t\t\tt.Fatalf(\"unexpected error getting service details: %v\", err)\n\t\t\t\treturn\n\t\t\tcase err != nil && testcase.wantError != \"\":\n\t\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\t\treturn\n\t\t\tcase err == nil && testcase.wantError != \"\":\n\t\t\t\tt.Fatalf(\"expected error, have nil (service details: %s, %d)\", serviceID, serviceVersion.Number)\n\t\t\tcase err == nil && testcase.wantError == \"\":\n\t\t\t\thave, err := testcase.cmd.ConstructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\t\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\t\ttestutil.AssertEqual(t, testcase.want, have)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc createCommandRequired() *logshuttle.CreateCommand {\n\tvar b bytes.Buffer\n\n\tg := global.Data{\n\t\tConfig: config.File{},\n\t\tEnv:    config.Environment{},\n\t\tOutput: &b,\n\t}\n\tg.APIClient, _ = mock.APIClient(mock.API{\n\t\tGetVersionFn:   testutil.GetVersion,\n\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t})(\"token\", \"endpoint\", false)\n\n\treturn &logshuttle.CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: &g,\n\t\t},\n\t\tManifest: manifest.Data{\n\t\t\tFlag: manifest.Flag{\n\t\t\t\tServiceID: \"123\",\n\t\t\t},\n\t\t},\n\t\tEndpointName: argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"log\"},\n\t\tToken:        argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"tkn\"},\n\t\tURL:          argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"example.com\"},\n\t\tServiceVersion: argparser.OptionalServiceVersion{\n\t\t\tOptionalString: argparser.OptionalString{Value: \"1\"},\n\t\t},\n\t\tAutoClone: argparser.OptionalAutoClone{\n\t\t\tOptionalBool: argparser.OptionalBool{\n\t\t\t\tOptional: argparser.Optional{\n\t\t\t\t\tWasSet: true,\n\t\t\t\t},\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t},\n\t}\n}\n\nfunc createCommandAll() *logshuttle.CreateCommand {\n\tvar b bytes.Buffer\n\n\tg := global.Data{\n\t\tConfig: config.File{},\n\t\tEnv:    config.Environment{},\n\t\tOutput: &b,\n\t}\n\tg.APIClient, _ = mock.APIClient(mock.API{\n\t\tGetVersionFn:   testutil.GetVersion,\n\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t})(\"token\", \"endpoint\", false)\n\n\treturn &logshuttle.CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: &g,\n\t\t},\n\t\tManifest: manifest.Data{\n\t\t\tFlag: manifest.Flag{\n\t\t\t\tServiceID: \"123\",\n\t\t\t},\n\t\t},\n\t\tEndpointName: argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"log\"},\n\t\tToken:        argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"tkn\"},\n\t\tURL:          argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"example.com\"},\n\t\tServiceVersion: argparser.OptionalServiceVersion{\n\t\t\tOptionalString: argparser.OptionalString{Value: \"1\"},\n\t\t},\n\t\tAutoClone: argparser.OptionalAutoClone{\n\t\t\tOptionalBool: argparser.OptionalBool{\n\t\t\t\tOptional: argparser.Optional{\n\t\t\t\t\tWasSet: true,\n\t\t\t\t},\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t},\n\t\tFormat:            argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: `%h %l %u %t \"%r\" %>s %b`},\n\t\tFormatVersion:     argparser.OptionalInt{Optional: argparser.Optional{WasSet: true}, Value: 2},\n\t\tResponseCondition: argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"Prevent default logging\"},\n\t\tPlacement:         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"none\"},\n\t\tProcessingRegion:  argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"eu\"},\n\t}\n}\n\nfunc createCommandMissingServiceID() *logshuttle.CreateCommand {\n\tres := createCommandAll()\n\tres.Manifest = manifest.Data{}\n\tres.ServiceVersion = argparser.OptionalServiceVersion{}\n\treturn res\n}\n\nfunc updateCommandNoUpdate() *logshuttle.UpdateCommand {\n\tvar b bytes.Buffer\n\n\tg := global.Data{\n\t\tConfig: config.File{},\n\t\tEnv:    config.Environment{},\n\t\tOutput: &b,\n\t}\n\n\treturn &logshuttle.UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: &g,\n\t\t},\n\t\tManifest: manifest.Data{\n\t\t\tFlag: manifest.Flag{\n\t\t\t\tServiceID: \"123\",\n\t\t\t},\n\t\t},\n\t\tEndpointName: \"log\",\n\t\tServiceVersion: argparser.OptionalServiceVersion{\n\t\t\tOptionalString: argparser.OptionalString{Value: \"1\"},\n\t\t},\n\t\tAutoClone: argparser.OptionalAutoClone{\n\t\t\tOptionalBool: argparser.OptionalBool{\n\t\t\t\tOptional: argparser.Optional{\n\t\t\t\t\tWasSet: true,\n\t\t\t\t},\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t},\n\t}\n}\n\nfunc updateCommandAll() *logshuttle.UpdateCommand {\n\tvar b bytes.Buffer\n\n\tg := global.Data{\n\t\tConfig: config.File{},\n\t\tEnv:    config.Environment{},\n\t\tOutput: &b,\n\t}\n\n\treturn &logshuttle.UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: &g,\n\t\t},\n\t\tManifest: manifest.Data{\n\t\t\tFlag: manifest.Flag{\n\t\t\t\tServiceID: \"123\",\n\t\t\t},\n\t\t},\n\t\tEndpointName: \"log\",\n\t\tServiceVersion: argparser.OptionalServiceVersion{\n\t\t\tOptionalString: argparser.OptionalString{Value: \"1\"},\n\t\t},\n\t\tAutoClone: argparser.OptionalAutoClone{\n\t\t\tOptionalBool: argparser.OptionalBool{\n\t\t\t\tOptional: argparser.Optional{\n\t\t\t\t\tWasSet: true,\n\t\t\t\t},\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t},\n\t\tNewName:           argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new1\"},\n\t\tFormat:            argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new2\"},\n\t\tFormatVersion:     argparser.OptionalInt{Optional: argparser.Optional{WasSet: true}, Value: 3},\n\t\tToken:             argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new3\"},\n\t\tURL:               argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new4\"},\n\t\tResponseCondition: argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new5\"},\n\t\tPlacement:         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new6\"},\n\t\tProcessingRegion:  argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"eu\"},\n\t}\n}\n\nfunc updateCommandMissingServiceID() *logshuttle.UpdateCommand {\n\tres := updateCommandAll()\n\tres.Manifest = manifest.Data{}\n\tres.ServiceVersion = argparser.OptionalServiceVersion{}\n\treturn res\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/logshuttle/root.go",
    "content": "package logshuttle\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"logshuttle\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manipulate Fastly service version Logshuttle logging endpoints\")\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/logshuttle/update.go",
    "content": "package logshuttle\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/service/logging/logflags\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/manifest\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// UpdateCommand calls the Fastly API to update a Logshuttle logging endpoint.\ntype UpdateCommand struct {\n\targparser.Base\n\tManifest manifest.Data\n\n\t// Required.\n\tEndpointName   string // Can't shadow argparser.Base method Name().\n\tServiceName    argparser.OptionalServiceNameID\n\tServiceVersion argparser.OptionalServiceVersion\n\n\t// Optional.\n\tAutoClone         argparser.OptionalAutoClone\n\tFormat            argparser.OptionalString\n\tFormatVersion     argparser.OptionalInt\n\tNewName           argparser.OptionalString\n\tPlacement         argparser.OptionalString\n\tProcessingRegion  argparser.OptionalString\n\tResponseCondition argparser.OptionalString\n\tToken             argparser.OptionalString\n\tURL               argparser.OptionalString\n}\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"update\", \"Update a Logshuttle logging endpoint on a Fastly service version\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"The name of the Logshuttle logging object\").Short('n').Required().StringVar(&c.EndpointName)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.ServiceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.CmdClause.Flag(\"auth-token\", \"The data authentication token associated with this endpoint\").Action(c.Token.Set).StringVar(&c.Token.Value)\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.AutoClone.Set,\n\t\tDst:    &c.AutoClone.Value,\n\t})\n\tlogflags.Format(c.CmdClause, &c.Format)\n\tlogflags.FormatVersion(c.CmdClause, &c.FormatVersion)\n\tc.CmdClause.Flag(\"new-name\", \"New name of the Logshuttle logging object\").Action(c.NewName.Set).StringVar(&c.NewName.Value)\n\tlogflags.Placement(c.CmdClause, &c.Placement)\n\tlogflags.ProcessingRegion(c.CmdClause, &c.ProcessingRegion, \"Logshuttle\")\n\tlogflags.ResponseCondition(c.CmdClause, &c.ResponseCondition)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.ServiceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.ServiceName.Value,\n\t})\n\tc.CmdClause.Flag(\"url\", \"Your Logshuttle endpoint url\").Action(c.URL.Set).StringVar(&c.URL.Value)\n\treturn &c\n}\n\n// ConstructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *UpdateCommand) ConstructInput(serviceID string, serviceVersion int) (*fastly.UpdateLogshuttleInput, error) {\n\tinput := fastly.UpdateLogshuttleInput{\n\t\tServiceID:      serviceID,\n\t\tServiceVersion: serviceVersion,\n\t\tName:           c.EndpointName,\n\t}\n\n\t// Set new values if set by user.\n\tif c.NewName.WasSet {\n\t\tinput.NewName = &c.NewName.Value\n\t}\n\n\tif c.Format.WasSet {\n\t\tinput.Format = fastly.ToPointer(argparser.Content(c.Format.Value))\n\t}\n\n\tif c.FormatVersion.WasSet {\n\t\tinput.FormatVersion = &c.FormatVersion.Value\n\t}\n\n\tif c.URL.WasSet {\n\t\tinput.URL = &c.URL.Value\n\t}\n\n\tif c.Token.WasSet {\n\t\tinput.Token = &c.Token.Value\n\t}\n\n\tif c.ResponseCondition.WasSet {\n\t\tinput.ResponseCondition = &c.ResponseCondition.Value\n\t}\n\n\tif c.Placement.WasSet {\n\t\tinput.Placement = &c.Placement.Value\n\t}\n\n\tif c.ProcessingRegion.WasSet {\n\t\tinput.ProcessingRegion = &c.ProcessingRegion.Value\n\t}\n\n\treturn &input, nil\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *UpdateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(false),\n\t\tLocked:             optional.Of(false),\n\t\tAutoCloneFlag:      c.AutoClone,\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.ServiceName,\n\t\tServiceVersionFlag: c.ServiceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": errors.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tinput, err := c.ConstructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tlogshuttle, err := c.Globals.APIClient.UpdateLogshuttle(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\ttext.Success(\n\t\tout,\n\t\t\"Updated Logshuttle logging endpoint %s (service %s version %d)\",\n\t\tfastly.ToValue(logshuttle.Name),\n\t\tfastly.ToValue(logshuttle.ServiceID),\n\t\tfastly.ToValue(logshuttle.ServiceVersion),\n\t)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/newrelic/create.go",
    "content": "package newrelic\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/service/logging/logflags\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// CreateCommand calls the Fastly API to create an appropriate resource.\ntype CreateCommand struct {\n\targparser.Base\n\n\t// Required.\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n\n\t// Optional.\n\tautoClone         argparser.OptionalAutoClone\n\tformat            argparser.OptionalString\n\tformatVersion     argparser.OptionalInt\n\tkey               argparser.OptionalString\n\tname              argparser.OptionalString\n\tplacement         argparser.OptionalString\n\tprocessingregion  argparser.OptionalString\n\tregion            argparser.OptionalString\n\tresponseCondition argparser.OptionalString\n}\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"create\", \"Create an New Relic logging endpoint attached to the specified service version\").Alias(\"add\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"The name for the real-time logging configuration\").Action(c.name.Set).StringVar(&c.name.Value)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.autoClone.Set,\n\t\tDst:    &c.autoClone.Value,\n\t})\n\tlogflags.Format(c.CmdClause, &c.format)\n\tlogflags.FormatVersion(c.CmdClause, &c.formatVersion)\n\tc.CmdClause.Flag(\"key\", \"The Insert API key from the Account page of your New Relic account\").Action(c.key.Set).StringVar(&c.key.Value)\n\tc.CmdClause.Flag(\"placement\", \"Where in the generated VCL the logging call should be placed\").Action(c.placement.Set).StringVar(&c.placement.Value)\n\tlogflags.ProcessingRegion(c.CmdClause, &c.processingregion, \"New Relic\")\n\tc.CmdClause.Flag(\"region\", \"The region where logs are received and stored by New Relic\").Action(c.region.Set).StringVar(&c.region.Value)\n\tc.CmdClause.Flag(\"response-condition\", \"The name of an existing condition in the configured endpoint\").Action(c.responseCondition.Set).StringVar(&c.responseCondition.Value)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *CreateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(false),\n\t\tLocked:             optional.Of(false),\n\t\tAutoCloneFlag:      c.autoClone,\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": errors.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tinput := c.constructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\n\tl, err := c.Globals.APIClient.CreateNewRelic(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fastly.ToValue(serviceVersion.Number),\n\t\t})\n\t\treturn err\n\t}\n\n\ttext.Success(\n\t\tout,\n\t\t\"Created New Relic logging endpoint '%s' (service: %s, version: %d)\",\n\t\tfastly.ToValue(l.Name),\n\t\tfastly.ToValue(l.ServiceID),\n\t\tfastly.ToValue(l.ServiceVersion),\n\t)\n\treturn nil\n}\n\n// constructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *CreateCommand) constructInput(serviceID string, serviceVersion int) *fastly.CreateNewRelicInput {\n\tvar input fastly.CreateNewRelicInput\n\n\tif c.name.WasSet {\n\t\tinput.Name = &c.name.Value\n\t}\n\tinput.ServiceID = serviceID\n\tinput.ServiceVersion = serviceVersion\n\tif c.key.WasSet {\n\t\tinput.Token = &c.key.Value\n\t}\n\n\tif c.format.WasSet {\n\t\tinput.Format = fastly.ToPointer(argparser.Content(c.format.Value))\n\t}\n\n\tif c.formatVersion.WasSet {\n\t\tinput.FormatVersion = &c.formatVersion.Value\n\t}\n\n\tif c.placement.WasSet {\n\t\tinput.Placement = &c.placement.Value\n\t}\n\n\tif c.processingregion.WasSet {\n\t\tinput.ProcessingRegion = &c.processingregion.Value\n\t}\n\n\tif c.region.WasSet {\n\t\tinput.Region = &c.region.Value\n\t}\n\n\tif c.responseCondition.WasSet {\n\t\tinput.ResponseCondition = &c.responseCondition.Value\n\t}\n\n\treturn &input\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/newrelic/delete.go",
    "content": "package newrelic\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"delete\", \"Delete the New Relic Logs logging object for a particular service and version\").Alias(\"remove\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"The name for the real-time logging configuration to delete\").Required().StringVar(&c.name)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.autoClone.Set,\n\t\tDst:    &c.autoClone.Value,\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\n\treturn &c\n}\n\n// DeleteCommand calls the Fastly API to delete an appropriate resource.\ntype DeleteCommand struct {\n\targparser.Base\n\n\tautoClone      argparser.OptionalAutoClone\n\tname           string\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DeleteCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(false),\n\t\tLocked:             optional.Of(false),\n\t\tAutoCloneFlag:      c.autoClone,\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": errors.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tinput := c.constructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\n\terr = c.Globals.APIClient.DeleteNewRelic(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fastly.ToValue(serviceVersion.Number),\n\t\t})\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Deleted New Relic logging endpoint '%s' (service: %s, version: %d)\", c.name, serviceID, fastly.ToValue(serviceVersion.Number))\n\treturn nil\n}\n\n// constructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *DeleteCommand) constructInput(serviceID string, serviceVersion int) *fastly.DeleteNewRelicInput {\n\tvar input fastly.DeleteNewRelicInput\n\n\tinput.Name = c.name\n\tinput.ServiceID = serviceID\n\tinput.ServiceVersion = serviceVersion\n\n\treturn &input\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/newrelic/describe.go",
    "content": "package newrelic\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// NewDescribeCommand returns a usable command registered under the parent.\nfunc NewDescribeCommand(parent argparser.Registerer, g *global.Data) *DescribeCommand {\n\tc := DescribeCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"describe\", \"Get the details of a New Relic Logs logging object for a particular service and version\").Alias(\"get\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"The name for the real-time logging configuration\").Required().StringVar(&c.name)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\n\treturn &c\n}\n\n// DescribeCommand calls the Fastly API to describe an appropriate resource.\ntype DescribeCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tname           string\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DescribeCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fsterr.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tinput := c.constructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\n\to, err := c.Globals.APIClient.GetNewRelic(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fastly.ToValue(serviceVersion.Number),\n\t\t})\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\treturn c.print(out, o)\n}\n\n// constructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *DescribeCommand) constructInput(serviceID string, serviceVersion int) *fastly.GetNewRelicInput {\n\tvar input fastly.GetNewRelicInput\n\n\tinput.Name = c.name\n\tinput.ServiceID = serviceID\n\tinput.ServiceVersion = serviceVersion\n\n\treturn &input\n}\n\n// print displays the information returned from the API.\nfunc (c *DescribeCommand) print(out io.Writer, nr *fastly.NewRelic) error {\n\tlines := text.Lines{\n\t\t\"Format Version\":     fastly.ToValue(nr.FormatVersion),\n\t\t\"Format\":             fastly.ToValue(nr.Format),\n\t\t\"Name\":               fastly.ToValue(nr.Name),\n\t\t\"Placement\":          fastly.ToValue(nr.Placement),\n\t\t\"Processing region\":  fastly.ToValue(nr.ProcessingRegion),\n\t\t\"Region\":             fastly.ToValue(nr.Region),\n\t\t\"Response Condition\": fastly.ToValue(nr.ResponseCondition),\n\t\t\"Service Version\":    fastly.ToValue(nr.ServiceVersion),\n\t\t\"Token\":              fastly.ToValue(nr.Token),\n\t}\n\tif nr.CreatedAt != nil {\n\t\tlines[\"Created at\"] = nr.CreatedAt\n\t}\n\tif nr.UpdatedAt != nil {\n\t\tlines[\"Updated at\"] = nr.UpdatedAt\n\t}\n\tif nr.DeletedAt != nil {\n\t\tlines[\"Deleted at\"] = nr.DeletedAt\n\t}\n\n\tif !c.Globals.Verbose() {\n\t\tlines[\"Service ID\"] = fastly.ToValue(nr.ServiceID)\n\t}\n\ttext.PrintLines(out, lines)\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/newrelic/doc.go",
    "content": "// Package newrelic contains commands to inspect and manipulate NewRelic logging\n// endpoints.\npackage newrelic\n"
  },
  {
    "path": "pkg/commands/service/logging/newrelic/list.go",
    "content": "package newrelic\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"list\", \"List all of the New Relic Logs logging objects for a particular service and version\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\n\treturn &c\n}\n\n// ListCommand calls the Fastly API to list appropriate resources.\ntype ListCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *ListCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fsterr.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tinput := c.constructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\n\to, err := c.Globals.APIClient.ListNewRelic(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": serviceVersion.Number,\n\t\t})\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\tif c.Globals.Verbose() {\n\t\tc.printVerbose(out, fastly.ToValue(serviceVersion.Number), o)\n\t} else {\n\t\terr = c.printSummary(out, o)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\n// constructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *ListCommand) constructInput(serviceID string, serviceVersion int) *fastly.ListNewRelicInput {\n\tvar input fastly.ListNewRelicInput\n\n\tinput.ServiceID = serviceID\n\tinput.ServiceVersion = serviceVersion\n\n\treturn &input\n}\n\n// printVerbose displays the information returned from the API in a verbose\n// format.\nfunc (c *ListCommand) printVerbose(out io.Writer, serviceVersion int, ls []*fastly.NewRelic) {\n\tfmt.Fprintf(out, \"Service Version: %d\\n\", serviceVersion)\n\n\tfor _, l := range ls {\n\t\tfmt.Fprintf(out, \"\\nName: %s\\n\", fastly.ToValue(l.Name))\n\t\tfmt.Fprintf(out, \"\\nToken: %s\\n\", fastly.ToValue(l.Token))\n\t\tfmt.Fprintf(out, \"\\nFormat: %s\\n\", fastly.ToValue(l.Format))\n\t\tfmt.Fprintf(out, \"\\nFormat Version: %d\\n\", fastly.ToValue(l.FormatVersion))\n\t\tfmt.Fprintf(out, \"\\nPlacement: %s\\n\", fastly.ToValue(l.Placement))\n\t\tfmt.Fprintf(out, \"\\nRegion: %s\\n\", fastly.ToValue(l.Region))\n\t\tfmt.Fprintf(out, \"\\nProcessing region: %s\\n\", fastly.ToValue(l.ProcessingRegion))\n\t\tfmt.Fprintf(out, \"\\nResponse Condition: %s\\n\\n\", fastly.ToValue(l.ResponseCondition))\n\n\t\tif l.CreatedAt != nil {\n\t\t\tfmt.Fprintf(out, \"Created at: %s\\n\", l.CreatedAt)\n\t\t}\n\t\tif l.UpdatedAt != nil {\n\t\t\tfmt.Fprintf(out, \"Updated at: %s\\n\", l.UpdatedAt)\n\t\t}\n\t\tif l.DeletedAt != nil {\n\t\t\tfmt.Fprintf(out, \"Deleted at: %s\\n\", l.DeletedAt)\n\t\t}\n\t}\n}\n\n// printSummary displays the information returned from the API in a summarised\n// format.\nfunc (c *ListCommand) printSummary(out io.Writer, nrs []*fastly.NewRelic) error {\n\tt := text.NewTable(out)\n\tt.AddHeader(\"SERVICE ID\", \"VERSION\", \"NAME\")\n\tfor _, nr := range nrs {\n\t\tt.AddLine(\n\t\t\tfastly.ToValue(nr.ServiceID),\n\t\t\tfastly.ToValue(nr.ServiceVersion),\n\t\t\tfastly.ToValue(nr.Name),\n\t\t)\n\t}\n\tt.Print()\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/newrelic/newrelic_test.go",
    "content": "package newrelic_test\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\tserviceRoot \"github.com/fastly/cli/pkg/commands/service\"\n\tloggingRoot \"github.com/fastly/cli/pkg/commands/service/logging\"\n\tsub \"github.com/fastly/cli/pkg/commands/service/logging/newrelic\"\n\t\"github.com/fastly/cli/pkg/mock\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n)\n\nfunc TestNewRelicCreate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --service-id flag\",\n\t\t\tArgs:      \"--key abc --name foo --version 3\",\n\t\t\tEnvVars:   map[string]string{\"FASTLY_SERVICE_ID\": \"\"},\n\t\t\tWantError: \"error reading service: no service ID found\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate CreateNewRelic API error\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tCreateNewRelicFn: func(_ context.Context, _ *fastly.CreateNewRelicInput) (*fastly.NewRelic, error) {\n\t\t\t\t\treturn nil, testutil.Err\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:      \"--key abc --name foo --service-id 123 --version 3\",\n\t\t\tWantError: testutil.Err.Error(),\n\t\t},\n\t\t{\n\t\t\tName: \"validate CreateNewRelic API success\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tCreateNewRelicFn: func(_ context.Context, i *fastly.CreateNewRelicInput) (*fastly.NewRelic, error) {\n\t\t\t\t\treturn &fastly.NewRelic{\n\t\t\t\t\t\tName:           i.Name,\n\t\t\t\t\t\tServiceID:      fastly.ToPointer(i.ServiceID),\n\t\t\t\t\t\tServiceVersion: fastly.ToPointer(i.ServiceVersion),\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:       \"--key abc --name foo --service-id 123 --version 3\",\n\t\t\tWantOutput: \"Created New Relic logging endpoint 'foo' (service: 123, version: 3)\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate --autoclone results in cloned service version\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tCreateNewRelicFn: func(_ context.Context, i *fastly.CreateNewRelicInput) (*fastly.NewRelic, error) {\n\t\t\t\t\treturn &fastly.NewRelic{\n\t\t\t\t\t\tName:           i.Name,\n\t\t\t\t\t\tServiceID:      fastly.ToPointer(i.ServiceID),\n\t\t\t\t\t\tServiceVersion: fastly.ToPointer(i.ServiceVersion),\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:       \"--autoclone --key abc --name foo --service-id 123 --version 1\",\n\t\t\tWantOutput: \"Created New Relic logging endpoint 'foo' (service: 123, version: 4)\",\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{serviceRoot.CommandName, loggingRoot.CommandName, sub.CommandName, \"create\"}, scenarios)\n}\n\nfunc TestNewRelicDelete(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --name flag\",\n\t\t\tArgs:      \"--version 3\",\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --version flag\",\n\t\t\tArgs:      \"--name foobar\",\n\t\t\tWantError: \"error parsing arguments: required flag --version not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --service-id flag\",\n\t\t\tArgs:      \"--name foobar --version 3\",\n\t\t\tEnvVars:   map[string]string{\"FASTLY_SERVICE_ID\": \"\"},\n\t\t\tWantError: \"error reading service: no service ID found\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate DeleteNewRelic API error\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tDeleteNewRelicFn: func(_ context.Context, _ *fastly.DeleteNewRelicInput) error {\n\t\t\t\t\treturn testutil.Err\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:      \"--name foobar --service-id 123 --version 3\",\n\t\t\tWantError: testutil.Err.Error(),\n\t\t},\n\t\t{\n\t\t\tName: \"validate DeleteNewRelic API success\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tDeleteNewRelicFn: func(_ context.Context, _ *fastly.DeleteNewRelicInput) error {\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:       \"--name foobar --service-id 123 --version 3\",\n\t\t\tWantOutput: \"Deleted New Relic logging endpoint 'foobar' (service: 123, version: 3)\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API error when modifying active version\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tDeleteNewRelicFn: func(_ context.Context, i *fastly.DeleteNewRelicInput) error {\n\t\t\t\t\treturn fmt.Errorf(\"Cannot update version %d. Versions that have been activated cannot be updated\", i.ServiceVersion)\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:      \"--name foobar --service-id 123 --version 3\",\n\t\t\tWantError: \"Cannot update version 3. Versions that have been activated cannot be updated\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API error when modifying locked version\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tDeleteNewRelicFn: func(_ context.Context, i *fastly.DeleteNewRelicInput) error {\n\t\t\t\t\treturn fmt.Errorf(\"Cannot update version %d. Versions that have been locked cannot be updated\", i.ServiceVersion)\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:      \"--name foobar --service-id 123 --version 3\",\n\t\t\tWantError: \"Cannot update version 3. Versions that have been locked cannot be updated\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate --autoclone results in cloned service version\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tDeleteNewRelicFn: func(_ context.Context, _ *fastly.DeleteNewRelicInput) error {\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:       \"--autoclone --name foo --service-id 123 --version 1\",\n\t\t\tWantOutput: \"Deleted New Relic logging endpoint 'foo' (service: 123, version: 4)\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate --autoclone on locked version\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tDeleteNewRelicFn: func(_ context.Context, i *fastly.DeleteNewRelicInput) error {\n\t\t\t\t\t// Verify operation happens on the cloned version (4), not original (2)\n\t\t\t\t\tif i.ServiceVersion != 4 {\n\t\t\t\t\t\treturn fmt.Errorf(\"expected operation on cloned version 4, got %d\", i.ServiceVersion)\n\t\t\t\t\t}\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:       \"--autoclone --name foo --service-id 123 --version 2\",\n\t\t\tWantOutput: \"Deleted New Relic logging endpoint 'foo' (service: 123, version: 4)\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate --autoclone on editable version\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tDeleteNewRelicFn: func(_ context.Context, i *fastly.DeleteNewRelicInput) error {\n\t\t\t\t\t// Verify operation happens on the cloned version (4), not original (3)\n\t\t\t\t\tif i.ServiceVersion != 4 {\n\t\t\t\t\t\treturn fmt.Errorf(\"expected operation on cloned version 4, got %d\", i.ServiceVersion)\n\t\t\t\t\t}\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:       \"--autoclone --name foo --service-id 123 --version 3\",\n\t\t\tWantOutput: \"Deleted New Relic logging endpoint 'foo' (service: 123, version: 4)\",\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{serviceRoot.CommandName, loggingRoot.CommandName, sub.CommandName, \"delete\"}, scenarios)\n}\n\nfunc TestNewRelicDescribe(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --name flag\",\n\t\t\tArgs:      \"--version 3\",\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --version flag\",\n\t\t\tArgs:      \"--name foobar\",\n\t\t\tWantError: \"error parsing arguments: required flag --version not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --service-id flag\",\n\t\t\tArgs:      \"--name foobar --version 3\",\n\t\t\tWantError: \"error reading service: no service ID found\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate GetNewRelic API error\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tGetNewRelicFn: func(_ context.Context, _ *fastly.GetNewRelicInput) (*fastly.NewRelic, error) {\n\t\t\t\t\treturn nil, testutil.Err\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:      \"--name foobar --service-id 123 --version 3\",\n\t\t\tWantError: testutil.Err.Error(),\n\t\t},\n\t\t{\n\t\t\tName: \"validate GetNewRelic API success\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:  testutil.GetVersion,\n\t\t\t\tGetNewRelicFn: getNewRelic,\n\t\t\t},\n\t\t\tArgs:       \"--name foobar --service-id 123 --version 3\",\n\t\t\tWantOutput: \"\\nCreated at: 2021-06-15 23:00:00 +0000 UTC\\nDeleted at: 2021-06-15 23:00:00 +0000 UTC\\nFormat: \\nFormat Version: 0\\nName: foobar\\nPlacement: \\nProcessing region: \\nRegion: \\nResponse Condition: \\nService ID: 123\\nService Version: 3\\nToken: abc\\nUpdated at: 2021-06-15 23:00:00 +0000 UTC\\n\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate missing --autoclone flag is OK\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:  testutil.GetVersion,\n\t\t\t\tGetNewRelicFn: getNewRelic,\n\t\t\t},\n\t\t\tArgs:       \"--name foobar --service-id 123 --version 1\",\n\t\t\tWantOutput: \"\\nCreated at: 2021-06-15 23:00:00 +0000 UTC\\nDeleted at: 2021-06-15 23:00:00 +0000 UTC\\nFormat: \\nFormat Version: 0\\nName: foobar\\nPlacement: \\nProcessing region: \\nRegion: \\nResponse Condition: \\nService ID: 123\\nService Version: 1\\nToken: abc\\nUpdated at: 2021-06-15 23:00:00 +0000 UTC\\n\",\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{serviceRoot.CommandName, loggingRoot.CommandName, sub.CommandName, \"describe\"}, scenarios)\n}\n\nfunc TestNewRelicList(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --version flag\",\n\t\t\tWantError: \"error parsing arguments: required flag --version not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --service-id flag\",\n\t\t\tArgs:      \"--version 3\",\n\t\t\tWantError: \"error reading service: no service ID found\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate ListNewRelics API error\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tListNewRelicFn: func(_ context.Context, _ *fastly.ListNewRelicInput) ([]*fastly.NewRelic, error) {\n\t\t\t\t\treturn nil, testutil.Err\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:      \"--service-id 123 --version 3\",\n\t\t\tWantError: testutil.Err.Error(),\n\t\t},\n\t\t{\n\t\t\tName: \"validate ListNewRelics API success\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tListNewRelicFn: listNewRelic,\n\t\t\t},\n\t\t\tArgs:       \"--service-id 123 --version 3\",\n\t\t\tWantOutput: \"SERVICE ID  VERSION  NAME\\n123         3        foo\\n123         3        bar\\n\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate missing --autoclone flag is OK\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tListNewRelicFn: listNewRelic,\n\t\t\t},\n\t\t\tArgs:       \"--service-id 123 --version 1\",\n\t\t\tWantOutput: \"SERVICE ID  VERSION  NAME\\n123         1        foo\\n123         1        bar\\n\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate missing --verbose flag\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tListNewRelicFn: listNewRelic,\n\t\t\t},\n\t\t\tArgs:       \"--service-id 123 --verbose --version 1\",\n\t\t\tWantOutput: \"Fastly API endpoint: https://api.fastly.com\\nFastly API token provided via config file (auth: user)\\n\\nService ID (via --service-id): 123\\n\\nService Version: 1\\n\\nName: foo\\n\\nToken: \\n\\nFormat: \\n\\nFormat Version: 0\\n\\nPlacement: \\n\\nRegion: \\n\\nProcessing region: \\n\\nResponse Condition: \\n\\nCreated at: 2021-06-15 23:00:00 +0000 UTC\\nUpdated at: 2021-06-15 23:00:00 +0000 UTC\\nDeleted at: 2021-06-15 23:00:00 +0000 UTC\\n\\nName: bar\\n\\nToken: \\n\\nFormat: \\n\\nFormat Version: 0\\n\\nPlacement: \\n\\nRegion: \\n\\nProcessing region: \\n\\nResponse Condition: \\n\\nCreated at: 2021-06-15 23:00:00 +0000 UTC\\nUpdated at: 2021-06-15 23:00:00 +0000 UTC\\nDeleted at: 2021-06-15 23:00:00 +0000 UTC\\n\",\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{serviceRoot.CommandName, loggingRoot.CommandName, sub.CommandName, \"list\"}, scenarios)\n}\n\nfunc TestNewRelicUpdate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --name flag\",\n\t\t\tArgs:      \"--service-id 123 --version 3\",\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --version flag\",\n\t\t\tArgs:      \"--name foobar --service-id 123\",\n\t\t\tWantError: \"error parsing arguments: required flag --version not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --service-id flag\",\n\t\t\tArgs:      \"--name foobar --version 3\",\n\t\t\tEnvVars:   map[string]string{\"FASTLY_SERVICE_ID\": \"\"},\n\t\t\tWantError: \"error reading service: no service ID found\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate UpdateNewRelic API error\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tUpdateNewRelicFn: func(_ context.Context, _ *fastly.UpdateNewRelicInput) (*fastly.NewRelic, error) {\n\t\t\t\t\treturn nil, testutil.Err\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:      \"--name foobar --new-name beepboop --service-id 123 --version 3\",\n\t\t\tWantError: testutil.Err.Error(),\n\t\t},\n\t\t{\n\t\t\tName: \"validate UpdateNewRelic API success\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tUpdateNewRelicFn: func(_ context.Context, i *fastly.UpdateNewRelicInput) (*fastly.NewRelic, error) {\n\t\t\t\t\treturn &fastly.NewRelic{\n\t\t\t\t\t\tName:           i.NewName,\n\t\t\t\t\t\tServiceID:      fastly.ToPointer(i.ServiceID),\n\t\t\t\t\t\tServiceVersion: fastly.ToPointer(i.ServiceVersion),\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:       \"--name foobar --new-name beepboop --service-id 123 --version 3\",\n\t\t\tWantOutput: \"Updated New Relic logging endpoint 'beepboop' (previously: foobar, service: 123, version: 3)\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API error when modifying active version\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tUpdateNewRelicFn: func(_ context.Context, i *fastly.UpdateNewRelicInput) (*fastly.NewRelic, error) {\n\t\t\t\t\treturn nil, fmt.Errorf(\"Cannot update version %d. Versions that have been activated cannot be updated\", i.ServiceVersion)\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:      \"--name foobar --new-name beepboop --service-id 123 --version 3\",\n\t\t\tWantError: \"Cannot update version 3. Versions that have been activated cannot be updated\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API error when modifying locked version\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tUpdateNewRelicFn: func(_ context.Context, i *fastly.UpdateNewRelicInput) (*fastly.NewRelic, error) {\n\t\t\t\t\treturn nil, fmt.Errorf(\"Cannot update version %d. Versions that have been locked cannot be updated\", i.ServiceVersion)\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:      \"--name foobar --new-name beepboop --service-id 123 --version 3\",\n\t\t\tWantError: \"Cannot update version 3. Versions that have been locked cannot be updated\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate --autoclone results in cloned service version\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tUpdateNewRelicFn: func(_ context.Context, i *fastly.UpdateNewRelicInput) (*fastly.NewRelic, error) {\n\t\t\t\t\treturn &fastly.NewRelic{\n\t\t\t\t\t\tName:           i.NewName,\n\t\t\t\t\t\tServiceID:      fastly.ToPointer(i.ServiceID),\n\t\t\t\t\t\tServiceVersion: fastly.ToPointer(i.ServiceVersion),\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:       \"--autoclone --name foobar --new-name beepboop --service-id 123 --version 1\",\n\t\t\tWantOutput: \"Updated New Relic logging endpoint 'beepboop' (previously: foobar, service: 123, version: 4)\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate --autoclone on locked version\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tUpdateNewRelicFn: func(_ context.Context, i *fastly.UpdateNewRelicInput) (*fastly.NewRelic, error) {\n\t\t\t\t\t// Verify operation happens on the cloned version (4), not original (2)\n\t\t\t\t\tif i.ServiceVersion != 4 {\n\t\t\t\t\t\treturn nil, fmt.Errorf(\"expected operation on cloned version 4, got %d\", i.ServiceVersion)\n\t\t\t\t\t}\n\t\t\t\t\treturn &fastly.NewRelic{\n\t\t\t\t\t\tName:           i.NewName,\n\t\t\t\t\t\tServiceID:      fastly.ToPointer(i.ServiceID),\n\t\t\t\t\t\tServiceVersion: fastly.ToPointer(i.ServiceVersion),\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:       \"--autoclone --name foobar --new-name beepboop --service-id 123 --version 2\",\n\t\t\tWantOutput: \"Updated New Relic logging endpoint 'beepboop' (previously: foobar, service: 123, version: 4)\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate --autoclone on editable version\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tUpdateNewRelicFn: func(_ context.Context, i *fastly.UpdateNewRelicInput) (*fastly.NewRelic, error) {\n\t\t\t\t\t// Verify operation happens on the cloned version (4), not original (3)\n\t\t\t\t\tif i.ServiceVersion != 4 {\n\t\t\t\t\t\treturn nil, fmt.Errorf(\"expected operation on cloned version 4, got %d\", i.ServiceVersion)\n\t\t\t\t\t}\n\t\t\t\t\treturn &fastly.NewRelic{\n\t\t\t\t\t\tName:           i.NewName,\n\t\t\t\t\t\tServiceID:      fastly.ToPointer(i.ServiceID),\n\t\t\t\t\t\tServiceVersion: fastly.ToPointer(i.ServiceVersion),\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:       \"--autoclone --name foobar --new-name beepboop --service-id 123 --version 3\",\n\t\t\tWantOutput: \"Updated New Relic logging endpoint 'beepboop' (previously: foobar, service: 123, version: 4)\",\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{serviceRoot.CommandName, loggingRoot.CommandName, sub.CommandName, \"update\"}, scenarios)\n}\n\nfunc getNewRelic(_ context.Context, i *fastly.GetNewRelicInput) (*fastly.NewRelic, error) {\n\tt := testutil.Date\n\n\treturn &fastly.NewRelic{\n\t\tName:           fastly.ToPointer(i.Name),\n\t\tToken:          fastly.ToPointer(\"abc\"),\n\t\tServiceID:      fastly.ToPointer(i.ServiceID),\n\t\tServiceVersion: fastly.ToPointer(i.ServiceVersion),\n\n\t\tCreatedAt: &t,\n\t\tDeletedAt: &t,\n\t\tUpdatedAt: &t,\n\t}, nil\n}\n\nfunc listNewRelic(_ context.Context, i *fastly.ListNewRelicInput) ([]*fastly.NewRelic, error) {\n\tt := testutil.Date\n\tvs := []*fastly.NewRelic{\n\t\t{\n\t\t\tName:           fastly.ToPointer(\"foo\"),\n\t\t\tServiceID:      fastly.ToPointer(i.ServiceID),\n\t\t\tServiceVersion: fastly.ToPointer(i.ServiceVersion),\n\n\t\t\tCreatedAt: &t,\n\t\t\tDeletedAt: &t,\n\t\t\tUpdatedAt: &t,\n\t\t},\n\t\t{\n\t\t\tName:           fastly.ToPointer(\"bar\"),\n\t\t\tServiceID:      fastly.ToPointer(i.ServiceID),\n\t\t\tServiceVersion: fastly.ToPointer(i.ServiceVersion),\n\n\t\t\tCreatedAt: &t,\n\t\t\tDeletedAt: &t,\n\t\t\tUpdatedAt: &t,\n\t\t},\n\t}\n\treturn vs, nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/newrelic/root.go",
    "content": "package newrelic\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"newrelic\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manipulate a NewRelic logging endpoint for a specific Fastly service version\")\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/newrelic/update.go",
    "content": "package newrelic\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/service/logging/logflags\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// UpdateCommand calls the Fastly API to update an appropriate resource.\ntype UpdateCommand struct {\n\targparser.Base\n\n\tendpointName   string\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n\n\tautoClone         argparser.OptionalAutoClone\n\tformat            argparser.OptionalString\n\tformatVersion     argparser.OptionalInt\n\tkey               argparser.OptionalString\n\tnewName           argparser.OptionalString\n\tplacement         argparser.OptionalString\n\tprocessingregion  argparser.OptionalString\n\tregion            argparser.OptionalString\n\tresponseCondition argparser.OptionalString\n}\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"update\", \"Update a New Relic Logs logging object for a particular service and version\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"The name for the real-time logging configuration to update\").Required().StringVar(&c.endpointName)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.autoClone.Set,\n\t\tDst:    &c.autoClone.Value,\n\t})\n\tlogflags.Format(c.CmdClause, &c.format)\n\tc.CmdClause.Flag(\"format-version\", \"The version of the custom logging format used for the configured endpoint\").Action(c.formatVersion.Set).IntVar(&c.formatVersion.Value)\n\tc.CmdClause.Flag(\"key\", \"The Insert API key from the Account page of your New Relic account\").Action(c.key.Set).StringVar(&c.key.Value)\n\tc.CmdClause.Flag(\"new-name\", \"The name for the real-time logging configuration\").Action(c.newName.Set).StringVar(&c.newName.Value)\n\tc.CmdClause.Flag(\"placement\", \"Where in the generated VCL the logging call should be placed\").Action(c.placement.Set).StringVar(&c.placement.Value)\n\tlogflags.ProcessingRegion(c.CmdClause, &c.processingregion, \"New Relic\")\n\tc.CmdClause.Flag(\"region\", \"The region where logs are received and stored by New Relic\").Action(c.region.Set).StringVar(&c.region.Value)\n\tc.CmdClause.Flag(\"response-condition\", \"The name of an existing condition in the configured endpoint\").Action(c.responseCondition.Set).StringVar(&c.responseCondition.Value)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *UpdateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(false),\n\t\tLocked:             optional.Of(false),\n\t\tAutoCloneFlag:      c.autoClone,\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": errors.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tinput := c.constructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\n\tl, err := c.Globals.APIClient.UpdateNewRelic(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fastly.ToValue(serviceVersion.Number),\n\t\t})\n\t\treturn err\n\t}\n\n\tvar prev string\n\tif c.newName.WasSet {\n\t\tprev = fmt.Sprintf(\"previously: %s, \", c.endpointName)\n\t}\n\n\ttext.Success(\n\t\tout,\n\t\t\"Updated New Relic logging endpoint '%s' (%sservice: %s, version: %d)\",\n\t\tfastly.ToValue(l.Name),\n\t\tprev,\n\t\tfastly.ToValue(l.ServiceID),\n\t\tfastly.ToValue(l.ServiceVersion),\n\t)\n\treturn nil\n}\n\n// constructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *UpdateCommand) constructInput(serviceID string, serviceVersion int) *fastly.UpdateNewRelicInput {\n\tvar input fastly.UpdateNewRelicInput\n\n\tinput.Name = c.endpointName\n\tinput.ServiceID = serviceID\n\tinput.ServiceVersion = serviceVersion\n\n\tif c.format.WasSet {\n\t\tinput.Format = fastly.ToPointer(argparser.Content(c.format.Value))\n\t}\n\tif c.formatVersion.WasSet {\n\t\tinput.FormatVersion = &c.formatVersion.Value\n\t}\n\tif c.key.WasSet {\n\t\tinput.Token = &c.key.Value\n\t}\n\tif c.newName.WasSet {\n\t\tinput.NewName = &c.newName.Value\n\t}\n\tif c.placement.WasSet {\n\t\tinput.Placement = &c.placement.Value\n\t}\n\tif c.processingregion.WasSet {\n\t\tinput.ProcessingRegion = &c.processingregion.Value\n\t}\n\tif c.region.WasSet {\n\t\tinput.Region = &c.region.Value\n\t}\n\tif c.responseCondition.WasSet {\n\t\tinput.ResponseCondition = &c.responseCondition.Value\n\t}\n\n\treturn &input\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/newrelicotlp/create.go",
    "content": "package newrelicotlp\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/service/logging/logflags\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// CreateCommand calls the Fastly API to create an appropriate resource.\ntype CreateCommand struct {\n\targparser.Base\n\n\t// Required.\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n\n\t// Optional.\n\tautoClone         argparser.OptionalAutoClone\n\tformat            argparser.OptionalString\n\tformatVersion     argparser.OptionalInt\n\tkey               argparser.OptionalString\n\tname              argparser.OptionalString\n\tplacement         argparser.OptionalString\n\tprocessingregion  argparser.OptionalString\n\tregion            argparser.OptionalString\n\tresponseCondition argparser.OptionalString\n\turl               argparser.OptionalString\n}\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"create\", \"Create an New Relic logging endpoint attached to the specified service version\").Alias(\"add\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"The name for the real-time logging configuration\").Action(c.name.Set).StringVar(&c.name.Value)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.autoClone.Set,\n\t\tDst:    &c.autoClone.Value,\n\t})\n\tlogflags.Format(c.CmdClause, &c.format)\n\tlogflags.FormatVersion(c.CmdClause, &c.formatVersion)\n\tc.CmdClause.Flag(\"key\", \"The Insert API key from the Account page of your New Relic account\").Action(c.key.Set).StringVar(&c.key.Value)\n\tc.CmdClause.Flag(\"placement\", \"Where in the generated VCL the logging call should be placed\").Action(c.placement.Set).StringVar(&c.placement.Value)\n\tlogflags.ProcessingRegion(c.CmdClause, &c.processingregion, \"New Relic\")\n\tc.CmdClause.Flag(\"region\", \"The region to which to stream logs\").Action(c.region.Set).StringVar(&c.region.Value)\n\tc.CmdClause.Flag(\"response-condition\", \"The name of an existing condition in the configured endpoint\").Action(c.responseCondition.Set).StringVar(&c.responseCondition.Value)\n\tc.CmdClause.Flag(\"url\", \"URL of the New Relic Trace Observer, if you are using New Relic Infinite Tracing\").Action(c.url.Set).StringVar(&c.url.Value)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *CreateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(false),\n\t\tLocked:             optional.Of(false),\n\t\tAutoCloneFlag:      c.autoClone,\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": errors.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tinput := c.constructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\n\tl, err := c.Globals.APIClient.CreateNewRelicOTLP(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": serviceVersion.Number,\n\t\t})\n\t\treturn err\n\t}\n\n\ttext.Success(\n\t\tout,\n\t\t\"Created New Relic OTLP logging endpoint '%s' (service: %s, version: %d)\",\n\t\tfastly.ToValue(l.Name),\n\t\tfastly.ToValue(l.ServiceID),\n\t\tfastly.ToValue(l.ServiceVersion),\n\t)\n\treturn nil\n}\n\n// constructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *CreateCommand) constructInput(serviceID string, serviceVersion int) *fastly.CreateNewRelicOTLPInput {\n\tvar input fastly.CreateNewRelicOTLPInput\n\n\tif c.name.WasSet {\n\t\tinput.Name = &c.name.Value\n\t}\n\tinput.ServiceID = serviceID\n\tinput.ServiceVersion = serviceVersion\n\tif c.key.WasSet {\n\t\tinput.Token = &c.key.Value\n\t}\n\n\tif c.format.WasSet {\n\t\tinput.Format = fastly.ToPointer(argparser.Content(c.format.Value))\n\t}\n\n\tif c.formatVersion.WasSet {\n\t\tinput.FormatVersion = &c.formatVersion.Value\n\t}\n\n\tif c.placement.WasSet {\n\t\tinput.Placement = &c.placement.Value\n\t}\n\n\tif c.processingregion.WasSet {\n\t\tinput.ProcessingRegion = &c.processingregion.Value\n\t}\n\n\tif c.region.WasSet {\n\t\tinput.Region = &c.region.Value\n\t}\n\n\tif c.responseCondition.WasSet {\n\t\tinput.ResponseCondition = &c.responseCondition.Value\n\t}\n\n\tif c.url.WasSet {\n\t\tinput.URL = &c.url.Value\n\t}\n\n\treturn &input\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/newrelicotlp/delete.go",
    "content": "package newrelicotlp\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"delete\", \"Delete the New Relic OTLP Logs logging object for a particular service and version\").Alias(\"remove\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"The name for the real-time logging configuration to delete\").Required().StringVar(&c.name)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.autoClone.Set,\n\t\tDst:    &c.autoClone.Value,\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\n\treturn &c\n}\n\n// DeleteCommand calls the Fastly API to delete an appropriate resource.\ntype DeleteCommand struct {\n\targparser.Base\n\n\tautoClone      argparser.OptionalAutoClone\n\tname           string\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DeleteCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(false),\n\t\tLocked:             optional.Of(false),\n\t\tAutoCloneFlag:      c.autoClone,\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": errors.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tinput := c.constructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\n\terr = c.Globals.APIClient.DeleteNewRelicOTLP(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fastly.ToValue(serviceVersion.Number),\n\t\t})\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Deleted New Relic OTLP logging endpoint '%s' (service: %s, version: %d)\", c.name, serviceID, fastly.ToValue(serviceVersion.Number))\n\treturn nil\n}\n\n// constructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *DeleteCommand) constructInput(serviceID string, serviceVersion int) *fastly.DeleteNewRelicOTLPInput {\n\tvar input fastly.DeleteNewRelicOTLPInput\n\n\tinput.Name = c.name\n\tinput.ServiceID = serviceID\n\tinput.ServiceVersion = serviceVersion\n\n\treturn &input\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/newrelicotlp/describe.go",
    "content": "package newrelicotlp\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// NewDescribeCommand returns a usable command registered under the parent.\nfunc NewDescribeCommand(parent argparser.Registerer, g *global.Data) *DescribeCommand {\n\tc := DescribeCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"describe\", \"Get the details of a New Relic OTLP Logs logging object for a particular service and version\").Alias(\"get\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"The name for the real-time logging configuration\").Required().StringVar(&c.name)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\n\treturn &c\n}\n\n// DescribeCommand calls the Fastly API to describe an appropriate resource.\ntype DescribeCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tname           string\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DescribeCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fsterr.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tinput := c.constructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\n\to, err := c.Globals.APIClient.GetNewRelicOTLP(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fastly.ToValue(serviceVersion.Number),\n\t\t})\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\treturn c.print(out, o)\n}\n\n// constructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *DescribeCommand) constructInput(serviceID string, serviceVersion int) *fastly.GetNewRelicOTLPInput {\n\tvar input fastly.GetNewRelicOTLPInput\n\n\tinput.Name = c.name\n\tinput.ServiceID = serviceID\n\tinput.ServiceVersion = serviceVersion\n\n\treturn &input\n}\n\n// print displays the information returned from the API.\nfunc (c *DescribeCommand) print(out io.Writer, nr *fastly.NewRelicOTLP) error {\n\tlines := text.Lines{\n\t\t\"Format Version\":     fastly.ToValue(nr.FormatVersion),\n\t\t\"Format\":             fastly.ToValue(nr.Format),\n\t\t\"Name\":               fastly.ToValue(nr.Name),\n\t\t\"Placement\":          fastly.ToValue(nr.Placement),\n\t\t\"Processing region\":  fastly.ToValue(nr.ProcessingRegion),\n\t\t\"Region\":             fastly.ToValue(nr.Region),\n\t\t\"Response Condition\": fastly.ToValue(nr.ResponseCondition),\n\t\t\"Service Version\":    fastly.ToValue(nr.ServiceVersion),\n\t\t\"Token\":              fastly.ToValue(nr.Token),\n\t\t\"URL\":                fastly.ToValue(nr.URL),\n\t}\n\tif nr.CreatedAt != nil {\n\t\tlines[\"Created at\"] = nr.CreatedAt\n\t}\n\tif nr.UpdatedAt != nil {\n\t\tlines[\"Updated at\"] = nr.UpdatedAt\n\t}\n\tif nr.DeletedAt != nil {\n\t\tlines[\"Deleted at\"] = nr.DeletedAt\n\t}\n\n\tif !c.Globals.Verbose() {\n\t\tlines[\"Service ID\"] = fastly.ToValue(nr.ServiceID)\n\t}\n\ttext.PrintLines(out, lines)\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/newrelicotlp/doc.go",
    "content": "// Package newrelicotlp contains commands to inspect and manipulate NewRelicOTLP logging\n// endpoints.\npackage newrelicotlp\n"
  },
  {
    "path": "pkg/commands/service/logging/newrelicotlp/list.go",
    "content": "package newrelicotlp\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"list\", \"List all of the New Relic OTLP Logs logging objects for a particular service and version\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\n\treturn &c\n}\n\n// ListCommand calls the Fastly API to list appropriate resources.\ntype ListCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *ListCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fsterr.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tinput := c.constructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\n\to, err := c.Globals.APIClient.ListNewRelicOTLP(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fastly.ToValue(serviceVersion.Number),\n\t\t})\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\tif c.Globals.Verbose() {\n\t\tc.printVerbose(out, fastly.ToValue(serviceVersion.Number), o)\n\t} else {\n\t\terr = c.printSummary(out, o)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\n// constructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *ListCommand) constructInput(serviceID string, serviceVersion int) *fastly.ListNewRelicOTLPInput {\n\tvar input fastly.ListNewRelicOTLPInput\n\n\tinput.ServiceID = serviceID\n\tinput.ServiceVersion = serviceVersion\n\n\treturn &input\n}\n\n// printVerbose displays the information returned from the API in a verbose\n// format.\nfunc (c *ListCommand) printVerbose(out io.Writer, serviceVersion int, ls []*fastly.NewRelicOTLP) {\n\tfmt.Fprintf(out, \"Service Version: %d\\n\", serviceVersion)\n\n\tfor _, l := range ls {\n\t\tfmt.Fprintf(out, \"\\nName: %s\\n\", fastly.ToValue(l.Name))\n\t\tfmt.Fprintf(out, \"\\nToken: %s\\n\", fastly.ToValue(l.Token))\n\t\tfmt.Fprintf(out, \"\\nFormat: %s\\n\", fastly.ToValue(l.Format))\n\t\tfmt.Fprintf(out, \"\\nFormat Version: %d\\n\", fastly.ToValue(l.FormatVersion))\n\t\tfmt.Fprintf(out, \"\\nPlacement: %s\\n\", fastly.ToValue(l.Placement))\n\t\tfmt.Fprintf(out, \"\\nRegion: %s\\n\", fastly.ToValue(l.Region))\n\t\tfmt.Fprintf(out, \"\\nProcessing region: %s\\n\", fastly.ToValue(l.ProcessingRegion))\n\t\tfmt.Fprintf(out, \"\\nResponse Condition: %s\\n\\n\", fastly.ToValue(l.ResponseCondition))\n\n\t\tif l.CreatedAt != nil {\n\t\t\tfmt.Fprintf(out, \"Created at: %s\\n\", l.CreatedAt)\n\t\t}\n\t\tif l.UpdatedAt != nil {\n\t\t\tfmt.Fprintf(out, \"Updated at: %s\\n\", l.UpdatedAt)\n\t\t}\n\t\tif l.DeletedAt != nil {\n\t\t\tfmt.Fprintf(out, \"Deleted at: %s\\n\", l.DeletedAt)\n\t\t}\n\t}\n}\n\n// printSummary displays the information returned from the API in a summarised\n// format.\nfunc (c *ListCommand) printSummary(out io.Writer, nrs []*fastly.NewRelicOTLP) error {\n\tt := text.NewTable(out)\n\tt.AddHeader(\"SERVICE ID\", \"VERSION\", \"NAME\")\n\tfor _, nr := range nrs {\n\t\tt.AddLine(\n\t\t\tfastly.ToValue(nr.ServiceID),\n\t\t\tfastly.ToValue(nr.ServiceVersion),\n\t\t\tfastly.ToValue(nr.Name),\n\t\t)\n\t}\n\tt.Print()\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/newrelicotlp/newrelicotlp_test.go",
    "content": "package newrelicotlp_test\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\tserviceRoot \"github.com/fastly/cli/pkg/commands/service\"\n\tloggingRoot \"github.com/fastly/cli/pkg/commands/service/logging\"\n\tsub \"github.com/fastly/cli/pkg/commands/service/logging/newrelicotlp\"\n\t\"github.com/fastly/cli/pkg/mock\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n)\n\nfunc TestNewRelicOTLPCreate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --service-id flag\",\n\t\t\tArgs:      \"--key abc --name foo --version 3\",\n\t\t\tEnvVars:   map[string]string{\"FASTLY_SERVICE_ID\": \"\"},\n\t\t\tWantError: \"error reading service: no service ID found\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate CreateNewRelicOTLP API error\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tCreateNewRelicOTLPFn: func(_ context.Context, _ *fastly.CreateNewRelicOTLPInput) (*fastly.NewRelicOTLP, error) {\n\t\t\t\t\treturn nil, testutil.Err\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:      \"--key abc --name foo --service-id 123 --version 3\",\n\t\t\tWantError: testutil.Err.Error(),\n\t\t},\n\t\t{\n\t\t\tName: \"validate CreateNewRelicOTLP API success\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tCreateNewRelicOTLPFn: func(_ context.Context, i *fastly.CreateNewRelicOTLPInput) (*fastly.NewRelicOTLP, error) {\n\t\t\t\t\treturn &fastly.NewRelicOTLP{\n\t\t\t\t\t\tName:           i.Name,\n\t\t\t\t\t\tServiceID:      fastly.ToPointer(i.ServiceID),\n\t\t\t\t\t\tServiceVersion: fastly.ToPointer(i.ServiceVersion),\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:       \"--key abc --name foo --service-id 123 --version 3\",\n\t\t\tWantOutput: \"Created New Relic OTLP logging endpoint 'foo' (service: 123, version: 3)\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate --autoclone results in cloned service version\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tCreateNewRelicOTLPFn: func(_ context.Context, i *fastly.CreateNewRelicOTLPInput) (*fastly.NewRelicOTLP, error) {\n\t\t\t\t\treturn &fastly.NewRelicOTLP{\n\t\t\t\t\t\tName:           i.Name,\n\t\t\t\t\t\tServiceID:      fastly.ToPointer(i.ServiceID),\n\t\t\t\t\t\tServiceVersion: fastly.ToPointer(i.ServiceVersion),\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:       \"--autoclone --key abc --name foo --service-id 123 --version 1\",\n\t\t\tWantOutput: \"Created New Relic OTLP logging endpoint 'foo' (service: 123, version: 4)\",\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{serviceRoot.CommandName, loggingRoot.CommandName, sub.CommandName, \"create\"}, scenarios)\n}\n\nfunc TestNewRelicOTLPDelete(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --name flag\",\n\t\t\tArgs:      \"--version 3\",\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --version flag\",\n\t\t\tArgs:      \"--name foobar\",\n\t\t\tWantError: \"error parsing arguments: required flag --version not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --service-id flag\",\n\t\t\tArgs:      \"--name foobar --version 3\",\n\t\t\tEnvVars:   map[string]string{\"FASTLY_SERVICE_ID\": \"\"},\n\t\t\tWantError: \"error reading service: no service ID found\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate DeleteNewRelic API error\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tDeleteNewRelicOTLPFn: func(_ context.Context, _ *fastly.DeleteNewRelicOTLPInput) error {\n\t\t\t\t\treturn testutil.Err\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:      \"--name foobar --service-id 123 --version 3\",\n\t\t\tWantError: testutil.Err.Error(),\n\t\t},\n\t\t{\n\t\t\tName: \"validate DeleteNewRelic API success\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tDeleteNewRelicOTLPFn: func(_ context.Context, _ *fastly.DeleteNewRelicOTLPInput) error {\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:       \"--name foobar --service-id 123 --version 3\",\n\t\t\tWantOutput: \"Deleted New Relic OTLP logging endpoint 'foobar' (service: 123, version: 3)\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API error when modifying active version\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tDeleteNewRelicOTLPFn: func(_ context.Context, i *fastly.DeleteNewRelicOTLPInput) error {\n\t\t\t\t\treturn fmt.Errorf(\"Cannot update version %d. Versions that have been activated cannot be updated\", i.ServiceVersion)\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:      \"--name foobar --service-id 123 --version 3\",\n\t\t\tWantError: \"Cannot update version 3. Versions that have been activated cannot be updated\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API error when modifying locked version\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tDeleteNewRelicOTLPFn: func(_ context.Context, i *fastly.DeleteNewRelicOTLPInput) error {\n\t\t\t\t\treturn fmt.Errorf(\"Cannot update version %d. Versions that have been locked cannot be updated\", i.ServiceVersion)\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:      \"--name foobar --service-id 123 --version 3\",\n\t\t\tWantError: \"Cannot update version 3. Versions that have been locked cannot be updated\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate --autoclone results in cloned service version\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tDeleteNewRelicOTLPFn: func(_ context.Context, _ *fastly.DeleteNewRelicOTLPInput) error {\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:       \"--autoclone --name foo --service-id 123 --version 1\",\n\t\t\tWantOutput: \"Deleted New Relic OTLP logging endpoint 'foo' (service: 123, version: 4)\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate --autoclone on locked version\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tDeleteNewRelicOTLPFn: func(_ context.Context, i *fastly.DeleteNewRelicOTLPInput) error {\n\t\t\t\t\t// Verify operation happens on the cloned version (4), not original (2)\n\t\t\t\t\tif i.ServiceVersion != 4 {\n\t\t\t\t\t\treturn fmt.Errorf(\"expected operation on cloned version 4, got %d\", i.ServiceVersion)\n\t\t\t\t\t}\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:       \"--autoclone --name foo --service-id 123 --version 2\",\n\t\t\tWantOutput: \"Deleted New Relic OTLP logging endpoint 'foo' (service: 123, version: 4)\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate --autoclone on editable version\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tDeleteNewRelicOTLPFn: func(_ context.Context, i *fastly.DeleteNewRelicOTLPInput) error {\n\t\t\t\t\t// Verify operation happens on the cloned version (4), not original (3)\n\t\t\t\t\tif i.ServiceVersion != 4 {\n\t\t\t\t\t\treturn fmt.Errorf(\"expected operation on cloned version 4, got %d\", i.ServiceVersion)\n\t\t\t\t\t}\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:       \"--autoclone --name foo --service-id 123 --version 3\",\n\t\t\tWantOutput: \"Deleted New Relic OTLP logging endpoint 'foo' (service: 123, version: 4)\",\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{serviceRoot.CommandName, loggingRoot.CommandName, sub.CommandName, \"delete\"}, scenarios)\n}\n\nfunc TestNewRelicDescribe(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --name flag\",\n\t\t\tArgs:      \"--version 3\",\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --version flag\",\n\t\t\tArgs:      \"--name foobar\",\n\t\t\tWantError: \"error parsing arguments: required flag --version not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --service-id flag\",\n\t\t\tArgs:      \"--name foobar --version 3\",\n\t\t\tWantError: \"error reading service: no service ID found\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate GetNewRelic API error\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tGetNewRelicOTLPFn: func(_ context.Context, _ *fastly.GetNewRelicOTLPInput) (*fastly.NewRelicOTLP, error) {\n\t\t\t\t\treturn nil, testutil.Err\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:      \"--name foobar --service-id 123 --version 3\",\n\t\t\tWantError: testutil.Err.Error(),\n\t\t},\n\t\t{\n\t\t\tName: \"validate GetNewRelic API success\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:      testutil.GetVersion,\n\t\t\t\tGetNewRelicOTLPFn: getNewRelic,\n\t\t\t},\n\t\t\tArgs:       \"--name foobar --service-id 123 --version 3\",\n\t\t\tWantOutput: \"\\nCreated at: 2021-06-15 23:00:00 +0000 UTC\\nDeleted at: 2021-06-15 23:00:00 +0000 UTC\\nFormat: \\nFormat Version: 0\\nName: foobar\\nPlacement: \\nProcessing region: \\nRegion: \\nResponse Condition: \\nService ID: 123\\nService Version: 3\\nToken: abc\\nURL: \\nUpdated at: 2021-06-15 23:00:00 +0000 UTC\\n\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate missing --autoclone flag is OK\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:      testutil.GetVersion,\n\t\t\t\tGetNewRelicOTLPFn: getNewRelic,\n\t\t\t},\n\t\t\tArgs:       \"--name foobar --service-id 123 --version 1\",\n\t\t\tWantOutput: \"\\nCreated at: 2021-06-15 23:00:00 +0000 UTC\\nDeleted at: 2021-06-15 23:00:00 +0000 UTC\\nFormat: \\nFormat Version: 0\\nName: foobar\\nPlacement: \\nProcessing region: \\nRegion: \\nResponse Condition: \\nService ID: 123\\nService Version: 1\\nToken: abc\\nURL: \\nUpdated at: 2021-06-15 23:00:00 +0000 UTC\\n\",\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{serviceRoot.CommandName, loggingRoot.CommandName, sub.CommandName, \"describe\"}, scenarios)\n}\n\nfunc TestNewRelicList(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --version flag\",\n\t\t\tWantError: \"error parsing arguments: required flag --version not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --service-id flag\",\n\t\t\tArgs:      \"--version 3\",\n\t\t\tWantError: \"error reading service: no service ID found\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate ListNewRelics API error\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tListNewRelicOTLPFn: func(_ context.Context, _ *fastly.ListNewRelicOTLPInput) ([]*fastly.NewRelicOTLP, error) {\n\t\t\t\t\treturn nil, testutil.Err\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:      \"--service-id 123 --version 3\",\n\t\t\tWantError: testutil.Err.Error(),\n\t\t},\n\t\t{\n\t\t\tName: \"validate ListNewRelics API success\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:       testutil.GetVersion,\n\t\t\t\tListNewRelicOTLPFn: listNewRelic,\n\t\t\t},\n\t\t\tArgs:       \"--service-id 123 --version 3\",\n\t\t\tWantOutput: \"SERVICE ID  VERSION  NAME\\n123         3        foo\\n123         3        bar\\n\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate missing --autoclone flag is OK\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:       testutil.GetVersion,\n\t\t\t\tListNewRelicOTLPFn: listNewRelic,\n\t\t\t},\n\t\t\tArgs:       \"--service-id 123 --version 1\",\n\t\t\tWantOutput: \"SERVICE ID  VERSION  NAME\\n123         1        foo\\n123         1        bar\\n\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate missing --verbose flag\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:       testutil.GetVersion,\n\t\t\t\tListNewRelicOTLPFn: listNewRelic,\n\t\t\t},\n\t\t\tArgs:       \"--service-id 123 --verbose --version 1\",\n\t\t\tWantOutput: \"Fastly API endpoint: https://api.fastly.com\\nFastly API token provided via config file (auth: user)\\n\\nService ID (via --service-id): 123\\n\\nService Version: 1\\n\\nName: foo\\n\\nToken: \\n\\nFormat: \\n\\nFormat Version: 0\\n\\nPlacement: \\n\\nRegion: \\n\\nProcessing region: \\n\\nResponse Condition: \\n\\nCreated at: 2021-06-15 23:00:00 +0000 UTC\\nUpdated at: 2021-06-15 23:00:00 +0000 UTC\\nDeleted at: 2021-06-15 23:00:00 +0000 UTC\\n\\nName: bar\\n\\nToken: \\n\\nFormat: \\n\\nFormat Version: 0\\n\\nPlacement: \\n\\nRegion: \\n\\nProcessing region: \\n\\nResponse Condition: \\n\\nCreated at: 2021-06-15 23:00:00 +0000 UTC\\nUpdated at: 2021-06-15 23:00:00 +0000 UTC\\nDeleted at: 2021-06-15 23:00:00 +0000 UTC\\n\",\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{serviceRoot.CommandName, loggingRoot.CommandName, sub.CommandName, \"list\"}, scenarios)\n}\n\nfunc TestNewRelicUpdate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --name flag\",\n\t\t\tArgs:      \"--service-id 123 --version 3\",\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --version flag\",\n\t\t\tArgs:      \"--name foobar --service-id 123\",\n\t\t\tWantError: \"error parsing arguments: required flag --version not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --service-id flag\",\n\t\t\tArgs:      \"--name foobar --version 3\",\n\t\t\tEnvVars:   map[string]string{\"FASTLY_SERVICE_ID\": \"\"},\n\t\t\tWantError: \"error reading service: no service ID found\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate UpdateNewRelic API error\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tUpdateNewRelicOTLPFn: func(_ context.Context, _ *fastly.UpdateNewRelicOTLPInput) (*fastly.NewRelicOTLP, error) {\n\t\t\t\t\treturn nil, testutil.Err\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:      \"--name foobar --new-name beepboop --service-id 123 --version 3\",\n\t\t\tWantError: testutil.Err.Error(),\n\t\t},\n\t\t{\n\t\t\tName: \"validate UpdateNewRelic API success\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tUpdateNewRelicOTLPFn: func(_ context.Context, i *fastly.UpdateNewRelicOTLPInput) (*fastly.NewRelicOTLP, error) {\n\t\t\t\t\treturn &fastly.NewRelicOTLP{\n\t\t\t\t\t\tName:           i.NewName,\n\t\t\t\t\t\tServiceID:      fastly.ToPointer(i.ServiceID),\n\t\t\t\t\t\tServiceVersion: fastly.ToPointer(i.ServiceVersion),\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:       \"--name foobar --new-name beepboop --service-id 123 --version 3\",\n\t\t\tWantOutput: \"Updated New Relic OTLP logging endpoint 'beepboop' (previously: foobar, service: 123, version: 3)\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API error when modifying active version\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tUpdateNewRelicOTLPFn: func(_ context.Context, i *fastly.UpdateNewRelicOTLPInput) (*fastly.NewRelicOTLP, error) {\n\t\t\t\t\treturn nil, fmt.Errorf(\"Cannot update version %d. Versions that have been activated cannot be updated\", i.ServiceVersion)\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:      \"--name foobar --new-name beepboop --service-id 123 --version 3\",\n\t\t\tWantError: \"Cannot update version 3. Versions that have been activated cannot be updated\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API error when modifying locked version\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tUpdateNewRelicOTLPFn: func(_ context.Context, i *fastly.UpdateNewRelicOTLPInput) (*fastly.NewRelicOTLP, error) {\n\t\t\t\t\treturn nil, fmt.Errorf(\"Cannot update version %d. Versions that have been locked cannot be updated\", i.ServiceVersion)\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:      \"--name foobar --new-name beepboop --service-id 123 --version 3\",\n\t\t\tWantError: \"Cannot update version 3. Versions that have been locked cannot be updated\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate --autoclone results in cloned service version\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tUpdateNewRelicOTLPFn: func(_ context.Context, i *fastly.UpdateNewRelicOTLPInput) (*fastly.NewRelicOTLP, error) {\n\t\t\t\t\treturn &fastly.NewRelicOTLP{\n\t\t\t\t\t\tName:           i.NewName,\n\t\t\t\t\t\tServiceID:      fastly.ToPointer(i.ServiceID),\n\t\t\t\t\t\tServiceVersion: fastly.ToPointer(i.ServiceVersion),\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:       \"--autoclone --name foobar --new-name beepboop --service-id 123 --version 1\",\n\t\t\tWantOutput: \"Updated New Relic OTLP logging endpoint 'beepboop' (previously: foobar, service: 123, version: 4)\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate --autoclone on locked version\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tUpdateNewRelicOTLPFn: func(_ context.Context, i *fastly.UpdateNewRelicOTLPInput) (*fastly.NewRelicOTLP, error) {\n\t\t\t\t\t// Verify operation happens on the cloned version (4), not original (2)\n\t\t\t\t\tif i.ServiceVersion != 4 {\n\t\t\t\t\t\treturn nil, fmt.Errorf(\"expected operation on cloned version 4, got %d\", i.ServiceVersion)\n\t\t\t\t\t}\n\t\t\t\t\treturn &fastly.NewRelicOTLP{\n\t\t\t\t\t\tName:           i.NewName,\n\t\t\t\t\t\tServiceID:      fastly.ToPointer(i.ServiceID),\n\t\t\t\t\t\tServiceVersion: fastly.ToPointer(i.ServiceVersion),\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:       \"--autoclone --name foobar --new-name beepboop --service-id 123 --version 2\",\n\t\t\tWantOutput: \"Updated New Relic OTLP logging endpoint 'beepboop' (previously: foobar, service: 123, version: 4)\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate --autoclone on editable version\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tUpdateNewRelicOTLPFn: func(_ context.Context, i *fastly.UpdateNewRelicOTLPInput) (*fastly.NewRelicOTLP, error) {\n\t\t\t\t\t// Verify operation happens on the cloned version (4), not original (3)\n\t\t\t\t\tif i.ServiceVersion != 4 {\n\t\t\t\t\t\treturn nil, fmt.Errorf(\"expected operation on cloned version 4, got %d\", i.ServiceVersion)\n\t\t\t\t\t}\n\t\t\t\t\treturn &fastly.NewRelicOTLP{\n\t\t\t\t\t\tName:           i.NewName,\n\t\t\t\t\t\tServiceID:      fastly.ToPointer(i.ServiceID),\n\t\t\t\t\t\tServiceVersion: fastly.ToPointer(i.ServiceVersion),\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:       \"--autoclone --name foobar --new-name beepboop --service-id 123 --version 3\",\n\t\t\tWantOutput: \"Updated New Relic OTLP logging endpoint 'beepboop' (previously: foobar, service: 123, version: 4)\",\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{serviceRoot.CommandName, loggingRoot.CommandName, sub.CommandName, \"update\"}, scenarios)\n}\n\nfunc getNewRelic(_ context.Context, i *fastly.GetNewRelicOTLPInput) (*fastly.NewRelicOTLP, error) {\n\tt := testutil.Date\n\n\treturn &fastly.NewRelicOTLP{\n\t\tName:           fastly.ToPointer(i.Name),\n\t\tToken:          fastly.ToPointer(\"abc\"),\n\t\tServiceID:      fastly.ToPointer(i.ServiceID),\n\t\tServiceVersion: fastly.ToPointer(i.ServiceVersion),\n\n\t\tCreatedAt: &t,\n\t\tDeletedAt: &t,\n\t\tUpdatedAt: &t,\n\t}, nil\n}\n\nfunc listNewRelic(_ context.Context, i *fastly.ListNewRelicOTLPInput) ([]*fastly.NewRelicOTLP, error) {\n\tt := testutil.Date\n\tvs := []*fastly.NewRelicOTLP{\n\t\t{\n\t\t\tName:           fastly.ToPointer(\"foo\"),\n\t\t\tServiceID:      fastly.ToPointer(i.ServiceID),\n\t\t\tServiceVersion: fastly.ToPointer(i.ServiceVersion),\n\n\t\t\tCreatedAt: &t,\n\t\t\tDeletedAt: &t,\n\t\t\tUpdatedAt: &t,\n\t\t},\n\t\t{\n\t\t\tName:           fastly.ToPointer(\"bar\"),\n\t\t\tServiceID:      fastly.ToPointer(i.ServiceID),\n\t\t\tServiceVersion: fastly.ToPointer(i.ServiceVersion),\n\n\t\t\tCreatedAt: &t,\n\t\t\tDeletedAt: &t,\n\t\t\tUpdatedAt: &t,\n\t\t},\n\t}\n\treturn vs, nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/newrelicotlp/root.go",
    "content": "package newrelicotlp\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"newrelicotlp\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manipulate a NewRelic OTLP logging endpoint for a specific Fastly service version\")\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/newrelicotlp/update.go",
    "content": "package newrelicotlp\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/service/logging/logflags\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// UpdateCommand calls the Fastly API to update an appropriate resource.\ntype UpdateCommand struct {\n\targparser.Base\n\n\tendpointName   string\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n\n\tautoClone         argparser.OptionalAutoClone\n\tformat            argparser.OptionalString\n\tformatVersion     argparser.OptionalInt\n\tkey               argparser.OptionalString\n\tnewName           argparser.OptionalString\n\tplacement         argparser.OptionalString\n\tprocessingregion  argparser.OptionalString\n\tregion            argparser.OptionalString\n\tresponseCondition argparser.OptionalString\n\turl               argparser.OptionalString\n}\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"update\", \"Update a New Relic Logs logging object for a particular service and version\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"The name for the real-time logging configuration to update\").Required().StringVar(&c.endpointName)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.autoClone.Set,\n\t\tDst:    &c.autoClone.Value,\n\t})\n\tlogflags.Format(c.CmdClause, &c.format)\n\tc.CmdClause.Flag(\"format-version\", \"The version of the custom logging format used for the configured endpoint\").Action(c.formatVersion.Set).IntVar(&c.formatVersion.Value)\n\tc.CmdClause.Flag(\"key\", \"The Insert API key from the Account page of your New Relic account\").Action(c.key.Set).StringVar(&c.key.Value)\n\tc.CmdClause.Flag(\"new-name\", \"The name for the real-time logging configuration\").Action(c.newName.Set).StringVar(&c.newName.Value)\n\tc.CmdClause.Flag(\"placement\", \"Where in the generated VCL the logging call should be placed\").Action(c.placement.Set).StringVar(&c.placement.Value)\n\tlogflags.ProcessingRegion(c.CmdClause, &c.processingregion, \"New Relic\")\n\tc.CmdClause.Flag(\"region\", \"The region to which to stream logs\").Action(c.region.Set).StringVar(&c.region.Value)\n\tc.CmdClause.Flag(\"response-condition\", \"The name of an existing condition in the configured endpoint\").Action(c.responseCondition.Set).StringVar(&c.responseCondition.Value)\n\tc.CmdClause.Flag(\"url\", \"URL of the New Relic Trace Observer, if you are using New Relic Infinite Tracing\").Action(c.url.Set).StringVar(&c.url.Value)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *UpdateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(false),\n\t\tLocked:             optional.Of(false),\n\t\tAutoCloneFlag:      c.autoClone,\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": errors.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tinput := c.constructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\n\tl, err := c.Globals.APIClient.UpdateNewRelicOTLP(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fastly.ToValue(serviceVersion.Number),\n\t\t})\n\t\treturn err\n\t}\n\n\tvar prev string\n\tif c.newName.WasSet {\n\t\tprev = fmt.Sprintf(\"previously: %s, \", c.endpointName)\n\t}\n\n\ttext.Success(\n\t\tout,\n\t\t\"Updated New Relic OTLP logging endpoint '%s' (%sservice: %s, version: %d)\",\n\t\tfastly.ToValue(l.Name),\n\t\tprev,\n\t\tfastly.ToValue(l.ServiceID),\n\t\tfastly.ToValue(l.ServiceVersion),\n\t)\n\treturn nil\n}\n\n// constructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *UpdateCommand) constructInput(serviceID string, serviceVersion int) *fastly.UpdateNewRelicOTLPInput {\n\tvar input fastly.UpdateNewRelicOTLPInput\n\n\tinput.Name = c.endpointName\n\tinput.ServiceID = serviceID\n\tinput.ServiceVersion = serviceVersion\n\n\tif c.format.WasSet {\n\t\tinput.Format = fastly.ToPointer(argparser.Content(c.format.Value))\n\t}\n\tif c.formatVersion.WasSet {\n\t\tinput.FormatVersion = &c.formatVersion.Value\n\t}\n\tif c.key.WasSet {\n\t\tinput.Token = &c.key.Value\n\t}\n\tif c.newName.WasSet {\n\t\tinput.NewName = &c.newName.Value\n\t}\n\tif c.placement.WasSet {\n\t\tinput.Placement = &c.placement.Value\n\t}\n\tif c.processingregion.WasSet {\n\t\tinput.ProcessingRegion = &c.processingregion.Value\n\t}\n\tif c.region.WasSet {\n\t\tinput.Region = &c.region.Value\n\t}\n\tif c.responseCondition.WasSet {\n\t\tinput.ResponseCondition = &c.responseCondition.Value\n\t}\n\tif c.url.WasSet {\n\t\tinput.URL = &c.url.Value\n\t}\n\n\treturn &input\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/openstack/create.go",
    "content": "package openstack\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/service/logging/logflags\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/manifest\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// CreateCommand calls the Fastly API to create an OpenStack logging endpoint.\ntype CreateCommand struct {\n\targparser.Base\n\tManifest manifest.Data\n\n\t// Required.\n\tServiceName    argparser.OptionalServiceNameID\n\tServiceVersion argparser.OptionalServiceVersion\n\n\t// Optional.\n\tAccessKey         argparser.OptionalString\n\tAutoClone         argparser.OptionalAutoClone\n\tBucketName        argparser.OptionalString\n\tCompressionCodec  argparser.OptionalString\n\tEndpointName      argparser.OptionalString // Can't shadow argparser.Base method Name().\n\tFormat            argparser.OptionalString\n\tFormatVersion     argparser.OptionalInt\n\tGzipLevel         argparser.OptionalInt\n\tMessageType       argparser.OptionalString\n\tPath              argparser.OptionalString\n\tPeriod            argparser.OptionalInt\n\tPlacement         argparser.OptionalString\n\tProcessingRegion  argparser.OptionalString\n\tPublicKey         argparser.OptionalString\n\tResponseCondition argparser.OptionalString\n\tTimestampFormat   argparser.OptionalString\n\tURL               argparser.OptionalString\n\tUser              argparser.OptionalString\n}\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"create\", \"Create an OpenStack logging endpoint on a Fastly service version\").Alias(\"add\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"The name of the OpenStack logging object. Used as a primary key for API access\").Short('n').Action(c.EndpointName.Set).StringVar(&c.EndpointName.Value)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.ServiceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.CmdClause.Flag(\"access-key\", \"Your OpenStack account access key\").Action(c.AccessKey.Set).StringVar(&c.AccessKey.Value)\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.AutoClone.Set,\n\t\tDst:    &c.AutoClone.Value,\n\t})\n\tc.CmdClause.Flag(\"bucket\", \"The name of your OpenStack container\").Action(c.BucketName.Set).StringVar(&c.BucketName.Value)\n\tlogflags.CompressionCodec(c.CmdClause, &c.CompressionCodec)\n\tlogflags.Format(c.CmdClause, &c.Format)\n\tlogflags.FormatVersion(c.CmdClause, &c.FormatVersion)\n\tlogflags.GzipLevel(c.CmdClause, &c.GzipLevel)\n\tlogflags.MessageType(c.CmdClause, &c.MessageType)\n\tlogflags.Path(c.CmdClause, &c.Path)\n\tlogflags.Period(c.CmdClause, &c.Period)\n\tlogflags.Placement(c.CmdClause, &c.Placement)\n\tlogflags.PublicKey(c.CmdClause, &c.PublicKey)\n\tlogflags.ProcessingRegion(c.CmdClause, &c.ProcessingRegion, \"OpenStack\")\n\tlogflags.ResponseCondition(c.CmdClause, &c.ResponseCondition)\n\tlogflags.TimestampFormat(c.CmdClause, &c.TimestampFormat)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.ServiceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.ServiceName.Value,\n\t})\n\tc.CmdClause.Flag(\"url\", \"Your OpenStack auth url\").Action(c.URL.Set).StringVar(&c.URL.Value)\n\tc.CmdClause.Flag(\"user\", \"The username for your OpenStack account\").Action(c.User.Set).StringVar(&c.User.Value)\n\treturn &c\n}\n\n// ConstructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *CreateCommand) ConstructInput(serviceID string, serviceVersion int) (*fastly.CreateOpenstackInput, error) {\n\tvar input fastly.CreateOpenstackInput\n\n\tinput.ServiceID = serviceID\n\tinput.ServiceVersion = serviceVersion\n\tif c.EndpointName.WasSet {\n\t\tinput.Name = &c.EndpointName.Value\n\t}\n\tif c.BucketName.WasSet {\n\t\tinput.BucketName = &c.BucketName.Value\n\t}\n\tif c.AccessKey.WasSet {\n\t\tinput.AccessKey = &c.AccessKey.Value\n\t}\n\tif c.User.WasSet {\n\t\tinput.User = &c.User.Value\n\t}\n\tif c.URL.WasSet {\n\t\tinput.URL = &c.URL.Value\n\t}\n\n\t// The following blocks enforces the mutual exclusivity of the\n\t// CompressionCodec and GzipLevel flags.\n\tif c.CompressionCodec.WasSet && c.GzipLevel.WasSet {\n\t\treturn nil, fmt.Errorf(\"error parsing arguments: the --compression-codec flag is mutually exclusive with the --gzip-level flag\")\n\t}\n\n\tif c.PublicKey.WasSet {\n\t\tinput.PublicKey = &c.PublicKey.Value\n\t}\n\n\tif c.Path.WasSet {\n\t\tinput.Path = &c.Path.Value\n\t}\n\n\tif c.Period.WasSet {\n\t\tinput.Period = &c.Period.Value\n\t}\n\n\tif c.GzipLevel.WasSet {\n\t\tinput.GzipLevel = &c.GzipLevel.Value\n\t}\n\n\tif c.Format.WasSet {\n\t\tinput.Format = fastly.ToPointer(argparser.Content(c.Format.Value))\n\t}\n\n\tif c.FormatVersion.WasSet {\n\t\tinput.FormatVersion = &c.FormatVersion.Value\n\t}\n\n\tif c.ResponseCondition.WasSet {\n\t\tinput.ResponseCondition = &c.ResponseCondition.Value\n\t}\n\n\tif c.MessageType.WasSet {\n\t\tinput.MessageType = &c.MessageType.Value\n\t}\n\n\tif c.TimestampFormat.WasSet {\n\t\tinput.TimestampFormat = &c.TimestampFormat.Value\n\t}\n\n\tif c.Placement.WasSet {\n\t\tinput.Placement = &c.Placement.Value\n\t}\n\n\tif c.CompressionCodec.WasSet {\n\t\tinput.CompressionCodec = &c.CompressionCodec.Value\n\t}\n\n\tif c.ProcessingRegion.WasSet {\n\t\tinput.ProcessingRegion = &c.ProcessingRegion.Value\n\t}\n\n\treturn &input, nil\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *CreateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(false),\n\t\tLocked:             optional.Of(false),\n\t\tAutoCloneFlag:      c.AutoClone,\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.ServiceName,\n\t\tServiceVersionFlag: c.ServiceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": errors.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tinput, err := c.ConstructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\td, err := c.Globals.APIClient.CreateOpenstack(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\ttext.Success(\n\t\tout,\n\t\t\"Created OpenStack logging endpoint %s (service %s version %d)\",\n\t\tfastly.ToValue(d.Name),\n\t\tfastly.ToValue(d.ServiceID),\n\t\tfastly.ToValue(d.ServiceVersion),\n\t)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/openstack/delete.go",
    "content": "package openstack\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DeleteCommand calls the Fastly API to delete an OpenStack logging endpoint.\ntype DeleteCommand struct {\n\targparser.Base\n\tInput          fastly.DeleteOpenstackInput\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n\tautoClone      argparser.OptionalAutoClone\n}\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"delete\", \"Delete an OpenStack logging endpoint on a Fastly service version\").Alias(\"remove\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"The name of the OpenStack logging object\").Short('n').Required().StringVar(&c.Input.Name)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.autoClone.Set,\n\t\tDst:    &c.autoClone.Value,\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DeleteCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(false),\n\t\tLocked:             optional.Of(false),\n\t\tAutoCloneFlag:      c.autoClone,\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": errors.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tc.Input.ServiceID = serviceID\n\tc.Input.ServiceVersion = fastly.ToValue(serviceVersion.Number)\n\n\tif err := c.Globals.APIClient.DeleteOpenstack(context.TODO(), &c.Input); err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Deleted OpenStack logging endpoint %s (service %s version %d)\", c.Input.Name, c.Input.ServiceID, c.Input.ServiceVersion)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/openstack/describe.go",
    "content": "package openstack\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DescribeCommand calls the Fastly API to describe an OpenStack logging endpoint.\ntype DescribeCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tInput          fastly.GetOpenstackInput\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n}\n\n// NewDescribeCommand returns a usable command registered under the parent.\nfunc NewDescribeCommand(parent argparser.Registerer, g *global.Data) *DescribeCommand {\n\tc := DescribeCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"describe\", \"Show detailed information about an OpenStack logging endpoint on a Fastly service version\").Alias(\"get\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"The name of the OpenStack logging object\").Short('n').Required().StringVar(&c.Input.Name)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DescribeCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fsterr.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tc.Input.ServiceID = serviceID\n\tc.Input.ServiceVersion = fastly.ToValue(serviceVersion.Number)\n\n\to, err := c.Globals.APIClient.GetOpenstack(context.TODO(), &c.Input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\tlines := text.Lines{\n\t\t\"Access key\":         fastly.ToValue(o.AccessKey),\n\t\t\"Bucket\":             fastly.ToValue(o.BucketName),\n\t\t\"Compression codec\":  fastly.ToValue(o.CompressionCodec),\n\t\t\"Format version\":     fastly.ToValue(o.FormatVersion),\n\t\t\"Format\":             fastly.ToValue(o.Format),\n\t\t\"GZip level\":         fastly.ToValue(o.GzipLevel),\n\t\t\"Message type\":       fastly.ToValue(o.MessageType),\n\t\t\"Name\":               fastly.ToValue(o.Name),\n\t\t\"Path\":               fastly.ToValue(o.Path),\n\t\t\"Period\":             fastly.ToValue(o.Period),\n\t\t\"Placement\":          fastly.ToValue(o.Placement),\n\t\t\"Processing region\":  fastly.ToValue(o.ProcessingRegion),\n\t\t\"Public key\":         fastly.ToValue(o.PublicKey),\n\t\t\"Response condition\": fastly.ToValue(o.ResponseCondition),\n\t\t\"Timestamp format\":   fastly.ToValue(o.TimestampFormat),\n\t\t\"URL\":                fastly.ToValue(o.URL),\n\t\t\"User\":               fastly.ToValue(o.User),\n\t\t\"Version\":            fastly.ToValue(o.ServiceVersion),\n\t}\n\tif !c.Globals.Verbose() {\n\t\tlines[\"Service ID\"] = fastly.ToValue(o.ServiceID)\n\t}\n\ttext.PrintLines(out, lines)\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/openstack/doc.go",
    "content": "// Package openstack contains commands to inspect and manipulate Fastly service OpenStack\n// logging endpoints.\npackage openstack\n"
  },
  {
    "path": "pkg/commands/service/logging/openstack/list.go",
    "content": "package openstack\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// ListCommand calls the Fastly API to list OpenStack logging endpoints.\ntype ListCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tInput          fastly.ListOpenstackInput\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n}\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"list\", \"List OpenStack logging endpoints on a Fastly service version\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *ListCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fsterr.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tc.Input.ServiceID = serviceID\n\tc.Input.ServiceVersion = fastly.ToValue(serviceVersion.Number)\n\n\to, err := c.Globals.APIClient.ListOpenstack(context.TODO(), &c.Input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\tif !c.Globals.Verbose() {\n\t\ttw := text.NewTable(out)\n\t\ttw.AddHeader(\"SERVICE\", \"VERSION\", \"NAME\")\n\t\tfor _, openstack := range o {\n\t\t\ttw.AddLine(\n\t\t\t\tfastly.ToValue(openstack.ServiceID),\n\t\t\t\tfastly.ToValue(openstack.ServiceVersion),\n\t\t\t\tfastly.ToValue(openstack.Name),\n\t\t\t)\n\t\t}\n\t\ttw.Print()\n\t\treturn nil\n\t}\n\n\tfmt.Fprintf(out, \"Version: %d\\n\", c.Input.ServiceVersion)\n\tfor i, openstack := range o {\n\t\tfmt.Fprintf(out, \"\\tOpenstack %d/%d\\n\", i+1, len(o))\n\t\tfmt.Fprintf(out, \"\\t\\tService ID: %s\\n\", fastly.ToValue(openstack.ServiceID))\n\t\tfmt.Fprintf(out, \"\\t\\tVersion: %d\\n\", fastly.ToValue(openstack.ServiceVersion))\n\t\tfmt.Fprintf(out, \"\\t\\tName: %s\\n\", fastly.ToValue(openstack.Name))\n\t\tfmt.Fprintf(out, \"\\t\\tBucket: %s\\n\", fastly.ToValue(openstack.BucketName))\n\t\tfmt.Fprintf(out, \"\\t\\tAccess key: %s\\n\", fastly.ToValue(openstack.AccessKey))\n\t\tfmt.Fprintf(out, \"\\t\\tUser: %s\\n\", fastly.ToValue(openstack.User))\n\t\tfmt.Fprintf(out, \"\\t\\tURL: %s\\n\", fastly.ToValue(openstack.URL))\n\t\tfmt.Fprintf(out, \"\\t\\tPath: %s\\n\", fastly.ToValue(openstack.Path))\n\t\tfmt.Fprintf(out, \"\\t\\tPeriod: %d\\n\", fastly.ToValue(openstack.Period))\n\t\tfmt.Fprintf(out, \"\\t\\tGZip level: %d\\n\", fastly.ToValue(openstack.GzipLevel))\n\t\tfmt.Fprintf(out, \"\\t\\tFormat: %s\\n\", fastly.ToValue(openstack.Format))\n\t\tfmt.Fprintf(out, \"\\t\\tFormat version: %d\\n\", fastly.ToValue(openstack.FormatVersion))\n\t\tfmt.Fprintf(out, \"\\t\\tResponse condition: %s\\n\", fastly.ToValue(openstack.ResponseCondition))\n\t\tfmt.Fprintf(out, \"\\t\\tMessage type: %s\\n\", fastly.ToValue(openstack.MessageType))\n\t\tfmt.Fprintf(out, \"\\t\\tTimestamp format: %s\\n\", fastly.ToValue(openstack.TimestampFormat))\n\t\tfmt.Fprintf(out, \"\\t\\tPlacement: %s\\n\", fastly.ToValue(openstack.Placement))\n\t\tfmt.Fprintf(out, \"\\t\\tPublic key: %s\\n\", fastly.ToValue(openstack.PublicKey))\n\t\tfmt.Fprintf(out, \"\\t\\tCompression codec: %s\\n\", fastly.ToValue(openstack.CompressionCodec))\n\t\tfmt.Fprintf(out, \"\\t\\tProcessing region: %s\\n\", fastly.ToValue(openstack.ProcessingRegion))\n\t}\n\tfmt.Fprintln(out)\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/openstack/openstack_integration_test.go",
    "content": "package openstack_test\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/mock\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n\n\troot \"github.com/fastly/cli/pkg/commands/service\"\n\tparent \"github.com/fastly/cli/pkg/commands/service/logging\"\n\tsub \"github.com/fastly/cli/pkg/commands/service/logging/openstack\"\n)\n\nfunc TestOpenstackCreate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name log --bucket log --access-key foo --user user --url https://example.com --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:      testutil.GetVersion,\n\t\t\t\tCloneVersionFn:    testutil.CloneVersionResult(4),\n\t\t\t\tCreateOpenstackFn: createOpenstackOK,\n\t\t\t},\n\t\t\tWantOutput: \"Created OpenStack logging endpoint log (service 123 version 4)\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name log --bucket log --access-key foo --user user --url https://example.com --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:      testutil.GetVersion,\n\t\t\t\tCloneVersionFn:    testutil.CloneVersionResult(4),\n\t\t\t\tCreateOpenstackFn: createOpenstackError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name log --bucket log --access-key foo --user user --url https://example.com --compression-codec zstd --gzip-level 9 --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t},\n\t\t\tWantError: \"error parsing arguments: the --compression-codec flag is mutually exclusive with the --gzip-level flag\",\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"create\"}, scenarios)\n}\n\nfunc TestOpenstackList(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:     testutil.GetVersion,\n\t\t\t\tListOpenstacksFn: listOpenstacksOK,\n\t\t\t},\n\t\t\tWantOutput: listOpenstacksShortOutput,\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --verbose\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:     testutil.GetVersion,\n\t\t\t\tListOpenstacksFn: listOpenstacksOK,\n\t\t\t},\n\t\t\tWantOutput: listOpenstacksVerboseOutput,\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 -v\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:     testutil.GetVersion,\n\t\t\t\tListOpenstacksFn: listOpenstacksOK,\n\t\t\t},\n\t\t\tWantOutput: listOpenstacksVerboseOutput,\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:     testutil.GetVersion,\n\t\t\t\tListOpenstacksFn: listOpenstacksError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"list\"}, scenarios)\n}\n\nfunc TestOpenstackDescribe(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"--service-id 123 --version 1\",\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tGetOpenstackFn: getOpenstackError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tGetOpenstackFn: getOpenstackOK,\n\t\t\t},\n\t\t\tWantOutput: describeOpenstackOutput,\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"describe\"}, scenarios)\n}\n\nfunc TestOpenstackUpdate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"--service-id 123 --version 1 --new-name log\",\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs --new-name log --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:      testutil.GetVersion,\n\t\t\t\tCloneVersionFn:    testutil.CloneVersionResult(4),\n\t\t\t\tUpdateOpenstackFn: updateOpenstackError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs --new-name log --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:      testutil.GetVersion,\n\t\t\t\tCloneVersionFn:    testutil.CloneVersionResult(4),\n\t\t\t\tUpdateOpenstackFn: updateOpenstackOK,\n\t\t\t},\n\t\t\tWantOutput: \"Updated OpenStack logging endpoint log (service 123 version 4)\",\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"update\"}, scenarios)\n}\n\nfunc TestOpenstackDelete(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"--service-id 123 --version 1\",\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:      testutil.GetVersion,\n\t\t\t\tCloneVersionFn:    testutil.CloneVersionResult(4),\n\t\t\t\tDeleteOpenstackFn: deleteOpenstackError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:      testutil.GetVersion,\n\t\t\t\tCloneVersionFn:    testutil.CloneVersionResult(4),\n\t\t\t\tDeleteOpenstackFn: deleteOpenstackOK,\n\t\t\t},\n\t\t\tWantOutput: \"Deleted OpenStack logging endpoint logs (service 123 version 4)\",\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"delete\"}, scenarios)\n}\n\nvar errTest = errors.New(\"fixture error\")\n\nfunc createOpenstackOK(_ context.Context, i *fastly.CreateOpenstackInput) (*fastly.Openstack, error) {\n\ts := fastly.Openstack{\n\t\tServiceID:      fastly.ToPointer(i.ServiceID),\n\t\tServiceVersion: fastly.ToPointer(i.ServiceVersion),\n\t}\n\n\tif i.Name != nil {\n\t\ts.Name = i.Name\n\t}\n\n\treturn &s, nil\n}\n\nfunc createOpenstackError(_ context.Context, _ *fastly.CreateOpenstackInput) (*fastly.Openstack, error) {\n\treturn nil, errTest\n}\n\nfunc listOpenstacksOK(_ context.Context, i *fastly.ListOpenstackInput) ([]*fastly.Openstack, error) {\n\treturn []*fastly.Openstack{\n\t\t{\n\t\t\tServiceID:         fastly.ToPointer(i.ServiceID),\n\t\t\tServiceVersion:    fastly.ToPointer(i.ServiceVersion),\n\t\t\tName:              fastly.ToPointer(\"logs\"),\n\t\t\tBucketName:        fastly.ToPointer(\"my-logs\"),\n\t\t\tAccessKey:         fastly.ToPointer(\"1234\"),\n\t\t\tUser:              fastly.ToPointer(\"user\"),\n\t\t\tURL:               fastly.ToPointer(\"https://example.com\"),\n\t\t\tPath:              fastly.ToPointer(\"logs/\"),\n\t\t\tPeriod:            fastly.ToPointer(3600),\n\t\t\tGzipLevel:         fastly.ToPointer(0),\n\t\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\t\tFormatVersion:     fastly.ToPointer(2),\n\t\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\t\tMessageType:       fastly.ToPointer(\"classic\"),\n\t\t\tTimestampFormat:   fastly.ToPointer(\"%Y-%m-%dT%H:%M:%S.000\"),\n\t\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\t\tPublicKey:         fastly.ToPointer(pgpPublicKey()),\n\t\t\tCompressionCodec:  fastly.ToPointer(\"zstd\"),\n\t\t\tProcessingRegion:  fastly.ToPointer(\"us\"),\n\t\t},\n\t\t{\n\t\t\tServiceID:         fastly.ToPointer(i.ServiceID),\n\t\t\tServiceVersion:    fastly.ToPointer(i.ServiceVersion),\n\t\t\tName:              fastly.ToPointer(\"analytics\"),\n\t\t\tBucketName:        fastly.ToPointer(\"analytics\"),\n\t\t\tAccessKey:         fastly.ToPointer(\"1234\"),\n\t\t\tUser:              fastly.ToPointer(\"user2\"),\n\t\t\tURL:               fastly.ToPointer(\"https://two.example.com\"),\n\t\t\tPath:              fastly.ToPointer(\"logs/\"),\n\t\t\tPeriod:            fastly.ToPointer(86400),\n\t\t\tGzipLevel:         fastly.ToPointer(0),\n\t\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\t\tFormatVersion:     fastly.ToPointer(2),\n\t\t\tMessageType:       fastly.ToPointer(\"classic\"),\n\t\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\t\tTimestampFormat:   fastly.ToPointer(\"%Y-%m-%dT%H:%M:%S.000\"),\n\t\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\t\tPublicKey:         fastly.ToPointer(pgpPublicKey()),\n\t\t\tCompressionCodec:  fastly.ToPointer(\"zstd\"),\n\t\t\tProcessingRegion:  fastly.ToPointer(\"us\"),\n\t\t},\n\t}, nil\n}\n\nfunc listOpenstacksError(_ context.Context, _ *fastly.ListOpenstackInput) ([]*fastly.Openstack, error) {\n\treturn nil, errTest\n}\n\nvar listOpenstacksShortOutput = strings.TrimSpace(`\nSERVICE  VERSION  NAME\n123      1        logs\n123      1        analytics\n`) + \"\\n\"\n\nvar listOpenstacksVerboseOutput = strings.TrimSpace(`\nFastly API endpoint: https://api.fastly.com\nFastly API token provided via config file (auth: user)\n\nService ID (via --service-id): 123\n\nVersion: 1\n\tOpenstack 1/2\n\t\tService ID: 123\n\t\tVersion: 1\n\t\tName: logs\n\t\tBucket: my-logs\n\t\tAccess key: 1234\n\t\tUser: user\n\t\tURL: https://example.com\n\t\tPath: logs/\n\t\tPeriod: 3600\n\t\tGZip level: 0\n\t\tFormat: %h %l %u %t \"%r\" %>s %b\n\t\tFormat version: 2\n\t\tResponse condition: Prevent default logging\n\t\tMessage type: classic\n\t\tTimestamp format: %Y-%m-%dT%H:%M:%S.000\n\t\tPlacement: none\n\t\tPublic key: `+pgpPublicKey()+`\n\t\tCompression codec: zstd\n\t\tProcessing region: us\n\tOpenstack 2/2\n\t\tService ID: 123\n\t\tVersion: 1\n\t\tName: analytics\n\t\tBucket: analytics\n\t\tAccess key: 1234\n\t\tUser: user2\n\t\tURL: https://two.example.com\n\t\tPath: logs/\n\t\tPeriod: 86400\n\t\tGZip level: 0\n\t\tFormat: %h %l %u %t \"%r\" %>s %b\n\t\tFormat version: 2\n\t\tResponse condition: Prevent default logging\n\t\tMessage type: classic\n\t\tTimestamp format: %Y-%m-%dT%H:%M:%S.000\n\t\tPlacement: none\n\t\tPublic key: `+pgpPublicKey()+`\n\t\tCompression codec: zstd\n\t\tProcessing region: us\n`) + \"\\n\\n\"\n\nfunc getOpenstackOK(_ context.Context, i *fastly.GetOpenstackInput) (*fastly.Openstack, error) {\n\treturn &fastly.Openstack{\n\t\tServiceID:         fastly.ToPointer(i.ServiceID),\n\t\tServiceVersion:    fastly.ToPointer(i.ServiceVersion),\n\t\tName:              fastly.ToPointer(\"logs\"),\n\t\tBucketName:        fastly.ToPointer(\"my-logs\"),\n\t\tAccessKey:         fastly.ToPointer(\"1234\"),\n\t\tUser:              fastly.ToPointer(\"user\"),\n\t\tURL:               fastly.ToPointer(\"https://example.com\"),\n\t\tPath:              fastly.ToPointer(\"logs/\"),\n\t\tPeriod:            fastly.ToPointer(3600),\n\t\tGzipLevel:         fastly.ToPointer(0),\n\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\tFormatVersion:     fastly.ToPointer(2),\n\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\tMessageType:       fastly.ToPointer(\"classic\"),\n\t\tTimestampFormat:   fastly.ToPointer(\"%Y-%m-%dT%H:%M:%S.000\"),\n\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\tPublicKey:         fastly.ToPointer(pgpPublicKey()),\n\t\tCompressionCodec:  fastly.ToPointer(\"zstd\"),\n\t\tProcessingRegion:  fastly.ToPointer(\"us\"),\n\t}, nil\n}\n\nfunc getOpenstackError(_ context.Context, _ *fastly.GetOpenstackInput) (*fastly.Openstack, error) {\n\treturn nil, errTest\n}\n\nvar describeOpenstackOutput = \"\\n\" + strings.TrimSpace(`\nAccess key: 1234\nBucket: my-logs\nCompression codec: zstd\nFormat: %h %l %u %t \"%r\" %>s %b\nFormat version: 2\nGZip level: 0\nMessage type: classic\nName: logs\nPath: logs/\nPeriod: 3600\nPlacement: none\nProcessing region: us\nPublic key: `+pgpPublicKey()+`\nResponse condition: Prevent default logging\nService ID: 123\nTimestamp format: %Y-%m-%dT%H:%M:%S.000\nURL: https://example.com\nUser: user\nVersion: 1\n`) + \"\\n\"\n\nfunc updateOpenstackOK(_ context.Context, i *fastly.UpdateOpenstackInput) (*fastly.Openstack, error) {\n\treturn &fastly.Openstack{\n\t\tServiceID:         fastly.ToPointer(i.ServiceID),\n\t\tServiceVersion:    fastly.ToPointer(i.ServiceVersion),\n\t\tName:              fastly.ToPointer(\"log\"),\n\t\tBucketName:        fastly.ToPointer(\"my-logs\"),\n\t\tAccessKey:         fastly.ToPointer(\"1234\"),\n\t\tUser:              fastly.ToPointer(\"userupdate\"),\n\t\tURL:               fastly.ToPointer(\"https://update.example.com\"),\n\t\tPath:              fastly.ToPointer(\"logs/\"),\n\t\tPeriod:            fastly.ToPointer(3600),\n\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\tFormatVersion:     fastly.ToPointer(2),\n\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\tMessageType:       fastly.ToPointer(\"classic\"),\n\t\tTimestampFormat:   fastly.ToPointer(\"%Y-%m-%dT%H:%M:%S.000\"),\n\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\tPublicKey:         fastly.ToPointer(pgpPublicKey()),\n\t\tCompressionCodec:  fastly.ToPointer(\"zstd\"),\n\t}, nil\n}\n\nfunc updateOpenstackError(_ context.Context, _ *fastly.UpdateOpenstackInput) (*fastly.Openstack, error) {\n\treturn nil, errTest\n}\n\nfunc deleteOpenstackOK(_ context.Context, _ *fastly.DeleteOpenstackInput) error {\n\treturn nil\n}\n\nfunc deleteOpenstackError(_ context.Context, _ *fastly.DeleteOpenstackInput) error {\n\treturn errTest\n}\n\n// pgpPublicKey returns a PEM encoded PGP public key suitable for testing.\nfunc pgpPublicKey() string {\n\treturn strings.TrimSpace(`-----BEGIN PGP PUBLIC KEY BLOCK-----\nmQENBFyUD8sBCACyFnB39AuuTygseek+eA4fo0cgwva6/FSjnWq7riouQee8GgQ/\nibXTRyv4iVlwI12GswvMTIy7zNvs1R54i0qvsLr+IZ4GVGJqs6ZJnvQcqe3xPoR4\n8AnBfw90o32r/LuHf6QCJXi+AEu35koNlNAvLJ2B+KACaNB7N0EeWmqpV/1V2k9p\nlDYk+th7LcCuaFNGqKS/PrMnnMqR6VDLCjHhNx4KR79b0Twm/2qp6an3hyNRu8Gn\ndwxpf1/BUu3JWf+LqkN4Y3mbOmSUL3MaJNvyQguUzTfS0P0uGuBDHrJCVkMZCzDB\n89ag55jCPHyGeHBTd02gHMWzsg3WMBWvCsrzABEBAAG0JXRlcnJhZm9ybSAodGVz\ndCkgPHRlc3RAdGVycmFmb3JtLmNvbT6JAU4EEwEIADgWIQSHYyc6Kj9l6HzQsau6\nvFFc9jxV/wUCXJQPywIbAwULCQgHAgYVCgkICwIEFgIDAQIeAQIXgAAKCRC6vFFc\n9jxV/815CAClb32OxV7wG01yF97TzlyTl8TnvjMtoG29Mw4nSyg+mjM3b8N7iXm9\nOLX59fbDAWtBSldSZE22RXd3CvlFOG/EnKBXSjBtEqfyxYSnyOPkMPBYWGL/ApkX\nSvPYJ4LKdvipYToKFh3y9kk2gk1DcDBDyaaHvR+3rv1u3aoy7/s2EltAfDS3ZQIq\n7/cWTLJml/lleeB/Y6rPj8xqeCYhE5ahw9gsV/Mdqatl24V9Tks30iijx0Hhw+Gx\nkATUikMGr2GDVqoIRga5kXI7CzYff4rkc0Twn47fMHHHe/KY9M2yVnMHUXmAZwbG\nM1cMI/NH1DjevCKdGBLcRJlhuLPKF/anuQENBFyUD8sBCADIpd7r7GuPd6n/Ikxe\nu6h7umV6IIPoAm88xCYpTbSZiaK30Svh6Ywra9jfE2KlU9o6Y/art8ip0VJ3m07L\n4RSfSpnzqgSwdjSq5hNour2Fo/BzYhK7yaz2AzVSbe33R0+RYhb4b/6N+bKbjwGF\nftCsqVFMH+PyvYkLbvxyQrHlA9woAZaNThI1ztO5rGSnGUR8xt84eup28WIFKg0K\nUEGUcTzz+8QGAwAra+0ewPXo/AkO+8BvZjDidP417u6gpBHOJ9qYIcO9FxHeqFyu\nYrjlrxowEgXn5wO8xuNz6Vu1vhHGDHGDsRbZF8pv1d5O+0F1G7ttZ2GRRgVBZPwi\nkiyRABEBAAGJATYEGAEIACAWIQSHYyc6Kj9l6HzQsau6vFFc9jxV/wUCXJQPywIb\nDAAKCRC6vFFc9jxV/9YOCACe8qmOSnKQpQfW+PqYOqo3dt7JyweTs3FkD6NT8Zml\ndYy/vkstbTjPpX6aTvUZjkb46BVi7AOneVHpD5GBqvRsZ9iVgDYHaehmLCdKiG5L\n3Tp90NN+QY5WDbsGmsyk6+6ZMYejb4qYfweQeduOj27aavCJdLkCYMoRKfcFYI8c\nFaNmEfKKy/r1PO20NXEG6t9t05K/frHy6ZG8bCNYdpagfFVot47r9JaQqWlTNtIR\n5+zkkSq/eG9BEtRij3a6cTdQbktdBzx2KBeI0PYc1vlZR0LpuFKZqY9vlE6vTGLR\nwMfrTEOvx0NxUM3rpaCgEmuWbB1G1Hu371oyr4srrr+N\n=28dr\n-----END PGP PUBLIC KEY BLOCK-----\n`)\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/openstack/openstack_test.go",
    "content": "package openstack_test\n\nimport (\n\t\"bytes\"\n\t\"testing\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/service/logging/openstack\"\n\t\"github.com/fastly/cli/pkg/config\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/manifest\"\n\t\"github.com/fastly/cli/pkg/mock\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n)\n\nfunc TestCreateOpenstackInput(t *testing.T) {\n\tfor _, testcase := range []struct {\n\t\tname      string\n\t\tcmd       *openstack.CreateCommand\n\t\twant      *fastly.CreateOpenstackInput\n\t\twantError string\n\t}{\n\t\t{\n\t\t\tname: \"required values set flag serviceID\",\n\t\t\tcmd:  createCommandRequired(),\n\t\t\twant: &fastly.CreateOpenstackInput{\n\t\t\t\tServiceID:      \"123\",\n\t\t\t\tServiceVersion: 4,\n\t\t\t\tName:           fastly.ToPointer(\"log\"),\n\t\t\t\tBucketName:     fastly.ToPointer(\"bucket\"),\n\t\t\t\tAccessKey:      fastly.ToPointer(\"access\"),\n\t\t\t\tUser:           fastly.ToPointer(\"user\"),\n\t\t\t\tURL:            fastly.ToPointer(\"https://example.com\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"all values set flag serviceID\",\n\t\t\tcmd:  createCommandAll(),\n\t\t\twant: &fastly.CreateOpenstackInput{\n\t\t\t\tServiceID:         \"123\",\n\t\t\t\tServiceVersion:    4,\n\t\t\t\tName:              fastly.ToPointer(\"log\"),\n\t\t\t\tBucketName:        fastly.ToPointer(\"bucket\"),\n\t\t\t\tAccessKey:         fastly.ToPointer(\"access\"),\n\t\t\t\tUser:              fastly.ToPointer(\"user\"),\n\t\t\t\tURL:               fastly.ToPointer(\"https://example.com\"),\n\t\t\t\tPath:              fastly.ToPointer(\"/log\"),\n\t\t\t\tPeriod:            fastly.ToPointer(3600),\n\t\t\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\t\t\tMessageType:       fastly.ToPointer(\"classic\"),\n\t\t\t\tFormatVersion:     fastly.ToPointer(2),\n\t\t\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\t\t\tTimestampFormat:   fastly.ToPointer(\"%Y-%m-%dT%H:%M:%S.000\"),\n\t\t\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\t\t\tPublicKey:         fastly.ToPointer(pgpPublicKey()),\n\t\t\t\tCompressionCodec:  fastly.ToPointer(\"zstd\"),\n\t\t\t\tProcessingRegion:  fastly.ToPointer(\"eu\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:      \"error missing serviceID\",\n\t\t\tcmd:       createCommandMissingServiceID(),\n\t\t\twant:      nil,\n\t\t\twantError: errors.ErrNoServiceID.Error(),\n\t\t},\n\t} {\n\t\tt.Run(testcase.name, func(t *testing.T) {\n\t\t\tif testcase.wantError == errors.ErrNoServiceID.Error() {\n\t\t\t\tt.Setenv(\"FASTLY_SERVICE_ID\", \"\")\n\t\t\t}\n\t\t\tvar bs []byte\n\t\t\tout := bytes.NewBuffer(bs)\n\t\t\tverboseMode := true\n\n\t\t\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\t\t\tAutoCloneFlag:      testcase.cmd.AutoClone,\n\t\t\t\tAPIClient:          testcase.cmd.Globals.APIClient,\n\t\t\t\tManifest:           testcase.cmd.Manifest,\n\t\t\t\tOut:                out,\n\t\t\t\tServiceVersionFlag: testcase.cmd.ServiceVersion,\n\t\t\t\tVerboseMode:        verboseMode,\n\t\t\t})\n\n\t\t\tswitch {\n\t\t\tcase err != nil && testcase.wantError == \"\":\n\t\t\t\tt.Fatalf(\"unexpected error getting service details: %v\", err)\n\t\t\t\treturn\n\t\t\tcase err != nil && testcase.wantError != \"\":\n\t\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\t\treturn\n\t\t\tcase err == nil && testcase.wantError != \"\":\n\t\t\t\tt.Fatalf(\"expected error, have nil (service details: %s, %d)\", serviceID, serviceVersion.Number)\n\t\t\tcase err == nil && testcase.wantError == \"\":\n\t\t\t\thave, err := testcase.cmd.ConstructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\t\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\t\ttestutil.AssertEqual(t, testcase.want, have)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestUpdateOpenstackInput(t *testing.T) {\n\tscenarios := []struct {\n\t\tname      string\n\t\tcmd       *openstack.UpdateCommand\n\t\tapi       mock.API\n\t\twant      *fastly.UpdateOpenstackInput\n\t\twantError string\n\t}{\n\t\t{\n\t\t\tname: \"all values set flag serviceID\",\n\t\t\tcmd:  updateCommandAll(),\n\t\t\tapi: mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tGetOpenstackFn: getOpenstackOK,\n\t\t\t},\n\t\t\twant: &fastly.UpdateOpenstackInput{\n\t\t\t\tServiceID:         \"123\",\n\t\t\t\tServiceVersion:    4,\n\t\t\t\tName:              \"log\",\n\t\t\t\tNewName:           fastly.ToPointer(\"new1\"),\n\t\t\t\tBucketName:        fastly.ToPointer(\"new2\"),\n\t\t\t\tUser:              fastly.ToPointer(\"new3\"),\n\t\t\t\tAccessKey:         fastly.ToPointer(\"new4\"),\n\t\t\t\tURL:               fastly.ToPointer(\"new5\"),\n\t\t\t\tPath:              fastly.ToPointer(\"new6\"),\n\t\t\t\tPeriod:            fastly.ToPointer(3601),\n\t\t\t\tGzipLevel:         fastly.ToPointer(0),\n\t\t\t\tFormat:            fastly.ToPointer(\"new7\"),\n\t\t\t\tFormatVersion:     fastly.ToPointer(3),\n\t\t\t\tResponseCondition: fastly.ToPointer(\"new8\"),\n\t\t\t\tMessageType:       fastly.ToPointer(\"new9\"),\n\t\t\t\tTimestampFormat:   fastly.ToPointer(\"new10\"),\n\t\t\t\tPlacement:         fastly.ToPointer(\"new11\"),\n\t\t\t\tPublicKey:         fastly.ToPointer(\"new12\"),\n\t\t\t\tCompressionCodec:  fastly.ToPointer(\"new13\"),\n\t\t\t\tProcessingRegion:  fastly.ToPointer(\"eu\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"no updates\",\n\t\t\tcmd:  updateCommandNoUpdates(),\n\t\t\tapi: mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tGetOpenstackFn: getOpenstackOK,\n\t\t\t},\n\t\t\twant: &fastly.UpdateOpenstackInput{\n\t\t\t\tServiceID:      \"123\",\n\t\t\t\tServiceVersion: 4,\n\t\t\t\tName:           \"log\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:      \"error missing serviceID\",\n\t\t\tcmd:       updateCommandMissingServiceID(),\n\t\t\twant:      nil,\n\t\t\twantError: errors.ErrNoServiceID.Error(),\n\t\t},\n\t}\n\tfor testcaseIdx := range scenarios {\n\t\ttestcase := &scenarios[testcaseIdx]\n\t\tt.Run(testcase.name, func(t *testing.T) {\n\t\t\tif testcase.wantError == errors.ErrNoServiceID.Error() {\n\t\t\t\tt.Setenv(\"FASTLY_SERVICE_ID\", \"\")\n\t\t\t}\n\t\t\ttestcase.cmd.Globals.APIClient = testcase.api\n\n\t\t\tvar bs []byte\n\t\t\tout := bytes.NewBuffer(bs)\n\t\t\tverboseMode := true\n\n\t\t\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\t\t\tAutoCloneFlag:      testcase.cmd.AutoClone,\n\t\t\t\tAPIClient:          testcase.api,\n\t\t\t\tManifest:           testcase.cmd.Manifest,\n\t\t\t\tOut:                out,\n\t\t\t\tServiceVersionFlag: testcase.cmd.ServiceVersion,\n\t\t\t\tVerboseMode:        verboseMode,\n\t\t\t})\n\n\t\t\tswitch {\n\t\t\tcase err != nil && testcase.wantError == \"\":\n\t\t\t\tt.Fatalf(\"unexpected error getting service details: %v\", err)\n\t\t\t\treturn\n\t\t\tcase err != nil && testcase.wantError != \"\":\n\t\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\t\treturn\n\t\t\tcase err == nil && testcase.wantError != \"\":\n\t\t\t\tt.Fatalf(\"expected error, have nil (service details: %s, %d)\", serviceID, serviceVersion.Number)\n\t\t\tcase err == nil && testcase.wantError == \"\":\n\t\t\t\thave, err := testcase.cmd.ConstructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\t\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\t\ttestutil.AssertEqual(t, testcase.want, have)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc createCommandRequired() *openstack.CreateCommand {\n\tvar b bytes.Buffer\n\n\tg := global.Data{\n\t\tConfig: config.File{},\n\t\tEnv:    config.Environment{},\n\t\tOutput: &b,\n\t}\n\tg.APIClient, _ = mock.APIClient(mock.API{\n\t\tGetVersionFn:   testutil.GetVersion,\n\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t})(\"token\", \"endpoint\", false)\n\n\treturn &openstack.CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: &g,\n\t\t},\n\t\tManifest: manifest.Data{\n\t\t\tFlag: manifest.Flag{\n\t\t\t\tServiceID: \"123\",\n\t\t\t},\n\t\t},\n\t\tServiceVersion: argparser.OptionalServiceVersion{\n\t\t\tOptionalString: argparser.OptionalString{Value: \"1\"},\n\t\t},\n\t\tAutoClone: argparser.OptionalAutoClone{\n\t\t\tOptionalBool: argparser.OptionalBool{\n\t\t\t\tOptional: argparser.Optional{\n\t\t\t\t\tWasSet: true,\n\t\t\t\t},\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t},\n\t\tEndpointName: argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"log\"},\n\t\tBucketName:   argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"bucket\"},\n\t\tAccessKey:    argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"access\"},\n\t\tUser:         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"user\"},\n\t\tURL:          argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"https://example.com\"},\n\t}\n}\n\nfunc createCommandAll() *openstack.CreateCommand {\n\tvar b bytes.Buffer\n\n\tg := global.Data{\n\t\tConfig: config.File{},\n\t\tEnv:    config.Environment{},\n\t\tOutput: &b,\n\t}\n\tg.APIClient, _ = mock.APIClient(mock.API{\n\t\tGetVersionFn:   testutil.GetVersion,\n\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t})(\"token\", \"endpoint\", false)\n\n\treturn &openstack.CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: &g,\n\t\t},\n\t\tManifest: manifest.Data{\n\t\t\tFlag: manifest.Flag{\n\t\t\t\tServiceID: \"123\",\n\t\t\t},\n\t\t},\n\t\tServiceVersion: argparser.OptionalServiceVersion{\n\t\t\tOptionalString: argparser.OptionalString{Value: \"1\"},\n\t\t},\n\t\tAutoClone: argparser.OptionalAutoClone{\n\t\t\tOptionalBool: argparser.OptionalBool{\n\t\t\t\tOptional: argparser.Optional{\n\t\t\t\t\tWasSet: true,\n\t\t\t\t},\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t},\n\t\tEndpointName:      argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"log\"},\n\t\tBucketName:        argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"bucket\"},\n\t\tAccessKey:         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"access\"},\n\t\tUser:              argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"user\"},\n\t\tURL:               argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"https://example.com\"},\n\t\tPath:              argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"/log\"},\n\t\tPeriod:            argparser.OptionalInt{Optional: argparser.Optional{WasSet: true}, Value: 3600},\n\t\tFormat:            argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: `%h %l %u %t \"%r\" %>s %b`},\n\t\tFormatVersion:     argparser.OptionalInt{Optional: argparser.Optional{WasSet: true}, Value: 2},\n\t\tResponseCondition: argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"Prevent default logging\"},\n\t\tTimestampFormat:   argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"%Y-%m-%dT%H:%M:%S.000\"},\n\t\tPlacement:         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"none\"},\n\t\tMessageType:       argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"classic\"},\n\t\tPublicKey:         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: pgpPublicKey()},\n\t\tCompressionCodec:  argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"zstd\"},\n\t\tProcessingRegion:  argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"eu\"},\n\t}\n}\n\nfunc createCommandMissingServiceID() *openstack.CreateCommand {\n\tres := createCommandAll()\n\tres.Manifest = manifest.Data{}\n\tres.ServiceVersion = argparser.OptionalServiceVersion{}\n\treturn res\n}\n\nfunc updateCommandNoUpdates() *openstack.UpdateCommand {\n\tvar b bytes.Buffer\n\n\tg := global.Data{\n\t\tConfig: config.File{},\n\t\tEnv:    config.Environment{},\n\t\tOutput: &b,\n\t}\n\n\treturn &openstack.UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: &g,\n\t\t},\n\t\tManifest: manifest.Data{\n\t\t\tFlag: manifest.Flag{\n\t\t\t\tServiceID: \"123\",\n\t\t\t},\n\t\t},\n\t\tEndpointName: \"log\",\n\t\tServiceVersion: argparser.OptionalServiceVersion{\n\t\t\tOptionalString: argparser.OptionalString{Value: \"1\"},\n\t\t},\n\t\tAutoClone: argparser.OptionalAutoClone{\n\t\t\tOptionalBool: argparser.OptionalBool{\n\t\t\t\tOptional: argparser.Optional{\n\t\t\t\t\tWasSet: true,\n\t\t\t\t},\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t},\n\t}\n}\n\nfunc updateCommandAll() *openstack.UpdateCommand {\n\tvar b bytes.Buffer\n\n\tg := global.Data{\n\t\tConfig: config.File{},\n\t\tEnv:    config.Environment{},\n\t\tOutput: &b,\n\t}\n\n\treturn &openstack.UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: &g,\n\t\t},\n\t\tManifest: manifest.Data{\n\t\t\tFlag: manifest.Flag{\n\t\t\t\tServiceID: \"123\",\n\t\t\t},\n\t\t},\n\t\tEndpointName: \"log\",\n\t\tServiceVersion: argparser.OptionalServiceVersion{\n\t\t\tOptionalString: argparser.OptionalString{Value: \"1\"},\n\t\t},\n\t\tAutoClone: argparser.OptionalAutoClone{\n\t\t\tOptionalBool: argparser.OptionalBool{\n\t\t\t\tOptional: argparser.Optional{\n\t\t\t\t\tWasSet: true,\n\t\t\t\t},\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t},\n\t\tNewName:           argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new1\"},\n\t\tBucketName:        argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new2\"},\n\t\tUser:              argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new3\"},\n\t\tAccessKey:         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new4\"},\n\t\tURL:               argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new5\"},\n\t\tPath:              argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new6\"},\n\t\tPeriod:            argparser.OptionalInt{Optional: argparser.Optional{WasSet: true}, Value: 3601},\n\t\tGzipLevel:         argparser.OptionalInt{Optional: argparser.Optional{WasSet: true}, Value: 0},\n\t\tFormat:            argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new7\"},\n\t\tFormatVersion:     argparser.OptionalInt{Optional: argparser.Optional{WasSet: true}, Value: 3},\n\t\tResponseCondition: argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new8\"},\n\t\tMessageType:       argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new9\"},\n\t\tTimestampFormat:   argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new10\"},\n\t\tPlacement:         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new11\"},\n\t\tPublicKey:         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new12\"},\n\t\tCompressionCodec:  argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new13\"},\n\t\tProcessingRegion:  argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"eu\"},\n\t}\n}\n\nfunc updateCommandMissingServiceID() *openstack.UpdateCommand {\n\tres := updateCommandAll()\n\tres.Manifest = manifest.Data{}\n\tres.ServiceVersion = argparser.OptionalServiceVersion{}\n\treturn res\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/openstack/root.go",
    "content": "package openstack\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"openstack\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manipulate Fastly service version OpenStack logging endpoints\")\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/openstack/update.go",
    "content": "package openstack\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/service/logging/logflags\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/manifest\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// UpdateCommand calls the Fastly API to update an OpenStack logging endpoint.\ntype UpdateCommand struct {\n\targparser.Base\n\tManifest manifest.Data\n\n\t// Required.\n\tEndpointName   string\n\tServiceName    argparser.OptionalServiceNameID\n\tServiceVersion argparser.OptionalServiceVersion\n\n\t// Optional.\n\tAccessKey         argparser.OptionalString\n\tAutoClone         argparser.OptionalAutoClone\n\tBucketName        argparser.OptionalString\n\tCompressionCodec  argparser.OptionalString\n\tFormat            argparser.OptionalString\n\tFormatVersion     argparser.OptionalInt\n\tGzipLevel         argparser.OptionalInt\n\tMessageType       argparser.OptionalString\n\tNewName           argparser.OptionalString\n\tPath              argparser.OptionalString\n\tPeriod            argparser.OptionalInt\n\tPlacement         argparser.OptionalString\n\tProcessingRegion  argparser.OptionalString\n\tPublicKey         argparser.OptionalString\n\tResponseCondition argparser.OptionalString\n\tTimestampFormat   argparser.OptionalString\n\tURL               argparser.OptionalString\n\tUser              argparser.OptionalString\n}\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"update\", \"Update an OpenStack logging endpoint on a Fastly service version\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"The name of the OpenStack logging object\").Short('n').Required().StringVar(&c.EndpointName)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.ServiceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.AutoClone.Set,\n\t\tDst:    &c.AutoClone.Value,\n\t})\n\tc.CmdClause.Flag(\"access-key\", \"Your OpenStack account access key\").Action(c.AccessKey.Set).StringVar(&c.AccessKey.Value)\n\tc.CmdClause.Flag(\"bucket\", \"The name of the Openstack Space\").Action(c.BucketName.Set).StringVar(&c.BucketName.Value)\n\tlogflags.CompressionCodec(c.CmdClause, &c.CompressionCodec)\n\tlogflags.Format(c.CmdClause, &c.Format)\n\tlogflags.FormatVersion(c.CmdClause, &c.FormatVersion)\n\tlogflags.GzipLevel(c.CmdClause, &c.GzipLevel)\n\tlogflags.MessageType(c.CmdClause, &c.MessageType)\n\tc.CmdClause.Flag(\"new-name\", \"New name of the OpenStack logging object\").Action(c.NewName.Set).StringVar(&c.NewName.Value)\n\tlogflags.Path(c.CmdClause, &c.Path)\n\tlogflags.Period(c.CmdClause, &c.Period)\n\tlogflags.Placement(c.CmdClause, &c.Placement)\n\tlogflags.ProcessingRegion(c.CmdClause, &c.ProcessingRegion, \"OpenStack\")\n\tlogflags.PublicKey(c.CmdClause, &c.PublicKey)\n\tlogflags.ResponseCondition(c.CmdClause, &c.ResponseCondition)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.ServiceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.ServiceName.Value,\n\t})\n\tlogflags.TimestampFormat(c.CmdClause, &c.TimestampFormat)\n\tc.CmdClause.Flag(\"url\", \"Your OpenStack auth url.\").Action(c.URL.Set).StringVar(&c.URL.Value)\n\tc.CmdClause.Flag(\"user\", \"The username for your OpenStack account.\").Action(c.User.Set).StringVar(&c.User.Value)\n\n\treturn &c\n}\n\n// ConstructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *UpdateCommand) ConstructInput(serviceID string, serviceVersion int) (*fastly.UpdateOpenstackInput, error) {\n\tinput := fastly.UpdateOpenstackInput{\n\t\tServiceID:      serviceID,\n\t\tServiceVersion: serviceVersion,\n\t\tName:           c.EndpointName,\n\t}\n\n\t// Set new values if set by user.\n\tif c.NewName.WasSet {\n\t\tinput.NewName = &c.NewName.Value\n\t}\n\n\tif c.BucketName.WasSet {\n\t\tinput.BucketName = &c.BucketName.Value\n\t}\n\n\tif c.AccessKey.WasSet {\n\t\tinput.AccessKey = &c.AccessKey.Value\n\t}\n\n\tif c.User.WasSet {\n\t\tinput.User = &c.User.Value\n\t}\n\n\tif c.URL.WasSet {\n\t\tinput.URL = &c.URL.Value\n\t}\n\n\tif c.Path.WasSet {\n\t\tinput.Path = &c.Path.Value\n\t}\n\n\tif c.Period.WasSet {\n\t\tinput.Period = &c.Period.Value\n\t}\n\n\tif c.GzipLevel.WasSet {\n\t\tinput.GzipLevel = &c.GzipLevel.Value\n\t}\n\n\tif c.Format.WasSet {\n\t\tinput.Format = fastly.ToPointer(argparser.Content(c.Format.Value))\n\t}\n\n\tif c.FormatVersion.WasSet {\n\t\tinput.FormatVersion = &c.FormatVersion.Value\n\t}\n\n\tif c.ResponseCondition.WasSet {\n\t\tinput.ResponseCondition = &c.ResponseCondition.Value\n\t}\n\n\tif c.MessageType.WasSet {\n\t\tinput.MessageType = &c.MessageType.Value\n\t}\n\n\tif c.TimestampFormat.WasSet {\n\t\tinput.TimestampFormat = &c.TimestampFormat.Value\n\t}\n\n\tif c.Placement.WasSet {\n\t\tinput.Placement = &c.Placement.Value\n\t}\n\n\tif c.PublicKey.WasSet {\n\t\tinput.PublicKey = &c.PublicKey.Value\n\t}\n\n\tif c.CompressionCodec.WasSet {\n\t\tinput.CompressionCodec = &c.CompressionCodec.Value\n\t}\n\n\tif c.ProcessingRegion.WasSet {\n\t\tinput.ProcessingRegion = &c.ProcessingRegion.Value\n\t}\n\n\treturn &input, nil\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *UpdateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(false),\n\t\tLocked:             optional.Of(false),\n\t\tAutoCloneFlag:      c.AutoClone,\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.ServiceName,\n\t\tServiceVersionFlag: c.ServiceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": errors.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tinput, err := c.ConstructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\topenstack, err := c.Globals.APIClient.UpdateOpenstack(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\ttext.Success(\n\t\tout,\n\t\t\"Updated OpenStack logging endpoint %s (service %s version %d)\",\n\t\tfastly.ToValue(openstack.Name),\n\t\tfastly.ToValue(openstack.ServiceID),\n\t\tfastly.ToValue(openstack.ServiceVersion),\n\t)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/papertrail/create.go",
    "content": "package papertrail\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/service/logging/logflags\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/manifest\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// CreateCommand calls the Fastly API to create a Papertrail logging endpoint.\ntype CreateCommand struct {\n\targparser.Base\n\tManifest manifest.Data\n\n\t// Required.\n\tServiceName    argparser.OptionalServiceNameID\n\tServiceVersion argparser.OptionalServiceVersion\n\n\t// Optional.\n\tAddress           argparser.OptionalString\n\tAutoClone         argparser.OptionalAutoClone\n\tEndpointName      argparser.OptionalString // Can't shadow argparser.Base method Name().\n\tFormat            argparser.OptionalString\n\tFormatVersion     argparser.OptionalInt\n\tPlacement         argparser.OptionalString\n\tPort              argparser.OptionalInt\n\tProcessingRegion  argparser.OptionalString\n\tResponseCondition argparser.OptionalString\n}\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"create\", \"Create a Papertrail logging endpoint on a Fastly service version\").Alias(\"add\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"The name of the Papertrail logging object. Used as a primary key for API access\").Short('n').Action(c.EndpointName.Set).StringVar(&c.EndpointName.Value)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.ServiceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.CmdClause.Flag(\"address\", \"A hostname or IPv4 address\").Action(c.Address.Set).StringVar(&c.Address.Value)\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.AutoClone.Set,\n\t\tDst:    &c.AutoClone.Value,\n\t})\n\tlogflags.FormatVersion(c.CmdClause, &c.FormatVersion)\n\tlogflags.Format(c.CmdClause, &c.Format)\n\tlogflags.Placement(c.CmdClause, &c.Placement)\n\tc.CmdClause.Flag(\"port\", \"The port number\").Action(c.Port.Set).IntVar(&c.Port.Value)\n\tlogflags.ProcessingRegion(c.CmdClause, &c.ProcessingRegion, \"Papertrail\")\n\tlogflags.ResponseCondition(c.CmdClause, &c.ResponseCondition)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.ServiceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.ServiceName.Value,\n\t})\n\treturn &c\n}\n\n// ConstructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *CreateCommand) ConstructInput(serviceID string, serviceVersion int) (*fastly.CreatePapertrailInput, error) {\n\tvar input fastly.CreatePapertrailInput\n\n\tinput.ServiceID = serviceID\n\tif c.EndpointName.WasSet {\n\t\tinput.Name = &c.EndpointName.Value\n\t}\n\tinput.ServiceVersion = serviceVersion\n\tif c.Address.WasSet {\n\t\tinput.Address = &c.Address.Value\n\t}\n\n\tif c.Port.WasSet {\n\t\tinput.Port = &c.Port.Value\n\t}\n\n\tif c.Format.WasSet {\n\t\tinput.Format = fastly.ToPointer(argparser.Content(c.Format.Value))\n\t}\n\n\tif c.FormatVersion.WasSet {\n\t\tinput.FormatVersion = &c.FormatVersion.Value\n\t}\n\n\tif c.ResponseCondition.WasSet {\n\t\tinput.ResponseCondition = &c.ResponseCondition.Value\n\t}\n\n\tif c.Placement.WasSet {\n\t\tinput.Placement = &c.Placement.Value\n\t}\n\n\tif c.ProcessingRegion.WasSet {\n\t\tinput.ProcessingRegion = &c.ProcessingRegion.Value\n\t}\n\n\treturn &input, nil\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *CreateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(false),\n\t\tLocked:             optional.Of(false),\n\t\tAutoCloneFlag:      c.AutoClone,\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.ServiceName,\n\t\tServiceVersionFlag: c.ServiceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": errors.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tinput, err := c.ConstructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\td, err := c.Globals.APIClient.CreatePapertrail(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\ttext.Success(\n\t\tout,\n\t\t\"Created Papertrail logging endpoint %s (service %s version %d)\",\n\t\tfastly.ToValue(d.Name),\n\t\tfastly.ToValue(d.ServiceID),\n\t\tfastly.ToValue(d.ServiceVersion),\n\t)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/papertrail/delete.go",
    "content": "package papertrail\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DeleteCommand calls the Fastly API to delete a Papertrail logging endpoint.\ntype DeleteCommand struct {\n\targparser.Base\n\tInput          fastly.DeletePapertrailInput\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n\tautoClone      argparser.OptionalAutoClone\n}\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"delete\", \"Delete a Papertrail logging endpoint on a Fastly service version\").Alias(\"remove\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"The name of the Papertrail logging object\").Short('n').Required().StringVar(&c.Input.Name)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.autoClone.Set,\n\t\tDst:    &c.autoClone.Value,\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DeleteCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(false),\n\t\tLocked:             optional.Of(false),\n\t\tAutoCloneFlag:      c.autoClone,\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": errors.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tc.Input.ServiceID = serviceID\n\tc.Input.ServiceVersion = fastly.ToValue(serviceVersion.Number)\n\n\tif err := c.Globals.APIClient.DeletePapertrail(context.TODO(), &c.Input); err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Deleted Papertrail logging endpoint %s (service %s version %d)\", c.Input.Name, c.Input.ServiceID, c.Input.ServiceVersion)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/papertrail/describe.go",
    "content": "package papertrail\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DescribeCommand calls the Fastly API to describe a Papertrail logging endpoint.\ntype DescribeCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tInput          fastly.GetPapertrailInput\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n}\n\n// NewDescribeCommand returns a usable command registered under the parent.\nfunc NewDescribeCommand(parent argparser.Registerer, g *global.Data) *DescribeCommand {\n\tc := DescribeCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"describe\", \"Show detailed information about a Papertrail logging endpoint on a Fastly service version\").Alias(\"get\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"The name of the Papertrail logging object\").Short('n').Required().StringVar(&c.Input.Name)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DescribeCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fsterr.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tc.Input.ServiceID = serviceID\n\tc.Input.ServiceVersion = fastly.ToValue(serviceVersion.Number)\n\n\to, err := c.Globals.APIClient.GetPapertrail(context.TODO(), &c.Input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\tlines := text.Lines{\n\t\t\"Address\":            fastly.ToValue(o.Address),\n\t\t\"Format version\":     fastly.ToValue(o.FormatVersion),\n\t\t\"Format\":             fastly.ToValue(o.Format),\n\t\t\"Name\":               fastly.ToValue(o.Name),\n\t\t\"Placement\":          fastly.ToValue(o.Placement),\n\t\t\"Port\":               fastly.ToValue(o.Port),\n\t\t\"Processing region\":  fastly.ToValue(o.ProcessingRegion),\n\t\t\"Response condition\": fastly.ToValue(o.ResponseCondition),\n\t\t\"Version\":            fastly.ToValue(o.ServiceVersion),\n\t}\n\tif !c.Globals.Verbose() {\n\t\tlines[\"Service ID\"] = fastly.ToValue(o.ServiceID)\n\t}\n\ttext.PrintLines(out, lines)\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/papertrail/doc.go",
    "content": "// Package papertrail contains commands to inspect and manipulate Fastly service Papertrail\n// logging endpoints.\npackage papertrail\n"
  },
  {
    "path": "pkg/commands/service/logging/papertrail/list.go",
    "content": "package papertrail\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// ListCommand calls the Fastly API to list Papertrail logging endpoints.\ntype ListCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tInput          fastly.ListPapertrailsInput\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n}\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"list\", \"List Papertrail endpoints on a Fastly service version\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *ListCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fsterr.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tc.Input.ServiceID = serviceID\n\tc.Input.ServiceVersion = fastly.ToValue(serviceVersion.Number)\n\n\to, err := c.Globals.APIClient.ListPapertrails(context.TODO(), &c.Input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\tif !c.Globals.Verbose() {\n\t\ttw := text.NewTable(out)\n\t\ttw.AddHeader(\"SERVICE\", \"VERSION\", \"NAME\")\n\t\tfor _, papertrail := range o {\n\t\t\ttw.AddLine(\n\t\t\t\tfastly.ToValue(papertrail.ServiceID),\n\t\t\t\tfastly.ToValue(papertrail.ServiceVersion),\n\t\t\t\tfastly.ToValue(papertrail.Name),\n\t\t\t)\n\t\t}\n\t\ttw.Print()\n\t\treturn nil\n\t}\n\n\tfmt.Fprintf(out, \"Version: %d\\n\", c.Input.ServiceVersion)\n\tfor i, papertrail := range o {\n\t\tfmt.Fprintf(out, \"\\tPapertrail %d/%d\\n\", i+1, len(o))\n\t\tfmt.Fprintf(out, \"\\t\\tService ID: %s\\n\", fastly.ToValue(papertrail.ServiceID))\n\t\tfmt.Fprintf(out, \"\\t\\tVersion: %d\\n\", fastly.ToValue(papertrail.ServiceVersion))\n\t\tfmt.Fprintf(out, \"\\t\\tName: %s\\n\", fastly.ToValue(papertrail.Name))\n\t\tfmt.Fprintf(out, \"\\t\\tAddress: %s\\n\", fastly.ToValue(papertrail.Address))\n\t\tfmt.Fprintf(out, \"\\t\\tPort: %d\\n\", fastly.ToValue(papertrail.Port))\n\t\tfmt.Fprintf(out, \"\\t\\tFormat: %s\\n\", fastly.ToValue(papertrail.Format))\n\t\tfmt.Fprintf(out, \"\\t\\tFormat version: %d\\n\", fastly.ToValue(papertrail.FormatVersion))\n\t\tfmt.Fprintf(out, \"\\t\\tResponse condition: %s\\n\", fastly.ToValue(papertrail.ResponseCondition))\n\t\tfmt.Fprintf(out, \"\\t\\tPlacement: %s\\n\", fastly.ToValue(papertrail.Placement))\n\t\tfmt.Fprintf(out, \"\\t\\tProcessing region: %s\\n\", fastly.ToValue(papertrail.ProcessingRegion))\n\t}\n\tfmt.Fprintln(out)\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/papertrail/papertrail_integration_test.go",
    "content": "package papertrail_test\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/mock\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n\n\troot \"github.com/fastly/cli/pkg/commands/service\"\n\tparent \"github.com/fastly/cli/pkg/commands/service/logging\"\n\tsub \"github.com/fastly/cli/pkg/commands/service/logging/papertrail\"\n)\n\nfunc TestPapertrailCreate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name log --address example.com:123 --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:       testutil.GetVersion,\n\t\t\t\tCloneVersionFn:     testutil.CloneVersionResult(4),\n\t\t\t\tCreatePapertrailFn: createPapertrailOK,\n\t\t\t},\n\t\t\tWantOutput: \"Created Papertrail logging endpoint log (service 123 version 4)\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name log --address example.com:123 --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:       testutil.GetVersion,\n\t\t\t\tCloneVersionFn:     testutil.CloneVersionResult(4),\n\t\t\t\tCreatePapertrailFn: createPapertrailError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"create\"}, scenarios)\n}\n\nfunc TestPapertrailList(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:      testutil.GetVersion,\n\t\t\t\tListPapertrailsFn: listPapertrailsOK,\n\t\t\t},\n\t\t\tWantOutput: listPapertrailsShortOutput,\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --verbose\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:      testutil.GetVersion,\n\t\t\t\tListPapertrailsFn: listPapertrailsOK,\n\t\t\t},\n\t\t\tWantOutput: listPapertrailsVerboseOutput,\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 -v\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:      testutil.GetVersion,\n\t\t\t\tListPapertrailsFn: listPapertrailsOK,\n\t\t\t},\n\t\t\tWantOutput: listPapertrailsVerboseOutput,\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:      testutil.GetVersion,\n\t\t\t\tListPapertrailsFn: listPapertrailsError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"list\"}, scenarios)\n}\n\nfunc TestPapertrailDescribe(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"--service-id 123 --version 1\",\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:    testutil.GetVersion,\n\t\t\t\tGetPapertrailFn: getPapertrailError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:    testutil.GetVersion,\n\t\t\t\tGetPapertrailFn: getPapertrailOK,\n\t\t\t},\n\t\t\tWantOutput: describePapertrailOutput,\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"describe\"}, scenarios)\n}\n\nfunc TestPapertrailUpdate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"--service-id 123 --version 1 --new-name log\",\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs --new-name log --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:       testutil.GetVersion,\n\t\t\t\tCloneVersionFn:     testutil.CloneVersionResult(4),\n\t\t\t\tUpdatePapertrailFn: updatePapertrailError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs --new-name log --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:       testutil.GetVersion,\n\t\t\t\tCloneVersionFn:     testutil.CloneVersionResult(4),\n\t\t\t\tUpdatePapertrailFn: updatePapertrailOK,\n\t\t\t},\n\t\t\tWantOutput: \"Updated Papertrail logging endpoint log (service 123 version 4)\",\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"update\"}, scenarios)\n}\n\nfunc TestPapertrailDelete(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"--service-id 123 --version 1\",\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:       testutil.GetVersion,\n\t\t\t\tCloneVersionFn:     testutil.CloneVersionResult(4),\n\t\t\t\tDeletePapertrailFn: deletePapertrailError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:       testutil.GetVersion,\n\t\t\t\tCloneVersionFn:     testutil.CloneVersionResult(4),\n\t\t\t\tDeletePapertrailFn: deletePapertrailOK,\n\t\t\t},\n\t\t\tWantOutput: \"Deleted Papertrail logging endpoint logs (service 123 version 4)\",\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"delete\"}, scenarios)\n}\n\nvar errTest = errors.New(\"fixture error\")\n\nfunc createPapertrailOK(_ context.Context, i *fastly.CreatePapertrailInput) (*fastly.Papertrail, error) {\n\treturn &fastly.Papertrail{\n\t\tServiceID:      fastly.ToPointer(i.ServiceID),\n\t\tServiceVersion: fastly.ToPointer(i.ServiceVersion),\n\t\tName:           i.Name,\n\t}, nil\n}\n\nfunc createPapertrailError(_ context.Context, _ *fastly.CreatePapertrailInput) (*fastly.Papertrail, error) {\n\treturn nil, errTest\n}\n\nfunc listPapertrailsOK(_ context.Context, i *fastly.ListPapertrailsInput) ([]*fastly.Papertrail, error) {\n\treturn []*fastly.Papertrail{\n\t\t{\n\t\t\tServiceID:         fastly.ToPointer(i.ServiceID),\n\t\t\tServiceVersion:    fastly.ToPointer(i.ServiceVersion),\n\t\t\tName:              fastly.ToPointer(\"logs\"),\n\t\t\tAddress:           fastly.ToPointer(\"example.com:123\"),\n\t\t\tPort:              fastly.ToPointer(123),\n\t\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\t\tFormatVersion:     fastly.ToPointer(2),\n\t\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\t\tProcessingRegion:  fastly.ToPointer(\"us\"),\n\t\t},\n\t\t{\n\t\t\tServiceID:         fastly.ToPointer(i.ServiceID),\n\t\t\tServiceVersion:    fastly.ToPointer(i.ServiceVersion),\n\t\t\tName:              fastly.ToPointer(\"analytics\"),\n\t\t\tAddress:           fastly.ToPointer(\"127.0.0.1:456\"),\n\t\t\tPort:              fastly.ToPointer(456),\n\t\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\t\tFormatVersion:     fastly.ToPointer(2),\n\t\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\t\tProcessingRegion:  fastly.ToPointer(\"us\"),\n\t\t},\n\t}, nil\n}\n\nfunc listPapertrailsError(_ context.Context, _ *fastly.ListPapertrailsInput) ([]*fastly.Papertrail, error) {\n\treturn nil, errTest\n}\n\nvar listPapertrailsShortOutput = strings.TrimSpace(`\nSERVICE  VERSION  NAME\n123      1        logs\n123      1        analytics\n`) + \"\\n\"\n\nvar listPapertrailsVerboseOutput = strings.TrimSpace(`\nFastly API endpoint: https://api.fastly.com\nFastly API token provided via config file (auth: user)\n\nService ID (via --service-id): 123\n\nVersion: 1\n\tPapertrail 1/2\n\t\tService ID: 123\n\t\tVersion: 1\n\t\tName: logs\n\t\tAddress: example.com:123\n\t\tPort: 123\n\t\tFormat: %h %l %u %t \"%r\" %>s %b\n\t\tFormat version: 2\n\t\tResponse condition: Prevent default logging\n\t\tPlacement: none\n\t\tProcessing region: us\n\tPapertrail 2/2\n\t\tService ID: 123\n\t\tVersion: 1\n\t\tName: analytics\n\t\tAddress: 127.0.0.1:456\n\t\tPort: 456\n\t\tFormat: %h %l %u %t \"%r\" %>s %b\n\t\tFormat version: 2\n\t\tResponse condition: Prevent default logging\n\t\tPlacement: none\n\t\tProcessing region: us\n`) + \"\\n\\n\"\n\nfunc getPapertrailOK(_ context.Context, i *fastly.GetPapertrailInput) (*fastly.Papertrail, error) {\n\treturn &fastly.Papertrail{\n\t\tServiceID:         fastly.ToPointer(i.ServiceID),\n\t\tServiceVersion:    fastly.ToPointer(i.ServiceVersion),\n\t\tName:              fastly.ToPointer(\"logs\"),\n\t\tAddress:           fastly.ToPointer(\"example.com:123\"),\n\t\tPort:              fastly.ToPointer(123),\n\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\tFormatVersion:     fastly.ToPointer(2),\n\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\tProcessingRegion:  fastly.ToPointer(\"us\"),\n\t}, nil\n}\n\nfunc getPapertrailError(_ context.Context, _ *fastly.GetPapertrailInput) (*fastly.Papertrail, error) {\n\treturn nil, errTest\n}\n\nvar describePapertrailOutput = \"\\n\" + strings.TrimSpace(`\nAddress: example.com:123\nFormat: %h %l %u %t \"%r\" %>s %b\nFormat version: 2\nName: logs\nPlacement: none\nPort: 123\nProcessing region: us\nResponse condition: Prevent default logging\nService ID: 123\nVersion: 1\n`) + \"\\n\"\n\nfunc updatePapertrailOK(_ context.Context, i *fastly.UpdatePapertrailInput) (*fastly.Papertrail, error) {\n\treturn &fastly.Papertrail{\n\t\tServiceID:         fastly.ToPointer(i.ServiceID),\n\t\tServiceVersion:    fastly.ToPointer(i.ServiceVersion),\n\t\tName:              fastly.ToPointer(\"log\"),\n\t\tAddress:           fastly.ToPointer(\"example.com:123\"),\n\t\tPort:              fastly.ToPointer(123),\n\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\tFormatVersion:     fastly.ToPointer(2),\n\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t}, nil\n}\n\nfunc updatePapertrailError(_ context.Context, _ *fastly.UpdatePapertrailInput) (*fastly.Papertrail, error) {\n\treturn nil, errTest\n}\n\nfunc deletePapertrailOK(_ context.Context, _ *fastly.DeletePapertrailInput) error {\n\treturn nil\n}\n\nfunc deletePapertrailError(_ context.Context, _ *fastly.DeletePapertrailInput) error {\n\treturn errTest\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/papertrail/papertrail_test.go",
    "content": "package papertrail_test\n\nimport (\n\t\"bytes\"\n\t\"testing\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/service/logging/papertrail\"\n\t\"github.com/fastly/cli/pkg/config\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/manifest\"\n\t\"github.com/fastly/cli/pkg/mock\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n)\n\nfunc TestCreatePapertrailInput(t *testing.T) {\n\tfor _, testcase := range []struct {\n\t\tname      string\n\t\tcmd       *papertrail.CreateCommand\n\t\twant      *fastly.CreatePapertrailInput\n\t\twantError string\n\t}{\n\t\t{\n\t\t\tname: \"required values set flag serviceID\",\n\t\t\tcmd:  createCommandRequired(),\n\t\t\twant: &fastly.CreatePapertrailInput{\n\t\t\t\tServiceID:      \"123\",\n\t\t\t\tServiceVersion: 4,\n\t\t\t\tName:           fastly.ToPointer(\"log\"),\n\t\t\t\tAddress:        fastly.ToPointer(\"example.com\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"all values set flag serviceID\",\n\t\t\tcmd:  createCommandAll(),\n\t\t\twant: &fastly.CreatePapertrailInput{\n\t\t\t\tServiceID:         \"123\",\n\t\t\t\tServiceVersion:    4,\n\t\t\t\tName:              fastly.ToPointer(\"log\"),\n\t\t\t\tAddress:           fastly.ToPointer(\"example.com\"),\n\t\t\t\tPort:              fastly.ToPointer(22),\n\t\t\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\t\t\tFormatVersion:     fastly.ToPointer(2),\n\t\t\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\t\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\t\t\tProcessingRegion:  fastly.ToPointer(\"eu\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:      \"error missing serviceID\",\n\t\t\tcmd:       createCommandMissingServiceID(),\n\t\t\twant:      nil,\n\t\t\twantError: errors.ErrNoServiceID.Error(),\n\t\t},\n\t} {\n\t\tt.Run(testcase.name, func(t *testing.T) {\n\t\t\tif testcase.wantError == errors.ErrNoServiceID.Error() {\n\t\t\t\tt.Setenv(\"FASTLY_SERVICE_ID\", \"\")\n\t\t\t}\n\t\t\tvar bs []byte\n\t\t\tout := bytes.NewBuffer(bs)\n\t\t\tverboseMode := true\n\n\t\t\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\t\t\tAutoCloneFlag:      testcase.cmd.AutoClone,\n\t\t\t\tAPIClient:          testcase.cmd.Globals.APIClient,\n\t\t\t\tManifest:           testcase.cmd.Manifest,\n\t\t\t\tOut:                out,\n\t\t\t\tServiceVersionFlag: testcase.cmd.ServiceVersion,\n\t\t\t\tVerboseMode:        verboseMode,\n\t\t\t})\n\n\t\t\tswitch {\n\t\t\tcase err != nil && testcase.wantError == \"\":\n\t\t\t\tt.Fatalf(\"unexpected error getting service details: %v\", err)\n\t\t\t\treturn\n\t\t\tcase err != nil && testcase.wantError != \"\":\n\t\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\t\treturn\n\t\t\tcase err == nil && testcase.wantError != \"\":\n\t\t\t\tt.Fatalf(\"expected error, have nil (service details: %s, %d)\", serviceID, serviceVersion.Number)\n\t\t\tcase err == nil && testcase.wantError == \"\":\n\t\t\t\thave, err := testcase.cmd.ConstructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\t\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\t\ttestutil.AssertEqual(t, testcase.want, have)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestUpdatePapertrailInput(t *testing.T) {\n\tscenarios := []struct {\n\t\tname      string\n\t\tcmd       *papertrail.UpdateCommand\n\t\tapi       mock.API\n\t\twant      *fastly.UpdatePapertrailInput\n\t\twantError string\n\t}{\n\t\t{\n\t\t\tname: \"no updates\",\n\t\t\tcmd:  updateCommandNoUpdates(),\n\t\t\tapi: mock.API{\n\t\t\t\tGetVersionFn:    testutil.GetVersion,\n\t\t\t\tCloneVersionFn:  testutil.CloneVersionResult(4),\n\t\t\t\tGetPapertrailFn: getPapertrailOK,\n\t\t\t},\n\t\t\twant: &fastly.UpdatePapertrailInput{\n\t\t\t\tServiceID:      \"123\",\n\t\t\t\tServiceVersion: 4,\n\t\t\t\tName:           \"log\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"all values set flag serviceID\",\n\t\t\tcmd:  updateCommandAll(),\n\t\t\tapi: mock.API{\n\t\t\t\tGetVersionFn:    testutil.GetVersion,\n\t\t\t\tCloneVersionFn:  testutil.CloneVersionResult(4),\n\t\t\t\tGetPapertrailFn: getPapertrailOK,\n\t\t\t},\n\t\t\twant: &fastly.UpdatePapertrailInput{\n\t\t\t\tServiceID:         \"123\",\n\t\t\t\tServiceVersion:    4,\n\t\t\t\tName:              \"log\",\n\t\t\t\tNewName:           fastly.ToPointer(\"new1\"),\n\t\t\t\tAddress:           fastly.ToPointer(\"new2\"),\n\t\t\t\tPort:              fastly.ToPointer(23),\n\t\t\t\tFormat:            fastly.ToPointer(\"new3\"),\n\t\t\t\tFormatVersion:     fastly.ToPointer(3),\n\t\t\t\tResponseCondition: fastly.ToPointer(\"new4\"),\n\t\t\t\tPlacement:         fastly.ToPointer(\"new5\"),\n\t\t\t\tProcessingRegion:  fastly.ToPointer(\"eu\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:      \"error missing serviceID\",\n\t\t\tcmd:       updateCommandMissingServiceID(),\n\t\t\twant:      nil,\n\t\t\twantError: errors.ErrNoServiceID.Error(),\n\t\t},\n\t}\n\tfor testcaseIdx := range scenarios {\n\t\ttestcase := &scenarios[testcaseIdx]\n\t\tt.Run(testcase.name, func(t *testing.T) {\n\t\t\tif testcase.wantError == errors.ErrNoServiceID.Error() {\n\t\t\t\tt.Setenv(\"FASTLY_SERVICE_ID\", \"\")\n\t\t\t}\n\t\t\ttestcase.cmd.Globals.APIClient = testcase.api\n\n\t\t\tvar bs []byte\n\t\t\tout := bytes.NewBuffer(bs)\n\t\t\tverboseMode := true\n\n\t\t\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\t\t\tAutoCloneFlag:      testcase.cmd.AutoClone,\n\t\t\t\tAPIClient:          testcase.api,\n\t\t\t\tManifest:           testcase.cmd.Manifest,\n\t\t\t\tOut:                out,\n\t\t\t\tServiceVersionFlag: testcase.cmd.ServiceVersion,\n\t\t\t\tVerboseMode:        verboseMode,\n\t\t\t})\n\n\t\t\tswitch {\n\t\t\tcase err != nil && testcase.wantError == \"\":\n\t\t\t\tt.Fatalf(\"unexpected error getting service details: %v\", err)\n\t\t\t\treturn\n\t\t\tcase err != nil && testcase.wantError != \"\":\n\t\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\t\treturn\n\t\t\tcase err == nil && testcase.wantError != \"\":\n\t\t\t\tt.Fatalf(\"expected error, have nil (service details: %s, %d)\", serviceID, serviceVersion.Number)\n\t\t\tcase err == nil && testcase.wantError == \"\":\n\t\t\t\thave, err := testcase.cmd.ConstructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\t\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\t\ttestutil.AssertEqual(t, testcase.want, have)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc createCommandRequired() *papertrail.CreateCommand {\n\tvar b bytes.Buffer\n\n\tg := global.Data{\n\t\tConfig: config.File{},\n\t\tEnv:    config.Environment{},\n\t\tOutput: &b,\n\t}\n\tg.APIClient, _ = mock.APIClient(mock.API{\n\t\tGetVersionFn:   testutil.GetVersion,\n\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t})(\"token\", \"endpoint\", false)\n\n\treturn &papertrail.CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: &g,\n\t\t},\n\t\tManifest: manifest.Data{\n\t\t\tFlag: manifest.Flag{\n\t\t\t\tServiceID: \"123\",\n\t\t\t},\n\t\t},\n\t\tEndpointName: argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"log\"},\n\t\tAddress:      argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"example.com\"},\n\t\tServiceVersion: argparser.OptionalServiceVersion{\n\t\t\tOptionalString: argparser.OptionalString{Value: \"1\"},\n\t\t},\n\t\tAutoClone: argparser.OptionalAutoClone{\n\t\t\tOptionalBool: argparser.OptionalBool{\n\t\t\t\tOptional: argparser.Optional{\n\t\t\t\t\tWasSet: true,\n\t\t\t\t},\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t},\n\t}\n}\n\nfunc createCommandAll() *papertrail.CreateCommand {\n\tvar b bytes.Buffer\n\n\tg := global.Data{\n\t\tConfig: config.File{},\n\t\tEnv:    config.Environment{},\n\t\tOutput: &b,\n\t}\n\tg.APIClient, _ = mock.APIClient(mock.API{\n\t\tGetVersionFn:   testutil.GetVersion,\n\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t})(\"token\", \"endpoint\", false)\n\n\treturn &papertrail.CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: &g,\n\t\t},\n\t\tManifest: manifest.Data{\n\t\t\tFlag: manifest.Flag{\n\t\t\t\tServiceID: \"123\",\n\t\t\t},\n\t\t},\n\t\tServiceVersion: argparser.OptionalServiceVersion{\n\t\t\tOptionalString: argparser.OptionalString{Value: \"1\"},\n\t\t},\n\t\tAutoClone: argparser.OptionalAutoClone{\n\t\t\tOptionalBool: argparser.OptionalBool{\n\t\t\t\tOptional: argparser.Optional{\n\t\t\t\t\tWasSet: true,\n\t\t\t\t},\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t},\n\t\tEndpointName:      argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"log\"},\n\t\tAddress:           argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"example.com\"},\n\t\tFormat:            argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: `%h %l %u %t \"%r\" %>s %b`},\n\t\tFormatVersion:     argparser.OptionalInt{Optional: argparser.Optional{WasSet: true}, Value: 2},\n\t\tResponseCondition: argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"Prevent default logging\"},\n\t\tPlacement:         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"none\"},\n\t\tPort:              argparser.OptionalInt{Optional: argparser.Optional{WasSet: true}, Value: 22},\n\t\tProcessingRegion:  argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"eu\"},\n\t}\n}\n\nfunc createCommandMissingServiceID() *papertrail.CreateCommand {\n\tres := createCommandAll()\n\tres.Manifest = manifest.Data{}\n\tres.ServiceVersion = argparser.OptionalServiceVersion{}\n\treturn res\n}\n\nfunc updateCommandNoUpdates() *papertrail.UpdateCommand {\n\tvar b bytes.Buffer\n\n\tg := global.Data{\n\t\tConfig: config.File{},\n\t\tEnv:    config.Environment{},\n\t\tOutput: &b,\n\t}\n\n\treturn &papertrail.UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: &g,\n\t\t},\n\t\tManifest: manifest.Data{\n\t\t\tFlag: manifest.Flag{\n\t\t\t\tServiceID: \"123\",\n\t\t\t},\n\t\t},\n\t\tEndpointName: \"log\",\n\t\tServiceVersion: argparser.OptionalServiceVersion{\n\t\t\tOptionalString: argparser.OptionalString{Value: \"1\"},\n\t\t},\n\t\tAutoClone: argparser.OptionalAutoClone{\n\t\t\tOptionalBool: argparser.OptionalBool{\n\t\t\t\tOptional: argparser.Optional{\n\t\t\t\t\tWasSet: true,\n\t\t\t\t},\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t},\n\t}\n}\n\nfunc updateCommandAll() *papertrail.UpdateCommand {\n\tvar b bytes.Buffer\n\n\tg := global.Data{\n\t\tConfig: config.File{},\n\t\tEnv:    config.Environment{},\n\t\tOutput: &b,\n\t}\n\n\treturn &papertrail.UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: &g,\n\t\t},\n\t\tManifest: manifest.Data{\n\t\t\tFlag: manifest.Flag{\n\t\t\t\tServiceID: \"123\",\n\t\t\t},\n\t\t},\n\t\tEndpointName: \"log\",\n\t\tServiceVersion: argparser.OptionalServiceVersion{\n\t\t\tOptionalString: argparser.OptionalString{Value: \"1\"},\n\t\t},\n\t\tAutoClone: argparser.OptionalAutoClone{\n\t\t\tOptionalBool: argparser.OptionalBool{\n\t\t\t\tOptional: argparser.Optional{\n\t\t\t\t\tWasSet: true,\n\t\t\t\t},\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t},\n\t\tNewName:           argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new1\"},\n\t\tAddress:           argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new2\"},\n\t\tPort:              argparser.OptionalInt{Optional: argparser.Optional{WasSet: true}, Value: 23},\n\t\tFormat:            argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new3\"},\n\t\tFormatVersion:     argparser.OptionalInt{Optional: argparser.Optional{WasSet: true}, Value: 3},\n\t\tResponseCondition: argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new4\"},\n\t\tPlacement:         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new5\"},\n\t\tProcessingRegion:  argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"eu\"},\n\t}\n}\n\nfunc updateCommandMissingServiceID() *papertrail.UpdateCommand {\n\tres := updateCommandAll()\n\tres.Manifest = manifest.Data{}\n\tres.ServiceVersion = argparser.OptionalServiceVersion{}\n\treturn res\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/papertrail/root.go",
    "content": "package papertrail\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"papertrail\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manipulate Fastly service version Papertrail logging endpoints.\")\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/papertrail/update.go",
    "content": "package papertrail\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/service/logging/logflags\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/manifest\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// UpdateCommand calls the Fastly API to update a Papertrail logging endpoint.\ntype UpdateCommand struct {\n\targparser.Base\n\tManifest manifest.Data\n\n\t// Required.\n\tEndpointName   string\n\tServiceName    argparser.OptionalServiceNameID\n\tServiceVersion argparser.OptionalServiceVersion\n\n\t// Optional.\n\tAddress           argparser.OptionalString\n\tAutoClone         argparser.OptionalAutoClone\n\tFormat            argparser.OptionalString\n\tFormatVersion     argparser.OptionalInt\n\tNewName           argparser.OptionalString\n\tPlacement         argparser.OptionalString\n\tPort              argparser.OptionalInt\n\tProcessingRegion  argparser.OptionalString\n\tResponseCondition argparser.OptionalString\n}\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"update\", \"Update a Papertrail logging endpoint on a Fastly service version\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"The name of the Papertrail logging object\").Short('n').Required().StringVar(&c.EndpointName)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.ServiceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.AutoClone.Set,\n\t\tDst:    &c.AutoClone.Value,\n\t})\n\tc.CmdClause.Flag(\"address\", \"A hostname or IPv4 address\").Action(c.Address.Set).StringVar(&c.Address.Value)\n\tlogflags.Format(c.CmdClause, &c.Format)\n\tlogflags.FormatVersion(c.CmdClause, &c.FormatVersion)\n\tc.CmdClause.Flag(\"new-name\", \"New name of the Papertrail logging object\").Action(c.NewName.Set).StringVar(&c.NewName.Value)\n\tlogflags.Placement(c.CmdClause, &c.Placement)\n\tc.CmdClause.Flag(\"port\", \"The port number\").Action(c.Port.Set).IntVar(&c.Port.Value)\n\tlogflags.ProcessingRegion(c.CmdClause, &c.ProcessingRegion, \"Papertrail\")\n\tlogflags.ResponseCondition(c.CmdClause, &c.ResponseCondition)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.ServiceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.ServiceName.Value,\n\t})\n\treturn &c\n}\n\n// ConstructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *UpdateCommand) ConstructInput(serviceID string, serviceVersion int) (*fastly.UpdatePapertrailInput, error) {\n\tinput := fastly.UpdatePapertrailInput{\n\t\tServiceID:      serviceID,\n\t\tServiceVersion: serviceVersion,\n\t\tName:           c.EndpointName,\n\t}\n\n\t// Set new values if set by user.\n\tif c.NewName.WasSet {\n\t\tinput.NewName = &c.NewName.Value\n\t}\n\n\tif c.NewName.WasSet {\n\t\tinput.NewName = &c.NewName.Value\n\t}\n\n\tif c.Address.WasSet {\n\t\tinput.Address = &c.Address.Value\n\t}\n\n\tif c.Port.WasSet {\n\t\tinput.Port = &c.Port.Value\n\t}\n\n\tif c.FormatVersion.WasSet {\n\t\tinput.FormatVersion = &c.FormatVersion.Value\n\t}\n\n\tif c.Format.WasSet {\n\t\tinput.Format = fastly.ToPointer(argparser.Content(c.Format.Value))\n\t}\n\n\tif c.ResponseCondition.WasSet {\n\t\tinput.ResponseCondition = &c.ResponseCondition.Value\n\t}\n\n\tif c.Placement.WasSet {\n\t\tinput.Placement = &c.Placement.Value\n\t}\n\n\tif c.ProcessingRegion.WasSet {\n\t\tinput.ProcessingRegion = &c.ProcessingRegion.Value\n\t}\n\n\treturn &input, nil\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *UpdateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(false),\n\t\tLocked:             optional.Of(false),\n\t\tAutoCloneFlag:      c.AutoClone,\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.ServiceName,\n\t\tServiceVersionFlag: c.ServiceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": errors.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tinput, err := c.ConstructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tpapertrail, err := c.Globals.APIClient.UpdatePapertrail(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\ttext.Success(\n\t\tout,\n\t\t\"Updated Papertrail logging endpoint %s (service %s version %d)\",\n\t\tfastly.ToValue(papertrail.Name),\n\t\tfastly.ToValue(papertrail.ServiceID),\n\t\tfastly.ToValue(papertrail.ServiceVersion),\n\t)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/root.go",
    "content": "package logging\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"logging\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manipulate Fastly service version logging endpoints\")\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/s3/create.go",
    "content": "package s3\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/service/logging/logflags\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/manifest\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// CreateCommand calls the Fastly API to create an Amazon S3 logging endpoint.\ntype CreateCommand struct {\n\targparser.Base\n\tManifest manifest.Data\n\n\t// Required.\n\tServiceName    argparser.OptionalServiceNameID\n\tServiceVersion argparser.OptionalServiceVersion\n\n\t// mutual exclusions\n\t// AccessKey + SecretKey or IAMRole must be provided\n\tAccessKey argparser.OptionalString\n\tSecretKey argparser.OptionalString\n\tIAMRole   argparser.OptionalString\n\n\t// Optional.\n\tAutoClone                    argparser.OptionalAutoClone\n\tBucketName                   argparser.OptionalString\n\tCompressionCodec             argparser.OptionalString\n\tDomain                       argparser.OptionalString\n\tEndpointName                 argparser.OptionalString // Can't shadow argparser.Base method Name().\n\tFileMaxBytes                 argparser.OptionalInt\n\tFormat                       argparser.OptionalString\n\tFormatVersion                argparser.OptionalInt\n\tGzipLevel                    argparser.OptionalInt\n\tMessageType                  argparser.OptionalString\n\tPath                         argparser.OptionalString\n\tPeriod                       argparser.OptionalInt\n\tPlacement                    argparser.OptionalString\n\tProcessingRegion             argparser.OptionalString\n\tPublicKey                    argparser.OptionalString\n\tRedundancy                   argparser.OptionalString\n\tResponseCondition            argparser.OptionalString\n\tServerSideEncryption         argparser.OptionalString\n\tServerSideEncryptionKMSKeyID argparser.OptionalString\n\tTimestampFormat              argparser.OptionalString\n}\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"create\", \"Create an Amazon S3 logging endpoint on a Fastly service version\").Alias(\"add\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"The name of the S3 logging object. Used as a primary key for API access\").Short('n').Action(c.EndpointName.Set).StringVar(&c.EndpointName.Value)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.ServiceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.AutoClone.Set,\n\t\tDst:    &c.AutoClone.Value,\n\t})\n\tc.CmdClause.Flag(\"access-key\", \"Your S3 account access key\").Action(c.AccessKey.Set).StringVar(&c.AccessKey.Value)\n\tc.CmdClause.Flag(\"bucket\", \"Your S3 bucket name\").Action(c.BucketName.Set).StringVar(&c.BucketName.Value)\n\tlogflags.CompressionCodec(c.CmdClause, &c.CompressionCodec)\n\tc.CmdClause.Flag(\"domain\", \"The domain of the S3 endpoint\").Action(c.Domain.Set).StringVar(&c.Domain.Value)\n\tc.CmdClause.Flag(\"file-max-bytes\", \"The maximum size of a log file in bytes\").Action(c.FileMaxBytes.Set).IntVar(&c.FileMaxBytes.Value)\n\tlogflags.Format(c.CmdClause, &c.Format)\n\tlogflags.FormatVersion(c.CmdClause, &c.FormatVersion)\n\tlogflags.GzipLevel(c.CmdClause, &c.GzipLevel)\n\tc.CmdClause.Flag(\"iam-role\", \"The IAM role ARN for logging\").Action(c.IAMRole.Set).StringVar(&c.IAMRole.Value)\n\tlogflags.MessageType(c.CmdClause, &c.MessageType)\n\tlogflags.Path(c.CmdClause, &c.Path)\n\tlogflags.Period(c.CmdClause, &c.Period)\n\tlogflags.Placement(c.CmdClause, &c.Placement)\n\tlogflags.ProcessingRegion(c.CmdClause, &c.ProcessingRegion, \"S3\")\n\tlogflags.PublicKey(c.CmdClause, &c.PublicKey)\n\tc.CmdClause.Flag(\"redundancy\", \"The S3 storage class. One of: standard, intelligent_tiering, standard_ia, onezone_ia, glacier, glacier_ir, deep_archive, or reduced_redundancy\").Action(c.Redundancy.Set).EnumVar(&c.Redundancy.Value, string(fastly.S3RedundancyStandard), string(fastly.S3RedundancyIntelligentTiering), string(fastly.S3RedundancyStandardIA), string(fastly.S3RedundancyOneZoneIA), string(fastly.S3RedundancyGlacierFlexibleRetrieval), string(fastly.S3RedundancyGlacierInstantRetrieval), string(fastly.S3RedundancyGlacierDeepArchive), string(fastly.S3RedundancyReduced))\n\tlogflags.ResponseCondition(c.CmdClause, &c.ResponseCondition)\n\tc.CmdClause.Flag(\"secret-key\", \"Your S3 account secret key\").Action(c.SecretKey.Set).StringVar(&c.SecretKey.Value)\n\tc.CmdClause.Flag(\"server-side-encryption\", \"Set to enable S3 Server Side Encryption. Can be either AES256 or aws:kms\").Action(c.ServerSideEncryption.Set).EnumVar(&c.ServerSideEncryption.Value, string(fastly.S3ServerSideEncryptionAES), string(fastly.S3ServerSideEncryptionKMS))\n\tc.CmdClause.Flag(\"server-side-encryption-kms-key-id\", \"Server-side KMS Key ID. Must be set if server-side-encryption is set to aws:kms\").Action(c.ServerSideEncryptionKMSKeyID.Set).StringVar(&c.ServerSideEncryptionKMSKeyID.Value)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.ServiceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.ServiceName.Value,\n\t})\n\tlogflags.TimestampFormat(c.CmdClause, &c.TimestampFormat)\n\treturn &c\n}\n\n// ConstructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *CreateCommand) ConstructInput(serviceID string, serviceVersion int) (*fastly.CreateS3Input, error) {\n\tvar input fastly.CreateS3Input\n\n\tinput.ServiceID = serviceID\n\tinput.ServiceVersion = serviceVersion\n\tif c.EndpointName.WasSet {\n\t\tinput.Name = &c.EndpointName.Value\n\t}\n\tif c.BucketName.WasSet {\n\t\tinput.BucketName = &c.BucketName.Value\n\t}\n\n\t// The following block checks for invalid permutations of the ways in\n\t// which the AccessKey + SecretKey and IAMRole flags can be\n\t// provided. This is necessary because either the AccessKey and\n\t// SecretKey or the IAMRole is required, but they are mutually\n\t// exclusive. The kingpin library lacks a way to express this constraint\n\t// via the flag specification API so we enforce it manually here.\n\tswitch {\n\tcase !c.AccessKey.WasSet && !c.SecretKey.WasSet && !c.IAMRole.WasSet:\n\t\treturn nil, fmt.Errorf(\"error parsing arguments: the --access-key and --secret-key flags or the --iam-role flag must be provided\")\n\tcase (c.AccessKey.WasSet || c.SecretKey.WasSet) && c.IAMRole.WasSet:\n\t\t// Enforce mutual exclusion\n\t\treturn nil, fmt.Errorf(\"error parsing arguments: the --access-key and --secret-key flags are mutually exclusive with the --iam-role flag\")\n\tcase c.AccessKey.WasSet && !c.SecretKey.WasSet:\n\t\treturn nil, fmt.Errorf(\"error parsing arguments: required flag --secret-key not provided\")\n\tcase !c.AccessKey.WasSet && c.SecretKey.WasSet:\n\t\treturn nil, fmt.Errorf(\"error parsing arguments: required flag --access-key not provided\")\n\t}\n\n\t// The following blocks enforces the mutual exclusivity of the\n\t// CompressionCodec and GzipLevel flags.\n\tif c.CompressionCodec.WasSet && c.GzipLevel.WasSet {\n\t\treturn nil, fmt.Errorf(\"error parsing arguments: the --compression-codec flag is mutually exclusive with the --gzip-level flag\")\n\t}\n\n\tif c.AccessKey.WasSet {\n\t\tinput.AccessKey = &c.AccessKey.Value\n\t}\n\n\tif c.SecretKey.WasSet {\n\t\tinput.SecretKey = &c.SecretKey.Value\n\t}\n\n\tif c.IAMRole.WasSet {\n\t\tinput.IAMRole = &c.IAMRole.Value\n\t}\n\n\tif c.Domain.WasSet {\n\t\tinput.Domain = &c.Domain.Value\n\t}\n\n\tif c.FileMaxBytes.WasSet {\n\t\tinput.FileMaxBytes = &c.FileMaxBytes.Value\n\t}\n\n\tif c.Path.WasSet {\n\t\tinput.Path = &c.Path.Value\n\t}\n\n\tif c.Period.WasSet {\n\t\tinput.Period = &c.Period.Value\n\t}\n\n\tif c.GzipLevel.WasSet {\n\t\tinput.GzipLevel = &c.GzipLevel.Value\n\t}\n\n\tif c.Format.WasSet {\n\t\tinput.Format = fastly.ToPointer(argparser.Content(c.Format.Value))\n\t}\n\n\tif c.FormatVersion.WasSet {\n\t\tinput.FormatVersion = &c.FormatVersion.Value\n\t}\n\n\tif c.MessageType.WasSet {\n\t\tinput.MessageType = &c.MessageType.Value\n\t}\n\n\tif c.ResponseCondition.WasSet {\n\t\tinput.ResponseCondition = &c.ResponseCondition.Value\n\t}\n\n\tif c.TimestampFormat.WasSet {\n\t\tinput.TimestampFormat = &c.TimestampFormat.Value\n\t}\n\n\tif c.Placement.WasSet {\n\t\tinput.Placement = &c.Placement.Value\n\t}\n\n\tif c.ProcessingRegion.WasSet {\n\t\tinput.ProcessingRegion = &c.ProcessingRegion.Value\n\t}\n\n\tif c.PublicKey.WasSet {\n\t\tinput.PublicKey = &c.PublicKey.Value\n\t}\n\n\tif c.ServerSideEncryptionKMSKeyID.WasSet {\n\t\tinput.ServerSideEncryptionKMSKeyID = &c.ServerSideEncryptionKMSKeyID.Value\n\t}\n\n\tif c.CompressionCodec.WasSet {\n\t\tinput.CompressionCodec = &c.CompressionCodec.Value\n\t}\n\n\tif c.Redundancy.WasSet {\n\t\tredundancy, err := ValidateRedundancy(c.Redundancy.Value)\n\t\tif err == nil {\n\t\t\tinput.Redundancy = &redundancy\n\t\t}\n\t}\n\n\tif c.ServerSideEncryption.WasSet {\n\t\tswitch c.ServerSideEncryption.Value {\n\t\tcase string(fastly.S3ServerSideEncryptionAES):\n\t\t\tsse := fastly.S3ServerSideEncryptionAES\n\t\t\tinput.ServerSideEncryption = &sse\n\t\tcase string(fastly.S3ServerSideEncryptionKMS):\n\t\t\tsse := fastly.S3ServerSideEncryptionKMS\n\t\t\tinput.ServerSideEncryption = &sse\n\t\t}\n\t}\n\n\treturn &input, nil\n}\n\n// ValidateRedundancy identifies the given redundancy type.\nfunc ValidateRedundancy(val string) (redundancy fastly.S3Redundancy, err error) {\n\tswitch val {\n\tcase string(fastly.S3RedundancyStandard):\n\t\tredundancy = fastly.S3RedundancyStandard\n\tcase string(fastly.S3RedundancyIntelligentTiering):\n\t\tredundancy = fastly.S3RedundancyIntelligentTiering\n\tcase string(fastly.S3RedundancyStandardIA):\n\t\tredundancy = fastly.S3RedundancyStandardIA\n\tcase string(fastly.S3RedundancyOneZoneIA):\n\t\tredundancy = fastly.S3RedundancyOneZoneIA\n\tcase string(fastly.S3RedundancyGlacierInstantRetrieval):\n\t\tredundancy = fastly.S3RedundancyGlacierInstantRetrieval\n\tcase string(fastly.S3RedundancyGlacierFlexibleRetrieval):\n\t\tredundancy = fastly.S3RedundancyGlacierFlexibleRetrieval\n\tcase string(fastly.S3RedundancyGlacierDeepArchive):\n\t\tredundancy = fastly.S3RedundancyGlacierDeepArchive\n\tcase string(fastly.S3RedundancyReduced):\n\t\tredundancy = fastly.S3RedundancyReduced\n\tdefault:\n\t\terr = fmt.Errorf(\"unknown redundancy: %s\", val)\n\t}\n\treturn redundancy, err\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *CreateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(false),\n\t\tLocked:             optional.Of(false),\n\t\tAutoCloneFlag:      c.AutoClone,\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.ServiceName,\n\t\tServiceVersionFlag: c.ServiceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": errors.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tinput, err := c.ConstructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\td, err := c.Globals.APIClient.CreateS3(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\ttext.Success(\n\t\tout,\n\t\t\"Created S3 logging endpoint %s (service %s version %d)\",\n\t\tfastly.ToValue(d.Name),\n\t\tfastly.ToValue(d.ServiceID),\n\t\tfastly.ToValue(d.ServiceVersion),\n\t)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/s3/delete.go",
    "content": "package s3\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DeleteCommand calls the Fastly API to delete an Amazon S3 logging endpoint.\ntype DeleteCommand struct {\n\targparser.Base\n\tInput          fastly.DeleteS3Input\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n\tautoClone      argparser.OptionalAutoClone\n}\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"delete\", \"Delete a S3 logging endpoint on a Fastly service version\").Alias(\"remove\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"The name of the S3 logging object\").Short('n').Required().StringVar(&c.Input.Name)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.autoClone.Set,\n\t\tDst:    &c.autoClone.Value,\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DeleteCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(false),\n\t\tLocked:             optional.Of(false),\n\t\tAutoCloneFlag:      c.autoClone,\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": errors.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tc.Input.ServiceID = serviceID\n\tc.Input.ServiceVersion = fastly.ToValue(serviceVersion.Number)\n\n\tif err := c.Globals.APIClient.DeleteS3(context.TODO(), &c.Input); err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Deleted S3 logging endpoint %s (service %s version %d)\", c.Input.Name, c.Input.ServiceID, c.Input.ServiceVersion)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/s3/describe.go",
    "content": "package s3\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DescribeCommand calls the Fastly API to describe an Amazon S3 logging endpoint.\ntype DescribeCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tInput          fastly.GetS3Input\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n}\n\n// NewDescribeCommand returns a usable command registered under the parent.\nfunc NewDescribeCommand(parent argparser.Registerer, g *global.Data) *DescribeCommand {\n\tc := DescribeCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"describe\", \"Show detailed information about a S3 logging endpoint on a Fastly service version\").Alias(\"get\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"The name of the S3 logging object\").Short('n').Required().StringVar(&c.Input.Name)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DescribeCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fsterr.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tc.Input.ServiceID = serviceID\n\tc.Input.ServiceVersion = fastly.ToValue(serviceVersion.Number)\n\n\to, err := c.Globals.APIClient.GetS3(context.TODO(), &c.Input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\tlines := text.Lines{\n\t\t\"Bucket\":                            fastly.ToValue(o.BucketName),\n\t\t\"Compression codec\":                 fastly.ToValue(o.CompressionCodec),\n\t\t\"File max bytes\":                    fastly.ToValue(o.FileMaxBytes),\n\t\t\"Format version\":                    fastly.ToValue(o.FormatVersion),\n\t\t\"Format\":                            fastly.ToValue(o.Format),\n\t\t\"GZip level\":                        fastly.ToValue(o.GzipLevel),\n\t\t\"Message type\":                      fastly.ToValue(o.MessageType),\n\t\t\"Name\":                              fastly.ToValue(o.Name),\n\t\t\"Path\":                              fastly.ToValue(o.Path),\n\t\t\"Period\":                            fastly.ToValue(o.Period),\n\t\t\"Placement\":                         fastly.ToValue(o.Placement),\n\t\t\"Processing region\":                 fastly.ToValue(o.ProcessingRegion),\n\t\t\"Public key\":                        fastly.ToValue(o.PublicKey),\n\t\t\"Redundancy\":                        fastly.ToValue(o.Redundancy),\n\t\t\"Response condition\":                fastly.ToValue(o.ResponseCondition),\n\t\t\"Server-side encryption KMS key ID\": fastly.ToValue(o.ServerSideEncryption),\n\t\t\"Server-side encryption\":            fastly.ToValue(o.ServerSideEncryption),\n\t\t\"Timestamp format\":                  fastly.ToValue(o.TimestampFormat),\n\t\t\"Version\":                           fastly.ToValue(o.ServiceVersion),\n\t}\n\tif o.AccessKey != nil || o.SecretKey != nil {\n\t\tlines[\"Access key\"] = fastly.ToValue(o.AccessKey)\n\t\tlines[\"Secret key\"] = fastly.ToValue(o.SecretKey)\n\t}\n\tif o.IAMRole != nil {\n\t\tlines[\"IAM role\"] = fastly.ToValue(o.IAMRole)\n\t}\n\tif !c.Globals.Verbose() {\n\t\tlines[\"Service ID\"] = fastly.ToValue(o.ServiceID)\n\t}\n\ttext.PrintLines(out, lines)\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/s3/doc.go",
    "content": "// Package s3 contains commands to inspect and manipulate Fastly service S3\n// logging endpoints.\npackage s3\n"
  },
  {
    "path": "pkg/commands/service/logging/s3/list.go",
    "content": "package s3\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// ListCommand calls the Fastly API to list Amazon S3 logging endpoints.\ntype ListCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tInput          fastly.ListS3sInput\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n}\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"list\", \"List S3 endpoints on a Fastly service version\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *ListCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fsterr.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tc.Input.ServiceID = serviceID\n\tc.Input.ServiceVersion = fastly.ToValue(serviceVersion.Number)\n\n\to, err := c.Globals.APIClient.ListS3s(context.TODO(), &c.Input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\tif !c.Globals.Verbose() {\n\t\ttw := text.NewTable(out)\n\t\ttw.AddHeader(\"SERVICE\", \"VERSION\", \"NAME\")\n\t\tfor _, s3 := range o {\n\t\t\ttw.AddLine(\n\t\t\t\tfastly.ToValue(s3.ServiceID),\n\t\t\t\tfastly.ToValue(s3.ServiceVersion),\n\t\t\t\tfastly.ToValue(s3.Name),\n\t\t\t)\n\t\t}\n\t\ttw.Print()\n\t\treturn nil\n\t}\n\n\tfmt.Fprintf(out, \"Version: %d\\n\", c.Input.ServiceVersion)\n\tfor i, s3 := range o {\n\t\tfmt.Fprintf(out, \"\\tS3 %d/%d\\n\", i+1, len(o))\n\t\tfmt.Fprintf(out, \"\\t\\tService ID: %s\\n\", fastly.ToValue(s3.ServiceID))\n\t\tfmt.Fprintf(out, \"\\t\\tVersion: %d\\n\", fastly.ToValue(s3.ServiceVersion))\n\t\tfmt.Fprintf(out, \"\\t\\tName: %s\\n\", fastly.ToValue(s3.Name))\n\t\tfmt.Fprintf(out, \"\\t\\tBucket: %s\\n\", fastly.ToValue(s3.BucketName))\n\t\tif s3.AccessKey != nil || s3.SecretKey != nil {\n\t\t\tfmt.Fprintf(out, \"\\t\\tAccess key: %s\\n\", fastly.ToValue(s3.AccessKey))\n\t\t\tfmt.Fprintf(out, \"\\t\\tSecret key: %s\\n\", fastly.ToValue(s3.SecretKey))\n\t\t}\n\t\tif s3.IAMRole != nil {\n\t\t\tfmt.Fprintf(out, \"\\t\\tIAM role: %s\\n\", fastly.ToValue(s3.IAMRole))\n\t\t}\n\t\tfmt.Fprintf(out, \"\\t\\tPath: %s\\n\", fastly.ToValue(s3.Path))\n\t\tfmt.Fprintf(out, \"\\t\\tPeriod: %d\\n\", fastly.ToValue(s3.Period))\n\t\tfmt.Fprintf(out, \"\\t\\tGZip level: %d\\n\", fastly.ToValue(s3.GzipLevel))\n\t\tfmt.Fprintf(out, \"\\t\\tFormat: %s\\n\", fastly.ToValue(s3.Format))\n\t\tfmt.Fprintf(out, \"\\t\\tFormat version: %d\\n\", fastly.ToValue(s3.FormatVersion))\n\t\tfmt.Fprintf(out, \"\\t\\tResponse condition: %s\\n\", fastly.ToValue(s3.ResponseCondition))\n\t\tfmt.Fprintf(out, \"\\t\\tMessage type: %s\\n\", fastly.ToValue(s3.MessageType))\n\t\tfmt.Fprintf(out, \"\\t\\tTimestamp format: %s\\n\", fastly.ToValue(s3.TimestampFormat))\n\t\tfmt.Fprintf(out, \"\\t\\tPlacement: %s\\n\", fastly.ToValue(s3.Placement))\n\t\tfmt.Fprintf(out, \"\\t\\tPublic key: %s\\n\", fastly.ToValue(s3.PublicKey))\n\t\tfmt.Fprintf(out, \"\\t\\tRedundancy: %s\\n\", fastly.ToValue(s3.Redundancy))\n\t\tfmt.Fprintf(out, \"\\t\\tServer-side encryption: %s\\n\", fastly.ToValue(s3.ServerSideEncryption))\n\t\tfmt.Fprintf(out, \"\\t\\tServer-side encryption KMS key ID: %s\\n\", fastly.ToValue(s3.ServerSideEncryption))\n\t\tfmt.Fprintf(out, \"\\t\\tFile max bytes: %d\\n\", fastly.ToValue(s3.FileMaxBytes))\n\t\tfmt.Fprintf(out, \"\\t\\tCompression codec: %s\\n\", fastly.ToValue(s3.CompressionCodec))\n\t\tfmt.Fprintf(out, \"\\t\\tProcessing region: %s\\n\", fastly.ToValue(s3.ProcessingRegion))\n\t}\n\tfmt.Fprintln(out)\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/s3/root.go",
    "content": "package s3\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"s3\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manipulate Fastly service version S3 logging endpoints\")\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/s3/s3_integration_test.go",
    "content": "package s3_test\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/mock\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n\n\troot \"github.com/fastly/cli/pkg/commands/service\"\n\tparent \"github.com/fastly/cli/pkg/commands/service/logging\"\n\tsub \"github.com/fastly/cli/pkg/commands/service/logging/s3\"\n)\n\nfunc TestS3Create(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name log --bucket log --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t},\n\t\t\tWantError: \"error parsing arguments: the --access-key and --secret-key flags or the --iam-role flag must be provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name log --bucket log --secret-key bar --iam-role arn:aws:iam::123456789012:role/S3Access --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t},\n\t\t\tWantError: \"error parsing arguments: the --access-key and --secret-key flags are mutually exclusive with the --iam-role flag\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name log --bucket log --access-key foo --iam-role arn:aws:iam::123456789012:role/S3Access --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t},\n\t\t\tWantError: \"error parsing arguments: the --access-key and --secret-key flags are mutually exclusive with the --iam-role flag\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name log --bucket log --access-key foo --secret-key bar --iam-role arn:aws:iam::123456789012:role/S3Access --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t},\n\t\t\tWantError: \"error parsing arguments: the --access-key and --secret-key flags are mutually exclusive with the --iam-role flag\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name log --bucket log --access-key foo --secret-key bar --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tCreateS3Fn:     createS3OK,\n\t\t\t},\n\t\t\tWantOutput: \"Created S3 logging endpoint log (service 123 version 4)\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name log --bucket log --access-key foo --secret-key bar --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tCreateS3Fn:     createS3Error,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name log2 --bucket log --iam-role arn:aws:iam::123456789012:role/S3Access --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tCreateS3Fn:     createS3OK,\n\t\t\t},\n\t\t\tWantOutput: \"Created S3 logging endpoint log2 (service 123 version 4)\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name log2 --bucket log --iam-role arn:aws:iam::123456789012:role/S3Access --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tCreateS3Fn:     createS3Error,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name log --bucket log --iam-role arn:aws:iam::123456789012:role/S3Access --compression-codec zstd --gzip-level 9 --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t},\n\t\t\tWantError: \"error parsing arguments: the --compression-codec flag is mutually exclusive with the --gzip-level flag\",\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"create\"}, scenarios)\n}\n\nfunc TestS3List(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tListS3sFn:    listS3sOK,\n\t\t\t},\n\t\t\tWantOutput: listS3sShortOutput,\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --verbose\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tListS3sFn:    listS3sOK,\n\t\t\t},\n\t\t\tWantOutput: listS3sVerboseOutput,\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 -v\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tListS3sFn:    listS3sOK,\n\t\t\t},\n\t\t\tWantOutput: listS3sVerboseOutput,\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tListS3sFn:    listS3sError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"list\"}, scenarios)\n}\n\nfunc TestS3Describe(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"--service-id 123 --version 1\",\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tGetS3Fn:      getS3Error,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tGetS3Fn:      getS3OK,\n\t\t\t},\n\t\t\tWantOutput: describeS3Output,\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"describe\"}, scenarios)\n}\n\nfunc TestS3Update(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"--service-id 123 --version 1 --new-name log\",\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs --new-name log --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tUpdateS3Fn:     updateS3Error,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs --new-name log --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tUpdateS3Fn:     updateS3OK,\n\t\t\t},\n\t\t\tWantOutput: \"Updated S3 logging endpoint log (service 123 version 4)\",\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"update\"}, scenarios)\n}\n\nfunc TestS3Delete(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"--service-id 123 --version 1\",\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tDeleteS3Fn:     deleteS3Error,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tDeleteS3Fn:     deleteS3OK,\n\t\t\t},\n\t\t\tWantOutput: \"Deleted S3 logging endpoint logs (service 123 version 4)\",\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"delete\"}, scenarios)\n}\n\nvar errTest = errors.New(\"fixture error\")\n\nfunc createS3OK(_ context.Context, i *fastly.CreateS3Input) (*fastly.S3, error) {\n\treturn &fastly.S3{\n\t\tServiceID:        fastly.ToPointer(i.ServiceID),\n\t\tServiceVersion:   fastly.ToPointer(i.ServiceVersion),\n\t\tName:             i.Name,\n\t\tCompressionCodec: fastly.ToPointer(\"zstd\"),\n\t}, nil\n}\n\nfunc createS3Error(_ context.Context, _ *fastly.CreateS3Input) (*fastly.S3, error) {\n\treturn nil, errTest\n}\n\nfunc listS3sOK(_ context.Context, i *fastly.ListS3sInput) ([]*fastly.S3, error) {\n\treturn []*fastly.S3{\n\t\t{\n\t\t\tServiceID:                    fastly.ToPointer(i.ServiceID),\n\t\t\tServiceVersion:               fastly.ToPointer(i.ServiceVersion),\n\t\t\tName:                         fastly.ToPointer(\"logs\"),\n\t\t\tBucketName:                   fastly.ToPointer(\"my-logs\"),\n\t\t\tAccessKey:                    fastly.ToPointer(\"1234\"),\n\t\t\tSecretKey:                    fastly.ToPointer(\"-----BEGIN RSA PRIVATE KEY-----MIIEogIBAAKCA\"),\n\t\t\tIAMRole:                      fastly.ToPointer(\"xyz\"),\n\t\t\tDomain:                       fastly.ToPointer(\"https://s3.us-east-1.amazonaws.com\"),\n\t\t\tPath:                         fastly.ToPointer(\"logs/\"),\n\t\t\tPeriod:                       fastly.ToPointer(3600),\n\t\t\tFormat:                       fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\t\tFormatVersion:                fastly.ToPointer(2),\n\t\t\tMessageType:                  fastly.ToPointer(\"classic\"),\n\t\t\tResponseCondition:            fastly.ToPointer(\"Prevent default logging\"),\n\t\t\tTimestampFormat:              fastly.ToPointer(\"%Y-%m-%dT%H:%M:%S.000\"),\n\t\t\tRedundancy:                   fastly.ToPointer(fastly.S3RedundancyStandard),\n\t\t\tPlacement:                    fastly.ToPointer(\"none\"),\n\t\t\tPublicKey:                    fastly.ToPointer(pgpPublicKey()),\n\t\t\tServerSideEncryption:         fastly.ToPointer(fastly.S3ServerSideEncryptionKMS),\n\t\t\tServerSideEncryptionKMSKeyID: fastly.ToPointer(\"1234\"),\n\t\t\tCompressionCodec:             fastly.ToPointer(\"zstd\"),\n\t\t\tProcessingRegion:             fastly.ToPointer(\"us\"),\n\t\t},\n\t\t{\n\t\t\tServiceID:                    fastly.ToPointer(i.ServiceID),\n\t\t\tServiceVersion:               fastly.ToPointer(i.ServiceVersion),\n\t\t\tName:                         fastly.ToPointer(\"analytics\"),\n\t\t\tBucketName:                   fastly.ToPointer(\"analytics\"),\n\t\t\tAccessKey:                    fastly.ToPointer(\"1234\"),\n\t\t\tSecretKey:                    fastly.ToPointer(\"-----BEGIN RSA PRIVATE KEY-----MIIEogIBAAKCA\"),\n\t\t\tDomain:                       fastly.ToPointer(\"https://s3.us-east-2.amazonaws.com\"),\n\t\t\tPath:                         fastly.ToPointer(\"logs/\"),\n\t\t\tPeriod:                       fastly.ToPointer(86400),\n\t\t\tFormat:                       fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\t\tFormatVersion:                fastly.ToPointer(2),\n\t\t\tMessageType:                  fastly.ToPointer(\"classic\"),\n\t\t\tResponseCondition:            fastly.ToPointer(\"Prevent default logging\"),\n\t\t\tTimestampFormat:              fastly.ToPointer(\"%Y-%m-%dT%H:%M:%S.000\"),\n\t\t\tRedundancy:                   fastly.ToPointer(fastly.S3RedundancyStandard),\n\t\t\tPlacement:                    fastly.ToPointer(\"none\"),\n\t\t\tPublicKey:                    fastly.ToPointer(pgpPublicKey()),\n\t\t\tServerSideEncryption:         fastly.ToPointer(fastly.S3ServerSideEncryptionKMS),\n\t\t\tServerSideEncryptionKMSKeyID: fastly.ToPointer(\"1234\"),\n\t\t\tFileMaxBytes:                 fastly.ToPointer(12345),\n\t\t\tCompressionCodec:             fastly.ToPointer(\"zstd\"),\n\t\t\tProcessingRegion:             fastly.ToPointer(\"us\"),\n\t\t},\n\t}, nil\n}\n\nfunc listS3sError(_ context.Context, _ *fastly.ListS3sInput) ([]*fastly.S3, error) {\n\treturn nil, errTest\n}\n\nvar listS3sShortOutput = strings.TrimSpace(`\nSERVICE  VERSION  NAME\n123      1        logs\n123      1        analytics\n`) + \"\\n\"\n\nvar listS3sVerboseOutput = strings.TrimSpace(`\nFastly API endpoint: https://api.fastly.com\nFastly API token provided via config file (auth: user)\n\nService ID (via --service-id): 123\n\nVersion: 1\n\tS3 1/2\n\t\tService ID: 123\n\t\tVersion: 1\n\t\tName: logs\n\t\tBucket: my-logs\n\t\tAccess key: 1234\n\t\tSecret key: -----BEGIN RSA PRIVATE KEY-----MIIEogIBAAKCA\n\t\tIAM role: xyz\n\t\tPath: logs/\n\t\tPeriod: 3600\n\t\tGZip level: 0\n\t\tFormat: %h %l %u %t \"%r\" %>s %b\n\t\tFormat version: 2\n\t\tResponse condition: Prevent default logging\n\t\tMessage type: classic\n\t\tTimestamp format: %Y-%m-%dT%H:%M:%S.000\n\t\tPlacement: none\n\t\tPublic key: `+pgpPublicKey()+`\n\t\tRedundancy: standard\n\t\tServer-side encryption: aws:kms\n\t\tServer-side encryption KMS key ID: aws:kms\n\t\tFile max bytes: 0\n\t\tCompression codec: zstd\n\t\tProcessing region: us\n\tS3 2/2\n\t\tService ID: 123\n\t\tVersion: 1\n\t\tName: analytics\n\t\tBucket: analytics\n\t\tAccess key: 1234\n\t\tSecret key: -----BEGIN RSA PRIVATE KEY-----MIIEogIBAAKCA\n\t\tPath: logs/\n\t\tPeriod: 86400\n\t\tGZip level: 0\n\t\tFormat: %h %l %u %t \"%r\" %>s %b\n\t\tFormat version: 2\n\t\tResponse condition: Prevent default logging\n\t\tMessage type: classic\n\t\tTimestamp format: %Y-%m-%dT%H:%M:%S.000\n\t\tPlacement: none\n\t\tPublic key: `+pgpPublicKey()+`\n\t\tRedundancy: standard\n\t\tServer-side encryption: aws:kms\n\t\tServer-side encryption KMS key ID: aws:kms\n\t\tFile max bytes: 12345\n\t\tCompression codec: zstd\n\t\tProcessing region: us\n`) + \"\\n\\n\"\n\nfunc getS3OK(_ context.Context, i *fastly.GetS3Input) (*fastly.S3, error) {\n\treturn &fastly.S3{\n\t\tServiceID:                    fastly.ToPointer(i.ServiceID),\n\t\tServiceVersion:               fastly.ToPointer(i.ServiceVersion),\n\t\tName:                         fastly.ToPointer(\"logs\"),\n\t\tBucketName:                   fastly.ToPointer(\"my-logs\"),\n\t\tAccessKey:                    fastly.ToPointer(\"1234\"),\n\t\tSecretKey:                    fastly.ToPointer(\"-----BEGIN RSA PRIVATE KEY-----MIIEogIBAAKCA\"),\n\t\tDomain:                       fastly.ToPointer(\"https://s3.us-east-1.amazonaws.com\"),\n\t\tPath:                         fastly.ToPointer(\"logs/\"),\n\t\tPeriod:                       fastly.ToPointer(3600),\n\t\tFormat:                       fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\tFormatVersion:                fastly.ToPointer(2),\n\t\tMessageType:                  fastly.ToPointer(\"classic\"),\n\t\tResponseCondition:            fastly.ToPointer(\"Prevent default logging\"),\n\t\tTimestampFormat:              fastly.ToPointer(\"%Y-%m-%dT%H:%M:%S.000\"),\n\t\tRedundancy:                   fastly.ToPointer(fastly.S3RedundancyStandard),\n\t\tPlacement:                    fastly.ToPointer(\"none\"),\n\t\tPublicKey:                    fastly.ToPointer(pgpPublicKey()),\n\t\tServerSideEncryption:         fastly.ToPointer(fastly.S3ServerSideEncryptionKMS),\n\t\tServerSideEncryptionKMSKeyID: fastly.ToPointer(\"1234\"),\n\t\tCompressionCodec:             fastly.ToPointer(\"zstd\"),\n\t\tProcessingRegion:             fastly.ToPointer(\"us\"),\n\t}, nil\n}\n\nfunc getS3Error(_ context.Context, _ *fastly.GetS3Input) (*fastly.S3, error) {\n\treturn nil, errTest\n}\n\nvar describeS3Output = \"\\n\" + strings.TrimSpace(`\nAccess key: 1234\nBucket: my-logs\nCompression codec: zstd\nFile max bytes: 0\nFormat: %h %l %u %t \"%r\" %>s %b\nFormat version: 2\nGZip level: 0\nMessage type: classic\nName: logs\nPath: logs/\nPeriod: 3600\nPlacement: none\nProcessing region: us\nPublic key: `+pgpPublicKey()+`\nRedundancy: standard\nResponse condition: Prevent default logging\nSecret key: -----BEGIN RSA PRIVATE KEY-----MIIEogIBAAKCA\nServer-side encryption: aws:kms\nServer-side encryption KMS key ID: aws:kms\nService ID: 123\nTimestamp format: %Y-%m-%dT%H:%M:%S.000\nVersion: 1\n`) + \"\\n\"\n\nfunc updateS3OK(_ context.Context, i *fastly.UpdateS3Input) (*fastly.S3, error) {\n\treturn &fastly.S3{\n\t\tServiceID:                    fastly.ToPointer(i.ServiceID),\n\t\tServiceVersion:               fastly.ToPointer(i.ServiceVersion),\n\t\tName:                         fastly.ToPointer(\"log\"),\n\t\tBucketName:                   fastly.ToPointer(\"my-logs\"),\n\t\tAccessKey:                    fastly.ToPointer(\"1234\"),\n\t\tSecretKey:                    fastly.ToPointer(\"-----BEGIN RSA PRIVATE KEY-----MIIEogIBAAKCA\"),\n\t\tDomain:                       fastly.ToPointer(\"https://s3.us-east-1.amazonaws.com\"),\n\t\tPath:                         fastly.ToPointer(\"logs/\"),\n\t\tPeriod:                       fastly.ToPointer(3600),\n\t\tFormat:                       fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\tFormatVersion:                fastly.ToPointer(2),\n\t\tMessageType:                  fastly.ToPointer(\"classic\"),\n\t\tResponseCondition:            fastly.ToPointer(\"Prevent default logging\"),\n\t\tTimestampFormat:              fastly.ToPointer(\"%Y-%m-%dT%H:%M:%S.000\"),\n\t\tRedundancy:                   fastly.ToPointer(fastly.S3RedundancyStandard),\n\t\tPlacement:                    fastly.ToPointer(\"none\"),\n\t\tPublicKey:                    fastly.ToPointer(pgpPublicKey()),\n\t\tServerSideEncryption:         fastly.ToPointer(fastly.S3ServerSideEncryptionKMS),\n\t\tServerSideEncryptionKMSKeyID: fastly.ToPointer(\"1234\"),\n\t\tCompressionCodec:             fastly.ToPointer(\"zstd\"),\n\t}, nil\n}\n\nfunc updateS3Error(_ context.Context, _ *fastly.UpdateS3Input) (*fastly.S3, error) {\n\treturn nil, errTest\n}\n\nfunc deleteS3OK(_ context.Context, _ *fastly.DeleteS3Input) error {\n\treturn nil\n}\n\nfunc deleteS3Error(_ context.Context, _ *fastly.DeleteS3Input) error {\n\treturn errTest\n}\n\n// pgpPublicKey returns a PEM encoded PGP public key suitable for testing.\nfunc pgpPublicKey() string {\n\treturn strings.TrimSpace(`-----BEGIN PGP PUBLIC KEY BLOCK-----\nmQENBFyUD8sBCACyFnB39AuuTygseek+eA4fo0cgwva6/FSjnWq7riouQee8GgQ/\nibXTRyv4iVlwI12GswvMTIy7zNvs1R54i0qvsLr+IZ4GVGJqs6ZJnvQcqe3xPoR4\n8AnBfw90o32r/LuHf6QCJXi+AEu35koNlNAvLJ2B+KACaNB7N0EeWmqpV/1V2k9p\nlDYk+th7LcCuaFNGqKS/PrMnnMqR6VDLCjHhNx4KR79b0Twm/2qp6an3hyNRu8Gn\ndwxpf1/BUu3JWf+LqkN4Y3mbOmSUL3MaJNvyQguUzTfS0P0uGuBDHrJCVkMZCzDB\n89ag55jCPHyGeHBTd02gHMWzsg3WMBWvCsrzABEBAAG0JXRlcnJhZm9ybSAodGVz\ndCkgPHRlc3RAdGVycmFmb3JtLmNvbT6JAU4EEwEIADgWIQSHYyc6Kj9l6HzQsau6\nvFFc9jxV/wUCXJQPywIbAwULCQgHAgYVCgkICwIEFgIDAQIeAQIXgAAKCRC6vFFc\n9jxV/815CAClb32OxV7wG01yF97TzlyTl8TnvjMtoG29Mw4nSyg+mjM3b8N7iXm9\nOLX59fbDAWtBSldSZE22RXd3CvlFOG/EnKBXSjBtEqfyxYSnyOPkMPBYWGL/ApkX\nSvPYJ4LKdvipYToKFh3y9kk2gk1DcDBDyaaHvR+3rv1u3aoy7/s2EltAfDS3ZQIq\n7/cWTLJml/lleeB/Y6rPj8xqeCYhE5ahw9gsV/Mdqatl24V9Tks30iijx0Hhw+Gx\nkATUikMGr2GDVqoIRga5kXI7CzYff4rkc0Twn47fMHHHe/KY9M2yVnMHUXmAZwbG\nM1cMI/NH1DjevCKdGBLcRJlhuLPKF/anuQENBFyUD8sBCADIpd7r7GuPd6n/Ikxe\nu6h7umV6IIPoAm88xCYpTbSZiaK30Svh6Ywra9jfE2KlU9o6Y/art8ip0VJ3m07L\n4RSfSpnzqgSwdjSq5hNour2Fo/BzYhK7yaz2AzVSbe33R0+RYhb4b/6N+bKbjwGF\nftCsqVFMH+PyvYkLbvxyQrHlA9woAZaNThI1ztO5rGSnGUR8xt84eup28WIFKg0K\nUEGUcTzz+8QGAwAra+0ewPXo/AkO+8BvZjDidP417u6gpBHOJ9qYIcO9FxHeqFyu\nYrjlrxowEgXn5wO8xuNz6Vu1vhHGDHGDsRbZF8pv1d5O+0F1G7ttZ2GRRgVBZPwi\nkiyRABEBAAGJATYEGAEIACAWIQSHYyc6Kj9l6HzQsau6vFFc9jxV/wUCXJQPywIb\nDAAKCRC6vFFc9jxV/9YOCACe8qmOSnKQpQfW+PqYOqo3dt7JyweTs3FkD6NT8Zml\ndYy/vkstbTjPpX6aTvUZjkb46BVi7AOneVHpD5GBqvRsZ9iVgDYHaehmLCdKiG5L\n3Tp90NN+QY5WDbsGmsyk6+6ZMYejb4qYfweQeduOj27aavCJdLkCYMoRKfcFYI8c\nFaNmEfKKy/r1PO20NXEG6t9t05K/frHy6ZG8bCNYdpagfFVot47r9JaQqWlTNtIR\n5+zkkSq/eG9BEtRij3a6cTdQbktdBzx2KBeI0PYc1vlZR0LpuFKZqY9vlE6vTGLR\nwMfrTEOvx0NxUM3rpaCgEmuWbB1G1Hu371oyr4srrr+N\n=28dr\n-----END PGP PUBLIC KEY BLOCK-----\n`)\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/s3/s3_test.go",
    "content": "package s3_test\n\nimport (\n\t\"bytes\"\n\t\"testing\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/service/logging/s3\"\n\t\"github.com/fastly/cli/pkg/config\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/manifest\"\n\t\"github.com/fastly/cli/pkg/mock\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n)\n\nfunc TestCreateS3Input(t *testing.T) {\n\tred := fastly.S3RedundancyStandard\n\tsse := fastly.S3ServerSideEncryptionAES\n\tfor _, testcase := range []struct {\n\t\tname      string\n\t\tcmd       *s3.CreateCommand\n\t\twant      *fastly.CreateS3Input\n\t\twantError string\n\t}{\n\t\t{\n\t\t\tname: \"required values set flag serviceID using access credentials\",\n\t\t\tcmd:  createCommandRequired(),\n\t\t\twant: &fastly.CreateS3Input{\n\t\t\t\tServiceID:      \"123\",\n\t\t\t\tServiceVersion: 4,\n\t\t\t\tName:           fastly.ToPointer(\"log\"),\n\t\t\t\tBucketName:     fastly.ToPointer(\"bucket\"),\n\t\t\t\tAccessKey:      fastly.ToPointer(\"access\"),\n\t\t\t\tSecretKey:      fastly.ToPointer(\"secret\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"required values set flag serviceID using IAM role\",\n\t\t\tcmd:  createCommandRequiredIAMRole(),\n\t\t\twant: &fastly.CreateS3Input{\n\t\t\t\tServiceID:      \"123\",\n\t\t\t\tServiceVersion: 4,\n\t\t\t\tName:           fastly.ToPointer(\"log\"),\n\t\t\t\tBucketName:     fastly.ToPointer(\"bucket\"),\n\t\t\t\tIAMRole:        fastly.ToPointer(\"arn:aws:iam::123456789012:role/S3Access\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"all values set flag serviceID\",\n\t\t\tcmd:  createCommandAll(),\n\t\t\twant: &fastly.CreateS3Input{\n\t\t\t\tServiceID:                    \"123\",\n\t\t\t\tServiceVersion:               4,\n\t\t\t\tName:                         fastly.ToPointer(\"logs\"),\n\t\t\t\tBucketName:                   fastly.ToPointer(\"bucket\"),\n\t\t\t\tDomain:                       fastly.ToPointer(\"domain\"),\n\t\t\t\tAccessKey:                    fastly.ToPointer(\"access\"),\n\t\t\t\tSecretKey:                    fastly.ToPointer(\"secret\"),\n\t\t\t\tPath:                         fastly.ToPointer(\"path\"),\n\t\t\t\tPeriod:                       fastly.ToPointer(3600),\n\t\t\t\tFormat:                       fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\t\t\tMessageType:                  fastly.ToPointer(\"classic\"),\n\t\t\t\tFormatVersion:                fastly.ToPointer(2),\n\t\t\t\tResponseCondition:            fastly.ToPointer(\"Prevent default logging\"),\n\t\t\t\tTimestampFormat:              fastly.ToPointer(\"%Y-%m-%dT%H:%M:%S.000\"),\n\t\t\t\tRedundancy:                   &red,\n\t\t\t\tPlacement:                    fastly.ToPointer(\"none\"),\n\t\t\t\tPublicKey:                    fastly.ToPointer(pgpPublicKey()),\n\t\t\t\tServerSideEncryptionKMSKeyID: fastly.ToPointer(\"kmskey\"),\n\t\t\t\tServerSideEncryption:         &sse,\n\t\t\t\tCompressionCodec:             fastly.ToPointer(\"zstd\"),\n\t\t\t\tProcessingRegion:             fastly.ToPointer(\"eu\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:      \"error missing serviceID\",\n\t\t\tcmd:       createCommandMissingServiceID(),\n\t\t\twant:      nil,\n\t\t\twantError: errors.ErrNoServiceID.Error(),\n\t\t},\n\t} {\n\t\tt.Run(testcase.name, func(t *testing.T) {\n\t\t\tif testcase.wantError == errors.ErrNoServiceID.Error() {\n\t\t\t\tt.Setenv(\"FASTLY_SERVICE_ID\", \"\")\n\t\t\t}\n\t\t\tvar bs []byte\n\t\t\tout := bytes.NewBuffer(bs)\n\t\t\tverboseMode := true\n\n\t\t\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\t\t\tAutoCloneFlag:      testcase.cmd.AutoClone,\n\t\t\t\tAPIClient:          testcase.cmd.Globals.APIClient,\n\t\t\t\tManifest:           testcase.cmd.Manifest,\n\t\t\t\tOut:                out,\n\t\t\t\tServiceVersionFlag: testcase.cmd.ServiceVersion,\n\t\t\t\tVerboseMode:        verboseMode,\n\t\t\t})\n\n\t\t\tswitch {\n\t\t\tcase err != nil && testcase.wantError == \"\":\n\t\t\t\tt.Fatalf(\"unexpected error getting service details: %v\", err)\n\t\t\t\treturn\n\t\t\tcase err != nil && testcase.wantError != \"\":\n\t\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\t\treturn\n\t\t\tcase err == nil && testcase.wantError != \"\":\n\t\t\t\tt.Fatalf(\"expected error, have nil (service details: %s, %d)\", serviceID, serviceVersion.Number)\n\t\t\tcase err == nil && testcase.wantError == \"\":\n\t\t\t\thave, err := testcase.cmd.ConstructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\t\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\t\ttestutil.AssertEqual(t, testcase.want, have)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestUpdateS3Input(t *testing.T) {\n\tscenarios := []struct {\n\t\tname      string\n\t\tcmd       *s3.UpdateCommand\n\t\tapi       mock.API\n\t\twant      *fastly.UpdateS3Input\n\t\twantError string\n\t}{\n\t\t{\n\t\t\tname: \"no updates\",\n\t\t\tcmd:  updateCommandNoUpdates(),\n\t\t\tapi: mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tGetS3Fn:        getS3OK,\n\t\t\t},\n\t\t\twant: &fastly.UpdateS3Input{\n\t\t\t\tServiceID:      \"123\",\n\t\t\t\tServiceVersion: 4,\n\t\t\t\tName:           \"log\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"all values set flag serviceID\",\n\t\t\tcmd:  updateCommandAll(),\n\t\t\tapi: mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tGetS3Fn:        getS3OK,\n\t\t\t},\n\t\t\twant: &fastly.UpdateS3Input{\n\t\t\t\tServiceID:                    \"123\",\n\t\t\t\tServiceVersion:               4,\n\t\t\t\tName:                         \"log\",\n\t\t\t\tNewName:                      fastly.ToPointer(\"new1\"),\n\t\t\t\tBucketName:                   fastly.ToPointer(\"new2\"),\n\t\t\t\tAccessKey:                    fastly.ToPointer(\"new3\"),\n\t\t\t\tSecretKey:                    fastly.ToPointer(\"new4\"),\n\t\t\t\tIAMRole:                      fastly.ToPointer(\"\"),\n\t\t\t\tDomain:                       fastly.ToPointer(\"new5\"),\n\t\t\t\tPath:                         fastly.ToPointer(\"new6\"),\n\t\t\t\tPeriod:                       fastly.ToPointer(3601),\n\t\t\t\tGzipLevel:                    fastly.ToPointer(0),\n\t\t\t\tFormat:                       fastly.ToPointer(\"new7\"),\n\t\t\t\tFormatVersion:                fastly.ToPointer(3),\n\t\t\t\tMessageType:                  fastly.ToPointer(\"new8\"),\n\t\t\t\tResponseCondition:            fastly.ToPointer(\"new9\"),\n\t\t\t\tTimestampFormat:              fastly.ToPointer(\"new10\"),\n\t\t\t\tPlacement:                    fastly.ToPointer(\"new11\"),\n\t\t\t\tRedundancy:                   fastly.ToPointer(fastly.S3RedundancyReduced),\n\t\t\t\tServerSideEncryption:         fastly.ToPointer(fastly.S3ServerSideEncryptionKMS),\n\t\t\t\tServerSideEncryptionKMSKeyID: fastly.ToPointer(\"new12\"),\n\t\t\t\tPublicKey:                    fastly.ToPointer(\"new13\"),\n\t\t\t\tCompressionCodec:             fastly.ToPointer(\"new14\"),\n\t\t\t\tProcessingRegion:             fastly.ToPointer(\"eu\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:      \"error missing serviceID\",\n\t\t\tcmd:       updateCommandMissingServiceID(),\n\t\t\twant:      nil,\n\t\t\twantError: errors.ErrNoServiceID.Error(),\n\t\t},\n\t}\n\tfor testcaseIdx := range scenarios {\n\t\ttestcase := &scenarios[testcaseIdx]\n\t\tt.Run(testcase.name, func(t *testing.T) {\n\t\t\tif testcase.wantError == errors.ErrNoServiceID.Error() {\n\t\t\t\tt.Setenv(\"FASTLY_SERVICE_ID\", \"\")\n\t\t\t}\n\t\t\ttestcase.cmd.Globals.APIClient = testcase.api\n\n\t\t\tvar bs []byte\n\t\t\tout := bytes.NewBuffer(bs)\n\t\t\tverboseMode := true\n\n\t\t\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\t\t\tAutoCloneFlag:      testcase.cmd.AutoClone,\n\t\t\t\tAPIClient:          testcase.api,\n\t\t\t\tManifest:           testcase.cmd.Manifest,\n\t\t\t\tOut:                out,\n\t\t\t\tServiceVersionFlag: testcase.cmd.ServiceVersion,\n\t\t\t\tVerboseMode:        verboseMode,\n\t\t\t})\n\n\t\t\tswitch {\n\t\t\tcase err != nil && testcase.wantError == \"\":\n\t\t\t\tt.Fatalf(\"unexpected error getting service details: %v\", err)\n\t\t\t\treturn\n\t\t\tcase err != nil && testcase.wantError != \"\":\n\t\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\t\treturn\n\t\t\tcase err == nil && testcase.wantError != \"\":\n\t\t\t\tt.Fatalf(\"expected error, have nil (service details: %s, %d)\", serviceID, serviceVersion.Number)\n\t\t\tcase err == nil && testcase.wantError == \"\":\n\t\t\t\thave, err := testcase.cmd.ConstructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\t\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\t\ttestutil.AssertEqual(t, testcase.want, have)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc createCommandRequired() *s3.CreateCommand {\n\tvar b bytes.Buffer\n\n\tg := global.Data{\n\t\tConfig: config.File{},\n\t\tEnv:    config.Environment{},\n\t\tOutput: &b,\n\t}\n\tg.APIClient, _ = mock.APIClient(mock.API{\n\t\tGetVersionFn:   testutil.GetVersion,\n\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t})(\"token\", \"endpoint\", false)\n\n\treturn &s3.CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: &g,\n\t\t},\n\t\tManifest: manifest.Data{\n\t\t\tFlag: manifest.Flag{\n\t\t\t\tServiceID: \"123\",\n\t\t\t},\n\t\t},\n\t\tServiceVersion: argparser.OptionalServiceVersion{\n\t\t\tOptionalString: argparser.OptionalString{Value: \"1\"},\n\t\t},\n\t\tAutoClone: argparser.OptionalAutoClone{\n\t\t\tOptionalBool: argparser.OptionalBool{\n\t\t\t\tOptional: argparser.Optional{\n\t\t\t\t\tWasSet: true,\n\t\t\t\t},\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t},\n\t\tEndpointName: argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"log\"},\n\t\tBucketName:   argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"bucket\"},\n\t\tAccessKey:    argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"access\"},\n\t\tSecretKey:    argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"secret\"},\n\t}\n}\n\nfunc createCommandRequiredIAMRole() *s3.CreateCommand {\n\tvar b bytes.Buffer\n\n\tg := global.Data{\n\t\tConfig: config.File{},\n\t\tEnv:    config.Environment{},\n\t\tOutput: &b,\n\t}\n\tg.APIClient, _ = mock.APIClient(mock.API{\n\t\tGetVersionFn:   testutil.GetVersion,\n\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t})(\"token\", \"endpoint\", false)\n\n\treturn &s3.CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: &g,\n\t\t},\n\t\tManifest: manifest.Data{\n\t\t\tFlag: manifest.Flag{\n\t\t\t\tServiceID: \"123\",\n\t\t\t},\n\t\t},\n\t\tServiceVersion: argparser.OptionalServiceVersion{\n\t\t\tOptionalString: argparser.OptionalString{Value: \"1\"},\n\t\t},\n\t\tAutoClone: argparser.OptionalAutoClone{\n\t\t\tOptionalBool: argparser.OptionalBool{\n\t\t\t\tOptional: argparser.Optional{\n\t\t\t\t\tWasSet: true,\n\t\t\t\t},\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t},\n\t\tEndpointName: argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"log\"},\n\t\tBucketName:   argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"bucket\"},\n\t\tIAMRole:      argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"arn:aws:iam::123456789012:role/S3Access\"},\n\t}\n}\n\nfunc createCommandAll() *s3.CreateCommand {\n\tvar b bytes.Buffer\n\n\tg := global.Data{\n\t\tConfig: config.File{},\n\t\tEnv:    config.Environment{},\n\t\tOutput: &b,\n\t}\n\tg.APIClient, _ = mock.APIClient(mock.API{\n\t\tGetVersionFn:   testutil.GetVersion,\n\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t})(\"token\", \"endpoint\", false)\n\n\treturn &s3.CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: &g,\n\t\t},\n\t\tManifest: manifest.Data{\n\t\t\tFlag: manifest.Flag{\n\t\t\t\tServiceID: \"123\",\n\t\t\t},\n\t\t},\n\t\tServiceVersion: argparser.OptionalServiceVersion{\n\t\t\tOptionalString: argparser.OptionalString{Value: \"1\"},\n\t\t},\n\t\tAutoClone: argparser.OptionalAutoClone{\n\t\t\tOptionalBool: argparser.OptionalBool{\n\t\t\t\tOptional: argparser.Optional{\n\t\t\t\t\tWasSet: true,\n\t\t\t\t},\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t},\n\t\tEndpointName:                 argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"logs\"},\n\t\tBucketName:                   argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"bucket\"},\n\t\tAccessKey:                    argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"access\"},\n\t\tSecretKey:                    argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"secret\"},\n\t\tDomain:                       argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"domain\"},\n\t\tPath:                         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"path\"},\n\t\tPeriod:                       argparser.OptionalInt{Optional: argparser.Optional{WasSet: true}, Value: 3600},\n\t\tFormat:                       argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: `%h %l %u %t \"%r\" %>s %b`},\n\t\tFormatVersion:                argparser.OptionalInt{Optional: argparser.Optional{WasSet: true}, Value: 2},\n\t\tMessageType:                  argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"classic\"},\n\t\tResponseCondition:            argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"Prevent default logging\"},\n\t\tTimestampFormat:              argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"%Y-%m-%dT%H:%M:%S.000\"},\n\t\tPlacement:                    argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"none\"},\n\t\tPublicKey:                    argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: pgpPublicKey()},\n\t\tRedundancy:                   argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: string(fastly.S3RedundancyStandard)},\n\t\tServerSideEncryption:         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: string(fastly.S3ServerSideEncryptionAES)},\n\t\tServerSideEncryptionKMSKeyID: argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"kmskey\"},\n\t\tCompressionCodec:             argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"zstd\"},\n\t\tProcessingRegion:             argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"eu\"},\n\t}\n}\n\nfunc createCommandMissingServiceID() *s3.CreateCommand {\n\tres := createCommandAll()\n\tres.Manifest = manifest.Data{}\n\tres.ServiceVersion = argparser.OptionalServiceVersion{}\n\treturn res\n}\n\nfunc updateCommandNoUpdates() *s3.UpdateCommand {\n\tvar b bytes.Buffer\n\n\tg := global.Data{\n\t\tConfig: config.File{},\n\t\tEnv:    config.Environment{},\n\t\tOutput: &b,\n\t}\n\n\treturn &s3.UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: &g,\n\t\t},\n\t\tManifest: manifest.Data{\n\t\t\tFlag: manifest.Flag{\n\t\t\t\tServiceID: \"123\",\n\t\t\t},\n\t\t},\n\t\tEndpointName: \"log\",\n\t\tServiceVersion: argparser.OptionalServiceVersion{\n\t\t\tOptionalString: argparser.OptionalString{Value: \"1\"},\n\t\t},\n\t\tAutoClone: argparser.OptionalAutoClone{\n\t\t\tOptionalBool: argparser.OptionalBool{\n\t\t\t\tOptional: argparser.Optional{\n\t\t\t\t\tWasSet: true,\n\t\t\t\t},\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t},\n\t}\n}\n\nfunc updateCommandAll() *s3.UpdateCommand {\n\tvar b bytes.Buffer\n\n\tg := global.Data{\n\t\tConfig: config.File{},\n\t\tEnv:    config.Environment{},\n\t\tOutput: &b,\n\t}\n\n\treturn &s3.UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: &g,\n\t\t},\n\t\tManifest: manifest.Data{\n\t\t\tFlag: manifest.Flag{\n\t\t\t\tServiceID: \"123\",\n\t\t\t},\n\t\t},\n\t\tEndpointName: \"log\",\n\t\tServiceVersion: argparser.OptionalServiceVersion{\n\t\t\tOptionalString: argparser.OptionalString{Value: \"1\"},\n\t\t},\n\t\tAutoClone: argparser.OptionalAutoClone{\n\t\t\tOptionalBool: argparser.OptionalBool{\n\t\t\t\tOptional: argparser.Optional{\n\t\t\t\t\tWasSet: true,\n\t\t\t\t},\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t},\n\t\tNewName:                      argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new1\"},\n\t\tBucketName:                   argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new2\"},\n\t\tAccessKey:                    argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new3\"},\n\t\tSecretKey:                    argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new4\"},\n\t\tIAMRole:                      argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"\"},\n\t\tDomain:                       argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new5\"},\n\t\tPath:                         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new6\"},\n\t\tPeriod:                       argparser.OptionalInt{Optional: argparser.Optional{WasSet: true}, Value: 3601},\n\t\tGzipLevel:                    argparser.OptionalInt{Optional: argparser.Optional{WasSet: true}, Value: 0},\n\t\tFormat:                       argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new7\"},\n\t\tFormatVersion:                argparser.OptionalInt{Optional: argparser.Optional{WasSet: true}, Value: 3},\n\t\tMessageType:                  argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new8\"},\n\t\tResponseCondition:            argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new9\"},\n\t\tTimestampFormat:              argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new10\"},\n\t\tPlacement:                    argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new11\"},\n\t\tRedundancy:                   argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: string(fastly.S3RedundancyReduced)},\n\t\tServerSideEncryption:         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: string(fastly.S3ServerSideEncryptionKMS)},\n\t\tServerSideEncryptionKMSKeyID: argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new12\"},\n\t\tPublicKey:                    argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new13\"},\n\t\tCompressionCodec:             argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new14\"},\n\t\tProcessingRegion:             argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"eu\"},\n\t}\n}\n\nfunc updateCommandMissingServiceID() *s3.UpdateCommand {\n\tres := updateCommandAll()\n\tres.Manifest = manifest.Data{}\n\tres.ServiceVersion = argparser.OptionalServiceVersion{}\n\treturn res\n}\n\nfunc TestValidateRedundancy(t *testing.T) {\n\tfor _, testcase := range []struct {\n\t\tvalue     string\n\t\twant      fastly.S3Redundancy\n\t\twantError string\n\t}{\n\t\t{value: \"standard\", want: fastly.S3RedundancyStandard},\n\t\t{value: \"standard_ia\", want: fastly.S3RedundancyStandardIA},\n\t\t{value: \"onezone_ia\", want: fastly.S3RedundancyOneZoneIA},\n\t\t{value: \"glacier\", want: fastly.S3RedundancyGlacierFlexibleRetrieval},\n\t\t{value: \"glacier_ir\", want: fastly.S3RedundancyGlacierInstantRetrieval},\n\t\t{value: \"deep_archive\", want: fastly.S3RedundancyGlacierDeepArchive},\n\t\t{value: \"reduced_redundancy\", want: fastly.S3RedundancyReduced},\n\t\t{value: \"bad_value\", wantError: \"unknown redundancy\"},\n\t} {\n\t\tt.Run(testcase.value, func(t *testing.T) {\n\t\t\thave, err := s3.ValidateRedundancy(testcase.value)\n\n\t\t\tswitch {\n\t\t\tcase err != nil && testcase.wantError == \"\":\n\t\t\t\tt.Fatalf(\"unexpected error ValidateRedundancy: %v\", err)\n\t\t\t\treturn\n\t\t\tcase err != nil && testcase.wantError != \"\":\n\t\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\t\treturn\n\t\t\tcase err == nil && testcase.wantError != \"\":\n\t\t\t\tt.Fatalf(\"expected error, have nil (redundancy: %s)\", testcase.value)\n\t\t\tcase err == nil && testcase.wantError == \"\":\n\t\t\t\ttestutil.AssertEqual(t, testcase.want, have)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/s3/update.go",
    "content": "package s3\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/service/logging/logflags\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/manifest\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// UpdateCommand calls the Fastly API to update an Amazon S3 logging endpoint.\ntype UpdateCommand struct {\n\targparser.Base\n\tManifest manifest.Data\n\n\t// Required.\n\tEndpointName   string // Can't shadow argparser.Base method Name().\n\tServiceName    argparser.OptionalServiceNameID\n\tServiceVersion argparser.OptionalServiceVersion\n\n\t// Optional.\n\tAccessKey                    argparser.OptionalString\n\tAddress                      argparser.OptionalString\n\tAutoClone                    argparser.OptionalAutoClone\n\tBucketName                   argparser.OptionalString\n\tCompressionCodec             argparser.OptionalString\n\tDomain                       argparser.OptionalString\n\tFileMaxBytes                 argparser.OptionalInt\n\tFormat                       argparser.OptionalString\n\tFormatVersion                argparser.OptionalInt\n\tGzipLevel                    argparser.OptionalInt\n\tIAMRole                      argparser.OptionalString\n\tMessageType                  argparser.OptionalString\n\tNewName                      argparser.OptionalString\n\tPath                         argparser.OptionalString\n\tPeriod                       argparser.OptionalInt\n\tPlacement                    argparser.OptionalString\n\tProcessingRegion             argparser.OptionalString\n\tPublicKey                    argparser.OptionalString\n\tRedundancy                   argparser.OptionalString\n\tResponseCondition            argparser.OptionalString\n\tSecretKey                    argparser.OptionalString\n\tServerSideEncryption         argparser.OptionalString\n\tServerSideEncryptionKMSKeyID argparser.OptionalString\n\tTimestampFormat              argparser.OptionalString\n}\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"update\", \"Update a S3 logging endpoint on a Fastly service version\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"The name of the S3 logging object\").Short('n').Required().StringVar(&c.EndpointName)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.ServiceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.AutoClone.Set,\n\t\tDst:    &c.AutoClone.Value,\n\t})\n\tc.CmdClause.Flag(\"access-key\", \"Your S3 account access key\").Action(c.AccessKey.Set).StringVar(&c.AccessKey.Value)\n\tc.CmdClause.Flag(\"bucket\", \"Your S3 bucket name\").Action(c.BucketName.Set).StringVar(&c.BucketName.Value)\n\tlogflags.CompressionCodec(c.CmdClause, &c.CompressionCodec)\n\tc.CmdClause.Flag(\"domain\", \"The domain of the S3 endpoint\").Action(c.Domain.Set).StringVar(&c.Domain.Value)\n\tc.CmdClause.Flag(\"file-max-bytes\", \"The maximum size of a log file in bytes\").Action(c.FileMaxBytes.Set).IntVar(&c.FileMaxBytes.Value)\n\tlogflags.Format(c.CmdClause, &c.Format)\n\tlogflags.FormatVersion(c.CmdClause, &c.FormatVersion)\n\tlogflags.GzipLevel(c.CmdClause, &c.GzipLevel)\n\tc.CmdClause.Flag(\"iam-role\", \"The IAM role ARN for logging\").Action(c.IAMRole.Set).StringVar(&c.IAMRole.Value)\n\tlogflags.MessageType(c.CmdClause, &c.MessageType)\n\tc.CmdClause.Flag(\"new-name\", \"New name of the S3 logging object\").Action(c.NewName.Set).StringVar(&c.NewName.Value)\n\tlogflags.Path(c.CmdClause, &c.Path)\n\tlogflags.Period(c.CmdClause, &c.Period)\n\tlogflags.Placement(c.CmdClause, &c.Placement)\n\tlogflags.ProcessingRegion(c.CmdClause, &c.ProcessingRegion, \"S3\")\n\tlogflags.PublicKey(c.CmdClause, &c.PublicKey)\n\tc.CmdClause.Flag(\"redundancy\", \"The S3 storage class. One of: standard, intelligent_tiering, standard_ia, onezone_ia, glacier, glacier_ir, deep_archive, or reduced_redundancy\").Action(c.Redundancy.Set).EnumVar(&c.Redundancy.Value, string(fastly.S3RedundancyStandard), string(fastly.S3RedundancyIntelligentTiering), string(fastly.S3RedundancyStandardIA), string(fastly.S3RedundancyOneZoneIA), string(fastly.S3RedundancyGlacierFlexibleRetrieval), string(fastly.S3RedundancyGlacierInstantRetrieval), string(fastly.S3RedundancyGlacierDeepArchive), string(fastly.S3RedundancyReduced))\n\tlogflags.ResponseCondition(c.CmdClause, &c.ResponseCondition)\n\tc.CmdClause.Flag(\"secret-key\", \"Your S3 account secret key\").Action(c.SecretKey.Set).StringVar(&c.SecretKey.Value)\n\tc.CmdClause.Flag(\"server-side-encryption\", \"Set to enable S3 Server Side Encryption. Can be either AES256 or aws:kms\").Action(c.ServerSideEncryption.Set).EnumVar(&c.ServerSideEncryption.Value, string(fastly.S3ServerSideEncryptionAES), string(fastly.S3ServerSideEncryptionKMS))\n\tc.CmdClause.Flag(\"server-side-encryption-kms-key-id\", \"Server-side KMS Key ID. Must be set if server-side-encryption is set to aws:kms\").Action(c.ServerSideEncryptionKMSKeyID.Set).StringVar(&c.ServerSideEncryptionKMSKeyID.Value)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.ServiceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.ServiceName.Value,\n\t})\n\tlogflags.TimestampFormat(c.CmdClause, &c.TimestampFormat)\n\treturn &c\n}\n\n// ConstructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *UpdateCommand) ConstructInput(serviceID string, serviceVersion int) (*fastly.UpdateS3Input, error) {\n\tinput := fastly.UpdateS3Input{\n\t\tServiceID:      serviceID,\n\t\tServiceVersion: serviceVersion,\n\t\tName:           c.EndpointName,\n\t}\n\n\tif c.NewName.WasSet {\n\t\tinput.NewName = &c.NewName.Value\n\t}\n\n\tif c.BucketName.WasSet {\n\t\tinput.BucketName = &c.BucketName.Value\n\t}\n\n\tif c.AccessKey.WasSet {\n\t\tinput.AccessKey = &c.AccessKey.Value\n\t}\n\n\tif c.SecretKey.WasSet {\n\t\tinput.SecretKey = &c.SecretKey.Value\n\t}\n\n\tif c.IAMRole.WasSet {\n\t\tinput.IAMRole = &c.IAMRole.Value\n\t}\n\n\tif c.Domain.WasSet {\n\t\tinput.Domain = &c.Domain.Value\n\t}\n\n\tif c.Path.WasSet {\n\t\tinput.Path = &c.Path.Value\n\t}\n\n\tif c.Period.WasSet {\n\t\tinput.Period = &c.Period.Value\n\t}\n\n\tif c.GzipLevel.WasSet {\n\t\tinput.GzipLevel = &c.GzipLevel.Value\n\t}\n\n\tif c.FileMaxBytes.WasSet {\n\t\tinput.FileMaxBytes = &c.FileMaxBytes.Value\n\t}\n\n\tif c.Format.WasSet {\n\t\tinput.Format = fastly.ToPointer(argparser.Content(c.Format.Value))\n\t}\n\n\tif c.FormatVersion.WasSet {\n\t\tinput.FormatVersion = &c.FormatVersion.Value\n\t}\n\n\tif c.MessageType.WasSet {\n\t\tinput.MessageType = &c.MessageType.Value\n\t}\n\n\tif c.ResponseCondition.WasSet {\n\t\tinput.ResponseCondition = &c.ResponseCondition.Value\n\t}\n\n\tif c.TimestampFormat.WasSet {\n\t\tinput.TimestampFormat = &c.TimestampFormat.Value\n\t}\n\n\tif c.Placement.WasSet {\n\t\tinput.Placement = &c.Placement.Value\n\t}\n\n\tif c.ProcessingRegion.WasSet {\n\t\tinput.ProcessingRegion = &c.ProcessingRegion.Value\n\t}\n\n\tif c.PublicKey.WasSet {\n\t\tinput.PublicKey = &c.PublicKey.Value\n\t}\n\n\tif c.ServerSideEncryptionKMSKeyID.WasSet {\n\t\tinput.ServerSideEncryptionKMSKeyID = &c.ServerSideEncryptionKMSKeyID.Value\n\t}\n\n\tif c.CompressionCodec.WasSet {\n\t\tinput.CompressionCodec = &c.CompressionCodec.Value\n\t}\n\n\tif c.Redundancy.WasSet {\n\t\tredundancy, err := ValidateRedundancy(c.Redundancy.Value)\n\t\tif err == nil {\n\t\t\tinput.Redundancy = fastly.ToPointer(redundancy)\n\t\t}\n\t}\n\n\tif c.ServerSideEncryption.WasSet {\n\t\tswitch c.ServerSideEncryption.Value {\n\t\tcase string(fastly.S3ServerSideEncryptionAES):\n\t\t\tinput.ServerSideEncryption = fastly.ToPointer(fastly.S3ServerSideEncryptionAES)\n\t\tcase string(fastly.S3ServerSideEncryptionKMS):\n\t\t\tinput.ServerSideEncryption = fastly.ToPointer(fastly.S3ServerSideEncryptionKMS)\n\t\t}\n\t}\n\n\treturn &input, nil\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *UpdateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(false),\n\t\tLocked:             optional.Of(false),\n\t\tAutoCloneFlag:      c.AutoClone,\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.ServiceName,\n\t\tServiceVersionFlag: c.ServiceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": errors.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tinput, err := c.ConstructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\ts3, err := c.Globals.APIClient.UpdateS3(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\ttext.Success(\n\t\tout,\n\t\t\"Updated S3 logging endpoint %s (service %s version %d)\",\n\t\tfastly.ToValue(s3.Name),\n\t\tfastly.ToValue(s3.ServiceID),\n\t\tfastly.ToValue(s3.ServiceVersion),\n\t)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/scalyr/create.go",
    "content": "package scalyr\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/service/logging/logflags\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/manifest\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// CreateCommand calls the Fastly API to create a Scalyr logging endpoint.\ntype CreateCommand struct {\n\targparser.Base\n\tManifest manifest.Data\n\n\t// Required.\n\tServiceName    argparser.OptionalServiceNameID\n\tEndpointName   argparser.OptionalString // Can't shadow argparser.Base method Name().\n\tToken          argparser.OptionalString\n\tServiceVersion argparser.OptionalServiceVersion\n\n\t// Optional.\n\tAutoClone         argparser.OptionalAutoClone\n\tFormat            argparser.OptionalString\n\tFormatVersion     argparser.OptionalInt\n\tPlacement         argparser.OptionalString\n\tProcessingRegion  argparser.OptionalString\n\tRegion            argparser.OptionalString\n\tResponseCondition argparser.OptionalString\n\tProjectID         argparser.OptionalString\n}\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"create\", \"Create a Scalyr logging endpoint on a Fastly service version\").Alias(\"add\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"The name of the Scalyr logging object. Used as a primary key for API access\").Short('n').Action(c.EndpointName.Set).StringVar(&c.EndpointName.Value)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.ServiceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.CmdClause.Flag(\"auth-token\", \"The token to use for authentication (https://www.scalyr.com/keys)\").Action(c.Token.Set).StringVar(&c.Token.Value)\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.AutoClone.Set,\n\t\tDst:    &c.AutoClone.Value,\n\t})\n\tlogflags.Format(c.CmdClause, &c.Format)\n\tlogflags.FormatVersion(c.CmdClause, &c.FormatVersion)\n\tlogflags.Placement(c.CmdClause, &c.Placement)\n\tlogflags.ProcessingRegion(c.CmdClause, &c.ProcessingRegion, \"Scalyr\")\n\tc.CmdClause.Flag(\"project-id\", \"The name of the logfile field sent to Scalyr\").Action(c.ProjectID.Set).StringVar(&c.ProjectID.Value)\n\tc.CmdClause.Flag(\"region\", \"The region where logs are received and stored by Scalyr. Either US or EU. Defaults to US if undefined\").Action(c.Region.Set).StringVar(&c.Region.Value)\n\tlogflags.ResponseCondition(c.CmdClause, &c.ResponseCondition)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.ServiceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.ServiceName.Value,\n\t})\n\treturn &c\n}\n\n// ConstructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *CreateCommand) ConstructInput(serviceID string, serviceVersion int) (*fastly.CreateScalyrInput, error) {\n\tvar input fastly.CreateScalyrInput\n\n\tinput.ServiceID = serviceID\n\tinput.ServiceVersion = serviceVersion\n\tif c.EndpointName.WasSet {\n\t\tinput.Name = &c.EndpointName.Value\n\t}\n\tif c.Token.WasSet {\n\t\tinput.Token = &c.Token.Value\n\t}\n\tif c.Region.WasSet {\n\t\tinput.Region = &c.Region.Value\n\t}\n\tif c.Format.WasSet {\n\t\tinput.Format = fastly.ToPointer(argparser.Content(c.Format.Value))\n\t}\n\tif c.FormatVersion.WasSet {\n\t\tinput.FormatVersion = &c.FormatVersion.Value\n\t}\n\tif c.ResponseCondition.WasSet {\n\t\tinput.ResponseCondition = &c.ResponseCondition.Value\n\t}\n\tif c.Placement.WasSet {\n\t\tinput.Placement = &c.Placement.Value\n\t}\n\tif c.ProcessingRegion.WasSet {\n\t\tinput.ProcessingRegion = &c.ProcessingRegion.Value\n\t}\n\tif c.ProjectID.WasSet {\n\t\tinput.ProjectID = &c.ProjectID.Value\n\t}\n\n\treturn &input, nil\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *CreateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(false),\n\t\tLocked:             optional.Of(false),\n\t\tAutoCloneFlag:      c.AutoClone,\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.ServiceName,\n\t\tServiceVersionFlag: c.ServiceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": errors.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tinput, err := c.ConstructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\td, err := c.Globals.APIClient.CreateScalyr(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\ttext.Success(\n\t\tout,\n\t\t\"Created Scalyr logging endpoint %s (service %s version %d)\",\n\t\tfastly.ToValue(d.Name),\n\t\tfastly.ToValue(d.ServiceID),\n\t\tfastly.ToValue(d.ServiceVersion),\n\t)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/scalyr/delete.go",
    "content": "package scalyr\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DeleteCommand calls the Fastly API to delete a Scalyr logging endpoint.\ntype DeleteCommand struct {\n\targparser.Base\n\tInput          fastly.DeleteScalyrInput\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n\tautoClone      argparser.OptionalAutoClone\n}\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"delete\", \"Delete a Scalyr logging endpoint on a Fastly service version\").Alias(\"remove\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"The name of the Scalyr logging object\").Short('n').Required().StringVar(&c.Input.Name)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.autoClone.Set,\n\t\tDst:    &c.autoClone.Value,\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DeleteCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(false),\n\t\tLocked:             optional.Of(false),\n\t\tAutoCloneFlag:      c.autoClone,\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": errors.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tc.Input.ServiceID = serviceID\n\tc.Input.ServiceVersion = fastly.ToValue(serviceVersion.Number)\n\n\tif err := c.Globals.APIClient.DeleteScalyr(context.TODO(), &c.Input); err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Deleted Scalyr logging endpoint %s (service %s version %d)\", c.Input.Name, c.Input.ServiceID, c.Input.ServiceVersion)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/scalyr/describe.go",
    "content": "package scalyr\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DescribeCommand calls the Fastly API to describe a Scalyr logging endpoint.\ntype DescribeCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tInput          fastly.GetScalyrInput\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n}\n\n// NewDescribeCommand returns a usable command registered under the parent.\nfunc NewDescribeCommand(parent argparser.Registerer, g *global.Data) *DescribeCommand {\n\tc := DescribeCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"describe\", \"Show detailed information about a Scalyr logging endpoint on a Fastly service version\").Alias(\"get\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"The name of the Scalyr logging object\").Short('n').Required().StringVar(&c.Input.Name)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DescribeCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fsterr.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tc.Input.ServiceID = serviceID\n\tc.Input.ServiceVersion = fastly.ToValue(serviceVersion.Number)\n\n\to, err := c.Globals.APIClient.GetScalyr(context.TODO(), &c.Input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\tlines := text.Lines{\n\t\t\"Format version\":     fastly.ToValue(o.FormatVersion),\n\t\t\"Format\":             fastly.ToValue(o.Format),\n\t\t\"Name\":               fastly.ToValue(o.Name),\n\t\t\"Placement\":          fastly.ToValue(o.Placement),\n\t\t\"Processing region\":  fastly.ToValue(o.ProcessingRegion),\n\t\t\"Project ID\":         fastly.ToValue(o.ProjectID),\n\t\t\"Region\":             fastly.ToValue(o.Region),\n\t\t\"Response condition\": fastly.ToValue(o.ResponseCondition),\n\t\t\"Token\":              fastly.ToValue(o.Token),\n\t\t\"Version\":            fastly.ToValue(o.ServiceVersion),\n\t}\n\tif !c.Globals.Verbose() {\n\t\tlines[\"Service ID\"] = fastly.ToValue(o.ServiceID)\n\t}\n\ttext.PrintLines(out, lines)\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/scalyr/doc.go",
    "content": "// Package scalyr contains commands to inspect and manipulate Fastly service Scalyr\n// logging endpoints.\npackage scalyr\n"
  },
  {
    "path": "pkg/commands/service/logging/scalyr/list.go",
    "content": "package scalyr\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// ListCommand calls the Fastly API to list Scalyr logging endpoints.\ntype ListCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tInput          fastly.ListScalyrsInput\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n}\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"list\", \"List Scalyr endpoints on a Fastly service version\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *ListCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fsterr.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tc.Input.ServiceID = serviceID\n\tc.Input.ServiceVersion = fastly.ToValue(serviceVersion.Number)\n\n\to, err := c.Globals.APIClient.ListScalyrs(context.TODO(), &c.Input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\tif !c.Globals.Verbose() {\n\t\ttw := text.NewTable(out)\n\t\ttw.AddHeader(\"SERVICE\", \"VERSION\", \"NAME\")\n\t\tfor _, scalyr := range o {\n\t\t\ttw.AddLine(\n\t\t\t\tfastly.ToValue(scalyr.ServiceID),\n\t\t\t\tfastly.ToValue(scalyr.ServiceVersion),\n\t\t\t\tfastly.ToValue(scalyr.Name),\n\t\t\t)\n\t\t}\n\t\ttw.Print()\n\t\treturn nil\n\t}\n\n\tfmt.Fprintf(out, \"Version: %d\\n\", c.Input.ServiceVersion)\n\tfor i, scalyr := range o {\n\t\tfmt.Fprintf(out, \"\\tScalyr %d/%d\\n\", i+1, len(o))\n\t\tfmt.Fprintf(out, \"\\t\\tService ID: %s\\n\", fastly.ToValue(scalyr.ServiceID))\n\t\tfmt.Fprintf(out, \"\\t\\tVersion: %d\\n\", fastly.ToValue(scalyr.ServiceVersion))\n\t\tfmt.Fprintf(out, \"\\t\\tName: %s\\n\", fastly.ToValue(scalyr.Name))\n\t\tfmt.Fprintf(out, \"\\t\\tToken: %s\\n\", fastly.ToValue(scalyr.Token))\n\t\tfmt.Fprintf(out, \"\\t\\tRegion: %s\\n\", fastly.ToValue(scalyr.Region))\n\t\tfmt.Fprintf(out, \"\\t\\tFormat: %s\\n\", fastly.ToValue(scalyr.Format))\n\t\tfmt.Fprintf(out, \"\\t\\tFormat version: %d\\n\", fastly.ToValue(scalyr.FormatVersion))\n\t\tfmt.Fprintf(out, \"\\t\\tResponse condition: %s\\n\", fastly.ToValue(scalyr.ResponseCondition))\n\t\tfmt.Fprintf(out, \"\\t\\tPlacement: %s\\n\", fastly.ToValue(scalyr.Placement))\n\t\tfmt.Fprintf(out, \"\\t\\tProject ID: %s\\n\", fastly.ToValue(scalyr.ProjectID))\n\t\tfmt.Fprintf(out, \"\\t\\tProcessing region: %s\\n\", fastly.ToValue(scalyr.ProcessingRegion))\n\t}\n\tfmt.Fprintln(out)\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/scalyr/root.go",
    "content": "package scalyr\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"scalyr\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manipulate Fastly service version Scalyr logging endpoints\")\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/scalyr/scalyr_integration_test.go",
    "content": "package scalyr_test\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\tfsterrs \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/mock\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n\n\troot \"github.com/fastly/cli/pkg/commands/service\"\n\tparent \"github.com/fastly/cli/pkg/commands/service/logging\"\n\tsub \"github.com/fastly/cli/pkg/commands/service/logging/scalyr\"\n)\n\nfunc TestScalyrCreate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:    \"--name log --version 1 --auth-token abc --autoclone\",\n\t\t\tEnvVars: map[string]string{\"FASTLY_SERVICE_ID\": \"\"},\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t},\n\t\t\tWantError: fsterrs.ErrNoServiceID.Error(),\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name log --auth-token abc --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tCreateScalyrFn: createScalyrOK,\n\t\t\t},\n\t\t\tWantOutput: \"Created Scalyr logging endpoint log (service 123 version 4)\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name log --auth-token abc --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tCreateScalyrFn: createScalyrError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"create\"}, scenarios)\n}\n\nfunc TestScalyrList(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:  testutil.GetVersion,\n\t\t\t\tListScalyrsFn: listScalyrsOK,\n\t\t\t},\n\t\t\tWantOutput: listScalyrsShortOutput,\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --verbose\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:  testutil.GetVersion,\n\t\t\t\tListScalyrsFn: listScalyrsOK,\n\t\t\t},\n\t\t\tWantOutput: listScalyrsVerboseOutput,\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 -v\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:  testutil.GetVersion,\n\t\t\t\tListScalyrsFn: listScalyrsOK,\n\t\t\t},\n\t\t\tWantOutput: listScalyrsVerboseOutput,\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:  testutil.GetVersion,\n\t\t\t\tListScalyrsFn: listScalyrsError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"list\"}, scenarios)\n}\n\nfunc TestScalyrDescribe(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"--service-id 123 --version 1\",\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tGetScalyrFn:  getScalyrError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tGetScalyrFn:  getScalyrOK,\n\t\t\t},\n\t\t\tWantOutput: describeScalyrOutput,\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"describe\"}, scenarios)\n}\n\nfunc TestScalyrUpdate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"--service-id 123 --version 1 --new-name log\",\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs --new-name log --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tUpdateScalyrFn: updateScalyrError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs --new-name log --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tUpdateScalyrFn: updateScalyrOK,\n\t\t\t},\n\t\t\tWantOutput: \"Updated Scalyr logging endpoint log (service 123 version 4)\",\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"update\"}, scenarios)\n}\n\nfunc TestScalyrDelete(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"--service-id 123 --version 1\",\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tDeleteScalyrFn: deleteScalyrError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tDeleteScalyrFn: deleteScalyrOK,\n\t\t\t},\n\t\t\tWantOutput: \"Deleted Scalyr logging endpoint logs (service 123 version 4)\",\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"delete\"}, scenarios)\n}\n\nvar errTest = errors.New(\"fixture error\")\n\nfunc createScalyrOK(_ context.Context, i *fastly.CreateScalyrInput) (*fastly.Scalyr, error) {\n\ts := fastly.Scalyr{\n\t\tServiceID:      fastly.ToPointer(i.ServiceID),\n\t\tServiceVersion: fastly.ToPointer(i.ServiceVersion),\n\t}\n\n\t// Avoids null pointer dereference for test cases with missing required params.\n\t// If omitted, tests are guaranteed to panic.\n\tif i.Name != nil {\n\t\ts.Name = i.Name\n\t}\n\n\tif i.Token != nil {\n\t\ts.Token = i.Token\n\t}\n\n\tif i.Format != nil {\n\t\ts.Format = i.Format\n\t}\n\n\tif i.FormatVersion != nil {\n\t\ts.FormatVersion = i.FormatVersion\n\t}\n\n\tif i.ResponseCondition != nil {\n\t\ts.ResponseCondition = i.ResponseCondition\n\t}\n\n\tif i.Placement != nil {\n\t\ts.Placement = i.Placement\n\t}\n\n\treturn &s, nil\n}\n\nfunc createScalyrError(_ context.Context, _ *fastly.CreateScalyrInput) (*fastly.Scalyr, error) {\n\treturn nil, errTest\n}\n\nfunc listScalyrsOK(_ context.Context, i *fastly.ListScalyrsInput) ([]*fastly.Scalyr, error) {\n\treturn []*fastly.Scalyr{\n\t\t{\n\t\t\tServiceID:         fastly.ToPointer(i.ServiceID),\n\t\t\tServiceVersion:    fastly.ToPointer(i.ServiceVersion),\n\t\t\tName:              fastly.ToPointer(\"logs\"),\n\t\t\tToken:             fastly.ToPointer(\"abc\"),\n\t\t\tRegion:            fastly.ToPointer(\"US\"),\n\t\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\t\tFormatVersion:     fastly.ToPointer(2),\n\t\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\t\tProjectID:         fastly.ToPointer(\"example-project\"),\n\t\t\tProcessingRegion:  fastly.ToPointer(\"us\"),\n\t\t},\n\t\t{\n\t\t\tServiceID:         fastly.ToPointer(i.ServiceID),\n\t\t\tServiceVersion:    fastly.ToPointer(i.ServiceVersion),\n\t\t\tName:              fastly.ToPointer(\"analytics\"),\n\t\t\tToken:             fastly.ToPointer(\"abc\"),\n\t\t\tRegion:            fastly.ToPointer(\"US\"),\n\t\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\t\tFormatVersion:     fastly.ToPointer(2),\n\t\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\t\tProjectID:         fastly.ToPointer(\"example-project\"),\n\t\t\tProcessingRegion:  fastly.ToPointer(\"us\"),\n\t\t},\n\t}, nil\n}\n\nfunc listScalyrsError(_ context.Context, _ *fastly.ListScalyrsInput) ([]*fastly.Scalyr, error) {\n\treturn nil, errTest\n}\n\nvar listScalyrsShortOutput = strings.TrimSpace(`\nSERVICE  VERSION  NAME\n123      1        logs\n123      1        analytics\n`) + \"\\n\"\n\nvar listScalyrsVerboseOutput = strings.TrimSpace(`\nFastly API endpoint: https://api.fastly.com\nFastly API token provided via config file (auth: user)\n\nService ID (via --service-id): 123\n\nVersion: 1\n\tScalyr 1/2\n\t\tService ID: 123\n\t\tVersion: 1\n\t\tName: logs\n\t\tToken: abc\n\t\tRegion: US\n\t\tFormat: %h %l %u %t \"%r\" %>s %b\n\t\tFormat version: 2\n\t\tResponse condition: Prevent default logging\n\t\tPlacement: none\n\t\tProject ID: example-project\n\t\tProcessing region: us\n\tScalyr 2/2\n\t\tService ID: 123\n\t\tVersion: 1\n\t\tName: analytics\n\t\tToken: abc\n\t\tRegion: US\n\t\tFormat: %h %l %u %t \"%r\" %>s %b\n\t\tFormat version: 2\n\t\tResponse condition: Prevent default logging\n\t\tPlacement: none\n\t\tProject ID: example-project\n\t\tProcessing region: us\n`) + \"\\n\\n\"\n\nfunc getScalyrOK(_ context.Context, i *fastly.GetScalyrInput) (*fastly.Scalyr, error) {\n\treturn &fastly.Scalyr{\n\t\tServiceID:         fastly.ToPointer(i.ServiceID),\n\t\tServiceVersion:    fastly.ToPointer(i.ServiceVersion),\n\t\tName:              fastly.ToPointer(\"logs\"),\n\t\tToken:             fastly.ToPointer(\"abc\"),\n\t\tRegion:            fastly.ToPointer(\"US\"),\n\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\tFormatVersion:     fastly.ToPointer(2),\n\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\tProjectID:         fastly.ToPointer(\"example-project\"),\n\t\tProcessingRegion:  fastly.ToPointer(\"us\"),\n\t}, nil\n}\n\nfunc getScalyrError(_ context.Context, _ *fastly.GetScalyrInput) (*fastly.Scalyr, error) {\n\treturn nil, errTest\n}\n\nvar describeScalyrOutput = \"\\n\" + strings.TrimSpace(`\nFormat: %h %l %u %t \"%r\" %>s %b\nFormat version: 2\nName: logs\nPlacement: none\nProcessing region: us\nProject ID: example-project\nRegion: US\nResponse condition: Prevent default logging\nService ID: 123\nToken: abc\nVersion: 1\n`) + \"\\n\"\n\nfunc updateScalyrOK(_ context.Context, i *fastly.UpdateScalyrInput) (*fastly.Scalyr, error) {\n\treturn &fastly.Scalyr{\n\t\tServiceID:         fastly.ToPointer(i.ServiceID),\n\t\tServiceVersion:    fastly.ToPointer(i.ServiceVersion),\n\t\tName:              fastly.ToPointer(\"log\"),\n\t\tToken:             fastly.ToPointer(\"abc\"),\n\t\tRegion:            fastly.ToPointer(\"EU\"),\n\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\tFormatVersion:     fastly.ToPointer(2),\n\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t}, nil\n}\n\nfunc updateScalyrError(_ context.Context, _ *fastly.UpdateScalyrInput) (*fastly.Scalyr, error) {\n\treturn nil, errTest\n}\n\nfunc deleteScalyrOK(_ context.Context, _ *fastly.DeleteScalyrInput) error {\n\treturn nil\n}\n\nfunc deleteScalyrError(_ context.Context, _ *fastly.DeleteScalyrInput) error {\n\treturn errTest\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/scalyr/scalyr_test.go",
    "content": "package scalyr_test\n\nimport (\n\t\"bytes\"\n\t\"testing\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/service/logging/scalyr\"\n\t\"github.com/fastly/cli/pkg/config\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/manifest\"\n\t\"github.com/fastly/cli/pkg/mock\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n)\n\nfunc TestCreateScalyrInput(t *testing.T) {\n\tfor _, testcase := range []struct {\n\t\tname      string\n\t\tcmd       *scalyr.CreateCommand\n\t\twant      *fastly.CreateScalyrInput\n\t\twantError string\n\t}{\n\t\t{\n\t\t\tname: \"required values set flag serviceID\",\n\t\t\tcmd:  createCommandRequired(),\n\t\t\twant: &fastly.CreateScalyrInput{\n\t\t\t\tServiceID:      \"123\",\n\t\t\t\tServiceVersion: 4,\n\t\t\t\tName:           fastly.ToPointer(\"log\"),\n\t\t\t\tToken:          fastly.ToPointer(\"tkn\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"all values set flag serviceID\",\n\t\t\tcmd:  createCommandAll(),\n\t\t\twant: &fastly.CreateScalyrInput{\n\t\t\t\tServiceID:         \"123\",\n\t\t\t\tServiceVersion:    4,\n\t\t\t\tName:              fastly.ToPointer(\"log\"),\n\t\t\t\tToken:             fastly.ToPointer(\"tkn\"),\n\t\t\t\tRegion:            fastly.ToPointer(\"US\"),\n\t\t\t\tFormatVersion:     fastly.ToPointer(2),\n\t\t\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\t\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\t\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\t\t\tProjectID:         fastly.ToPointer(\"example-project\"),\n\t\t\t\tProcessingRegion:  fastly.ToPointer(\"eu\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:      \"error missing serviceID\",\n\t\t\tcmd:       createCommandMissingServiceID(),\n\t\t\twant:      nil,\n\t\t\twantError: errors.ErrNoServiceID.Error(),\n\t\t},\n\t} {\n\t\tt.Run(testcase.name, func(t *testing.T) {\n\t\t\tif testcase.wantError == errors.ErrNoServiceID.Error() {\n\t\t\t\tt.Setenv(\"FASTLY_SERVICE_ID\", \"\")\n\t\t\t}\n\t\t\tvar bs []byte\n\t\t\tout := bytes.NewBuffer(bs)\n\t\t\tverboseMode := true\n\n\t\t\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\t\t\tAutoCloneFlag:      testcase.cmd.AutoClone,\n\t\t\t\tAPIClient:          testcase.cmd.Globals.APIClient,\n\t\t\t\tManifest:           testcase.cmd.Manifest,\n\t\t\t\tOut:                out,\n\t\t\t\tServiceVersionFlag: testcase.cmd.ServiceVersion,\n\t\t\t\tVerboseMode:        verboseMode,\n\t\t\t})\n\n\t\t\tswitch {\n\t\t\tcase err != nil && testcase.wantError == \"\":\n\t\t\t\tt.Fatalf(\"unexpected error getting service details: %v\", err)\n\t\t\t\treturn\n\t\t\tcase err != nil && testcase.wantError != \"\":\n\t\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\t\treturn\n\t\t\tcase err == nil && testcase.wantError != \"\":\n\t\t\t\tt.Fatalf(\"expected error, have nil (service details: %s, %d)\", serviceID, serviceVersion.Number)\n\t\t\tcase err == nil && testcase.wantError == \"\":\n\t\t\t\thave, err := testcase.cmd.ConstructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\t\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\t\ttestutil.AssertEqual(t, testcase.want, have)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestUpdateScalyrInput(t *testing.T) {\n\tscenarios := []struct {\n\t\tname      string\n\t\tcmd       *scalyr.UpdateCommand\n\t\tapi       mock.API\n\t\twant      *fastly.UpdateScalyrInput\n\t\twantError string\n\t}{\n\t\t{\n\t\t\tname: \"no updates\",\n\t\t\tcmd:  updateCommandNoUpdates(),\n\t\t\tapi: mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tGetScalyrFn:    getScalyrOK,\n\t\t\t},\n\t\t\twant: &fastly.UpdateScalyrInput{\n\t\t\t\tServiceID:      \"123\",\n\t\t\t\tServiceVersion: 4,\n\t\t\t\tName:           \"log\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"all values set flag serviceID\",\n\t\t\tcmd:  updateCommandAll(),\n\t\t\tapi: mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tGetScalyrFn:    getScalyrOK,\n\t\t\t},\n\t\t\twant: &fastly.UpdateScalyrInput{\n\t\t\t\tServiceID:         \"123\",\n\t\t\t\tServiceVersion:    4,\n\t\t\t\tName:              \"log\",\n\t\t\t\tNewName:           fastly.ToPointer(\"new1\"),\n\t\t\t\tToken:             fastly.ToPointer(\"new2\"),\n\t\t\t\tFormatVersion:     fastly.ToPointer(3),\n\t\t\t\tFormat:            fastly.ToPointer(\"new3\"),\n\t\t\t\tResponseCondition: fastly.ToPointer(\"new4\"),\n\t\t\t\tPlacement:         fastly.ToPointer(\"new5\"),\n\t\t\t\tRegion:            fastly.ToPointer(\"new6\"),\n\t\t\t\tProjectID:         fastly.ToPointer(\"new7\"),\n\t\t\t\tProcessingRegion:  fastly.ToPointer(\"eu\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:      \"error missing serviceID\",\n\t\t\tcmd:       updateCommandMissingServiceID(),\n\t\t\twant:      nil,\n\t\t\twantError: errors.ErrNoServiceID.Error(),\n\t\t},\n\t}\n\tfor testcaseIdx := range scenarios {\n\t\ttestcase := &scenarios[testcaseIdx]\n\t\tt.Run(testcase.name, func(t *testing.T) {\n\t\t\tif testcase.wantError == errors.ErrNoServiceID.Error() {\n\t\t\t\tt.Setenv(\"FASTLY_SERVICE_ID\", \"\")\n\t\t\t}\n\t\t\ttestcase.cmd.Globals.APIClient = testcase.api\n\n\t\t\tvar bs []byte\n\t\t\tout := bytes.NewBuffer(bs)\n\t\t\tverboseMode := true\n\n\t\t\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\t\t\tAutoCloneFlag:      testcase.cmd.AutoClone,\n\t\t\t\tAPIClient:          testcase.api,\n\t\t\t\tManifest:           testcase.cmd.Manifest,\n\t\t\t\tOut:                out,\n\t\t\t\tServiceVersionFlag: testcase.cmd.ServiceVersion,\n\t\t\t\tVerboseMode:        verboseMode,\n\t\t\t})\n\n\t\t\tswitch {\n\t\t\tcase err != nil && testcase.wantError == \"\":\n\t\t\t\tt.Fatalf(\"unexpected error getting service details: %v\", err)\n\t\t\t\treturn\n\t\t\tcase err != nil && testcase.wantError != \"\":\n\t\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\t\treturn\n\t\t\tcase err == nil && testcase.wantError != \"\":\n\t\t\t\tt.Fatalf(\"expected error, have nil (service details: %s, %d)\", serviceID, serviceVersion.Number)\n\t\t\tcase err == nil && testcase.wantError == \"\":\n\t\t\t\thave, err := testcase.cmd.ConstructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\t\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\t\ttestutil.AssertEqual(t, testcase.want, have)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc createCommandRequired() *scalyr.CreateCommand {\n\tvar b bytes.Buffer\n\n\tg := global.Data{\n\t\tConfig: config.File{},\n\t\tEnv:    config.Environment{},\n\t\tOutput: &b,\n\t}\n\tg.APIClient, _ = mock.APIClient(mock.API{\n\t\tGetVersionFn:   testutil.GetVersion,\n\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t})(\"token\", \"endpoint\", false)\n\n\treturn &scalyr.CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: &g,\n\t\t},\n\t\tManifest: manifest.Data{\n\t\t\tFlag: manifest.Flag{\n\t\t\t\tServiceID: \"123\",\n\t\t\t},\n\t\t},\n\t\tServiceVersion: argparser.OptionalServiceVersion{\n\t\t\tOptionalString: argparser.OptionalString{Value: \"1\"},\n\t\t},\n\t\tAutoClone: argparser.OptionalAutoClone{\n\t\t\tOptionalBool: argparser.OptionalBool{\n\t\t\t\tOptional: argparser.Optional{\n\t\t\t\t\tWasSet: true,\n\t\t\t\t},\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t},\n\t\tEndpointName: argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"log\"},\n\t\tToken:        argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"tkn\"},\n\t}\n}\n\nfunc createCommandAll() *scalyr.CreateCommand {\n\tvar b bytes.Buffer\n\n\tg := global.Data{\n\t\tConfig: config.File{},\n\t\tEnv:    config.Environment{},\n\t\tOutput: &b,\n\t}\n\tg.APIClient, _ = mock.APIClient(mock.API{\n\t\tGetVersionFn:   testutil.GetVersion,\n\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t})(\"token\", \"endpoint\", false)\n\n\treturn &scalyr.CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: &g,\n\t\t},\n\t\tManifest: manifest.Data{\n\t\t\tFlag: manifest.Flag{\n\t\t\t\tServiceID: \"123\",\n\t\t\t},\n\t\t},\n\t\tServiceVersion: argparser.OptionalServiceVersion{\n\t\t\tOptionalString: argparser.OptionalString{Value: \"1\"},\n\t\t},\n\t\tAutoClone: argparser.OptionalAutoClone{\n\t\t\tOptionalBool: argparser.OptionalBool{\n\t\t\t\tOptional: argparser.Optional{\n\t\t\t\t\tWasSet: true,\n\t\t\t\t},\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t},\n\t\tEndpointName:      argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"log\"},\n\t\tToken:             argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"tkn\"},\n\t\tRegion:            argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"US\"},\n\t\tFormat:            argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: `%h %l %u %t \"%r\" %>s %b`},\n\t\tFormatVersion:     argparser.OptionalInt{Optional: argparser.Optional{WasSet: true}, Value: 2},\n\t\tResponseCondition: argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"Prevent default logging\"},\n\t\tPlacement:         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"none\"},\n\t\tProjectID:         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"example-project\"},\n\t\tProcessingRegion:  argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"eu\"},\n\t}\n}\n\nfunc createCommandMissingServiceID() *scalyr.CreateCommand {\n\tres := createCommandAll()\n\tres.Manifest = manifest.Data{}\n\tres.ServiceVersion = argparser.OptionalServiceVersion{}\n\treturn res\n}\n\nfunc updateCommandNoUpdates() *scalyr.UpdateCommand {\n\tvar b bytes.Buffer\n\n\tg := global.Data{\n\t\tConfig: config.File{},\n\t\tEnv:    config.Environment{},\n\t\tOutput: &b,\n\t}\n\n\treturn &scalyr.UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: &g,\n\t\t},\n\t\tManifest: manifest.Data{\n\t\t\tFlag: manifest.Flag{\n\t\t\t\tServiceID: \"123\",\n\t\t\t},\n\t\t},\n\t\tEndpointName: \"log\",\n\t\tServiceVersion: argparser.OptionalServiceVersion{\n\t\t\tOptionalString: argparser.OptionalString{Value: \"1\"},\n\t\t},\n\t\tAutoClone: argparser.OptionalAutoClone{\n\t\t\tOptionalBool: argparser.OptionalBool{\n\t\t\t\tOptional: argparser.Optional{\n\t\t\t\t\tWasSet: true,\n\t\t\t\t},\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t},\n\t}\n}\n\nfunc updateCommandAll() *scalyr.UpdateCommand {\n\tvar b bytes.Buffer\n\n\tg := global.Data{\n\t\tConfig: config.File{},\n\t\tEnv:    config.Environment{},\n\t\tOutput: &b,\n\t}\n\n\treturn &scalyr.UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: &g,\n\t\t},\n\t\tManifest: manifest.Data{\n\t\t\tFlag: manifest.Flag{\n\t\t\t\tServiceID: \"123\",\n\t\t\t},\n\t\t},\n\t\tEndpointName: \"log\",\n\t\tServiceVersion: argparser.OptionalServiceVersion{\n\t\t\tOptionalString: argparser.OptionalString{Value: \"1\"},\n\t\t},\n\t\tAutoClone: argparser.OptionalAutoClone{\n\t\t\tOptionalBool: argparser.OptionalBool{\n\t\t\t\tOptional: argparser.Optional{\n\t\t\t\t\tWasSet: true,\n\t\t\t\t},\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t},\n\t\tNewName:           argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new1\"},\n\t\tToken:             argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new2\"},\n\t\tFormat:            argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new3\"},\n\t\tFormatVersion:     argparser.OptionalInt{Optional: argparser.Optional{WasSet: true}, Value: 3},\n\t\tResponseCondition: argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new4\"},\n\t\tPlacement:         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new5\"},\n\t\tRegion:            argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new6\"},\n\t\tProjectID:         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new7\"},\n\t\tProcessingRegion:  argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"eu\"},\n\t}\n}\n\nfunc updateCommandMissingServiceID() *scalyr.UpdateCommand {\n\tres := updateCommandAll()\n\tres.Manifest = manifest.Data{}\n\tres.ServiceVersion = argparser.OptionalServiceVersion{}\n\treturn res\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/scalyr/update.go",
    "content": "package scalyr\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/service/logging/logflags\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/manifest\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// UpdateCommand calls the Fastly API to update Scalyr logging endpoints.\ntype UpdateCommand struct {\n\targparser.Base\n\tManifest manifest.Data\n\n\t// Required.\n\tEndpointName   string // Can't shadow argparser.Base method Name().\n\tServiceName    argparser.OptionalServiceNameID\n\tServiceVersion argparser.OptionalServiceVersion\n\n\t// Optional.\n\tAutoClone         argparser.OptionalAutoClone\n\tFormat            argparser.OptionalString\n\tFormatVersion     argparser.OptionalInt\n\tNewName           argparser.OptionalString\n\tPlacement         argparser.OptionalString\n\tProcessingRegion  argparser.OptionalString\n\tProjectID         argparser.OptionalString\n\tRegion            argparser.OptionalString\n\tResponseCondition argparser.OptionalString\n\tToken             argparser.OptionalString\n}\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"update\", \"Update a Scalyr logging endpoint on a Fastly service version\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"The name of the Scalyr logging object\").Short('n').Required().StringVar(&c.EndpointName)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.ServiceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.CmdClause.Flag(\"auth-token\", \"The token to use for authentication (https://www.scalyr.com/keys)\").Action(c.Token.Set).StringVar(&c.Token.Value)\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.AutoClone.Set,\n\t\tDst:    &c.AutoClone.Value,\n\t})\n\tlogflags.Format(c.CmdClause, &c.Format)\n\tlogflags.FormatVersion(c.CmdClause, &c.FormatVersion)\n\tc.CmdClause.Flag(\"new-name\", \"New name of the Scalyr logging object\").Action(c.NewName.Set).StringVar(&c.NewName.Value)\n\tlogflags.Placement(c.CmdClause, &c.Placement)\n\tlogflags.ProcessingRegion(c.CmdClause, &c.ProcessingRegion, \"Scalyr\")\n\tc.CmdClause.Flag(\"project-id\", \"The name of the logfile field sent to Scalyr\").Action(c.ProjectID.Set).StringVar(&c.ProjectID.Value)\n\tc.CmdClause.Flag(\"region\", \"The region where logs are received and stored by Scalyr. Either US or EU. Defaults to US if undefined\").Action(c.Region.Set).StringVar(&c.Region.Value)\n\tlogflags.ResponseCondition(c.CmdClause, &c.ResponseCondition)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.ServiceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.ServiceName.Value,\n\t})\n\treturn &c\n}\n\n// ConstructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *UpdateCommand) ConstructInput(serviceID string, serviceVersion int) (*fastly.UpdateScalyrInput, error) {\n\tinput := fastly.UpdateScalyrInput{\n\t\tServiceID:      serviceID,\n\t\tServiceVersion: serviceVersion,\n\t\tName:           c.EndpointName,\n\t}\n\tif c.NewName.WasSet {\n\t\tinput.NewName = &c.NewName.Value\n\t}\n\tif c.Format.WasSet {\n\t\tinput.Format = fastly.ToPointer(argparser.Content(c.Format.Value))\n\t}\n\tif c.FormatVersion.WasSet {\n\t\tinput.FormatVersion = &c.FormatVersion.Value\n\t}\n\tif c.Token.WasSet {\n\t\tinput.Token = &c.Token.Value\n\t}\n\tif c.Region.WasSet {\n\t\tinput.Region = &c.Region.Value\n\t}\n\tif c.ResponseCondition.WasSet {\n\t\tinput.ResponseCondition = &c.ResponseCondition.Value\n\t}\n\tif c.Placement.WasSet {\n\t\tinput.Placement = &c.Placement.Value\n\t}\n\tif c.ProcessingRegion.WasSet {\n\t\tinput.ProcessingRegion = &c.ProcessingRegion.Value\n\t}\n\tif c.ProjectID.WasSet {\n\t\tinput.ProjectID = &c.ProjectID.Value\n\t}\n\treturn &input, nil\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *UpdateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(false),\n\t\tLocked:             optional.Of(false),\n\t\tAutoCloneFlag:      c.AutoClone,\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.ServiceName,\n\t\tServiceVersionFlag: c.ServiceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": errors.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tinput, err := c.ConstructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tscalyr, err := c.Globals.APIClient.UpdateScalyr(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\ttext.Success(\n\t\tout,\n\t\t\"Updated Scalyr logging endpoint %s (service %s version %d)\",\n\t\tfastly.ToValue(scalyr.Name),\n\t\tfastly.ToValue(scalyr.ServiceID),\n\t\tfastly.ToValue(scalyr.ServiceVersion),\n\t)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/sftp/create.go",
    "content": "package sftp\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/service/logging/logflags\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/manifest\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// CreateCommand calls the Fastly API to create an SFTP logging endpoint.\ntype CreateCommand struct {\n\targparser.Base\n\tManifest manifest.Data\n\n\t// Required.\n\tServiceName    argparser.OptionalServiceNameID\n\tServiceVersion argparser.OptionalServiceVersion\n\n\t// Optional.\n\tAddress           argparser.OptionalString\n\tAutoClone         argparser.OptionalAutoClone\n\tCompressionCodec  argparser.OptionalString\n\tEndpointName      argparser.OptionalString // Can't shadow argparser.Base method Name().\n\tFormat            argparser.OptionalString\n\tFormatVersion     argparser.OptionalInt\n\tGzipLevel         argparser.OptionalInt\n\tMessageType       argparser.OptionalString\n\tPassword          argparser.OptionalString\n\tPath              argparser.OptionalString\n\tPeriod            argparser.OptionalInt\n\tPlacement         argparser.OptionalString\n\tPort              argparser.OptionalInt\n\tProcessingRegion  argparser.OptionalString\n\tPublicKey         argparser.OptionalString\n\tResponseCondition argparser.OptionalString\n\tSecretKey         argparser.OptionalString\n\tSSHKnownHosts     argparser.OptionalString\n\tTimestampFormat   argparser.OptionalString\n\tUser              argparser.OptionalString\n}\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"create\", \"Create an SFTP logging endpoint on a Fastly service version\").Alias(\"add\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"The name of the SFTP logging object. Used as a primary key for API access\").Short('n').Action(c.EndpointName.Set).StringVar(&c.EndpointName.Value)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.ServiceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.AutoClone.Set,\n\t\tDst:    &c.AutoClone.Value,\n\t})\n\tc.CmdClause.Flag(\"address\", \"The hostname or IPv4 address\").Action(c.Address.Set).StringVar(&c.Address.Value)\n\tlogflags.CompressionCodec(c.CmdClause, &c.CompressionCodec)\n\tlogflags.Format(c.CmdClause, &c.Format)\n\tlogflags.FormatVersion(c.CmdClause, &c.FormatVersion)\n\tlogflags.GzipLevel(c.CmdClause, &c.GzipLevel)\n\tlogflags.MessageType(c.CmdClause, &c.MessageType)\n\tc.CmdClause.Flag(\"password\", \"The password for the server. If both password and secret_key are passed, secret_key will be used in preference\").Action(c.Password.Set).StringVar(&c.Password.Value)\n\tc.CmdClause.Flag(\"path\", \"The path to upload logs to. The directory must exist on the SFTP server before logs can be saved to it\").Action(c.Path.Set).StringVar(&c.Path.Value)\n\tlogflags.Period(c.CmdClause, &c.Period)\n\tlogflags.Placement(c.CmdClause, &c.Placement)\n\tlogflags.ProcessingRegion(c.CmdClause, &c.ProcessingRegion, \"SFTP\")\n\tc.CmdClause.Flag(\"port\", \"The port number\").Action(c.Port.Set).IntVar(&c.Port.Value)\n\tlogflags.PublicKey(c.CmdClause, &c.PublicKey)\n\tlogflags.ResponseCondition(c.CmdClause, &c.ResponseCondition)\n\tc.CmdClause.Flag(\"secret-key\", \"The SSH private key for the server. If both password and secret_key are passed, secret_key will be used in preference\").Action(c.SecretKey.Set).StringVar(&c.SecretKey.Value)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.ServiceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.ServiceName.Value,\n\t})\n\tc.CmdClause.Flag(\"ssh-known-hosts\", \"A list of host keys for all hosts we can connect to over SFTP\").Action(c.SSHKnownHosts.Set).StringVar(&c.SSHKnownHosts.Value)\n\tlogflags.TimestampFormat(c.CmdClause, &c.TimestampFormat)\n\tc.CmdClause.Flag(\"user\", \"The username for the server\").Action(c.User.Set).StringVar(&c.User.Value)\n\treturn &c\n}\n\n// ConstructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *CreateCommand) ConstructInput(serviceID string, serviceVersion int) (*fastly.CreateSFTPInput, error) {\n\tvar input fastly.CreateSFTPInput\n\n\tinput.ServiceID = serviceID\n\tinput.ServiceVersion = serviceVersion\n\tif c.EndpointName.WasSet {\n\t\tinput.Name = &c.EndpointName.Value\n\t}\n\tif c.Address.WasSet {\n\t\tinput.Address = &c.Address.Value\n\t}\n\tif c.User.WasSet {\n\t\tinput.User = &c.User.Value\n\t}\n\tif c.SSHKnownHosts.WasSet {\n\t\tinput.SSHKnownHosts = &c.SSHKnownHosts.Value\n\t}\n\n\t// The following blocks enforces the mutual exclusivity of the\n\t// CompressionCodec and GzipLevel flags.\n\tif c.CompressionCodec.WasSet && c.GzipLevel.WasSet {\n\t\treturn nil, fmt.Errorf(\"error parsing arguments: the --compression-codec flag is mutually exclusive with the --gzip-level flag\")\n\t}\n\n\tif c.Port.WasSet {\n\t\tinput.Port = &c.Port.Value\n\t}\n\n\tif c.Password.WasSet {\n\t\tinput.Password = &c.Password.Value\n\t}\n\n\tif c.PublicKey.WasSet {\n\t\tinput.PublicKey = &c.PublicKey.Value\n\t}\n\n\tif c.SecretKey.WasSet {\n\t\tinput.SecretKey = &c.SecretKey.Value\n\t}\n\n\tif c.Path.WasSet {\n\t\tinput.Path = &c.Path.Value\n\t}\n\n\tif c.Period.WasSet {\n\t\tinput.Period = &c.Period.Value\n\t}\n\n\tif c.Format.WasSet {\n\t\tinput.Format = fastly.ToPointer(argparser.Content(c.Format.Value))\n\t}\n\n\tif c.FormatVersion.WasSet {\n\t\tinput.FormatVersion = &c.FormatVersion.Value\n\t}\n\n\tif c.GzipLevel.WasSet {\n\t\tinput.GzipLevel = &c.GzipLevel.Value\n\t}\n\n\tif c.MessageType.WasSet {\n\t\tinput.MessageType = &c.MessageType.Value\n\t}\n\n\tif c.ResponseCondition.WasSet {\n\t\tinput.ResponseCondition = &c.ResponseCondition.Value\n\t}\n\n\tif c.TimestampFormat.WasSet {\n\t\tinput.TimestampFormat = &c.TimestampFormat.Value\n\t}\n\n\tif c.Placement.WasSet {\n\t\tinput.Placement = &c.Placement.Value\n\t}\n\n\tif c.CompressionCodec.WasSet {\n\t\tinput.CompressionCodec = &c.CompressionCodec.Value\n\t}\n\n\tif c.ProcessingRegion.WasSet {\n\t\tinput.ProcessingRegion = &c.ProcessingRegion.Value\n\t}\n\n\treturn &input, nil\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *CreateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(false),\n\t\tLocked:             optional.Of(false),\n\t\tAutoCloneFlag:      c.AutoClone,\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.ServiceName,\n\t\tServiceVersionFlag: c.ServiceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": errors.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tinput, err := c.ConstructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\td, err := c.Globals.APIClient.CreateSFTP(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\ttext.Success(\n\t\tout,\n\t\t\"Created SFTP logging endpoint %s (service %s version %d)\",\n\t\tfastly.ToValue(d.Name),\n\t\tfastly.ToValue(d.ServiceID),\n\t\tfastly.ToValue(d.ServiceVersion),\n\t)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/sftp/delete.go",
    "content": "package sftp\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DeleteCommand calls the Fastly API to delete an SFTP logging endpoint.\ntype DeleteCommand struct {\n\targparser.Base\n\tInput          fastly.DeleteSFTPInput\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n\tautoClone      argparser.OptionalAutoClone\n}\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"delete\", \"Delete an SFTP logging endpoint on a Fastly service version\").Alias(\"remove\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"The name of the SFTP logging object\").Short('n').Required().StringVar(&c.Input.Name)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.autoClone.Set,\n\t\tDst:    &c.autoClone.Value,\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DeleteCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(false),\n\t\tLocked:             optional.Of(false),\n\t\tAutoCloneFlag:      c.autoClone,\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": errors.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tc.Input.ServiceID = serviceID\n\tc.Input.ServiceVersion = fastly.ToValue(serviceVersion.Number)\n\n\tif err := c.Globals.APIClient.DeleteSFTP(context.TODO(), &c.Input); err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Deleted SFTP logging endpoint %s (service %s version %d)\", c.Input.Name, c.Input.ServiceID, c.Input.ServiceVersion)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/sftp/describe.go",
    "content": "package sftp\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DescribeCommand calls the Fastly API to describe an SFTP logging endpoint.\ntype DescribeCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tInput          fastly.GetSFTPInput\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n}\n\n// NewDescribeCommand returns a usable command registered under the parent.\nfunc NewDescribeCommand(parent argparser.Registerer, g *global.Data) *DescribeCommand {\n\tc := DescribeCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"describe\", \"Show detailed information about an SFTP logging endpoint on a Fastly service version\").Alias(\"get\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"The name of the SFTP logging object\").Short('n').Required().StringVar(&c.Input.Name)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DescribeCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fsterr.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tc.Input.ServiceID = serviceID\n\tc.Input.ServiceVersion = fastly.ToValue(serviceVersion.Number)\n\n\to, err := c.Globals.APIClient.GetSFTP(context.TODO(), &c.Input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\tlines := text.Lines{\n\t\t\"Address\":            fastly.ToValue(o.Address),\n\t\t\"Compression codec\":  fastly.ToValue(o.CompressionCodec),\n\t\t\"Format version\":     fastly.ToValue(o.FormatVersion),\n\t\t\"Format\":             fastly.ToValue(o.Format),\n\t\t\"GZip level\":         fastly.ToValue(o.GzipLevel),\n\t\t\"Message type\":       fastly.ToValue(o.MessageType),\n\t\t\"Name\":               fastly.ToValue(o.Name),\n\t\t\"Password\":           fastly.ToValue(o.Password),\n\t\t\"Path\":               fastly.ToValue(o.Path),\n\t\t\"Period\":             fastly.ToValue(o.Period),\n\t\t\"Placement\":          fastly.ToValue(o.Placement),\n\t\t\"Port\":               fastly.ToValue(o.Port),\n\t\t\"Processing region\":  fastly.ToValue(o.ProcessingRegion),\n\t\t\"Public key\":         fastly.ToValue(o.PublicKey),\n\t\t\"Response condition\": fastly.ToValue(o.ResponseCondition),\n\t\t\"Secret key\":         fastly.ToValue(o.SecretKey),\n\t\t\"SSH known hosts\":    fastly.ToValue(o.SSHKnownHosts),\n\t\t\"Timestamp format\":   fastly.ToValue(o.TimestampFormat),\n\t\t\"User\":               fastly.ToValue(o.User),\n\t\t\"Version\":            fastly.ToValue(o.ServiceVersion),\n\t}\n\tif !c.Globals.Verbose() {\n\t\tlines[\"Service ID\"] = fastly.ToValue(o.ServiceID)\n\t}\n\ttext.PrintLines(out, lines)\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/sftp/doc.go",
    "content": "// Package sftp contains commands to inspect and manipulate Fastly service SFTP\n// logging endpoints.\npackage sftp\n"
  },
  {
    "path": "pkg/commands/service/logging/sftp/list.go",
    "content": "package sftp\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// ListCommand calls the Fastly API to list SFTP logging endpoints.\ntype ListCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tInput          fastly.ListSFTPsInput\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n}\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"list\", \"List SFTP endpoints on a Fastly service version\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *ListCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fsterr.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tc.Input.ServiceID = serviceID\n\tc.Input.ServiceVersion = fastly.ToValue(serviceVersion.Number)\n\n\to, err := c.Globals.APIClient.ListSFTPs(context.TODO(), &c.Input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\tif !c.Globals.Verbose() {\n\t\ttw := text.NewTable(out)\n\t\ttw.AddHeader(\"SERVICE\", \"VERSION\", \"NAME\")\n\t\tfor _, sftp := range o {\n\t\t\ttw.AddLine(\n\t\t\t\tfastly.ToValue(sftp.ServiceID),\n\t\t\t\tfastly.ToValue(sftp.ServiceVersion),\n\t\t\t\tfastly.ToValue(sftp.Name),\n\t\t\t)\n\t\t}\n\t\ttw.Print()\n\t\treturn nil\n\t}\n\n\tfmt.Fprintf(out, \"Version: %d\\n\", c.Input.ServiceVersion)\n\tfor i, sftp := range o {\n\t\tfmt.Fprintf(out, \"\\tSFTP %d/%d\\n\", i+1, len(o))\n\t\tfmt.Fprintf(out, \"\\t\\tService ID: %s\\n\", fastly.ToValue(sftp.ServiceID))\n\t\tfmt.Fprintf(out, \"\\t\\tVersion: %d\\n\", fastly.ToValue(sftp.ServiceVersion))\n\t\tfmt.Fprintf(out, \"\\t\\tName: %s\\n\", fastly.ToValue(sftp.Name))\n\t\tfmt.Fprintf(out, \"\\t\\tAddress: %s\\n\", fastly.ToValue(sftp.Address))\n\t\tfmt.Fprintf(out, \"\\t\\tPort: %d\\n\", fastly.ToValue(sftp.Port))\n\t\tfmt.Fprintf(out, \"\\t\\tUser: %s\\n\", fastly.ToValue(sftp.User))\n\t\tfmt.Fprintf(out, \"\\t\\tPassword: %s\\n\", fastly.ToValue(sftp.Password))\n\t\tfmt.Fprintf(out, \"\\t\\tPublic key: %s\\n\", fastly.ToValue(sftp.PublicKey))\n\t\tfmt.Fprintf(out, \"\\t\\tSecret key: %s\\n\", fastly.ToValue(sftp.SecretKey))\n\t\tfmt.Fprintf(out, \"\\t\\tSSH known hosts: %s\\n\", fastly.ToValue(sftp.SSHKnownHosts))\n\t\tfmt.Fprintf(out, \"\\t\\tPath: %s\\n\", fastly.ToValue(sftp.Path))\n\t\tfmt.Fprintf(out, \"\\t\\tPeriod: %d\\n\", fastly.ToValue(sftp.Period))\n\t\tfmt.Fprintf(out, \"\\t\\tGZip level: %d\\n\", fastly.ToValue(sftp.GzipLevel))\n\t\tfmt.Fprintf(out, \"\\t\\tFormat: %s\\n\", fastly.ToValue(sftp.Format))\n\t\tfmt.Fprintf(out, \"\\t\\tFormat version: %d\\n\", fastly.ToValue(sftp.FormatVersion))\n\t\tfmt.Fprintf(out, \"\\t\\tMessage type: %s\\n\", fastly.ToValue(sftp.MessageType))\n\t\tfmt.Fprintf(out, \"\\t\\tResponse condition: %s\\n\", fastly.ToValue(sftp.ResponseCondition))\n\t\tfmt.Fprintf(out, \"\\t\\tTimestamp format: %s\\n\", fastly.ToValue(sftp.TimestampFormat))\n\t\tfmt.Fprintf(out, \"\\t\\tPlacement: %s\\n\", fastly.ToValue(sftp.Placement))\n\t\tfmt.Fprintf(out, \"\\t\\tCompression codec: %s\\n\", fastly.ToValue(sftp.CompressionCodec))\n\t\tfmt.Fprintf(out, \"\\t\\tProcessing region: %s\\n\", fastly.ToValue(sftp.ProcessingRegion))\n\t}\n\tfmt.Fprintln(out)\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/sftp/root.go",
    "content": "package sftp\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"sftp\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manipulate Fastly service version SFTP logging endpoints\")\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/sftp/sftp_integration_test.go",
    "content": "package sftp_test\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/mock\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n\n\troot \"github.com/fastly/cli/pkg/commands/service\"\n\tparent \"github.com/fastly/cli/pkg/commands/service/logging\"\n\tsub \"github.com/fastly/cli/pkg/commands/service/logging/sftp\"\n)\n\nfunc TestSFTPCreate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name log --address example.com --user user --ssh-known-hosts knownHosts() --port 80 --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tCreateSFTPFn:   createSFTPOK,\n\t\t\t},\n\t\t\tWantOutput: \"Created SFTP logging endpoint log (service 123 version 4)\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name log --address example.com --user user --ssh-known-hosts knownHosts() --port 80 --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tCreateSFTPFn:   createSFTPError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name log --address example.com --user anonymous --ssh-known-hosts knownHosts() --port 80 --compression-codec zstd --gzip-level 9 --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t},\n\t\t\tWantError: \"error parsing arguments: the --compression-codec flag is mutually exclusive with the --gzip-level flag\",\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"create\"}, scenarios)\n}\n\nfunc TestSFTPList(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tListSFTPsFn:  listSFTPsOK,\n\t\t\t},\n\t\t\tWantOutput: listSFTPsShortOutput,\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --verbose\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tListSFTPsFn:  listSFTPsOK,\n\t\t\t},\n\t\t\tWantOutput: listSFTPsVerboseOutput,\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 -v\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tListSFTPsFn:  listSFTPsOK,\n\t\t\t},\n\t\t\tWantOutput: listSFTPsVerboseOutput,\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tListSFTPsFn:  listSFTPsError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"list\"}, scenarios)\n}\n\nfunc TestSFTPDescribe(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"--service-id 123 --version 1\",\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tGetSFTPFn:    getSFTPError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tGetSFTPFn:    getSFTPOK,\n\t\t\t},\n\t\t\tWantOutput: describeSFTPOutput,\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"describe\"}, scenarios)\n}\n\nfunc TestSFTPUpdate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"--service-id 123 --version 1 --new-name log\",\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs --new-name log --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tUpdateSFTPFn:   updateSFTPError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs --new-name log --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tUpdateSFTPFn:   updateSFTPOK,\n\t\t\t},\n\t\t\tWantOutput: \"Updated SFTP logging endpoint log (service 123 version 4)\",\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"update\"}, scenarios)\n}\n\nfunc TestSFTPDelete(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"--service-id 123 --version 1\",\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tDeleteSFTPFn:   deleteSFTPError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tDeleteSFTPFn:   deleteSFTPOK,\n\t\t\t},\n\t\t\tWantOutput: \"Deleted SFTP logging endpoint logs (service 123 version 4)\",\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"delete\"}, scenarios)\n}\n\nvar errTest = errors.New(\"fixture error\")\n\nfunc createSFTPOK(_ context.Context, i *fastly.CreateSFTPInput) (*fastly.SFTP, error) {\n\ts := fastly.SFTP{\n\t\tServiceID:        fastly.ToPointer(i.ServiceID),\n\t\tServiceVersion:   fastly.ToPointer(i.ServiceVersion),\n\t\tCompressionCodec: fastly.ToPointer(\"zstd\"),\n\t}\n\n\tif i.Name != nil {\n\t\ts.Name = i.Name\n\t}\n\n\treturn &s, nil\n}\n\nfunc createSFTPError(_ context.Context, _ *fastly.CreateSFTPInput) (*fastly.SFTP, error) {\n\treturn nil, errTest\n}\n\nfunc listSFTPsOK(_ context.Context, i *fastly.ListSFTPsInput) ([]*fastly.SFTP, error) {\n\treturn []*fastly.SFTP{\n\t\t{\n\t\t\tServiceID:         fastly.ToPointer(i.ServiceID),\n\t\t\tServiceVersion:    fastly.ToPointer(i.ServiceVersion),\n\t\t\tName:              fastly.ToPointer(\"logs\"),\n\t\t\tAddress:           fastly.ToPointer(\"127.0.0.1\"),\n\t\t\tPort:              fastly.ToPointer(514),\n\t\t\tUser:              fastly.ToPointer(\"user\"),\n\t\t\tPassword:          fastly.ToPointer(\"password\"),\n\t\t\tPublicKey:         fastly.ToPointer(pgpPublicKey()),\n\t\t\tSecretKey:         fastly.ToPointer(sshPrivateKey()),\n\t\t\tSSHKnownHosts:     fastly.ToPointer(knownHosts()),\n\t\t\tPath:              fastly.ToPointer(\"/logs\"),\n\t\t\tPeriod:            fastly.ToPointer(3600),\n\t\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\t\tFormatVersion:     fastly.ToPointer(2),\n\t\t\tMessageType:       fastly.ToPointer(\"classic\"),\n\t\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\t\tTimestampFormat:   fastly.ToPointer(\"%Y-%m-%dT%H:%M:%S.000\"),\n\t\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\t\tCompressionCodec:  fastly.ToPointer(\"zstd\"),\n\t\t\tProcessingRegion:  fastly.ToPointer(\"us\"),\n\t\t},\n\t\t{\n\t\t\tServiceID:         fastly.ToPointer(i.ServiceID),\n\t\t\tServiceVersion:    fastly.ToPointer(i.ServiceVersion),\n\t\t\tName:              fastly.ToPointer(\"analytics\"),\n\t\t\tAddress:           fastly.ToPointer(\"example.com\"),\n\t\t\tPort:              fastly.ToPointer(123),\n\t\t\tUser:              fastly.ToPointer(\"user\"),\n\t\t\tPassword:          fastly.ToPointer(\"password\"),\n\t\t\tPublicKey:         fastly.ToPointer(pgpPublicKey()),\n\t\t\tSecretKey:         fastly.ToPointer(sshPrivateKey()),\n\t\t\tSSHKnownHosts:     fastly.ToPointer(knownHosts()),\n\t\t\tPath:              fastly.ToPointer(\"/analytics\"),\n\t\t\tPeriod:            fastly.ToPointer(3600),\n\t\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\t\tMessageType:       fastly.ToPointer(\"classic\"),\n\t\t\tFormatVersion:     fastly.ToPointer(2),\n\t\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\t\tTimestampFormat:   fastly.ToPointer(\"%Y-%m-%dT%H:%M:%S.000\"),\n\t\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\t\tCompressionCodec:  fastly.ToPointer(\"zstd\"),\n\t\t\tProcessingRegion:  fastly.ToPointer(\"us\"),\n\t\t},\n\t}, nil\n}\n\nfunc listSFTPsError(_ context.Context, _ *fastly.ListSFTPsInput) ([]*fastly.SFTP, error) {\n\treturn nil, errTest\n}\n\nvar listSFTPsShortOutput = strings.TrimSpace(`\nSERVICE  VERSION  NAME\n123      1        logs\n123      1        analytics\n`) + \"\\n\"\n\nvar listSFTPsVerboseOutput = strings.TrimSpace(`\nFastly API endpoint: https://api.fastly.com\nFastly API token provided via config file (auth: user)\n\nService ID (via --service-id): 123\n\nVersion: 1\n\tSFTP 1/2\n\t\tService ID: 123\n\t\tVersion: 1\n\t\tName: logs\n\t\tAddress: 127.0.0.1\n\t\tPort: 514\n\t\tUser: user\n\t\tPassword: password\n\t\tPublic key: `+pgpPublicKey()+`\n\t\tSecret key: `+sshPrivateKey()+`\n\t\tSSH known hosts: `+knownHosts()+`\n\t\tPath: /logs\n\t\tPeriod: 3600\n\t\tGZip level: 0\n\t\tFormat: %h %l %u %t \"%r\" %>s %b\n\t\tFormat version: 2\n\t\tMessage type: classic\n\t\tResponse condition: Prevent default logging\n\t\tTimestamp format: %Y-%m-%dT%H:%M:%S.000\n\t\tPlacement: none\n\t\tCompression codec: zstd\n\t\tProcessing region: us\n\tSFTP 2/2\n\t\tService ID: 123\n\t\tVersion: 1\n\t\tName: analytics\n\t\tAddress: example.com\n\t\tPort: 123\n\t\tUser: user\n\t\tPassword: password\n\t\tPublic key: `+pgpPublicKey()+`\n\t\tSecret key: `+sshPrivateKey()+`\n\t\tSSH known hosts: `+knownHosts()+`\n\t\tPath: /analytics\n\t\tPeriod: 3600\n\t\tGZip level: 0\n\t\tFormat: %h %l %u %t \"%r\" %>s %b\n\t\tFormat version: 2\n\t\tMessage type: classic\n\t\tResponse condition: Prevent default logging\n\t\tTimestamp format: %Y-%m-%dT%H:%M:%S.000\n\t\tPlacement: none\n\t\tCompression codec: zstd\n\t\tProcessing region: us\n`) + \"\\n\\n\"\n\nfunc getSFTPOK(_ context.Context, i *fastly.GetSFTPInput) (*fastly.SFTP, error) {\n\treturn &fastly.SFTP{\n\t\tServiceID:         fastly.ToPointer(i.ServiceID),\n\t\tServiceVersion:    fastly.ToPointer(i.ServiceVersion),\n\t\tName:              fastly.ToPointer(\"logs\"),\n\t\tAddress:           fastly.ToPointer(\"example.com\"),\n\t\tPort:              fastly.ToPointer(514),\n\t\tUser:              fastly.ToPointer(\"user\"),\n\t\tPassword:          fastly.ToPointer(\"password\"),\n\t\tPublicKey:         fastly.ToPointer(pgpPublicKey()),\n\t\tSecretKey:         fastly.ToPointer(sshPrivateKey()),\n\t\tSSHKnownHosts:     fastly.ToPointer(knownHosts()),\n\t\tPath:              fastly.ToPointer(\"/logs\"),\n\t\tPeriod:            fastly.ToPointer(3600),\n\t\tGzipLevel:         fastly.ToPointer(2),\n\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\tFormatVersion:     fastly.ToPointer(2),\n\t\tMessageType:       fastly.ToPointer(\"classic\"),\n\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\tTimestampFormat:   fastly.ToPointer(\"%Y-%m-%dT%H:%M:%S.000\"),\n\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\tCompressionCodec:  fastly.ToPointer(\"zstd\"),\n\t\tProcessingRegion:  fastly.ToPointer(\"us\"),\n\t}, nil\n}\n\nfunc getSFTPError(_ context.Context, _ *fastly.GetSFTPInput) (*fastly.SFTP, error) {\n\treturn nil, errTest\n}\n\nvar describeSFTPOutput = `\nAddress: example.com\nCompression codec: zstd\nFormat: %h %l %u %t \"%r\" %>s %b\nFormat version: 2\nGZip level: 2\nMessage type: classic\nName: logs\nPassword: password\nPath: /logs\nPeriod: 3600\nPlacement: none\nPort: 514\nProcessing region: us\nPublic key: ` + pgpPublicKey() + `\nResponse condition: Prevent default logging\nSSH known hosts: ` + knownHosts() + `\nSecret key: ` + sshPrivateKey() + `\nService ID: 123\nTimestamp format: %Y-%m-%dT%H:%M:%S.000\nUser: user\nVersion: 1\n`\n\nfunc updateSFTPOK(_ context.Context, i *fastly.UpdateSFTPInput) (*fastly.SFTP, error) {\n\treturn &fastly.SFTP{\n\t\tServiceID:         fastly.ToPointer(i.ServiceID),\n\t\tServiceVersion:    fastly.ToPointer(i.ServiceVersion),\n\t\tName:              fastly.ToPointer(\"log\"),\n\t\tAddress:           fastly.ToPointer(\"example.com\"),\n\t\tPort:              fastly.ToPointer(514),\n\t\tUser:              fastly.ToPointer(\"user\"),\n\t\tPassword:          fastly.ToPointer(\"password\"),\n\t\tPublicKey:         fastly.ToPointer(pgpPublicKey()),\n\t\tSecretKey:         fastly.ToPointer(sshPrivateKey()),\n\t\tSSHKnownHosts:     fastly.ToPointer(knownHosts()),\n\t\tPath:              fastly.ToPointer(\"/logs\"),\n\t\tPeriod:            fastly.ToPointer(3600),\n\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\tFormatVersion:     fastly.ToPointer(2),\n\t\tMessageType:       fastly.ToPointer(\"classic\"),\n\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\tTimestampFormat:   fastly.ToPointer(\"%Y-%m-%dT%H:%M:%S.000\"),\n\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\tCompressionCodec:  fastly.ToPointer(\"zstd\"),\n\t}, nil\n}\n\nfunc updateSFTPError(_ context.Context, _ *fastly.UpdateSFTPInput) (*fastly.SFTP, error) {\n\treturn nil, errTest\n}\n\nfunc deleteSFTPOK(_ context.Context, _ *fastly.DeleteSFTPInput) error {\n\treturn nil\n}\n\nfunc deleteSFTPError(_ context.Context, _ *fastly.DeleteSFTPInput) error {\n\treturn errTest\n}\n\n// knownHosts returns sample known hosts suitable for testing.\nfunc knownHosts() string {\n\treturn strings.TrimSpace(`\nexample.com\n127.0.0.1\n`)\n}\n\n// pgpPublicKey returns a PEM encoded PGP public key suitable for testing.\nfunc pgpPublicKey() string {\n\treturn strings.TrimSpace(`-----BEGIN PGP PUBLIC KEY BLOCK-----\nmQENBFyUD8sBCACyFnB39AuuTygseek+eA4fo0cgwva6/FSjnWq7riouQee8GgQ/\nibXTRyv4iVlwI12GswvMTIy7zNvs1R54i0qvsLr+IZ4GVGJqs6ZJnvQcqe3xPoR4\n8AnBfw90o32r/LuHf6QCJXi+AEu35koNlNAvLJ2B+KACaNB7N0EeWmqpV/1V2k9p\nlDYk+th7LcCuaFNGqKS/PrMnnMqR6VDLCjHhNx4KR79b0Twm/2qp6an3hyNRu8Gn\ndwxpf1/BUu3JWf+LqkN4Y3mbOmSUL3MaJNvyQguUzTfS0P0uGuBDHrJCVkMZCzDB\n89ag55jCPHyGeHBTd02gHMWzsg3WMBWvCsrzABEBAAG0JXRlcnJhZm9ybSAodGVz\ndCkgPHRlc3RAdGVycmFmb3JtLmNvbT6JAU4EEwEIADgWIQSHYyc6Kj9l6HzQsau6\nvFFc9jxV/wUCXJQPywIbAwULCQgHAgYVCgkICwIEFgIDAQIeAQIXgAAKCRC6vFFc\n9jxV/815CAClb32OxV7wG01yF97TzlyTl8TnvjMtoG29Mw4nSyg+mjM3b8N7iXm9\nOLX59fbDAWtBSldSZE22RXd3CvlFOG/EnKBXSjBtEqfyxYSnyOPkMPBYWGL/ApkX\nSvPYJ4LKdvipYToKFh3y9kk2gk1DcDBDyaaHvR+3rv1u3aoy7/s2EltAfDS3ZQIq\n7/cWTLJml/lleeB/Y6rPj8xqeCYhE5ahw9gsV/Mdqatl24V9Tks30iijx0Hhw+Gx\nkATUikMGr2GDVqoIRga5kXI7CzYff4rkc0Twn47fMHHHe/KY9M2yVnMHUXmAZwbG\nM1cMI/NH1DjevCKdGBLcRJlhuLPKF/anuQENBFyUD8sBCADIpd7r7GuPd6n/Ikxe\nu6h7umV6IIPoAm88xCYpTbSZiaK30Svh6Ywra9jfE2KlU9o6Y/art8ip0VJ3m07L\n4RSfSpnzqgSwdjSq5hNour2Fo/BzYhK7yaz2AzVSbe33R0+RYhb4b/6N+bKbjwGF\nftCsqVFMH+PyvYkLbvxyQrHlA9woAZaNThI1ztO5rGSnGUR8xt84eup28WIFKg0K\nUEGUcTzz+8QGAwAra+0ewPXo/AkO+8BvZjDidP417u6gpBHOJ9qYIcO9FxHeqFyu\nYrjlrxowEgXn5wO8xuNz6Vu1vhHGDHGDsRbZF8pv1d5O+0F1G7ttZ2GRRgVBZPwi\nkiyRABEBAAGJATYEGAEIACAWIQSHYyc6Kj9l6HzQsau6vFFc9jxV/wUCXJQPywIb\nDAAKCRC6vFFc9jxV/9YOCACe8qmOSnKQpQfW+PqYOqo3dt7JyweTs3FkD6NT8Zml\ndYy/vkstbTjPpX6aTvUZjkb46BVi7AOneVHpD5GBqvRsZ9iVgDYHaehmLCdKiG5L\n3Tp90NN+QY5WDbsGmsyk6+6ZMYejb4qYfweQeduOj27aavCJdLkCYMoRKfcFYI8c\nFaNmEfKKy/r1PO20NXEG6t9t05K/frHy6ZG8bCNYdpagfFVot47r9JaQqWlTNtIR\n5+zkkSq/eG9BEtRij3a6cTdQbktdBzx2KBeI0PYc1vlZR0LpuFKZqY9vlE6vTGLR\nwMfrTEOvx0NxUM3rpaCgEmuWbB1G1Hu371oyr4srrr+N\n=28dr\n-----END PGP PUBLIC KEY BLOCK-----\n`)\n}\n\n// sshPrivateKey returns a private key suitable for testing.\nfunc sshPrivateKey() string {\n\treturn strings.TrimSpace(`-----BEGIN RSA PRIVATE KEY-----\nMIICXAIBAAKBgQDDo+/YbQ1cZVoRhZ/bbQtPxpycDS5Lty+M8e5swCKpmo0/Eym2\nKrVpEVMoU8eGtwVRvGDR2LtmFKvd86QUWkn2V3lYgY66SNj9n4R/YSDT4/GRkg+4\nEgi++ihpZA+SAIODF4+l1bh/FFu0XUpQLXvJ4Tm0++7bm3tEq+XQr9znrwIDAQAB\nAoGAfDa374e9te47s2hNyLmBNxN5F7Nes4AJVsm8gZuz5k9UYrm+AAU5zQ3M6IvY\n4PWPEQgzyMh8oyF4xaENikaRMhSMfinUmTd979cHbOM6cEKPk28oQcIybsdSzX7G\nZWRh65Ze1DUmBe6R2BUh3Zn4lq9PsqB0TeZeV7Xo/VaIpFECQQDoznQi8HOY8MNM\n7ZDdRhFAkS2X5OGqXOjYdLABGNvJhajgoRsTbgDyJG83qn6yYq7wEHYlMddGZ3ln\nRLnpsThjAkEA1yGXae8WURFEqjp5dMLBxU07apKvEF4zK1OxZ0VjIOJdIpoRBBuL\nIthGBuMrfbF1W5tlmQlj5ik0KhVpBZoHRQJAZP7DdTDZBT1VjHb3RHcUHu2cWOvL\nVkvuG5ErlZ5CIv+gDqr1gw1SzbkuoniNdDfJao3Jo0Mm//z9tuYivRXLvwJBALG3\nWzi0vI/Nnxas5YayGJaf3XSFpj70QnsJUWUJagFRXjTmZyYohsELPpYT9eqIvXUm\no0BQBImvAhu9whtRia0CQCFdDHdNnyyzKH8vC0NsEN65h3Bp2KEPkv8SOV27ZRR2\nxIGqLusk3y+yzbueLZJ117osdB1Owr19fvAHR7vq6Mw=\n-----END RSA PRIVATE KEY-----`)\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/sftp/sftp_test.go",
    "content": "package sftp_test\n\nimport (\n\t\"bytes\"\n\t\"testing\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/service/logging/sftp\"\n\t\"github.com/fastly/cli/pkg/config\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/manifest\"\n\t\"github.com/fastly/cli/pkg/mock\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n)\n\nfunc TestCreateSFTPInput(t *testing.T) {\n\tfor _, testcase := range []struct {\n\t\tname      string\n\t\tcmd       *sftp.CreateCommand\n\t\twant      *fastly.CreateSFTPInput\n\t\twantError string\n\t}{\n\t\t{\n\t\t\tname: \"required values set flag serviceID\",\n\t\t\tcmd:  createCommandRequired(),\n\t\t\twant: &fastly.CreateSFTPInput{\n\t\t\t\tServiceID:      \"123\",\n\t\t\t\tServiceVersion: 4,\n\t\t\t\tName:           fastly.ToPointer(\"log\"),\n\t\t\t\tAddress:        fastly.ToPointer(\"127.0.0.1\"),\n\t\t\t\tUser:           fastly.ToPointer(\"user\"),\n\t\t\t\tSSHKnownHosts:  fastly.ToPointer(knownHosts()),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"all values set flag serviceID\",\n\t\t\tcmd:  createCommandAll(),\n\t\t\twant: &fastly.CreateSFTPInput{\n\t\t\t\tServiceID:         \"123\",\n\t\t\t\tServiceVersion:    4,\n\t\t\t\tName:              fastly.ToPointer(\"log\"),\n\t\t\t\tAddress:           fastly.ToPointer(\"127.0.0.1\"),\n\t\t\t\tPort:              fastly.ToPointer(80),\n\t\t\t\tUser:              fastly.ToPointer(\"user\"),\n\t\t\t\tPassword:          fastly.ToPointer(\"password\"),\n\t\t\t\tPublicKey:         fastly.ToPointer(pgpPublicKey()),\n\t\t\t\tSecretKey:         fastly.ToPointer(sshPrivateKey()),\n\t\t\t\tSSHKnownHosts:     fastly.ToPointer(knownHosts()),\n\t\t\t\tPath:              fastly.ToPointer(\"/log\"),\n\t\t\t\tPeriod:            fastly.ToPointer(3600),\n\t\t\t\tFormatVersion:     fastly.ToPointer(2),\n\t\t\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\t\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\t\t\tMessageType:       fastly.ToPointer(\"classic\"),\n\t\t\t\tTimestampFormat:   fastly.ToPointer(\"%Y-%m-%dT%H:%M:%S.000\"),\n\t\t\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\t\t\tCompressionCodec:  fastly.ToPointer(\"zstd\"),\n\t\t\t\tProcessingRegion:  fastly.ToPointer(\"eu\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:      \"error missing serviceID\",\n\t\t\tcmd:       createCommandMissingServiceID(),\n\t\t\twant:      nil,\n\t\t\twantError: errors.ErrNoServiceID.Error(),\n\t\t},\n\t} {\n\t\tt.Run(testcase.name, func(t *testing.T) {\n\t\t\tif testcase.wantError == errors.ErrNoServiceID.Error() {\n\t\t\t\tt.Setenv(\"FASTLY_SERVICE_ID\", \"\")\n\t\t\t}\n\t\t\tvar bs []byte\n\t\t\tout := bytes.NewBuffer(bs)\n\t\t\tverboseMode := true\n\n\t\t\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\t\t\tAutoCloneFlag:      testcase.cmd.AutoClone,\n\t\t\t\tAPIClient:          testcase.cmd.Globals.APIClient,\n\t\t\t\tManifest:           testcase.cmd.Manifest,\n\t\t\t\tOut:                out,\n\t\t\t\tServiceVersionFlag: testcase.cmd.ServiceVersion,\n\t\t\t\tVerboseMode:        verboseMode,\n\t\t\t})\n\n\t\t\tswitch {\n\t\t\tcase err != nil && testcase.wantError == \"\":\n\t\t\t\tt.Fatalf(\"unexpected error getting service details: %v\", err)\n\t\t\t\treturn\n\t\t\tcase err != nil && testcase.wantError != \"\":\n\t\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\t\treturn\n\t\t\tcase err == nil && testcase.wantError != \"\":\n\t\t\t\tt.Fatalf(\"expected error, have nil (service details: %s, %d)\", serviceID, serviceVersion.Number)\n\t\t\tcase err == nil && testcase.wantError == \"\":\n\t\t\t\thave, err := testcase.cmd.ConstructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\t\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\t\ttestutil.AssertEqual(t, testcase.want, have)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestUpdateSFTPInput(t *testing.T) {\n\tscenarios := []struct {\n\t\tname      string\n\t\tcmd       *sftp.UpdateCommand\n\t\tapi       mock.API\n\t\twant      *fastly.UpdateSFTPInput\n\t\twantError string\n\t}{\n\t\t{\n\t\t\tname: \"all values set flag serviceID\",\n\t\t\tcmd:  updateCommandAll(),\n\t\t\tapi: mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tGetSFTPFn:      getSFTPOK,\n\t\t\t},\n\t\t\twant: &fastly.UpdateSFTPInput{\n\t\t\t\tServiceID:         \"123\",\n\t\t\t\tServiceVersion:    4,\n\t\t\t\tName:              \"log\",\n\t\t\t\tNewName:           fastly.ToPointer(\"new1\"),\n\t\t\t\tAddress:           fastly.ToPointer(\"new2\"),\n\t\t\t\tPort:              fastly.ToPointer(81),\n\t\t\t\tUser:              fastly.ToPointer(\"new3\"),\n\t\t\t\tSSHKnownHosts:     fastly.ToPointer(\"new4\"),\n\t\t\t\tPassword:          fastly.ToPointer(\"new5\"),\n\t\t\t\tPublicKey:         fastly.ToPointer(\"new6\"),\n\t\t\t\tSecretKey:         fastly.ToPointer(\"new7\"),\n\t\t\t\tPath:              fastly.ToPointer(\"new8\"),\n\t\t\t\tPeriod:            fastly.ToPointer(3601),\n\t\t\t\tFormatVersion:     fastly.ToPointer(3),\n\t\t\t\tGzipLevel:         fastly.ToPointer(0),\n\t\t\t\tFormat:            fastly.ToPointer(\"new9\"),\n\t\t\t\tResponseCondition: fastly.ToPointer(\"new10\"),\n\t\t\t\tTimestampFormat:   fastly.ToPointer(\"new11\"),\n\t\t\t\tPlacement:         fastly.ToPointer(\"new12\"),\n\t\t\t\tMessageType:       fastly.ToPointer(\"new13\"),\n\t\t\t\tCompressionCodec:  fastly.ToPointer(\"new14\"),\n\t\t\t\tProcessingRegion:  fastly.ToPointer(\"eu\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"no updates\",\n\t\t\tcmd:  updateCommandNoUpdates(),\n\t\t\tapi: mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tGetSFTPFn:      getSFTPOK,\n\t\t\t},\n\t\t\twant: &fastly.UpdateSFTPInput{\n\t\t\t\tServiceID:      \"123\",\n\t\t\t\tServiceVersion: 4,\n\t\t\t\tName:           \"log\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:      \"error missing serviceID\",\n\t\t\tcmd:       updateCommandMissingServiceID(),\n\t\t\twant:      nil,\n\t\t\twantError: errors.ErrNoServiceID.Error(),\n\t\t},\n\t}\n\tfor testcaseIdx := range scenarios {\n\t\ttestcase := &scenarios[testcaseIdx]\n\t\tt.Run(testcase.name, func(t *testing.T) {\n\t\t\tif testcase.wantError == errors.ErrNoServiceID.Error() {\n\t\t\t\tt.Setenv(\"FASTLY_SERVICE_ID\", \"\")\n\t\t\t}\n\t\t\ttestcase.cmd.Globals.APIClient = testcase.api\n\n\t\t\tvar bs []byte\n\t\t\tout := bytes.NewBuffer(bs)\n\t\t\tverboseMode := true\n\n\t\t\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\t\t\tAutoCloneFlag:      testcase.cmd.AutoClone,\n\t\t\t\tAPIClient:          testcase.api,\n\t\t\t\tManifest:           testcase.cmd.Manifest,\n\t\t\t\tOut:                out,\n\t\t\t\tServiceVersionFlag: testcase.cmd.ServiceVersion,\n\t\t\t\tVerboseMode:        verboseMode,\n\t\t\t})\n\n\t\t\tswitch {\n\t\t\tcase err != nil && testcase.wantError == \"\":\n\t\t\t\tt.Fatalf(\"unexpected error getting service details: %v\", err)\n\t\t\t\treturn\n\t\t\tcase err != nil && testcase.wantError != \"\":\n\t\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\t\treturn\n\t\t\tcase err == nil && testcase.wantError != \"\":\n\t\t\t\tt.Fatalf(\"expected error, have nil (service details: %s, %d)\", serviceID, serviceVersion.Number)\n\t\t\tcase err == nil && testcase.wantError == \"\":\n\t\t\t\thave, err := testcase.cmd.ConstructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\t\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\t\ttestutil.AssertEqual(t, testcase.want, have)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc createCommandRequired() *sftp.CreateCommand {\n\tvar b bytes.Buffer\n\n\tg := global.Data{\n\t\tConfig: config.File{},\n\t\tEnv:    config.Environment{},\n\t\tOutput: &b,\n\t}\n\tg.APIClient, _ = mock.APIClient(mock.API{\n\t\tGetVersionFn:   testutil.GetVersion,\n\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t})(\"token\", \"endpoint\", false)\n\n\treturn &sftp.CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: &g,\n\t\t},\n\t\tManifest: manifest.Data{\n\t\t\tFlag: manifest.Flag{\n\t\t\t\tServiceID: \"123\",\n\t\t\t},\n\t\t},\n\t\tServiceVersion: argparser.OptionalServiceVersion{\n\t\t\tOptionalString: argparser.OptionalString{Value: \"1\"},\n\t\t},\n\t\tAutoClone: argparser.OptionalAutoClone{\n\t\t\tOptionalBool: argparser.OptionalBool{\n\t\t\t\tOptional: argparser.Optional{\n\t\t\t\t\tWasSet: true,\n\t\t\t\t},\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t},\n\t\tEndpointName:  argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"log\"},\n\t\tAddress:       argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"127.0.0.1\"},\n\t\tUser:          argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"user\"},\n\t\tSSHKnownHosts: argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: knownHosts()},\n\t}\n}\n\nfunc createCommandAll() *sftp.CreateCommand {\n\tvar b bytes.Buffer\n\n\tg := global.Data{\n\t\tConfig: config.File{},\n\t\tEnv:    config.Environment{},\n\t\tOutput: &b,\n\t}\n\tg.APIClient, _ = mock.APIClient(mock.API{\n\t\tGetVersionFn:   testutil.GetVersion,\n\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t})(\"token\", \"endpoint\", false)\n\n\treturn &sftp.CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: &g,\n\t\t},\n\t\tManifest: manifest.Data{\n\t\t\tFlag: manifest.Flag{\n\t\t\t\tServiceID: \"123\",\n\t\t\t},\n\t\t},\n\t\tServiceVersion: argparser.OptionalServiceVersion{\n\t\t\tOptionalString: argparser.OptionalString{Value: \"1\"},\n\t\t},\n\t\tAutoClone: argparser.OptionalAutoClone{\n\t\t\tOptionalBool: argparser.OptionalBool{\n\t\t\t\tOptional: argparser.Optional{\n\t\t\t\t\tWasSet: true,\n\t\t\t\t},\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t},\n\t\tEndpointName:      argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"log\"},\n\t\tAddress:           argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"127.0.0.1\"},\n\t\tUser:              argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"user\"},\n\t\tSSHKnownHosts:     argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: knownHosts()},\n\t\tPort:              argparser.OptionalInt{Optional: argparser.Optional{WasSet: true}, Value: 80},\n\t\tPassword:          argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"password\"},\n\t\tPublicKey:         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: pgpPublicKey()},\n\t\tSecretKey:         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: sshPrivateKey()},\n\t\tPath:              argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"/log\"},\n\t\tPeriod:            argparser.OptionalInt{Optional: argparser.Optional{WasSet: true}, Value: 3600},\n\t\tFormat:            argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: `%h %l %u %t \"%r\" %>s %b`},\n\t\tFormatVersion:     argparser.OptionalInt{Optional: argparser.Optional{WasSet: true}, Value: 2},\n\t\tMessageType:       argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"classic\"},\n\t\tResponseCondition: argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"Prevent default logging\"},\n\t\tTimestampFormat:   argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"%Y-%m-%dT%H:%M:%S.000\"},\n\t\tPlacement:         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"none\"},\n\t\tCompressionCodec:  argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"zstd\"},\n\t\tProcessingRegion:  argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"eu\"},\n\t}\n}\n\nfunc createCommandMissingServiceID() *sftp.CreateCommand {\n\tres := createCommandAll()\n\tres.Manifest = manifest.Data{}\n\tres.ServiceVersion = argparser.OptionalServiceVersion{}\n\treturn res\n}\n\nfunc updateCommandNoUpdates() *sftp.UpdateCommand {\n\tvar b bytes.Buffer\n\n\tg := global.Data{\n\t\tConfig: config.File{},\n\t\tEnv:    config.Environment{},\n\t\tOutput: &b,\n\t}\n\n\treturn &sftp.UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: &g,\n\t\t},\n\t\tManifest: manifest.Data{\n\t\t\tFlag: manifest.Flag{\n\t\t\t\tServiceID: \"123\",\n\t\t\t},\n\t\t},\n\t\tEndpointName: \"log\",\n\t\tServiceVersion: argparser.OptionalServiceVersion{\n\t\t\tOptionalString: argparser.OptionalString{Value: \"1\"},\n\t\t},\n\t\tAutoClone: argparser.OptionalAutoClone{\n\t\t\tOptionalBool: argparser.OptionalBool{\n\t\t\t\tOptional: argparser.Optional{\n\t\t\t\t\tWasSet: true,\n\t\t\t\t},\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t},\n\t}\n}\n\nfunc updateCommandAll() *sftp.UpdateCommand {\n\tvar b bytes.Buffer\n\n\tg := global.Data{\n\t\tConfig: config.File{},\n\t\tEnv:    config.Environment{},\n\t\tOutput: &b,\n\t}\n\n\treturn &sftp.UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: &g,\n\t\t},\n\t\tManifest: manifest.Data{\n\t\t\tFlag: manifest.Flag{\n\t\t\t\tServiceID: \"123\",\n\t\t\t},\n\t\t},\n\t\tEndpointName: \"log\",\n\t\tServiceVersion: argparser.OptionalServiceVersion{\n\t\t\tOptionalString: argparser.OptionalString{Value: \"1\"},\n\t\t},\n\t\tAutoClone: argparser.OptionalAutoClone{\n\t\t\tOptionalBool: argparser.OptionalBool{\n\t\t\t\tOptional: argparser.Optional{\n\t\t\t\t\tWasSet: true,\n\t\t\t\t},\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t},\n\t\tNewName:           argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new1\"},\n\t\tAddress:           argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new2\"},\n\t\tUser:              argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new3\"},\n\t\tSSHKnownHosts:     argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new4\"},\n\t\tPort:              argparser.OptionalInt{Optional: argparser.Optional{WasSet: true}, Value: 81},\n\t\tPassword:          argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new5\"},\n\t\tPublicKey:         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new6\"},\n\t\tSecretKey:         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new7\"},\n\t\tPath:              argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new8\"},\n\t\tPeriod:            argparser.OptionalInt{Optional: argparser.Optional{WasSet: true}, Value: 3601},\n\t\tFormat:            argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new9\"},\n\t\tFormatVersion:     argparser.OptionalInt{Optional: argparser.Optional{WasSet: true}, Value: 3},\n\t\tGzipLevel:         argparser.OptionalInt{Optional: argparser.Optional{WasSet: true}, Value: 0},\n\t\tResponseCondition: argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new10\"},\n\t\tTimestampFormat:   argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new11\"},\n\t\tPlacement:         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new12\"},\n\t\tMessageType:       argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new13\"},\n\t\tCompressionCodec:  argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new14\"},\n\t\tProcessingRegion:  argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"eu\"},\n\t}\n}\n\nfunc updateCommandMissingServiceID() *sftp.UpdateCommand {\n\tres := updateCommandAll()\n\tres.Manifest = manifest.Data{}\n\tres.ServiceVersion = argparser.OptionalServiceVersion{}\n\treturn res\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/sftp/update.go",
    "content": "package sftp\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/service/logging/logflags\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/manifest\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// UpdateCommand calls the Fastly API to update an SFTP logging endpoint.\ntype UpdateCommand struct {\n\targparser.Base\n\tManifest manifest.Data\n\n\t// Required.\n\tEndpointName   string\n\tServiceName    argparser.OptionalServiceNameID\n\tServiceVersion argparser.OptionalServiceVersion\n\n\t// Optional.\n\tAddress           argparser.OptionalString\n\tAutoClone         argparser.OptionalAutoClone\n\tCompressionCodec  argparser.OptionalString\n\tFormat            argparser.OptionalString\n\tFormatVersion     argparser.OptionalInt\n\tGzipLevel         argparser.OptionalInt\n\tMessageType       argparser.OptionalString\n\tNewName           argparser.OptionalString\n\tPassword          argparser.OptionalString\n\tPath              argparser.OptionalString\n\tPeriod            argparser.OptionalInt\n\tPlacement         argparser.OptionalString\n\tProcessingRegion  argparser.OptionalString\n\tPort              argparser.OptionalInt\n\tPublicKey         argparser.OptionalString\n\tResponseCondition argparser.OptionalString\n\tSSHKnownHosts     argparser.OptionalString\n\tSecretKey         argparser.OptionalString\n\tTimestampFormat   argparser.OptionalString\n\tUser              argparser.OptionalString\n}\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"update\", \"Update an SFTP logging endpoint on a Fastly service version\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"The name of the SFTP logging object\").Short('n').Required().StringVar(&c.EndpointName)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.ServiceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.AutoClone.Set,\n\t\tDst:    &c.AutoClone.Value,\n\t})\n\tc.CmdClause.Flag(\"address\", \"The hostname or IPv4 address\").Action(c.Address.Set).StringVar(&c.Address.Value)\n\tlogflags.CompressionCodec(c.CmdClause, &c.CompressionCodec)\n\tc.CmdClause.Flag(\"new-name\", \"New name of the SFTP logging object\").Action(c.NewName.Set).StringVar(&c.NewName.Value)\n\tlogflags.Format(c.CmdClause, &c.Format)\n\tlogflags.FormatVersion(c.CmdClause, &c.FormatVersion)\n\tlogflags.GzipLevel(c.CmdClause, &c.GzipLevel)\n\tlogflags.MessageType(c.CmdClause, &c.MessageType)\n\tc.CmdClause.Flag(\"password\", \"The password for the server. If both password and secret_key are passed, secret_key will be used in preference\").Action(c.Password.Set).StringVar(&c.Password.Value)\n\tc.CmdClause.Flag(\"path\", \"The path to upload logs to. The directory must exist on the SFTP server before logs can be saved to it\").Action(c.Path.Set).StringVar(&c.Path.Value)\n\tlogflags.Period(c.CmdClause, &c.Period)\n\tlogflags.Placement(c.CmdClause, &c.Placement)\n\tc.CmdClause.Flag(\"port\", \"The port number\").Action(c.Port.Set).IntVar(&c.Port.Value)\n\tlogflags.ProcessingRegion(c.CmdClause, &c.ProcessingRegion, \"SFTP\")\n\tlogflags.PublicKey(c.CmdClause, &c.PublicKey)\n\tlogflags.ResponseCondition(c.CmdClause, &c.ResponseCondition)\n\tc.CmdClause.Flag(\"secret-key\", \"The SSH private key for the server. If both password and secret_key are passed, secret_key will be used in preference\").Action(c.SecretKey.Set).StringVar(&c.SecretKey.Value)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.ServiceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.ServiceName.Value,\n\t})\n\tc.CmdClause.Flag(\"ssh-known-hosts\", \"A list of host keys for all hosts we can connect to over SFTP\").Action(c.SSHKnownHosts.Set).StringVar(&c.SSHKnownHosts.Value)\n\tc.CmdClause.Flag(\"user\", \"The username for the server\").Action(c.User.Set).StringVar(&c.User.Value)\n\tlogflags.TimestampFormat(c.CmdClause, &c.TimestampFormat)\n\treturn &c\n}\n\n// ConstructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *UpdateCommand) ConstructInput(serviceID string, serviceVersion int) (*fastly.UpdateSFTPInput, error) {\n\tinput := fastly.UpdateSFTPInput{\n\t\tServiceID:      serviceID,\n\t\tServiceVersion: serviceVersion,\n\t\tName:           c.EndpointName,\n\t}\n\n\tif c.NewName.WasSet {\n\t\tinput.NewName = &c.NewName.Value\n\t}\n\n\tif c.Address.WasSet {\n\t\tinput.Address = &c.Address.Value\n\t}\n\n\tif c.Port.WasSet {\n\t\tinput.Port = &c.Port.Value\n\t}\n\n\tif c.Password.WasSet {\n\t\tinput.Password = &c.Password.Value\n\t}\n\n\tif c.PublicKey.WasSet {\n\t\tinput.PublicKey = &c.PublicKey.Value\n\t}\n\n\tif c.SecretKey.WasSet {\n\t\tinput.SecretKey = &c.SecretKey.Value\n\t}\n\n\tif c.SSHKnownHosts.WasSet {\n\t\tinput.SSHKnownHosts = &c.SSHKnownHosts.Value\n\t}\n\n\tif c.User.WasSet {\n\t\tinput.User = &c.User.Value\n\t}\n\n\tif c.Path.WasSet {\n\t\tinput.Path = &c.Path.Value\n\t}\n\n\tif c.Period.WasSet {\n\t\tinput.Period = &c.Period.Value\n\t}\n\n\tif c.Format.WasSet {\n\t\tinput.Format = fastly.ToPointer(argparser.Content(c.Format.Value))\n\t}\n\n\tif c.FormatVersion.WasSet {\n\t\tinput.FormatVersion = &c.FormatVersion.Value\n\t}\n\n\tif c.GzipLevel.WasSet {\n\t\tinput.GzipLevel = &c.GzipLevel.Value\n\t}\n\n\tif c.MessageType.WasSet {\n\t\tinput.MessageType = &c.MessageType.Value\n\t}\n\n\tif c.ResponseCondition.WasSet {\n\t\tinput.ResponseCondition = &c.ResponseCondition.Value\n\t}\n\n\tif c.TimestampFormat.WasSet {\n\t\tinput.TimestampFormat = &c.TimestampFormat.Value\n\t}\n\n\tif c.Placement.WasSet {\n\t\tinput.Placement = &c.Placement.Value\n\t}\n\n\tif c.CompressionCodec.WasSet {\n\t\tinput.CompressionCodec = &c.CompressionCodec.Value\n\t}\n\n\tif c.ProcessingRegion.WasSet {\n\t\tinput.ProcessingRegion = &c.ProcessingRegion.Value\n\t}\n\n\treturn &input, nil\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *UpdateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(false),\n\t\tLocked:             optional.Of(false),\n\t\tAutoCloneFlag:      c.AutoClone,\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.ServiceName,\n\t\tServiceVersionFlag: c.ServiceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": errors.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tinput, err := c.ConstructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tsftp, err := c.Globals.APIClient.UpdateSFTP(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\ttext.Success(\n\t\tout,\n\t\t\"Updated SFTP logging endpoint %s (service %s version %d)\",\n\t\tfastly.ToValue(sftp.Name),\n\t\tfastly.ToValue(sftp.ServiceID),\n\t\tfastly.ToValue(sftp.ServiceVersion),\n\t)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/splunk/create.go",
    "content": "package splunk\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/service/logging/logflags\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/manifest\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// CreateCommand calls the Fastly API to create a Splunk logging endpoint.\ntype CreateCommand struct {\n\targparser.Base\n\tManifest manifest.Data\n\n\t// Required.\n\tServiceName    argparser.OptionalServiceNameID\n\tServiceVersion argparser.OptionalServiceVersion\n\n\t// Optional.\n\tAutoClone         argparser.OptionalAutoClone\n\tEndpointName      argparser.OptionalString // Can't shadow argparser.Base method Name().\n\tFormat            argparser.OptionalString\n\tFormatVersion     argparser.OptionalInt\n\tPlacement         argparser.OptionalString\n\tProcessingRegion  argparser.OptionalString\n\tResponseCondition argparser.OptionalString\n\tTimestampFormat   argparser.OptionalString\n\tTLSCACert         argparser.OptionalString\n\tTLSClientCert     argparser.OptionalString\n\tTLSClientKey      argparser.OptionalString\n\tTLSHostname       argparser.OptionalString\n\tToken             argparser.OptionalString\n\tURL               argparser.OptionalString\n}\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"create\", \"Create a Splunk logging endpoint on a Fastly service version\").Alias(\"add\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"The name of the Splunk logging object. Used as a primary key for API access\").Short('n').Action(c.EndpointName.Set).StringVar(&c.EndpointName.Value)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.ServiceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.CmdClause.Flag(\"auth-token\", \"A Splunk token for use in posting logs over HTTP to your collector\").Action(c.Token.Set).StringVar(&c.Token.Value)\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.AutoClone.Set,\n\t\tDst:    &c.AutoClone.Value,\n\t})\n\tlogflags.Format(c.CmdClause, &c.Format)\n\tlogflags.FormatVersion(c.CmdClause, &c.FormatVersion)\n\tlogflags.Placement(c.CmdClause, &c.Placement)\n\tlogflags.ProcessingRegion(c.CmdClause, &c.ProcessingRegion, \"Splunk\")\n\tlogflags.ResponseCondition(c.CmdClause, &c.ResponseCondition)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.ServiceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.ServiceName.Value,\n\t})\n\tlogflags.TLSCACert(c.CmdClause, &c.TLSCACert)\n\tlogflags.TLSClientCert(c.CmdClause, &c.TLSClientCert)\n\tlogflags.TLSClientKey(c.CmdClause, &c.TLSClientKey)\n\tlogflags.TLSHostname(c.CmdClause, &c.TLSHostname)\n\tc.CmdClause.Flag(\"url\", \"The URL to POST to\").Action(c.URL.Set).StringVar(&c.URL.Value)\n\treturn &c\n}\n\n// ConstructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *CreateCommand) ConstructInput(serviceID string, serviceVersion int) (*fastly.CreateSplunkInput, error) {\n\tvar input fastly.CreateSplunkInput\n\n\tinput.ServiceID = serviceID\n\tinput.ServiceVersion = serviceVersion\n\tif c.EndpointName.WasSet {\n\t\tinput.Name = &c.EndpointName.Value\n\t}\n\tif c.URL.WasSet {\n\t\tinput.URL = &c.URL.Value\n\t}\n\n\tif c.TLSHostname.WasSet {\n\t\tinput.TLSHostname = &c.TLSHostname.Value\n\t}\n\n\tif c.TLSCACert.WasSet {\n\t\tinput.TLSCACert = &c.TLSCACert.Value\n\t}\n\n\tif c.TLSClientCert.WasSet {\n\t\tinput.TLSClientCert = &c.TLSClientCert.Value\n\t}\n\n\tif c.TLSClientKey.WasSet {\n\t\tinput.TLSClientKey = &c.TLSClientKey.Value\n\t}\n\n\tif c.Format.WasSet {\n\t\tinput.Format = fastly.ToPointer(argparser.Content(c.Format.Value))\n\t}\n\n\tif c.FormatVersion.WasSet {\n\t\tinput.FormatVersion = &c.FormatVersion.Value\n\t}\n\n\tif c.ResponseCondition.WasSet {\n\t\tinput.ResponseCondition = &c.ResponseCondition.Value\n\t}\n\n\tif c.Token.WasSet {\n\t\tinput.Token = &c.Token.Value\n\t}\n\n\tif c.Placement.WasSet {\n\t\tinput.Placement = &c.Placement.Value\n\t}\n\n\tif c.ProcessingRegion.WasSet {\n\t\tinput.ProcessingRegion = &c.ProcessingRegion.Value\n\t}\n\n\treturn &input, nil\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *CreateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(false),\n\t\tLocked:             optional.Of(false),\n\t\tAutoCloneFlag:      c.AutoClone,\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.ServiceName,\n\t\tServiceVersionFlag: c.ServiceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": errors.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tinput, err := c.ConstructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\td, err := c.Globals.APIClient.CreateSplunk(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\ttext.Success(\n\t\tout,\n\t\t\"Created Splunk logging endpoint %s (service %s version %d)\",\n\t\tfastly.ToValue(d.Name),\n\t\tfastly.ToValue(d.ServiceID),\n\t\tfastly.ToValue(d.ServiceVersion),\n\t)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/splunk/delete.go",
    "content": "package splunk\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DeleteCommand calls the Fastly API to delete a Splunk logging endpoint.\ntype DeleteCommand struct {\n\targparser.Base\n\tInput          fastly.DeleteSplunkInput\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n\tautoClone      argparser.OptionalAutoClone\n}\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"delete\", \"Delete a Splunk logging endpoint on a Fastly service version\").Alias(\"remove\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"The name of the Splunk logging object\").Short('n').Required().StringVar(&c.Input.Name)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.autoClone.Set,\n\t\tDst:    &c.autoClone.Value,\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DeleteCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(false),\n\t\tLocked:             optional.Of(false),\n\t\tAutoCloneFlag:      c.autoClone,\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": errors.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tc.Input.ServiceID = serviceID\n\tc.Input.ServiceVersion = fastly.ToValue(serviceVersion.Number)\n\n\tif err := c.Globals.APIClient.DeleteSplunk(context.TODO(), &c.Input); err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Deleted Splunk logging endpoint %s (service %s version %d)\", c.Input.Name, c.Input.ServiceID, c.Input.ServiceVersion)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/splunk/describe.go",
    "content": "package splunk\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DescribeCommand calls the Fastly API to describe a Splunk logging endpoint.\ntype DescribeCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tInput          fastly.GetSplunkInput\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n}\n\n// NewDescribeCommand returns a usable command registered under the parent.\nfunc NewDescribeCommand(parent argparser.Registerer, g *global.Data) *DescribeCommand {\n\tc := DescribeCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"describe\", \"Show detailed information about a Splunk logging endpoint on a Fastly service version\").Alias(\"get\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"The name of the Splunk logging object\").Short('n').Required().StringVar(&c.Input.Name)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DescribeCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fsterr.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tc.Input.ServiceID = serviceID\n\tc.Input.ServiceVersion = fastly.ToValue(serviceVersion.Number)\n\n\to, err := c.Globals.APIClient.GetSplunk(context.TODO(), &c.Input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\tlines := text.Lines{\n\t\t\"Format version\":         fastly.ToValue(o.FormatVersion),\n\t\t\"Format\":                 fastly.ToValue(o.Format),\n\t\t\"Name\":                   fastly.ToValue(o.Name),\n\t\t\"Placement\":              fastly.ToValue(o.Placement),\n\t\t\"Processing region\":      fastly.ToValue(o.ProcessingRegion),\n\t\t\"Response condition\":     fastly.ToValue(o.ResponseCondition),\n\t\t\"TLS CA certificate\":     fastly.ToValue(o.TLSCACert),\n\t\t\"TLS client certificate\": fastly.ToValue(o.TLSClientCert),\n\t\t\"TLS client key\":         fastly.ToValue(o.TLSClientKey),\n\t\t\"TLS hostname\":           fastly.ToValue(o.TLSHostname),\n\t\t\"Token\":                  fastly.ToValue(o.Token),\n\t\t\"URL\":                    fastly.ToValue(o.URL),\n\t\t\"Version\":                fastly.ToValue(o.ServiceVersion),\n\t}\n\tif !c.Globals.Verbose() {\n\t\tlines[\"Service ID\"] = fastly.ToValue(o.ServiceID)\n\t}\n\ttext.PrintLines(out, lines)\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/splunk/doc.go",
    "content": "// Package splunk contains commands to inspect and manipulate Fastly service Splunk\n// logging endpoints.\npackage splunk\n"
  },
  {
    "path": "pkg/commands/service/logging/splunk/list.go",
    "content": "package splunk\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// ListCommand calls the Fastly API to list Splunk logging endpoints.\ntype ListCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tInput          fastly.ListSplunksInput\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n}\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"list\", \"List Splunk endpoints on a Fastly service version\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *ListCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fsterr.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tc.Input.ServiceID = serviceID\n\tc.Input.ServiceVersion = fastly.ToValue(serviceVersion.Number)\n\n\to, err := c.Globals.APIClient.ListSplunks(context.TODO(), &c.Input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\tif !c.Globals.Verbose() {\n\t\ttw := text.NewTable(out)\n\t\ttw.AddHeader(\"SERVICE\", \"VERSION\", \"NAME\")\n\t\tfor _, splunk := range o {\n\t\t\ttw.AddLine(\n\t\t\t\tfastly.ToValue(splunk.ServiceID),\n\t\t\t\tfastly.ToValue(splunk.ServiceVersion),\n\t\t\t\tfastly.ToValue(splunk.Name),\n\t\t\t)\n\t\t}\n\t\ttw.Print()\n\t\treturn nil\n\t}\n\n\tfmt.Fprintf(out, \"Version: %d\\n\", c.Input.ServiceVersion)\n\tfor i, splunk := range o {\n\t\tfmt.Fprintf(out, \"\\tSplunk %d/%d\\n\", i+1, len(o))\n\t\tfmt.Fprintf(out, \"\\t\\tService ID: %s\\n\", fastly.ToValue(splunk.ServiceID))\n\t\tfmt.Fprintf(out, \"\\t\\tVersion: %d\\n\", fastly.ToValue(splunk.ServiceVersion))\n\t\tfmt.Fprintf(out, \"\\t\\tName: %s\\n\", fastly.ToValue(splunk.Name))\n\t\tfmt.Fprintf(out, \"\\t\\tURL: %s\\n\", fastly.ToValue(splunk.URL))\n\t\tfmt.Fprintf(out, \"\\t\\tToken: %s\\n\", fastly.ToValue(splunk.Token))\n\t\tfmt.Fprintf(out, \"\\t\\tTLS CA certificate: %s\\n\", fastly.ToValue(splunk.TLSCACert))\n\t\tfmt.Fprintf(out, \"\\t\\tTLS hostname: %s\\n\", fastly.ToValue(splunk.TLSHostname))\n\t\tfmt.Fprintf(out, \"\\t\\tTLS client certificate: %s\\n\", fastly.ToValue(splunk.TLSClientCert))\n\t\tfmt.Fprintf(out, \"\\t\\tTLS client key: %s\\n\", fastly.ToValue(splunk.TLSClientKey))\n\t\tfmt.Fprintf(out, \"\\t\\tFormat: %s\\n\", fastly.ToValue(splunk.Format))\n\t\tfmt.Fprintf(out, \"\\t\\tFormat version: %d\\n\", fastly.ToValue(splunk.FormatVersion))\n\t\tfmt.Fprintf(out, \"\\t\\tResponse condition: %s\\n\", fastly.ToValue(splunk.ResponseCondition))\n\t\tfmt.Fprintf(out, \"\\t\\tPlacement: %s\\n\", fastly.ToValue(splunk.Placement))\n\t\tfmt.Fprintf(out, \"\\t\\tProcessing region: %s\\n\", fastly.ToValue(splunk.ProcessingRegion))\n\t}\n\tfmt.Fprintln(out)\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/splunk/root.go",
    "content": "package splunk\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"splunk\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manipulate Fastly service version Splunk logging endpoints\")\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/splunk/splunk_integration_test.go",
    "content": "package splunk_test\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\troot \"github.com/fastly/cli/pkg/commands/service\"\n\tparent \"github.com/fastly/cli/pkg/commands/service/logging\"\n\tsub \"github.com/fastly/cli/pkg/commands/service/logging/splunk\"\n\t\"github.com/fastly/cli/pkg/mock\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n)\n\nfunc TestSplunkCreate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name log --url example.com --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tCreateSplunkFn: createSplunkOK,\n\t\t\t},\n\t\t\tWantOutput: \"Created Splunk logging endpoint log (service 123 version 4)\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name log --url example.com --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tCreateSplunkFn: createSplunkError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"create\"}, scenarios)\n}\n\nfunc TestSplunkList(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:  testutil.GetVersion,\n\t\t\t\tListSplunksFn: listSplunksOK,\n\t\t\t},\n\t\t\tWantOutput: listSplunksShortOutput,\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --verbose\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:  testutil.GetVersion,\n\t\t\t\tListSplunksFn: listSplunksOK,\n\t\t\t},\n\t\t\tWantOutput: listSplunksVerboseOutput,\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 -v\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:  testutil.GetVersion,\n\t\t\t\tListSplunksFn: listSplunksOK,\n\t\t\t},\n\t\t\tWantOutput: listSplunksVerboseOutput,\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:  testutil.GetVersion,\n\t\t\t\tListSplunksFn: listSplunksError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"list\"}, scenarios)\n}\n\nfunc TestSplunkDescribe(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"--service-id 123 --version 1\",\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tGetSplunkFn:  getSplunkError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tGetSplunkFn:  getSplunkOK,\n\t\t\t},\n\t\t\tWantOutput: describeSplunkOutput,\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"describe\"}, scenarios)\n}\n\nfunc TestSplunkUpdate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"--service-id 123 --version 1 --new-name log\",\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs --new-name log --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tUpdateSplunkFn: updateSplunkError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs --new-name log --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tUpdateSplunkFn: updateSplunkOK,\n\t\t\t},\n\t\t\tWantOutput: \"Updated Splunk logging endpoint log (service 123 version 4)\",\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"update\"}, scenarios)\n}\n\nfunc TestSplunkDelete(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"--service-id 123 --version 1\",\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tDeleteSplunkFn: deleteSplunkError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tDeleteSplunkFn: deleteSplunkOK,\n\t\t\t},\n\t\t\tWantOutput: \"Deleted Splunk logging endpoint logs (service 123 version 4)\",\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"delete\"}, scenarios)\n}\n\nvar errTest = errors.New(\"fixture error\")\n\nfunc createSplunkOK(_ context.Context, i *fastly.CreateSplunkInput) (*fastly.Splunk, error) {\n\treturn &fastly.Splunk{\n\t\tServiceID:      fastly.ToPointer(i.ServiceID),\n\t\tServiceVersion: fastly.ToPointer(i.ServiceVersion),\n\t\tName:           i.Name,\n\t}, nil\n}\n\nfunc createSplunkError(_ context.Context, _ *fastly.CreateSplunkInput) (*fastly.Splunk, error) {\n\treturn nil, errTest\n}\n\nfunc listSplunksOK(_ context.Context, i *fastly.ListSplunksInput) ([]*fastly.Splunk, error) {\n\treturn []*fastly.Splunk{\n\t\t{\n\t\t\tServiceID:         fastly.ToPointer(i.ServiceID),\n\t\t\tServiceVersion:    fastly.ToPointer(i.ServiceVersion),\n\t\t\tName:              fastly.ToPointer(\"logs\"),\n\t\t\tURL:               fastly.ToPointer(\"example.com\"),\n\t\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\t\tFormatVersion:     fastly.ToPointer(2),\n\t\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\t\tToken:             fastly.ToPointer(\"tkn\"),\n\t\t\tTLSCACert:         fastly.ToPointer(\"-----BEGIN CERTIFICATE-----foo\"),\n\t\t\tTLSHostname:       fastly.ToPointer(\"example.com\"),\n\t\t\tTLSClientCert:     fastly.ToPointer(\"-----BEGIN CERTIFICATE-----bar\"),\n\t\t\tTLSClientKey:      fastly.ToPointer(\"-----BEGIN PRIVATE KEY-----bar\"),\n\t\t\tProcessingRegion:  fastly.ToPointer(\"us\"),\n\t\t},\n\t\t{\n\t\t\tServiceID:         fastly.ToPointer(i.ServiceID),\n\t\t\tServiceVersion:    fastly.ToPointer(i.ServiceVersion),\n\t\t\tName:              fastly.ToPointer(\"analytics\"),\n\t\t\tURL:               fastly.ToPointer(\"127.0.0.1\"),\n\t\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\t\tFormatVersion:     fastly.ToPointer(2),\n\t\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\t\tToken:             fastly.ToPointer(\"tkn1\"),\n\t\t\tTLSCACert:         fastly.ToPointer(\"-----BEGIN CERTIFICATE-----foo\"),\n\t\t\tTLSHostname:       fastly.ToPointer(\"example.com\"),\n\t\t\tTLSClientCert:     fastly.ToPointer(\"-----BEGIN CERTIFICATE-----qux\"),\n\t\t\tTLSClientKey:      fastly.ToPointer(\"-----BEGIN PRIVATE KEY-----qux\"),\n\t\t\tProcessingRegion:  fastly.ToPointer(\"us\"),\n\t\t},\n\t}, nil\n}\n\nfunc listSplunksError(_ context.Context, _ *fastly.ListSplunksInput) ([]*fastly.Splunk, error) {\n\treturn nil, errTest\n}\n\nvar listSplunksShortOutput = strings.TrimSpace(`\nSERVICE  VERSION  NAME\n123      1        logs\n123      1        analytics\n`) + \"\\n\"\n\nvar listSplunksVerboseOutput = strings.TrimSpace(`\nFastly API endpoint: https://api.fastly.com\nFastly API token provided via config file (auth: user)\n\nService ID (via --service-id): 123\n\nVersion: 1\n\tSplunk 1/2\n\t\tService ID: 123\n\t\tVersion: 1\n\t\tName: logs\n\t\tURL: example.com\n\t\tToken: tkn\n\t\tTLS CA certificate: -----BEGIN CERTIFICATE-----foo\n\t\tTLS hostname: example.com\n\t\tTLS client certificate: -----BEGIN CERTIFICATE-----bar\n\t\tTLS client key: -----BEGIN PRIVATE KEY-----bar\n\t\tFormat: %h %l %u %t \"%r\" %>s %b\n\t\tFormat version: 2\n\t\tResponse condition: Prevent default logging\n\t\tPlacement: none\n\t\tProcessing region: us\n\tSplunk 2/2\n\t\tService ID: 123\n\t\tVersion: 1\n\t\tName: analytics\n\t\tURL: 127.0.0.1\n\t\tToken: tkn1\n\t\tTLS CA certificate: -----BEGIN CERTIFICATE-----foo\n\t\tTLS hostname: example.com\n\t\tTLS client certificate: -----BEGIN CERTIFICATE-----qux\n\t\tTLS client key: -----BEGIN PRIVATE KEY-----qux\n\t\tFormat: %h %l %u %t \"%r\" %>s %b\n\t\tFormat version: 2\n\t\tResponse condition: Prevent default logging\n\t\tPlacement: none\n\t\tProcessing region: us\n`) + \"\\n\\n\"\n\nfunc getSplunkOK(_ context.Context, i *fastly.GetSplunkInput) (*fastly.Splunk, error) {\n\treturn &fastly.Splunk{\n\t\tServiceID:         fastly.ToPointer(i.ServiceID),\n\t\tServiceVersion:    fastly.ToPointer(i.ServiceVersion),\n\t\tName:              fastly.ToPointer(\"logs\"),\n\t\tURL:               fastly.ToPointer(\"example.com\"),\n\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\tFormatVersion:     fastly.ToPointer(2),\n\t\tTLSCACert:         fastly.ToPointer(\"-----BEGIN CERTIFICATE-----foo\"),\n\t\tTLSHostname:       fastly.ToPointer(\"example.com\"),\n\t\tTLSClientCert:     fastly.ToPointer(\"-----BEGIN CERTIFICATE-----bar\"),\n\t\tTLSClientKey:      fastly.ToPointer(\"-----BEGIN PRIVATE KEY-----bar\"),\n\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\tToken:             fastly.ToPointer(\"tkn\"),\n\t\tProcessingRegion:  fastly.ToPointer(\"us\"),\n\t}, nil\n}\n\nfunc getSplunkError(_ context.Context, _ *fastly.GetSplunkInput) (*fastly.Splunk, error) {\n\treturn nil, errTest\n}\n\nvar describeSplunkOutput = \"\\n\" + strings.TrimSpace(`\nFormat: %h %l %u %t \"%r\" %>s %b\nFormat version: 2\nName: logs\nPlacement: none\nProcessing region: us\nResponse condition: Prevent default logging\nService ID: 123\nTLS CA certificate: -----BEGIN CERTIFICATE-----foo\nTLS client certificate: -----BEGIN CERTIFICATE-----bar\nTLS client key: -----BEGIN PRIVATE KEY-----bar\nTLS hostname: example.com\nToken: tkn\nURL: example.com\nVersion: 1\n`) + \"\\n\"\n\nfunc updateSplunkOK(_ context.Context, i *fastly.UpdateSplunkInput) (*fastly.Splunk, error) {\n\treturn &fastly.Splunk{\n\t\tServiceID:         fastly.ToPointer(i.ServiceID),\n\t\tServiceVersion:    fastly.ToPointer(i.ServiceVersion),\n\t\tName:              fastly.ToPointer(\"log\"),\n\t\tURL:               fastly.ToPointer(\"example.com\"),\n\t\tToken:             fastly.ToPointer(\"tkn\"),\n\t\tTLSCACert:         fastly.ToPointer(\"-----BEGIN CERTIFICATE-----foo\"),\n\t\tTLSHostname:       fastly.ToPointer(\"example.com\"),\n\t\tTLSClientCert:     fastly.ToPointer(\"-----BEGIN CERTIFICATE-----bar\"),\n\t\tTLSClientKey:      fastly.ToPointer(\"-----BEGIN PRIVATE KEY-----bar\"),\n\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\tFormatVersion:     fastly.ToPointer(2),\n\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t}, nil\n}\n\nfunc updateSplunkError(_ context.Context, _ *fastly.UpdateSplunkInput) (*fastly.Splunk, error) {\n\treturn nil, errTest\n}\n\nfunc deleteSplunkOK(_ context.Context, _ *fastly.DeleteSplunkInput) error {\n\treturn nil\n}\n\nfunc deleteSplunkError(_ context.Context, _ *fastly.DeleteSplunkInput) error {\n\treturn errTest\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/splunk/splunk_test.go",
    "content": "package splunk_test\n\nimport (\n\t\"bytes\"\n\t\"testing\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/service/logging/splunk\"\n\t\"github.com/fastly/cli/pkg/config\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/manifest\"\n\t\"github.com/fastly/cli/pkg/mock\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n)\n\nfunc TestCreateSplunkInput(t *testing.T) {\n\tfor _, testcase := range []struct {\n\t\tname      string\n\t\tcmd       *splunk.CreateCommand\n\t\twant      *fastly.CreateSplunkInput\n\t\twantError string\n\t}{\n\t\t{\n\t\t\tname: \"required values set flag serviceID\",\n\t\t\tcmd:  createCommandRequired(),\n\t\t\twant: &fastly.CreateSplunkInput{\n\t\t\t\tServiceID:      \"123\",\n\t\t\t\tServiceVersion: 4,\n\t\t\t\tName:           fastly.ToPointer(\"log\"),\n\t\t\t\tURL:            fastly.ToPointer(\"example.com\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"all values set flag serviceID\",\n\t\t\tcmd:  createCommandAll(),\n\t\t\twant: &fastly.CreateSplunkInput{\n\t\t\t\tServiceID:         \"123\",\n\t\t\t\tServiceVersion:    4,\n\t\t\t\tName:              fastly.ToPointer(\"log\"),\n\t\t\t\tURL:               fastly.ToPointer(\"example.com\"),\n\t\t\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\t\t\tFormatVersion:     fastly.ToPointer(2),\n\t\t\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\t\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\t\t\tToken:             fastly.ToPointer(\"tkn\"),\n\t\t\t\tTLSCACert:         fastly.ToPointer(\"-----BEGIN CERTIFICATE-----foo\"),\n\t\t\t\tTLSHostname:       fastly.ToPointer(\"example.com\"),\n\t\t\t\tTLSClientCert:     fastly.ToPointer(\"-----BEGIN CERTIFICATE-----bar\"),\n\t\t\t\tTLSClientKey:      fastly.ToPointer(\"-----BEGIN PRIVATE KEY-----bar\"),\n\t\t\t\tProcessingRegion:  fastly.ToPointer(\"eu\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:      \"error missing serviceID\",\n\t\t\tcmd:       createCommandMissingServiceID(),\n\t\t\twant:      nil,\n\t\t\twantError: errors.ErrNoServiceID.Error(),\n\t\t},\n\t} {\n\t\tt.Run(testcase.name, func(t *testing.T) {\n\t\t\tif testcase.wantError == errors.ErrNoServiceID.Error() {\n\t\t\t\tt.Setenv(\"FASTLY_SERVICE_ID\", \"\")\n\t\t\t}\n\t\t\tvar bs []byte\n\t\t\tout := bytes.NewBuffer(bs)\n\t\t\tverboseMode := true\n\n\t\t\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\t\t\tAutoCloneFlag:      testcase.cmd.AutoClone,\n\t\t\t\tAPIClient:          testcase.cmd.Globals.APIClient,\n\t\t\t\tManifest:           testcase.cmd.Manifest,\n\t\t\t\tOut:                out,\n\t\t\t\tServiceVersionFlag: testcase.cmd.ServiceVersion,\n\t\t\t\tVerboseMode:        verboseMode,\n\t\t\t})\n\n\t\t\tswitch {\n\t\t\tcase err != nil && testcase.wantError == \"\":\n\t\t\t\tt.Fatalf(\"unexpected error getting service details: %v\", err)\n\t\t\t\treturn\n\t\t\tcase err != nil && testcase.wantError != \"\":\n\t\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\t\treturn\n\t\t\tcase err == nil && testcase.wantError != \"\":\n\t\t\t\tt.Fatalf(\"expected error, have nil (service details: %s, %d)\", serviceID, serviceVersion.Number)\n\t\t\tcase err == nil && testcase.wantError == \"\":\n\t\t\t\thave, err := testcase.cmd.ConstructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\t\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\t\ttestutil.AssertEqual(t, testcase.want, have)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestUpdateSplunkInput(t *testing.T) {\n\tscenarios := []struct {\n\t\tname      string\n\t\tcmd       *splunk.UpdateCommand\n\t\tapi       mock.API\n\t\twant      *fastly.UpdateSplunkInput\n\t\twantError string\n\t}{\n\t\t{\n\t\t\tname: \"no updates\",\n\t\t\tcmd:  updateCommandNoUpdates(),\n\t\t\tapi: mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tGetSplunkFn:    getSplunkOK,\n\t\t\t},\n\t\t\twant: &fastly.UpdateSplunkInput{\n\t\t\t\tServiceID:      \"123\",\n\t\t\t\tServiceVersion: 4,\n\t\t\t\tName:           \"log\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"all values set flag serviceID\",\n\t\t\tcmd:  updateCommandAll(),\n\t\t\tapi: mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tGetSplunkFn:    getSplunkOK,\n\t\t\t},\n\t\t\twant: &fastly.UpdateSplunkInput{\n\t\t\t\tServiceID:         \"123\",\n\t\t\t\tServiceVersion:    4,\n\t\t\t\tName:              \"log\",\n\t\t\t\tNewName:           fastly.ToPointer(\"new1\"),\n\t\t\t\tURL:               fastly.ToPointer(\"new2\"),\n\t\t\t\tFormat:            fastly.ToPointer(\"new3\"),\n\t\t\t\tFormatVersion:     fastly.ToPointer(3),\n\t\t\t\tResponseCondition: fastly.ToPointer(\"new4\"),\n\t\t\t\tPlacement:         fastly.ToPointer(\"new5\"),\n\t\t\t\tToken:             fastly.ToPointer(\"new6\"),\n\t\t\t\tTLSCACert:         fastly.ToPointer(\"new7\"),\n\t\t\t\tTLSHostname:       fastly.ToPointer(\"new8\"),\n\t\t\t\tTLSClientCert:     fastly.ToPointer(\"new9\"),\n\t\t\t\tTLSClientKey:      fastly.ToPointer(\"new10\"),\n\t\t\t\tProcessingRegion:  fastly.ToPointer(\"eu\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:      \"error missing serviceID\",\n\t\t\tcmd:       updateCommandMissingServiceID(),\n\t\t\twant:      nil,\n\t\t\twantError: errors.ErrNoServiceID.Error(),\n\t\t},\n\t}\n\tfor testcaseIdx := range scenarios {\n\t\ttestcase := &scenarios[testcaseIdx]\n\t\tt.Run(testcase.name, func(t *testing.T) {\n\t\t\tif testcase.wantError == errors.ErrNoServiceID.Error() {\n\t\t\t\tt.Setenv(\"FASTLY_SERVICE_ID\", \"\")\n\t\t\t}\n\t\t\ttestcase.cmd.Globals.APIClient = testcase.api\n\n\t\t\tvar bs []byte\n\t\t\tout := bytes.NewBuffer(bs)\n\t\t\tverboseMode := true\n\n\t\t\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\t\t\tAutoCloneFlag:      testcase.cmd.AutoClone,\n\t\t\t\tAPIClient:          testcase.api,\n\t\t\t\tManifest:           testcase.cmd.Manifest,\n\t\t\t\tOut:                out,\n\t\t\t\tServiceVersionFlag: testcase.cmd.ServiceVersion,\n\t\t\t\tVerboseMode:        verboseMode,\n\t\t\t})\n\n\t\t\tswitch {\n\t\t\tcase err != nil && testcase.wantError == \"\":\n\t\t\t\tt.Fatalf(\"unexpected error getting service details: %v\", err)\n\t\t\t\treturn\n\t\t\tcase err != nil && testcase.wantError != \"\":\n\t\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\t\treturn\n\t\t\tcase err == nil && testcase.wantError != \"\":\n\t\t\t\tt.Fatalf(\"expected error, have nil (service details: %s, %d)\", serviceID, serviceVersion.Number)\n\t\t\tcase err == nil && testcase.wantError == \"\":\n\t\t\t\thave, err := testcase.cmd.ConstructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\t\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\t\ttestutil.AssertEqual(t, testcase.want, have)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc createCommandRequired() *splunk.CreateCommand {\n\tvar b bytes.Buffer\n\n\tg := global.Data{\n\t\tConfig: config.File{},\n\t\tEnv:    config.Environment{},\n\t\tOutput: &b,\n\t}\n\tg.APIClient, _ = mock.APIClient(mock.API{\n\t\tGetVersionFn:   testutil.GetVersion,\n\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t})(\"token\", \"endpoint\", false)\n\n\treturn &splunk.CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: &g,\n\t\t},\n\t\tManifest: manifest.Data{\n\t\t\tFlag: manifest.Flag{\n\t\t\t\tServiceID: \"123\",\n\t\t\t},\n\t\t},\n\t\tServiceVersion: argparser.OptionalServiceVersion{\n\t\t\tOptionalString: argparser.OptionalString{Value: \"1\"},\n\t\t},\n\t\tAutoClone: argparser.OptionalAutoClone{\n\t\t\tOptionalBool: argparser.OptionalBool{\n\t\t\t\tOptional: argparser.Optional{\n\t\t\t\t\tWasSet: true,\n\t\t\t\t},\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t},\n\t\tEndpointName: argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"log\"},\n\t\tURL:          argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"example.com\"},\n\t}\n}\n\nfunc createCommandAll() *splunk.CreateCommand {\n\tvar b bytes.Buffer\n\n\tg := global.Data{\n\t\tConfig: config.File{},\n\t\tEnv:    config.Environment{},\n\t\tOutput: &b,\n\t}\n\tg.APIClient, _ = mock.APIClient(mock.API{\n\t\tGetVersionFn:   testutil.GetVersion,\n\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t})(\"token\", \"endpoint\", false)\n\n\treturn &splunk.CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: &g,\n\t\t},\n\t\tManifest: manifest.Data{\n\t\t\tFlag: manifest.Flag{\n\t\t\t\tServiceID: \"123\",\n\t\t\t},\n\t\t},\n\t\tServiceVersion: argparser.OptionalServiceVersion{\n\t\t\tOptionalString: argparser.OptionalString{Value: \"1\"},\n\t\t},\n\t\tAutoClone: argparser.OptionalAutoClone{\n\t\t\tOptionalBool: argparser.OptionalBool{\n\t\t\t\tOptional: argparser.Optional{\n\t\t\t\t\tWasSet: true,\n\t\t\t\t},\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t},\n\t\tEndpointName:      argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"log\"},\n\t\tURL:               argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"example.com\"},\n\t\tFormat:            argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: `%h %l %u %t \"%r\" %>s %b`},\n\t\tFormatVersion:     argparser.OptionalInt{Optional: argparser.Optional{WasSet: true}, Value: 2},\n\t\tTimestampFormat:   argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"%Y-%m-%dT%H:%M:%S.000\"},\n\t\tResponseCondition: argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"Prevent default logging\"},\n\t\tPlacement:         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"none\"},\n\t\tToken:             argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"tkn\"},\n\t\tTLSCACert:         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"-----BEGIN CERTIFICATE-----foo\"},\n\t\tTLSHostname:       argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"example.com\"},\n\t\tTLSClientCert:     argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"-----BEGIN CERTIFICATE-----bar\"},\n\t\tTLSClientKey:      argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"-----BEGIN PRIVATE KEY-----bar\"},\n\t\tProcessingRegion:  argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"eu\"},\n\t}\n}\n\nfunc createCommandMissingServiceID() *splunk.CreateCommand {\n\tres := createCommandAll()\n\tres.Manifest = manifest.Data{}\n\tres.ServiceVersion = argparser.OptionalServiceVersion{}\n\treturn res\n}\n\nfunc updateCommandNoUpdates() *splunk.UpdateCommand {\n\tvar b bytes.Buffer\n\n\tg := global.Data{\n\t\tConfig: config.File{},\n\t\tEnv:    config.Environment{},\n\t\tOutput: &b,\n\t}\n\n\treturn &splunk.UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: &g,\n\t\t},\n\t\tManifest: manifest.Data{\n\t\t\tFlag: manifest.Flag{\n\t\t\t\tServiceID: \"123\",\n\t\t\t},\n\t\t},\n\t\tEndpointName: \"log\",\n\t\tServiceVersion: argparser.OptionalServiceVersion{\n\t\t\tOptionalString: argparser.OptionalString{Value: \"1\"},\n\t\t},\n\t\tAutoClone: argparser.OptionalAutoClone{\n\t\t\tOptionalBool: argparser.OptionalBool{\n\t\t\t\tOptional: argparser.Optional{\n\t\t\t\t\tWasSet: true,\n\t\t\t\t},\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t},\n\t}\n}\n\nfunc updateCommandAll() *splunk.UpdateCommand {\n\tvar b bytes.Buffer\n\n\tg := global.Data{\n\t\tConfig: config.File{},\n\t\tEnv:    config.Environment{},\n\t\tOutput: &b,\n\t}\n\n\treturn &splunk.UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: &g,\n\t\t},\n\t\tManifest: manifest.Data{\n\t\t\tFlag: manifest.Flag{\n\t\t\t\tServiceID: \"123\",\n\t\t\t},\n\t\t},\n\t\tEndpointName: \"log\",\n\t\tServiceVersion: argparser.OptionalServiceVersion{\n\t\t\tOptionalString: argparser.OptionalString{Value: \"1\"},\n\t\t},\n\t\tAutoClone: argparser.OptionalAutoClone{\n\t\t\tOptionalBool: argparser.OptionalBool{\n\t\t\t\tOptional: argparser.Optional{\n\t\t\t\t\tWasSet: true,\n\t\t\t\t},\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t},\n\t\tNewName:           argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new1\"},\n\t\tURL:               argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new2\"},\n\t\tFormat:            argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new3\"},\n\t\tFormatVersion:     argparser.OptionalInt{Optional: argparser.Optional{WasSet: true}, Value: 3},\n\t\tResponseCondition: argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new4\"},\n\t\tPlacement:         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new5\"},\n\t\tToken:             argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new6\"},\n\t\tTLSCACert:         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new7\"},\n\t\tTLSHostname:       argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new8\"},\n\t\tTLSClientCert:     argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new9\"},\n\t\tTLSClientKey:      argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new10\"},\n\t\tProcessingRegion:  argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"eu\"},\n\t}\n}\n\nfunc updateCommandMissingServiceID() *splunk.UpdateCommand {\n\tres := updateCommandAll()\n\tres.Manifest = manifest.Data{}\n\tres.ServiceVersion = argparser.OptionalServiceVersion{}\n\treturn res\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/splunk/update.go",
    "content": "package splunk\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/service/logging/logflags\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/manifest\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// UpdateCommand calls the Fastly API to update a Splunk logging endpoint.\ntype UpdateCommand struct {\n\targparser.Base\n\tManifest manifest.Data\n\n\t// Required.\n\tEndpointName   string // Can't shadow argparser.Base method Name().\n\tServiceName    argparser.OptionalServiceNameID\n\tServiceVersion argparser.OptionalServiceVersion\n\n\t// Optional.\n\tAutoClone         argparser.OptionalAutoClone\n\tFormat            argparser.OptionalString\n\tFormatVersion     argparser.OptionalInt\n\tNewName           argparser.OptionalString\n\tPlacement         argparser.OptionalString\n\tProcessingRegion  argparser.OptionalString\n\tResponseCondition argparser.OptionalString\n\tTLSCACert         argparser.OptionalString\n\tTLSClientCert     argparser.OptionalString\n\tTLSClientKey      argparser.OptionalString\n\tTLSHostname       argparser.OptionalString\n\tToken             argparser.OptionalString\n\tURL               argparser.OptionalString\n}\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"update\", \"Update a Splunk logging endpoint on a Fastly service version\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"The name of the Splunk logging object\").Short('n').Required().StringVar(&c.EndpointName)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.ServiceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.CmdClause.Flag(\"auth-token\", \"\").Action(c.Token.Set).StringVar(&c.Token.Value)\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.AutoClone.Set,\n\t\tDst:    &c.AutoClone.Value,\n\t})\n\tc.CmdClause.Flag(\"new-name\", \"New name of the Splunk logging object\").Action(c.NewName.Set).StringVar(&c.NewName.Value)\n\tlogflags.Format(c.CmdClause, &c.Format)\n\tlogflags.FormatVersion(c.CmdClause, &c.FormatVersion)\n\tc.CmdClause.Flag(\"placement\", \"\tWhere in the generated VCL the logging call should be placed, overriding any format_version default. Can be none or waf_debug. This field is not required and has no default value\").Action(c.Placement.Set).StringVar(&c.Placement.Value)\n\tlogflags.ProcessingRegion(c.CmdClause, &c.ProcessingRegion, \"Splunk\")\n\tlogflags.ResponseCondition(c.CmdClause, &c.ResponseCondition)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.ServiceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.ServiceName.Value,\n\t})\n\tlogflags.TLSCACert(c.CmdClause, &c.TLSCACert)\n\tlogflags.TLSClientCert(c.CmdClause, &c.TLSClientCert)\n\tlogflags.TLSClientKey(c.CmdClause, &c.TLSClientKey)\n\tlogflags.TLSHostname(c.CmdClause, &c.TLSHostname)\n\tc.CmdClause.Flag(\"url\", \"The URL to POST to.\").Action(c.URL.Set).StringVar(&c.URL.Value)\n\treturn &c\n}\n\n// ConstructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *UpdateCommand) ConstructInput(serviceID string, serviceVersion int) (*fastly.UpdateSplunkInput, error) {\n\tinput := fastly.UpdateSplunkInput{\n\t\tServiceID:      serviceID,\n\t\tServiceVersion: serviceVersion,\n\t\tName:           c.EndpointName,\n\t}\n\n\t// Set new values if set by user.\n\tif c.NewName.WasSet {\n\t\tinput.NewName = &c.NewName.Value\n\t}\n\n\tif c.URL.WasSet {\n\t\tinput.URL = &c.URL.Value\n\t}\n\n\tif c.Format.WasSet {\n\t\tinput.Format = fastly.ToPointer(argparser.Content(c.Format.Value))\n\t}\n\n\tif c.FormatVersion.WasSet {\n\t\tinput.FormatVersion = &c.FormatVersion.Value\n\t}\n\n\tif c.ResponseCondition.WasSet {\n\t\tinput.ResponseCondition = &c.ResponseCondition.Value\n\t}\n\n\tif c.Placement.WasSet {\n\t\tinput.Placement = &c.Placement.Value\n\t}\n\n\tif c.ProcessingRegion.WasSet {\n\t\tinput.ProcessingRegion = &c.ProcessingRegion.Value\n\t}\n\n\tif c.Token.WasSet {\n\t\tinput.Token = &c.Token.Value\n\t}\n\n\tif c.TLSCACert.WasSet {\n\t\tinput.TLSCACert = &c.TLSCACert.Value\n\t}\n\n\tif c.TLSHostname.WasSet {\n\t\tinput.TLSHostname = &c.TLSHostname.Value\n\t}\n\n\tif c.TLSClientCert.WasSet {\n\t\tinput.TLSClientCert = &c.TLSClientCert.Value\n\t}\n\n\tif c.TLSClientKey.WasSet {\n\t\tinput.TLSClientKey = &c.TLSClientKey.Value\n\t}\n\n\treturn &input, nil\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *UpdateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(false),\n\t\tLocked:             optional.Of(false),\n\t\tAutoCloneFlag:      c.AutoClone,\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.ServiceName,\n\t\tServiceVersionFlag: c.ServiceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": errors.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tinput, err := c.ConstructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tsplunk, err := c.Globals.APIClient.UpdateSplunk(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\ttext.Success(\n\t\tout,\n\t\t\"Updated Splunk logging endpoint %s (service %s version %d)\",\n\t\tfastly.ToValue(splunk.Name),\n\t\tfastly.ToValue(splunk.ServiceID),\n\t\tfastly.ToValue(splunk.ServiceVersion),\n\t)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/sumologic/create.go",
    "content": "package sumologic\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/service/logging/logflags\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/manifest\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// CreateCommand calls the Fastly API to create a Sumologic logging endpoint.\ntype CreateCommand struct {\n\targparser.Base\n\tManifest manifest.Data\n\n\t// Required.\n\tServiceName    argparser.OptionalServiceNameID\n\tServiceVersion argparser.OptionalServiceVersion\n\n\t// Optional.\n\tAutoClone         argparser.OptionalAutoClone\n\tEndpointName      argparser.OptionalString // Can't shadow argparser.Base method Name().\n\tFormat            argparser.OptionalString\n\tFormatVersion     argparser.OptionalInt\n\tMessageType       argparser.OptionalString\n\tPlacement         argparser.OptionalString\n\tProcessingRegion  argparser.OptionalString\n\tResponseCondition argparser.OptionalString\n\tURL               argparser.OptionalString\n}\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"create\", \"Create a Sumologic logging endpoint on a Fastly service version\").Alias(\"add\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"The name of the Sumologic logging object. Used as a primary key for API access\").Short('n').Action(c.EndpointName.Set).StringVar(&c.EndpointName.Value)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.ServiceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.AutoClone.Set,\n\t\tDst:    &c.AutoClone.Value,\n\t})\n\tc.CmdClause.Flag(\"format-version\", \"The version of the custom logging format used for the configured endpoint. Can be either 2 (the default, version 2 log format) or 1 (the version 1 log format). The logging call gets placed by default in vcl_log if format_version is set to 2 and in vcl_deliver if format_version is set to 1\").Action(c.FormatVersion.Set).IntVar(&c.FormatVersion.Value)\n\tlogflags.Format(c.CmdClause, &c.Format)\n\tlogflags.MessageType(c.CmdClause, &c.MessageType)\n\tlogflags.Placement(c.CmdClause, &c.Placement)\n\tlogflags.ProcessingRegion(c.CmdClause, &c.ProcessingRegion, \"Sumologic\")\n\tlogflags.ResponseCondition(c.CmdClause, &c.ResponseCondition)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.ServiceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.ServiceName.Value,\n\t})\n\tc.CmdClause.Flag(\"url\", \"The URL to POST to\").Action(c.URL.Set).StringVar(&c.URL.Value)\n\treturn &c\n}\n\n// ConstructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *CreateCommand) ConstructInput(serviceID string, serviceVersion int) (*fastly.CreateSumologicInput, error) {\n\tvar input fastly.CreateSumologicInput\n\n\tinput.ServiceID = serviceID\n\tinput.ServiceVersion = serviceVersion\n\tif c.EndpointName.WasSet {\n\t\tinput.Name = &c.EndpointName.Value\n\t}\n\tif c.URL.WasSet {\n\t\tinput.URL = &c.URL.Value\n\t}\n\n\tif c.Format.WasSet {\n\t\tinput.Format = fastly.ToPointer(argparser.Content(c.Format.Value))\n\t}\n\n\tif c.FormatVersion.WasSet {\n\t\tinput.FormatVersion = &c.FormatVersion.Value\n\t}\n\n\tif c.ResponseCondition.WasSet {\n\t\tinput.ResponseCondition = &c.ResponseCondition.Value\n\t}\n\n\tif c.Placement.WasSet {\n\t\tinput.Placement = &c.Placement.Value\n\t}\n\n\tif c.MessageType.WasSet {\n\t\tinput.MessageType = &c.MessageType.Value\n\t}\n\n\tif c.ProcessingRegion.WasSet {\n\t\tinput.ProcessingRegion = &c.ProcessingRegion.Value\n\t}\n\n\treturn &input, nil\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *CreateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(false),\n\t\tLocked:             optional.Of(false),\n\t\tAutoCloneFlag:      c.AutoClone,\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.ServiceName,\n\t\tServiceVersionFlag: c.ServiceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": errors.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tinput, err := c.ConstructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\td, err := c.Globals.APIClient.CreateSumologic(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\ttext.Success(\n\t\tout,\n\t\t\"Created Sumologic logging endpoint %s (service %s version %d)\",\n\t\tfastly.ToValue(d.Name),\n\t\tfastly.ToValue(d.ServiceID),\n\t\tfastly.ToValue(d.ServiceVersion),\n\t)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/sumologic/delete.go",
    "content": "package sumologic\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DeleteCommand calls the Fastly API to delete a Sumologic logging endpoint.\ntype DeleteCommand struct {\n\targparser.Base\n\tInput          fastly.DeleteSumologicInput\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n\tautoClone      argparser.OptionalAutoClone\n}\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"delete\", \"Delete a Sumologic logging endpoint on a Fastly service version\").Alias(\"remove\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"The name of the Sumologic logging object\").Short('n').Required().StringVar(&c.Input.Name)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.autoClone.Set,\n\t\tDst:    &c.autoClone.Value,\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DeleteCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(false),\n\t\tLocked:             optional.Of(false),\n\t\tAutoCloneFlag:      c.autoClone,\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": errors.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tc.Input.ServiceID = serviceID\n\tc.Input.ServiceVersion = fastly.ToValue(serviceVersion.Number)\n\n\tif err := c.Globals.APIClient.DeleteSumologic(context.TODO(), &c.Input); err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Deleted Sumologic logging endpoint %s (service %s version %d)\", c.Input.Name, c.Input.ServiceID, c.Input.ServiceVersion)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/sumologic/describe.go",
    "content": "package sumologic\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DescribeCommand calls the Fastly API to describe a Sumologic logging endpoint.\ntype DescribeCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tInput          fastly.GetSumologicInput\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n}\n\n// NewDescribeCommand returns a usable command registered under the parent.\nfunc NewDescribeCommand(parent argparser.Registerer, g *global.Data) *DescribeCommand {\n\tc := DescribeCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"describe\", \"Show detailed information about a Sumologic logging endpoint on a Fastly service version\").Alias(\"get\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"The name of the Sumologic logging object\").Short('n').Required().StringVar(&c.Input.Name)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DescribeCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fsterr.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tc.Input.ServiceID = serviceID\n\tc.Input.ServiceVersion = fastly.ToValue(serviceVersion.Number)\n\n\to, err := c.Globals.APIClient.GetSumologic(context.TODO(), &c.Input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\tlines := text.Lines{\n\t\t\"Format version\":     fastly.ToValue(o.FormatVersion),\n\t\t\"Format\":             fastly.ToValue(o.Format),\n\t\t\"Message type\":       fastly.ToValue(o.MessageType),\n\t\t\"Name\":               fastly.ToValue(o.Name),\n\t\t\"Placement\":          fastly.ToValue(o.Placement),\n\t\t\"Processing region\":  fastly.ToValue(o.ProcessingRegion),\n\t\t\"Response condition\": fastly.ToValue(o.ResponseCondition),\n\t\t\"URL\":                fastly.ToValue(o.URL),\n\t\t\"Version\":            fastly.ToValue(o.ServiceVersion),\n\t}\n\tif !c.Globals.Verbose() {\n\t\tlines[\"Service ID\"] = fastly.ToValue(o.ServiceID)\n\t}\n\ttext.PrintLines(out, lines)\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/sumologic/doc.go",
    "content": "// Package sumologic contains commands to inspect and manipulate Fastly service Sumologic\n// logging endpoints.\npackage sumologic\n"
  },
  {
    "path": "pkg/commands/service/logging/sumologic/list.go",
    "content": "package sumologic\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// ListCommand calls the Fastly API to list Sumologic logging endpoints.\ntype ListCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tInput          fastly.ListSumologicsInput\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n}\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"list\", \"List Sumologic endpoints on a Fastly service version\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *ListCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fsterr.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tc.Input.ServiceID = serviceID\n\tc.Input.ServiceVersion = fastly.ToValue(serviceVersion.Number)\n\n\to, err := c.Globals.APIClient.ListSumologics(context.TODO(), &c.Input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\tif !c.Globals.Verbose() {\n\t\ttw := text.NewTable(out)\n\t\ttw.AddHeader(\"SERVICE\", \"VERSION\", \"NAME\")\n\t\tfor _, sumologic := range o {\n\t\t\ttw.AddLine(\n\t\t\t\tfastly.ToValue(sumologic.ServiceID),\n\t\t\t\tfastly.ToValue(sumologic.ServiceVersion),\n\t\t\t\tfastly.ToValue(sumologic.Name),\n\t\t\t)\n\t\t}\n\t\ttw.Print()\n\t\treturn nil\n\t}\n\n\tfmt.Fprintf(out, \"Version: %d\\n\", c.Input.ServiceVersion)\n\tfor i, sumologic := range o {\n\t\tfmt.Fprintf(out, \"\\tSumologic %d/%d\\n\", i+1, len(o))\n\t\tfmt.Fprintf(out, \"\\t\\tService ID: %s\\n\", fastly.ToValue(sumologic.ServiceID))\n\t\tfmt.Fprintf(out, \"\\t\\tVersion: %d\\n\", fastly.ToValue(sumologic.ServiceVersion))\n\t\tfmt.Fprintf(out, \"\\t\\tName: %s\\n\", fastly.ToValue(sumologic.Name))\n\t\tfmt.Fprintf(out, \"\\t\\tURL: %s\\n\", fastly.ToValue(sumologic.URL))\n\t\tfmt.Fprintf(out, \"\\t\\tFormat: %s\\n\", fastly.ToValue(sumologic.Format))\n\t\tfmt.Fprintf(out, \"\\t\\tFormat version: %d\\n\", fastly.ToValue(sumologic.FormatVersion))\n\t\tfmt.Fprintf(out, \"\\t\\tResponse condition: %s\\n\", fastly.ToValue(sumologic.ResponseCondition))\n\t\tfmt.Fprintf(out, \"\\t\\tMessage type: %s\\n\", fastly.ToValue(sumologic.MessageType))\n\t\tfmt.Fprintf(out, \"\\t\\tPlacement: %s\\n\", fastly.ToValue(sumologic.Placement))\n\t\tfmt.Fprintf(out, \"\\t\\tProcessing region: %s\\n\", fastly.ToValue(sumologic.ProcessingRegion))\n\t}\n\tfmt.Fprintln(out)\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/sumologic/root.go",
    "content": "package sumologic\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"sumologic\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manipulate Fastly service version Sumologic logging endpoints\")\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/sumologic/sumologic_integration_test.go",
    "content": "package sumologic_test\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\troot \"github.com/fastly/cli/pkg/commands/service\"\n\tparent \"github.com/fastly/cli/pkg/commands/service/logging\"\n\tsub \"github.com/fastly/cli/pkg/commands/service/logging/sumologic\"\n\t\"github.com/fastly/cli/pkg/mock\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n)\n\nfunc TestSumologicCreate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name log --url example.com --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:      testutil.GetVersion,\n\t\t\t\tCloneVersionFn:    testutil.CloneVersionResult(4),\n\t\t\t\tCreateSumologicFn: createSumologicOK,\n\t\t\t},\n\t\t\tWantOutput: \"Created Sumologic logging endpoint log (service 123 version 4)\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name log --url example.com --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:      testutil.GetVersion,\n\t\t\t\tCloneVersionFn:    testutil.CloneVersionResult(4),\n\t\t\t\tCreateSumologicFn: createSumologicError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"create\"}, scenarios)\n}\n\nfunc TestSumologicList(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:     testutil.GetVersion,\n\t\t\t\tListSumologicsFn: listSumologicsOK,\n\t\t\t},\n\t\t\tWantOutput: listSumologicsShortOutput,\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --verbose\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:     testutil.GetVersion,\n\t\t\t\tListSumologicsFn: listSumologicsOK,\n\t\t\t},\n\t\t\tWantOutput: listSumologicsVerboseOutput,\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 -v\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:     testutil.GetVersion,\n\t\t\t\tListSumologicsFn: listSumologicsOK,\n\t\t\t},\n\t\t\tWantOutput: listSumologicsVerboseOutput,\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:     testutil.GetVersion,\n\t\t\t\tListSumologicsFn: listSumologicsError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"list\"}, scenarios)\n}\n\nfunc TestSumologicDescribe(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"--service-id 123 --version 1\",\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tGetSumologicFn: getSumologicError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tGetSumologicFn: getSumologicOK,\n\t\t\t},\n\t\t\tWantOutput: describeSumologicOutput,\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"describe\"}, scenarios)\n}\n\nfunc TestSumologicUpdate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"--service-id 123 --version 1 --new-name log\",\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs --new-name log --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:      testutil.GetVersion,\n\t\t\t\tCloneVersionFn:    testutil.CloneVersionResult(4),\n\t\t\t\tUpdateSumologicFn: updateSumologicError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs --new-name log --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:      testutil.GetVersion,\n\t\t\t\tCloneVersionFn:    testutil.CloneVersionResult(4),\n\t\t\t\tUpdateSumologicFn: updateSumologicOK,\n\t\t\t},\n\t\t\tWantOutput: \"Updated Sumologic logging endpoint log (service 123 version 4)\",\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"update\"}, scenarios)\n}\n\nfunc TestSumologicDelete(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"--service-id 123 --version 1\",\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:      testutil.GetVersion,\n\t\t\t\tCloneVersionFn:    testutil.CloneVersionResult(4),\n\t\t\t\tDeleteSumologicFn: deleteSumologicError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:      testutil.GetVersion,\n\t\t\t\tCloneVersionFn:    testutil.CloneVersionResult(4),\n\t\t\t\tDeleteSumologicFn: deleteSumologicOK,\n\t\t\t},\n\t\t\tWantOutput: \"Deleted Sumologic logging endpoint logs (service 123 version 4)\",\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"delete\"}, scenarios)\n}\n\nvar errTest = errors.New(\"fixture error\")\n\nfunc createSumologicOK(_ context.Context, i *fastly.CreateSumologicInput) (*fastly.Sumologic, error) {\n\treturn &fastly.Sumologic{\n\t\tServiceID:      fastly.ToPointer(i.ServiceID),\n\t\tServiceVersion: fastly.ToPointer(i.ServiceVersion),\n\t\tName:           i.Name,\n\t}, nil\n}\n\nfunc createSumologicError(_ context.Context, _ *fastly.CreateSumologicInput) (*fastly.Sumologic, error) {\n\treturn nil, errTest\n}\n\nfunc listSumologicsOK(_ context.Context, i *fastly.ListSumologicsInput) ([]*fastly.Sumologic, error) {\n\treturn []*fastly.Sumologic{\n\t\t{\n\t\t\tServiceID:         fastly.ToPointer(i.ServiceID),\n\t\t\tServiceVersion:    fastly.ToPointer(i.ServiceVersion),\n\t\t\tName:              fastly.ToPointer(\"logs\"),\n\t\t\tURL:               fastly.ToPointer(\"example.com\"),\n\t\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\t\tFormatVersion:     fastly.ToPointer(2),\n\t\t\tMessageType:       fastly.ToPointer(\"classic\"),\n\t\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\t\tProcessingRegion:  fastly.ToPointer(\"us\"),\n\t\t},\n\t\t{\n\t\t\tServiceID:         fastly.ToPointer(i.ServiceID),\n\t\t\tServiceVersion:    fastly.ToPointer(i.ServiceVersion),\n\t\t\tName:              fastly.ToPointer(\"analytics\"),\n\t\t\tURL:               fastly.ToPointer(\"bar.com\"),\n\t\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\t\tMessageType:       fastly.ToPointer(\"classic\"),\n\t\t\tFormatVersion:     fastly.ToPointer(2),\n\t\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\t\tProcessingRegion:  fastly.ToPointer(\"us\"),\n\t\t},\n\t}, nil\n}\n\nfunc listSumologicsError(_ context.Context, _ *fastly.ListSumologicsInput) ([]*fastly.Sumologic, error) {\n\treturn nil, errTest\n}\n\nvar listSumologicsShortOutput = strings.TrimSpace(`\nSERVICE  VERSION  NAME\n123      1        logs\n123      1        analytics\n`) + \"\\n\"\n\nvar listSumologicsVerboseOutput = strings.TrimSpace(`\nFastly API endpoint: https://api.fastly.com\nFastly API token provided via config file (auth: user)\n\nService ID (via --service-id): 123\n\nVersion: 1\n\tSumologic 1/2\n\t\tService ID: 123\n\t\tVersion: 1\n\t\tName: logs\n\t\tURL: example.com\n\t\tFormat: %h %l %u %t \"%r\" %>s %b\n\t\tFormat version: 2\n\t\tResponse condition: Prevent default logging\n\t\tMessage type: classic\n\t\tPlacement: none\n\t\tProcessing region: us\n\tSumologic 2/2\n\t\tService ID: 123\n\t\tVersion: 1\n\t\tName: analytics\n\t\tURL: bar.com\n\t\tFormat: %h %l %u %t \"%r\" %>s %b\n\t\tFormat version: 2\n\t\tResponse condition: Prevent default logging\n\t\tMessage type: classic\n\t\tPlacement: none\n\t\tProcessing region: us\n`) + \"\\n\\n\"\n\nfunc getSumologicOK(_ context.Context, i *fastly.GetSumologicInput) (*fastly.Sumologic, error) {\n\treturn &fastly.Sumologic{\n\t\tServiceID:         fastly.ToPointer(i.ServiceID),\n\t\tServiceVersion:    fastly.ToPointer(i.ServiceVersion),\n\t\tName:              fastly.ToPointer(\"logs\"),\n\t\tURL:               fastly.ToPointer(\"example.com\"),\n\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\tFormatVersion:     fastly.ToPointer(2),\n\t\tMessageType:       fastly.ToPointer(\"classic\"),\n\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\tProcessingRegion:  fastly.ToPointer(\"us\"),\n\t}, nil\n}\n\nfunc getSumologicError(_ context.Context, _ *fastly.GetSumologicInput) (*fastly.Sumologic, error) {\n\treturn nil, errTest\n}\n\nvar describeSumologicOutput = \"\\n\" + strings.TrimSpace(`\nFormat: %h %l %u %t \"%r\" %>s %b\nFormat version: 2\nMessage type: classic\nName: logs\nPlacement: none\nProcessing region: us\nResponse condition: Prevent default logging\nService ID: 123\nURL: example.com\nVersion: 1\n`) + \"\\n\"\n\nfunc updateSumologicOK(_ context.Context, i *fastly.UpdateSumologicInput) (*fastly.Sumologic, error) {\n\treturn &fastly.Sumologic{\n\t\tServiceID:         fastly.ToPointer(i.ServiceID),\n\t\tServiceVersion:    fastly.ToPointer(i.ServiceVersion),\n\t\tName:              fastly.ToPointer(\"log\"),\n\t\tURL:               fastly.ToPointer(\"example.com\"),\n\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\tFormatVersion:     fastly.ToPointer(2),\n\t\tMessageType:       fastly.ToPointer(\"classic\"),\n\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t}, nil\n}\n\nfunc updateSumologicError(_ context.Context, _ *fastly.UpdateSumologicInput) (*fastly.Sumologic, error) {\n\treturn nil, errTest\n}\n\nfunc deleteSumologicOK(_ context.Context, _ *fastly.DeleteSumologicInput) error {\n\treturn nil\n}\n\nfunc deleteSumologicError(_ context.Context, _ *fastly.DeleteSumologicInput) error {\n\treturn errTest\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/sumologic/sumologic_test.go",
    "content": "package sumologic_test\n\nimport (\n\t\"bytes\"\n\t\"testing\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/service/logging/sumologic\"\n\t\"github.com/fastly/cli/pkg/config\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/manifest\"\n\t\"github.com/fastly/cli/pkg/mock\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n)\n\nfunc TestCreateSumologicInput(t *testing.T) {\n\tfor _, testcase := range []struct {\n\t\tname      string\n\t\tcmd       *sumologic.CreateCommand\n\t\twant      *fastly.CreateSumologicInput\n\t\twantError string\n\t}{\n\t\t{\n\t\t\tname: \"required values set flag serviceID\",\n\t\t\tcmd:  createCommandRequired(),\n\t\t\twant: &fastly.CreateSumologicInput{\n\t\t\t\tServiceID:      \"123\",\n\t\t\t\tServiceVersion: 4,\n\t\t\t\tName:           fastly.ToPointer(\"log\"),\n\t\t\t\tURL:            fastly.ToPointer(\"example.com\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"all values set flag serviceID\",\n\t\t\tcmd:  createCommandOK(),\n\t\t\twant: &fastly.CreateSumologicInput{\n\t\t\t\tServiceID:         \"123\",\n\t\t\t\tServiceVersion:    4,\n\t\t\t\tName:              fastly.ToPointer(\"log\"),\n\t\t\t\tURL:               fastly.ToPointer(\"example.com\"),\n\t\t\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\t\t\tFormatVersion:     fastly.ToPointer(2),\n\t\t\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\t\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\t\t\tMessageType:       fastly.ToPointer(\"classic\"),\n\t\t\t\tProcessingRegion:  fastly.ToPointer(\"eu\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:      \"error missing serviceID\",\n\t\t\tcmd:       createCommandMissingServiceID(),\n\t\t\twant:      nil,\n\t\t\twantError: errors.ErrNoServiceID.Error(),\n\t\t},\n\t} {\n\t\tt.Run(testcase.name, func(t *testing.T) {\n\t\t\tif testcase.wantError == errors.ErrNoServiceID.Error() {\n\t\t\t\tt.Setenv(\"FASTLY_SERVICE_ID\", \"\")\n\t\t\t}\n\t\t\tvar bs []byte\n\t\t\tout := bytes.NewBuffer(bs)\n\t\t\tverboseMode := true\n\n\t\t\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\t\t\tAutoCloneFlag:      testcase.cmd.AutoClone,\n\t\t\t\tAPIClient:          testcase.cmd.Globals.APIClient,\n\t\t\t\tManifest:           testcase.cmd.Manifest,\n\t\t\t\tOut:                out,\n\t\t\t\tServiceVersionFlag: testcase.cmd.ServiceVersion,\n\t\t\t\tVerboseMode:        verboseMode,\n\t\t\t})\n\n\t\t\tswitch {\n\t\t\tcase err != nil && testcase.wantError == \"\":\n\t\t\t\tt.Fatalf(\"unexpected error getting service details: %v\", err)\n\t\t\t\treturn\n\t\t\tcase err != nil && testcase.wantError != \"\":\n\t\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\t\treturn\n\t\t\tcase err == nil && testcase.wantError != \"\":\n\t\t\t\tt.Fatalf(\"expected error, have nil (service details: %s, %d)\", serviceID, serviceVersion.Number)\n\t\t\tcase err == nil && testcase.wantError == \"\":\n\t\t\t\thave, err := testcase.cmd.ConstructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\t\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\t\ttestutil.AssertEqual(t, testcase.want, have)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestUpdateSumologicInput(t *testing.T) {\n\tscenarios := []struct {\n\t\tname      string\n\t\tcmd       *sumologic.UpdateCommand\n\t\tapi       mock.API\n\t\twant      *fastly.UpdateSumologicInput\n\t\twantError string\n\t}{\n\t\t{\n\t\t\tname: \"no updates\",\n\t\t\tcmd:  updateCommandNoUpdates(),\n\t\t\tapi: mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tGetSumologicFn: getSumologicOK,\n\t\t\t},\n\t\t\twant: &fastly.UpdateSumologicInput{\n\t\t\t\tServiceID:      \"123\",\n\t\t\t\tServiceVersion: 4,\n\t\t\t\tName:           \"log\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"all values set flag serviceID\",\n\t\t\tcmd:  updateCommandAll(),\n\t\t\tapi: mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tGetSumologicFn: getSumologicOK,\n\t\t\t},\n\t\t\twant: &fastly.UpdateSumologicInput{\n\t\t\t\tServiceID:         \"123\",\n\t\t\t\tServiceVersion:    4,\n\t\t\t\tName:              \"log\",\n\t\t\t\tNewName:           fastly.ToPointer(\"new1\"),\n\t\t\t\tURL:               fastly.ToPointer(\"new2\"),\n\t\t\t\tFormat:            fastly.ToPointer(\"new3\"),\n\t\t\t\tFormatVersion:     fastly.ToPointer(3),\n\t\t\t\tResponseCondition: fastly.ToPointer(\"new4\"),\n\t\t\t\tPlacement:         fastly.ToPointer(\"new5\"),\n\t\t\t\tMessageType:       fastly.ToPointer(\"new6\"),\n\t\t\t\tProcessingRegion:  fastly.ToPointer(\"eu\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:      \"error missing serviceID\",\n\t\t\tcmd:       updateCommandMissingServiceID(),\n\t\t\twant:      nil,\n\t\t\twantError: errors.ErrNoServiceID.Error(),\n\t\t},\n\t}\n\tfor testcaseIdx := range scenarios {\n\t\ttestcase := &scenarios[testcaseIdx]\n\t\tt.Run(testcase.name, func(t *testing.T) {\n\t\t\tif testcase.wantError == errors.ErrNoServiceID.Error() {\n\t\t\t\tt.Setenv(\"FASTLY_SERVICE_ID\", \"\")\n\t\t\t}\n\t\t\ttestcase.cmd.Globals.APIClient = testcase.api\n\n\t\t\tvar bs []byte\n\t\t\tout := bytes.NewBuffer(bs)\n\t\t\tverboseMode := true\n\n\t\t\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\t\t\tAutoCloneFlag:      testcase.cmd.AutoClone,\n\t\t\t\tAPIClient:          testcase.api,\n\t\t\t\tManifest:           testcase.cmd.Manifest,\n\t\t\t\tOut:                out,\n\t\t\t\tServiceVersionFlag: testcase.cmd.ServiceVersion,\n\t\t\t\tVerboseMode:        verboseMode,\n\t\t\t})\n\n\t\t\tswitch {\n\t\t\tcase err != nil && testcase.wantError == \"\":\n\t\t\t\tt.Fatalf(\"unexpected error getting service details: %v\", err)\n\t\t\t\treturn\n\t\t\tcase err != nil && testcase.wantError != \"\":\n\t\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\t\treturn\n\t\t\tcase err == nil && testcase.wantError != \"\":\n\t\t\t\tt.Fatalf(\"expected error, have nil (service details: %s, %d)\", serviceID, serviceVersion.Number)\n\t\t\tcase err == nil && testcase.wantError == \"\":\n\t\t\t\thave, err := testcase.cmd.ConstructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\t\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\t\ttestutil.AssertEqual(t, testcase.want, have)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc createCommandOK() *sumologic.CreateCommand {\n\tvar b bytes.Buffer\n\n\tg := global.Data{\n\t\tConfig: config.File{},\n\t\tEnv:    config.Environment{},\n\t\tOutput: &b,\n\t}\n\tg.APIClient, _ = mock.APIClient(mock.API{\n\t\tGetVersionFn:   testutil.GetVersion,\n\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t})(\"token\", \"endpoint\", false)\n\n\treturn &sumologic.CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: &g,\n\t\t},\n\t\tManifest: manifest.Data{\n\t\t\tFlag: manifest.Flag{\n\t\t\t\tServiceID: \"123\",\n\t\t\t},\n\t\t},\n\t\tEndpointName: argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"log\"},\n\t\tURL:          argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"example.com\"},\n\t\tServiceVersion: argparser.OptionalServiceVersion{\n\t\t\tOptionalString: argparser.OptionalString{Value: \"1\"},\n\t\t},\n\t\tAutoClone: argparser.OptionalAutoClone{\n\t\t\tOptionalBool: argparser.OptionalBool{\n\t\t\t\tOptional: argparser.Optional{\n\t\t\t\t\tWasSet: true,\n\t\t\t\t},\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t},\n\t\tFormat:            argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: `%h %l %u %t \"%r\" %>s %b`},\n\t\tFormatVersion:     argparser.OptionalInt{Optional: argparser.Optional{WasSet: true}, Value: 2},\n\t\tResponseCondition: argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"Prevent default logging\"},\n\t\tPlacement:         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"none\"},\n\t\tMessageType:       argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"classic\"},\n\t\tProcessingRegion:  argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"eu\"},\n\t}\n}\n\nfunc createCommandRequired() *sumologic.CreateCommand {\n\tvar b bytes.Buffer\n\n\tg := global.Data{\n\t\tConfig: config.File{},\n\t\tEnv:    config.Environment{},\n\t\tOutput: &b,\n\t}\n\tg.APIClient, _ = mock.APIClient(mock.API{\n\t\tGetVersionFn:   testutil.GetVersion,\n\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t})(\"token\", \"endpoint\", false)\n\n\treturn &sumologic.CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: &g,\n\t\t},\n\t\tManifest: manifest.Data{\n\t\t\tFlag: manifest.Flag{\n\t\t\t\tServiceID: \"123\",\n\t\t\t},\n\t\t},\n\t\tEndpointName: argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"log\"},\n\t\tURL:          argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"example.com\"},\n\t\tServiceVersion: argparser.OptionalServiceVersion{\n\t\t\tOptionalString: argparser.OptionalString{Value: \"1\"},\n\t\t},\n\t\tAutoClone: argparser.OptionalAutoClone{\n\t\t\tOptionalBool: argparser.OptionalBool{\n\t\t\t\tOptional: argparser.Optional{\n\t\t\t\t\tWasSet: true,\n\t\t\t\t},\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t},\n\t}\n}\n\nfunc createCommandMissingServiceID() *sumologic.CreateCommand {\n\tres := createCommandOK()\n\tres.Manifest = manifest.Data{}\n\tres.ServiceVersion = argparser.OptionalServiceVersion{}\n\treturn res\n}\n\nfunc updateCommandNoUpdates() *sumologic.UpdateCommand {\n\tvar b bytes.Buffer\n\n\tg := global.Data{\n\t\tConfig: config.File{},\n\t\tEnv:    config.Environment{},\n\t\tOutput: &b,\n\t}\n\n\treturn &sumologic.UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: &g,\n\t\t},\n\t\tManifest: manifest.Data{\n\t\t\tFlag: manifest.Flag{\n\t\t\t\tServiceID: \"123\",\n\t\t\t},\n\t\t},\n\t\tEndpointName: \"log\",\n\t\tServiceVersion: argparser.OptionalServiceVersion{\n\t\t\tOptionalString: argparser.OptionalString{Value: \"1\"},\n\t\t},\n\t\tAutoClone: argparser.OptionalAutoClone{\n\t\t\tOptionalBool: argparser.OptionalBool{\n\t\t\t\tOptional: argparser.Optional{\n\t\t\t\t\tWasSet: true,\n\t\t\t\t},\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t},\n\t}\n}\n\nfunc updateCommandAll() *sumologic.UpdateCommand {\n\tvar b bytes.Buffer\n\n\tg := global.Data{\n\t\tConfig: config.File{},\n\t\tEnv:    config.Environment{},\n\t\tOutput: &b,\n\t}\n\n\treturn &sumologic.UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: &g,\n\t\t},\n\t\tManifest: manifest.Data{\n\t\t\tFlag: manifest.Flag{\n\t\t\t\tServiceID: \"123\",\n\t\t\t},\n\t\t},\n\t\tEndpointName: \"log\",\n\t\tServiceVersion: argparser.OptionalServiceVersion{\n\t\t\tOptionalString: argparser.OptionalString{Value: \"1\"},\n\t\t},\n\t\tAutoClone: argparser.OptionalAutoClone{\n\t\t\tOptionalBool: argparser.OptionalBool{\n\t\t\t\tOptional: argparser.Optional{\n\t\t\t\t\tWasSet: true,\n\t\t\t\t},\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t},\n\t\tNewName:           argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new1\"},\n\t\tURL:               argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new2\"},\n\t\tFormat:            argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new3\"},\n\t\tFormatVersion:     argparser.OptionalInt{Optional: argparser.Optional{WasSet: true}, Value: 3},\n\t\tResponseCondition: argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new4\"},\n\t\tPlacement:         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new5\"},\n\t\tMessageType:       argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new6\"},\n\t\tProcessingRegion:  argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"eu\"},\n\t}\n}\n\nfunc updateCommandMissingServiceID() *sumologic.UpdateCommand {\n\tres := updateCommandAll()\n\tres.Manifest = manifest.Data{}\n\tres.ServiceVersion = argparser.OptionalServiceVersion{}\n\treturn res\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/sumologic/update.go",
    "content": "package sumologic\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/service/logging/logflags\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/manifest\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// UpdateCommand calls the Fastly API to update a Sumologic logging endpoint.\ntype UpdateCommand struct {\n\targparser.Base\n\tManifest manifest.Data\n\n\t// Required.\n\tEndpointName   string // Can't shadow argparser.Base method Name().\n\tServiceName    argparser.OptionalServiceNameID\n\tServiceVersion argparser.OptionalServiceVersion\n\n\t// Optional.\n\tAutoClone         argparser.OptionalAutoClone\n\tFormat            argparser.OptionalString\n\tFormatVersion     argparser.OptionalInt // Inconsistent with other logging endpoints, but remaining as int to avoid breaking changes in fastly/go-fastly.\n\tMessageType       argparser.OptionalString\n\tNewName           argparser.OptionalString\n\tPlacement         argparser.OptionalString\n\tProcessingRegion  argparser.OptionalString\n\tResponseCondition argparser.OptionalString\n\tURL               argparser.OptionalString\n}\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"update\", \"Update a Sumologic logging endpoint on a Fastly service version\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"The name of the Sumologic logging object\").Short('n').Required().StringVar(&c.EndpointName)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.ServiceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.AutoClone.Set,\n\t\tDst:    &c.AutoClone.Value,\n\t})\n\tlogflags.Format(c.CmdClause, &c.Format)\n\tc.CmdClause.Flag(\"format-version\", \"The version of the custom logging format used for the configured endpoint. Can be either 2 (the default, version 2 log format) or 1 (the version 1 log format). The logging call gets placed by default in vcl_log if format_version is set to 2 and in vcl_deliver if format_version is set to 1\").Action(c.FormatVersion.Set).IntVar(&c.FormatVersion.Value)\n\tlogflags.MessageType(c.CmdClause, &c.MessageType)\n\tc.CmdClause.Flag(\"new-name\", \"New name of the Sumologic logging object\").Action(c.NewName.Set).StringVar(&c.NewName.Value)\n\tlogflags.Placement(c.CmdClause, &c.Placement)\n\tlogflags.ProcessingRegion(c.CmdClause, &c.ProcessingRegion, \"Sumologic\")\n\tlogflags.ResponseCondition(c.CmdClause, &c.ResponseCondition)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.ServiceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.ServiceName.Value,\n\t})\n\tc.CmdClause.Flag(\"url\", \"The URL to POST to\").Action(c.URL.Set).StringVar(&c.URL.Value)\n\treturn &c\n}\n\n// ConstructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *UpdateCommand) ConstructInput(serviceID string, serviceVersion int) (*fastly.UpdateSumologicInput, error) {\n\tinput := fastly.UpdateSumologicInput{\n\t\tServiceID:      serviceID,\n\t\tServiceVersion: serviceVersion,\n\t\tName:           c.EndpointName,\n\t}\n\n\t// Set new values if set by user.\n\tif c.NewName.WasSet {\n\t\tinput.NewName = &c.NewName.Value\n\t}\n\n\tif c.URL.WasSet {\n\t\tinput.URL = &c.URL.Value\n\t}\n\n\tif c.Format.WasSet {\n\t\tinput.Format = fastly.ToPointer(argparser.Content(c.Format.Value))\n\t}\n\n\tif c.ResponseCondition.WasSet {\n\t\tinput.ResponseCondition = &c.ResponseCondition.Value\n\t}\n\n\tif c.MessageType.WasSet {\n\t\tinput.MessageType = &c.MessageType.Value\n\t}\n\n\tif c.FormatVersion.WasSet {\n\t\tinput.FormatVersion = &c.FormatVersion.Value\n\t}\n\n\tif c.Placement.WasSet {\n\t\tinput.Placement = &c.Placement.Value\n\t}\n\n\tif c.ProcessingRegion.WasSet {\n\t\tinput.ProcessingRegion = &c.ProcessingRegion.Value\n\t}\n\n\treturn &input, nil\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *UpdateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(false),\n\t\tLocked:             optional.Of(false),\n\t\tAutoCloneFlag:      c.AutoClone,\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.ServiceName,\n\t\tServiceVersionFlag: c.ServiceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": errors.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tinput, err := c.ConstructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\tsumologic, err := c.Globals.APIClient.UpdateSumologic(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\ttext.Success(\n\t\tout,\n\t\t\"Updated Sumologic logging endpoint %s (service %s version %d)\",\n\t\tfastly.ToValue(sumologic.Name),\n\t\tfastly.ToValue(sumologic.ServiceID),\n\t\tfastly.ToValue(sumologic.ServiceVersion),\n\t)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/syslog/create.go",
    "content": "package syslog\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/service/logging/logflags\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/manifest\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// CreateCommand calls the Fastly API to create a Syslog logging endpoint.\ntype CreateCommand struct {\n\targparser.Base\n\tManifest manifest.Data\n\n\t// Required.\n\tServiceName    argparser.OptionalServiceNameID\n\tServiceVersion argparser.OptionalServiceVersion\n\n\t// Optional.\n\tAddress           argparser.OptionalString\n\tAutoClone         argparser.OptionalAutoClone\n\tEndpointName      argparser.OptionalString // Can't shadow argparser.Base method Name().\n\tFormat            argparser.OptionalString\n\tFormatVersion     argparser.OptionalInt\n\tMessageType       argparser.OptionalString\n\tPlacement         argparser.OptionalString\n\tProcessingRegion  argparser.OptionalString\n\tPort              argparser.OptionalInt\n\tResponseCondition argparser.OptionalString\n\tTLSCACert         argparser.OptionalString\n\tTLSClientCert     argparser.OptionalString\n\tTLSClientKey      argparser.OptionalString\n\tTLSHostname       argparser.OptionalString\n\tToken             argparser.OptionalString\n\tUseTLS            argparser.OptionalBool\n}\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"create\", \"Create a Syslog logging endpoint on a Fastly service version\").Alias(\"add\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"The name of the Syslog logging object. Used as a primary key for API access\").Short('n').Action(c.EndpointName.Set).StringVar(&c.EndpointName.Value)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.ServiceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.CmdClause.Flag(\"address\", \"A hostname or IPv4 address\").Action(c.Address.Set).StringVar(&c.Address.Value)\n\tc.CmdClause.Flag(\"auth-token\", \"Whether to prepend each message with a specific token\").Action(c.Token.Set).StringVar(&c.Token.Value)\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.AutoClone.Set,\n\t\tDst:    &c.AutoClone.Value,\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tlogflags.Format(c.CmdClause, &c.Format)\n\tlogflags.FormatVersion(c.CmdClause, &c.FormatVersion)\n\tlogflags.MessageType(c.CmdClause, &c.MessageType)\n\tlogflags.Placement(c.CmdClause, &c.Placement)\n\tc.CmdClause.Flag(\"port\", \"The port number\").Action(c.Port.Set).IntVar(&c.Port.Value)\n\tlogflags.ProcessingRegion(c.CmdClause, &c.ProcessingRegion, \"syslog\")\n\tlogflags.ResponseCondition(c.CmdClause, &c.ResponseCondition)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.ServiceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.ServiceName.Value,\n\t})\n\tlogflags.TLSCACert(c.CmdClause, &c.TLSCACert)\n\tlogflags.TLSClientCert(c.CmdClause, &c.TLSClientCert)\n\tlogflags.TLSClientKey(c.CmdClause, &c.TLSClientKey)\n\tlogflags.TLSHostname(c.CmdClause, &c.TLSHostname)\n\tc.CmdClause.Flag(\"use-tls\", \"Whether to use TLS for secure logging. Can be either true or false\").Action(c.UseTLS.Set).BoolVar(&c.UseTLS.Value)\n\treturn &c\n}\n\n// ConstructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *CreateCommand) ConstructInput(serviceID string, serviceVersion int) (*fastly.CreateSyslogInput, error) {\n\tvar input fastly.CreateSyslogInput\n\n\tinput.ServiceID = serviceID\n\tif c.EndpointName.WasSet {\n\t\tinput.Name = &c.EndpointName.Value\n\t}\n\tinput.ServiceVersion = serviceVersion\n\tif c.Address.WasSet {\n\t\tinput.Address = &c.Address.Value\n\t}\n\n\tif c.Port.WasSet {\n\t\tinput.Port = &c.Port.Value\n\t}\n\n\tif c.UseTLS.WasSet {\n\t\tinput.UseTLS = fastly.ToPointer(fastly.Compatibool(c.UseTLS.Value))\n\t}\n\n\tif c.TLSCACert.WasSet {\n\t\tinput.TLSCACert = &c.TLSCACert.Value\n\t}\n\n\tif c.TLSHostname.WasSet {\n\t\tinput.TLSHostname = &c.TLSHostname.Value\n\t}\n\n\tif c.TLSClientCert.WasSet {\n\t\tinput.TLSClientCert = &c.TLSClientCert.Value\n\t}\n\n\tif c.TLSClientKey.WasSet {\n\t\tinput.TLSClientKey = &c.TLSClientKey.Value\n\t}\n\n\tif c.Token.WasSet {\n\t\tinput.Token = &c.Token.Value\n\t}\n\n\tif c.Format.WasSet {\n\t\tinput.Format = fastly.ToPointer(argparser.Content(c.Format.Value))\n\t}\n\n\tif c.FormatVersion.WasSet {\n\t\tinput.FormatVersion = &c.FormatVersion.Value\n\t}\n\n\tif c.MessageType.WasSet {\n\t\tinput.MessageType = &c.MessageType.Value\n\t}\n\n\tif c.ResponseCondition.WasSet {\n\t\tinput.ResponseCondition = &c.ResponseCondition.Value\n\t}\n\n\tif c.Placement.WasSet {\n\t\tinput.Placement = &c.Placement.Value\n\t}\n\n\tif c.ProcessingRegion.WasSet {\n\t\tinput.ProcessingRegion = &c.ProcessingRegion.Value\n\t}\n\n\treturn &input, nil\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *CreateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(false),\n\t\tLocked:             optional.Of(false),\n\t\tAutoCloneFlag:      c.AutoClone,\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.ServiceName,\n\t\tServiceVersionFlag: c.ServiceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": errors.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tinput, err := c.ConstructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\td, err := c.Globals.APIClient.CreateSyslog(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\ttext.Success(\n\t\tout,\n\t\t\"Created Syslog logging endpoint %s (service %s version %d)\",\n\t\tfastly.ToValue(d.Name),\n\t\tfastly.ToValue(d.ServiceID),\n\t\tfastly.ToValue(d.ServiceVersion),\n\t)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/syslog/delete.go",
    "content": "package syslog\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DeleteCommand calls the Fastly API to delete a Syslog logging endpoint.\ntype DeleteCommand struct {\n\targparser.Base\n\tInput          fastly.DeleteSyslogInput\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n\tautoClone      argparser.OptionalAutoClone\n}\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"delete\", \"Delete a Syslog logging endpoint on a Fastly service version\").Alias(\"remove\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"The name of the Syslog logging object\").Short('n').Required().StringVar(&c.Input.Name)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.autoClone.Set,\n\t\tDst:    &c.autoClone.Value,\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DeleteCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(false),\n\t\tLocked:             optional.Of(false),\n\t\tAutoCloneFlag:      c.autoClone,\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": errors.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tc.Input.ServiceID = serviceID\n\tc.Input.ServiceVersion = fastly.ToValue(serviceVersion.Number)\n\n\tif err := c.Globals.APIClient.DeleteSyslog(context.TODO(), &c.Input); err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Deleted Syslog logging endpoint %s (service %s version %d)\", c.Input.Name, c.Input.ServiceID, c.Input.ServiceVersion)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/syslog/describe.go",
    "content": "package syslog\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DescribeCommand calls the Fastly API to describe a Syslog logging endpoint.\ntype DescribeCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tInput          fastly.GetSyslogInput\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n}\n\n// NewDescribeCommand returns a usable command registered under the parent.\nfunc NewDescribeCommand(parent argparser.Registerer, g *global.Data) *DescribeCommand {\n\tc := DescribeCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"describe\", \"Show detailed information about a Syslog logging endpoint on a Fastly service version\").Alias(\"get\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"The name of the Syslog logging object\").Short('n').Required().StringVar(&c.Input.Name)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DescribeCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fsterr.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tc.Input.ServiceID = serviceID\n\tc.Input.ServiceVersion = fastly.ToValue(serviceVersion.Number)\n\n\to, err := c.Globals.APIClient.GetSyslog(context.TODO(), &c.Input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\tlines := text.Lines{\n\t\t\"Address\":                fastly.ToValue(o.Address),\n\t\t\"Format version\":         fastly.ToValue(o.FormatVersion),\n\t\t\"Format\":                 fastly.ToValue(o.Format),\n\t\t\"Hostname\":               fastly.ToValue(o.Hostname),\n\t\t\"IPV4\":                   fastly.ToValue(o.IPV4),\n\t\t\"Message type\":           fastly.ToValue(o.MessageType),\n\t\t\"Name\":                   fastly.ToValue(o.Name),\n\t\t\"Placement\":              fastly.ToValue(o.Placement),\n\t\t\"Port\":                   fastly.ToValue(o.Port),\n\t\t\"Processing region\":      fastly.ToValue(o.ProcessingRegion),\n\t\t\"Response condition\":     fastly.ToValue(o.ResponseCondition),\n\t\t\"TLS CA certificate\":     fastly.ToValue(o.TLSCACert),\n\t\t\"TLS client certificate\": fastly.ToValue(o.TLSClientCert),\n\t\t\"TLS client key\":         fastly.ToValue(o.TLSClientKey),\n\t\t\"TLS hostname\":           fastly.ToValue(o.TLSHostname),\n\t\t\"Token\":                  fastly.ToValue(o.Token),\n\t\t\"Use TLS\":                fastly.ToValue(o.UseTLS),\n\t\t\"Version\":                fastly.ToValue(o.ServiceVersion),\n\t}\n\tif !c.Globals.Verbose() {\n\t\tlines[\"Service ID\"] = fastly.ToValue(o.ServiceID)\n\t}\n\ttext.PrintLines(out, lines)\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/syslog/doc.go",
    "content": "// Package syslog contains commands to inspect and manipulate Fastly service Syslog\n// logging endpoints.\npackage syslog\n"
  },
  {
    "path": "pkg/commands/service/logging/syslog/list.go",
    "content": "package syslog\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// ListCommand calls the Fastly API to list Syslog logging endpoints.\ntype ListCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tInput          fastly.ListSyslogsInput\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n}\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"list\", \"List Syslog endpoints on a Fastly service version\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *ListCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fsterr.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tc.Input.ServiceID = serviceID\n\tc.Input.ServiceVersion = fastly.ToValue(serviceVersion.Number)\n\n\to, err := c.Globals.APIClient.ListSyslogs(context.TODO(), &c.Input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\tif !c.Globals.Verbose() {\n\t\ttw := text.NewTable(out)\n\t\ttw.AddHeader(\"SERVICE\", \"VERSION\", \"NAME\")\n\t\tfor _, syslog := range o {\n\t\t\ttw.AddLine(\n\t\t\t\tfastly.ToValue(syslog.ServiceID),\n\t\t\t\tfastly.ToValue(syslog.ServiceVersion),\n\t\t\t\tfastly.ToValue(syslog.Name),\n\t\t\t)\n\t\t}\n\t\ttw.Print()\n\t\treturn nil\n\t}\n\n\tfmt.Fprintf(out, \"Version: %d\\n\", c.Input.ServiceVersion)\n\tfor i, syslog := range o {\n\t\tfmt.Fprintf(out, \"\\tSyslog %d/%d\\n\", i+1, len(o))\n\t\tfmt.Fprintf(out, \"\\t\\tService ID: %s\\n\", fastly.ToValue(syslog.ServiceID))\n\t\tfmt.Fprintf(out, \"\\t\\tVersion: %d\\n\", fastly.ToValue(syslog.ServiceVersion))\n\t\tfmt.Fprintf(out, \"\\t\\tName: %s\\n\", fastly.ToValue(syslog.Name))\n\t\tfmt.Fprintf(out, \"\\t\\tAddress: %s\\n\", fastly.ToValue(syslog.Address))\n\t\tfmt.Fprintf(out, \"\\t\\tHostname: %s\\n\", fastly.ToValue(syslog.Hostname))\n\t\tfmt.Fprintf(out, \"\\t\\tPort: %d\\n\", fastly.ToValue(syslog.Port))\n\t\tfmt.Fprintf(out, \"\\t\\tUse TLS: %t\\n\", fastly.ToValue(syslog.UseTLS))\n\t\tfmt.Fprintf(out, \"\\t\\tIPV4: %s\\n\", fastly.ToValue(syslog.IPV4))\n\t\tfmt.Fprintf(out, \"\\t\\tTLS CA certificate: %s\\n\", fastly.ToValue(syslog.TLSCACert))\n\t\tfmt.Fprintf(out, \"\\t\\tTLS hostname: %s\\n\", fastly.ToValue(syslog.TLSHostname))\n\t\tfmt.Fprintf(out, \"\\t\\tTLS client certificate: %s\\n\", fastly.ToValue(syslog.TLSClientCert))\n\t\tfmt.Fprintf(out, \"\\t\\tTLS client key: %s\\n\", fastly.ToValue(syslog.TLSClientKey))\n\t\tfmt.Fprintf(out, \"\\t\\tToken: %s\\n\", fastly.ToValue(syslog.Token))\n\t\tfmt.Fprintf(out, \"\\t\\tFormat: %s\\n\", fastly.ToValue(syslog.Format))\n\t\tfmt.Fprintf(out, \"\\t\\tFormat version: %d\\n\", fastly.ToValue(syslog.FormatVersion))\n\t\tfmt.Fprintf(out, \"\\t\\tMessage type: %s\\n\", fastly.ToValue(syslog.MessageType))\n\t\tfmt.Fprintf(out, \"\\t\\tResponse condition: %s\\n\", fastly.ToValue(syslog.ResponseCondition))\n\t\tfmt.Fprintf(out, \"\\t\\tPlacement: %s\\n\", fastly.ToValue(syslog.Placement))\n\t\tfmt.Fprintf(out, \"\\t\\tProcessing region: %s\\n\", fastly.ToValue(syslog.ProcessingRegion))\n\t}\n\tfmt.Fprintln(out)\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/syslog/root.go",
    "content": "package syslog\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"syslog\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manipulate Fastly service version Syslog logging endpoints\")\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/syslog/syslog_integration_test.go",
    "content": "package syslog_test\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/mock\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n\n\troot \"github.com/fastly/cli/pkg/commands/service\"\n\tparent \"github.com/fastly/cli/pkg/commands/service/logging\"\n\tsub \"github.com/fastly/cli/pkg/commands/service/logging/syslog\"\n)\n\nfunc TestSyslogCreate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name log --address 127.0.0.1 --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tCreateSyslogFn: createSyslogOK,\n\t\t\t},\n\t\t\tWantOutput: \"Created Syslog logging endpoint log (service 123 version 4)\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name log --address 127.0.0.1 --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tCreateSyslogFn: createSyslogError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"create\"}, scenarios)\n}\n\nfunc TestSyslogList(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:  testutil.GetVersion,\n\t\t\t\tListSyslogsFn: listSyslogsOK,\n\t\t\t},\n\t\t\tWantOutput: listSyslogsShortOutput,\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --verbose\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:  testutil.GetVersion,\n\t\t\t\tListSyslogsFn: listSyslogsOK,\n\t\t\t},\n\t\t\tWantOutput: listSyslogsVerboseOutput,\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 -v\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:  testutil.GetVersion,\n\t\t\t\tListSyslogsFn: listSyslogsOK,\n\t\t\t},\n\t\t\tWantOutput: listSyslogsVerboseOutput,\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:  testutil.GetVersion,\n\t\t\t\tListSyslogsFn: listSyslogsError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"list\"}, scenarios)\n}\n\nfunc TestSyslogDescribe(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"--service-id 123 --version 1\",\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tGetSyslogFn:  getSyslogError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tGetSyslogFn:  getSyslogOK,\n\t\t\t},\n\t\t\tWantOutput: describeSyslogOutput,\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"describe\"}, scenarios)\n}\n\nfunc TestSyslogUpdate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"--service-id 123 --version 1 --new-name log\",\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs --new-name log --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tUpdateSyslogFn: updateSyslogError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs --new-name log --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tUpdateSyslogFn: updateSyslogOK,\n\t\t\t},\n\t\t\tWantOutput: \"Updated Syslog logging endpoint log (service 123 version 4)\",\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"update\"}, scenarios)\n}\n\nfunc TestSyslogDelete(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"--service-id 123 --version 1\",\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tDeleteSyslogFn: deleteSyslogError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name logs --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tDeleteSyslogFn: deleteSyslogOK,\n\t\t\t},\n\t\t\tWantOutput: \"Deleted Syslog logging endpoint logs (service 123 version 4)\",\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, parent.CommandName, sub.CommandName, \"delete\"}, scenarios)\n}\n\nvar errTest = errors.New(\"fixture error\")\n\nfunc createSyslogOK(_ context.Context, i *fastly.CreateSyslogInput) (*fastly.Syslog, error) {\n\treturn &fastly.Syslog{\n\t\tServiceID:      fastly.ToPointer(i.ServiceID),\n\t\tServiceVersion: fastly.ToPointer(i.ServiceVersion),\n\t\tName:           i.Name,\n\t}, nil\n}\n\nfunc createSyslogError(_ context.Context, _ *fastly.CreateSyslogInput) (*fastly.Syslog, error) {\n\treturn nil, errTest\n}\n\nfunc listSyslogsOK(_ context.Context, i *fastly.ListSyslogsInput) ([]*fastly.Syslog, error) {\n\treturn []*fastly.Syslog{\n\t\t{\n\t\t\tServiceID:         fastly.ToPointer(i.ServiceID),\n\t\t\tServiceVersion:    fastly.ToPointer(i.ServiceVersion),\n\t\t\tName:              fastly.ToPointer(\"logs\"),\n\t\t\tAddress:           fastly.ToPointer(\"127.0.0.1\"),\n\t\t\tHostname:          fastly.ToPointer(\"127.0.0.1\"),\n\t\t\tPort:              fastly.ToPointer(514),\n\t\t\tUseTLS:            fastly.ToPointer(false),\n\t\t\tIPV4:              fastly.ToPointer(\"127.0.0.1\"),\n\t\t\tTLSCACert:         fastly.ToPointer(\"-----BEGIN CERTIFICATE-----foo\"),\n\t\t\tTLSHostname:       fastly.ToPointer(\"example.com\"),\n\t\t\tTLSClientCert:     fastly.ToPointer(\"-----BEGIN CERTIFICATE-----bar\"),\n\t\t\tTLSClientKey:      fastly.ToPointer(\"-----BEGIN PRIVATE KEY-----bar\"),\n\t\t\tToken:             fastly.ToPointer(\"tkn\"),\n\t\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\t\tFormatVersion:     fastly.ToPointer(2),\n\t\t\tMessageType:       fastly.ToPointer(\"classic\"),\n\t\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\t\tProcessingRegion:  fastly.ToPointer(\"us\"),\n\t\t},\n\t\t{\n\t\t\tServiceID:         fastly.ToPointer(i.ServiceID),\n\t\t\tServiceVersion:    fastly.ToPointer(i.ServiceVersion),\n\t\t\tName:              fastly.ToPointer(\"analytics\"),\n\t\t\tAddress:           fastly.ToPointer(\"example.com\"),\n\t\t\tHostname:          fastly.ToPointer(\"example.com\"),\n\t\t\tPort:              fastly.ToPointer(789),\n\t\t\tUseTLS:            fastly.ToPointer(true),\n\t\t\tIPV4:              fastly.ToPointer(\"127.0.0.1\"),\n\t\t\tTLSCACert:         fastly.ToPointer(\"-----BEGIN CERTIFICATE-----baz\"),\n\t\t\tTLSHostname:       fastly.ToPointer(\"example.com\"),\n\t\t\tTLSClientCert:     fastly.ToPointer(\"-----BEGIN CERTIFICATE-----qux\"),\n\t\t\tTLSClientKey:      fastly.ToPointer(\"-----BEGIN PRIVATE KEY-----qux\"),\n\t\t\tToken:             fastly.ToPointer(\"tkn\"),\n\t\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\t\tFormatVersion:     fastly.ToPointer(2),\n\t\t\tMessageType:       fastly.ToPointer(\"classic\"),\n\t\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\t\tProcessingRegion:  fastly.ToPointer(\"us\"),\n\t\t},\n\t}, nil\n}\n\nfunc listSyslogsError(_ context.Context, _ *fastly.ListSyslogsInput) ([]*fastly.Syslog, error) {\n\treturn nil, errTest\n}\n\nvar listSyslogsShortOutput = strings.TrimSpace(`\nSERVICE  VERSION  NAME\n123      1        logs\n123      1        analytics\n`) + \"\\n\"\n\nvar listSyslogsVerboseOutput = strings.TrimSpace(`\nFastly API endpoint: https://api.fastly.com\nFastly API token provided via config file (auth: user)\n\nService ID (via --service-id): 123\n\nVersion: 1\n\tSyslog 1/2\n\t\tService ID: 123\n\t\tVersion: 1\n\t\tName: logs\n\t\tAddress: 127.0.0.1\n\t\tHostname: 127.0.0.1\n\t\tPort: 514\n\t\tUse TLS: false\n\t\tIPV4: 127.0.0.1\n\t\tTLS CA certificate: -----BEGIN CERTIFICATE-----foo\n\t\tTLS hostname: example.com\n\t\tTLS client certificate: -----BEGIN CERTIFICATE-----bar\n\t\tTLS client key: -----BEGIN PRIVATE KEY-----bar\n\t\tToken: tkn\n\t\tFormat: %h %l %u %t \"%r\" %>s %b\n\t\tFormat version: 2\n\t\tMessage type: classic\n\t\tResponse condition: Prevent default logging\n\t\tPlacement: none\n\t\tProcessing region: us\n\tSyslog 2/2\n\t\tService ID: 123\n\t\tVersion: 1\n\t\tName: analytics\n\t\tAddress: example.com\n\t\tHostname: example.com\n\t\tPort: 789\n\t\tUse TLS: true\n\t\tIPV4: 127.0.0.1\n\t\tTLS CA certificate: -----BEGIN CERTIFICATE-----baz\n\t\tTLS hostname: example.com\n\t\tTLS client certificate: -----BEGIN CERTIFICATE-----qux\n\t\tTLS client key: -----BEGIN PRIVATE KEY-----qux\n\t\tToken: tkn\n\t\tFormat: %h %l %u %t \"%r\" %>s %b\n\t\tFormat version: 2\n\t\tMessage type: classic\n\t\tResponse condition: Prevent default logging\n\t\tPlacement: none\n\t\tProcessing region: us\n`) + \"\\n\\n\"\n\nfunc getSyslogOK(_ context.Context, i *fastly.GetSyslogInput) (*fastly.Syslog, error) {\n\treturn &fastly.Syslog{\n\t\tServiceID:         fastly.ToPointer(i.ServiceID),\n\t\tServiceVersion:    fastly.ToPointer(i.ServiceVersion),\n\t\tName:              fastly.ToPointer(\"logs\"),\n\t\tAddress:           fastly.ToPointer(\"example.com\"),\n\t\tHostname:          fastly.ToPointer(\"example.com\"),\n\t\tPort:              fastly.ToPointer(514),\n\t\tUseTLS:            fastly.ToPointer(true),\n\t\tIPV4:              fastly.ToPointer(\"\"),\n\t\tTLSCACert:         fastly.ToPointer(\"-----BEGIN CERTIFICATE-----foo\"),\n\t\tTLSHostname:       fastly.ToPointer(\"example.com\"),\n\t\tTLSClientCert:     fastly.ToPointer(\"-----BEGIN CERTIFICATE-----bar\"),\n\t\tTLSClientKey:      fastly.ToPointer(\"-----BEGIN PRIVATE KEY-----bar\"),\n\t\tToken:             fastly.ToPointer(\"tkn\"),\n\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\tFormatVersion:     fastly.ToPointer(2),\n\t\tMessageType:       fastly.ToPointer(\"classic\"),\n\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\tProcessingRegion:  fastly.ToPointer(\"us\"),\n\t}, nil\n}\n\nfunc getSyslogError(_ context.Context, _ *fastly.GetSyslogInput) (*fastly.Syslog, error) {\n\treturn nil, errTest\n}\n\nvar describeSyslogOutput = `\nAddress: example.com\nFormat: %h %l %u %t \"%r\" %>s %b\nFormat version: 2\nHostname: example.com\nIPV4: ` + `\nMessage type: classic\nName: logs\nPlacement: none\nPort: 514\nProcessing region: us\nResponse condition: Prevent default logging\nService ID: 123\nTLS CA certificate: -----BEGIN CERTIFICATE-----foo\nTLS client certificate: -----BEGIN CERTIFICATE-----bar\nTLS client key: -----BEGIN PRIVATE KEY-----bar\nTLS hostname: example.com\nToken: tkn\nUse TLS: true\nVersion: 1\n`\n\nfunc updateSyslogOK(_ context.Context, i *fastly.UpdateSyslogInput) (*fastly.Syslog, error) {\n\treturn &fastly.Syslog{\n\t\tServiceID:         fastly.ToPointer(i.ServiceID),\n\t\tServiceVersion:    fastly.ToPointer(i.ServiceVersion),\n\t\tName:              fastly.ToPointer(\"log\"),\n\t\tAddress:           fastly.ToPointer(\"example.com\"),\n\t\tHostname:          fastly.ToPointer(\"example.com\"),\n\t\tPort:              fastly.ToPointer(514),\n\t\tUseTLS:            fastly.ToPointer(true),\n\t\tIPV4:              fastly.ToPointer(\"\"),\n\t\tTLSCACert:         fastly.ToPointer(\"-----BEGIN CERTIFICATE-----foo\"),\n\t\tTLSHostname:       fastly.ToPointer(\"example.com\"),\n\t\tTLSClientCert:     fastly.ToPointer(\"-----BEGIN CERTIFICATE-----bar\"),\n\t\tTLSClientKey:      fastly.ToPointer(\"-----BEGIN PRIVATE KEY-----bar\"),\n\t\tToken:             fastly.ToPointer(\"tkn\"),\n\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\tFormatVersion:     fastly.ToPointer(2),\n\t\tMessageType:       fastly.ToPointer(\"classic\"),\n\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t}, nil\n}\n\nfunc updateSyslogError(_ context.Context, _ *fastly.UpdateSyslogInput) (*fastly.Syslog, error) {\n\treturn nil, errTest\n}\n\nfunc deleteSyslogOK(_ context.Context, _ *fastly.DeleteSyslogInput) error {\n\treturn nil\n}\n\nfunc deleteSyslogError(_ context.Context, _ *fastly.DeleteSyslogInput) error {\n\treturn errTest\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/syslog/syslog_test.go",
    "content": "package syslog_test\n\nimport (\n\t\"bytes\"\n\t\"testing\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/service/logging/syslog\"\n\t\"github.com/fastly/cli/pkg/config\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/manifest\"\n\t\"github.com/fastly/cli/pkg/mock\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n)\n\nfunc TestCreateSyslogInput(t *testing.T) {\n\tfor _, testcase := range []struct {\n\t\tname      string\n\t\tcmd       *syslog.CreateCommand\n\t\twant      *fastly.CreateSyslogInput\n\t\twantError string\n\t}{\n\t\t{\n\t\t\tname: \"required values set flag serviceID\",\n\t\t\tcmd:  createCommandRequired(),\n\t\t\twant: &fastly.CreateSyslogInput{\n\t\t\t\tServiceID:      \"123\",\n\t\t\t\tServiceVersion: 4,\n\t\t\t\tName:           fastly.ToPointer(\"log\"),\n\t\t\t\tAddress:        fastly.ToPointer(\"example.com\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"all values set flag serviceID\",\n\t\t\tcmd:  createCommandAll(),\n\t\t\twant: &fastly.CreateSyslogInput{\n\t\t\t\tServiceID:         \"123\",\n\t\t\t\tServiceVersion:    4,\n\t\t\t\tName:              fastly.ToPointer(\"log\"),\n\t\t\t\tAddress:           fastly.ToPointer(\"example.com\"),\n\t\t\t\tPort:              fastly.ToPointer(22),\n\t\t\t\tUseTLS:            fastly.ToPointer(fastly.Compatibool(true)),\n\t\t\t\tTLSCACert:         fastly.ToPointer(\"-----BEGIN CERTIFICATE-----foo\"),\n\t\t\t\tTLSHostname:       fastly.ToPointer(\"example.com\"),\n\t\t\t\tTLSClientCert:     fastly.ToPointer(\"-----BEGIN CERTIFICATE-----bar\"),\n\t\t\t\tTLSClientKey:      fastly.ToPointer(\"-----BEGIN PRIVATE KEY-----bar\"),\n\t\t\t\tToken:             fastly.ToPointer(\"tkn\"),\n\t\t\t\tFormat:            fastly.ToPointer(`%h %l %u %t \"%r\" %>s %b`),\n\t\t\t\tFormatVersion:     fastly.ToPointer(2),\n\t\t\t\tMessageType:       fastly.ToPointer(\"classic\"),\n\t\t\t\tResponseCondition: fastly.ToPointer(\"Prevent default logging\"),\n\t\t\t\tPlacement:         fastly.ToPointer(\"none\"),\n\t\t\t\tProcessingRegion:  fastly.ToPointer(\"eu\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:      \"error missing serviceID\",\n\t\t\tcmd:       createCommandMissingServiceID(),\n\t\t\twant:      nil,\n\t\t\twantError: errors.ErrNoServiceID.Error(),\n\t\t},\n\t} {\n\t\tt.Run(testcase.name, func(t *testing.T) {\n\t\t\tif testcase.wantError == errors.ErrNoServiceID.Error() {\n\t\t\t\tt.Setenv(\"FASTLY_SERVICE_ID\", \"\")\n\t\t\t}\n\t\t\tvar bs []byte\n\t\t\tout := bytes.NewBuffer(bs)\n\t\t\tverboseMode := true\n\n\t\t\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\t\t\tAutoCloneFlag:      testcase.cmd.AutoClone,\n\t\t\t\tAPIClient:          testcase.cmd.Globals.APIClient,\n\t\t\t\tManifest:           testcase.cmd.Manifest,\n\t\t\t\tOut:                out,\n\t\t\t\tServiceVersionFlag: testcase.cmd.ServiceVersion,\n\t\t\t\tVerboseMode:        verboseMode,\n\t\t\t})\n\n\t\t\tswitch {\n\t\t\tcase err != nil && testcase.wantError == \"\":\n\t\t\t\tt.Fatalf(\"unexpected error getting service details: %v\", err)\n\t\t\t\treturn\n\t\t\tcase err != nil && testcase.wantError != \"\":\n\t\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\t\treturn\n\t\t\tcase err == nil && testcase.wantError != \"\":\n\t\t\t\tt.Fatalf(\"expected error, have nil (service details: %s, %d)\", serviceID, serviceVersion.Number)\n\t\t\tcase err == nil && testcase.wantError == \"\":\n\t\t\t\thave, err := testcase.cmd.ConstructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\t\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\t\ttestutil.AssertEqual(t, testcase.want, have)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestUpdateSyslogInput(t *testing.T) {\n\tscenarios := []struct {\n\t\tname      string\n\t\tcmd       *syslog.UpdateCommand\n\t\tapi       mock.API\n\t\twant      *fastly.UpdateSyslogInput\n\t\twantError string\n\t}{\n\t\t{\n\t\t\tname: \"no updates\",\n\t\t\tcmd:  updateCommandNoUpdates(),\n\t\t\tapi: mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tGetSyslogFn:    getSyslogOK,\n\t\t\t},\n\t\t\twant: &fastly.UpdateSyslogInput{\n\t\t\t\tServiceID:      \"123\",\n\t\t\t\tServiceVersion: 4,\n\t\t\t\tName:           \"log\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"all values set flag serviceID\",\n\t\t\tcmd:  updateCommandAll(),\n\t\t\tapi: mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tGetSyslogFn:    getSyslogOK,\n\t\t\t},\n\t\t\twant: &fastly.UpdateSyslogInput{\n\t\t\t\tServiceID:         \"123\",\n\t\t\t\tServiceVersion:    4,\n\t\t\t\tName:              \"log\",\n\t\t\t\tNewName:           fastly.ToPointer(\"new1\"),\n\t\t\t\tAddress:           fastly.ToPointer(\"new2\"),\n\t\t\t\tPort:              fastly.ToPointer(23),\n\t\t\t\tUseTLS:            fastly.ToPointer(fastly.Compatibool(false)),\n\t\t\t\tTLSCACert:         fastly.ToPointer(\"new3\"),\n\t\t\t\tTLSHostname:       fastly.ToPointer(\"new4\"),\n\t\t\t\tTLSClientCert:     fastly.ToPointer(\"new5\"),\n\t\t\t\tTLSClientKey:      fastly.ToPointer(\"new6\"),\n\t\t\t\tToken:             fastly.ToPointer(\"new7\"),\n\t\t\t\tFormat:            fastly.ToPointer(\"new8\"),\n\t\t\t\tFormatVersion:     fastly.ToPointer(3),\n\t\t\t\tMessageType:       fastly.ToPointer(\"new9\"),\n\t\t\t\tResponseCondition: fastly.ToPointer(\"new10\"),\n\t\t\t\tPlacement:         fastly.ToPointer(\"new11\"),\n\t\t\t\tProcessingRegion:  fastly.ToPointer(\"eu\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:      \"error missing serviceID\",\n\t\t\tcmd:       updateCommandMissingServiceID(),\n\t\t\twant:      nil,\n\t\t\twantError: errors.ErrNoServiceID.Error(),\n\t\t},\n\t}\n\tfor testcaseIdx := range scenarios {\n\t\ttestcase := &scenarios[testcaseIdx]\n\t\tt.Run(testcase.name, func(t *testing.T) {\n\t\t\tif testcase.wantError == errors.ErrNoServiceID.Error() {\n\t\t\t\tt.Setenv(\"FASTLY_SERVICE_ID\", \"\")\n\t\t\t}\n\t\t\ttestcase.cmd.Globals.APIClient = testcase.api\n\n\t\t\tvar bs []byte\n\t\t\tout := bytes.NewBuffer(bs)\n\t\t\tverboseMode := true\n\n\t\t\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\t\t\tAutoCloneFlag:      testcase.cmd.AutoClone,\n\t\t\t\tAPIClient:          testcase.api,\n\t\t\t\tManifest:           testcase.cmd.Manifest,\n\t\t\t\tOut:                out,\n\t\t\t\tServiceVersionFlag: testcase.cmd.ServiceVersion,\n\t\t\t\tVerboseMode:        verboseMode,\n\t\t\t})\n\n\t\t\tswitch {\n\t\t\tcase err != nil && testcase.wantError == \"\":\n\t\t\t\tt.Fatalf(\"unexpected error getting service details: %v\", err)\n\t\t\t\treturn\n\t\t\tcase err != nil && testcase.wantError != \"\":\n\t\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\t\treturn\n\t\t\tcase err == nil && testcase.wantError != \"\":\n\t\t\t\tt.Fatalf(\"expected error, have nil (service details: %s, %d)\", serviceID, serviceVersion.Number)\n\t\t\tcase err == nil && testcase.wantError == \"\":\n\t\t\t\thave, err := testcase.cmd.ConstructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\t\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\t\ttestutil.AssertEqual(t, testcase.want, have)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc createCommandRequired() *syslog.CreateCommand {\n\tvar b bytes.Buffer\n\n\tg := global.Data{\n\t\tConfig: config.File{},\n\t\tEnv:    config.Environment{},\n\t\tOutput: &b,\n\t}\n\tg.APIClient, _ = mock.APIClient(mock.API{\n\t\tGetVersionFn:   testutil.GetVersion,\n\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t})(\"token\", \"endpoint\", false)\n\n\treturn &syslog.CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: &g,\n\t\t},\n\t\tManifest: manifest.Data{\n\t\t\tFlag: manifest.Flag{\n\t\t\t\tServiceID: \"123\",\n\t\t\t},\n\t\t},\n\t\tEndpointName: argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"log\"},\n\t\tAddress:      argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"example.com\"},\n\t\tServiceVersion: argparser.OptionalServiceVersion{\n\t\t\tOptionalString: argparser.OptionalString{Value: \"1\"},\n\t\t},\n\t\tAutoClone: argparser.OptionalAutoClone{\n\t\t\tOptionalBool: argparser.OptionalBool{\n\t\t\t\tOptional: argparser.Optional{\n\t\t\t\t\tWasSet: true,\n\t\t\t\t},\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t},\n\t}\n}\n\nfunc createCommandAll() *syslog.CreateCommand {\n\tvar b bytes.Buffer\n\n\tg := global.Data{\n\t\tConfig: config.File{},\n\t\tEnv:    config.Environment{},\n\t\tOutput: &b,\n\t}\n\tg.APIClient, _ = mock.APIClient(mock.API{\n\t\tGetVersionFn:   testutil.GetVersion,\n\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t})(\"token\", \"endpoint\", false)\n\n\treturn &syslog.CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: &g,\n\t\t},\n\t\tManifest: manifest.Data{\n\t\t\tFlag: manifest.Flag{\n\t\t\t\tServiceID: \"123\",\n\t\t\t},\n\t\t},\n\t\tServiceVersion: argparser.OptionalServiceVersion{\n\t\t\tOptionalString: argparser.OptionalString{Value: \"1\"},\n\t\t},\n\t\tAutoClone: argparser.OptionalAutoClone{\n\t\t\tOptionalBool: argparser.OptionalBool{\n\t\t\t\tOptional: argparser.Optional{\n\t\t\t\t\tWasSet: true,\n\t\t\t\t},\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t},\n\t\tEndpointName:      argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"log\"},\n\t\tAddress:           argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"example.com\"},\n\t\tFormat:            argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: `%h %l %u %t \"%r\" %>s %b`},\n\t\tFormatVersion:     argparser.OptionalInt{Optional: argparser.Optional{WasSet: true}, Value: 2},\n\t\tResponseCondition: argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"Prevent default logging\"},\n\t\tPlacement:         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"none\"},\n\t\tPort:              argparser.OptionalInt{Optional: argparser.Optional{WasSet: true}, Value: 22},\n\t\tUseTLS:            argparser.OptionalBool{Optional: argparser.Optional{WasSet: true}, Value: true},\n\t\tTLSCACert:         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"-----BEGIN CERTIFICATE-----foo\"},\n\t\tTLSHostname:       argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"example.com\"},\n\t\tTLSClientCert:     argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"-----BEGIN CERTIFICATE-----bar\"},\n\t\tTLSClientKey:      argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"-----BEGIN PRIVATE KEY-----bar\"},\n\t\tToken:             argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"tkn\"},\n\t\tMessageType:       argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"classic\"},\n\t\tProcessingRegion:  argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"eu\"},\n\t}\n}\n\nfunc createCommandMissingServiceID() *syslog.CreateCommand {\n\tres := createCommandAll()\n\tres.Manifest = manifest.Data{}\n\tres.ServiceVersion = argparser.OptionalServiceVersion{}\n\treturn res\n}\n\nfunc updateCommandNoUpdates() *syslog.UpdateCommand {\n\tvar b bytes.Buffer\n\n\tg := global.Data{\n\t\tConfig: config.File{},\n\t\tEnv:    config.Environment{},\n\t\tOutput: &b,\n\t}\n\n\treturn &syslog.UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: &g,\n\t\t},\n\t\tManifest: manifest.Data{\n\t\t\tFlag: manifest.Flag{\n\t\t\t\tServiceID: \"123\",\n\t\t\t},\n\t\t},\n\t\tEndpointName: \"log\",\n\t\tServiceVersion: argparser.OptionalServiceVersion{\n\t\t\tOptionalString: argparser.OptionalString{Value: \"1\"},\n\t\t},\n\t\tAutoClone: argparser.OptionalAutoClone{\n\t\t\tOptionalBool: argparser.OptionalBool{\n\t\t\t\tOptional: argparser.Optional{\n\t\t\t\t\tWasSet: true,\n\t\t\t\t},\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t},\n\t}\n}\n\nfunc updateCommandAll() *syslog.UpdateCommand {\n\tvar b bytes.Buffer\n\n\tg := global.Data{\n\t\tConfig: config.File{},\n\t\tEnv:    config.Environment{},\n\t\tOutput: &b,\n\t}\n\n\treturn &syslog.UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: &g,\n\t\t},\n\t\tManifest: manifest.Data{\n\t\t\tFlag: manifest.Flag{\n\t\t\t\tServiceID: \"123\",\n\t\t\t},\n\t\t},\n\t\tEndpointName: \"log\",\n\t\tServiceVersion: argparser.OptionalServiceVersion{\n\t\t\tOptionalString: argparser.OptionalString{Value: \"1\"},\n\t\t},\n\t\tAutoClone: argparser.OptionalAutoClone{\n\t\t\tOptionalBool: argparser.OptionalBool{\n\t\t\t\tOptional: argparser.Optional{\n\t\t\t\t\tWasSet: true,\n\t\t\t\t},\n\t\t\t\tValue: true,\n\t\t\t},\n\t\t},\n\t\tNewName:           argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new1\"},\n\t\tAddress:           argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new2\"},\n\t\tPort:              argparser.OptionalInt{Optional: argparser.Optional{WasSet: true}, Value: 23},\n\t\tUseTLS:            argparser.OptionalBool{Optional: argparser.Optional{WasSet: true}, Value: false},\n\t\tTLSCACert:         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new3\"},\n\t\tTLSHostname:       argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new4\"},\n\t\tTLSClientCert:     argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new5\"},\n\t\tTLSClientKey:      argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new6\"},\n\t\tToken:             argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new7\"},\n\t\tFormat:            argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new8\"},\n\t\tFormatVersion:     argparser.OptionalInt{Optional: argparser.Optional{WasSet: true}, Value: 3},\n\t\tMessageType:       argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new9\"},\n\t\tResponseCondition: argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new10\"},\n\t\tPlacement:         argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"new11\"},\n\t\tProcessingRegion:  argparser.OptionalString{Optional: argparser.Optional{WasSet: true}, Value: \"eu\"},\n\t}\n}\n\nfunc updateCommandMissingServiceID() *syslog.UpdateCommand {\n\tres := updateCommandAll()\n\tres.Manifest = manifest.Data{}\n\tres.ServiceVersion = argparser.OptionalServiceVersion{}\n\treturn res\n}\n"
  },
  {
    "path": "pkg/commands/service/logging/syslog/update.go",
    "content": "package syslog\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/commands/service/logging/logflags\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/manifest\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// UpdateCommand calls the Fastly API to update a Syslog logging endpoint.\ntype UpdateCommand struct {\n\targparser.Base\n\tManifest manifest.Data\n\n\t// Required.\n\tEndpointName   string\n\tServiceName    argparser.OptionalServiceNameID\n\tServiceVersion argparser.OptionalServiceVersion\n\n\t// Optional.\n\tAddress           argparser.OptionalString\n\tAutoClone         argparser.OptionalAutoClone\n\tFormat            argparser.OptionalString\n\tFormatVersion     argparser.OptionalInt\n\tMessageType       argparser.OptionalString\n\tNewName           argparser.OptionalString\n\tPlacement         argparser.OptionalString\n\tPort              argparser.OptionalInt\n\tProcessingRegion  argparser.OptionalString\n\tResponseCondition argparser.OptionalString\n\tTLSCACert         argparser.OptionalString\n\tTLSClientCert     argparser.OptionalString\n\tTLSClientKey      argparser.OptionalString\n\tTLSHostname       argparser.OptionalString\n\tToken             argparser.OptionalString\n\tUseTLS            argparser.OptionalBool\n}\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"update\", \"Update a Syslog logging endpoint on a Fastly service version\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"The name of the Syslog logging object\").Short('n').Required().StringVar(&c.EndpointName)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.ServiceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.CmdClause.Flag(\"address\", \"A hostname or IPv4 address\").Action(c.Address.Set).StringVar(&c.Address.Value)\n\tc.CmdClause.Flag(\"auth-token\", \"Whether to prepend each message with a specific token\").Action(c.Token.Set).StringVar(&c.Token.Value)\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.AutoClone.Set,\n\t\tDst:    &c.AutoClone.Value,\n\t})\n\tlogflags.Format(c.CmdClause, &c.Format)\n\tlogflags.FormatVersion(c.CmdClause, &c.FormatVersion)\n\tc.CmdClause.Flag(\"new-name\", \"New name of the Syslog logging object\").Action(c.NewName.Set).StringVar(&c.NewName.Value)\n\tlogflags.MessageType(c.CmdClause, &c.MessageType)\n\tlogflags.Placement(c.CmdClause, &c.Placement)\n\tlogflags.ProcessingRegion(c.CmdClause, &c.ProcessingRegion, \"syslog\")\n\tc.CmdClause.Flag(\"port\", \"The port number\").Action(c.Port.Set).IntVar(&c.Port.Value)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.ServiceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.ServiceName.Value,\n\t})\n\tlogflags.ResponseCondition(c.CmdClause, &c.ResponseCondition)\n\tlogflags.TLSCACert(c.CmdClause, &c.TLSCACert)\n\tlogflags.TLSClientCert(c.CmdClause, &c.TLSClientCert)\n\tlogflags.TLSClientKey(c.CmdClause, &c.TLSClientKey)\n\tc.CmdClause.Flag(\"tls-hostname\", \"Used during the TLS handshake to validate the certificate\").Action(c.TLSHostname.Set).StringVar(&c.TLSHostname.Value)\n\tc.CmdClause.Flag(\"use-tls\", \"Whether to use TLS for secure logging. Can be either true or false\").Action(c.UseTLS.Set).BoolVar(&c.UseTLS.Value)\n\treturn &c\n}\n\n// ConstructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *UpdateCommand) ConstructInput(serviceID string, serviceVersion int) (*fastly.UpdateSyslogInput, error) {\n\tinput := fastly.UpdateSyslogInput{\n\t\tServiceID:      serviceID,\n\t\tServiceVersion: serviceVersion,\n\t\tName:           c.EndpointName,\n\t}\n\n\t// Set new values if set by user.\n\tif c.NewName.WasSet {\n\t\tinput.NewName = &c.NewName.Value\n\t}\n\n\tif c.Address.WasSet {\n\t\tinput.Address = &c.Address.Value\n\t}\n\n\tif c.Port.WasSet {\n\t\tinput.Port = &c.Port.Value\n\t}\n\n\tif c.UseTLS.WasSet {\n\t\tinput.UseTLS = fastly.ToPointer(fastly.Compatibool(c.UseTLS.Value))\n\t}\n\n\tif c.TLSCACert.WasSet {\n\t\tinput.TLSCACert = &c.TLSCACert.Value\n\t}\n\n\tif c.TLSHostname.WasSet {\n\t\tinput.TLSHostname = &c.TLSHostname.Value\n\t}\n\n\tif c.TLSClientCert.WasSet {\n\t\tinput.TLSClientCert = &c.TLSClientCert.Value\n\t}\n\n\tif c.TLSClientKey.WasSet {\n\t\tinput.TLSClientKey = &c.TLSClientKey.Value\n\t}\n\n\tif c.Token.WasSet {\n\t\tinput.Token = &c.Token.Value\n\t}\n\n\tif c.Format.WasSet {\n\t\tinput.Format = fastly.ToPointer(argparser.Content(c.Format.Value))\n\t}\n\n\tif c.FormatVersion.WasSet {\n\t\tinput.FormatVersion = &c.FormatVersion.Value\n\t}\n\n\tif c.MessageType.WasSet {\n\t\tinput.MessageType = &c.MessageType.Value\n\t}\n\n\tif c.ResponseCondition.WasSet {\n\t\tinput.ResponseCondition = &c.ResponseCondition.Value\n\t}\n\n\tif c.Placement.WasSet {\n\t\tinput.Placement = &c.Placement.Value\n\t}\n\n\tif c.ProcessingRegion.WasSet {\n\t\tinput.ProcessingRegion = &c.ProcessingRegion.Value\n\t}\n\n\treturn &input, nil\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *UpdateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(false),\n\t\tLocked:             optional.Of(false),\n\t\tAutoCloneFlag:      c.AutoClone,\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.ServiceName,\n\t\tServiceVersionFlag: c.ServiceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": errors.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tinput, err := c.ConstructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tsyslog, err := c.Globals.APIClient.UpdateSyslog(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\ttext.Success(\n\t\tout,\n\t\t\"Updated Syslog logging endpoint %s (service %s version %d)\",\n\t\tfastly.ToValue(syslog.Name),\n\t\tfastly.ToValue(syslog.ServiceID),\n\t\tfastly.ToValue(syslog.ServiceVersion),\n\t)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/purge/doc.go",
    "content": "// Package purge contains commands to inspect and manipulate Fastly edge cache.\npackage purge\n"
  },
  {
    "path": "pkg/commands/service/purge/purge.go",
    "content": "package purge\n\nimport (\n\t\"bufio\"\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"sort\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/manifest\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"purge\"\n\n// PurgeCommand calls the Fastly API to purge items from the cache.\ntype PurgeCommand struct { //revive:disable:exported\n\targparser.Base\n\n\tall         bool\n\tfile        string\n\tkey         string\n\tserviceName argparser.OptionalServiceNameID\n\tsoft        bool\n\turl         string\n}\n\n// NewPurgeCommand returns a usable command registered under the parent.\nfunc NewPurgeCommand(parent argparser.Registerer, g *global.Data) *PurgeCommand {\n\tvar c PurgeCommand\n\n\tc.CmdClause = parent.Command(CommandName, \"Invalidate objects in the Fastly cache\")\n\tc.Globals = g\n\n\t// Optional.\n\tc.CmdClause.Flag(\"all\", \"Purge everything from a service\").BoolVar(&c.all)\n\tc.CmdClause.Flag(\"file\", \"Purge a service of a newline delimited list of Surrogate Keys\").StringVar(&c.file)\n\tc.CmdClause.Flag(\"key\", \"Purge a service of objects tagged with a Surrogate Key\").StringVar(&c.key)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\tc.CmdClause.Flag(\"soft\", \"A 'soft' purge marks affected objects as stale rather than making them inaccessible\").BoolVar(&c.soft)\n\tc.CmdClause.Flag(\"url\", \"Purge an individual URL\").StringVar(&c.url)\n\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *PurgeCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, source, flag, err := argparser.ServiceID(c.serviceName, *c.Globals.Manifest, c.Globals.APIClient, c.Globals.ErrLog)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif c.Globals.Verbose() {\n\t\targparser.DisplayServiceID(serviceID, flag, source, out)\n\t}\n\n\t// The URL purge API call doesn't require a Service ID.\n\tif c.url == \"\" {\n\t\tif source == manifest.SourceUndefined {\n\t\t\treturn fsterr.ErrNoServiceID\n\t\t}\n\t}\n\n\tif c.all {\n\t\tif c.soft {\n\t\t\treturn fsterr.RemediationError{\n\t\t\t\tInner:       fmt.Errorf(\"purge-all requests cannot be done in soft mode (--soft) and will always immediately invalidate all cached content associated with the service\"),\n\t\t\t\tRemediation: \"The --soft flag should not be used with --all so retry command without it.\",\n\t\t\t}\n\t\t}\n\t\terr := c.purgeAll(serviceID, out)\n\t\tif err != nil {\n\t\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\t\"Service ID\": serviceID,\n\t\t\t\t\"All\":        c.all,\n\t\t\t})\n\t\t\treturn err\n\t\t}\n\t\treturn nil\n\t}\n\n\tif c.file != \"\" {\n\t\terr := c.purgeKeys(serviceID, out)\n\t\tif err != nil {\n\t\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\t\"Service ID\": serviceID,\n\t\t\t\t\"File\":       c.file,\n\t\t\t})\n\t\t\treturn err\n\t\t}\n\t\treturn nil\n\t}\n\n\tif c.key != \"\" {\n\t\terr := c.purgeKey(serviceID, out)\n\t\tif err != nil {\n\t\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\t\"Service ID\": serviceID,\n\t\t\t\t\"Key\":        c.key,\n\t\t\t})\n\t\t\treturn err\n\t\t}\n\t\treturn nil\n\t}\n\n\tif c.url != \"\" {\n\t\terr := c.purgeURL(out)\n\t\tif err != nil {\n\t\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\t\"URL\": c.url,\n\t\t\t})\n\t\t\treturn err\n\t\t}\n\t\treturn nil\n\t}\n\n\treturn nil\n}\n\nfunc (c *PurgeCommand) purgeAll(serviceID string, out io.Writer) error {\n\tp, err := c.Globals.APIClient.PurgeAll(context.TODO(), &fastly.PurgeAllInput{\n\t\tServiceID: serviceID,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\": serviceID,\n\t\t})\n\t\treturn err\n\t}\n\ttext.Success(out, \"Purge all status: %s\", fastly.ToValue(p.Status))\n\treturn nil\n}\n\n// purgeKey now uses the bulk purge endpoint to avoid serialization of the 'key' field values.\n// This serialization occurs due to the nature of the POST /service/{service_id}/purge/{surrogate_key}\n// endpoint storing the 'key' as part of the URL.\nfunc (c *PurgeCommand) purgeKey(serviceID string, out io.Writer) error {\n\tm, err := c.Globals.APIClient.PurgeKeys(context.TODO(), &fastly.PurgeKeysInput{\n\t\tServiceID: serviceID,\n\t\tKeys:      []string{c.key},\n\t\tSoft:      c.soft,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\": serviceID,\n\t\t\t\"Key\":        c.key,\n\t\t\t\"Soft\":       c.soft,\n\t\t})\n\t\treturn err\n\t}\n\tpurgeID, ok := m[c.key]\n\tif !ok {\n\t\treturn fmt.Errorf(\"no purge ID returned for key: %s\", c.key)\n\t}\n\t// The bulk purge endpoint doesn't return a 'Status' field like the single-key\n\t// endpoint did. To avoid a breaking change in the CLI output, we hardcode\n\t// 'Status: ok' in the success message to maintain consistent behavior.\n\ttext.Success(out, \"Purged key: %s (soft: %t). Status: ok, ID: %s\", c.key, c.soft, purgeID)\n\treturn nil\n}\n\nfunc (c *PurgeCommand) purgeKeys(serviceID string, out io.Writer) error {\n\tkeys, err := populateKeys(c.file, c.Globals.ErrLog)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\": serviceID,\n\t\t})\n\t\treturn err\n\t}\n\n\treturn c.purgeBulkKeys(serviceID, keys, out)\n}\n\nfunc (c *PurgeCommand) purgeBulkKeys(serviceID string, keys []string, out io.Writer) error {\n\tm, err := c.Globals.APIClient.PurgeKeys(context.TODO(), &fastly.PurgeKeysInput{\n\t\tServiceID: serviceID,\n\t\tKeys:      keys,\n\t\tSoft:      c.soft,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\": serviceID,\n\t\t\t\"Keys\":       keys,\n\t\t\t\"Soft\":       c.soft,\n\t\t})\n\t\treturn err\n\t}\n\n\tsortedKeys := make([]string, 0, len(m))\n\tfor k := range m {\n\t\tsortedKeys = append(sortedKeys, k)\n\t}\n\tsort.Strings(sortedKeys)\n\n\tt := text.NewTable(out)\n\tt.AddHeader(\"KEY\", \"ID\")\n\tfor _, k := range sortedKeys {\n\t\tt.AddLine(k, m[k])\n\t}\n\tt.Print()\n\n\treturn nil\n}\n\nfunc (c *PurgeCommand) purgeURL(out io.Writer) error {\n\tp, err := c.Globals.APIClient.Purge(context.TODO(), &fastly.PurgeInput{\n\t\tURL:  c.url,\n\t\tSoft: c.soft,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"URL\":  c.url,\n\t\t\t\"Soft\": c.soft,\n\t\t})\n\t\treturn err\n\t}\n\ttext.Success(out, \"Purged URL: %s (soft: %t). Status: %s, ID: %s\", c.url, c.soft, fastly.ToValue(p.Status), fastly.ToValue(p.PurgeID))\n\treturn nil\n}\n\n// populateKeys opens the given file path, initializes a scanner, and appends\n// each line of the file (expected to be a surrogate key) to a slice.\nfunc populateKeys(fpath string, errLog fsterr.LogInterface) (keys []string, err error) {\n\tvar (\n\t\tfile io.Reader\n\t\tpath string\n\t)\n\n\tif path, err = filepath.Abs(fpath); err == nil {\n\t\tif _, err = os.Stat(path); err == nil {\n\t\t\tif file, err = os.Open(path); err == nil /* #nosec */ {\n\t\t\t\tscanner := bufio.NewScanner(file)\n\t\t\t\tfor scanner.Scan() {\n\t\t\t\t\tkeys = append(keys, scanner.Text())\n\t\t\t\t}\n\t\t\t\terr = scanner.Err()\n\t\t\t}\n\t\t}\n\t}\n\n\tif err != nil {\n\t\terrLog.Add(err)\n\t\treturn nil, err\n\t}\n\treturn keys, nil\n}\n"
  },
  {
    "path": "pkg/commands/service/purge/purge_test.go",
    "content": "package purge_test\n\nimport (\n\t\"context\"\n\t\"reflect\"\n\t\"testing\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\troot \"github.com/fastly/cli/pkg/commands/service\"\n\tpurge \"github.com/fastly/cli/pkg/commands/service/purge\"\n\t\"github.com/fastly/cli/pkg/mock\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n)\n\nfunc TestPurgeAll(t *testing.T) {\n\tconst (\n\t\ttestServiceID = \"123\"\n\t\ttestStatus    = \"ok\"\n\t)\n\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --service-id flag\",\n\t\t\tArgs:      \"--all\",\n\t\t\tEnvVars:   map[string]string{\"FASTLY_SERVICE_ID\": \"\"},\n\t\t\tWantError: \"error reading service: no service ID found\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate --soft flag isn't usable\",\n\t\t\tArgs:      \"--all --service-id \" + testServiceID + \" --soft\",\n\t\t\tWantError: \"purge-all requests cannot be done in soft mode (--soft) and will always immediately invalidate all cached content associated with the service\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate PurgeAll API error\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tPurgeAllFn: func(_ context.Context, _ *fastly.PurgeAllInput) (*fastly.Purge, error) {\n\t\t\t\t\treturn nil, testutil.Err\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:      \"--all --service-id \" + testServiceID,\n\t\t\tWantError: testutil.Err.Error(),\n\t\t},\n\t\t{\n\t\t\tName: \"validate PurgeAll API success\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tPurgeAllFn: func(_ context.Context, _ *fastly.PurgeAllInput) (*fastly.Purge, error) {\n\t\t\t\t\treturn &fastly.Purge{\n\t\t\t\t\t\tStatus: fastly.ToPointer(testStatus),\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:       \"--all --service-id \" + testServiceID,\n\t\t\tWantOutput: \"Purge all status: \" + testStatus,\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, purge.CommandName}, scenarios)\n}\n\nfunc TestPurgeKeys(t *testing.T) {\n\tconst (\n\t\ttestServiceID = \"123\"\n\t\ttestKey1      = \"foo\"\n\t\ttestKey2      = \"bar\"\n\t\ttestKey3      = \"baz\"\n\t\ttestPurgeID1  = \"123\"\n\t\ttestPurgeID2  = \"456\"\n\t\ttestPurgeID3  = \"789\"\n\t)\n\n\tvar keys []string\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --service-id flag\",\n\t\t\tArgs:      \"--file ./testdata/keys\",\n\t\t\tEnvVars:   map[string]string{\"FASTLY_SERVICE_ID\": \"\"},\n\t\t\tWantError: \"error reading service: no service ID found\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate PurgeKeys API error\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tPurgeKeysFn: func(_ context.Context, _ *fastly.PurgeKeysInput) (map[string]string, error) {\n\t\t\t\t\treturn nil, testutil.Err\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:      \"--file ./testdata/keys --service-id \" + testServiceID,\n\t\t\tWantError: testutil.Err.Error(),\n\t\t},\n\t\t{\n\t\t\tName: \"validate PurgeKeys API success\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tPurgeKeysFn: func(_ context.Context, i *fastly.PurgeKeysInput) (map[string]string, error) {\n\t\t\t\t\t// Track the keys parsed\n\t\t\t\t\tkeys = i.Keys\n\n\t\t\t\t\treturn map[string]string{\n\t\t\t\t\t\ttestKey1: testPurgeID1,\n\t\t\t\t\t\ttestKey2: testPurgeID2,\n\t\t\t\t\t\ttestKey3: testPurgeID3,\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:       \"--file ./testdata/keys --service-id \" + testServiceID,\n\t\t\tWantOutput: \"KEY  ID\\nbar  456\\nbaz  789\\nfoo  123\\n\",\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, purge.CommandName}, scenarios)\n\tassertKeys(keys, t)\n}\n\n// assertKeys validates that the --file flag is parsed correctly. It does this\n// by ensuring the internal logic has parsed the given file and generated the\n// correct []string type.\nfunc assertKeys(keys []string, t *testing.T) {\n\twant := []string{\"foo\", \"bar\", \"baz\"}\n\tif !reflect.DeepEqual(keys, want) {\n\t\tt.Errorf(\"wanted %s, have %s\", want, keys)\n\t}\n}\n\nfunc TestPurgeKey(t *testing.T) {\n\tconst (\n\t\ttestServiceID = \"123\"\n\t\ttestKey       = \"foobar\"\n\t\ttestPurgeID   = \"123\"\n\t\ttestStatus    = \"ok\"\n\t)\n\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --service-id flag\",\n\t\t\tArgs:      \"--key \" + testKey,\n\t\t\tEnvVars:   map[string]string{\"FASTLY_SERVICE_ID\": \"\"},\n\t\t\tWantError: \"error reading service: no service ID found\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate PurgeKeys API error\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tPurgeKeysFn: func(_ context.Context, _ *fastly.PurgeKeysInput) (map[string]string, error) {\n\t\t\t\t\treturn nil, testutil.Err\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:      \"--key \" + testKey + \" --service-id \" + testServiceID,\n\t\t\tWantError: testutil.Err.Error(),\n\t\t},\n\t\t{\n\t\t\tName: \"validate PurgeKeys API success\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tPurgeKeysFn: func(_ context.Context, _ *fastly.PurgeKeysInput) (map[string]string, error) {\n\t\t\t\t\treturn map[string]string{\n\t\t\t\t\t\ttestKey: testPurgeID,\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:       \"--key \" + testKey + \" --service-id \" + testServiceID,\n\t\t\tWantOutput: \"Purged key: \" + testKey + \" (soft: false). Status: \" + testStatus + \", ID: \" + testPurgeID,\n\t\t},\n\t\t{\n\t\t\tName: \"validate PurgeKeys API success with soft purge\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tPurgeKeysFn: func(_ context.Context, _ *fastly.PurgeKeysInput) (map[string]string, error) {\n\t\t\t\t\treturn map[string]string{\n\t\t\t\t\t\ttestKey: testPurgeID,\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:       \"--key \" + testKey + \" --service-id \" + testServiceID + \" --soft\",\n\t\t\tWantOutput: \"Purged key: \" + testKey + \" (soft: true). Status: \" + testStatus + \", ID: \" + testPurgeID,\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, purge.CommandName}, scenarios)\n}\n\nfunc TestPurgeURL(t *testing.T) {\n\tconst (\n\t\ttestServiceID = \"123\"\n\t\ttestURL       = \"https://example.com\"\n\t\ttestPurgeID   = \"123\"\n\t\ttestStatus    = \"ok\"\n\t)\n\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName: \"validate Purge API error\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tPurgeFn: func(_ context.Context, _ *fastly.PurgeInput) (*fastly.Purge, error) {\n\t\t\t\t\treturn nil, testutil.Err\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:      \"--service-id \" + testServiceID + \" --url \" + testURL,\n\t\t\tWantError: testutil.Err.Error(),\n\t\t},\n\t\t{\n\t\t\tName: \"validate Purge API success\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tPurgeFn: func(_ context.Context, _ *fastly.PurgeInput) (*fastly.Purge, error) {\n\t\t\t\t\treturn &fastly.Purge{\n\t\t\t\t\t\tStatus:  fastly.ToPointer(testStatus),\n\t\t\t\t\t\tPurgeID: fastly.ToPointer(testPurgeID),\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:       \"--service-id \" + testServiceID + \" --url \" + testURL,\n\t\t\tWantOutput: \"Purged URL: \" + testURL + \" (soft: false). Status: \" + testStatus + \", ID: \" + testPurgeID,\n\t\t},\n\t\t{\n\t\t\tName: \"validate Purge API success with soft purge\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tPurgeFn: func(_ context.Context, _ *fastly.PurgeInput) (*fastly.Purge, error) {\n\t\t\t\t\treturn &fastly.Purge{\n\t\t\t\t\t\tStatus:  fastly.ToPointer(testStatus),\n\t\t\t\t\t\tPurgeID: fastly.ToPointer(testPurgeID),\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:       \"--service-id \" + testServiceID + \" --soft --url \" + testURL,\n\t\t\tWantOutput: \"Purged URL: \" + testURL + \" (soft: true). Status: \" + testStatus + \", ID: \" + testPurgeID,\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, purge.CommandName}, scenarios)\n}\n"
  },
  {
    "path": "pkg/commands/service/purge/testdata/keys",
    "content": "foo\nbar\nbaz\n"
  },
  {
    "path": "pkg/commands/service/ratelimit/create.go",
    "content": "package ratelimit\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"strings\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// rateLimitActionFlagOpts is a string representation of rateLimitActions\n// suitable for use within the enum flag definition below.\nvar rateLimitActionFlagOpts = func() (actions []string) {\n\tfor _, a := range fastly.ERLActions {\n\t\tactions = append(actions, string(a))\n\t}\n\treturn actions\n}()\n\n// rateLimitLoggerFlagOpts is a string representation of rateLimitLoggers\n// suitable for use within the enum flag definition below.\nvar rateLimitLoggerFlagOpts = func() (loggers []string) {\n\tfor _, l := range fastly.ERLLoggers {\n\t\tloggers = append(loggers, string(l))\n\t}\n\treturn loggers\n}()\n\n// rateLimitWindowSizeFlagOpts is a string representation of rateLimitWindowSizes\n// suitable for use within the enum flag definition below.\nvar rateLimitWindowSizeFlagOpts = func() (windowSizes []string) {\n\tfor _, w := range fastly.ERLWindowSizes {\n\t\twindowSizes = append(windowSizes, fmt.Sprint(w))\n\t}\n\treturn windowSizes\n}()\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\n\tc.CmdClause = parent.Command(\"create\", \"Create a rate limiter for a particular service and version\").Alias(\"add\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.CmdClause.Flag(\"action\", \"The action to take when a rate limiter violation is detected\").HintOptions(rateLimitActionFlagOpts...).EnumVar(&c.action, rateLimitActionFlagOpts...)\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.autoClone.Set,\n\t\tDst:    &c.autoClone.Value,\n\t})\n\tc.CmdClause.Flag(\"client-key\", \"Comma-separated list of VCL variable used to generate a counter key to identify a client\").StringVar(&c.clientKeys)\n\tc.CmdClause.Flag(\"feature-revision\", \"Revision number of the rate limiting feature implementation\").IntVar(&c.featRevision)\n\tc.CmdClause.Flag(\"http-methods\", \"Comma-separated list of HTTP methods to apply rate limiting to\").StringVar(&c.httpMethods)\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\tc.CmdClause.Flag(\"logger-type\", \"Name of the type of logging endpoint to be used when action is `log_only`\").HintOptions(rateLimitLoggerFlagOpts...).EnumVar(&c.loggerType, rateLimitLoggerFlagOpts...)\n\tc.CmdClause.Flag(\"name\", \"A human readable name for the rate limiting rule\").StringVar(&c.name)\n\tc.CmdClause.Flag(\"penalty-box-dur\", \"Length of time in minutes that the rate limiter is in effect after the initial violation is detected\").IntVar(&c.penaltyDuration)\n\tc.CmdClause.Flag(\"response-content\", \"HTTP response body data\").StringVar(&c.responseContent)\n\tc.CmdClause.Flag(\"response-content-type\", \"HTTP Content-Type (e.g. application/json)\").StringVar(&c.responseContentType)\n\tc.CmdClause.Flag(\"response-object-name\", \"Name of existing response object. Required if action is response_object\").StringVar(&c.responseObjectName)\n\tc.CmdClause.Flag(\"response-status\", \"HTTP response status code (e.g. 429)\").IntVar(&c.responseStatus)\n\tc.CmdClause.Flag(\"rps-limit\", \"Upper limit of requests per second allowed by the rate limiter\").IntVar(&c.rpsLimit)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\tc.CmdClause.Flag(\"uri-dict-name\", \"The name of an Edge Dictionary containing URIs as keys\").StringVar(&c.uriDictName)\n\tc.CmdClause.Flag(\"window-size\", \"Number of seconds during which the RPS limit must be exceeded in order to trigger a violation\").HintOptions(rateLimitWindowSizeFlagOpts...).EnumVar(&c.windowSize, rateLimitWindowSizeFlagOpts...)\n\n\treturn &c\n}\n\n// CreateCommand calls the Fastly API to create an appropriate resource.\ntype CreateCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\taction              string\n\tautoClone           argparser.OptionalAutoClone\n\tclientKeys          string\n\tfeatRevision        int\n\thttpMethods         string\n\tloggerType          string\n\tname                string\n\tpenaltyDuration     int\n\tresponseContent     string\n\tresponseContentType string\n\tresponseObjectName  string\n\tresponseStatus      int\n\trpsLimit            int\n\tserviceName         argparser.OptionalServiceNameID\n\tserviceVersion      argparser.OptionalServiceVersion\n\turiDictName         string\n\twindowSize          string\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *CreateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tif err := c.responseFlagValidator(); err != nil {\n\t\treturn fsterr.RemediationError{\n\t\t\tInner:       err,\n\t\t\tRemediation: \"When defining a response, all response flags (--response-content, --response-content-type, --response-status) should be set\",\n\t\t}\n\t}\n\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(false),\n\t\tLocked:             optional.Of(false),\n\t\tAutoCloneFlag:      c.autoClone,\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fsterr.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tinput := c.constructInput()\n\tinput.ServiceID = serviceID\n\tinput.ServiceVersion = fastly.ToValue(serviceVersion.Number)\n\n\to, err := c.Globals.APIClient.CreateERL(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fsterr.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Created rate limiter '%s' (%s)\", fastly.ToValue(o.Name), fastly.ToValue(o.RateLimiterID))\n\treturn nil\n}\n\n// constructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *CreateCommand) constructInput() *fastly.CreateERLInput {\n\tvar input fastly.CreateERLInput\n\n\tif c.action != \"\" {\n\t\tfor _, a := range fastly.ERLActions {\n\t\t\tif c.action == string(a) {\n\t\t\t\tinput.Action = fastly.ToPointer(a)\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\tif c.clientKeys != \"\" {\n\t\tclientKeys := strings.Split(strings.ReplaceAll(c.clientKeys, \" \", \"\"), \",\")\n\t\tinput.ClientKey = &clientKeys\n\t}\n\n\tif c.featRevision > 0 {\n\t\tinput.FeatureRevision = fastly.ToPointer(c.featRevision)\n\t}\n\n\tif c.httpMethods != \"\" {\n\t\thttpMethods := strings.Split(strings.ReplaceAll(c.httpMethods, \" \", \"\"), \",\")\n\t\tinput.HTTPMethods = &httpMethods\n\t}\n\n\tif c.loggerType != \"\" {\n\t\tfor _, l := range fastly.ERLLoggers {\n\t\t\tif c.loggerType == string(l) {\n\t\t\t\tinput.LoggerType = fastly.ToPointer(l)\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\tif c.name != \"\" {\n\t\tinput.Name = fastly.ToPointer(c.name)\n\t}\n\n\tif c.penaltyDuration > 0 {\n\t\tinput.PenaltyBoxDuration = fastly.ToPointer(c.penaltyDuration)\n\t}\n\n\tif c.responseContent != \"\" && c.responseContentType != \"\" && c.responseStatus > 0 {\n\t\tinput.Response = &fastly.ERLResponseType{\n\t\t\tERLContent:     fastly.ToPointer(c.responseContent),\n\t\t\tERLContentType: fastly.ToPointer(c.responseContentType),\n\t\t\tERLStatus:      fastly.ToPointer(c.responseStatus),\n\t\t}\n\t}\n\n\tif c.responseObjectName != \"\" {\n\t\tinput.ResponseObjectName = fastly.ToPointer(c.responseObjectName)\n\t}\n\n\tif c.rpsLimit > 0 {\n\t\tinput.RpsLimit = fastly.ToPointer(c.rpsLimit)\n\t}\n\n\tif c.uriDictName != \"\" {\n\t\tinput.URIDictionaryName = fastly.ToPointer(c.uriDictName)\n\t}\n\n\tif c.windowSize != \"\" {\n\t\tfor _, w := range fastly.ERLWindowSizes {\n\t\t\tif c.windowSize == fmt.Sprint(w) {\n\t\t\t\tinput.WindowSize = fastly.ToPointer(w)\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\treturn &input\n}\n\n// responseFlagValidator ensures if a user specifies one of the response flags,\n// that they must specify ALL of the response flags.\nfunc (c *CreateCommand) responseFlagValidator() error {\n\tvar state int\n\tif c.responseContent != \"\" {\n\t\tstate++\n\t}\n\tif c.responseContentType != \"\" {\n\t\tstate++\n\t}\n\tif c.responseStatus > 0 {\n\t\tstate++\n\t}\n\tif state > 0 && state < 3 {\n\t\treturn errors.New(\"invalid flag use\")\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/ratelimit/delete.go",
    "content": "package ratelimit\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, globals *global.Data) *DeleteCommand {\n\tvar c DeleteCommand\n\tc.CmdClause = parent.Command(\"delete\", \"Delete a rate limiter by its ID\").Alias(\"remove\")\n\tc.Globals = globals\n\n\t// Required.\n\tc.CmdClause.Flag(\"id\", \"Alphanumeric string identifying the rate limiter\").Required().StringVar(&c.id)\n\n\treturn &c\n}\n\n// DeleteCommand calls the Fastly API to delete an appropriate resource.\ntype DeleteCommand struct {\n\targparser.Base\n\n\tid string\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DeleteCommand) Exec(_ io.Reader, out io.Writer) error {\n\tinput := c.constructInput()\n\n\terr := c.Globals.APIClient.DeleteERL(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"User ID\": c.id,\n\t\t})\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Deleted rate limiter '%s'\", c.id)\n\treturn nil\n}\n\n// constructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *DeleteCommand) constructInput() *fastly.DeleteERLInput {\n\tvar input fastly.DeleteERLInput\n\n\tinput.ERLID = c.id\n\n\treturn &input\n}\n"
  },
  {
    "path": "pkg/commands/service/ratelimit/describe.go",
    "content": "package ratelimit\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// NewDescribeCommand returns a usable command registered under the parent.\nfunc NewDescribeCommand(parent argparser.Registerer, g *global.Data) *DescribeCommand {\n\tvar c DescribeCommand\n\tc.CmdClause = parent.Command(\"describe\", \"Get a rate limiter by its ID\").Alias(\"get\")\n\tc.Globals = g\n\n\t// Required.\n\tc.CmdClause.Flag(\"id\", \"Alphanumeric string identifying the rate limiter\").Required().StringVar(&c.id)\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\n\treturn &c\n}\n\n// DescribeCommand calls the Fastly API to describe an appropriate resource.\ntype DescribeCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tid string\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DescribeCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\to, err := c.Globals.APIClient.GetERL(context.TODO(), c.constructInput())\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\tc.print(out, o)\n\treturn nil\n}\n\n// constructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *DescribeCommand) constructInput() *fastly.GetERLInput {\n\tvar input fastly.GetERLInput\n\tinput.ERLID = c.id\n\treturn &input\n}\n\n// print displays the information returned from the API.\nfunc (c *DescribeCommand) print(out io.Writer, o *fastly.ERL) {\n\tfmt.Fprintf(out, \"\\nAction: %+v\\n\", fastly.ToValue(o.Action))\n\tfmt.Fprintf(out, \"Client Key: %+v\\n\", o.ClientKey)\n\tfmt.Fprintf(out, \"Feature Revision: %+v\\n\", fastly.ToValue(o.FeatureRevision))\n\tfmt.Fprintf(out, \"HTTP Methods: %+v\\n\", o.HTTPMethods)\n\tfmt.Fprintf(out, \"ID: %+v\\n\", fastly.ToValue(o.RateLimiterID))\n\tfmt.Fprintf(out, \"Logger Type: %+v\\n\", fastly.ToValue(o.LoggerType))\n\tfmt.Fprintf(out, \"Name: %+v\\n\", fastly.ToValue(o.Name))\n\tfmt.Fprintf(out, \"Penalty Box Duration: %+v\\n\", fastly.ToValue(o.PenaltyBoxDuration))\n\tfmt.Fprintf(out, \"Response: %+v\\n\", parseResponse(o.Response))\n\tfmt.Fprintf(out, \"Response Object Name: %+v\\n\", fastly.ToValue(o.ResponseObjectName))\n\tfmt.Fprintf(out, \"RPS Limit: %+v\\n\", fastly.ToValue(o.RpsLimit))\n\tfmt.Fprintf(out, \"Service ID: %+v\\n\", fastly.ToValue(o.ServiceID))\n\tfmt.Fprintf(out, \"URI Dictionary Name: %+v\\n\", fastly.ToValue(o.URIDictionaryName))\n\tfmt.Fprintf(out, \"Version: %+v\\n\", fastly.ToValue(o.Version))\n\tfmt.Fprintf(out, \"WindowSize: %+v\\n\", fastly.ToValue(o.WindowSize))\n\n\tif o.CreatedAt != nil {\n\t\tfmt.Fprintf(out, \"Created at: %s\\n\", o.CreatedAt)\n\t}\n\tif o.UpdatedAt != nil {\n\t\tfmt.Fprintf(out, \"Updated at: %s\\n\", o.UpdatedAt)\n\t}\n\tif o.DeletedAt != nil {\n\t\tfmt.Fprintf(out, \"Deleted at: %s\\n\", o.DeletedAt)\n\t}\n}\n\nfunc parseResponse(r *fastly.ERLResponse) string {\n\tif r != nil {\n\t\treturn fmt.Sprintf(\n\t\t\t`{ERLContent:%v ERLContentType:%v ERLStatus:%v}`,\n\t\t\tfastly.ToValue(r.ERLContent),\n\t\t\tfastly.ToValue(r.ERLContentType),\n\t\t\tfastly.ToValue(r.ERLStatus),\n\t\t)\n\t}\n\treturn \"\"\n}\n"
  },
  {
    "path": "pkg/commands/service/ratelimit/doc.go",
    "content": "// Package ratelimit contains commands to inspect and manipulate Fastly edge\n// rate limiters.\npackage ratelimit\n"
  },
  {
    "path": "pkg/commands/service/ratelimit/list.go",
    "content": "package ratelimit\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tvar c ListCommand\n\tc.CmdClause = parent.Command(\"list\", \"List all rate limiters for a particular service and version\")\n\tc.Globals = g\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\treturn &c\n}\n\n// ListCommand calls the Fastly API to list appropriate resources.\ntype ListCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *ListCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fsterr.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tinput := &fastly.ListERLsInput{\n\t\tServiceID:      serviceID,\n\t\tServiceVersion: fastly.ToValue(serviceVersion.Number),\n\t}\n\n\to, err := c.Globals.APIClient.ListERLs(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fastly.ToValue(serviceVersion.Number),\n\t\t})\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\tif c.Globals.Verbose() {\n\t\tc.printVerbose(out, o)\n\t} else {\n\t\tc.printSummary(out, o)\n\t}\n\treturn nil\n}\n\n// printVerbose displays the information returned from the API in a verbose\n// format.\nfunc (c *ListCommand) printVerbose(out io.Writer, o []*fastly.ERL) {\n\tfor _, u := range o {\n\t\tfmt.Fprintf(out, \"\\nAction: %+v\\n\", fastly.ToValue(u.Action))\n\t\tfmt.Fprintf(out, \"Client Key: %+v\\n\", u.ClientKey)\n\t\tfmt.Fprintf(out, \"Feature Revision: %+v\\n\", fastly.ToValue(u.FeatureRevision))\n\t\tfmt.Fprintf(out, \"HTTP Methods: %+v\\n\", u.HTTPMethods)\n\t\tfmt.Fprintf(out, \"ID: %+v\\n\", fastly.ToValue(u.RateLimiterID))\n\t\tfmt.Fprintf(out, \"Logger Type: %+v\\n\", fastly.ToValue(u.LoggerType))\n\t\tfmt.Fprintf(out, \"Name: %+v\\n\", fastly.ToValue(u.Name))\n\t\tfmt.Fprintf(out, \"Penalty Box Duration: %+v\\n\", fastly.ToValue(u.PenaltyBoxDuration))\n\t\tif u.Response != nil {\n\t\t\tfmt.Fprintf(out, \"Response: %+v\\n\", *u.Response)\n\t\t}\n\t\tfmt.Fprintf(out, \"Response Object Name: %+v\\n\", fastly.ToValue(u.ResponseObjectName))\n\t\tfmt.Fprintf(out, \"RPS Limit: %+v\\n\", fastly.ToValue(u.RpsLimit))\n\t\tfmt.Fprintf(out, \"Service ID: %+v\\n\", fastly.ToValue(u.ServiceID))\n\t\tfmt.Fprintf(out, \"URI Dictionary Name: %+v\\n\", fastly.ToValue(u.URIDictionaryName))\n\t\tfmt.Fprintf(out, \"Version: %+v\\n\", fastly.ToValue(u.Version))\n\t\tfmt.Fprintf(out, \"WindowSize: %+v\\n\", fastly.ToValue(u.WindowSize))\n\t\tif u.CreatedAt != nil {\n\t\t\tfmt.Fprintf(out, \"Created at: %s\\n\", u.CreatedAt)\n\t\t}\n\t\tif u.UpdatedAt != nil {\n\t\t\tfmt.Fprintf(out, \"Updated at: %s\\n\", u.UpdatedAt)\n\t\t}\n\t\tif u.DeletedAt != nil {\n\t\t\tfmt.Fprintf(out, \"Deleted at: %s\\n\", u.DeletedAt)\n\t\t}\n\t}\n}\n\n// printSummary displays the information returned from the API in a summarised\n// format.\nfunc (c *ListCommand) printSummary(out io.Writer, o []*fastly.ERL) {\n\tt := text.NewTable(out)\n\tt.AddHeader(\"ID\", \"NAME\", \"ACTION\", \"RPS LIMIT\", \"WINDOW SIZE\", \"PENALTY BOX DURATION\")\n\tfor _, u := range o {\n\t\tt.AddLine(\n\t\t\tfastly.ToValue(u.RateLimiterID),\n\t\t\tfastly.ToValue(u.Name),\n\t\t\tfastly.ToValue(u.Action),\n\t\t\tfastly.ToValue(u.RpsLimit),\n\t\t\tfastly.ToValue(u.WindowSize),\n\t\t\tfastly.ToValue(u.PenaltyBoxDuration),\n\t\t)\n\t}\n\tt.Print()\n}\n"
  },
  {
    "path": "pkg/commands/service/ratelimit/ratelimit_test.go",
    "content": "package ratelimit_test\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\troot \"github.com/fastly/cli/pkg/commands/service\"\n\tsub \"github.com/fastly/cli/pkg/commands/service/ratelimit\"\n\t\"github.com/fastly/cli/pkg/mock\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n)\n\nfunc TestRateLimitCreate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName: \"validate CreateERL API error\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tCreateERLFn: func(_ context.Context, _ *fastly.CreateERLInput) (*fastly.ERL, error) {\n\t\t\t\t\treturn nil, testutil.Err\n\t\t\t\t},\n\t\t\t\tListVersionsFn: testutil.ListVersions,\n\t\t\t},\n\t\t\tArgs:      \"--name example --service-id 123 --version 3\",\n\t\t\tWantError: testutil.Err.Error(),\n\t\t},\n\t\t{\n\t\t\tName: \"validate CreateERL API success\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tCreateERLFn: func(_ context.Context, i *fastly.CreateERLInput) (*fastly.ERL, error) {\n\t\t\t\t\treturn &fastly.ERL{\n\t\t\t\t\t\tName:          i.Name,\n\t\t\t\t\t\tRateLimiterID: fastly.ToPointer(\"123\"),\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t\tListVersionsFn: testutil.ListVersions,\n\t\t\t},\n\t\t\tArgs:       \"--name example --service-id 123 --version 3\",\n\t\t\tWantOutput: \"Created rate limiter 'example' (123)\",\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"create\"}, scenarios)\n}\n\nfunc TestRateLimitDelete(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName: \"validate DeleteERL API error\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tDeleteERLFn: func(_ context.Context, _ *fastly.DeleteERLInput) error {\n\t\t\t\t\treturn testutil.Err\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:      \"--id 123\",\n\t\t\tWantError: testutil.Err.Error(),\n\t\t},\n\t\t{\n\t\t\tName: \"validate DeleteERL API success\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tDeleteERLFn: func(_ context.Context, _ *fastly.DeleteERLInput) error {\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:       \"--id 123\",\n\t\t\tWantOutput: \"SUCCESS: Deleted rate limiter '123'\\n\",\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"delete\"}, scenarios)\n}\n\nfunc TestRateLimitDescribe(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName: \"validate GetERL API error\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tGetERLFn: func(_ context.Context, _ *fastly.GetERLInput) (*fastly.ERL, error) {\n\t\t\t\t\treturn nil, testutil.Err\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:      \"--id 123\",\n\t\t\tWantError: testutil.Err.Error(),\n\t\t},\n\t\t{\n\t\t\tName: \"validate ListERL API success\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tGetERLFn: func(_ context.Context, _ *fastly.GetERLInput) (*fastly.ERL, error) {\n\t\t\t\t\treturn &fastly.ERL{\n\t\t\t\t\t\tRateLimiterID:      fastly.ToPointer(\"123\"),\n\t\t\t\t\t\tName:               fastly.ToPointer(\"example\"),\n\t\t\t\t\t\tAction:             fastly.ToPointer(fastly.ERLActionResponse),\n\t\t\t\t\t\tRpsLimit:           fastly.ToPointer(10),\n\t\t\t\t\t\tWindowSize:         fastly.ToPointer(fastly.ERLSize60),\n\t\t\t\t\t\tPenaltyBoxDuration: fastly.ToPointer(20),\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:       \"--id 123\",\n\t\t\tWantOutput: \"\\nAction: response\\nClient Key: []\\nFeature Revision: 0\\nHTTP Methods: []\\nID: 123\\nLogger Type: \\nName: example\\nPenalty Box Duration: 20\\nResponse: \\nResponse Object Name: \\nRPS Limit: 10\\nService ID: \\nURI Dictionary Name: \\nVersion: 0\\nWindowSize: 60\\n\",\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"describe\"}, scenarios)\n}\n\nfunc TestRateLimitList(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName: \"validate ListERL API error\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tListERLsFn: func(_ context.Context, _ *fastly.ListERLsInput) ([]*fastly.ERL, error) {\n\t\t\t\t\treturn nil, testutil.Err\n\t\t\t\t},\n\t\t\t\tListVersionsFn: testutil.ListVersions,\n\t\t\t},\n\t\t\tArgs:      \"--service-id 123 --version 3\",\n\t\t\tWantError: testutil.Err.Error(),\n\t\t},\n\t\t{\n\t\t\tName: \"validate ListERL API success\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tListERLsFn: func(_ context.Context, _ *fastly.ListERLsInput) ([]*fastly.ERL, error) {\n\t\t\t\t\treturn []*fastly.ERL{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tRateLimiterID:      fastly.ToPointer(\"123\"),\n\t\t\t\t\t\t\tName:               fastly.ToPointer(\"example\"),\n\t\t\t\t\t\t\tAction:             fastly.ToPointer(fastly.ERLActionResponse),\n\t\t\t\t\t\t\tRpsLimit:           fastly.ToPointer(10),\n\t\t\t\t\t\t\tWindowSize:         fastly.ToPointer(fastly.ERLSize60),\n\t\t\t\t\t\t\tPenaltyBoxDuration: fastly.ToPointer(20),\n\t\t\t\t\t\t},\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t\tListVersionsFn: testutil.ListVersions,\n\t\t\t},\n\t\t\tArgs:       \"--service-id 123 --version 3\",\n\t\t\tWantOutput: \"ID   NAME     ACTION    RPS LIMIT  WINDOW SIZE  PENALTY BOX DURATION\\n123  example  response  10         60           20\\n\",\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"list\"}, scenarios)\n}\n\nfunc TesRateLimitUpdate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName: \"validate UpdateERL API error\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tUpdateERLFn: func(_ context.Context, _ *fastly.UpdateERLInput) (*fastly.ERL, error) {\n\t\t\t\t\treturn nil, testutil.Err\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:      \"--id 123 --name example\",\n\t\t\tWantError: testutil.Err.Error(),\n\t\t},\n\t\t{\n\t\t\tName: \"validate UpdateERL API success\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tUpdateERLFn: func(_ context.Context, i *fastly.UpdateERLInput) (*fastly.ERL, error) {\n\t\t\t\t\treturn &fastly.ERL{\n\t\t\t\t\t\tName:          i.Name,\n\t\t\t\t\t\tRateLimiterID: fastly.ToPointer(\"123\"),\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:       \"--id 123 --name example\",\n\t\t\tWantOutput: \"Updated rate limiter 'example' (123)\",\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"update\"}, scenarios)\n}\n"
  },
  {
    "path": "pkg/commands/service/ratelimit/root.go",
    "content": "package ratelimit\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"rate-limit\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manipulate rate-limiters of the Fastly API and web interface\")\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/service/ratelimit/update.go",
    "content": "package ratelimit\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"strings\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\n\tc.CmdClause = parent.Command(\"update\", \"Update a rate limiter by its ID\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"id\", \"Alphanumeric string identifying the rate limiter\").Required().StringVar(&c.id)\n\n\t// Optional.\n\tc.CmdClause.Flag(\"action\", \"The action to take when a rate limiter violation is detected\").HintOptions(rateLimitActionFlagOpts...).EnumVar(&c.action, rateLimitActionFlagOpts...)\n\tc.CmdClause.Flag(\"client-key\", \"Comma-separated list of VCL variable used to generate a counter key to identify a client\").StringVar(&c.clientKeys)\n\tc.CmdClause.Flag(\"feature-revision\", \"Revision number of the rate limiting feature implementation\").IntVar(&c.featRevision)\n\tc.CmdClause.Flag(\"http-methods\", \"Comma-separated list of HTTP methods to apply rate limiting to\").StringVar(&c.httpMethods)\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\tc.CmdClause.Flag(\"logger-type\", \"Name of the type of logging endpoint to be used when action is `log_only`\").HintOptions(rateLimitLoggerFlagOpts...).EnumVar(&c.loggerType, rateLimitLoggerFlagOpts...)\n\tc.CmdClause.Flag(\"name\", \"A human readable name for the rate limiting rule\").StringVar(&c.name)\n\tc.CmdClause.Flag(\"penalty-box-dur\", \"Length of time in minutes that the rate limiter is in effect after the initial violation is detected\").IntVar(&c.penaltyDuration)\n\tc.CmdClause.Flag(\"response-content\", \"HTTP response body data\").StringVar(&c.responseContent)\n\tc.CmdClause.Flag(\"response-content-type\", \"HTTP Content-Type (e.g. application/json)\").StringVar(&c.responseContentType)\n\tc.CmdClause.Flag(\"response-object-name\", \"Name of existing response object. Required if action is response_object\").StringVar(&c.responseObjectName)\n\tc.CmdClause.Flag(\"response-status\", \"HTTP response status code (e.g. 429)\").IntVar(&c.responseStatus)\n\tc.CmdClause.Flag(\"rps-limit\", \"Upper limit of requests per second allowed by the rate limiter\").IntVar(&c.rpsLimit)\n\tc.CmdClause.Flag(\"uri-dict-name\", \"The name of an Edge Dictionary containing URIs as keys\").StringVar(&c.uriDictName)\n\tc.CmdClause.Flag(\"window-size\", \"Number of seconds during which the RPS limit must be exceeded in order to trigger a violation\").HintOptions(rateLimitWindowSizeFlagOpts...).EnumVar(&c.windowSize, rateLimitWindowSizeFlagOpts...)\n\n\treturn &c\n}\n\n// UpdateCommand calls the Fastly API to create an appropriate resource.\ntype UpdateCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\taction              string\n\tclientKeys          string\n\tfeatRevision        int\n\thttpMethods         string\n\tid                  string\n\tloggerType          string\n\tname                string\n\tpenaltyDuration     int\n\tresponseContent     string\n\tresponseContentType string\n\tresponseObjectName  string\n\tresponseStatus      int\n\trpsLimit            int\n\turiDictName         string\n\twindowSize          string\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *UpdateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tif err := c.responseFlagValidator(); err != nil {\n\t\treturn fsterr.RemediationError{\n\t\t\tInner:       err,\n\t\t\tRemediation: \"When updating a response, all response flags (--response-content, --response-content-type, --response-status) should be set\",\n\t\t}\n\t}\n\n\tinput := c.constructInput()\n\to, err := c.Globals.APIClient.UpdateERL(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Updated rate limiter '%s' (%s)\", fastly.ToValue(o.Name), fastly.ToValue(o.RateLimiterID))\n\treturn nil\n}\n\n// constructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *UpdateCommand) constructInput() *fastly.UpdateERLInput {\n\tvar input fastly.UpdateERLInput\n\tinput.ERLID = c.id\n\n\t// NOTE: rateLimitActions is defined in ./create.go\n\tif c.action != \"\" {\n\t\tfor _, a := range fastly.ERLActions {\n\t\t\tif c.action == string(a) {\n\t\t\t\tinput.Action = fastly.ToPointer(a)\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\tif c.clientKeys != \"\" {\n\t\tclientKeys := strings.Split(strings.ReplaceAll(c.clientKeys, \" \", \"\"), \",\")\n\t\tinput.ClientKey = &clientKeys\n\t}\n\n\tif c.featRevision > 0 {\n\t\tinput.FeatureRevision = fastly.ToPointer(c.featRevision)\n\t}\n\n\tif c.httpMethods != \"\" {\n\t\thttpMethods := strings.Split(strings.ReplaceAll(c.httpMethods, \" \", \"\"), \",\")\n\t\tinput.HTTPMethods = &httpMethods\n\t}\n\n\t// NOTE: rateLimitLoggers is defined in ./create.go\n\tif c.loggerType != \"\" {\n\t\tfor _, l := range fastly.ERLLoggers {\n\t\t\tif c.loggerType == string(l) {\n\t\t\t\tinput.LoggerType = fastly.ToPointer(l)\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\tif c.name != \"\" {\n\t\tinput.Name = fastly.ToPointer(c.name)\n\t}\n\n\tif c.penaltyDuration > 0 {\n\t\tinput.PenaltyBoxDuration = fastly.ToPointer(c.penaltyDuration)\n\t}\n\n\tif c.responseContent != \"\" && c.responseContentType != \"\" && c.responseStatus > 0 {\n\t\tinput.Response = &fastly.ERLResponseType{\n\t\t\tERLContent:     fastly.ToPointer(c.responseContent),\n\t\t\tERLContentType: fastly.ToPointer(c.responseContentType),\n\t\t\tERLStatus:      fastly.ToPointer(c.responseStatus),\n\t\t}\n\t}\n\n\tif c.responseObjectName != \"\" {\n\t\tinput.ResponseObjectName = fastly.ToPointer(c.responseObjectName)\n\t}\n\n\tif c.rpsLimit > 0 {\n\t\tinput.RpsLimit = fastly.ToPointer(c.rpsLimit)\n\t}\n\n\tif c.uriDictName != \"\" {\n\t\tinput.URIDictionaryName = fastly.ToPointer(c.uriDictName)\n\t}\n\n\t// NOTE: rateLimitWindowSizes is defined in ./create.go\n\tif c.windowSize != \"\" {\n\t\tfor _, w := range fastly.ERLWindowSizes {\n\t\t\tif c.windowSize == fmt.Sprint(w) {\n\t\t\t\tinput.WindowSize = fastly.ToPointer(w)\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\treturn &input\n}\n\n// responseFlagValidator ensures if a user specifies one of the response flags,\n// that they must specify ALL of the response flags.\nfunc (c *UpdateCommand) responseFlagValidator() error {\n\tvar state int\n\tif c.responseContent != \"\" {\n\t\tstate++\n\t}\n\tif c.responseContentType != \"\" {\n\t\tstate++\n\t}\n\tif c.responseStatus > 0 {\n\t\tstate++\n\t}\n\tif state > 0 && state < 3 {\n\t\treturn errors.New(\"invalid flag use\")\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/resourcelink/create.go",
    "content": "package resourcelink\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// CreateCommand calls the Fastly API to create a resource link.\ntype CreateCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tautoClone      argparser.OptionalAutoClone\n\tinput          fastly.CreateResourceInput\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n}\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t\tinput: fastly.CreateResourceInput{\n\t\t\t// Kingpin requires the following to be initialized.\n\t\t\tResourceID: new(string),\n\t\t\tName:       new(string),\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"create\", \"Create a Fastly service resource link\").Alias(\"link\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        \"resource-id\",\n\t\tShort:       'r',\n\t\tDescription: flagResourceIDDescription,\n\t\tDst:         c.input.ResourceID,\n\t\tRequired:    true,\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// At least one of the following is required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tShort:       's',\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceName,\n\t\tAction:      c.serviceName.Set,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\n\t// Optional.\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.autoClone.Set,\n\t\tDst:    &c.autoClone.Value,\n\t})\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        \"name\",\n\t\tShort:       'n',\n\t\tDescription: flagNameDescription,\n\t\tDst:         c.input.Name,\n\t})\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *CreateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(false),\n\t\tLocked:             optional.Of(false),\n\t\tAutoCloneFlag:      c.autoClone,\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      c.Globals.Manifest.Flag.ServiceID,\n\t\t\t\"Service Version\": fsterr.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tc.input.ServiceID = serviceID\n\tc.input.ServiceVersion = fastly.ToValue(serviceVersion.Number)\n\n\to, err := c.Globals.APIClient.CreateResource(context.TODO(), &c.input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"ID\":              c.input.ResourceID,\n\t\t\t\"Service ID\":      c.input.ServiceID,\n\t\t\t\"Service Version\": c.input.ServiceVersion,\n\t\t})\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\ttext.Success(out,\n\t\t\"Created service resource link %q (%s) on service %s version %d\",\n\t\tfastly.ToValue(o.Name),\n\t\tfastly.ToValue(o.LinkID),\n\t\tfastly.ToValue(o.ServiceID),\n\t\tfastly.ToValue(o.ServiceVersion),\n\t)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/resourcelink/delete.go",
    "content": "package resourcelink\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DeleteCommand calls the Fastly API to delete service resource links.\ntype DeleteCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tautoClone      argparser.OptionalAutoClone\n\tinput          fastly.DeleteResourceInput\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n}\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"delete\", \"Delete a resource link for a Fastly service version\").Alias(\"remove\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        \"id\",\n\t\tDescription: flagIDDescription,\n\t\tDst:         &c.input.ResourceID,\n\t\tRequired:    true,\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// At least one of the following is required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tShort:       's',\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceName,\n\t\tAction:      c.serviceName.Set,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\n\t// Optional.\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.autoClone.Set,\n\t\tDst:    &c.autoClone.Value,\n\t})\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DeleteCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(false),\n\t\tLocked:             optional.Of(false),\n\t\tAutoCloneFlag:      c.autoClone,\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      c.Globals.Manifest.Flag.ServiceID,\n\t\t\t\"Service Version\": fsterr.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tc.input.ServiceID = serviceID\n\tc.input.ServiceVersion = fastly.ToValue(serviceVersion.Number)\n\n\terr = c.Globals.APIClient.DeleteResource(context.TODO(), &c.input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"ID\":              c.input.ResourceID,\n\t\t\t\"Service ID\":      c.input.ServiceID,\n\t\t\t\"Service Version\": c.input.ServiceVersion,\n\t\t})\n\t\treturn err\n\t}\n\n\tif c.JSONOutput.Enabled {\n\t\to := struct {\n\t\t\tID             string `json:\"id\"`\n\t\t\tServiceID      string `json:\"service_id\"`\n\t\t\tServiceVersion int    `json:\"service_version\"`\n\t\t\tDeleted        bool   `json:\"deleted\"`\n\t\t}{\n\t\t\tc.input.ResourceID,\n\t\t\tc.input.ServiceID,\n\t\t\tc.input.ServiceVersion,\n\t\t\ttrue,\n\t\t}\n\t\t_, err := c.WriteJSON(out, o)\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Deleted service resource link %s from service %s version %d\", c.input.ResourceID, c.input.ServiceID, c.input.ServiceVersion)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/resourcelink/describe.go",
    "content": "package resourcelink\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DescribeCommand calls the Fastly API to describe a service resource link.\ntype DescribeCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tinput          fastly.GetResourceInput\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n}\n\n// NewDescribeCommand returns a usable command registered under the parent.\nfunc NewDescribeCommand(parent argparser.Registerer, g *global.Data) *DescribeCommand {\n\tc := DescribeCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"describe\", \"Show detailed information about a Fastly service resource link\").Alias(\"get\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        \"id\",\n\t\tDescription: flagIDDescription,\n\t\tDst:         &c.input.ResourceID,\n\t\tRequired:    true,\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// At least one of the following is required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tShort:       's',\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceName,\n\t\tAction:      c.serviceName.Set,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DescribeCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tserviceID, source, flag, err := argparser.ServiceID(c.serviceName, *c.Globals.Manifest, c.Globals.APIClient, c.Globals.ErrLog)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif c.Globals.Verbose() {\n\t\targparser.DisplayServiceID(serviceID, flag, source, out)\n\t}\n\n\tserviceVersion, err := c.serviceVersion.Parse(serviceID, c.Globals.APIClient)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tc.input.ServiceID = serviceID\n\tc.input.ServiceVersion = fastly.ToValue(serviceVersion.Number)\n\n\to, err := c.Globals.APIClient.GetResource(context.TODO(), &c.input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"ID\":              c.input.ResourceID,\n\t\t\t\"Service ID\":      c.input.ServiceID,\n\t\t\t\"Service Version\": c.input.ServiceVersion,\n\t\t})\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\tif !c.Globals.Verbose() {\n\t\ttext.Output(out, \"Service ID: %s\", fastly.ToValue(o.ServiceID))\n\t}\n\ttext.Output(out, \"Service Version: %d\", fastly.ToValue(o.ServiceVersion))\n\ttext.PrintResource(out, \"\", o)\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/resourcelink/doc.go",
    "content": "// Package resourcelink contains commands to inspect and\n// manipulate service resource links.\n// https://www.fastly.com/documentation/reference/api/services/resource\npackage resourcelink\n"
  },
  {
    "path": "pkg/commands/service/resourcelink/list.go",
    "content": "package resourcelink\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// ListCommand calls the Fastly API to list service resource links.\ntype ListCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tinput          fastly.ListResourcesInput\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n}\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"list\", \"List all resource links for a Fastly service version\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// At least one of the following is required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tShort:       's',\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceName,\n\t\tAction:      c.serviceName.Set,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *ListCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tserviceID, source, flag, err := argparser.ServiceID(c.serviceName, *c.Globals.Manifest, c.Globals.APIClient, c.Globals.ErrLog)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif c.Globals.Verbose() {\n\t\targparser.DisplayServiceID(serviceID, flag, source, out)\n\t}\n\n\tserviceVersion, err := c.serviceVersion.Parse(serviceID, c.Globals.APIClient)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tc.input.ServiceID = serviceID\n\tc.input.ServiceVersion = fastly.ToValue(serviceVersion.Number)\n\n\to, err := c.Globals.APIClient.ListResources(context.TODO(), &c.input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      c.input.ServiceID,\n\t\t\t\"Service Version\": c.input.ServiceVersion,\n\t\t})\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\tif !c.Globals.Verbose() {\n\t\tfmt.Fprintf(out, \"Service ID: %s\\n\", c.input.ServiceID)\n\t}\n\ttext.Output(out, \"Service Version: %d\\n\", c.input.ServiceVersion)\n\n\tfor i, resource := range o {\n\t\tfmt.Fprintf(out, \"Resource Link %d/%d\\n\", i+1, len(o))\n\t\ttext.PrintResource(out, \"  \", resource)\n\t\tfmt.Fprintln(out)\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/resourcelink/resourcelink_test.go",
    "content": "package resourcelink_test\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\troot \"github.com/fastly/cli/pkg/commands/service\"\n\tsub \"github.com/fastly/cli/pkg/commands/service/resourcelink\"\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/mock\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n)\n\nfunc TestCreateServiceResourceCommand(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t// Missing required arguments.\n\t\t{\n\t\t\tArgs:      \"--service-id abc --resource-id 123\",\n\t\t\tWantError: \"error parsing arguments: required flag --version not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs:      \"--service-id abc --version latest\",\n\t\t\tWantError: \"error parsing arguments: required flag --resource-id not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs:      \"--resource-id abc --version latest\",\n\t\t\tEnvVars:   map[string]string{\"FASTLY_SERVICE_ID\": \"\"},\n\t\t\tWantError: \"error reading service: no service ID found\",\n\t\t},\n\t\t// Success.\n\t\t{\n\t\t\tArgs: \"--resource-id abc --service-id 123 --version 42\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tCreateResourceFn: func(_ context.Context, i *fastly.CreateResourceInput) (*fastly.Resource, error) {\n\t\t\t\t\tif got, want := *i.ResourceID, \"abc\"; got != want {\n\t\t\t\t\t\treturn nil, fmt.Errorf(\"ResourceID: got %q, want %q\", got, want)\n\t\t\t\t\t}\n\t\t\t\t\tif got, want := i.ServiceID, \"123\"; got != want {\n\t\t\t\t\t\treturn nil, fmt.Errorf(\"ServiceID: got %q, want %q\", got, want)\n\t\t\t\t\t}\n\t\t\t\t\tif got, want := *i.Name, \"\"; got != want {\n\t\t\t\t\t\treturn nil, fmt.Errorf(\"Name: got %q, want %q\", got, want)\n\t\t\t\t\t}\n\t\t\t\t\tnow := time.Now()\n\t\t\t\t\treturn &fastly.Resource{\n\t\t\t\t\t\tLinkID:         fastly.ToPointer(\"rand-id\"),\n\t\t\t\t\t\tName:           fastly.ToPointer(\"the-name\"),\n\t\t\t\t\t\tResourceID:     fastly.ToPointer(\"abc\"),\n\t\t\t\t\t\tServiceID:      fastly.ToPointer(\"123\"),\n\t\t\t\t\t\tServiceVersion: fastly.ToPointer(42),\n\t\t\t\t\t\tCreatedAt:      &now,\n\t\t\t\t\t\tUpdatedAt:      &now,\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: `SUCCESS: Created service resource link \"the-name\" (rand-id) on service 123 version 42`,\n\t\t},\n\t\t// Success with --name.\n\t\t{\n\t\t\tArgs: \"--resource-id abc --service-id 123 --version 42 --name testing\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tCreateResourceFn: func(_ context.Context, i *fastly.CreateResourceInput) (*fastly.Resource, error) {\n\t\t\t\t\tif got, want := *i.ResourceID, \"abc\"; got != want {\n\t\t\t\t\t\treturn nil, fmt.Errorf(\"ResourceID: got %q, want %q\", got, want)\n\t\t\t\t\t}\n\t\t\t\t\tif got, want := i.ServiceID, \"123\"; got != want {\n\t\t\t\t\t\treturn nil, fmt.Errorf(\"ServiceID: got %q, want %q\", got, want)\n\t\t\t\t\t}\n\t\t\t\t\tif got, want := *i.Name, \"testing\"; got != want {\n\t\t\t\t\t\treturn nil, fmt.Errorf(\"Name: got %q, want %q\", got, want)\n\t\t\t\t\t}\n\t\t\t\t\tnow := time.Now()\n\t\t\t\t\treturn &fastly.Resource{\n\t\t\t\t\t\tLinkID:         fastly.ToPointer(\"rand-id\"),\n\t\t\t\t\t\tName:           fastly.ToPointer(\"a-name\"),\n\t\t\t\t\t\tResourceID:     fastly.ToPointer(\"abc\"),\n\t\t\t\t\t\tServiceID:      fastly.ToPointer(\"123\"),\n\t\t\t\t\t\tServiceVersion: fastly.ToPointer(42),\n\t\t\t\t\t\tCreatedAt:      &now,\n\t\t\t\t\t\tUpdatedAt:      &now,\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: `SUCCESS: Created service resource link \"a-name\" (rand-id) on service 123 version 42`,\n\t\t},\n\t\t// Success with --autoclone.\n\t\t{\n\t\t\tArgs: \"--resource-id abc --service-id 123 --version=latest --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:        testutil.GetVersion,\n\t\t\t\tGetServiceDetailsFn: testutil.GetServiceDetails,\n\t\t\t\tListVersionsFn: func(_ context.Context, _ *fastly.ListVersionsInput) ([]*fastly.Version, error) {\n\t\t\t\t\t// Specified version is active, meaning a service clone will be attempted.\n\t\t\t\t\treturn []*fastly.Version{{Active: fastly.ToPointer(true), Number: fastly.ToPointer(42)}}, nil\n\t\t\t\t},\n\t\t\t\tCloneVersionFn: func(_ context.Context, _ *fastly.CloneVersionInput) (*fastly.Version, error) {\n\t\t\t\t\treturn &fastly.Version{Number: fastly.ToPointer(43)}, nil\n\t\t\t\t},\n\t\t\t\tCreateResourceFn: func(_ context.Context, i *fastly.CreateResourceInput) (*fastly.Resource, error) {\n\t\t\t\t\tif got, want := *i.ResourceID, \"abc\"; got != want {\n\t\t\t\t\t\treturn nil, fmt.Errorf(\"ResourceID: got %q, want %q\", got, want)\n\t\t\t\t\t}\n\t\t\t\t\tif got, want := i.ServiceID, \"123\"; got != want {\n\t\t\t\t\t\treturn nil, fmt.Errorf(\"ServiceID: got %q, want %q\", got, want)\n\t\t\t\t\t}\n\t\t\t\t\tif got, want := *i.Name, \"\"; got != want {\n\t\t\t\t\t\treturn nil, fmt.Errorf(\"Name: got %q, want %q\", got, want)\n\t\t\t\t\t}\n\t\t\t\t\tnow := time.Now()\n\t\t\t\t\treturn &fastly.Resource{\n\t\t\t\t\t\tLinkID:         fastly.ToPointer(\"rand-id\"),\n\t\t\t\t\t\tName:           fastly.ToPointer(\"cloned\"),\n\t\t\t\t\t\tResourceID:     fastly.ToPointer(\"abc\"),\n\t\t\t\t\t\tServiceID:      fastly.ToPointer(\"123\"),\n\t\t\t\t\t\tServiceVersion: fastly.ToPointer(43), // Cloned version.\n\t\t\t\t\t\tCreatedAt:      &now,\n\t\t\t\t\t\tUpdatedAt:      &now,\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: `SUCCESS: Created service resource link \"cloned\" (rand-id) on service 123 version 43`,\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"create\"}, scenarios)\n}\n\nfunc TestDeleteServiceResourceCommand(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t// Missing required arguments.\n\t\t{\n\t\t\tArgs:      \"--id LINK-ID --service-id abc\",\n\t\t\tWantError: \"error parsing arguments: required flag --version not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs:      \"--id LINK-ID --version 123\",\n\t\t\tEnvVars:   map[string]string{\"FASTLY_SERVICE_ID\": \"\"},\n\t\t\tWantError: \"error reading service: no service ID found\",\n\t\t},\n\t\t{\n\t\t\tArgs:      \"--service-id abc --version 123\",\n\t\t\tWantError: \"error parsing arguments: required flag --id not provided\",\n\t\t},\n\t\t// Success.\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 42 --id LINKID\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tDeleteResourceFn: func(_ context.Context, i *fastly.DeleteResourceInput) error {\n\t\t\t\t\tif got, want := i.ResourceID, \"LINKID\"; got != want {\n\t\t\t\t\t\treturn fmt.Errorf(\"ID: got %q, want %q\", got, want)\n\t\t\t\t\t}\n\t\t\t\t\tif got, want := i.ServiceID, \"123\"; got != want {\n\t\t\t\t\t\treturn fmt.Errorf(\"ServiceID: got %q, want %q\", got, want)\n\t\t\t\t\t}\n\t\t\t\t\tif got, want := i.ServiceVersion, 42; got != want {\n\t\t\t\t\t\treturn fmt.Errorf(\"ServiceVersion: got %d, want %d\", got, want)\n\t\t\t\t\t}\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: \"SUCCESS: Deleted service resource link LINKID from service 123 version 42\",\n\t\t},\n\t\t// Success with --autoclone.\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 42 --id LINKID --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: func(_ context.Context, _ *fastly.GetVersionInput) (*fastly.Version, error) {\n\t\t\t\t\t// Specified version is active, meaning a service clone will be attempted.\n\t\t\t\t\treturn &fastly.Version{Active: fastly.ToPointer(true), Number: fastly.ToPointer(42)}, nil\n\t\t\t\t},\n\t\t\t\tCloneVersionFn: func(_ context.Context, _ *fastly.CloneVersionInput) (*fastly.Version, error) {\n\t\t\t\t\treturn &fastly.Version{Number: fastly.ToPointer(43)}, nil\n\t\t\t\t},\n\t\t\t\tDeleteResourceFn: func(_ context.Context, i *fastly.DeleteResourceInput) error {\n\t\t\t\t\tif got, want := i.ResourceID, \"LINKID\"; got != want {\n\t\t\t\t\t\treturn fmt.Errorf(\"ID: got %q, want %q\", got, want)\n\t\t\t\t\t}\n\t\t\t\t\tif got, want := i.ServiceID, \"123\"; got != want {\n\t\t\t\t\t\treturn fmt.Errorf(\"ServiceID: got %q, want %q\", got, want)\n\t\t\t\t\t}\n\t\t\t\t\tif got, want := i.ServiceVersion, 43; got != want {\n\t\t\t\t\t\treturn fmt.Errorf(\"ServiceVersion: got %d, want %d\", got, want)\n\t\t\t\t\t}\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: \"SUCCESS: Deleted service resource link LINKID from service 123 version 43\",\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"delete\"}, scenarios)\n}\n\nfunc TestDescribeServiceResourceCommand(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t// Missing required arguments.\n\t\t{\n\t\t\tArgs:      \"--id LINK-ID --service-id abc\",\n\t\t\tWantError: \"error parsing arguments: required flag --version not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs:      \"--id LINK-ID --version 123\",\n\t\t\tEnvVars:   map[string]string{\"FASTLY_SERVICE_ID\": \"\"},\n\t\t\tWantError: \"error reading service: no service ID found\",\n\t\t},\n\t\t{\n\t\t\tArgs:      \"--service-id abc --version 123\",\n\t\t\tWantError: \"error parsing arguments: required flag --id not provided\",\n\t\t},\n\t\t// Success.\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 42 --id LINKID\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tGetResourceFn: func(_ context.Context, i *fastly.GetResourceInput) (*fastly.Resource, error) {\n\t\t\t\t\tif got, want := i.ResourceID, \"LINKID\"; got != want {\n\t\t\t\t\t\treturn nil, fmt.Errorf(\"ID: got %q, want %q\", got, want)\n\t\t\t\t\t}\n\t\t\t\t\tif got, want := i.ServiceID, \"123\"; got != want {\n\t\t\t\t\t\treturn nil, fmt.Errorf(\"ServiceID: got %q, want %q\", got, want)\n\t\t\t\t\t}\n\t\t\t\t\tif got, want := i.ServiceVersion, 42; got != want {\n\t\t\t\t\t\treturn nil, fmt.Errorf(\"ServiceVersion: got %d, want %d\", got, want)\n\t\t\t\t\t}\n\t\t\t\t\tnow := time.Unix(1697372322, 0)\n\t\t\t\t\treturn &fastly.Resource{\n\t\t\t\t\t\tLinkID:         fastly.ToPointer(\"LINKID\"),\n\t\t\t\t\t\tResourceID:     fastly.ToPointer(\"abc\"),\n\t\t\t\t\t\tResourceType:   fastly.ToPointer(\"secret-store\"),\n\t\t\t\t\t\tName:           fastly.ToPointer(\"test-name\"),\n\t\t\t\t\t\tServiceID:      fastly.ToPointer(\"123\"),\n\t\t\t\t\t\tServiceVersion: fastly.ToPointer(42),\n\t\t\t\t\t\tCreatedAt:      &now,\n\t\t\t\t\t\tUpdatedAt:      &now,\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: `Service ID: 123\nService Version: 42\nID: LINKID\nName: test-name\nService ID: 123\nService Version: 42\nResource ID: abc\nResource Type: secret-store\nCreated (UTC): 2023-10-15 12:18\nLast edited (UTC): 2023-10-15 12:18`,\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"describe\"}, scenarios)\n}\n\nfunc TestListServiceResourceCommand(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t// Missing required arguments.\n\t\t{\n\t\t\tArgs:      \"--service-id abc\",\n\t\t\tWantError: \"error parsing arguments: required flag --version not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs:      \"--version 123\",\n\t\t\tEnvVars:   map[string]string{\"FASTLY_SERVICE_ID\": \"\"},\n\t\t\tWantError: \"error reading service: no service ID found\",\n\t\t},\n\t\t// Success.\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 42\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tListResourcesFn: func(_ context.Context, i *fastly.ListResourcesInput) ([]*fastly.Resource, error) {\n\t\t\t\t\tif got, want := i.ServiceID, \"123\"; got != want {\n\t\t\t\t\t\treturn nil, fmt.Errorf(\"ServiceID: got %q, want %q\", got, want)\n\t\t\t\t\t}\n\t\t\t\t\tif got, want := i.ServiceVersion, 42; got != want {\n\t\t\t\t\t\treturn nil, fmt.Errorf(\"ServiceVersion: got %d, want %d\", got, want)\n\t\t\t\t\t}\n\n\t\t\t\t\tnow := time.Unix(1697372322, 0)\n\t\t\t\t\tresources := make([]*fastly.Resource, 3)\n\t\t\t\t\tfor i := range resources {\n\t\t\t\t\t\tresources[i] = &fastly.Resource{\n\t\t\t\t\t\t\tLinkID:         fastly.ToPointer(fmt.Sprintf(\"LINKID-%02d\", i)),\n\t\t\t\t\t\t\tResourceID:     fastly.ToPointer(\"abc\"),\n\t\t\t\t\t\t\tResourceType:   fastly.ToPointer(\"secret-store\"),\n\t\t\t\t\t\t\tName:           fastly.ToPointer(\"test-name\"),\n\t\t\t\t\t\t\tServiceID:      fastly.ToPointer(\"123\"),\n\t\t\t\t\t\t\tServiceVersion: fastly.ToPointer(42),\n\t\t\t\t\t\t\tCreatedAt:      &now,\n\t\t\t\t\t\t\tUpdatedAt:      &now,\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\treturn resources, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: `Service ID: 123\nService Version: 42\nResource Link 1/3\n  ID: LINKID-00\n  Name: test-name\n  Service ID: 123\n  Service Version: 42\n  Resource ID: abc\n  Resource Type: secret-store\n  Created (UTC): 2023-10-15 12:18\n  Last edited (UTC): 2023-10-15 12:18\n\nResource Link 2/3\n  ID: LINKID-01\n  Name: test-name\n  Service ID: 123\n  Service Version: 42\n  Resource ID: abc\n  Resource Type: secret-store\n  Created (UTC): 2023-10-15 12:18\n  Last edited (UTC): 2023-10-15 12:18\n\nResource Link 3/3\n  ID: LINKID-02\n  Name: test-name\n  Service ID: 123\n  Service Version: 42\n  Resource ID: abc\n  Resource Type: secret-store\n  Created (UTC): 2023-10-15 12:18\n  Last edited (UTC): 2023-10-15 12:18`,\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"list\"}, scenarios)\n}\n\nfunc TestUpdateServiceResourceCommand(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t// Missing required arguments.\n\t\t{\n\t\t\tArgs:      \"--id LINK-ID --name new-name --service-id abc\",\n\t\t\tWantError: \"error parsing arguments: required flag --version not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs:      \"--id LINK-ID --name new-name --version 123\",\n\t\t\tEnvVars:   map[string]string{\"FASTLY_SERVICE_ID\": \"\"},\n\t\t\tWantError: \"error reading service: no service ID found\",\n\t\t},\n\t\t{\n\t\t\tArgs:      \"--id LINK-ID --service-id abc --version 123\",\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs:      \"--name new-name --service-id abc --version 123\",\n\t\t\tWantError: \"error parsing arguments: required flag --id not provided\",\n\t\t},\n\t\t// Success.\n\t\t{\n\t\t\tArgs: \"--id LINK-ID --name new-name --service-id 123 --version 42\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tUpdateResourceFn: func(_ context.Context, i *fastly.UpdateResourceInput) (*fastly.Resource, error) {\n\t\t\t\t\tif got, want := i.ResourceID, \"LINK-ID\"; got != want {\n\t\t\t\t\t\treturn nil, fmt.Errorf(\"ID: got %q, want %q\", got, want)\n\t\t\t\t\t}\n\t\t\t\t\tif got, want := *i.Name, \"new-name\"; got != want {\n\t\t\t\t\t\treturn nil, fmt.Errorf(\"Name: got %q, want %q\", got, want)\n\t\t\t\t\t}\n\t\t\t\t\tif got, want := i.ServiceID, \"123\"; got != want {\n\t\t\t\t\t\treturn nil, fmt.Errorf(\"ServiceID: got %q, want %q\", got, want)\n\t\t\t\t\t}\n\t\t\t\t\tif got, want := i.ServiceVersion, 42; got != want {\n\t\t\t\t\t\treturn nil, fmt.Errorf(\"ServiceVersion: got %d, want %d\", got, want)\n\t\t\t\t\t}\n\n\t\t\t\t\tnow := time.Now()\n\t\t\t\t\treturn &fastly.Resource{\n\t\t\t\t\t\tLinkID:         fastly.ToPointer(\"LINK-ID\"),\n\t\t\t\t\t\tResourceID:     fastly.ToPointer(\"abc\"),\n\t\t\t\t\t\tResourceType:   fastly.ToPointer(\"secret-store\"),\n\t\t\t\t\t\tName:           fastly.ToPointer(\"new-name\"),\n\t\t\t\t\t\tServiceID:      fastly.ToPointer(\"123\"),\n\t\t\t\t\t\tServiceVersion: fastly.ToPointer(42),\n\t\t\t\t\t\tCreatedAt:      &now,\n\t\t\t\t\t\tUpdatedAt:      &now,\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: \"SUCCESS: Updated service resource link LINK-ID on service 123 version 42\",\n\t\t},\n\t\t// Success with --autoclone.\n\t\t{\n\t\t\tArgs: \"--id LINK-ID --name new-name --service-id 123 --version 42 --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: func(_ context.Context, _ *fastly.GetVersionInput) (*fastly.Version, error) {\n\t\t\t\t\t// Specified version is active, meaning a service clone will be attempted.\n\t\t\t\t\treturn &fastly.Version{Active: fastly.ToPointer(true), Number: fastly.ToPointer(42)}, nil\n\t\t\t\t},\n\t\t\t\tCloneVersionFn: func(_ context.Context, _ *fastly.CloneVersionInput) (*fastly.Version, error) {\n\t\t\t\t\treturn &fastly.Version{Number: fastly.ToPointer(43)}, nil\n\t\t\t\t},\n\t\t\t\tUpdateResourceFn: func(_ context.Context, i *fastly.UpdateResourceInput) (*fastly.Resource, error) {\n\t\t\t\t\tif got, want := i.ResourceID, \"LINK-ID\"; got != want {\n\t\t\t\t\t\treturn nil, fmt.Errorf(\"ID: got %q, want %q\", got, want)\n\t\t\t\t\t}\n\t\t\t\t\tif got, want := *i.Name, \"new-name\"; got != want {\n\t\t\t\t\t\treturn nil, fmt.Errorf(\"Name: got %q, want %q\", got, want)\n\t\t\t\t\t}\n\t\t\t\t\tif got, want := i.ServiceID, \"123\"; got != want {\n\t\t\t\t\t\treturn nil, fmt.Errorf(\"ServiceID: got %q, want %q\", got, want)\n\t\t\t\t\t}\n\t\t\t\t\tif got, want := i.ServiceVersion, 43; got != want {\n\t\t\t\t\t\treturn nil, fmt.Errorf(\"ServiceVersion: got %d, want %d\", got, want)\n\t\t\t\t\t}\n\n\t\t\t\t\tnow := time.Now()\n\t\t\t\t\treturn &fastly.Resource{\n\t\t\t\t\t\tLinkID:         fastly.ToPointer(\"LINK-ID\"),\n\t\t\t\t\t\tResourceID:     fastly.ToPointer(\"abc\"),\n\t\t\t\t\t\tResourceType:   fastly.ToPointer(\"secret-store\"),\n\t\t\t\t\t\tName:           fastly.ToPointer(\"new-name\"),\n\t\t\t\t\t\tServiceID:      fastly.ToPointer(\"123\"),\n\t\t\t\t\t\tServiceVersion: fastly.ToPointer(43),\n\t\t\t\t\t\tCreatedAt:      &now,\n\t\t\t\t\t\tUpdatedAt:      &now,\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: \"SUCCESS: Updated service resource link LINK-ID on service 123 version 43\",\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"update\"}, scenarios)\n}\n"
  },
  {
    "path": "pkg/commands/service/resourcelink/root.go",
    "content": "package resourcelink\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootName is the name of this package's sub-command in the CLI,\n// e.g. \"fastly resource-link\".\nconst RootName = \"resource-link\"\n\n// Common flag descriptions.\nconst (\n\tflagNameDescription       = \"Resource link name (alias). Defaults to resource's name\"\n\tflagIDDescription         = \"Resource link ID\"\n\tflagResourceIDDescription = \"Resource ID\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"resource-link\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manipulate Fastly service resource links\")\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/service/resourcelink/update.go",
    "content": "package resourcelink\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// UpdateCommand calls the Fastly API to update a dictionary.\ntype UpdateCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tautoClone      argparser.OptionalAutoClone\n\tinput          fastly.UpdateResourceInput\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n}\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t\tinput: fastly.UpdateResourceInput{\n\t\t\t// Kingpin requires the following to be initialized.\n\t\t\tName: new(string),\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"update\", \"Update a resource link for a Fastly service version\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        \"id\",\n\t\tDescription: flagIDDescription,\n\t\tDst:         &c.input.ResourceID,\n\t\tRequired:    true,\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        \"name\",\n\t\tShort:       'n',\n\t\tDescription: flagNameDescription,\n\t\tDst:         c.input.Name,\n\t\tRequired:    true,\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// At least one of the following is required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tShort:       's',\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceName,\n\t\tAction:      c.serviceName.Set,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\n\t// Optional.\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.autoClone.Set,\n\t\tDst:    &c.autoClone.Value,\n\t})\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *UpdateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(false),\n\t\tLocked:             optional.Of(false),\n\t\tAutoCloneFlag:      c.autoClone,\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      c.Globals.Manifest.Flag.ServiceID,\n\t\t\t\"Service Version\": fsterr.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tc.input.ServiceID = serviceID\n\tc.input.ServiceVersion = fastly.ToValue(serviceVersion.Number)\n\n\to, err := c.Globals.APIClient.UpdateResource(context.TODO(), &c.input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"ID\":              c.input.ResourceID,\n\t\t\t\"Service ID\":      c.input.ServiceID,\n\t\t\t\"Service Version\": c.input.ServiceVersion,\n\t\t})\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\ttext.Success(out,\n\t\t\"Updated service resource link %s on service %s version %d\",\n\t\tfastly.ToValue(o.LinkID),\n\t\tfastly.ToValue(o.ServiceID),\n\t\tfastly.ToValue(o.ServiceVersion),\n\t)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/root.go",
    "content": "package service\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"service\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manipulate Fastly services\")\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/service/search.go",
    "content": "package service\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// SearchCommand calls the Fastly API to describe a service.\ntype SearchCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tInput fastly.SearchServiceInput\n}\n\n// NewSearchCommand returns a usable command registered under the parent.\nfunc NewSearchCommand(parent argparser.Registerer, g *global.Data) *SearchCommand {\n\tc := SearchCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"search\", \"Search for a Fastly service by name\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"Service name\").Short('n').Required().StringVar(&c.Input.Name)\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *SearchCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tservice, err := c.Globals.APIClient.SearchService(context.TODO(), &c.Input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service Name\": c.Input.Name,\n\t\t})\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, service); ok {\n\t\treturn err\n\t}\n\n\ttext.PrintService(out, \"\", service)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/service_test.go",
    "content": "package service_test\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\t\"net/http\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"regexp\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/app\"\n\troot \"github.com/fastly/cli/pkg/commands/service\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/manifest\"\n\t\"github.com/fastly/cli/pkg/mock\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n)\n\nfunc TestServiceCreate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:       \"success with long flag\",\n\t\t\tArgs:       \"--name Foo\",\n\t\t\tAPI:        &mock.API{CreateServiceFn: createServiceOK},\n\t\t\tWantOutput: \"Created service 12345\",\n\t\t},\n\t\t{\n\t\t\tName:       \"success with short flag and equals\",\n\t\t\tArgs:       \"-n=Foo\",\n\t\t\tAPI:        &mock.API{CreateServiceFn: createServiceOK},\n\t\t\tWantOutput: \"Created service 12345\",\n\t\t},\n\t\t{\n\t\t\tName:       \"success with type\",\n\t\t\tArgs:       \"--name Foo --type wasm\",\n\t\t\tAPI:        &mock.API{CreateServiceFn: createServiceOK},\n\t\t\tWantOutput: \"Created service 12345\",\n\t\t},\n\t\t{\n\t\t\tName:       \"success with type and comment\",\n\t\t\tArgs:       \"--name Foo --type wasm --comment Hello\",\n\t\t\tAPI:        &mock.API{CreateServiceFn: createServiceOK},\n\t\t\tWantOutput: \"Created service 12345\",\n\t\t},\n\t\t{\n\t\t\tName:       \"success with short flag and comment\",\n\t\t\tArgs:       \"-n Foo --comment Hello\",\n\t\t\tAPI:        &mock.API{CreateServiceFn: createServiceOK},\n\t\t\tWantOutput: \"Created service 12345\",\n\t\t},\n\t\t{\n\t\t\tName:      \"api failure\",\n\t\t\tArgs:      \"-n Foo\",\n\t\t\tAPI:       &mock.API{CreateServiceFn: createServiceError},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, \"create\"}, scenarios)\n}\n\nfunc TestServiceList(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName: \"api failure\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tGetServicesFn: func(ctx context.Context, _ *fastly.GetServicesInput) *fastly.ListPaginator[fastly.Service] {\n\t\t\t\t\treturn fastly.NewPaginator[fastly.Service](ctx, &mock.HTTPClient{\n\t\t\t\t\t\tErrors: []error{\n\t\t\t\t\t\t\ttestutil.Err,\n\t\t\t\t\t\t},\n\t\t\t\t\t\tResponses: []*http.Response{nil},\n\t\t\t\t\t}, fastly.ListOpts{}, \"/example\")\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:      \"\",\n\t\t\tWantError: testutil.Err.Error(),\n\t\t},\n\t\t{\n\t\t\tName: \"success with pagination\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tGetServicesFn: func(ctx context.Context, _ *fastly.GetServicesInput) *fastly.ListPaginator[fastly.Service] {\n\t\t\t\t\treturn fastly.NewPaginator[fastly.Service](ctx, &mock.HTTPClient{\n\t\t\t\t\t\tErrors: []error{nil},\n\t\t\t\t\t\tResponses: []*http.Response{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tBody: io.NopCloser(strings.NewReader(`[\n                  {\n                    \"name\": \"Foo\",\n                    \"id\": \"123\",\n                    \"type\": \"wasm\",\n                    \"version\": 2,\n                    \"updated_at\": \"2021-06-15T23:00:00Z\"\n                  },\n                  {\n                    \"name\": \"Bar\",\n                    \"id\": \"456\",\n                    \"type\": \"wasm\",\n                    \"version\": 1,\n                    \"updated_at\": \"2021-06-15T23:00:00Z\"\n                  },\n                  {\n                    \"name\": \"Baz\",\n                    \"id\": \"789\",\n                    \"type\": \"vcl\",\n                    \"version\": 1\n                  }\n                ]`)),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t}, fastly.ListOpts{}, \"/example\")\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:       \"--per-page 1\",\n\t\t\tWantOutput: listServicesShortOutput,\n\t\t},\n\t\t{\n\t\t\tName: \"success with verbose\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tGetServicesFn: func(ctx context.Context, _ *fastly.GetServicesInput) *fastly.ListPaginator[fastly.Service] {\n\t\t\t\t\treturn fastly.NewPaginator[fastly.Service](ctx, &mock.HTTPClient{\n\t\t\t\t\t\tErrors: []error{nil},\n\t\t\t\t\t\tResponses: []*http.Response{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tBody: io.NopCloser(strings.NewReader(`[\n                  {\n                    \"name\": \"Foo\",\n                    \"id\": \"123\",\n                    \"type\": \"wasm\",\n                    \"version\": 2,\n                    \"updated_at\": \"2021-06-15T23:00:00Z\",\n                    \"customer_id\": \"mycustomerid\",\n                    \"versions\": [\n                      {\n                        \"number\": 1,\n                        \"comment\": \"a\",\n                        \"service_id\": \"b\",\n                        \"active\": false,\n                        \"locked\": false,\n                        \"deployed\": false,\n                        \"staging\": false,\n                        \"testing\": false,\n                        \"created_at\": \"2021-06-15T23:00:00Z\",\n                        \"deleted_at\": \"2021-06-15T23:00:00Z\",\n                        \"updated_at\": \"2021-06-15T23:00:00Z\"\n                      },\n                      {\n                        \"number\": 2,\n                        \"comment\": \"c\",\n                        \"service_id\": \"d\",\n                        \"active\": true,\n                        \"locked\": false,\n                        \"deployed\": true,\n                        \"staging\": false,\n                        \"testing\": false,\n                        \"created_at\": \"2021-06-15T23:00:00Z\",\n                        \"updated_at\": \"2021-06-15T23:00:00Z\"\n                      }\n                    ]\n                  },\n                  {\n                    \"name\": \"Bar\",\n                    \"id\": \"456\",\n                    \"type\": \"wasm\",\n                    \"version\": 1,\n                    \"updated_at\": \"2021-06-15T23:00:00Z\",\n                    \"customer_id\": \"mycustomerid\"\n                  },\n                  {\n                    \"name\": \"Baz\",\n                    \"id\": \"789\",\n                    \"type\": \"vcl\",\n                    \"version\": 1,\n                    \"customer_id\": \"mycustomerid\"\n                  }\n                ]`)),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t}, fastly.ListOpts{}, \"/example\")\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:       \"--verbose\",\n\t\t\tWantOutput: listServicesVerboseOutput,\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, \"list\"}, scenarios)\n}\n\nfunc TestServiceDescribe(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"no service id\",\n\t\t\tArgs:      \"\",\n\t\t\tAPI:       &mock.API{GetServiceDetailsFn: describeServiceOK},\n\t\t\tEnvVars:   map[string]string{\"FASTLY_SERVICE_ID\": \"\"},\n\t\t\tWantError: \"error reading service: no service ID found\",\n\t\t},\n\t\t{\n\t\t\tName:       \"success\",\n\t\t\tArgs:       \"--service-id 123\",\n\t\t\tAPI:        &mock.API{GetServiceDetailsFn: describeServiceOK},\n\t\t\tWantOutput: describeServiceShortOutput,\n\t\t},\n\t\t{\n\t\t\tName:       \"verbose flag after args\",\n\t\t\tArgs:       \"--service-id 123 --verbose\",\n\t\t\tAPI:        &mock.API{GetServiceDetailsFn: describeServiceOK},\n\t\t\tWantOutput: describeServiceVerboseOutput,\n\t\t},\n\t\t{\n\t\t\tName:       \"verbose short flag after args\",\n\t\t\tArgs:       \"--service-id 123 -v\",\n\t\t\tAPI:        &mock.API{GetServiceDetailsFn: describeServiceOK},\n\t\t\tWantOutput: describeServiceVerboseOutput,\n\t\t},\n\t\t{\n\t\t\tName:      \"api failure\",\n\t\t\tArgs:      \"--service-id 123\",\n\t\t\tAPI:       &mock.API{GetServiceDetailsFn: describeServiceError},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, \"describe\"}, scenarios)\n}\n\nfunc TestServiceSearch(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"missing required flag\",\n\t\t\tArgs:      \"\",\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tName:       \"success\",\n\t\t\tArgs:       \"--name Foo\",\n\t\t\tAPI:        &mock.API{SearchServiceFn: searchServiceOK},\n\t\t\tWantOutput: searchServiceShortOutput,\n\t\t},\n\t\t{\n\t\t\tName:       \"success with verbose\",\n\t\t\tArgs:       \"--name Foo -v\",\n\t\t\tAPI:        &mock.API{SearchServiceFn: searchServiceOK},\n\t\t\tWantOutput: searchServiceVerboseOutput,\n\t\t},\n\t\t{\n\t\t\tName:      \"missing flag value\",\n\t\t\tArgs:      \"--name\",\n\t\t\tAPI:       &mock.API{SearchServiceFn: searchServiceOK},\n\t\t\tWantError: \"error parsing arguments: expected argument for flag '--name'\",\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, \"search\"}, scenarios)\n}\n\nfunc TestServiceUpdate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName: \"no service id\",\n\t\t\tArgs: \"\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:    testutil.GetVersion,\n\t\t\t\tGetServiceFn:    getServiceOK,\n\t\t\t\tUpdateServiceFn: updateServiceOK,\n\t\t\t},\n\t\t\tEnvVars:   map[string]string{\"FASTLY_SERVICE_ID\": \"\"},\n\t\t\tWantError: \"error reading service: no service ID found\",\n\t\t},\n\t\t{\n\t\t\tName:      \"missing name or comment\",\n\t\t\tArgs:      \"--service-id 12345\",\n\t\t\tAPI:       &mock.API{UpdateServiceFn: updateServiceOK},\n\t\t\tWantError: \"error parsing arguments: must provide either --name or --comment to update service\",\n\t\t},\n\t\t{\n\t\t\tName:       \"success with long flag\",\n\t\t\tArgs:       \"--service-id 12345 --name Foo\",\n\t\t\tAPI:        &mock.API{UpdateServiceFn: updateServiceOK},\n\t\t\tWantOutput: \"Updated service 12345\",\n\t\t},\n\t\t{\n\t\t\tName:       \"success with short flag and equals\",\n\t\t\tArgs:       \"--service-id 12345 -n=Foo\",\n\t\t\tAPI:        &mock.API{UpdateServiceFn: updateServiceOK},\n\t\t\tWantOutput: \"Updated service 12345\",\n\t\t},\n\t\t{\n\t\t\tName:       \"success with long flag duplicate\",\n\t\t\tArgs:       \"--service-id 12345 --name Foo\",\n\t\t\tAPI:        &mock.API{UpdateServiceFn: updateServiceOK},\n\t\t\tWantOutput: \"Updated service 12345\",\n\t\t},\n\t\t{\n\t\t\tName:       \"success with name and comment\",\n\t\t\tArgs:       \"--service-id 12345 --name Foo --comment Hello\",\n\t\t\tAPI:        &mock.API{UpdateServiceFn: updateServiceOK},\n\t\t\tWantOutput: \"Updated service 12345\",\n\t\t},\n\t\t{\n\t\t\tName:       \"success with short flag and comment\",\n\t\t\tArgs:       \"--service-id 12345 -n Foo --comment Hello\",\n\t\t\tAPI:        &mock.API{UpdateServiceFn: updateServiceOK},\n\t\t\tWantOutput: \"Updated service 12345\",\n\t\t},\n\t\t{\n\t\t\tName:      \"api failure\",\n\t\t\tArgs:      \"--service-id 12345 -n Foo\",\n\t\t\tAPI:       &mock.API{UpdateServiceFn: updateServiceError},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, \"update\"}, scenarios)\n}\n\nfunc TestServiceDelete(t *testing.T) {\n\targs := testutil.SplitArgs\n\tnonEmptyServiceID := regexp.MustCompile(`service_id = \"[^\"]+\"`)\n\n\tscenarios := []struct {\n\t\targs                 []string\n\t\tapi                  mock.API\n\t\tmanifest             string\n\t\twantError            string\n\t\twantOutput           string\n\t\texpectEmptyServiceID bool\n\t}{\n\t\t{\n\t\t\targs:      args(\"service delete\"),\n\t\t\tapi:       mock.API{DeleteServiceFn: deleteServiceOK},\n\t\t\tmanifest:  \"fastly-no-serviceid.toml\",\n\t\t\twantError: \"error reading service: no service ID found\",\n\t\t},\n\t\t{\n\t\t\targs:                 args(\"service delete\"),\n\t\t\tapi:                  mock.API{DeleteServiceFn: deleteServiceOK},\n\t\t\tmanifest:             \"fastly-valid.toml\",\n\t\t\twantOutput:           \"Deleted service ID 123\",\n\t\t\texpectEmptyServiceID: true,\n\t\t},\n\t\t{\n\t\t\targs:       args(\"service delete --service-id 001\"),\n\t\t\tapi:        mock.API{DeleteServiceFn: deleteServiceOK},\n\t\t\twantOutput: \"Deleted service ID 001\",\n\t\t},\n\t\t{\n\t\t\targs:                 args(\"service delete --service-id 001\"),\n\t\t\tapi:                  mock.API{DeleteServiceFn: deleteServiceOK},\n\t\t\tmanifest:             \"fastly-valid.toml\",\n\t\t\twantOutput:           \"Deleted service ID 001\",\n\t\t\texpectEmptyServiceID: false,\n\t\t},\n\t\t{\n\t\t\targs:      args(\"service delete --service-id 001\"),\n\t\t\tapi:       mock.API{DeleteServiceFn: deleteServiceError},\n\t\t\tmanifest:  \"fastly-valid.toml\",\n\t\t\twantError: errTest.Error(),\n\t\t},\n\t}\n\tfor testcaseIdx := range scenarios {\n\t\ttestcase := &scenarios[testcaseIdx]\n\t\tt.Run(strings.Join(testcase.args, \" \"), func(t *testing.T) {\n\t\t\t// We're going to chdir to a temp environment,\n\t\t\t// so save the PWD to return to, afterwards.\n\t\t\tpwd, err := os.Getwd()\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\t// Create test environment\n\t\t\topts := testutil.EnvOpts{T: t}\n\t\t\tif testcase.manifest != \"\" {\n\t\t\t\tb, err := os.ReadFile(filepath.Join(\"testdata\", testcase.manifest))\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatal(err)\n\t\t\t\t}\n\t\t\t\topts.Write = []testutil.FileIO{\n\t\t\t\t\t{Src: string(b), Dst: manifest.Filename},\n\t\t\t\t}\n\t\t\t}\n\t\t\trootdir := testutil.NewEnv(opts)\n\t\t\tdefer os.RemoveAll(rootdir)\n\n\t\t\t// Before running the test, chdir into the temp environment.\n\t\t\t// When we're done, chdir back to our original location.\n\t\t\t// This is so we can reliably assert file structure.\n\t\t\tif err := os.Chdir(rootdir); err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tdefer func() {\n\t\t\t\t_ = os.Chdir(pwd)\n\t\t\t}()\n\n\t\t\tvar stdout bytes.Buffer\n\t\t\tapp.Init = func(_ []string, _ io.Reader) (*global.Data, error) {\n\t\t\t\trunOpts := testutil.MockGlobalData(testcase.args, &stdout)\n\t\t\t\trunOpts.APIClientFactory = mock.APIClient(testcase.api)\n\t\t\t\treturn runOpts, nil\n\t\t\t}\n\t\t\trunErr := app.Run(testcase.args, nil)\n\t\t\ttestutil.AssertErrorContains(t, runErr, testcase.wantError)\n\t\t\ttestutil.AssertStringContains(t, stdout.String(), testcase.wantOutput)\n\n\t\t\tif testcase.manifest != \"\" {\n\t\t\t\tm := filepath.Join(rootdir, manifest.Filename)\n\t\t\t\tb, err := os.ReadFile(m)\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatal(err)\n\t\t\t\t}\n\n\t\t\t\tif testcase.expectEmptyServiceID {\n\t\t\t\t\ttestutil.AssertStringContains(t, string(b), `service_id = \"\"`)\n\t\t\t\t} else if !nonEmptyServiceID.Match(b) && runErr == nil {\n\t\t\t\t\t// The runErr check is to prevent the first test case from causing an\n\t\t\t\t\t// accidental failure. As the fastly.toml doesn't have a service_id\n\t\t\t\t\t// set, while marshalling back and forth it'll get converted to an\n\t\t\t\t\t// empty string in the manifest file which will accidentally trigger\n\t\t\t\t\t// the following test error otherwise if we don't check for the nil\n\t\t\t\t\t// error value. Because that first test case expects an error to be\n\t\t\t\t\t// raised we know that we can safely check for `runErr == nil` here.\n\t\t\t\t\tt.Fatal(\"expected service_id to contain a value\")\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nvar errTest = errors.New(\"fixture error\")\n\nfunc createServiceOK(_ context.Context, i *fastly.CreateServiceInput) (*fastly.Service, error) {\n\treturn &fastly.Service{\n\t\tServiceID: fastly.ToPointer(\"12345\"),\n\t\tName:      i.Name,\n\t}, nil\n}\n\nfunc createServiceError(_ context.Context, _ *fastly.CreateServiceInput) (*fastly.Service, error) {\n\treturn nil, errTest\n}\n\nvar listServicesShortOutput = strings.TrimSpace(`\nNAME  ID   TYPE  ACTIVE VERSION  LAST EDITED (UTC)\nFoo   123  wasm  2               2021-06-15 23:00\nBar   456  wasm  1               2021-06-15 23:00\nBaz   789  vcl   1               n/a\n`) + \"\\n\"\n\nvar listServicesVerboseOutput = strings.TrimSpace(`\nFastly API endpoint: https://api.fastly.com\nFastly API token provided via config file (auth: user)\n\nService 1/3\n\tID: 123\n\tName: Foo\n\tType: wasm\n\tCustomer ID: mycustomerid\n\tLast edited (UTC): 2021-06-15 23:00\n\tActive version: 2\n\tVersions: 2\n\t\tVersion 1/2\n\t\t\tNumber: 1\n\t\t\tComment: a\n\t\t\tService ID: b\n\t\t\tActive: false\n\t\t\tLocked: false\n\t\t\tDeployed: false\n\t\t\tStaged: false\n\t\t\tTesting: false\n\t\t\tCreated (UTC): 2021-06-15 23:00\n\t\t\tLast edited (UTC): 2021-06-15 23:00\n\t\t\tDeleted (UTC): 2021-06-15 23:00\n\t\tVersion 2/2\n\t\t\tNumber: 2\n\t\t\tComment: c\n\t\t\tService ID: d\n\t\t\tActive: true\n\t\t\tLocked: false\n\t\t\tDeployed: true\n\t\t\tStaged: false\n\t\t\tTesting: false\n\t\t\tCreated (UTC): 2021-06-15 23:00\n\t\t\tLast edited (UTC): 2021-06-15 23:00\n\nService 2/3\n\tID: 456\n\tName: Bar\n\tType: wasm\n\tCustomer ID: mycustomerid\n\tLast edited (UTC): 2021-06-15 23:00\n\tActive version: 1\n\tVersions: 0\n\nService 3/3\n\tID: 789\n\tName: Baz\n\tType: vcl\n\tCustomer ID: mycustomerid\n\tActive version: 1\n\tVersions: 0\n`) + \"\\n\\n\"\n\nfunc getServiceOK(_ context.Context, _ *fastly.GetServiceInput) (*fastly.Service, error) {\n\treturn &fastly.Service{\n\t\tServiceID: fastly.ToPointer(\"12345\"),\n\t\tName:      fastly.ToPointer(\"Foo\"),\n\t\tComment:   fastly.ToPointer(\"Bar\"),\n\t}, nil\n}\n\nfunc describeServiceOK(_ context.Context, _ *fastly.GetServiceDetailsInput) (*fastly.ServiceDetail, error) {\n\treturn &fastly.ServiceDetail{\n\t\tServiceID:  fastly.ToPointer(\"123\"),\n\t\tName:       fastly.ToPointer(\"Foo\"),\n\t\tType:       fastly.ToPointer(\"wasm\"),\n\t\tComment:    fastly.ToPointer(\"example\"),\n\t\tCustomerID: fastly.ToPointer(\"mycustomerid\"),\n\t\tActiveVersion: &fastly.Version{\n\t\t\tNumber:    fastly.ToPointer(2),\n\t\t\tComment:   fastly.ToPointer(\"c\"),\n\t\t\tServiceID: fastly.ToPointer(\"d\"),\n\t\t\tActive:    fastly.ToPointer(true),\n\t\t\tDeployed:  fastly.ToPointer(true),\n\t\t\tCreatedAt: testutil.MustParseTimeRFC3339(\"2001-03-03T04:05:06Z\"),\n\t\t\tUpdatedAt: testutil.MustParseTimeRFC3339(\"2001-03-04T04:05:06Z\"),\n\t\t},\n\t\tUpdatedAt: testutil.MustParseTimeRFC3339(\"2010-11-15T19:01:02Z\"),\n\t\tVersions: []*fastly.Version{\n\t\t\t{\n\t\t\t\tNumber:    fastly.ToPointer(1),\n\t\t\t\tComment:   fastly.ToPointer(\"a\"),\n\t\t\t\tServiceID: fastly.ToPointer(\"b\"),\n\t\t\t\tCreatedAt: testutil.MustParseTimeRFC3339(\"2001-02-03T04:05:06Z\"),\n\t\t\t\tUpdatedAt: testutil.MustParseTimeRFC3339(\"2001-02-04T04:05:06Z\"),\n\t\t\t\tDeletedAt: testutil.MustParseTimeRFC3339(\"2001-02-05T04:05:06Z\"),\n\t\t\t},\n\t\t\t{\n\t\t\t\tNumber:    fastly.ToPointer(2),\n\t\t\t\tComment:   fastly.ToPointer(\"c\"),\n\t\t\t\tServiceID: fastly.ToPointer(\"d\"),\n\t\t\t\tActive:    fastly.ToPointer(true),\n\t\t\t\tDeployed:  fastly.ToPointer(true),\n\t\t\t\tCreatedAt: testutil.MustParseTimeRFC3339(\"2001-03-03T04:05:06Z\"),\n\t\t\t\tUpdatedAt: testutil.MustParseTimeRFC3339(\"2001-03-04T04:05:06Z\"),\n\t\t\t},\n\t\t},\n\t}, nil\n}\n\nfunc describeServiceError(_ context.Context, _ *fastly.GetServiceDetailsInput) (*fastly.ServiceDetail, error) {\n\treturn nil, errTest\n}\n\nvar describeServiceShortOutput = strings.TrimSpace(`\nID: 123\nName: Foo\nType: wasm\nComment: example\nCustomer ID: mycustomerid\nLast edited (UTC): 2010-11-15 19:01\nActive version:\n\tNumber: 2\n\tComment: c\n\tService ID: d\n\tActive: true\n\tDeployed: true\n\tCreated (UTC): 2001-03-03 04:05\n\tLast edited (UTC): 2001-03-04 04:05\nVersions: 2\n\tVersion 1/2\n\t\tNumber: 1\n\t\tComment: a\n\t\tService ID: b\n\t\tCreated (UTC): 2001-02-03 04:05\n\t\tLast edited (UTC): 2001-02-04 04:05\n\t\tDeleted (UTC): 2001-02-05 04:05\n\tVersion 2/2\n\t\tNumber: 2\n\t\tComment: c\n\t\tService ID: d\n\t\tActive: true\n\t\tDeployed: true\n\t\tCreated (UTC): 2001-03-03 04:05\n\t\tLast edited (UTC): 2001-03-04 04:05\n`) + \"\\n\"\n\nvar describeServiceVerboseOutput = strings.TrimSpace(`\nFastly API endpoint: https://api.fastly.com\nFastly API token provided via config file (auth: user)\n\nService ID (via --service-id): 123\n\nID: 123\nName: Foo\nType: wasm\nComment: example\nCustomer ID: mycustomerid\nLast edited (UTC): 2010-11-15 19:01\nActive version:\n\tNumber: 2\n\tComment: c\n\tService ID: d\n\tActive: true\n\tDeployed: true\n\tCreated (UTC): 2001-03-03 04:05\n\tLast edited (UTC): 2001-03-04 04:05\nVersions: 2\n\tVersion 1/2\n\t\tNumber: 1\n\t\tComment: a\n\t\tService ID: b\n\t\tCreated (UTC): 2001-02-03 04:05\n\t\tLast edited (UTC): 2001-02-04 04:05\n\t\tDeleted (UTC): 2001-02-05 04:05\n\tVersion 2/2\n\t\tNumber: 2\n\t\tComment: c\n\t\tService ID: d\n\t\tActive: true\n\t\tDeployed: true\n\t\tCreated (UTC): 2001-03-03 04:05\n\t\tLast edited (UTC): 2001-03-04 04:05\n`) + \"\\n\"\n\nfunc searchServiceOK(_ context.Context, _ *fastly.SearchServiceInput) (*fastly.Service, error) {\n\treturn &fastly.Service{\n\t\tServiceID:  fastly.ToPointer(\"123\"),\n\t\tName:       fastly.ToPointer(\"Foo\"),\n\t\tType:       fastly.ToPointer(\"wasm\"),\n\t\tCustomerID: fastly.ToPointer(\"mycustomerid\"),\n\t\tUpdatedAt:  testutil.MustParseTimeRFC3339(\"2010-11-15T19:01:02Z\"),\n\t\tVersions: []*fastly.Version{\n\t\t\t{\n\t\t\t\tNumber:    fastly.ToPointer(1),\n\t\t\t\tComment:   fastly.ToPointer(\"a\"),\n\t\t\t\tServiceID: fastly.ToPointer(\"b\"),\n\t\t\t\tCreatedAt: testutil.MustParseTimeRFC3339(\"2001-02-03T04:05:06Z\"),\n\t\t\t\tUpdatedAt: testutil.MustParseTimeRFC3339(\"2001-02-04T04:05:06Z\"),\n\t\t\t\tDeletedAt: testutil.MustParseTimeRFC3339(\"2001-02-05T04:05:06Z\"),\n\t\t\t},\n\t\t\t{\n\t\t\t\tNumber:    fastly.ToPointer(2),\n\t\t\t\tComment:   fastly.ToPointer(\"c\"),\n\t\t\t\tServiceID: fastly.ToPointer(\"d\"),\n\t\t\t\tActive:    fastly.ToPointer(true),\n\t\t\t\tDeployed:  fastly.ToPointer(true),\n\t\t\t\tCreatedAt: testutil.MustParseTimeRFC3339(\"2001-03-03T04:05:06Z\"),\n\t\t\t\tUpdatedAt: testutil.MustParseTimeRFC3339(\"2001-03-04T04:05:06Z\"),\n\t\t\t},\n\t\t},\n\t}, nil\n}\n\nvar searchServiceShortOutput = strings.TrimSpace(`\nID: 123\nName: Foo\nType: wasm\nCustomer ID: mycustomerid\nLast edited (UTC): 2010-11-15 19:01\nVersions: 2\n\tVersion 1/2\n\t\tNumber: 1\n\t\tComment: a\n\t\tService ID: b\n\t\tCreated (UTC): 2001-02-03 04:05\n\t\tLast edited (UTC): 2001-02-04 04:05\n\t\tDeleted (UTC): 2001-02-05 04:05\n\tVersion 2/2\n\t\tNumber: 2\n\t\tComment: c\n\t\tService ID: d\n\t\tActive: true\n\t\tDeployed: true\n\t\tCreated (UTC): 2001-03-03 04:05\n\t\tLast edited (UTC): 2001-03-04 04:05\n`) + \"\\n\"\n\nvar searchServiceVerboseOutput = strings.TrimSpace(`\nFastly API endpoint: https://api.fastly.com\nFastly API token provided via config file (auth: user)\n\nID: 123\nName: Foo\nType: wasm\nCustomer ID: mycustomerid\nLast edited (UTC): 2010-11-15 19:01\nVersions: 2\n\tVersion 1/2\n\t\tNumber: 1\n\t\tComment: a\n\t\tService ID: b\n\t\tCreated (UTC): 2001-02-03 04:05\n\t\tLast edited (UTC): 2001-02-04 04:05\n\t\tDeleted (UTC): 2001-02-05 04:05\n\tVersion 2/2\n\t\tNumber: 2\n\t\tComment: c\n\t\tService ID: d\n\t\tActive: true\n\t\tDeployed: true\n\t\tCreated (UTC): 2001-03-03 04:05\n\t\tLast edited (UTC): 2001-03-04 04:05\n`) + \"\\n\"\n\nfunc updateServiceOK(_ context.Context, _ *fastly.UpdateServiceInput) (*fastly.Service, error) {\n\treturn &fastly.Service{\n\t\tServiceID: fastly.ToPointer(\"12345\"),\n\t}, nil\n}\n\nfunc updateServiceError(_ context.Context, _ *fastly.UpdateServiceInput) (*fastly.Service, error) {\n\treturn nil, errTest\n}\n\nfunc deleteServiceOK(_ context.Context, _ *fastly.DeleteServiceInput) error {\n\treturn nil\n}\n\nfunc deleteServiceError(_ context.Context, _ *fastly.DeleteServiceInput) error {\n\treturn errTest\n}\n"
  },
  {
    "path": "pkg/commands/service/testdata/fastly-no-serviceid.toml",
    "content": "manifest_version = 2\nname = \"Default Rust template\"\ndescription = \"Default package template for Rust based edge compute projects.\"\nauthors = [\"phamann <patrick@fastly.com>\"]\nlanguage = \"rust\"\n"
  },
  {
    "path": "pkg/commands/service/testdata/fastly-valid.toml",
    "content": "manifest_version = 2\nname = \"Default Rust template\"\ndescription = \"Default package template for Rust based edge compute projects.\"\nauthors = [\"phamann <patrick@fastly.com>\"]\nlanguage = \"rust\"\nservice_id = \"123\"\n"
  },
  {
    "path": "pkg/commands/service/update.go",
    "content": "package service\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// UpdateCommand calls the Fastly API to create services.\ntype UpdateCommand struct {\n\targparser.Base\n\n\tcomment     argparser.OptionalString\n\tinput       fastly.UpdateServiceInput\n\tname        argparser.OptionalString\n\tserviceName argparser.OptionalServiceNameID\n}\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"update\", \"Update a Fastly service\")\n\n\t// Optional.\n\tc.CmdClause.Flag(\"comment\", \"Human-readable comment\").Action(c.comment.Set).StringVar(&c.comment.Value)\n\tc.CmdClause.Flag(\"name\", \"Service name\").Short('n').Action(c.name.Set).StringVar(&c.name.Value)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *UpdateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, source, flag, err := argparser.ServiceID(c.serviceName, *c.Globals.Manifest, c.Globals.APIClient, c.Globals.ErrLog)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif c.Globals.Verbose() {\n\t\targparser.DisplayServiceID(serviceID, flag, source, out)\n\t}\n\n\tc.input.ServiceID = serviceID\n\n\tif !c.name.WasSet && !c.comment.WasSet {\n\t\treturn fmt.Errorf(\"error parsing arguments: must provide either --name or --comment to update service\")\n\t}\n\n\tif c.name.WasSet {\n\t\tc.input.Name = &c.name.Value\n\t}\n\tif c.comment.WasSet {\n\t\tc.input.Comment = &c.comment.Value\n\t}\n\n\ts, err := c.Globals.APIClient.UpdateService(context.TODO(), &c.input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":   serviceID,\n\t\t\t\"Service Name\": c.name.Value,\n\t\t\t\"Comment\":      c.comment.Value,\n\t\t})\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Updated service %s\", fastly.ToValue(s.ServiceID))\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/vcl/condition/condition_test.go",
    "content": "package condition_test\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\ttop \"github.com/fastly/cli/pkg/commands/service\"\n\troot \"github.com/fastly/cli/pkg/commands/service/vcl\"\n\tsub \"github.com/fastly/cli/pkg/commands/service/vcl/condition\"\n\t\"github.com/fastly/cli/pkg/mock\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n)\n\nfunc TestConditionCreate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"--version 1\",\n\t\t\tEnvVars:   map[string]string{\"FASTLY_SERVICE_ID\": \"\"},\n\t\t\tWantError: \"error reading service: no service ID found\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name always_false --statement false --type REQUEST --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:      testutil.GetVersion,\n\t\t\t\tCloneVersionFn:    testutil.CloneVersionResult(4),\n\t\t\t\tCreateConditionFn: createConditionOK,\n\t\t\t},\n\t\t\tWantOutput: \"Created condition always_false (service 123 version 4)\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name always_false --statement false --type REQUEST --priority 10 --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:      testutil.GetVersion,\n\t\t\t\tCloneVersionFn:    testutil.CloneVersionResult(4),\n\t\t\t\tCreateConditionFn: createConditionError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{top.CommandName, root.CommandName, sub.CommandName, \"create\"}, scenarios)\n}\n\nfunc TestConditionDelete(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"--service-id 123 --version 1\",\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name always_false --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:      testutil.GetVersion,\n\t\t\t\tCloneVersionFn:    testutil.CloneVersionResult(4),\n\t\t\t\tDeleteConditionFn: deleteConditionError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name always_false --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:      testutil.GetVersion,\n\t\t\t\tCloneVersionFn:    testutil.CloneVersionResult(4),\n\t\t\t\tDeleteConditionFn: deleteConditionOK,\n\t\t\t},\n\t\t\tWantOutput: \"Deleted condition always_false (service 123 version 4)\",\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{top.CommandName, root.CommandName, sub.CommandName, \"delete\"}, scenarios)\n}\n\nfunc TestConditionUpdate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"--service-id 123 --version 1 --new-name false_always --comment \",\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name always_false --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:      testutil.GetVersion,\n\t\t\t\tCloneVersionFn:    testutil.CloneVersionResult(4),\n\t\t\t\tUpdateConditionFn: updateConditionOK,\n\t\t\t},\n\t\t\tWantError: \"error parsing arguments: must provide either --new-name, --statement, --type, --priority or --comment to update condition\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name always_false --new-name false_always --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:      testutil.GetVersion,\n\t\t\t\tCloneVersionFn:    testutil.CloneVersionResult(4),\n\t\t\t\tUpdateConditionFn: updateConditionError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name always_false --new-name false_always --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:      testutil.GetVersion,\n\t\t\t\tCloneVersionFn:    testutil.CloneVersionResult(4),\n\t\t\t\tUpdateConditionFn: updateConditionOK,\n\t\t\t},\n\t\t\tWantOutput: \"Updated condition false_always (service 123 version 4)\",\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{top.CommandName, root.CommandName, sub.CommandName, \"update\"}, scenarios)\n}\n\nfunc TestConditionDescribe(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"--service-id 123 --version 1\",\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name always_false\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tGetConditionFn: getConditionError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --name always_false\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tGetConditionFn: getConditionOK,\n\t\t\t},\n\t\t\tWantOutput: describeConditionOutput,\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{top.CommandName, root.CommandName, sub.CommandName, \"describe\"}, scenarios)\n}\n\nfunc TestConditionList(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:     testutil.GetVersion,\n\t\t\t\tListConditionsFn: listConditionsOK,\n\t\t\t},\n\t\t\tWantOutput: listConditionsShortOutput,\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --verbose\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:     testutil.GetVersion,\n\t\t\t\tListConditionsFn: listConditionsOK,\n\t\t\t},\n\t\t\tWantOutput: listConditionsVerboseOutput,\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 -v\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:     testutil.GetVersion,\n\t\t\t\tListConditionsFn: listConditionsOK,\n\t\t\t},\n\t\t\tWantOutput: listConditionsVerboseOutput,\n\t\t},\n\t\t{\n\t\t\tArgs: \"--verbose --service-id 123 --version 1\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:     testutil.GetVersion,\n\t\t\t\tListConditionsFn: listConditionsOK,\n\t\t\t},\n\t\t\tWantOutput: listConditionsVerboseOutput,\n\t\t},\n\t\t{\n\t\t\tArgs: \"-v --service-id 123 --version 1\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:     testutil.GetVersion,\n\t\t\t\tListConditionsFn: listConditionsOK,\n\t\t\t},\n\t\t\tWantOutput: listConditionsVerboseOutput,\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:     testutil.GetVersion,\n\t\t\t\tListConditionsFn: listConditionsError,\n\t\t\t},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{top.CommandName, root.CommandName, sub.CommandName, \"list\"}, scenarios)\n}\n\nvar describeConditionOutput = \"\\n\" + strings.TrimSpace(`\nService ID: 123\nVersion: 1\nName: always_false\nStatement: false\nType: CACHE\nPriority: 10\n`) + \"\\n\"\n\nvar listConditionsShortOutput = strings.TrimSpace(`\nSERVICE  VERSION  NAME                  STATEMENT  TYPE     PRIORITY\n123      1        always_false_request  false      REQUEST  10\n123      1        always_false_cache    false      CACHE    10\n`) + \"\\n\"\n\nvar listConditionsVerboseOutput = strings.TrimSpace(`\nFastly API endpoint: https://api.fastly.com\nFastly API token provided via config file (auth: user)\n\nService ID (via --service-id): 123\n\nVersion: 1\n\tCondition 1/2\n\t\tName: always_false_request\n\t\tStatement: false\n\t\tType: REQUEST\n\t\tPriority: 10\n\tCondition 2/2\n\t\tName: always_false_cache\n\t\tStatement: false\n\t\tType: CACHE\n\t\tPriority: 10\n`) + \"\\n\\n\"\n\nvar errTest = errors.New(\"fixture error\")\n\nfunc createConditionOK(_ context.Context, i *fastly.CreateConditionInput) (*fastly.Condition, error) {\n\tpriority := 10\n\tif i.Priority != nil {\n\t\tpriority = *i.Priority\n\t}\n\n\tconditionType := \"REQUEST\"\n\tif i.Type != nil {\n\t\tconditionType = *i.Type\n\t}\n\n\treturn &fastly.Condition{\n\t\tServiceID:      fastly.ToPointer(i.ServiceID),\n\t\tServiceVersion: fastly.ToPointer(i.ServiceVersion),\n\t\tName:           i.Name,\n\t\tStatement:      i.Statement,\n\t\tType:           fastly.ToPointer(conditionType),\n\t\tPriority:       fastly.ToPointer(priority),\n\t}, nil\n}\n\nfunc createConditionError(_ context.Context, _ *fastly.CreateConditionInput) (*fastly.Condition, error) {\n\treturn nil, errTest\n}\n\nfunc deleteConditionOK(_ context.Context, _ *fastly.DeleteConditionInput) error {\n\treturn nil\n}\n\nfunc deleteConditionError(_ context.Context, _ *fastly.DeleteConditionInput) error {\n\treturn errTest\n}\n\nfunc updateConditionOK(_ context.Context, i *fastly.UpdateConditionInput) (*fastly.Condition, error) {\n\tpriority := 10\n\tif i.Priority != nil {\n\t\tpriority = *i.Priority\n\t}\n\n\tconditionType := \"REQUEST\"\n\tif i.Type != nil {\n\t\tconditionType = *i.Type\n\t}\n\n\tstatement := \"false\"\n\tif i.Statement != nil {\n\t\tstatement = *i.Type\n\t}\n\n\treturn &fastly.Condition{\n\t\tServiceID:      fastly.ToPointer(i.ServiceID),\n\t\tServiceVersion: fastly.ToPointer(i.ServiceVersion),\n\t\tName:           fastly.ToPointer(i.Name),\n\t\tStatement:      fastly.ToPointer(statement),\n\t\tType:           fastly.ToPointer(conditionType),\n\t\tPriority:       fastly.ToPointer(priority),\n\t}, nil\n}\n\nfunc updateConditionError(_ context.Context, _ *fastly.UpdateConditionInput) (*fastly.Condition, error) {\n\treturn nil, errTest\n}\n\nfunc getConditionOK(_ context.Context, i *fastly.GetConditionInput) (*fastly.Condition, error) {\n\tpriority := 10\n\tconditionType := \"CACHE\"\n\tstatement := \"false\"\n\n\treturn &fastly.Condition{\n\t\tServiceID:      fastly.ToPointer(i.ServiceID),\n\t\tServiceVersion: fastly.ToPointer(i.ServiceVersion),\n\t\tName:           fastly.ToPointer(i.Name),\n\t\tStatement:      fastly.ToPointer(statement),\n\t\tType:           fastly.ToPointer(conditionType),\n\t\tPriority:       fastly.ToPointer(priority),\n\t}, nil\n}\n\nfunc getConditionError(_ context.Context, _ *fastly.GetConditionInput) (*fastly.Condition, error) {\n\treturn nil, errTest\n}\n\nfunc listConditionsOK(_ context.Context, i *fastly.ListConditionsInput) ([]*fastly.Condition, error) {\n\treturn []*fastly.Condition{\n\t\t{\n\t\t\tServiceID:      fastly.ToPointer(i.ServiceID),\n\t\t\tServiceVersion: fastly.ToPointer(i.ServiceVersion),\n\t\t\tName:           fastly.ToPointer(\"always_false_request\"),\n\t\t\tStatement:      fastly.ToPointer(\"false\"),\n\t\t\tType:           fastly.ToPointer(\"REQUEST\"),\n\t\t\tPriority:       fastly.ToPointer(10),\n\t\t},\n\t\t{\n\t\t\tServiceID:      fastly.ToPointer(i.ServiceID),\n\t\t\tServiceVersion: fastly.ToPointer(i.ServiceVersion),\n\t\t\tName:           fastly.ToPointer(\"always_false_cache\"),\n\t\t\tStatement:      fastly.ToPointer(\"false\"),\n\t\t\tType:           fastly.ToPointer(\"CACHE\"),\n\t\t\tPriority:       fastly.ToPointer(10),\n\t\t},\n\t}, nil\n}\n\nfunc listConditionsError(_ context.Context, _ *fastly.ListConditionsInput) ([]*fastly.Condition, error) {\n\treturn nil, errTest\n}\n"
  },
  {
    "path": "pkg/commands/service/vcl/condition/create.go",
    "content": "package condition\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// ConditionTypes are the allowed input values for the --type flag.\n// Reference: https://www.fastly.com/documentation/reference/api/vcl-services/condition\nvar ConditionTypes = []string{\"REQUEST\", \"CACHE\", \"RESPONSE\", \"PREFETCH\"}\n\n// CreateCommand calls the Fastly API to create an appropriate resource.\ntype CreateCommand struct {\n\targparser.Base\n\n\t// Required.\n\tserviceVersion argparser.OptionalServiceVersion\n\n\t// Optional.\n\tautoClone     argparser.OptionalAutoClone\n\tconditionType argparser.OptionalString\n\tname          argparser.OptionalString\n\tpriority      argparser.OptionalInt\n\tserviceName   argparser.OptionalServiceNameID\n\tstatement     argparser.OptionalString\n}\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"create\", \"Create a condition on a Fastly service version\").Alias(\"add\")\n\n\t// Required flags\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional flags\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.autoClone.Set,\n\t\tDst:    &c.autoClone.Value,\n\t})\n\tc.CmdClause.Flag(\"name\", \"Condition name\").Short('n').Action(c.name.Set).StringVar(&c.name.Value)\n\tc.CmdClause.Flag(\"priority\", \"Condition priority\").Action(c.priority.Set).IntVar(&c.priority.Value)\n\tc.CmdClause.Flag(\"statement\", \"Condition statement\").Action(c.statement.Set).StringVar(&c.statement.Value)\n\tc.CmdClause.Flag(\"type\", \"Condition type\").HintOptions(ConditionTypes...).Action(c.conditionType.Set).EnumVar(&c.conditionType.Value, ConditionTypes...)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *CreateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(false),\n\t\tLocked:             optional.Of(false),\n\t\tAutoCloneFlag:      c.autoClone,\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": errors.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tinput := fastly.CreateConditionInput{\n\t\tServiceID:      serviceID,\n\t\tServiceVersion: fastly.ToValue(serviceVersion.Number),\n\t}\n\n\tif c.name.WasSet {\n\t\tinput.Name = &c.name.Value\n\t}\n\tif c.statement.WasSet {\n\t\tinput.Statement = &c.statement.Value\n\t}\n\tif c.conditionType.WasSet {\n\t\tinput.Type = &c.conditionType.Value\n\t}\n\tif c.priority.WasSet {\n\t\tinput.Priority = &c.priority.Value\n\t}\n\tr, err := c.Globals.APIClient.CreateCondition(context.TODO(), &input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fastly.ToValue(serviceVersion.Number),\n\t\t})\n\t\treturn err\n\t}\n\n\ttext.Success(out,\n\t\t\"Created condition %s (service %s version %d)\",\n\t\tfastly.ToValue(r.Name),\n\t\tfastly.ToValue(r.ServiceID),\n\t\tfastly.ToValue(r.ServiceVersion),\n\t)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/vcl/condition/delete.go",
    "content": "package condition\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DeleteCommand calls the Fastly API to delete an appropriate resource.\ntype DeleteCommand struct {\n\targparser.Base\n\tname           string\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n\tautoClone      argparser.OptionalAutoClone\n}\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"delete\", \"Delete a condition on a Fastly service version\").Alias(\"remove\")\n\n\t// Required flags\n\tc.CmdClause.Flag(\"name\", \"Condition name\").Short('n').Required().StringVar(&c.name)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional flags\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.autoClone.Set,\n\t\tDst:    &c.autoClone.Value,\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DeleteCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(false),\n\t\tLocked:             optional.Of(false),\n\t\tAutoCloneFlag:      c.autoClone,\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": errors.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tvar input fastly.DeleteConditionInput\n\tinput.ServiceID = serviceID\n\tinput.ServiceVersion = fastly.ToValue(serviceVersion.Number)\n\tinput.Name = c.name\n\n\tif err := c.Globals.APIClient.DeleteCondition(context.TODO(), &input); err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fastly.ToValue(serviceVersion.Number),\n\t\t})\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Deleted condition %s (service %s version %d)\", c.name, serviceID, fastly.ToValue(serviceVersion.Number))\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/vcl/condition/describe.go",
    "content": "package condition\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// DescribeCommand calls the Fastly API to describe an appropriate resource.\ntype DescribeCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\tname           string\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n}\n\n// NewDescribeCommand returns a usable command registered under the parent.\nfunc NewDescribeCommand(parent argparser.Registerer, g *global.Data) *DescribeCommand {\n\tvar c DescribeCommand\n\tc.CmdClause = parent.Command(\"describe\", \"Show detailed information about a condition on a Fastly service version\").Alias(\"get\")\n\tc.Globals = g\n\n\t// Required flags\n\tc.CmdClause.Flag(\"name\", \"Name of condition\").Short('n').Required().StringVar(&c.name)\n\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional flags\n\tc.RegisterFlagBool(c.JSONFlag())\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DescribeCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn errors.ErrInvalidVerboseJSONCombo\n\t}\n\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": errors.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tvar input fastly.GetConditionInput\n\tinput.ServiceID = serviceID\n\tinput.ServiceVersion = fastly.ToValue(serviceVersion.Number)\n\tinput.Name = c.name\n\n\tr, err := c.Globals.APIClient.GetCondition(context.TODO(), &input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fastly.ToValue(serviceVersion.Number),\n\t\t})\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, r); ok {\n\t\treturn err\n\t}\n\n\tif !c.Globals.Verbose() {\n\t\tfmt.Fprintf(out, \"\\nService ID: %s\\n\", fastly.ToValue(r.ServiceID))\n\t}\n\tfmt.Fprintf(out, \"Version: %d\\n\", fastly.ToValue(r.ServiceVersion))\n\tfmt.Fprintf(out, \"Name: %s\\n\", fastly.ToValue(r.Name))\n\tfmt.Fprintf(out, \"Statement: %s\\n\", fastly.ToValue(r.Statement))\n\tfmt.Fprintf(out, \"Type: %s\\n\", fastly.ToValue(r.Type))\n\tfmt.Fprintf(out, \"Priority: %d\\n\", fastly.ToValue(r.Priority))\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/vcl/condition/doc.go",
    "content": "// Package condition contains commands to inspect and manipulate Fastly service condition.\npackage condition\n"
  },
  {
    "path": "pkg/commands/service/vcl/condition/list.go",
    "content": "package condition\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// ListCommand calls the Fastly API to list appropriate resources.\ntype ListCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n}\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tvar c ListCommand\n\tc.CmdClause = parent.Command(\"list\", \"List condition on a Fastly service version\")\n\tc.Globals = g\n\n\t// Required flags\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional Flags\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *ListCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn errors.ErrInvalidVerboseJSONCombo\n\t}\n\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": errors.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tvar input fastly.ListConditionsInput\n\n\tinput.ServiceID = serviceID\n\tinput.ServiceVersion = fastly.ToValue(serviceVersion.Number)\n\n\to, err := c.Globals.APIClient.ListConditions(context.TODO(), &input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fastly.ToValue(serviceVersion.Number),\n\t\t})\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\tif !c.Globals.Verbose() {\n\t\ttw := text.NewTable(out)\n\t\ttw.AddHeader(\"SERVICE\", \"VERSION\", \"NAME\", \"STATEMENT\", \"TYPE\", \"PRIORITY\")\n\t\tfor _, r := range o {\n\t\t\ttw.AddLine(\n\t\t\t\tfastly.ToValue(r.ServiceID),\n\t\t\t\tfastly.ToValue(r.ServiceVersion),\n\t\t\t\tfastly.ToValue(r.Name),\n\t\t\t\tfastly.ToValue(r.Statement),\n\t\t\t\tfastly.ToValue(r.Type),\n\t\t\t\tfastly.ToValue(r.Priority),\n\t\t\t)\n\t\t}\n\t\ttw.Print()\n\t\treturn nil\n\t}\n\n\tfmt.Fprintf(out, \"Version: %d\\n\", input.ServiceVersion)\n\tfor i, r := range o {\n\t\tfmt.Fprintf(out, \"\\tCondition %d/%d\\n\", i+1, len(o))\n\t\tfmt.Fprintf(out, \"\\t\\tName: %s\\n\", fastly.ToValue(r.Name))\n\t\tfmt.Fprintf(out, \"\\t\\tStatement: %v\\n\", fastly.ToValue(r.Statement))\n\t\tfmt.Fprintf(out, \"\\t\\tType: %v\\n\", fastly.ToValue(r.Type))\n\t\tfmt.Fprintf(out, \"\\t\\tPriority: %v\\n\", fastly.ToValue(r.Priority))\n\t}\n\tfmt.Fprintln(out)\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/vcl/condition/root.go",
    "content": "package condition\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"condition\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manipulate Fastly service version conditions\")\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/service/vcl/condition/update.go",
    "content": "package condition\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// UpdateCommand calls the Fastly API to update an appropriate resource.\ntype UpdateCommand struct {\n\targparser.Base\n\tinput          fastly.UpdateConditionInput\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n\tautoClone      argparser.OptionalAutoClone\n\n\tnewName       argparser.OptionalString\n\tstatement     argparser.OptionalString\n\tconditionType argparser.OptionalString\n\tpriority      argparser.OptionalInt\n\tcomment       argparser.OptionalString\n}\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tvar c UpdateCommand\n\tc.CmdClause = parent.Command(\"update\", \"Update a condition on a Fastly service version\")\n\tc.Globals = g\n\n\t// Required flags\n\tc.CmdClause.Flag(\"name\", \"Domain name\").Short('n').Required().StringVar(&c.input.Name)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional flags\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.autoClone.Set,\n\t\tDst:    &c.autoClone.Value,\n\t})\n\tc.CmdClause.Flag(\"new-name\", \"New condition name\").Action(c.newName.Set).StringVar(&c.newName.Value)\n\tc.CmdClause.Flag(\"priority\", \"Condition priority\").Action(c.priority.Set).IntVar(&c.priority.Value)\n\tc.CmdClause.Flag(\"statement\", \"Condition statement\").Action(c.statement.Set).StringVar(&c.statement.Value)\n\tc.CmdClause.Flag(\"type\", \"Condition type\").Action(c.conditionType.Set).StringVar(&c.conditionType.Value)\n\tc.CmdClause.Flag(\"comment\", \"Condition comment\").Action(c.comment.Set).StringVar(&c.comment.Value)\n\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *UpdateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(false),\n\t\tLocked:             optional.Of(false),\n\t\tAutoCloneFlag:      c.autoClone,\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": errors.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tc.input.ServiceID = serviceID\n\tc.input.ServiceVersion = fastly.ToValue(serviceVersion.Number)\n\n\t// If no argument are provided, error with useful message.\n\tif !c.newName.WasSet && !c.priority.WasSet && !c.statement.WasSet && !c.conditionType.WasSet && !c.comment.WasSet {\n\t\treturn fmt.Errorf(\"error parsing arguments: must provide either --new-name, --statement, --type, --priority or --comment to update condition\")\n\t}\n\n\tif c.newName.WasSet {\n\t\tc.input.Name = c.newName.Value\n\t}\n\tif c.priority.WasSet {\n\t\tc.input.Priority = &c.priority.Value\n\t}\n\tif c.conditionType.WasSet {\n\t\tc.input.Type = &c.conditionType.Value\n\t}\n\tif c.statement.WasSet {\n\t\tc.input.Statement = &c.statement.Value\n\t}\n\tif c.comment.WasSet {\n\t\tc.input.Comment = &c.comment.Value\n\t}\n\n\tr, err := c.Globals.APIClient.UpdateCondition(context.TODO(), &c.input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fastly.ToValue(serviceVersion.Number),\n\t\t})\n\t\treturn err\n\t}\n\n\ttext.Success(out,\n\t\t\"Updated condition %s (service %s version %d)\",\n\t\tfastly.ToValue(r.Name),\n\t\tfastly.ToValue(r.ServiceID),\n\t\tfastly.ToValue(r.ServiceVersion),\n\t)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/vcl/custom/create.go",
    "content": "package custom\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"create\", \"Upload a VCL for a particular service and version\").Alias(\"add\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"content\", \"VCL passed as file path or content, e.g. $(< main.vcl)\").Action(c.content.Set).StringVar(&c.content.Value)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.autoClone.Set,\n\t\tDst:    &c.autoClone.Value,\n\t})\n\tc.CmdClause.Flag(\"main\", \"Whether the VCL is the 'main' entrypoint\").Action(c.main.Set).BoolVar(&c.main.Value)\n\tc.CmdClause.Flag(\"name\", \"The name of the VCL\").Action(c.name.Set).StringVar(&c.name.Value)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\n\treturn &c\n}\n\n// CreateCommand calls the Fastly API to create an appropriate resource.\ntype CreateCommand struct {\n\targparser.Base\n\n\tautoClone      argparser.OptionalAutoClone\n\tcontent        argparser.OptionalString\n\tmain           argparser.OptionalBool\n\tname           argparser.OptionalString\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *CreateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(false),\n\t\tLocked:             optional.Of(false),\n\t\tAutoCloneFlag:      c.autoClone,\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": errors.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tinput := c.constructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\n\tv, err := c.Globals.APIClient.CreateVCL(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fastly.ToValue(serviceVersion.Number),\n\t\t})\n\t\treturn err\n\t}\n\n\ttext.Success(out,\n\t\t\"Created custom VCL '%s' (service: %s, version: %d, main: %t)\",\n\t\tfastly.ToValue(v.Name),\n\t\tfastly.ToValue(v.ServiceID),\n\t\tfastly.ToValue(v.ServiceVersion),\n\t\tfastly.ToValue(v.Main),\n\t)\n\treturn nil\n}\n\n// constructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *CreateCommand) constructInput(serviceID string, serviceVersion int) *fastly.CreateVCLInput {\n\tinput := fastly.CreateVCLInput{\n\t\tServiceID:      serviceID,\n\t\tServiceVersion: serviceVersion,\n\t}\n\tif c.name.WasSet {\n\t\tinput.Name = &c.name.Value\n\t}\n\tif c.content.WasSet {\n\t\tinput.Content = fastly.ToPointer(argparser.Content(c.content.Value))\n\t}\n\tif c.main.WasSet {\n\t\tinput.Main = fastly.ToPointer(c.main.Value)\n\t}\n\treturn &input\n}\n"
  },
  {
    "path": "pkg/commands/service/vcl/custom/custom_test.go",
    "content": "package custom_test\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\ttop \"github.com/fastly/cli/pkg/commands/service\"\n\troot \"github.com/fastly/cli/pkg/commands/service/vcl\"\n\tsub \"github.com/fastly/cli/pkg/commands/service/vcl/custom\"\n\t\"github.com/fastly/cli/pkg/mock\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n)\n\nfunc TestVCLCustomCreate(t *testing.T) {\n\tvar content string\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName: \"validate CreateVCL API error\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tCreateVCLFn: func(_ context.Context, _ *fastly.CreateVCLInput) (*fastly.VCL, error) {\n\t\t\t\t\treturn nil, testutil.Err\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:      \"--content ./testdata/example.vcl --name foo --service-id 123 --version 3\",\n\t\t\tWantError: testutil.Err.Error(),\n\t\t},\n\t\t{\n\t\t\tName: \"validate CreateVCL API success for non-main VCL\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tCreateVCLFn: func(_ context.Context, i *fastly.CreateVCLInput) (*fastly.VCL, error) {\n\t\t\t\t\t// Track the contents parsed\n\t\t\t\t\tcontent = *i.Content\n\t\t\t\t\tif i.Content == nil {\n\t\t\t\t\t\ti.Content = fastly.ToPointer(\"\")\n\t\t\t\t\t}\n\t\t\t\t\tif i.Main == nil {\n\t\t\t\t\t\tb := false\n\t\t\t\t\t\ti.Main = &b\n\t\t\t\t\t}\n\t\t\t\t\tif i.Name == nil {\n\t\t\t\t\t\ti.Name = fastly.ToPointer(\"\")\n\t\t\t\t\t}\n\t\t\t\t\treturn &fastly.VCL{\n\t\t\t\t\t\tContent:        i.Content,\n\t\t\t\t\t\tMain:           i.Main,\n\t\t\t\t\t\tName:           i.Name,\n\t\t\t\t\t\tServiceID:      fastly.ToPointer(i.ServiceID),\n\t\t\t\t\t\tServiceVersion: fastly.ToPointer(i.ServiceVersion),\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:            \"--content ./testdata/example.vcl --name foo --service-id 123 --version 3\",\n\t\t\tWantOutput:      \"Created custom VCL 'foo' (service: 123, version: 3, main: false)\",\n\t\t\tPathContentFlag: &testutil.PathContentFlag{Flag: \"content\", Fixture: \"example.vcl\", Content: func() string { return content }},\n\t\t},\n\t\t{\n\t\t\tName: \"validate CreateVCL API success for main VCL\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tCreateVCLFn: func(_ context.Context, i *fastly.CreateVCLInput) (*fastly.VCL, error) {\n\t\t\t\t\t// Track the contents parsed\n\t\t\t\t\t// Track the contents parsed\n\t\t\t\t\tcontent = *i.Content\n\t\t\t\t\tif i.Content == nil {\n\t\t\t\t\t\ti.Content = fastly.ToPointer(\"\")\n\t\t\t\t\t}\n\t\t\t\t\tif i.Main == nil {\n\t\t\t\t\t\tb := false\n\t\t\t\t\t\ti.Main = &b\n\t\t\t\t\t}\n\t\t\t\t\tif i.Name == nil {\n\t\t\t\t\t\ti.Name = fastly.ToPointer(\"\")\n\t\t\t\t\t}\n\t\t\t\t\treturn &fastly.VCL{\n\t\t\t\t\t\tContent:        i.Content,\n\t\t\t\t\t\tMain:           i.Main,\n\t\t\t\t\t\tName:           i.Name,\n\t\t\t\t\t\tServiceID:      fastly.ToPointer(i.ServiceID),\n\t\t\t\t\t\tServiceVersion: fastly.ToPointer(i.ServiceVersion),\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:            \"--content ./testdata/example.vcl --main --name foo --service-id 123 --version 3\",\n\t\t\tWantOutput:      \"Created custom VCL 'foo' (service: 123, version: 3, main: true)\",\n\t\t\tPathContentFlag: &testutil.PathContentFlag{Flag: \"content\", Fixture: \"example.vcl\", Content: func() string { return content }},\n\t\t},\n\t\t{\n\t\t\tName: \"validate --autoclone results in cloned service version\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tCreateVCLFn: func(_ context.Context, i *fastly.CreateVCLInput) (*fastly.VCL, error) {\n\t\t\t\t\t// Track the contents parsed\n\t\t\t\t\tcontent = *i.Content\n\t\t\t\t\tif i.Content == nil {\n\t\t\t\t\t\ti.Content = fastly.ToPointer(\"\")\n\t\t\t\t\t}\n\t\t\t\t\tif i.Main == nil {\n\t\t\t\t\t\tb := false\n\t\t\t\t\t\ti.Main = &b\n\t\t\t\t\t}\n\t\t\t\t\tif i.Name == nil {\n\t\t\t\t\t\ti.Name = fastly.ToPointer(\"\")\n\t\t\t\t\t}\n\t\t\t\t\treturn &fastly.VCL{\n\t\t\t\t\t\tContent:        i.Content,\n\t\t\t\t\t\tMain:           i.Main,\n\t\t\t\t\t\tName:           i.Name,\n\t\t\t\t\t\tServiceID:      fastly.ToPointer(i.ServiceID),\n\t\t\t\t\t\tServiceVersion: fastly.ToPointer(i.ServiceVersion),\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:            \"--autoclone --content ./testdata/example.vcl --name foo --service-id 123 --version 1\",\n\t\t\tWantOutput:      \"Created custom VCL 'foo' (service: 123, version: 4, main: false)\",\n\t\t\tPathContentFlag: &testutil.PathContentFlag{Flag: \"content\", Fixture: \"example.vcl\", Content: func() string { return content }},\n\t\t},\n\t\t{\n\t\t\tName: \"validate CreateVCL API success with inline VCL content\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tCreateVCLFn: func(_ context.Context, i *fastly.CreateVCLInput) (*fastly.VCL, error) {\n\t\t\t\t\t// Track the contents parsed\n\t\t\t\t\tcontent = *i.Content\n\t\t\t\t\tif i.Content == nil {\n\t\t\t\t\t\ti.Content = fastly.ToPointer(\"\")\n\t\t\t\t\t}\n\t\t\t\t\tif i.Main == nil {\n\t\t\t\t\t\tb := false\n\t\t\t\t\t\ti.Main = &b\n\t\t\t\t\t}\n\t\t\t\t\tif i.Name == nil {\n\t\t\t\t\t\ti.Name = fastly.ToPointer(\"\")\n\t\t\t\t\t}\n\t\t\t\t\treturn &fastly.VCL{\n\t\t\t\t\t\tContent:        i.Content,\n\t\t\t\t\t\tMain:           i.Main,\n\t\t\t\t\t\tName:           i.Name,\n\t\t\t\t\t\tServiceID:      fastly.ToPointer(i.ServiceID),\n\t\t\t\t\t\tServiceVersion: fastly.ToPointer(i.ServiceVersion),\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:            \"--content inline_vcl --name foo --service-id 123 --version 3\",\n\t\t\tWantOutput:      \"Created custom VCL 'foo' (service: 123, version: 3, main: false)\",\n\t\t\tPathContentFlag: &testutil.PathContentFlag{Flag: \"content\", Fixture: \"example.vcl\", Content: func() string { return content }},\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{top.CommandName, root.CommandName, sub.CommandName, \"create\"}, scenarios)\n}\n\nfunc TestVCLCustomDelete(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --name flag\",\n\t\t\tArgs:      \"--version 3\",\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --version flag\",\n\t\t\tArgs:      \"--name foobar\",\n\t\t\tWantError: \"error parsing arguments: required flag --version not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --service-id flag\",\n\t\t\tArgs:      \"--name foobar --version 3\",\n\t\t\tEnvVars:   map[string]string{\"FASTLY_SERVICE_ID\": \"\"},\n\t\t\tWantError: \"error reading service: no service ID found\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate DeleteVCL API error\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tDeleteVCLFn: func(_ context.Context, _ *fastly.DeleteVCLInput) error {\n\t\t\t\t\treturn testutil.Err\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:      \"--name foobar --service-id 123 --version 3\",\n\t\t\tWantError: testutil.Err.Error(),\n\t\t},\n\t\t{\n\t\t\tName: \"validate DeleteVCL API success\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tDeleteVCLFn: func(_ context.Context, _ *fastly.DeleteVCLInput) error {\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:       \"--name foobar --service-id 123 --version 3\",\n\t\t\tWantOutput: \"Deleted custom VCL 'foobar' (service: 123, version: 3)\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API error when modifying active version\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tDeleteVCLFn: func(_ context.Context, i *fastly.DeleteVCLInput) error {\n\t\t\t\t\treturn fmt.Errorf(\"Cannot update version %d. Versions that have been activated cannot be updated\", i.ServiceVersion)\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:      \"--name foobar --service-id 123 --version 3\",\n\t\t\tWantError: \"Cannot update version 3. Versions that have been activated cannot be updated\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API error when modifying locked version\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tDeleteVCLFn: func(_ context.Context, i *fastly.DeleteVCLInput) error {\n\t\t\t\t\treturn fmt.Errorf(\"Cannot update version %d. Versions that have been locked cannot be updated\", i.ServiceVersion)\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:      \"--name foobar --service-id 123 --version 3\",\n\t\t\tWantError: \"Cannot update version 3. Versions that have been locked cannot be updated\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate --autoclone results in cloned service version\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tDeleteVCLFn: func(_ context.Context, _ *fastly.DeleteVCLInput) error {\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:       \"--autoclone --name foo --service-id 123 --version 1\",\n\t\t\tWantOutput: \"Deleted custom VCL 'foo' (service: 123, version: 4)\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate --autoclone on locked version\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tDeleteVCLFn: func(_ context.Context, i *fastly.DeleteVCLInput) error {\n\t\t\t\t\t// Verify operation happens on the cloned version (4), not original (2)\n\t\t\t\t\tif i.ServiceVersion != 4 {\n\t\t\t\t\t\treturn fmt.Errorf(\"expected operation on cloned version 4, got %d\", i.ServiceVersion)\n\t\t\t\t\t}\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:       \"--autoclone --name foo --service-id 123 --version 2\",\n\t\t\tWantOutput: \"Deleted custom VCL 'foo' (service: 123, version: 4)\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate --autoclone on editable version\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tDeleteVCLFn: func(_ context.Context, i *fastly.DeleteVCLInput) error {\n\t\t\t\t\t// Verify operation happens on the cloned version (4), not original (3)\n\t\t\t\t\tif i.ServiceVersion != 4 {\n\t\t\t\t\t\treturn fmt.Errorf(\"expected operation on cloned version 4, got %d\", i.ServiceVersion)\n\t\t\t\t\t}\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:       \"--autoclone --name foo --service-id 123 --version 3\",\n\t\t\tWantOutput: \"Deleted custom VCL 'foo' (service: 123, version: 4)\",\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{top.CommandName, root.CommandName, sub.CommandName, \"delete\"}, scenarios)\n}\n\nfunc TestVCLCustomDescribe(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --name flag\",\n\t\t\tArgs:      \"--version 3\",\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --version flag\",\n\t\t\tArgs:      \"--name foobar\",\n\t\t\tWantError: \"error parsing arguments: required flag --version not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --service-id flag\",\n\t\t\tArgs:      \"--name foobar --version 3\",\n\t\t\tWantError: \"error reading service: no service ID found\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate GetVCL API error\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tGetVCLFn: func(_ context.Context, _ *fastly.GetVCLInput) (*fastly.VCL, error) {\n\t\t\t\t\treturn nil, testutil.Err\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:      \"--name foobar --service-id 123 --version 3\",\n\t\t\tWantError: testutil.Err.Error(),\n\t\t},\n\t\t{\n\t\t\tName: \"validate GetVCL API success\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tGetVCLFn:     getVCL,\n\t\t\t},\n\t\t\tArgs:       \"--name foobar --service-id 123 --version 3\",\n\t\t\tWantOutput: \"\\nService ID: 123\\nService Version: 3\\n\\nName: foobar\\nMain: true\\nContent: \\n# some vcl content\\n\\nCreated at: 2021-06-15 23:00:00 +0000 UTC\\nUpdated at: 2021-06-15 23:00:00 +0000 UTC\\nDeleted at: 2021-06-15 23:00:00 +0000 UTC\\n\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate missing --autoclone flag is OK\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tGetVCLFn:     getVCL,\n\t\t\t},\n\t\t\tArgs:       \"--name foobar --service-id 123 --version 1\",\n\t\t\tWantOutput: \"\\nService ID: 123\\nService Version: 1\\n\\nName: foobar\\nMain: true\\nContent: \\n# some vcl content\\n\\nCreated at: 2021-06-15 23:00:00 +0000 UTC\\nUpdated at: 2021-06-15 23:00:00 +0000 UTC\\nDeleted at: 2021-06-15 23:00:00 +0000 UTC\\n\",\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{top.CommandName, root.CommandName, sub.CommandName, \"describe\"}, scenarios)\n}\n\nfunc TestVCLCustomList(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --version flag\",\n\t\t\tWantError: \"error parsing arguments: required flag --version not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --service-id flag\",\n\t\t\tArgs:      \"--version 3\",\n\t\t\tEnvVars:   map[string]string{\"FASTLY_SERVICE_ID\": \"\"},\n\t\t\tWantError: \"error reading service: no service ID found\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate ListVCLs API error\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tListVCLsFn: func(_ context.Context, _ *fastly.ListVCLsInput) ([]*fastly.VCL, error) {\n\t\t\t\t\treturn nil, testutil.Err\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:      \"--service-id 123 --version 3\",\n\t\t\tWantError: testutil.Err.Error(),\n\t\t},\n\t\t{\n\t\t\tName: \"validate ListVCLs API success\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tListVCLsFn:   listVCLs,\n\t\t\t},\n\t\t\tArgs:       \"--service-id 123 --version 3\",\n\t\t\tWantOutput: \"SERVICE ID  VERSION  NAME  MAIN\\n123         3        foo   true\\n123         3        bar   false\\n\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate missing --autoclone flag is OK\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tListVCLsFn:   listVCLs,\n\t\t\t},\n\t\t\tArgs:       \"--service-id 123 --version 1\",\n\t\t\tWantOutput: \"SERVICE ID  VERSION  NAME  MAIN\\n123         1        foo   true\\n123         1        bar   false\\n\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate missing --verbose flag\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tListVCLsFn:   listVCLs,\n\t\t\t},\n\t\t\tArgs:       \"--service-id 123 --verbose --version 1\",\n\t\t\tWantOutput: \"Fastly API endpoint: https://api.fastly.com\\nFastly API token provided via config file (auth: user)\\n\\nService ID (via --service-id): 123\\n\\nService Version: 1\\n\\nName: foo\\nMain: true\\nContent: \\n# some vcl content\\n\\nCreated at: 2021-06-15 23:00:00 +0000 UTC\\nUpdated at: 2021-06-15 23:00:00 +0000 UTC\\nDeleted at: 2021-06-15 23:00:00 +0000 UTC\\n\\nName: bar\\nMain: false\\nContent: \\n# some vcl content\\n\\nCreated at: 2021-06-15 23:00:00 +0000 UTC\\nUpdated at: 2021-06-15 23:00:00 +0000 UTC\\nDeleted at: 2021-06-15 23:00:00 +0000 UTC\\n\",\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{top.CommandName, root.CommandName, sub.CommandName, \"list\"}, scenarios)\n}\n\nfunc TestVCLCustomUpdate(t *testing.T) {\n\tvar content string\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --name flag\",\n\t\t\tArgs:      \"--version 3\",\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --version flag\",\n\t\t\tArgs:      \"--name foobar\",\n\t\t\tWantError: \"error parsing arguments: required flag --version not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --service-id flag\",\n\t\t\tArgs:      \"--name foobar --version 3\",\n\t\t\tEnvVars:   map[string]string{\"FASTLY_SERVICE_ID\": \"\"},\n\t\t\tWantError: \"error reading service: no service ID found\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate UpdateVCL API error\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tUpdateVCLFn: func(_ context.Context, _ *fastly.UpdateVCLInput) (*fastly.VCL, error) {\n\t\t\t\t\treturn nil, testutil.Err\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:      \"--name foobar --new-name beepboop --service-id 123 --version 3\",\n\t\t\tWantError: testutil.Err.Error(),\n\t\t},\n\t\t{\n\t\t\tName: \"validate UpdateVCL API success with --new-name\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tUpdateVCLFn: func(_ context.Context, i *fastly.UpdateVCLInput) (*fastly.VCL, error) {\n\t\t\t\t\treturn &fastly.VCL{\n\t\t\t\t\t\tContent:        fastly.ToPointer(\"# untouched\"),\n\t\t\t\t\t\tMain:           fastly.ToPointer(true),\n\t\t\t\t\t\tName:           i.NewName,\n\t\t\t\t\t\tServiceID:      fastly.ToPointer(i.ServiceID),\n\t\t\t\t\t\tServiceVersion: fastly.ToPointer(i.ServiceVersion),\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:       \"--name foobar --new-name beepboop --service-id 123 --version 3\",\n\t\t\tWantOutput: \"Updated custom VCL 'beepboop' (previously: 'foobar', service: 123, version: 3)\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate UpdateVCL API success with --content\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tUpdateVCLFn: func(_ context.Context, i *fastly.UpdateVCLInput) (*fastly.VCL, error) {\n\t\t\t\t\t// Track the contents parsed\n\t\t\t\t\tcontent = *i.Content\n\n\t\t\t\t\treturn &fastly.VCL{\n\t\t\t\t\t\tContent:        i.Content,\n\t\t\t\t\t\tMain:           fastly.ToPointer(true),\n\t\t\t\t\t\tName:           fastly.ToPointer(i.Name),\n\t\t\t\t\t\tServiceID:      fastly.ToPointer(i.ServiceID),\n\t\t\t\t\t\tServiceVersion: fastly.ToPointer(i.ServiceVersion),\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:            \"--content updated --name foobar --service-id 123 --version 3\",\n\t\t\tWantOutput:      \"Updated custom VCL 'foobar' (service: 123, version: 3)\",\n\t\t\tPathContentFlag: &testutil.PathContentFlag{Flag: \"content\", Fixture: \"example.vcl\", Content: func() string { return content }},\n\t\t},\n\t\t{\n\t\t\tName: \"validate API error when modifying active version\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tUpdateVCLFn: func(_ context.Context, i *fastly.UpdateVCLInput) (*fastly.VCL, error) {\n\t\t\t\t\treturn nil, fmt.Errorf(\"Cannot update version %d. Versions that have been activated cannot be updated\", i.ServiceVersion)\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:      \"--content updated --name foobar --service-id 123 --version 3\",\n\t\t\tWantError: \"Cannot update version 3. Versions that have been activated cannot be updated\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API error when modifying locked version\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tUpdateVCLFn: func(_ context.Context, i *fastly.UpdateVCLInput) (*fastly.VCL, error) {\n\t\t\t\t\treturn nil, fmt.Errorf(\"Cannot update version %d. Versions that have been locked cannot be updated\", i.ServiceVersion)\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:      \"--content updated --name foobar --service-id 123 --version 3\",\n\t\t\tWantError: \"Cannot update version 3. Versions that have been locked cannot be updated\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate --autoclone results in cloned service version\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tUpdateVCLFn: func(_ context.Context, i *fastly.UpdateVCLInput) (*fastly.VCL, error) {\n\t\t\t\t\t// Track the contents parsed\n\t\t\t\t\tcontent = *i.Content\n\n\t\t\t\t\treturn &fastly.VCL{\n\t\t\t\t\t\tContent:        i.Content,\n\t\t\t\t\t\tMain:           fastly.ToPointer(true),\n\t\t\t\t\t\tName:           fastly.ToPointer(i.Name),\n\t\t\t\t\t\tServiceID:      fastly.ToPointer(i.ServiceID),\n\t\t\t\t\t\tServiceVersion: fastly.ToPointer(i.ServiceVersion),\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:            \"--autoclone --content ./testdata/example.vcl --name foo --service-id 123 --version 1\",\n\t\t\tWantOutput:      \"Updated custom VCL 'foo' (service: 123, version: 4)\",\n\t\t\tPathContentFlag: &testutil.PathContentFlag{Flag: \"content\", Fixture: \"example.vcl\", Content: func() string { return content }},\n\t\t},\n\t\t{\n\t\t\tName: \"validate --autoclone on locked version\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tUpdateVCLFn: func(_ context.Context, i *fastly.UpdateVCLInput) (*fastly.VCL, error) {\n\t\t\t\t\t// Verify operation happens on the cloned version (4), not original (2)\n\t\t\t\t\tif i.ServiceVersion != 4 {\n\t\t\t\t\t\treturn nil, fmt.Errorf(\"expected operation on cloned version 4, got %d\", i.ServiceVersion)\n\t\t\t\t\t}\n\t\t\t\t\t// Track the contents parsed\n\t\t\t\t\tcontent = *i.Content\n\n\t\t\t\t\treturn &fastly.VCL{\n\t\t\t\t\t\tContent:        i.Content,\n\t\t\t\t\t\tMain:           fastly.ToPointer(true),\n\t\t\t\t\t\tName:           fastly.ToPointer(i.Name),\n\t\t\t\t\t\tServiceID:      fastly.ToPointer(i.ServiceID),\n\t\t\t\t\t\tServiceVersion: fastly.ToPointer(i.ServiceVersion),\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:            \"--autoclone --content ./testdata/example.vcl --name foo --service-id 123 --version 2\",\n\t\t\tWantOutput:      \"Updated custom VCL 'foo' (service: 123, version: 4)\",\n\t\t\tPathContentFlag: &testutil.PathContentFlag{Flag: \"content\", Fixture: \"example.vcl\", Content: func() string { return content }},\n\t\t},\n\t\t{\n\t\t\tName: \"validate --autoclone on editable version\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tUpdateVCLFn: func(_ context.Context, i *fastly.UpdateVCLInput) (*fastly.VCL, error) {\n\t\t\t\t\t// Verify operation happens on the cloned version (4), not original (3)\n\t\t\t\t\tif i.ServiceVersion != 4 {\n\t\t\t\t\t\treturn nil, fmt.Errorf(\"expected operation on cloned version 4, got %d\", i.ServiceVersion)\n\t\t\t\t\t}\n\t\t\t\t\t// Track the contents parsed\n\t\t\t\t\tcontent = *i.Content\n\n\t\t\t\t\treturn &fastly.VCL{\n\t\t\t\t\t\tContent:        i.Content,\n\t\t\t\t\t\tMain:           fastly.ToPointer(true),\n\t\t\t\t\t\tName:           fastly.ToPointer(i.Name),\n\t\t\t\t\t\tServiceID:      fastly.ToPointer(i.ServiceID),\n\t\t\t\t\t\tServiceVersion: fastly.ToPointer(i.ServiceVersion),\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:            \"--autoclone --content ./testdata/example.vcl --name foo --service-id 123 --version 3\",\n\t\t\tWantOutput:      \"Updated custom VCL 'foo' (service: 123, version: 4)\",\n\t\t\tPathContentFlag: &testutil.PathContentFlag{Flag: \"content\", Fixture: \"example.vcl\", Content: func() string { return content }},\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{top.CommandName, root.CommandName, sub.CommandName, \"update\"}, scenarios)\n}\n\nfunc getVCL(_ context.Context, i *fastly.GetVCLInput) (*fastly.VCL, error) {\n\tt := testutil.Date\n\n\treturn &fastly.VCL{\n\t\tContent:        fastly.ToPointer(\"# some vcl content\"),\n\t\tMain:           fastly.ToPointer(true),\n\t\tName:           fastly.ToPointer(i.Name),\n\t\tServiceID:      fastly.ToPointer(i.ServiceID),\n\t\tServiceVersion: fastly.ToPointer(i.ServiceVersion),\n\n\t\tCreatedAt: &t,\n\t\tDeletedAt: &t,\n\t\tUpdatedAt: &t,\n\t}, nil\n}\n\nfunc listVCLs(_ context.Context, i *fastly.ListVCLsInput) ([]*fastly.VCL, error) {\n\tt := testutil.Date\n\tvs := []*fastly.VCL{\n\t\t{\n\t\t\tContent:        fastly.ToPointer(\"# some vcl content\"),\n\t\t\tMain:           fastly.ToPointer(true),\n\t\t\tName:           fastly.ToPointer(\"foo\"),\n\t\t\tServiceID:      fastly.ToPointer(i.ServiceID),\n\t\t\tServiceVersion: fastly.ToPointer(i.ServiceVersion),\n\n\t\t\tCreatedAt: &t,\n\t\t\tDeletedAt: &t,\n\t\t\tUpdatedAt: &t,\n\t\t},\n\t\t{\n\t\t\tContent:        fastly.ToPointer(\"# some vcl content\"),\n\t\t\tMain:           fastly.ToPointer(false),\n\t\t\tName:           fastly.ToPointer(\"bar\"),\n\t\t\tServiceID:      fastly.ToPointer(i.ServiceID),\n\t\t\tServiceVersion: fastly.ToPointer(i.ServiceVersion),\n\n\t\t\tCreatedAt: &t,\n\t\t\tDeletedAt: &t,\n\t\t\tUpdatedAt: &t,\n\t\t},\n\t}\n\treturn vs, nil\n}\n"
  },
  {
    "path": "pkg/commands/service/vcl/custom/delete.go",
    "content": "package custom\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"delete\", \"Delete the uploaded VCL for a particular service and version\").Alias(\"remove\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"The name of the VCL to delete\").Required().StringVar(&c.name)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.autoClone.Set,\n\t\tDst:    &c.autoClone.Value,\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\n\treturn &c\n}\n\n// DeleteCommand calls the Fastly API to delete an appropriate resource.\ntype DeleteCommand struct {\n\targparser.Base\n\n\tautoClone      argparser.OptionalAutoClone\n\tname           string\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DeleteCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(false),\n\t\tLocked:             optional.Of(false),\n\t\tAutoCloneFlag:      c.autoClone,\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": errors.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tinput := c.constructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\n\terr = c.Globals.APIClient.DeleteVCL(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fastly.ToValue(serviceVersion.Number),\n\t\t})\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Deleted custom VCL '%s' (service: %s, version: %d)\", c.name, serviceID, fastly.ToValue(serviceVersion.Number))\n\treturn nil\n}\n\n// constructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *DeleteCommand) constructInput(serviceID string, serviceVersion int) *fastly.DeleteVCLInput {\n\tvar input fastly.DeleteVCLInput\n\n\tinput.Name = c.name\n\tinput.ServiceID = serviceID\n\tinput.ServiceVersion = serviceVersion\n\n\treturn &input\n}\n"
  },
  {
    "path": "pkg/commands/service/vcl/custom/describe.go",
    "content": "package custom\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// NewDescribeCommand returns a usable command registered under the parent.\nfunc NewDescribeCommand(parent argparser.Registerer, g *global.Data) *DescribeCommand {\n\tc := DescribeCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"describe\", \"Get the uploaded VCL for a particular service and version\").Alias(\"get\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"The name of the VCL\").Required().StringVar(&c.name)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\n\treturn &c\n}\n\n// DescribeCommand calls the Fastly API to describe an appropriate resource.\ntype DescribeCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tname           string\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DescribeCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fsterr.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tinput := c.constructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\n\to, err := c.Globals.APIClient.GetVCL(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fastly.ToValue(serviceVersion.Number),\n\t\t})\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\treturn c.print(out, o)\n}\n\n// constructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *DescribeCommand) constructInput(serviceID string, serviceVersion int) *fastly.GetVCLInput {\n\tvar input fastly.GetVCLInput\n\n\tinput.Name = c.name\n\tinput.ServiceID = serviceID\n\tinput.ServiceVersion = serviceVersion\n\n\treturn &input\n}\n\n// print displays the information returned from the API.\nfunc (c *DescribeCommand) print(out io.Writer, v *fastly.VCL) error {\n\tif !c.Globals.Verbose() {\n\t\tfmt.Fprintf(out, \"\\nService ID: %s\\n\", fastly.ToValue(v.ServiceID))\n\t}\n\tfmt.Fprintf(out, \"Service Version: %d\\n\\n\", fastly.ToValue(v.ServiceVersion))\n\tfmt.Fprintf(out, \"Name: %s\\n\", fastly.ToValue(v.Name))\n\tfmt.Fprintf(out, \"Main: %t\\n\", fastly.ToValue(v.Main))\n\tfmt.Fprintf(out, \"Content: \\n%s\\n\\n\", text.SanitizeTerminalOutput(fastly.ToValue(v.Content)))\n\tif v.CreatedAt != nil {\n\t\tfmt.Fprintf(out, \"Created at: %s\\n\", v.CreatedAt)\n\t}\n\tif v.UpdatedAt != nil {\n\t\tfmt.Fprintf(out, \"Updated at: %s\\n\", v.UpdatedAt)\n\t}\n\tif v.DeletedAt != nil {\n\t\tfmt.Fprintf(out, \"Deleted at: %s\\n\", v.DeletedAt)\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/vcl/custom/doc.go",
    "content": "// Package custom contains commands for managing custom VCL.\npackage custom\n"
  },
  {
    "path": "pkg/commands/service/vcl/custom/list.go",
    "content": "package custom\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"list\", \"List the uploaded VCLs for a particular service and version\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\n\treturn &c\n}\n\n// ListCommand calls the Fastly API to list appropriate resources.\ntype ListCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *ListCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fsterr.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tinput := c.constructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\n\to, err := c.Globals.APIClient.ListVCLs(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fastly.ToValue(serviceVersion.Number),\n\t\t})\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\tif c.Globals.Verbose() {\n\t\tc.printVerbose(out, fastly.ToValue(serviceVersion.Number), o)\n\t} else {\n\t\terr = c.printSummary(out, o)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\n// constructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *ListCommand) constructInput(serviceID string, serviceVersion int) *fastly.ListVCLsInput {\n\tvar input fastly.ListVCLsInput\n\n\tinput.ServiceID = serviceID\n\tinput.ServiceVersion = serviceVersion\n\n\treturn &input\n}\n\n// printVerbose displays the information returned from the API in a verbose\n// format.\nfunc (c *ListCommand) printVerbose(out io.Writer, serviceVersion int, vs []*fastly.VCL) {\n\tfmt.Fprintf(out, \"Service Version: %d\\n\", serviceVersion)\n\n\tfor _, v := range vs {\n\t\tfmt.Fprintf(out, \"\\nName: %s\\n\", fastly.ToValue(v.Name))\n\t\tfmt.Fprintf(out, \"Main: %t\\n\", fastly.ToValue(v.Main))\n\t\tfmt.Fprintf(out, \"Content: \\n%s\\n\\n\", fastly.ToValue(v.Content))\n\t\tif v.CreatedAt != nil {\n\t\t\tfmt.Fprintf(out, \"Created at: %s\\n\", v.CreatedAt)\n\t\t}\n\t\tif v.UpdatedAt != nil {\n\t\t\tfmt.Fprintf(out, \"Updated at: %s\\n\", v.UpdatedAt)\n\t\t}\n\t\tif v.DeletedAt != nil {\n\t\t\tfmt.Fprintf(out, \"Deleted at: %s\\n\", v.DeletedAt)\n\t\t}\n\t}\n}\n\n// printSummary displays the information returned from the API in a summarised\n// format.\nfunc (c *ListCommand) printSummary(out io.Writer, vs []*fastly.VCL) error {\n\tt := text.NewTable(out)\n\tt.AddHeader(\"SERVICE ID\", \"VERSION\", \"NAME\", \"MAIN\")\n\tfor _, v := range vs {\n\t\tt.AddLine(\n\t\t\tfastly.ToValue(v.ServiceID),\n\t\t\tfastly.ToValue(v.ServiceVersion),\n\t\t\tfastly.ToValue(v.Name),\n\t\t\tfastly.ToValue(v.Main),\n\t\t)\n\t}\n\tt.Print()\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/vcl/custom/root.go",
    "content": "package custom\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"custom\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manipulate Fastly service version custom VCL\")\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/service/vcl/custom/testdata/example.vcl",
    "content": "# some vcl content\n"
  },
  {
    "path": "pkg/commands/service/vcl/custom/update.go",
    "content": "package custom\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"update\", \"Update the uploaded VCL for a particular service and version\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"The name of the VCL to update\").Required().StringVar(&c.name)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.autoClone.Set,\n\t\tDst:    &c.autoClone.Value,\n\t})\n\tc.CmdClause.Flag(\"new-name\", \"New name for the VCL\").Action(c.newName.Set).StringVar(&c.newName.Value)\n\tc.CmdClause.Flag(\"content\", \"VCL passed as file path or content, e.g. $(< main.vcl)\").Action(c.content.Set).StringVar(&c.content.Value)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\n\treturn &c\n}\n\n// UpdateCommand calls the Fastly API to update an appropriate resource.\ntype UpdateCommand struct {\n\targparser.Base\n\n\tautoClone      argparser.OptionalAutoClone\n\tcontent        argparser.OptionalString\n\tname           string\n\tnewName        argparser.OptionalString\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *UpdateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(false),\n\t\tLocked:             optional.Of(false),\n\t\tAutoCloneFlag:      c.autoClone,\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fsterr.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tinput, err := c.constructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fastly.ToValue(serviceVersion.Number),\n\t\t})\n\t\treturn err\n\t}\n\n\tv, err := c.Globals.APIClient.UpdateVCL(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fastly.ToValue(serviceVersion.Number),\n\t\t})\n\t\treturn err\n\t}\n\n\tif input.NewName != nil && *input.NewName != \"\" {\n\t\ttext.Success(out,\n\t\t\t\"Updated custom VCL '%s' (previously: '%s', service: %s, version: %d)\",\n\t\t\tfastly.ToValue(v.Name),\n\t\t\tinput.Name,\n\t\t\tfastly.ToValue(v.ServiceID),\n\t\t\tfastly.ToValue(v.ServiceVersion),\n\t\t)\n\t} else {\n\t\ttext.Success(out,\n\t\t\t\"Updated custom VCL '%s' (service: %s, version: %d)\",\n\t\t\tfastly.ToValue(v.Name),\n\t\t\tfastly.ToValue(v.ServiceID),\n\t\t\tfastly.ToValue(v.ServiceVersion),\n\t\t)\n\t}\n\treturn nil\n}\n\n// constructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *UpdateCommand) constructInput(serviceID string, serviceVersion int) (*fastly.UpdateVCLInput, error) {\n\tvar input fastly.UpdateVCLInput\n\n\tinput.Name = c.name\n\tinput.ServiceID = serviceID\n\tinput.ServiceVersion = serviceVersion\n\n\tif !c.newName.WasSet && !c.content.WasSet {\n\t\treturn nil, fmt.Errorf(\"error parsing arguments: must provide either --new-name or --content to update the VCL\")\n\t}\n\tif c.newName.WasSet {\n\t\tinput.NewName = &c.newName.Value\n\t}\n\tif c.content.WasSet {\n\t\tinput.Content = fastly.ToPointer(argparser.Content(c.content.Value))\n\t}\n\n\treturn &input, nil\n}\n"
  },
  {
    "path": "pkg/commands/service/vcl/describe.go",
    "content": "package vcl\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// NewDescribeCommand returns a usable command registered under the parent.\nfunc NewDescribeCommand(parent argparser.Registerer, g *global.Data) *DescribeCommand {\n\tc := DescribeCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"describe\", \"Get the generated VCL for a particular service and version\").Alias(\"get\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\n\treturn &c\n}\n\n// DescribeCommand calls the Fastly API to list appropriate resources.\ntype DescribeCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DescribeCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fsterr.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tinput := c.constructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\n\to, err := c.Globals.APIClient.GetGeneratedVCL(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fastly.ToValue(serviceVersion.Number),\n\t\t})\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\tif c.Globals.Verbose() {\n\t\tc.printVerbose(out, fastly.ToValue(serviceVersion.Number), o)\n\t} else {\n\t\terr = c.print(out, o)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\n// constructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *DescribeCommand) constructInput(serviceID string, serviceVersion int) *fastly.GetGeneratedVCLInput {\n\tvar input fastly.GetGeneratedVCLInput\n\n\tinput.ServiceID = serviceID\n\tinput.ServiceVersion = serviceVersion\n\n\treturn &input\n}\n\n// printVerbose displays the information returned from the API in a verbose\n// format.\nfunc (c *DescribeCommand) printVerbose(out io.Writer, serviceVersion int, v *fastly.VCL) {\n\tfmt.Fprintf(out, \"Service Version: %d\\n\", serviceVersion)\n\n\tfmt.Fprintf(out, \"\\n\")\n\tfmt.Fprintf(out, \"Name: %s\\n\", fastly.ToValue(v.Name))\n\tfmt.Fprintf(out, \"Main: %t\\n\", fastly.ToValue(v.Main))\n\n\tif v.CreatedAt != nil {\n\t\tfmt.Fprintf(out, \"Created at: %s\\n\", v.CreatedAt)\n\t}\n\tif v.UpdatedAt != nil {\n\t\tfmt.Fprintf(out, \"Updated at: %s\\n\", v.UpdatedAt)\n\t}\n\tif v.DeletedAt != nil {\n\t\tfmt.Fprintf(out, \"Deleted at: %s\\n\", v.DeletedAt)\n\t}\n\n\tfmt.Fprintf(out, \"Content: \\n%s\\n\", text.SanitizeTerminalOutput(fastly.ToValue(v.Content)))\n}\n\n// print the generated VCL.\nfunc (c *DescribeCommand) print(out io.Writer, v *fastly.VCL) error {\n\tfmt.Fprintf(out, \"%s\\n\", text.SanitizeTerminalOutput(fastly.ToValue(v.Content)))\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/vcl/doc.go",
    "content": "// Package vcl contains commands for managing VCL.\npackage vcl\n"
  },
  {
    "path": "pkg/commands/service/vcl/root.go",
    "content": "package vcl\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"vcl\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manipulate Fastly service version VCL\")\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/service/vcl/snippet/create.go",
    "content": "package snippet\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// Locations is a list of VCL subroutines.\nvar Locations = []string{\"init\", \"recv\", \"hash\", \"hit\", \"miss\", \"pass\", \"fetch\", \"error\", \"deliver\", \"log\", \"none\"}\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tc := CreateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"create\", \"Create a snippet for a particular service and version\").Alias(\"add\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.autoClone.Set,\n\t\tDst:    &c.autoClone.Value,\n\t})\n\tc.CmdClause.Flag(\"content\", \"VCL snippet passed as file path or content, e.g. $(< snippet.vcl)\").Action(c.content.Set).StringVar(&c.content.Value)\n\tc.CmdClause.Flag(\"dynamic\", \"Whether the VCL snippet is dynamic or versioned\").Action(c.dynamic.Set).BoolVar(&c.dynamic.Value)\n\tc.CmdClause.Flag(\"name\", \"The name of the VCL snippet\").Action(c.name.Set).StringVar(&c.name.Value)\n\tc.CmdClause.Flag(\"priority\", \"Priority determines execution order. Lower numbers execute first\").Short('p').Action(c.priority.Set).StringVar(&c.priority.Value)\n\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\tc.CmdClause.Flag(\"type\", \"The location in generated VCL where the snippet should be placed\").Action(c.location.Set).HintOptions(Locations...).EnumVar(&c.location.Value, Locations...)\n\n\treturn &c\n}\n\n// CreateCommand calls the Fastly API to create an appropriate resource.\ntype CreateCommand struct {\n\targparser.Base\n\n\tautoClone      argparser.OptionalAutoClone\n\tcontent        argparser.OptionalString\n\tdynamic        argparser.OptionalBool\n\tlocation       argparser.OptionalString\n\tname           argparser.OptionalString\n\tpriority       argparser.OptionalString\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *CreateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(false),\n\t\tLocked:             optional.Of(false),\n\t\tAutoCloneFlag:      c.autoClone,\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": errors.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tinput := c.constructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\n\tv, err := c.Globals.APIClient.CreateSnippet(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fastly.ToValue(serviceVersion.Number),\n\t\t})\n\t\treturn err\n\t}\n\n\ttext.Success(out,\n\t\t\"Created VCL snippet '%s' (service: %s, version: %d, dynamic: %t, snippet id: %s, type: %s, priority: %s)\",\n\t\tfastly.ToValue(v.Name),\n\t\tfastly.ToValue(v.ServiceID),\n\t\tfastly.ToValue(v.ServiceVersion),\n\t\tc.dynamic.WasSet,\n\t\tfastly.ToValue(v.SnippetID),\n\t\tc.location.Value,\n\t\tfastly.ToValue(v.Priority),\n\t)\n\treturn nil\n}\n\n// constructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *CreateCommand) constructInput(serviceID string, serviceVersion int) *fastly.CreateSnippetInput {\n\tinput := fastly.CreateSnippetInput{\n\t\tDynamic:        fastly.ToPointer(0),\n\t\tServiceID:      serviceID,\n\t\tServiceVersion: serviceVersion,\n\t}\n\tif c.name.WasSet {\n\t\tinput.Name = &c.name.Value\n\t}\n\tif c.content.WasSet {\n\t\tinput.Content = fastly.ToPointer(argparser.Content(c.content.Value))\n\t}\n\tif c.location.WasSet {\n\t\tsType := fastly.SnippetType(c.location.Value)\n\t\tinput.Type = &sType\n\t}\n\tif c.dynamic.WasSet {\n\t\tinput.Dynamic = fastly.ToPointer(1)\n\t}\n\tif c.priority.WasSet {\n\t\tinput.Priority = &c.priority.Value\n\t}\n\n\treturn &input\n}\n"
  },
  {
    "path": "pkg/commands/service/vcl/snippet/delete.go",
    "content": "package snippet\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tc := DeleteCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"delete\", \"Delete a specific snippet for a particular service and version\").Alias(\"remove\")\n\n\t// Required.\n\tc.CmdClause.Flag(\"name\", \"The name of the VCL snippet to delete\").Required().StringVar(&c.name)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.autoClone.Set,\n\t\tDst:    &c.autoClone.Value,\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\n\treturn &c\n}\n\n// DeleteCommand calls the Fastly API to delete an appropriate resource.\ntype DeleteCommand struct {\n\targparser.Base\n\n\tautoClone      argparser.OptionalAutoClone\n\tname           string\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DeleteCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(false),\n\t\tLocked:             optional.Of(false),\n\t\tAutoCloneFlag:      c.autoClone,\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": errors.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tinput := c.constructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\n\terr = c.Globals.APIClient.DeleteSnippet(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fastly.ToValue(serviceVersion.Number),\n\t\t})\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Deleted VCL snippet '%s' (service: %s, version: %d)\", c.name, serviceID, fastly.ToValue(serviceVersion.Number))\n\treturn nil\n}\n\n// constructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *DeleteCommand) constructInput(serviceID string, serviceVersion int) *fastly.DeleteSnippetInput {\n\tvar input fastly.DeleteSnippetInput\n\n\tinput.Name = c.name\n\tinput.ServiceID = serviceID\n\tinput.ServiceVersion = serviceVersion\n\n\treturn &input\n}\n"
  },
  {
    "path": "pkg/commands/service/vcl/snippet/describe.go",
    "content": "package snippet\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// NewDescribeCommand returns a usable command registered under the parent.\nfunc NewDescribeCommand(parent argparser.Registerer, g *global.Data) *DescribeCommand {\n\tc := DescribeCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"describe\", \"Get the uploaded VCL snippet for a particular service and version\").Alias(\"get\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.CmdClause.Flag(\"content\", \"Outputs the raw content of the identified snippet\").Action(c.content.Set).BoolVar(&c.content.Value)\n\tc.CmdClause.Flag(\"dynamic\", \"Whether the VCL snippet is dynamic or versioned\").Action(c.dynamic.Set).BoolVar(&c.dynamic.Value)\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\tc.CmdClause.Flag(\"name\", \"The name of the VCL snippet\").StringVar(&c.name)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\tc.CmdClause.Flag(\"snippet-id\", \"Alphanumeric string identifying a VCL Snippet\").StringVar(&c.snippetID)\n\n\treturn &c\n}\n\n// DescribeCommand calls the Fastly API to describe an appropriate resource.\ntype DescribeCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tdynamic        argparser.OptionalBool\n\tcontent        argparser.OptionalBool\n\tname           string\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n\tsnippetID      string\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DescribeCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\t// Ensure that the --content flag is not used\n\t// with --verbose or --json\n\tif c.Globals.Verbose() && c.content.WasSet {\n\t\treturn fsterr.ErrInvalidContentOutputCombo\n\t}\n\tif c.JSONOutput.Enabled && c.content.WasSet {\n\t\treturn fsterr.ErrInvalidContentOutputCombo\n\t}\n\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fsterr.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tserviceVersionNumber := fastly.ToValue(serviceVersion.Number)\n\n\tif c.dynamic.WasSet {\n\t\tinput, err := c.constructDynamicInput(serviceID, serviceVersionNumber)\n\t\tif err != nil {\n\t\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\t\"Service ID\":      serviceID,\n\t\t\t\t\"Service Version\": serviceVersionNumber,\n\t\t\t})\n\t\t\treturn err\n\t\t}\n\n\t\to, err := c.Globals.APIClient.GetDynamicSnippet(context.TODO(), input)\n\t\tif err != nil {\n\t\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\t\"Service ID\":      serviceID,\n\t\t\t\t\"Service Version\": serviceVersionNumber,\n\t\t\t})\n\t\t\treturn err\n\t\t}\n\n\t\tif ok, err := c.WriteJSON(out, o); ok {\n\t\t\treturn err\n\t\t}\n\n\t\treturn c.printDynamic(out, o)\n\t}\n\n\tinput, err := c.constructInput(serviceID, serviceVersionNumber)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": serviceVersionNumber,\n\t\t})\n\t\treturn err\n\t}\n\n\to, err := c.Globals.APIClient.GetSnippet(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": serviceVersionNumber,\n\t\t})\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\treturn c.print(out, o)\n}\n\n// constructDynamicInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *DescribeCommand) constructDynamicInput(serviceID string, _ int) (*fastly.GetDynamicSnippetInput, error) {\n\tvar input fastly.GetDynamicSnippetInput\n\n\tinput.SnippetID = c.snippetID\n\tinput.ServiceID = serviceID\n\n\tif c.snippetID == \"\" {\n\t\treturn nil, fmt.Errorf(\"error parsing arguments: must provide --snippet-id with a dynamic VCL snippet\")\n\t}\n\n\treturn &input, nil\n}\n\n// constructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *DescribeCommand) constructInput(serviceID string, serviceVersion int) (*fastly.GetSnippetInput, error) {\n\tvar input fastly.GetSnippetInput\n\n\tinput.Name = c.name\n\tinput.ServiceID = serviceID\n\tinput.ServiceVersion = serviceVersion\n\n\tif c.name == \"\" {\n\t\treturn nil, fmt.Errorf(\"error parsing arguments: must provide --name with a versioned VCL snippet\")\n\t}\n\n\treturn &input, nil\n}\n\n// print displays the 'dynamic' information returned from the API.\nfunc (c *DescribeCommand) printDynamic(out io.Writer, ds *fastly.DynamicSnippet) error {\n\t// If the --content flag is set, output only the raw VCL content.\n\tif c.content.WasSet {\n\t\tfmt.Fprint(out, fastly.ToValue(ds.Content))\n\t\treturn nil\n\t}\n\n\tfmt.Fprintf(out, \"\\nService ID: %s\\n\", fastly.ToValue(ds.ServiceID))\n\tfmt.Fprintf(out, \"ID: %s\\n\", fastly.ToValue(ds.SnippetID))\n\tfmt.Fprintf(out, \"Content: \\n%s\\n\", text.SanitizeTerminalOutput(fastly.ToValue(ds.Content)))\n\tif ds.CreatedAt != nil {\n\t\tfmt.Fprintf(out, \"Created at: %s\\n\", ds.CreatedAt)\n\t}\n\tif ds.UpdatedAt != nil {\n\t\tfmt.Fprintf(out, \"Updated at: %s\\n\", ds.UpdatedAt)\n\t}\n\treturn nil\n}\n\n// print displays the information returned from the API.\nfunc (c *DescribeCommand) print(out io.Writer, s *fastly.Snippet) error {\n\t// If the --content flag is set, output only the raw VCL content.\n\tif c.content.WasSet {\n\t\tfmt.Fprint(out, fastly.ToValue(s.Content))\n\t\treturn nil\n\t}\n\n\tif !c.Globals.Verbose() {\n\t\tfmt.Fprintf(out, \"\\nService ID: %s\\n\", fastly.ToValue(s.ServiceID))\n\t}\n\tfmt.Fprintf(out, \"Service Version: %d\\n\", fastly.ToValue(s.ServiceVersion))\n\tfmt.Fprintf(out, \"\\nName: %s\\n\", fastly.ToValue(s.Name))\n\tfmt.Fprintf(out, \"ID: %s\\n\", fastly.ToValue(s.SnippetID))\n\tfmt.Fprintf(out, \"Priority: %s\\n\", fastly.ToValue(s.Priority))\n\tfmt.Fprintf(out, \"Dynamic: %t\\n\", argparser.IntToBool(fastly.ToValue(s.Dynamic)))\n\tfmt.Fprintf(out, \"Type: %s\\n\", fastly.ToValue(s.Type))\n\tfmt.Fprintf(out, \"Content: \\n%s\\n\", text.SanitizeTerminalOutput(fastly.ToValue(s.Content)))\n\tif s.CreatedAt != nil {\n\t\tfmt.Fprintf(out, \"Created at: %s\\n\", s.CreatedAt)\n\t}\n\tif s.UpdatedAt != nil {\n\t\tfmt.Fprintf(out, \"Updated at: %s\\n\", s.UpdatedAt)\n\t}\n\tif s.DeletedAt != nil {\n\t\tfmt.Fprintf(out, \"Deleted at: %s\\n\", s.DeletedAt)\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/vcl/snippet/doc.go",
    "content": "// Package snippet contains commands for managing versioned and dynamic VCL\n// snippets.\npackage snippet\n"
  },
  {
    "path": "pkg/commands/service/vcl/snippet/list.go",
    "content": "package snippet\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"list\", \"List the uploaded VCL snippets for a particular service and version\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\n\treturn &c\n}\n\n// ListCommand calls the Fastly API to list appropriate resources.\ntype ListCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *ListCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fsterr.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tinput := c.constructInput(serviceID, fastly.ToValue(serviceVersion.Number))\n\n\to, err := c.Globals.APIClient.ListSnippets(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fastly.ToValue(serviceVersion.Number),\n\t\t})\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\tif c.Globals.Verbose() {\n\t\tc.printVerbose(out, fastly.ToValue(serviceVersion.Number), o)\n\t} else {\n\t\terr = c.printSummary(out, o)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\n// constructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *ListCommand) constructInput(serviceID string, serviceVersion int) *fastly.ListSnippetsInput {\n\tvar input fastly.ListSnippetsInput\n\n\tinput.ServiceID = serviceID\n\tinput.ServiceVersion = serviceVersion\n\n\treturn &input\n}\n\n// printVerbose displays the information returned from the API in a verbose\n// format.\nfunc (c *ListCommand) printVerbose(out io.Writer, serviceVersion int, vs []*fastly.Snippet) {\n\tfmt.Fprintf(out, \"Service Version: %d\\n\", serviceVersion)\n\n\tfor _, v := range vs {\n\t\tfmt.Fprintf(out, \"\\n\")\n\t\tfmt.Fprintf(out, \"Name: %s\\n\", fastly.ToValue(v.Name))\n\t\tfmt.Fprintf(out, \"ID: %s\\n\", fastly.ToValue(v.SnippetID))\n\t\tfmt.Fprintf(out, \"Priority: %s\\n\", fastly.ToValue(v.Priority))\n\t\tfmt.Fprintf(out, \"Dynamic: %t\\n\", argparser.IntToBool(fastly.ToValue(v.Dynamic)))\n\t\tfmt.Fprintf(out, \"Type: %s\\n\", fastly.ToValue(v.Type))\n\t\tfmt.Fprintf(out, \"Content: \\n%s\\n\", fastly.ToValue(v.Content))\n\n\t\tif v.CreatedAt != nil {\n\t\t\tfmt.Fprintf(out, \"Created at: %s\\n\", v.CreatedAt)\n\t\t}\n\t\tif v.UpdatedAt != nil {\n\t\t\tfmt.Fprintf(out, \"Updated at: %s\\n\", v.UpdatedAt)\n\t\t}\n\t\tif v.DeletedAt != nil {\n\t\t\tfmt.Fprintf(out, \"Deleted at: %s\\n\", v.DeletedAt)\n\t\t}\n\t}\n}\n\n// printSummary displays the information returned from the API in a summarised\n// format.\nfunc (c *ListCommand) printSummary(out io.Writer, ss []*fastly.Snippet) error {\n\tt := text.NewTable(out)\n\tt.AddHeader(\"SERVICE ID\", \"VERSION\", \"NAME\", \"DYNAMIC\", \"SNIPPET ID\")\n\tfor _, s := range ss {\n\t\tt.AddLine(\n\t\t\tfastly.ToValue(s.ServiceID),\n\t\t\tfastly.ToValue(s.ServiceVersion),\n\t\t\tfastly.ToValue(s.Name),\n\t\t\targparser.IntToBool(fastly.ToValue(s.Dynamic)),\n\t\t\tfastly.ToValue(s.SnippetID),\n\t\t)\n\t}\n\tt.Print()\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/vcl/snippet/root.go",
    "content": "package snippet\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"snippet\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manipulate Fastly VCL snippets (blocks of VCL logic inserted into your service's configuration that don't require custom VCL)\")\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/service/vcl/snippet/snippet_test.go",
    "content": "package snippet_test\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\ttop \"github.com/fastly/cli/pkg/commands/service\"\n\troot \"github.com/fastly/cli/pkg/commands/service/vcl\"\n\tsub \"github.com/fastly/cli/pkg/commands/service/vcl/snippet\"\n\t\"github.com/fastly/cli/pkg/mock\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n)\n\nfunc TestVCLSnippetCreate(t *testing.T) {\n\tvar content string\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --service-id flag\",\n\t\t\tArgs:      \"--content /path/to/snippet.vcl --name foo --type recv --version 3\",\n\t\t\tEnvVars:   map[string]string{\"FASTLY_SERVICE_ID\": \"\"},\n\t\t\tWantError: \"error reading service: no service ID found\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate CreateSnippet API error\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tCreateSnippetFn: func(_ context.Context, _ *fastly.CreateSnippetInput) (*fastly.Snippet, error) {\n\t\t\t\t\treturn nil, testutil.Err\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:      \"--content ./testdata/snippet.vcl --name foo --type recv --service-id 123 --version 3\",\n\t\t\tWantError: testutil.Err.Error(),\n\t\t},\n\t\t{\n\t\t\tName: \"validate CreateSnippet API success for versioned Snippet\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tCreateSnippetFn: func(_ context.Context, i *fastly.CreateSnippetInput) (*fastly.Snippet, error) {\n\t\t\t\t\t// Track the contents parsed\n\t\t\t\t\tcontent = *i.Content\n\t\t\t\t\tif i.Content == nil {\n\t\t\t\t\t\ti.Content = fastly.ToPointer(\"\")\n\t\t\t\t\t}\n\t\t\t\t\tif i.Dynamic == nil {\n\t\t\t\t\t\ti.Dynamic = fastly.ToPointer(0)\n\t\t\t\t\t}\n\t\t\t\t\tif i.Name == nil {\n\t\t\t\t\t\ti.Name = fastly.ToPointer(\"\")\n\t\t\t\t\t}\n\t\t\t\t\tif i.Priority == nil {\n\t\t\t\t\t\ti.Priority = fastly.ToPointer(\"100\")\n\t\t\t\t\t}\n\t\t\t\t\treturn &fastly.Snippet{\n\t\t\t\t\t\tContent:        i.Content,\n\t\t\t\t\t\tDynamic:        i.Dynamic,\n\t\t\t\t\t\tName:           i.Name,\n\t\t\t\t\t\tPriority:       i.Priority,\n\t\t\t\t\t\tServiceID:      fastly.ToPointer(i.ServiceID),\n\t\t\t\t\t\tServiceVersion: fastly.ToPointer(i.ServiceVersion),\n\t\t\t\t\t\tSnippetID:      fastly.ToPointer(\"123\"),\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:            \"--content ./testdata/snippet.vcl --name foo --service-id 123 --type recv --version 3\",\n\t\t\tWantOutput:      \"Created VCL snippet 'foo' (service: 123, version: 3, dynamic: false, snippet id: 123, type: recv, priority: 100)\",\n\t\t\tPathContentFlag: &testutil.PathContentFlag{Flag: \"content\", Fixture: \"snippet.vcl\", Content: func() string { return content }},\n\t\t},\n\t\t{\n\t\t\tName: \"validate CreateSnippet API success for dynamic Snippet\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tCreateSnippetFn: func(_ context.Context, i *fastly.CreateSnippetInput) (*fastly.Snippet, error) {\n\t\t\t\t\t// Track the contents parsed\n\t\t\t\t\tcontent = *i.Content\n\t\t\t\t\tif i.Content == nil {\n\t\t\t\t\t\ti.Content = fastly.ToPointer(\"\")\n\t\t\t\t\t}\n\t\t\t\t\tif i.Dynamic == nil {\n\t\t\t\t\t\ti.Dynamic = fastly.ToPointer(0)\n\t\t\t\t\t}\n\t\t\t\t\tif i.Name == nil {\n\t\t\t\t\t\ti.Name = fastly.ToPointer(\"\")\n\t\t\t\t\t}\n\t\t\t\t\tif i.Priority == nil {\n\t\t\t\t\t\ti.Priority = fastly.ToPointer(\"100\")\n\t\t\t\t\t}\n\t\t\t\t\treturn &fastly.Snippet{\n\t\t\t\t\t\tContent:        i.Content,\n\t\t\t\t\t\tDynamic:        i.Dynamic,\n\t\t\t\t\t\tName:           i.Name,\n\t\t\t\t\t\tPriority:       i.Priority,\n\t\t\t\t\t\tServiceID:      fastly.ToPointer(i.ServiceID),\n\t\t\t\t\t\tServiceVersion: fastly.ToPointer(i.ServiceVersion),\n\t\t\t\t\t\tSnippetID:      fastly.ToPointer(\"123\"),\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:            \"--content ./testdata/snippet.vcl --dynamic --name foo --service-id 123 --type recv --version 3\",\n\t\t\tWantOutput:      \"Created VCL snippet 'foo' (service: 123, version: 3, dynamic: true, snippet id: 123, type: recv, priority: 100)\",\n\t\t\tPathContentFlag: &testutil.PathContentFlag{Flag: \"content\", Fixture: \"snippet.vcl\", Content: func() string { return content }},\n\t\t},\n\t\t{\n\t\t\tName: \"validate Priority set\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tCreateSnippetFn: func(_ context.Context, i *fastly.CreateSnippetInput) (*fastly.Snippet, error) {\n\t\t\t\t\t// Track the contents parsed\n\t\t\t\t\tcontent = *i.Content\n\t\t\t\t\tif i.Content == nil {\n\t\t\t\t\t\ti.Content = fastly.ToPointer(\"\")\n\t\t\t\t\t}\n\t\t\t\t\tif i.Dynamic == nil {\n\t\t\t\t\t\ti.Dynamic = fastly.ToPointer(0)\n\t\t\t\t\t}\n\t\t\t\t\tif i.Name == nil {\n\t\t\t\t\t\ti.Name = fastly.ToPointer(\"\")\n\t\t\t\t\t}\n\t\t\t\t\tif i.Priority == nil {\n\t\t\t\t\t\ti.Priority = fastly.ToPointer(\"100\")\n\t\t\t\t\t}\n\t\t\t\t\treturn &fastly.Snippet{\n\t\t\t\t\t\tContent:        i.Content,\n\t\t\t\t\t\tDynamic:        i.Dynamic,\n\t\t\t\t\t\tName:           i.Name,\n\t\t\t\t\t\tPriority:       i.Priority,\n\t\t\t\t\t\tServiceID:      fastly.ToPointer(i.ServiceID),\n\t\t\t\t\t\tServiceVersion: fastly.ToPointer(i.ServiceVersion),\n\t\t\t\t\t\tSnippetID:      fastly.ToPointer(\"123\"),\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:            \"--content ./testdata/snippet.vcl --name foo --priority 1 --service-id 123 --type recv --version 3\",\n\t\t\tWantOutput:      \"Created VCL snippet 'foo' (service: 123, version: 3, dynamic: false, snippet id: 123, type: recv, priority: 1)\",\n\t\t\tPathContentFlag: &testutil.PathContentFlag{Flag: \"content\", Fixture: \"snippet.vcl\", Content: func() string { return content }},\n\t\t},\n\t\t{\n\t\t\tName: \"validate --autoclone results in cloned service version\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tCreateSnippetFn: func(_ context.Context, i *fastly.CreateSnippetInput) (*fastly.Snippet, error) {\n\t\t\t\t\t// Track the contents parsed\n\t\t\t\t\tcontent = *i.Content\n\t\t\t\t\tif i.Content == nil {\n\t\t\t\t\t\ti.Content = fastly.ToPointer(\"\")\n\t\t\t\t\t}\n\t\t\t\t\tif i.Dynamic == nil {\n\t\t\t\t\t\ti.Dynamic = fastly.ToPointer(0)\n\t\t\t\t\t}\n\t\t\t\t\tif i.Name == nil {\n\t\t\t\t\t\ti.Name = fastly.ToPointer(\"\")\n\t\t\t\t\t}\n\t\t\t\t\tif i.Priority == nil {\n\t\t\t\t\t\ti.Priority = fastly.ToPointer(\"100\")\n\t\t\t\t\t}\n\t\t\t\t\treturn &fastly.Snippet{\n\t\t\t\t\t\tContent:        i.Content,\n\t\t\t\t\t\tDynamic:        i.Dynamic,\n\t\t\t\t\t\tName:           i.Name,\n\t\t\t\t\t\tPriority:       i.Priority,\n\t\t\t\t\t\tServiceID:      fastly.ToPointer(i.ServiceID),\n\t\t\t\t\t\tServiceVersion: fastly.ToPointer(i.ServiceVersion),\n\t\t\t\t\t\tSnippetID:      fastly.ToPointer(\"123\"),\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:            \"--autoclone --content ./testdata/snippet.vcl --name foo --service-id 123 --type recv --version 1\",\n\t\t\tWantOutput:      \"Created VCL snippet 'foo' (service: 123, version: 4, dynamic: false, snippet id: 123, type: recv, priority: 100)\",\n\t\t\tPathContentFlag: &testutil.PathContentFlag{Flag: \"content\", Fixture: \"snippet.vcl\", Content: func() string { return content }},\n\t\t},\n\t\t{\n\t\t\tName: \"validate CreateSnippet API success with inline Snippet content\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tCreateSnippetFn: func(_ context.Context, i *fastly.CreateSnippetInput) (*fastly.Snippet, error) {\n\t\t\t\t\t// Track the contents parsed\n\t\t\t\t\tcontent = *i.Content\n\t\t\t\t\tif i.Content == nil {\n\t\t\t\t\t\ti.Content = fastly.ToPointer(\"\")\n\t\t\t\t\t}\n\t\t\t\t\tif i.Dynamic == nil {\n\t\t\t\t\t\ti.Dynamic = fastly.ToPointer(0)\n\t\t\t\t\t}\n\t\t\t\t\tif i.Name == nil {\n\t\t\t\t\t\ti.Name = fastly.ToPointer(\"\")\n\t\t\t\t\t}\n\t\t\t\t\tif i.Priority == nil {\n\t\t\t\t\t\ti.Priority = fastly.ToPointer(\"100\")\n\t\t\t\t\t}\n\t\t\t\t\treturn &fastly.Snippet{\n\t\t\t\t\t\tContent:        i.Content,\n\t\t\t\t\t\tDynamic:        i.Dynamic,\n\t\t\t\t\t\tName:           i.Name,\n\t\t\t\t\t\tPriority:       i.Priority,\n\t\t\t\t\t\tServiceID:      fastly.ToPointer(i.ServiceID),\n\t\t\t\t\t\tServiceVersion: fastly.ToPointer(i.ServiceVersion),\n\t\t\t\t\t\tSnippetID:      fastly.ToPointer(\"123\"),\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:            \"--content inline_vcl --name foo --service-id 123 --type recv --version 3\",\n\t\t\tWantOutput:      \"Created VCL snippet 'foo' (service: 123, version: 3, dynamic: false, snippet id: 123, type: recv, priority: 100)\",\n\t\t\tPathContentFlag: &testutil.PathContentFlag{Flag: \"content\", Fixture: \"snippet.vcl\", Content: func() string { return content }},\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{top.CommandName, root.CommandName, sub.CommandName, \"create\"}, scenarios)\n}\n\nfunc TestVCLSnippetDelete(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --name flag\",\n\t\t\tArgs:      \"--version 3\",\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --version flag\",\n\t\t\tArgs:      \"--name foobar\",\n\t\t\tWantError: \"error parsing arguments: required flag --version not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --service-id flag\",\n\t\t\tArgs:      \"--name foobar --version 3\",\n\t\t\tEnvVars:   map[string]string{\"FASTLY_SERVICE_ID\": \"\"},\n\t\t\tWantError: \"error reading service: no service ID found\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate DeleteSnippet API error\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tDeleteSnippetFn: func(_ context.Context, _ *fastly.DeleteSnippetInput) error {\n\t\t\t\t\treturn testutil.Err\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:      \"--name foobar --service-id 123 --version 3\",\n\t\t\tWantError: testutil.Err.Error(),\n\t\t},\n\t\t{\n\t\t\tName: \"validate DeleteSnippet API success\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tDeleteSnippetFn: func(_ context.Context, _ *fastly.DeleteSnippetInput) error {\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:       \"--name foobar --service-id 123 --version 3\",\n\t\t\tWantOutput: \"Deleted VCL snippet 'foobar' (service: 123, version: 3)\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API error when modifying active version\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tDeleteSnippetFn: func(_ context.Context, i *fastly.DeleteSnippetInput) error {\n\t\t\t\t\treturn fmt.Errorf(\"Cannot update version %d. Versions that have been activated cannot be updated\", i.ServiceVersion)\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:      \"--name foobar --service-id 123 --version 3\",\n\t\t\tWantError: \"Cannot update version 3. Versions that have been activated cannot be updated\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API error when modifying locked version\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tDeleteSnippetFn: func(_ context.Context, i *fastly.DeleteSnippetInput) error {\n\t\t\t\t\treturn fmt.Errorf(\"Cannot update version %d. Versions that have been locked cannot be updated\", i.ServiceVersion)\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:      \"--name foobar --service-id 123 --version 3\",\n\t\t\tWantError: \"Cannot update version 3. Versions that have been locked cannot be updated\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate --autoclone results in cloned service version\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tDeleteSnippetFn: func(_ context.Context, _ *fastly.DeleteSnippetInput) error {\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:       \"--autoclone --name foo --service-id 123 --version 1\",\n\t\t\tWantOutput: \"Deleted VCL snippet 'foo' (service: 123, version: 4)\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate --autoclone on locked version\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tDeleteSnippetFn: func(_ context.Context, i *fastly.DeleteSnippetInput) error {\n\t\t\t\t\t// Verify operation happens on the cloned version (4), not original (2)\n\t\t\t\t\tif i.ServiceVersion != 4 {\n\t\t\t\t\t\treturn fmt.Errorf(\"expected operation on cloned version 4, got %d\", i.ServiceVersion)\n\t\t\t\t\t}\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:       \"--autoclone --name foo --service-id 123 --version 2\",\n\t\t\tWantOutput: \"Deleted VCL snippet 'foo' (service: 123, version: 4)\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate --autoclone on editable version\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tDeleteSnippetFn: func(_ context.Context, i *fastly.DeleteSnippetInput) error {\n\t\t\t\t\t// Verify operation happens on the cloned version (4), not original (3)\n\t\t\t\t\tif i.ServiceVersion != 4 {\n\t\t\t\t\t\treturn fmt.Errorf(\"expected operation on cloned version 4, got %d\", i.ServiceVersion)\n\t\t\t\t\t}\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:       \"--autoclone --name foo --service-id 123 --version 3\",\n\t\t\tWantOutput: \"Deleted VCL snippet 'foo' (service: 123, version: 4)\",\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{top.CommandName, root.CommandName, sub.CommandName, \"delete\"}, scenarios)\n}\n\nfunc TestVCLSnippetDescribe(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --version flag\",\n\t\t\tWantError: \"error parsing arguments: required flag --version not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --service-id flag\",\n\t\t\tArgs:      \"--version 3\",\n\t\t\tWantError: \"error reading service: no service ID found\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate missing --name flag with versioned snippet\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t},\n\t\t\tArgs:      \"--service-id 123 --version 3\",\n\t\t\tWantError: \"error parsing arguments: must provide --name with a versioned VCL snippet\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate missing --snippet-id flag with dynamic snippet\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t},\n\t\t\tArgs:      \"--dynamic --service-id 123 --version 3\",\n\t\t\tWantError: \"error parsing arguments: must provide --snippet-id with a dynamic VCL snippet\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate GetSnippet API error\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tGetSnippetFn: func(_ context.Context, _ *fastly.GetSnippetInput) (*fastly.Snippet, error) {\n\t\t\t\t\treturn nil, testutil.Err\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:      \"--name foobar --service-id 123 --version 3\",\n\t\t\tWantError: testutil.Err.Error(),\n\t\t},\n\t\t{\n\t\t\tName: \"validate GetSnippet API success\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tGetSnippetFn: getSnippet,\n\t\t\t},\n\t\t\tArgs:       \"--name foobar --service-id 123 --version 3\",\n\t\t\tWantOutput: \"\\nService ID: 123\\nService Version: 3\\n\\nName: foobar\\nID: 456\\nPriority: 0\\nDynamic: false\\nType: recv\\nContent: \\n# some vcl content\\nCreated at: 2021-06-15 23:00:00 +0000 UTC\\nUpdated at: 2021-06-15 23:00:00 +0000 UTC\\nDeleted at: 2021-06-15 23:00:00 +0000 UTC\\n\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate missing --autoclone flag is OK\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tGetSnippetFn: getSnippet,\n\t\t\t},\n\t\t\tArgs:       \"--name foobar --service-id 123 --version 1\",\n\t\t\tWantOutput: \"\\nService ID: 123\\nService Version: 1\\n\\nName: foobar\\nID: 456\\nPriority: 0\\nDynamic: false\\nType: recv\\nContent: \\n# some vcl content\\nCreated at: 2021-06-15 23:00:00 +0000 UTC\\nUpdated at: 2021-06-15 23:00:00 +0000 UTC\\nDeleted at: 2021-06-15 23:00:00 +0000 UTC\\n\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate dynamic GetSnippet API success\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:        testutil.GetVersion,\n\t\t\t\tGetDynamicSnippetFn: getDynamicSnippet,\n\t\t\t},\n\t\t\tArgs:       \"--dynamic --service-id 123 --snippet-id 456 --version 3\",\n\t\t\tWantOutput: \"\\nService ID: 123\\nID: 456\\nContent: \\n# some vcl content\\nCreated at: 2021-06-15 23:00:00 +0000 UTC\\nUpdated at: 2021-06-15 23:00:00 +0000 UTC\\n\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate --content flag outputs raw VCL only for versioned snippet\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tGetSnippetFn: getSnippet,\n\t\t\t},\n\t\t\tArgs:       \"--content --name foobar --service-id 123 --version 3\",\n\t\t\tWantOutput: \"# some vcl content\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate --content flag outputs raw VCL only for dynamic snippet\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:        testutil.GetVersion,\n\t\t\t\tGetDynamicSnippetFn: getDynamicSnippet,\n\t\t\t},\n\t\t\tArgs:       \"--content --dynamic --service-id 123 --snippet-id 456 --version 3\",\n\t\t\tWantOutput: \"# some vcl content\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate --content flag with --verbose returns error\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t},\n\t\t\tArgs:      \"--content --name foobar --service-id 123 --verbose --version 3\",\n\t\t\tWantError: \"invalid flag combination, --content cannot be used together with --json or --verbose\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate --content flag with --json returns error\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t},\n\t\t\tArgs:      \"--content --json --name foobar --service-id 123 --version 3\",\n\t\t\tWantError: \"invalid flag combination, --content cannot be used together with --json or --verbose\",\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{top.CommandName, root.CommandName, sub.CommandName, \"describe\"}, scenarios)\n}\n\nfunc TestVCLSnippetList(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --version flag\",\n\t\t\tWantError: \"error parsing arguments: required flag --version not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --service-id flag\",\n\t\t\tArgs:      \"--version 3\",\n\t\t\tWantError: \"error reading service: no service ID found\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate ListSnippets API error\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tListSnippetsFn: func(_ context.Context, _ *fastly.ListSnippetsInput) ([]*fastly.Snippet, error) {\n\t\t\t\t\treturn nil, testutil.Err\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:      \"--service-id 123 --version 3\",\n\t\t\tWantError: testutil.Err.Error(),\n\t\t},\n\t\t{\n\t\t\tName: \"validate ListSnippets API success\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tListSnippetsFn: listSnippets,\n\t\t\t},\n\t\t\tArgs:       \"--service-id 123 --version 3\",\n\t\t\tWantOutput: \"SERVICE ID  VERSION  NAME  DYNAMIC  SNIPPET ID\\n123         3        foo   true     abc\\n123         3        bar   false    abc\\n\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate missing --autoclone flag is OK\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tListSnippetsFn: listSnippets,\n\t\t\t},\n\t\t\tArgs:       \"--service-id 123 --version 1\",\n\t\t\tWantOutput: \"SERVICE ID  VERSION  NAME  DYNAMIC  SNIPPET ID\\n123         1        foo   true     abc\\n123         1        bar   false    abc\\n\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate missing --verbose flag\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tListSnippetsFn: listSnippets,\n\t\t\t},\n\t\t\tArgs:       \"--service-id 123 --verbose --version 1\",\n\t\t\tWantOutput: \"Fastly API endpoint: https://api.fastly.com\\nFastly API token provided via config file (auth: user)\\n\\nService ID (via --service-id): 123\\n\\nService Version: 1\\n\\nName: foo\\nID: abc\\nPriority: 0\\nDynamic: true\\nType: recv\\nContent: \\n# some vcl content\\nCreated at: 2021-06-15 23:00:00 +0000 UTC\\nUpdated at: 2021-06-15 23:00:00 +0000 UTC\\nDeleted at: 2021-06-15 23:00:00 +0000 UTC\\n\\nName: bar\\nID: abc\\nPriority: 0\\nDynamic: false\\nType: recv\\nContent: \\n# some vcl content\\nCreated at: 2021-06-15 23:00:00 +0000 UTC\\nUpdated at: 2021-06-15 23:00:00 +0000 UTC\\nDeleted at: 2021-06-15 23:00:00 +0000 UTC\\n\",\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{top.CommandName, root.CommandName, sub.CommandName, \"list\"}, scenarios)\n}\n\nfunc TestVCLSnippetUpdate(t *testing.T) {\n\tvar content string\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --version flag\",\n\t\t\tWantError: \"error parsing arguments: required flag --version not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --service-id flag\",\n\t\t\tArgs:      \"--version 3\",\n\t\t\tEnvVars:   map[string]string{\"FASTLY_SERVICE_ID\": \"\"},\n\t\t\tWantError: \"error reading service: no service ID found\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate versioned snippet missing --name\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t},\n\t\t\tArgs:      \"--content inline_vcl --new-name bar --service-id 123 --type recv --version 3\",\n\t\t\tWantError: \"error parsing arguments: must provide --name to update a versioned VCL snippet\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate dynamic snippet missing --snippet-id\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t},\n\t\t\tArgs:      \"--content inline_vcl --dynamic --service-id 123 --version 3\",\n\t\t\tWantError: \"error parsing arguments: must provide --snippet-id to update a dynamic VCL snippet\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate versioned snippet with --snippet-id is not allowed\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t},\n\t\t\tArgs:      \"--content inline_vcl --new-name foobar --service-id 123 --snippet-id 456 --version 3\",\n\t\t\tWantError: \"error parsing arguments: --snippet-id is not supported when updating a versioned VCL snippet\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate dynamic snippet with --new-name is not allowed\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t},\n\t\t\tArgs:      \"--content inline_vcl --dynamic --new-name foobar --service-id 123 --snippet-id 456 --version 3\",\n\t\t\tWantError: \"error parsing arguments: --new-name is not supported when updating a dynamic VCL snippet\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate UpdateSnippet API error\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tUpdateSnippetFn: func(_ context.Context, _ *fastly.UpdateSnippetInput) (*fastly.Snippet, error) {\n\t\t\t\t\treturn nil, testutil.Err\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:      \"--content inline_vcl --name foo --new-name bar --service-id 123 --type recv --version 3\",\n\t\t\tWantError: testutil.Err.Error(),\n\t\t},\n\t\t{\n\t\t\tName: \"validate UpdateSnippet API success\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tUpdateSnippetFn: func(_ context.Context, i *fastly.UpdateSnippetInput) (*fastly.Snippet, error) {\n\t\t\t\t\t// Track the contents parsed\n\t\t\t\t\tcontent = *i.Content\n\n\t\t\t\t\treturn &fastly.Snippet{\n\t\t\t\t\t\tContent:        i.Content,\n\t\t\t\t\t\tName:           i.NewName,\n\t\t\t\t\t\tPriority:       fastly.ToPointer(\"100\"),\n\t\t\t\t\t\tServiceID:      fastly.ToPointer(i.ServiceID),\n\t\t\t\t\t\tServiceVersion: fastly.ToPointer(i.ServiceVersion),\n\t\t\t\t\t\tType:           i.Type,\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:            \"--content inline_vcl --name foo --new-name bar --service-id 123 --type recv --version 3\",\n\t\t\tWantOutput:      \"Updated VCL snippet 'bar' (previously: 'foo', service: 123, version: 3, type: recv, priority: 100)\",\n\t\t\tPathContentFlag: &testutil.PathContentFlag{Flag: \"content\", Fixture: \"snippet.vcl\", Content: func() string { return content }},\n\t\t},\n\t\t{\n\t\t\tName: \"validate UpdateDynamicSnippet API success\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tUpdateDynamicSnippetFn: func(_ context.Context, i *fastly.UpdateDynamicSnippetInput) (*fastly.DynamicSnippet, error) {\n\t\t\t\t\t// Track the contents parsed\n\t\t\t\t\tcontent = *i.Content\n\n\t\t\t\t\treturn &fastly.DynamicSnippet{\n\t\t\t\t\t\tContent:   i.Content,\n\t\t\t\t\t\tSnippetID: fastly.ToPointer(i.SnippetID),\n\t\t\t\t\t\tServiceID: fastly.ToPointer(i.ServiceID),\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:            \"--content inline_vcl --dynamic --service-id 123 --snippet-id 456 --version 3\",\n\t\t\tWantOutput:      \"Updated dynamic VCL snippet '456' (service: 123)\",\n\t\t\tPathContentFlag: &testutil.PathContentFlag{Flag: \"content\", Fixture: \"snippet.vcl\", Content: func() string { return content }},\n\t\t},\n\t\t{\n\t\t\tName: \"validate API error when modifying active version\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tUpdateSnippetFn: func(_ context.Context, i *fastly.UpdateSnippetInput) (*fastly.Snippet, error) {\n\t\t\t\t\treturn nil, fmt.Errorf(\"Cannot update version %d. Versions that have been activated cannot be updated\", i.ServiceVersion)\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:      \"--content inline_vcl --name foo --new-name bar --service-id 123 --type recv --version 3\",\n\t\t\tWantError: \"Cannot update version 3. Versions that have been activated cannot be updated\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API error when modifying locked version\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tUpdateSnippetFn: func(_ context.Context, i *fastly.UpdateSnippetInput) (*fastly.Snippet, error) {\n\t\t\t\t\treturn nil, fmt.Errorf(\"Cannot update version %d. Versions that have been locked cannot be updated\", i.ServiceVersion)\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:      \"--content inline_vcl --name foo --new-name bar --service-id 123 --type recv --version 3\",\n\t\t\tWantError: \"Cannot update version 3. Versions that have been locked cannot be updated\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate --autoclone results in cloned service version\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tUpdateSnippetFn: func(_ context.Context, i *fastly.UpdateSnippetInput) (*fastly.Snippet, error) {\n\t\t\t\t\t// Track the contents parsed\n\t\t\t\t\tcontent = *i.Content\n\n\t\t\t\t\treturn &fastly.Snippet{\n\t\t\t\t\t\tContent:        i.Content,\n\t\t\t\t\t\tName:           i.NewName,\n\t\t\t\t\t\tPriority:       i.Priority,\n\t\t\t\t\t\tServiceID:      fastly.ToPointer(i.ServiceID),\n\t\t\t\t\t\tServiceVersion: fastly.ToPointer(i.ServiceVersion),\n\t\t\t\t\t\tType:           i.Type,\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:            \"--autoclone --content inline_vcl --name foo --new-name bar --priority 1 --service-id 123 --type recv --version 1\",\n\t\t\tWantOutput:      \"Updated VCL snippet 'bar' (previously: 'foo', service: 123, version: 4, type: recv, priority: 1)\",\n\t\t\tPathContentFlag: &testutil.PathContentFlag{Flag: \"content\", Fixture: \"snippet.vcl\", Content: func() string { return content }},\n\t\t},\n\t\t{\n\t\t\tName: \"validate --autoclone on locked version\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tUpdateSnippetFn: func(_ context.Context, i *fastly.UpdateSnippetInput) (*fastly.Snippet, error) {\n\t\t\t\t\t// Verify operation happens on the cloned version (4), not original (2)\n\t\t\t\t\tif i.ServiceVersion != 4 {\n\t\t\t\t\t\treturn nil, fmt.Errorf(\"expected operation on cloned version 4, got %d\", i.ServiceVersion)\n\t\t\t\t\t}\n\t\t\t\t\t// Track the contents parsed\n\t\t\t\t\tcontent = *i.Content\n\n\t\t\t\t\treturn &fastly.Snippet{\n\t\t\t\t\t\tContent:        i.Content,\n\t\t\t\t\t\tName:           i.NewName,\n\t\t\t\t\t\tPriority:       i.Priority,\n\t\t\t\t\t\tServiceID:      fastly.ToPointer(i.ServiceID),\n\t\t\t\t\t\tServiceVersion: fastly.ToPointer(i.ServiceVersion),\n\t\t\t\t\t\tType:           i.Type,\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:            \"--autoclone --content inline_vcl --name foo --new-name bar --priority 1 --service-id 123 --type recv --version 2\",\n\t\t\tWantOutput:      \"Updated VCL snippet 'bar' (previously: 'foo', service: 123, version: 4, type: recv, priority: 1)\",\n\t\t\tPathContentFlag: &testutil.PathContentFlag{Flag: \"content\", Fixture: \"snippet.vcl\", Content: func() string { return content }},\n\t\t},\n\t\t{\n\t\t\tName: \"validate --autoclone on editable version\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tUpdateSnippetFn: func(_ context.Context, i *fastly.UpdateSnippetInput) (*fastly.Snippet, error) {\n\t\t\t\t\t// Verify operation happens on the cloned version (4), not original (3)\n\t\t\t\t\tif i.ServiceVersion != 4 {\n\t\t\t\t\t\treturn nil, fmt.Errorf(\"expected operation on cloned version 4, got %d\", i.ServiceVersion)\n\t\t\t\t\t}\n\t\t\t\t\t// Track the contents parsed\n\t\t\t\t\tcontent = *i.Content\n\n\t\t\t\t\treturn &fastly.Snippet{\n\t\t\t\t\t\tContent:        i.Content,\n\t\t\t\t\t\tName:           i.NewName,\n\t\t\t\t\t\tPriority:       i.Priority,\n\t\t\t\t\t\tServiceID:      fastly.ToPointer(i.ServiceID),\n\t\t\t\t\t\tServiceVersion: fastly.ToPointer(i.ServiceVersion),\n\t\t\t\t\t\tType:           i.Type,\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:            \"--autoclone --content inline_vcl --name foo --new-name bar --priority 1 --service-id 123 --type recv --version 3\",\n\t\t\tWantOutput:      \"Updated VCL snippet 'bar' (previously: 'foo', service: 123, version: 4, type: recv, priority: 1)\",\n\t\t\tPathContentFlag: &testutil.PathContentFlag{Flag: \"content\", Fixture: \"snippet.vcl\", Content: func() string { return content }},\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{top.CommandName, root.CommandName, sub.CommandName, \"update\"}, scenarios)\n}\n\nfunc getSnippet(_ context.Context, i *fastly.GetSnippetInput) (*fastly.Snippet, error) {\n\tt := testutil.Date\n\n\treturn &fastly.Snippet{\n\t\tContent:        fastly.ToPointer(\"# some vcl content\"),\n\t\tDynamic:        fastly.ToPointer(0),\n\t\tSnippetID:      fastly.ToPointer(\"456\"),\n\t\tName:           fastly.ToPointer(i.Name),\n\t\tPriority:       fastly.ToPointer(\"0\"),\n\t\tServiceID:      fastly.ToPointer(i.ServiceID),\n\t\tServiceVersion: fastly.ToPointer(i.ServiceVersion),\n\t\tType:           fastly.ToPointer(fastly.SnippetTypeRecv),\n\n\t\tCreatedAt: &t,\n\t\tDeletedAt: &t,\n\t\tUpdatedAt: &t,\n\t}, nil\n}\n\nfunc getDynamicSnippet(_ context.Context, i *fastly.GetDynamicSnippetInput) (*fastly.DynamicSnippet, error) {\n\tt := testutil.Date\n\n\treturn &fastly.DynamicSnippet{\n\t\tContent:   fastly.ToPointer(\"# some vcl content\"),\n\t\tSnippetID: fastly.ToPointer(i.SnippetID),\n\t\tServiceID: fastly.ToPointer(i.ServiceID),\n\n\t\tCreatedAt: &t,\n\t\tUpdatedAt: &t,\n\t}, nil\n}\n\nfunc listSnippets(_ context.Context, i *fastly.ListSnippetsInput) ([]*fastly.Snippet, error) {\n\tt := testutil.Date\n\tvs := []*fastly.Snippet{\n\t\t{\n\t\t\tContent:        fastly.ToPointer(\"# some vcl content\"),\n\t\t\tDynamic:        fastly.ToPointer(1),\n\t\t\tSnippetID:      fastly.ToPointer(\"abc\"),\n\t\t\tName:           fastly.ToPointer(\"foo\"),\n\t\t\tPriority:       fastly.ToPointer(\"0\"),\n\t\t\tServiceID:      fastly.ToPointer(i.ServiceID),\n\t\t\tServiceVersion: fastly.ToPointer(i.ServiceVersion),\n\t\t\tType:           fastly.ToPointer(fastly.SnippetTypeRecv),\n\n\t\t\tCreatedAt: &t,\n\t\t\tDeletedAt: &t,\n\t\t\tUpdatedAt: &t,\n\t\t},\n\t\t{\n\t\t\tContent:        fastly.ToPointer(\"# some vcl content\"),\n\t\t\tDynamic:        fastly.ToPointer(0),\n\t\t\tSnippetID:      fastly.ToPointer(\"abc\"),\n\t\t\tName:           fastly.ToPointer(\"bar\"),\n\t\t\tPriority:       fastly.ToPointer(\"0\"),\n\t\t\tServiceID:      fastly.ToPointer(i.ServiceID),\n\t\t\tServiceVersion: fastly.ToPointer(i.ServiceVersion),\n\t\t\tType:           fastly.ToPointer(fastly.SnippetTypeRecv),\n\n\t\t\tCreatedAt: &t,\n\t\t\tDeletedAt: &t,\n\t\t\tUpdatedAt: &t,\n\t\t},\n\t}\n\treturn vs, nil\n}\n"
  },
  {
    "path": "pkg/commands/service/vcl/snippet/testdata/snippet.vcl",
    "content": "# some vcl content\n"
  },
  {
    "path": "pkg/commands/service/vcl/snippet/update.go",
    "content": "package snippet\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"update\", \"Update a VCL snippet for a particular service and version\")\n\n\t// Required.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\n\t// Optional.\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.autoClone.Set,\n\t\tDst:    &c.autoClone.Value,\n\t})\n\tc.CmdClause.Flag(\"content\", \"VCL snippet passed as file path or content, e.g. $(< snippet.vcl)\").Action(c.content.Set).StringVar(&c.content.Value)\n\tc.CmdClause.Flag(\"dynamic\", \"Whether the VCL snippet is dynamic or versioned\").Action(c.dynamic.Set).BoolVar(&c.dynamic.Value)\n\tc.CmdClause.Flag(\"name\", \"The name of the VCL snippet to update\").StringVar(&c.name)\n\tc.CmdClause.Flag(\"new-name\", \"New name for the VCL snippet\").Action(c.newName.Set).StringVar(&c.newName.Value)\n\tc.CmdClause.Flag(\"priority\", \"Priority determines execution order. Lower numbers execute first\").Short('p').Action(c.priority.Set).StringVar(&c.priority.Value)\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\tc.CmdClause.Flag(\"snippet-id\", \"Alphanumeric string identifying a VCL Snippet\").StringVar(&c.snippetID)\n\n\t// NOTE: Locations is defined in the same snippet package inside create.go\n\tc.CmdClause.Flag(\"type\", \"The location in generated VCL where the snippet should be placed\").HintOptions(Locations...).Action(c.location.Set).EnumVar(&c.location.Value, Locations...)\n\n\treturn &c\n}\n\n// UpdateCommand calls the Fastly API to update an appropriate resource.\ntype UpdateCommand struct {\n\targparser.Base\n\n\tautoClone      argparser.OptionalAutoClone\n\tcontent        argparser.OptionalString\n\tdynamic        argparser.OptionalBool\n\tlocation       argparser.OptionalString\n\tname           string\n\tnewName        argparser.OptionalString\n\tpriority       argparser.OptionalString\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n\tsnippetID      string\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *UpdateCommand) Exec(_ io.Reader, out io.Writer) error {\n\t// in the normal case, we do not want to allow 'active' or 'locked' services to be updated,\n\t// so we require those states to be 'false'\n\tallowActive := optional.Of(false)\n\tallowLocked := optional.Of(false)\n\tif c.dynamic.WasSet && c.dynamic.Value {\n\t\t// in this case, we will accept all states ('active' and 'inactive', 'locked' and 'unlocked'),\n\t\t// so we mark the Optional[bool] fields as 'empty' and they will not be applied as filters\n\t\tallowActive = optional.Empty[bool]()\n\t\tallowLocked = optional.Empty[bool]()\n\t}\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             allowActive,\n\t\tLocked:             allowLocked,\n\t\tAutoCloneFlag:      c.autoClone,\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fsterr.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tserviceVersionNumber := fastly.ToValue(serviceVersion.Number)\n\n\tif c.dynamic.WasSet {\n\t\tinput, err := c.constructDynamicInput(serviceID, serviceVersionNumber)\n\t\tif err != nil {\n\t\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\t\"Service ID\":      serviceID,\n\t\t\t\t\"Service Version\": serviceVersionNumber,\n\t\t\t})\n\t\t\treturn err\n\t\t}\n\t\tv, err := c.Globals.APIClient.UpdateDynamicSnippet(context.TODO(), input)\n\t\tif err != nil {\n\t\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\t\"Service ID\":      serviceID,\n\t\t\t\t\"Service Version\": serviceVersionNumber,\n\t\t\t})\n\t\t\treturn err\n\t\t}\n\t\ttext.Success(out, \"Updated dynamic VCL snippet '%s' (service: %s)\", fastly.ToValue(v.SnippetID), fastly.ToValue(v.ServiceID))\n\t\treturn nil\n\t}\n\n\tinput, err := c.constructInput(serviceID, serviceVersionNumber)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": serviceVersionNumber,\n\t\t})\n\t\treturn err\n\t}\n\tv, err := c.Globals.APIClient.UpdateSnippet(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": serviceVersionNumber,\n\t\t})\n\t\treturn err\n\t}\n\ttext.Success(out,\n\t\t\"Updated VCL snippet '%s' (previously: '%s', service: %s, version: %d, type: %v, priority: %s)\",\n\t\tfastly.ToValue(v.Name),\n\t\tinput.Name,\n\t\tfastly.ToValue(v.ServiceID),\n\t\tfastly.ToValue(v.ServiceVersion),\n\t\tfastly.ToValue(v.Type),\n\t\tfastly.ToValue(v.Priority),\n\t)\n\treturn nil\n}\n\n// constructDynamicInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *UpdateCommand) constructDynamicInput(serviceID string, _ int) (*fastly.UpdateDynamicSnippetInput, error) {\n\tvar input fastly.UpdateDynamicSnippetInput\n\n\tinput.SnippetID = c.snippetID\n\tinput.ServiceID = serviceID\n\n\tif c.newName.WasSet {\n\t\treturn nil, fmt.Errorf(\"error parsing arguments: --new-name is not supported when updating a dynamic VCL snippet\")\n\t}\n\tif c.snippetID == \"\" {\n\t\treturn nil, fmt.Errorf(\"error parsing arguments: must provide --snippet-id to update a dynamic VCL snippet\")\n\t}\n\tif c.content.WasSet {\n\t\tinput.Content = fastly.ToPointer(argparser.Content(c.content.Value))\n\t}\n\n\treturn &input, nil\n}\n\n// constructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *UpdateCommand) constructInput(serviceID string, serviceVersion int) (*fastly.UpdateSnippetInput, error) {\n\tvar input fastly.UpdateSnippetInput\n\n\tinput.Name = c.name\n\tinput.ServiceID = serviceID\n\tinput.ServiceVersion = serviceVersion\n\n\tif c.snippetID != \"\" {\n\t\treturn nil, fmt.Errorf(\"error parsing arguments: --snippet-id is not supported when updating a versioned VCL snippet\")\n\t}\n\tif c.name == \"\" {\n\t\treturn nil, fmt.Errorf(\"error parsing arguments: must provide --name to update a versioned VCL snippet\")\n\t}\n\tif c.newName.WasSet {\n\t\tinput.NewName = &c.newName.Value\n\t}\n\tif c.priority.WasSet {\n\t\tinput.Priority = &c.priority.Value\n\t}\n\tif c.content.WasSet {\n\t\tinput.Content = fastly.ToPointer(argparser.Content(c.content.Value))\n\t}\n\tif c.location.WasSet {\n\t\tlocation := fastly.SnippetType(c.location.Value)\n\t\tinput.Type = &location\n\t}\n\n\treturn &input, nil\n}\n"
  },
  {
    "path": "pkg/commands/service/vcl/vcl_test.go",
    "content": "package vcl_test\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\troot \"github.com/fastly/cli/pkg/commands/service\"\n\tsub \"github.com/fastly/cli/pkg/commands/service/vcl\"\n\t\"github.com/fastly/cli/pkg/mock\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n)\n\nfunc TestVCLDescribe(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --version flag\",\n\t\t\tWantError: \"error parsing arguments: required flag --version not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --service-id flag\",\n\t\t\tArgs:      \"--version 3\",\n\t\t\tEnvVars:   map[string]string{\"FASTLY_SERVICE_ID\": \"\"},\n\t\t\tWantError: \"error reading service: no service ID found\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate DescribeVCL API error\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tGetGeneratedVCLFn: func(_ context.Context, _ *fastly.GetGeneratedVCLInput) (*fastly.VCL, error) {\n\t\t\t\t\treturn nil, testutil.Err\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:      \"--service-id 123 --version 3\",\n\t\t\tWantError: testutil.Err.Error(),\n\t\t},\n\t\t{\n\t\t\tName: \"validate DescribeVCL API success\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:      testutil.GetVersion,\n\t\t\t\tGetGeneratedVCLFn: getVCL,\n\t\t\t},\n\t\t\tArgs:       \"--service-id 123 --version 3\",\n\t\t\tWantOutput: \"# some vcl content\\n\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate missing --verbose flag\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:      testutil.GetVersion,\n\t\t\t\tGetGeneratedVCLFn: getVCL,\n\t\t\t},\n\t\t\tArgs:       \"--service-id 123 --verbose --version 1\",\n\t\t\tWantOutput: \"Fastly API endpoint: https://api.fastly.com\\nFastly API token provided via config file (auth: user)\\n\\nService ID (via --service-id): 123\\n\\nService Version: 1\\n\\nName: foo\\nMain: false\\nCreated at: 2021-06-15 23:00:00 +0000 UTC\\nUpdated at: 2021-06-15 23:00:00 +0000 UTC\\nDeleted at: 2021-06-15 23:00:00 +0000 UTC\\nContent: \\n# some vcl content\\n\",\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"describe\"}, scenarios)\n}\n\nfunc getVCL(_ context.Context, i *fastly.GetGeneratedVCLInput) (*fastly.VCL, error) {\n\tt := testutil.Date\n\n\treturn &fastly.VCL{\n\t\tContent:        fastly.ToPointer(\"# some vcl content\"),\n\t\tMain:           fastly.ToPointer(false),\n\t\tName:           fastly.ToPointer(\"foo\"),\n\t\tServiceID:      fastly.ToPointer(i.ServiceID),\n\t\tServiceVersion: fastly.ToPointer(i.ServiceVersion),\n\t\tCreatedAt:      &t,\n\t\tDeletedAt:      &t,\n\t\tUpdatedAt:      &t,\n\t}, nil\n}\n"
  },
  {
    "path": "pkg/commands/service/version/activate.go",
    "content": "package version\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// ActivateCommand calls the Fastly API to activate a service version.\ntype ActivateCommand struct {\n\targparser.Base\n\tInput          fastly.ActivateVersionInput\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n\tautoClone      argparser.OptionalAutoClone\n}\n\n// NewActivateCommand returns a usable command registered under the parent.\nfunc NewActivateCommand(parent argparser.Registerer, g *global.Data) *ActivateCommand {\n\tvar c ActivateCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(\"activate\", \"Activate a Fastly service version\")\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.autoClone.Set,\n\t\tDst:    &c.autoClone.Value,\n\t})\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *ActivateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(false),\n\t\tAutoCloneFlag:      c.autoClone,\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": errors.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tc.Input.ServiceID = serviceID\n\tc.Input.ServiceVersion = fastly.ToValue(serviceVersion.Number)\n\n\tver, err := c.Globals.APIClient.ActivateVersion(context.TODO(), &c.Input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": serviceVersion.Number,\n\t\t})\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Activated service %s version %d\", fastly.ToValue(ver.ServiceID), c.Input.ServiceVersion)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/version/clone.go",
    "content": "package version\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// CloneCommand calls the Fastly API to clone a service version.\ntype CloneCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tInput          fastly.CloneVersionInput\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n}\n\n// NewCloneCommand returns a usable command registered under the parent.\nfunc NewCloneCommand(parent argparser.Registerer, g *global.Data) *CloneCommand {\n\tvar c CloneCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(\"clone\", \"Clone a Fastly service version\")\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *CloneCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn errors.ErrInvalidVerboseJSONCombo\n\t}\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": errors.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tc.Input.ServiceID = serviceID\n\tc.Input.ServiceVersion = fastly.ToValue(serviceVersion.Number)\n\n\tver, err := c.Globals.APIClient.CloneVersion(context.TODO(), &c.Input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fastly.ToValue(serviceVersion.Number),\n\t\t})\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, ver); ok {\n\t\treturn err\n\t}\n\ttext.Success(out, \"Cloned service %s version %d to version %d\", fastly.ToValue(ver.ServiceID), c.Input.ServiceVersion, fastly.ToValue(ver.Number))\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/version/deactivate.go",
    "content": "package version\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DeactivateCommand calls the Fastly API to deactivate a service version.\ntype DeactivateCommand struct {\n\targparser.Base\n\tInput          fastly.DeactivateVersionInput\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n}\n\n// NewDeactivateCommand returns a usable command registered under the parent.\nfunc NewDeactivateCommand(parent argparser.Registerer, g *global.Data) *DeactivateCommand {\n\tvar c DeactivateCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(\"deactivate\", \"Deactivate a Fastly service version\")\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DeactivateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(true),\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": errors.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tc.Input.ServiceID = serviceID\n\tc.Input.ServiceVersion = fastly.ToValue(serviceVersion.Number)\n\n\tver, err := c.Globals.APIClient.DeactivateVersion(context.TODO(), &c.Input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fastly.ToValue(serviceVersion.Number),\n\t\t})\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Deactivated service %s version %d\", fastly.ToValue(ver.ServiceID), c.Input.ServiceVersion)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/version/doc.go",
    "content": "// Package version contains commands to inspect and manipulate Fastly\n// service versions.\npackage version\n"
  },
  {
    "path": "pkg/commands/service/version/list.go",
    "content": "package version\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"time\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n\tfsttime \"github.com/fastly/cli/pkg/time\"\n)\n\n// ListCommand calls the Fastly API to list services.\ntype ListCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tInput       fastly.ListVersionsInput\n\tserviceName argparser.OptionalServiceNameID\n}\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tc := ListCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"list\", \"List Fastly service versions\")\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *ListCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tserviceID, source, flag, err := argparser.ServiceID(c.serviceName, *c.Globals.Manifest, c.Globals.APIClient, c.Globals.ErrLog)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif c.Globals.Verbose() {\n\t\targparser.DisplayServiceID(serviceID, flag, source, out)\n\t}\n\n\tc.Input.ServiceID = serviceID\n\n\to, err := c.Globals.APIClient.ListVersions(context.TODO(), &c.Input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\": serviceID,\n\t\t})\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\tif !c.Globals.Verbose() {\n\t\ttw := text.NewTable(out)\n\t\ttw.AddHeader(\"NUMBER\", \"ACTIVE\", \"STAGED\", \"LAST EDITED (UTC)\")\n\t\tfor _, version := range o {\n\t\t\ttw.AddLine(\n\t\t\t\tfastly.ToValue(version.Number),\n\t\t\t\tfastly.ToValue(version.Active),\n\t\t\t\tfastly.ToValue(version.Staging),\n\t\t\t\tparseTime(version.UpdatedAt),\n\t\t\t)\n\t\t}\n\t\ttw.Print()\n\t\treturn nil\n\t}\n\n\tfmt.Fprintf(out, \"Versions: %d\\n\", len(o))\n\tfor i, version := range o {\n\t\tfmt.Fprintf(out, \"\\tVersion %d/%d\\n\", i+1, len(o))\n\t\ttext.PrintVersion(out, \"\\t\\t\", version)\n\t}\n\tfmt.Fprintln(out)\n\n\treturn nil\n}\n\nfunc parseTime(ua *time.Time) string {\n\tif ua == nil {\n\t\treturn \"\"\n\t}\n\treturn ua.UTC().Format(fsttime.Format)\n}\n"
  },
  {
    "path": "pkg/commands/service/version/lock.go",
    "content": "package version\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// LockCommand calls the Fastly API to lock a service version.\ntype LockCommand struct {\n\targparser.Base\n\tInput          fastly.LockVersionInput\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n}\n\n// NewLockCommand returns a usable command registered under the parent.\nfunc NewLockCommand(parent argparser.Registerer, g *global.Data) *LockCommand {\n\tvar c LockCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(\"lock\", \"Lock a Fastly service version\")\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *LockCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tLocked:             optional.Of(false),\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": errors.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tc.Input.ServiceID = serviceID\n\tc.Input.ServiceVersion = fastly.ToValue(serviceVersion.Number)\n\n\tver, err := c.Globals.APIClient.LockVersion(context.TODO(), &c.Input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fastly.ToValue(serviceVersion.Number),\n\t\t})\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Locked service %s version %d\", fastly.ToValue(ver.ServiceID), c.Input.ServiceVersion)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/version/root.go",
    "content": "package version\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"version\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manipulate Fastly service versions\")\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/service/version/serviceversion_test.go",
    "content": "package version_test\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\troot \"github.com/fastly/cli/pkg/commands/service\"\n\tsub \"github.com/fastly/cli/pkg/commands/service/version\"\n\t\"github.com/fastly/cli/pkg/mock\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n)\n\nfunc TestVersionClone(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --service-id flag\",\n\t\t\tArgs:      \"--version 1\",\n\t\t\tEnvVars:   map[string]string{\"FASTLY_SERVICE_ID\": \"\"},\n\t\t\tWantError: \"error reading service: no service ID found\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --version flag\",\n\t\t\tArgs:      \"--service-id 123\",\n\t\t\tWantError: \"error parsing arguments: required flag --version not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate successful clone\",\n\t\t\tArgs: \"--service-id 123 --version 1\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t},\n\t\t\tWantOutput: \"Cloned service 123 version 1 to version 4\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate successful clone json output\",\n\t\t\tArgs: \"--service-id 123 --version 1 --json\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t},\n\t\t\tWantOutput: cloneServiceVersionJSONOutput,\n\t\t},\n\t\t{\n\t\t\tName: \"validate error will be passed through if cloning fails\",\n\t\t\tArgs: \"--service-id 456 --version 1\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionError,\n\t\t\t},\n\t\t\tWantError: testutil.Err.Error(),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"clone\"}, scenarios)\n}\n\nfunc TestVersionList(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:       \"--service-id 123\",\n\t\t\tAPI:        &mock.API{ListVersionsFn: testutil.ListVersions},\n\t\t\tWantOutput: listVersionsShortOutput,\n\t\t},\n\t\t{\n\t\t\tArgs:       \"--service-id 123 --verbose\",\n\t\t\tAPI:        &mock.API{ListVersionsFn: testutil.ListVersions},\n\t\t\tWantOutput: listVersionsVerboseOutput,\n\t\t},\n\t\t{\n\t\t\tArgs:       \"--service-id 123 -v\",\n\t\t\tAPI:        &mock.API{ListVersionsFn: testutil.ListVersions},\n\t\t\tWantOutput: listVersionsVerboseOutput,\n\t\t},\n\t\t{\n\t\t\tArgs:       \"--verbose --service-id 123\",\n\t\t\tAPI:        &mock.API{ListVersionsFn: testutil.ListVersions},\n\t\t\tWantOutput: listVersionsVerboseOutput,\n\t\t},\n\t\t{\n\t\t\tArgs:       \"-v --service-id 123\",\n\t\t\tAPI:        &mock.API{ListVersionsFn: testutil.ListVersions},\n\t\t\tWantOutput: listVersionsVerboseOutput,\n\t\t},\n\t\t{\n\t\t\tArgs:      \"--service-id 123\",\n\t\t\tAPI:       &mock.API{ListVersionsFn: testutil.ListVersionsError},\n\t\t\tWantError: testutil.Err.Error(),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"list\"}, scenarios)\n}\n\nfunc TestVersionUpdate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --comment foo --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:    testutil.GetVersion,\n\t\t\t\tCloneVersionFn:  testutil.CloneVersionResult(4),\n\t\t\t\tUpdateVersionFn: updateVersionOK,\n\t\t\t},\n\t\t\tWantOutput: \"Updated service 123 version 4\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t},\n\t\t\tWantError: \"error parsing arguments: required flag --comment not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --comment foo --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:    testutil.GetVersion,\n\t\t\t\tCloneVersionFn:  testutil.CloneVersionResult(4),\n\t\t\t\tUpdateVersionFn: updateVersionError,\n\t\t\t},\n\t\t\tWantError: testutil.Err.Error(),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"update\"}, scenarios)\n}\n\nfunc TestVersionActivate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"--service-id 123\",\n\t\t\tWantError: \"error parsing arguments: required flag --version not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:      testutil.GetVersion,\n\t\t\t\tCloneVersionFn:    testutil.CloneVersionResult(4),\n\t\t\t\tActivateVersionFn: activateVersionError,\n\t\t\t},\n\t\t\tWantError: testutil.Err.Error(),\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1 --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:      testutil.GetVersion,\n\t\t\t\tCloneVersionFn:    testutil.CloneVersionResult(4),\n\t\t\t\tActivateVersionFn: activateVersionOK,\n\t\t\t},\n\t\t\tWantOutput: \"Activated service 123 version 4\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 2 --autoclone\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:      testutil.GetVersion,\n\t\t\t\tCloneVersionFn:    testutil.CloneVersionResult(4),\n\t\t\t\tActivateVersionFn: activateVersionOK,\n\t\t\t},\n\t\t\tWantOutput: \"Activated service 123 version 4\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API error when modifying active version\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tActivateVersionFn: func(_ context.Context, i *fastly.ActivateVersionInput) (*fastly.Version, error) {\n\t\t\t\t\treturn nil, fmt.Errorf(\"Cannot activate version %d. Versions that have been activated cannot be activated\", i.ServiceVersion)\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:      \"--service-id 123 --version 3\",\n\t\t\tWantError: \"Cannot activate version 3. Versions that have been activated cannot be activated\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate API error when modifying locked version\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn: testutil.GetVersion,\n\t\t\t\tActivateVersionFn: func(_ context.Context, i *fastly.ActivateVersionInput) (*fastly.Version, error) {\n\t\t\t\t\treturn nil, fmt.Errorf(\"Cannot activate version %d. Versions that have been locked cannot be activated\", i.ServiceVersion)\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:      \"--service-id 123 --version 3\",\n\t\t\tWantError: \"Cannot activate version 3. Versions that have been locked cannot be activated\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 3\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:      testutil.GetVersion,\n\t\t\t\tActivateVersionFn: activateVersionOK,\n\t\t\t},\n\t\t\tWantOutput: \"Activated service 123 version 3\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate --autoclone results in cloned service version\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:      testutil.GetVersion,\n\t\t\t\tCloneVersionFn:    testutil.CloneVersionResult(4),\n\t\t\t\tActivateVersionFn: activateVersionOK,\n\t\t\t},\n\t\t\tArgs:       \"--service-id 123 --version 3 --autoclone\",\n\t\t\tWantOutput: \"Activated service 123 version 4\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate --autoclone on locked version\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tActivateVersionFn: func(_ context.Context, i *fastly.ActivateVersionInput) (*fastly.Version, error) {\n\t\t\t\t\tif i.ServiceVersion != 4 {\n\t\t\t\t\t\treturn nil, fmt.Errorf(\"expected activation on cloned version 4, got %d\", i.ServiceVersion)\n\t\t\t\t\t}\n\t\t\t\t\treturn &fastly.Version{\n\t\t\t\t\t\tNumber:    fastly.ToPointer(i.ServiceVersion),\n\t\t\t\t\t\tServiceID: fastly.ToPointer(\"123\"),\n\t\t\t\t\t\tActive:    fastly.ToPointer(true),\n\t\t\t\t\t\tDeployed:  fastly.ToPointer(true),\n\t\t\t\t\t\tCreatedAt: testutil.MustParseTimeRFC3339(\"2010-11-15T19:01:02Z\"),\n\t\t\t\t\t\tUpdatedAt: testutil.MustParseTimeRFC3339(\"2010-11-15T19:01:02Z\"),\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:       \"--service-id 123 --version 3 --autoclone\",\n\t\t\tWantOutput: \"Activated service 123 version 4\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate --autoclone on editable version\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:   testutil.GetVersion,\n\t\t\t\tCloneVersionFn: testutil.CloneVersionResult(4),\n\t\t\t\tActivateVersionFn: func(_ context.Context, i *fastly.ActivateVersionInput) (*fastly.Version, error) {\n\t\t\t\t\tif i.ServiceVersion != 4 {\n\t\t\t\t\t\treturn nil, fmt.Errorf(\"expected activation on cloned version 4, got %d\", i.ServiceVersion)\n\t\t\t\t\t}\n\t\t\t\t\treturn &fastly.Version{\n\t\t\t\t\t\tNumber:    fastly.ToPointer(i.ServiceVersion),\n\t\t\t\t\t\tServiceID: fastly.ToPointer(\"123\"),\n\t\t\t\t\t\tActive:    fastly.ToPointer(true),\n\t\t\t\t\t\tDeployed:  fastly.ToPointer(true),\n\t\t\t\t\t\tCreatedAt: testutil.MustParseTimeRFC3339(\"2010-11-15T19:01:02Z\"),\n\t\t\t\t\t\tUpdatedAt: testutil.MustParseTimeRFC3339(\"2010-11-15T19:01:02Z\"),\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:       \"--service-id 123 --version 3 --autoclone\",\n\t\t\tWantOutput: \"Activated service 123 version 4\",\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"activate\"}, scenarios)\n}\n\nfunc TestVersionDeactivate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"--service-id 123\",\n\t\t\tEnvVars:   map[string]string{\"FASTLY_SERVICE_ID\": \"\"},\n\t\t\tWantError: \"error parsing arguments: required flag --version not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:        testutil.GetVersion,\n\t\t\t\tDeactivateVersionFn: deactivateVersionOK,\n\t\t\t},\n\t\t\tWantOutput: \"Deactivated service 123 version 1\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 3\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:        testutil.GetVersion,\n\t\t\t\tDeactivateVersionFn: deactivateVersionOK,\n\t\t\t},\n\t\t\tWantError: \"service version 3 is not active\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:        testutil.GetVersion,\n\t\t\t\tDeactivateVersionFn: deactivateVersionError,\n\t\t\t},\n\t\t\tWantError: testutil.Err.Error(),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"deactivate\"}, scenarios)\n}\n\nfunc TestVersionLock(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"--service-id 123\",\n\t\t\tEnvVars:   map[string]string{\"FASTLY_SERVICE_ID\": \"\"},\n\t\t\tWantError: \"error parsing arguments: required flag --version not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:  testutil.GetVersion,\n\t\t\t\tLockVersionFn: lockVersionOK,\n\t\t\t},\n\t\t\tWantOutput: \"Locked service 123 version 1\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:  testutil.GetVersion,\n\t\t\t\tLockVersionFn: lockVersionError,\n\t\t\t},\n\t\t\tWantError: testutil.Err.Error(),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"lock\"}, scenarios)\n}\n\nfunc TestVersionStage(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"--service-id 123\",\n\t\t\tEnvVars:   map[string]string{\"FASTLY_SERVICE_ID\": \"\"},\n\t\t\tWantError: \"error parsing arguments: required flag --version not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 3\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:      testutil.GetVersion,\n\t\t\t\tActivateVersionFn: stageVersionError,\n\t\t\t},\n\t\t\tWantError: testutil.Err.Error(),\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 3\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:      testutil.GetVersion,\n\t\t\t\tActivateVersionFn: stageVersionOK,\n\t\t\t},\n\t\t\tWantOutput: \"Staged service 123 version 3\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 4\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:      testutil.GetVersion,\n\t\t\t\tActivateVersionFn: stageVersionOK,\n\t\t\t},\n\t\t\tWantOutput: \"Staged service 123 version 4\",\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"stage\"}, scenarios)\n}\n\nfunc TestVersionUnstage(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"--service-id 123\",\n\t\t\tEnvVars:   map[string]string{\"FASTLY_SERVICE_ID\": \"\"},\n\t\t\tWantError: \"error parsing arguments: required flag --version not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 1\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:        testutil.GetVersion,\n\t\t\t\tDeactivateVersionFn: unstageVersionOK,\n\t\t\t},\n\t\t\tWantError: \"service version 1 is not staged\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 3\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:        testutil.GetVersion,\n\t\t\t\tDeactivateVersionFn: unstageVersionError,\n\t\t\t},\n\t\t\tWantError: \"service version 3 is not staged\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 4\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:        testutil.GetVersion,\n\t\t\t\tDeactivateVersionFn: unstageVersionError,\n\t\t\t},\n\t\t\tWantError: testutil.Err.Error(),\n\t\t},\n\t\t{\n\t\t\tArgs: \"--service-id 123 --version 4\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:        testutil.GetVersion,\n\t\t\t\tDeactivateVersionFn: unstageVersionOK,\n\t\t\t},\n\t\t\tWantOutput: \"Unstaged service 123 version 4\",\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"unstage\"}, scenarios)\n}\n\nvar cloneServiceVersionJSONOutput = strings.TrimSpace(`\n{\n  \"Active\": null,\n  \"Comment\": null,\n  \"CreatedAt\": null,\n  \"DeletedAt\": null,\n  \"Deployed\": null,\n  \"Locked\": null,\n  \"Number\": 4,\n  \"ServiceID\": \"123\",\n  \"Staging\": null,\n  \"Testing\": null,\n  \"UpdatedAt\": null,\n  \"Environments\": null\n}\n`) + \"\\n\"\n\nvar listVersionsShortOutput = strings.TrimSpace(`\nNUMBER  ACTIVE  STAGED  LAST EDITED (UTC)\n4       false   true    2000-01-04 01:00\n3       false   false   2000-01-03 01:00\n2       false   false   2000-01-02 01:00\n1       true    false   2000-01-01 01:00\n`) + \"\\n\"\n\nvar listVersionsVerboseOutput = strings.TrimSpace(`\nFastly API endpoint: https://api.fastly.com\nFastly API token provided via config file (auth: user)\n\nService ID (via --service-id): 123\n\nVersions: 4\n\tVersion 1/4\n\t\tNumber: 4\n\t\tService ID: 123\n\t\tStaged: true\n\t\tLast edited (UTC): 2000-01-04 01:00\n\tVersion 2/4\n\t\tNumber: 3\n\t\tService ID: 123\n\t\tLast edited (UTC): 2000-01-03 01:00\n\tVersion 3/4\n\t\tNumber: 2\n\t\tService ID: 123\n\t\tLocked: true\n\t\tLast edited (UTC): 2000-01-02 01:00\n\tVersion 4/4\n\t\tNumber: 1\n\t\tService ID: 123\n\t\tActive: true\n\t\tLast edited (UTC): 2000-01-01 01:00\n`) + \"\\n\\n\"\n\nfunc updateVersionOK(_ context.Context, i *fastly.UpdateVersionInput) (*fastly.Version, error) {\n\treturn &fastly.Version{\n\t\tNumber:    fastly.ToPointer(i.ServiceVersion),\n\t\tServiceID: fastly.ToPointer(\"123\"),\n\t\tActive:    fastly.ToPointer(true),\n\t\tDeployed:  fastly.ToPointer(true),\n\t\tComment:   fastly.ToPointer(\"foo\"),\n\t\tCreatedAt: testutil.MustParseTimeRFC3339(\"2010-11-15T19:01:02Z\"),\n\t}, nil\n}\n\nfunc updateVersionError(_ context.Context, _ *fastly.UpdateVersionInput) (*fastly.Version, error) {\n\treturn nil, testutil.Err\n}\n\nfunc activateVersionOK(_ context.Context, i *fastly.ActivateVersionInput) (*fastly.Version, error) {\n\treturn &fastly.Version{\n\t\tNumber:    fastly.ToPointer(i.ServiceVersion),\n\t\tServiceID: fastly.ToPointer(\"123\"),\n\t\tActive:    fastly.ToPointer(true),\n\t\tDeployed:  fastly.ToPointer(true),\n\t\tCreatedAt: testutil.MustParseTimeRFC3339(\"2010-11-15T19:01:02Z\"),\n\t\tUpdatedAt: testutil.MustParseTimeRFC3339(\"2010-11-15T19:01:02Z\"),\n\t}, nil\n}\n\nfunc activateVersionError(_ context.Context, _ *fastly.ActivateVersionInput) (*fastly.Version, error) {\n\treturn nil, testutil.Err\n}\n\nfunc deactivateVersionOK(_ context.Context, i *fastly.DeactivateVersionInput) (*fastly.Version, error) {\n\treturn &fastly.Version{\n\t\tNumber:    fastly.ToPointer(i.ServiceVersion),\n\t\tServiceID: fastly.ToPointer(\"123\"),\n\t\tActive:    fastly.ToPointer(false),\n\t\tDeployed:  fastly.ToPointer(true),\n\t\tCreatedAt: testutil.MustParseTimeRFC3339(\"2010-11-15T19:01:02Z\"),\n\t\tUpdatedAt: testutil.MustParseTimeRFC3339(\"2010-11-15T19:01:02Z\"),\n\t}, nil\n}\n\nfunc deactivateVersionError(_ context.Context, _ *fastly.DeactivateVersionInput) (*fastly.Version, error) {\n\treturn nil, testutil.Err\n}\n\nfunc stageVersionOK(_ context.Context, i *fastly.ActivateVersionInput) (*fastly.Version, error) {\n\treturn &fastly.Version{\n\t\tNumber:    fastly.ToPointer(i.ServiceVersion),\n\t\tServiceID: fastly.ToPointer(\"123\"),\n\t\tActive:    fastly.ToPointer(true),\n\t\tDeployed:  fastly.ToPointer(true),\n\t\tStaging:   fastly.ToPointer(true),\n\t\tCreatedAt: testutil.MustParseTimeRFC3339(\"2010-11-15T19:01:02Z\"),\n\t\tUpdatedAt: testutil.MustParseTimeRFC3339(\"2010-11-15T19:01:02Z\"),\n\t}, nil\n}\n\nfunc stageVersionError(_ context.Context, _ *fastly.ActivateVersionInput) (*fastly.Version, error) {\n\treturn nil, testutil.Err\n}\n\nfunc unstageVersionOK(_ context.Context, i *fastly.DeactivateVersionInput) (*fastly.Version, error) {\n\treturn &fastly.Version{\n\t\tNumber:    fastly.ToPointer(i.ServiceVersion),\n\t\tServiceID: fastly.ToPointer(\"123\"),\n\t\tActive:    fastly.ToPointer(false),\n\t\tDeployed:  fastly.ToPointer(true),\n\t\tStaging:   fastly.ToPointer(false),\n\t\tCreatedAt: testutil.MustParseTimeRFC3339(\"2010-11-15T19:01:02Z\"),\n\t\tUpdatedAt: testutil.MustParseTimeRFC3339(\"2010-11-15T19:01:02Z\"),\n\t}, nil\n}\n\nfunc unstageVersionError(_ context.Context, _ *fastly.DeactivateVersionInput) (*fastly.Version, error) {\n\treturn nil, testutil.Err\n}\n\nfunc lockVersionOK(_ context.Context, i *fastly.LockVersionInput) (*fastly.Version, error) {\n\treturn &fastly.Version{\n\t\tNumber:    fastly.ToPointer(i.ServiceVersion),\n\t\tServiceID: fastly.ToPointer(\"123\"),\n\t\tActive:    fastly.ToPointer(false),\n\t\tDeployed:  fastly.ToPointer(true),\n\t\tLocked:    fastly.ToPointer(true),\n\t\tCreatedAt: testutil.MustParseTimeRFC3339(\"2010-11-15T19:01:02Z\"),\n\t\tUpdatedAt: testutil.MustParseTimeRFC3339(\"2010-11-15T19:01:02Z\"),\n\t}, nil\n}\n\nfunc lockVersionError(_ context.Context, _ *fastly.LockVersionInput) (*fastly.Version, error) {\n\treturn nil, testutil.Err\n}\n\nfunc TestVersionValidate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --service-id flag\",\n\t\t\tArgs:      \"--version 1\",\n\t\t\tWantError: \"error parsing arguments: required flag --service-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --version flag\",\n\t\t\tArgs:      \"--service-id 123\",\n\t\t\tWantError: \"error parsing arguments: required flag --version not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate successful - valid version without message\",\n\t\t\tArgs: \"--service-id 123 --version 1\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:      testutil.GetVersion,\n\t\t\t\tValidateVersionFn: validateVersionValid(\"\"),\n\t\t\t},\n\t\t\tWantOutput: \"Service 123 version 1 is valid\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate successful - valid version with message\",\n\t\t\tArgs: \"--service-id 123 --version 1\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:      testutil.GetVersion,\n\t\t\t\tValidateVersionFn: validateVersionValid(\"All checks passed\"),\n\t\t\t},\n\t\t\tWantOutput: \"Service 123 version 1 is valid: All checks passed\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate successful - invalid version without message\",\n\t\t\tArgs: \"--service-id 123 --version 2\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:      testutil.GetVersion,\n\t\t\t\tValidateVersionFn: validateVersionInvalid(\"\"),\n\t\t\t},\n\t\t\tWantOutput: \"Service 123 version 2 is not valid\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate successful - invalid version with message\",\n\t\t\tArgs: \"--service-id 123 --version 2\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:      testutil.GetVersion,\n\t\t\t\tValidateVersionFn: validateVersionInvalid(\"Missing required backend\"),\n\t\t\t},\n\t\t\tWantOutput: \"Service 123 version 2 is not valid: Missing required backend\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate with json output - valid version\",\n\t\t\tArgs: \"--service-id 123 --version 1 --json\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:      testutil.GetVersion,\n\t\t\t\tValidateVersionFn: validateVersionValid(\"All checks passed\"),\n\t\t\t},\n\t\t\tWantOutput: validateVersionValidJSONOutput,\n\t\t},\n\t\t{\n\t\t\tName: \"validate with json output - invalid version\",\n\t\t\tArgs: \"--service-id 123 --version 2 --json\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:      testutil.GetVersion,\n\t\t\t\tValidateVersionFn: validateVersionInvalid(\"Missing required backend\"),\n\t\t\t},\n\t\t\tWantOutput: validateVersionInvalidJSONOutput,\n\t\t},\n\t\t{\n\t\t\tName: \"validate error from API\",\n\t\t\tArgs: \"--service-id 123 --version 1\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetVersionFn:      testutil.GetVersion,\n\t\t\t\tValidateVersionFn: validateVersionError,\n\t\t\t},\n\t\t\tWantError: testutil.Err.Error(),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"validate\"}, scenarios)\n}\n\nfunc validateVersionValid(message string) func(context.Context, *fastly.ValidateVersionInput) (bool, string, error) {\n\treturn func(_ context.Context, _ *fastly.ValidateVersionInput) (bool, string, error) {\n\t\treturn true, message, nil\n\t}\n}\n\nfunc validateVersionInvalid(message string) func(context.Context, *fastly.ValidateVersionInput) (bool, string, error) {\n\treturn func(_ context.Context, _ *fastly.ValidateVersionInput) (bool, string, error) {\n\t\treturn false, message, nil\n\t}\n}\n\nfunc validateVersionError(_ context.Context, _ *fastly.ValidateVersionInput) (bool, string, error) {\n\treturn false, \"\", testutil.Err\n}\n\nvar validateVersionValidJSONOutput = strings.TrimSpace(`\n{\n  \"message\": \"All checks passed\",\n  \"valid\": true\n}\n`) + \"\\n\"\n\nvar validateVersionInvalidJSONOutput = strings.TrimSpace(`\n{\n  \"message\": \"Missing required backend\",\n  \"valid\": false\n}\n`) + \"\\n\"\n"
  },
  {
    "path": "pkg/commands/service/version/stage.go",
    "content": "package version\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// StageCommand calls the Fastly API to stage a service version.\ntype StageCommand struct {\n\targparser.Base\n\tInput          fastly.ActivateVersionInput\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n}\n\n// NewStageCommand returns a usable command registered under the parent.\nfunc NewStageCommand(parent argparser.Registerer, g *global.Data) *StageCommand {\n\tvar c StageCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(\"stage\", \"Stage a Fastly service version\")\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *StageCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tActive:             optional.Of(false),\n\t\tLocked:             optional.Of(false),\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": errors.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tc.Input.ServiceID = serviceID\n\tc.Input.ServiceVersion = fastly.ToValue(serviceVersion.Number)\n\tc.Input.Environment = \"staging\"\n\n\tver, err := c.Globals.APIClient.ActivateVersion(context.TODO(), &c.Input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": serviceVersion.Number,\n\t\t})\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Staged service %s version %d\", fastly.ToValue(ver.ServiceID), c.Input.ServiceVersion)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/version/unstage.go",
    "content": "package version\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"4d63.com/optional\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// UnstageCommand calls the Fastly API to unstage a service version.\ntype UnstageCommand struct {\n\targparser.Base\n\tInput          fastly.DeactivateVersionInput\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n}\n\n// NewUnstageCommand returns a usable command registered under the parent.\nfunc NewUnstageCommand(parent argparser.Registerer, g *global.Data) *UnstageCommand {\n\tvar c UnstageCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(\"unstage\", \"Unstage a Fastly service version\")\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *UnstageCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tStaging:            optional.Of(true),\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": errors.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tc.Input.ServiceID = serviceID\n\tc.Input.ServiceVersion = fastly.ToValue(serviceVersion.Number)\n\tc.Input.Environment = \"staging\"\n\n\tver, err := c.Globals.APIClient.DeactivateVersion(context.TODO(), &c.Input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fastly.ToValue(serviceVersion.Number),\n\t\t})\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Unstaged service %s version %d\", fastly.ToValue(ver.ServiceID), c.Input.ServiceVersion)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/version/update.go",
    "content": "package version\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// UpdateCommand calls the Fastly API to update a service version.\ntype UpdateCommand struct {\n\targparser.Base\n\tinput          fastly.UpdateVersionInput\n\tserviceName    argparser.OptionalServiceNameID\n\tserviceVersion argparser.OptionalServiceVersion\n\tautoClone      argparser.OptionalAutoClone\n\n\tcomment argparser.OptionalString\n}\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tc := UpdateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"update\", \"Update a Fastly service version\")\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\tc.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{\n\t\tAction: c.autoClone.Set,\n\t\tDst:    &c.autoClone.Value,\n\t})\n\n\t// TODO(integralist):\n\t// Make 'comment' field mandatory once we roll out a new release of Go-Fastly\n\t// which will hopefully have better/more correct consistency as far as which\n\t// fields are supposed to be optional and which should be 'required'.\n\t//\n\tc.CmdClause.Flag(\"comment\", \"Human-readable comment\").Action(c.comment.Set).StringVar(&c.comment.Value)\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *UpdateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tAutoCloneFlag:      c.autoClone,\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceNameFlag:    c.serviceName,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": errors.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tc.input.ServiceID = serviceID\n\tc.input.ServiceVersion = fastly.ToValue(serviceVersion.Number)\n\tif !c.comment.WasSet {\n\t\treturn fmt.Errorf(\"error parsing arguments: required flag --comment not provided\")\n\t}\n\tc.input.Comment = &c.comment.Value\n\n\tver, err := c.Globals.APIClient.UpdateVersion(context.TODO(), &c.input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fastly.ToValue(serviceVersion.Number),\n\t\t\t\"Comment\":         c.comment.Value,\n\t\t})\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Updated service %s version %d\", fastly.ToValue(ver.ServiceID), c.input.ServiceVersion)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/service/version/validate.go",
    "content": "package version\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// ValidateCommand calls the Fastly API to validate a service version.\ntype ValidateCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tinput          fastly.ValidateVersionInput\n\tserviceVersion argparser.OptionalServiceVersion\n}\n\n// NewValidateCommand returns a usable command registered under the parent.\nfunc NewValidateCommand(parent argparser.Registerer, g *global.Data) *ValidateCommand {\n\tc := ValidateCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(\"validate\", \"Validate a service version\")\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t\tRequired:    true,\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagVersionName,\n\t\tDescription: argparser.FlagVersionDesc,\n\t\tDst:         &c.serviceVersion.Value,\n\t\tRequired:    true,\n\t})\n\treturn &c\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *ValidateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tserviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{\n\t\tAPIClient:          c.Globals.APIClient,\n\t\tManifest:           *c.Globals.Manifest,\n\t\tOut:                out,\n\t\tServiceVersionFlag: c.serviceVersion,\n\t\tVerboseMode:        c.Globals.Flags.Verbose,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\":      serviceID,\n\t\t\t\"Service Version\": fsterr.ServiceVersion(serviceVersion),\n\t\t})\n\t\treturn err\n\t}\n\n\tc.input.ServiceID = serviceID\n\tc.input.ServiceVersion = fastly.ToValue(serviceVersion.Number)\n\n\tvalid, msg, err := c.Globals.APIClient.ValidateVersion(context.TODO(), &c.input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\": serviceID,\n\t\t})\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, map[string]any{\n\t\t\"valid\":   valid,\n\t\t\"message\": msg,\n\t}); ok {\n\t\treturn err\n\t}\n\n\tif valid {\n\t\tif msg != \"\" {\n\t\t\ttext.Success(out, \"Service %s version %d is valid: %s\", serviceID, c.input.ServiceVersion, msg)\n\t\t} else {\n\t\t\ttext.Success(out, \"Service %s version %d is valid\", serviceID, c.input.ServiceVersion)\n\t\t}\n\t} else {\n\t\tif msg != \"\" {\n\t\t\ttext.Error(out, \"Service %s version %d is not valid: %s\", serviceID, c.input.ServiceVersion, msg)\n\t\t} else {\n\t\t\ttext.Error(out, \"Service %s version %d is not valid\", serviceID, c.input.ServiceVersion)\n\t\t}\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/shellcomplete/doc.go",
    "content": "// Package shellcomplete contains a hidden command used to prevent help output\n// when --completion-script-<T> is passed.\npackage shellcomplete\n"
  },
  {
    "path": "pkg/commands/shellcomplete/root.go",
    "content": "package shellcomplete\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"shellcomplete\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Hidden command used to prevent help output when using --completion-script-<T>\").Hidden()\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/sso/doc.go",
    "content": "// Package sso contains commands to authenticate with Fastly and to acquire a\n// temporary API token, which will be auto-rotated using an access/refresh token.\npackage sso\n"
  },
  {
    "path": "pkg/commands/sso/root.go",
    "content": "package sso\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tauthcmd \"github.com/fastly/cli/pkg/commands/auth\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// ForceReAuth indicates we want to force a re-auth of the user's session.\n// This variable is overridden by ../../app/run.go to force a re-auth.\nvar ForceReAuth = false\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\tprofile string\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"sso\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Single Sign-On authentication (deprecated: use 'fastly auth login --sso --token <name>' instead)\").Hidden()\n\tc.CmdClause.Arg(\"profile\", \"Profile to authenticate (i.e. create/update a token for)\").Short('p').StringVar(&c.profile)\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(in io.Reader, out io.Writer) error {\n\tif !c.Globals.Flags.Quiet {\n\t\ttext.Deprecated(\"This command will be removed in a future release. Use 'fastly auth login --sso --token <name>' instead.\\n\\n\")\n\t}\n\n\ttokenName, isFallback := c.resolveTokenName()\n\n\tif !isFallback && c.Globals.Config.GetAuthToken(tokenName) == nil {\n\t\treturn fsterr.RemediationError{\n\t\t\tInner:       fmt.Errorf(\"token %q does not exist\", tokenName),\n\t\t\tRemediation: \"Run 'fastly auth login --sso --token <name>' to create a new SSO token, or 'fastly auth add' to store an existing token.\",\n\t\t}\n\t}\n\n\tif err := authcmd.RunSSOWithTokenName(in, out, c.Globals, ForceReAuth, false, tokenName); err != nil {\n\t\treturn err\n\t}\n\n\tc.Globals.Config.Auth.Default = tokenName\n\tif err := c.Globals.Config.Write(c.Globals.ConfigPath); err != nil {\n\t\treturn fmt.Errorf(\"error saving config: %w\", err)\n\t}\n\treturn nil\n}\n\nfunc (c *RootCommand) resolveTokenName() (string, bool) {\n\tif c.Globals.Flags.Profile != \"\" {\n\t\treturn c.Globals.Flags.Profile, false\n\t}\n\tif c.Globals.Manifest != nil && c.Globals.Manifest.File.Profile != \"\" {\n\t\treturn c.Globals.Manifest.File.Profile, false\n\t}\n\tif c.profile != \"\" {\n\t\treturn c.profile, false\n\t}\n\tif name, _ := c.Globals.Config.GetDefaultAuthToken(); name != \"\" {\n\t\treturn name, false\n\t}\n\treturn \"default\", true\n}\n"
  },
  {
    "path": "pkg/commands/sso/sso_test.go",
    "content": "package sso_test\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/auth\"\n\t\"github.com/fastly/cli/pkg/config\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/mock\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n\t\"github.com/fastly/cli/pkg/threadsafe\"\n)\n\nfunc TestSSO(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t// 0. User cancels authentication prompt\n\t\t{\n\t\t\tArgs: \"sso\",\n\t\t\tStdin: []string{\n\t\t\t\t\"N\", // when prompted to open a web browser to start authentication\n\t\t\t},\n\t\t\tWantError: \"will not continue\",\n\t\t},\n\t\t// 1. Error opening web browser\n\t\t{\n\t\t\tArgs: \"sso\",\n\t\t\tStdin: []string{\n\t\t\t\t\"Y\", // when prompted to open a web browser to start authentication\n\t\t\t},\n\t\t\tSetup: func(_ *testing.T, _ *testutil.CLIScenario, opts *global.Data) {\n\t\t\t\topts.Opener = func(_ string) error {\n\t\t\t\t\treturn errors.New(\"failed to open web browser\")\n\t\t\t\t}\n\t\t\t},\n\t\t\tWantError: \"failed to open web browser\",\n\t\t},\n\t\t// 2. Error processing OAuth flow (error encountered)\n\t\t{\n\t\t\tArgs: \"sso\",\n\t\t\tStdin: []string{\n\t\t\t\t\"Y\", // when prompted to open a web browser to start authentication\n\t\t\t},\n\t\t\tSetup: func(_ *testing.T, _ *testutil.CLIScenario, opts *global.Data) {\n\t\t\t\tresult := make(chan auth.AuthorizationResult)\n\t\t\t\topts.AuthServer = testutil.MockAuthServer{\n\t\t\t\t\tResult: result,\n\t\t\t\t}\n\t\t\t\tgo func() {\n\t\t\t\t\tresult <- auth.AuthorizationResult{\n\t\t\t\t\t\tErr: errors.New(\"no authorization code returned\"),\n\t\t\t\t\t}\n\t\t\t\t}()\n\t\t\t},\n\t\t\tWantError: \"failed to authorize: no authorization code returned\",\n\t\t},\n\t\t// 3. Error processing OAuth flow (empty SessionToken field)\n\t\t{\n\t\t\tArgs: \"sso\",\n\t\t\tStdin: []string{\n\t\t\t\t\"Y\", // when prompted to open a web browser to start authentication\n\t\t\t},\n\t\t\tSetup: func(_ *testing.T, _ *testutil.CLIScenario, opts *global.Data) {\n\t\t\t\tresult := make(chan auth.AuthorizationResult)\n\t\t\t\topts.AuthServer = testutil.MockAuthServer{\n\t\t\t\t\tResult: result,\n\t\t\t\t}\n\t\t\t\tgo func() {\n\t\t\t\t\tresult <- auth.AuthorizationResult{\n\t\t\t\t\t\tSessionToken: \"\",\n\t\t\t\t\t}\n\t\t\t\t}()\n\t\t\t},\n\t\t\tWantError: \"failed to authorize: no session token\",\n\t\t},\n\t\t// 4. Success processing OAuth flow (uses default auth token name \"user\" from MockGlobalData)\n\t\t{\n\t\t\tArgs: \"sso\",\n\t\t\tStdin: []string{\n\t\t\t\t\"Y\", // when prompted to open a web browser to start authentication\n\t\t\t},\n\t\t\tSetup: func(_ *testing.T, _ *testutil.CLIScenario, opts *global.Data) {\n\t\t\t\tresult := make(chan auth.AuthorizationResult)\n\t\t\t\topts.AuthServer = testutil.MockAuthServer{\n\t\t\t\t\tResult: result,\n\t\t\t\t}\n\t\t\t\tgo func() {\n\t\t\t\t\tresult <- auth.AuthorizationResult{\n\t\t\t\t\t\tSessionToken: \"123\",\n\t\t\t\t\t}\n\t\t\t\t}()\n\t\t\t\topts.HTTPClient = testutil.CurrentCustomerClient(testutil.CurrentCustomerResponse)\n\t\t\t},\n\t\t\tWantOutputs: []string{\n\t\t\t\t\"We're going to authenticate the 'user' token\",\n\t\t\t\t\"We need to open your browser to authenticate you.\",\n\t\t\t\t\"Session token 'user' has been stored.\",\n\t\t\t},\n\t\t\tValidator: func(t *testing.T, _ *testutil.CLIScenario, opts *global.Data, _ *threadsafe.Buffer) {\n\t\t\t\tat := opts.Config.GetAuthToken(\"user\")\n\t\t\t\tif at == nil {\n\t\t\t\t\tt.Fatal(\"expected 'user' auth token to exist\")\n\t\t\t\t}\n\t\t\t\tif at.Token != \"123\" {\n\t\t\t\t\tt.Errorf(\"want token: 123, got token: %s\", at.Token)\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t// 5. Success processing OAuth flow while setting specific profile (test_user)\n\t\t{\n\t\t\tArgs: \"sso test_user\",\n\t\t\tConfigFile: &config.File{\n\t\t\t\tAuth: config.Auth{\n\t\t\t\t\tDefault: \"test_user\",\n\t\t\t\t\tTokens: config.AuthTokens{\n\t\t\t\t\t\t\"test_user\": &config.AuthToken{\n\t\t\t\t\t\t\tType:  config.AuthTokenTypeSSO,\n\t\t\t\t\t\t\tToken: \"mock-token\",\n\t\t\t\t\t\t\tEmail: \"test@example.com\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tStdin: []string{\n\t\t\t\t\"Y\", // when prompted to open a web browser to start authentication\n\t\t\t},\n\t\t\tSetup: func(_ *testing.T, _ *testutil.CLIScenario, opts *global.Data) {\n\t\t\t\tresult := make(chan auth.AuthorizationResult)\n\t\t\t\topts.AuthServer = testutil.MockAuthServer{\n\t\t\t\t\tResult: result,\n\t\t\t\t}\n\t\t\t\tgo func() {\n\t\t\t\t\tresult <- auth.AuthorizationResult{\n\t\t\t\t\t\tSessionToken: \"123\",\n\t\t\t\t\t}\n\t\t\t\t}()\n\t\t\t\topts.HTTPClient = testutil.CurrentCustomerClient(testutil.CurrentCustomerResponse)\n\t\t\t},\n\t\t\tWantOutputs: []string{\n\t\t\t\t\"We're going to authenticate the 'test_user' token\",\n\t\t\t\t\"We need to open your browser to authenticate you.\",\n\t\t\t\t\"Session token 'test_user' has been stored.\",\n\t\t\t},\n\t\t\tValidator: func(t *testing.T, _ *testutil.CLIScenario, opts *global.Data, _ *threadsafe.Buffer) {\n\t\t\t\tat := opts.Config.GetAuthToken(\"test_user\")\n\t\t\t\tif at == nil {\n\t\t\t\t\tt.Fatal(\"expected 'test_user' auth token to exist\")\n\t\t\t\t}\n\t\t\t\tif at.Token != \"123\" {\n\t\t\t\t\tt.Errorf(\"want token: 123, got token: %s\", at.Token)\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t// NOTE: The following tests indirectly validate our `app.Run()` logic.\n\t\t// Specifically the processing of the token before invoking the subcommand.\n\t\t// It allows us to check that the `sso` command is invoked when expected.\n\t\t//\n\t\t// 6. Success processing `pops` command.\n\t\t// We configure a non-SSO token so we can validate the INFO message.\n\t\t// Otherwise no OAuth flow is happening here.\n\t\t{\n\t\t\tArgs: \"pops\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tAllDatacentersFn: func(_ context.Context) ([]fastly.Datacenter, error) {\n\t\t\t\t\treturn []fastly.Datacenter{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName:   fastly.ToPointer(\"Foobar\"),\n\t\t\t\t\t\t\tCode:   fastly.ToPointer(\"FBR\"),\n\t\t\t\t\t\t\tGroup:  fastly.ToPointer(\"Bar\"),\n\t\t\t\t\t\t\tShield: fastly.ToPointer(\"Baz\"),\n\t\t\t\t\t\t\tCoordinates: &fastly.Coordinates{\n\t\t\t\t\t\t\t\tLatitude:  fastly.ToPointer(float64(1)),\n\t\t\t\t\t\t\t\tLongitude: fastly.ToPointer(float64(2)),\n\t\t\t\t\t\t\t\tX:         fastly.ToPointer(float64(3)),\n\t\t\t\t\t\t\t\tY:         fastly.ToPointer(float64(4)),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tConfigFile: &config.File{\n\t\t\t\tAuth: config.Auth{\n\t\t\t\t\tDefault: \"user\",\n\t\t\t\t\tTokens: config.AuthTokens{\n\t\t\t\t\t\t\"user\": &config.AuthToken{\n\t\t\t\t\t\t\tType:  config.AuthTokenTypeStatic,\n\t\t\t\t\t\t\tToken: \"mock-token\",\n\t\t\t\t\t\t\tEmail: \"test@example.com\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tSetup: func(_ *testing.T, _ *testutil.CLIScenario, opts *global.Data) {\n\t\t\t\topts.HTTPClient = testutil.CurrentCustomerClient(testutil.CurrentCustomerResponse)\n\t\t\t},\n\t\t\tWantOutputs: []string{\n\t\t\t\t\"{Latitude:1 Longitude:2 X:3 Y:4}\",\n\t\t\t},\n\t\t\tValidator: func(t *testing.T, _ *testutil.CLIScenario, opts *global.Data, _ *threadsafe.Buffer) {\n\t\t\t\tat := opts.Config.GetAuthToken(\"user\")\n\t\t\t\tif at == nil {\n\t\t\t\t\tt.Fatal(\"expected 'user' auth token to exist\")\n\t\t\t\t}\n\t\t\t\tif at.Token != \"mock-token\" {\n\t\t\t\t\tt.Errorf(\"want token: mock-token, got token: %s\", at.Token)\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t// 7. SSO token with both access and refresh expired.\n\t\t// The `whoami` command triggers re-auth via the processToken flow.\n\t\t// The user declines re-authentication.\n\t\t{\n\t\t\tArgs: \"whoami\",\n\t\t\tConfigFile: &config.File{\n\t\t\t\tAuth: config.Auth{\n\t\t\t\t\tDefault: \"user\",\n\t\t\t\t\tTokens: config.AuthTokens{\n\t\t\t\t\t\t\"user\": &config.AuthToken{\n\t\t\t\t\t\t\tType:             config.AuthTokenTypeSSO,\n\t\t\t\t\t\t\tToken:            \"mock-token\",\n\t\t\t\t\t\t\tEmail:            \"test@example.com\",\n\t\t\t\t\t\t\tRefreshToken:     \"mock-refresh\",\n\t\t\t\t\t\t\tAccessExpiresAt:  time.Now().Add(-600 * time.Second).Format(time.RFC3339),\n\t\t\t\t\t\t\tRefreshExpiresAt: time.Now().Add(-600 * time.Second).Format(time.RFC3339),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tStdin: []string{\n\t\t\t\t\"N\", // decline re-authentication\n\t\t\t},\n\t\t\tSetup: func(_ *testing.T, _ *testutil.CLIScenario, opts *global.Data) {\n\t\t\t\topts.HTTPClient = testutil.CurrentCustomerClient(testutil.CurrentCustomerResponse)\n\t\t\t},\n\t\t\tWantOutput:     \"Your auth token has expired and needs re-authentication\",\n\t\t\tDontWantOutput: \"{Latitude:1 Longitude:2 X:3 Y:4}\",\n\t\t},\n\t\t// 8. SSO token with both access and refresh expired.\n\t\t// The user accepts re-auth, and the `pops` command executes after.\n\t\t{\n\t\t\tArgs: \"pops\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tAllDatacentersFn: func(_ context.Context) ([]fastly.Datacenter, error) {\n\t\t\t\t\treturn []fastly.Datacenter{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName:   fastly.ToPointer(\"Foobar\"),\n\t\t\t\t\t\t\tCode:   fastly.ToPointer(\"FBR\"),\n\t\t\t\t\t\t\tGroup:  fastly.ToPointer(\"Bar\"),\n\t\t\t\t\t\t\tShield: fastly.ToPointer(\"Baz\"),\n\t\t\t\t\t\t\tCoordinates: &fastly.Coordinates{\n\t\t\t\t\t\t\t\tLatitude:  fastly.ToPointer(float64(1)),\n\t\t\t\t\t\t\t\tLongitude: fastly.ToPointer(float64(2)),\n\t\t\t\t\t\t\t\tX:         fastly.ToPointer(float64(3)),\n\t\t\t\t\t\t\t\tY:         fastly.ToPointer(float64(4)),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tConfigFile: &config.File{\n\t\t\t\tAuth: config.Auth{\n\t\t\t\t\tDefault: \"user\",\n\t\t\t\t\tTokens: config.AuthTokens{\n\t\t\t\t\t\t\"user\": &config.AuthToken{\n\t\t\t\t\t\t\tType:             config.AuthTokenTypeSSO,\n\t\t\t\t\t\t\tToken:            \"mock-token\",\n\t\t\t\t\t\t\tEmail:            \"test@example.com\",\n\t\t\t\t\t\t\tRefreshToken:     \"mock-refresh\",\n\t\t\t\t\t\t\tAccessExpiresAt:  time.Now().Add(-300 * time.Second).Format(time.RFC3339),\n\t\t\t\t\t\t\tRefreshExpiresAt: time.Now().Add(-300 * time.Second).Format(time.RFC3339),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tStdin: []string{\n\t\t\t\t\"Y\", // when prompted to open a web browser to start authentication\n\t\t\t},\n\t\t\tSetup: func(_ *testing.T, _ *testutil.CLIScenario, opts *global.Data) {\n\t\t\t\tresult := make(chan auth.AuthorizationResult)\n\t\t\t\topts.AuthServer = testutil.MockAuthServer{\n\t\t\t\t\tResult: result,\n\t\t\t\t}\n\t\t\t\tgo func() {\n\t\t\t\t\tresult <- auth.AuthorizationResult{\n\t\t\t\t\t\tSessionToken: \"123\",\n\t\t\t\t\t}\n\t\t\t\t}()\n\t\t\t\topts.HTTPClient = testutil.CurrentCustomerClient(testutil.CurrentCustomerResponse)\n\t\t\t},\n\t\t\tWantOutputs: []string{\n\t\t\t\t\"Your auth token has expired and needs re-authentication\",\n\t\t\t\t\"Starting a local server to handle the authentication flow.\",\n\t\t\t\t\"Session token 'user' has been stored.\",\n\t\t\t\t\"{Latitude:1 Longitude:2 X:3 Y:4}\",\n\t\t\t},\n\t\t\tValidator: func(t *testing.T, _ *testutil.CLIScenario, opts *global.Data, _ *threadsafe.Buffer) {\n\t\t\t\tat := opts.Config.GetAuthToken(\"user\")\n\t\t\t\tif at == nil {\n\t\t\t\t\tt.Fatal(\"expected 'user' auth token to exist\")\n\t\t\t\t}\n\t\t\t\tif at.Token != \"123\" {\n\t\t\t\t\tt.Errorf(\"want token: 123, got token: %s\", at.Token)\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t}\n\n\t// unlike the usual usage of this function, the \"command name\"\n\t// slice is empty here because the commands to be run are\n\t// embedded in the scenarios (some scenarios run different\n\t// commands)\n\ttestutil.RunCLIScenarios(t, []string{}, scenarios)\n}\n"
  },
  {
    "path": "pkg/commands/stats/aggregate.go",
    "content": "package stats\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// AggregateCommand exposes the Aggregate Stats API.\ntype AggregateCommand struct {\n\targparser.Base\n\n\tby         string\n\tformatFlag string\n\tfrom       string\n\tjsonFlag   bool\n\tregion     string\n\tto         string\n}\n\n// NewAggregateCommand is the \"stats aggregate\" subcommand.\nfunc NewAggregateCommand(parent argparser.Registerer, g *global.Data) *AggregateCommand {\n\tvar c AggregateCommand\n\tc.Globals = g\n\n\tc.CmdClause = parent.Command(\"aggregate\", \"View aggregated stats across all services\")\n\n\t// Optional.\n\tc.CmdClause.Flag(\"from\", \"Start time\").StringVar(&c.from)\n\tc.CmdClause.Flag(\"to\", \"End time\").StringVar(&c.to)\n\tc.CmdClause.Flag(\"by\", \"Aggregation period (minute/hour/day)\").EnumVar(&c.by, \"minute\", \"hour\", \"day\")\n\tc.CmdClause.Flag(\"region\", \"Filter by region ('stats regions' to list)\").StringVar(&c.region)\n\tc.CmdClause.Flag(\"format\", \"Output format (json)\").Hidden().EnumVar(&c.formatFlag, \"json\")\n\tc.CmdClause.Flag(\"json\", argparser.FlagJSONDesc).Short('j').BoolVar(&c.jsonFlag)\n\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *AggregateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif err := resolveJSONFormat(&c.formatFlag, c.jsonFlag, c.Globals); err != nil {\n\t\treturn err\n\t}\n\n\tinput := fastly.GetAggregateInput{}\n\tif c.by != \"\" {\n\t\tinput.By = &c.by\n\t}\n\tif c.from != \"\" {\n\t\tinput.From = &c.from\n\t}\n\tif c.region != \"\" {\n\t\tinput.Region = &c.region\n\t}\n\tif c.to != \"\" {\n\t\tinput.To = &c.to\n\t}\n\n\tvar envelope statsResponse\n\terr := c.Globals.APIClient.GetAggregateJSON(context.TODO(), &input, &envelope)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif envelope.Status != statusSuccess {\n\t\treturn fmt.Errorf(\"non-success response: %s\", envelope.Msg)\n\t}\n\n\tswitch c.formatFlag {\n\tcase \"json\":\n\t\tfor _, block := range envelope.Data {\n\t\t\tif err := json.NewEncoder(out).Encode(block); err != nil {\n\t\t\t\tc.Globals.ErrLog.Add(err)\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\tdefault:\n\t\twriteHeader(out, envelope.Meta)\n\t\tfor _, block := range envelope.Data {\n\t\t\tif err := fmtBlock(out, \"aggregate\", block); err != nil {\n\t\t\t\tc.Globals.ErrLog.Add(err)\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/stats/aggregate_test.go",
    "content": "package stats_test\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"io\"\n\t\"testing\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/app\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/mock\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n)\n\nfunc TestAggregate(t *testing.T) {\n\targs := testutil.SplitArgs\n\tscenarios := []struct {\n\t\tname       string\n\t\targs       []string\n\t\tapi        mock.API\n\t\twantError  string\n\t\twantOutput string\n\t}{\n\t\t{\n\t\t\tname:       \"success table\",\n\t\t\targs:       args(\"stats aggregate\"),\n\t\t\tapi:        mock.API{GetAggregateJSONFn: getAggregateJSONOK},\n\t\t\twantOutput: \"From:\",\n\t\t},\n\t\t{\n\t\t\tname:       \"success json\",\n\t\t\targs:       args(\"stats aggregate --format=json\"),\n\t\t\tapi:        mock.API{GetAggregateJSONFn: getAggregateJSONOK},\n\t\t\twantOutput: `\"start_time\":0`,\n\t\t},\n\t\t{\n\t\t\tname:       \"success json alias\",\n\t\t\targs:       args(\"stats aggregate --json\"),\n\t\t\tapi:        mock.API{GetAggregateJSONFn: getAggregateJSONOK},\n\t\t\twantOutput: `\"start_time\":0`,\n\t\t},\n\t\t{\n\t\t\tname:      \"verbose json combo\",\n\t\t\targs:      args(\"stats aggregate --json --verbose\"),\n\t\t\tapi:       mock.API{GetAggregateJSONFn: getAggregateJSONOK},\n\t\t\twantError: \"invalid flag combination\",\n\t\t},\n\t\t{\n\t\t\tname:      \"non-success status\",\n\t\t\targs:      args(\"stats aggregate\"),\n\t\t\tapi:       mock.API{GetAggregateJSONFn: getAggregateJSONNonSuccess},\n\t\t\twantError: \"non-success response\",\n\t\t},\n\t\t{\n\t\t\tname:      \"api error\",\n\t\t\targs:      args(\"stats aggregate\"),\n\t\t\tapi:       mock.API{GetAggregateJSONFn: getAggregateJSONError},\n\t\t\twantError: errTest.Error(),\n\t\t},\n\t}\n\tfor _, tc := range scenarios {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tvar stdout bytes.Buffer\n\t\t\tapp.Init = func(_ []string, _ io.Reader) (*global.Data, error) {\n\t\t\t\topts := testutil.MockGlobalData(tc.args, &stdout)\n\t\t\t\topts.APIClientFactory = mock.APIClient(tc.api)\n\t\t\t\treturn opts, nil\n\t\t\t}\n\t\t\terr := app.Run(tc.args, nil)\n\t\t\ttestutil.AssertErrorContains(t, err, tc.wantError)\n\t\t\ttestutil.AssertStringContains(t, stdout.String(), tc.wantOutput)\n\t\t})\n\t}\n}\n\nfunc getAggregateJSONOK(_ context.Context, _ *fastly.GetAggregateInput, o any) error {\n\tmsg := []byte(`{\n  \"status\": \"success\",\n  \"meta\": {\"to\": \"Thu May 16 20:08:35 UTC 2013\", \"from\": \"Wed May 15 20:08:35 UTC 2013\", \"by\": \"day\", \"region\": \"all\"},\n  \"msg\": null,\n  \"data\": [{\"start_time\": 0}]\n}`)\n\treturn json.Unmarshal(msg, o)\n}\n\nfunc getAggregateJSONNonSuccess(_ context.Context, _ *fastly.GetAggregateInput, o any) error {\n\tmsg := []byte(`{\"status\": \"error\", \"msg\": \"bad request\", \"meta\": {}, \"data\": []}`)\n\treturn json.Unmarshal(msg, o)\n}\n\nfunc getAggregateJSONError(_ context.Context, _ *fastly.GetAggregateInput, _ any) error {\n\treturn errTest\n}\n"
  },
  {
    "path": "pkg/commands/stats/doc.go",
    "content": "// Package stats contains commands to inspect Fastly statistic data.\npackage stats\n"
  },
  {
    "path": "pkg/commands/stats/domain_inspector.go",
    "content": "package stats\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// DomainInspectorCommand exposes the Domain Inspector API.\ntype DomainInspectorCommand struct {\n\targparser.Base\n\n\tcursor      string\n\tdatacenters []string\n\tdomains     []string\n\tdownsample  string\n\tformatFlag  string\n\tfrom        string\n\tgroupBy     []string\n\tjsonFlag    bool\n\tlimit       int\n\tmetrics     []string\n\tregions     []string\n\tserviceName argparser.OptionalServiceNameID\n\tto          string\n}\n\n// NewDomainInspectorCommand is the \"stats domain-inspector\" subcommand.\nfunc NewDomainInspectorCommand(parent argparser.Registerer, g *global.Data) *DomainInspectorCommand {\n\tvar c DomainInspectorCommand\n\tc.Globals = g\n\n\tc.CmdClause = parent.Command(\"domain-inspector\", \"View domain metrics for a Fastly service\")\n\n\t// Optional.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\n\tc.CmdClause.Flag(\"from\", \"Start time (RFC3339 or Unix timestamp)\").StringVar(&c.from)\n\tc.CmdClause.Flag(\"to\", \"End time (RFC3339 or Unix timestamp)\").StringVar(&c.to)\n\tc.CmdClause.Flag(\"downsample\", \"Sample window (minute/hour/day)\").EnumVar(&c.downsample, \"minute\", \"hour\", \"day\")\n\tc.CmdClause.Flag(\"metric\", \"Metrics to retrieve (repeatable, up to 10)\").StringsVar(&c.metrics)\n\tc.CmdClause.Flag(\"domain\", \"Filter by domain (repeatable)\").StringsVar(&c.domains)\n\tc.CmdClause.Flag(\"datacenter\", \"Filter by POP (repeatable)\").StringsVar(&c.datacenters)\n\tc.CmdClause.Flag(\"region\", \"Filter by region (repeatable)\").StringsVar(&c.regions)\n\tc.CmdClause.Flag(\"group-by\", \"Dimensions to group by (repeatable)\").StringsVar(&c.groupBy)\n\tc.CmdClause.Flag(\"limit\", \"Max entries to return\").IntVar(&c.limit)\n\tc.CmdClause.Flag(\"cursor\", \"Pagination cursor from a previous response\").StringVar(&c.cursor)\n\tc.CmdClause.Flag(\"format\", \"Output format (json)\").Hidden().EnumVar(&c.formatFlag, \"json\")\n\tc.CmdClause.Flag(\"json\", argparser.FlagJSONDesc).Short('j').BoolVar(&c.jsonFlag)\n\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *DomainInspectorCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif err := resolveJSONFormat(&c.formatFlag, c.jsonFlag, c.Globals); err != nil {\n\t\treturn err\n\t}\n\n\tserviceID, source, flag, err := argparser.ServiceID(c.serviceName, *c.Globals.Manifest, c.Globals.APIClient, c.Globals.ErrLog)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif c.Globals.Verbose() {\n\t\targparser.DisplayServiceID(serviceID, flag, source, out)\n\t}\n\n\tinput := fastly.GetDomainMetricsInput{\n\t\tServiceID:   serviceID,\n\t\tDatacenters: c.datacenters,\n\t\tDomains:     c.domains,\n\t\tGroupBy:     c.groupBy,\n\t\tMetrics:     c.metrics,\n\t\tRegions:     c.regions,\n\t}\n\tif c.cursor != \"\" {\n\t\tinput.Cursor = &c.cursor\n\t}\n\tif c.downsample != \"\" {\n\t\tinput.Downsample = &c.downsample\n\t}\n\tif c.from != \"\" {\n\t\tt, err := parseTime(c.from)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"invalid --from value: %w\", err)\n\t\t}\n\t\tinput.Start = &t\n\t}\n\tif c.to != \"\" {\n\t\tt, err := parseTime(c.to)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"invalid --to value: %w\", err)\n\t\t}\n\t\tinput.End = &t\n\t}\n\tif c.limit > 0 {\n\t\tinput.Limit = &c.limit\n\t}\n\n\tswitch c.formatFlag {\n\tcase \"json\":\n\t\tvar envelope struct {\n\t\t\tStatus *string `json:\"status\"`\n\t\t}\n\t\tvar raw json.RawMessage\n\t\tif err := c.Globals.APIClient.GetDomainMetricsForServiceJSON(context.TODO(), &input, &raw); err != nil {\n\t\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\"Service ID\": serviceID})\n\t\t\treturn err\n\t\t}\n\t\tif err := json.Unmarshal(raw, &envelope); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif fastly.ToValue(envelope.Status) != statusSuccess {\n\t\t\treturn fmt.Errorf(\"non-success response: %s\", fastly.ToValue(envelope.Status))\n\t\t}\n\t\t_, err := out.Write(raw)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tfmt.Fprintln(out)\n\t\treturn nil\n\n\tdefault:\n\t\tresp, err := c.Globals.APIClient.GetDomainMetricsForService(context.TODO(), &input)\n\t\tif err != nil {\n\t\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\"Service ID\": serviceID})\n\t\t\treturn err\n\t\t}\n\t\tif fastly.ToValue(resp.Status) != statusSuccess {\n\t\t\treturn fmt.Errorf(\"non-success response: %s\", fastly.ToValue(resp.Status))\n\t\t}\n\t\ttext.PrintDomainInspectorTbl(out, resp)\n\t\treturn nil\n\t}\n}\n\nfunc parseTime(s string) (time.Time, error) {\n\tif t, err := time.Parse(time.RFC3339, s); err == nil {\n\t\treturn t, nil\n\t}\n\tif epoch, err := strconv.ParseInt(s, 10, 64); err == nil {\n\t\treturn time.Unix(epoch, 0), nil\n\t}\n\treturn time.Time{}, fmt.Errorf(\"cannot parse %q as RFC3339 or Unix timestamp\", s)\n}\n"
  },
  {
    "path": "pkg/commands/stats/domain_inspector_test.go",
    "content": "package stats_test\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/app\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/mock\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n)\n\nfunc TestDomainInspector(t *testing.T) {\n\targs := testutil.SplitArgs\n\tscenarios := []struct {\n\t\tname       string\n\t\targs       []string\n\t\tapi        mock.API\n\t\twantError  string\n\t\twantOutput string\n\t}{\n\t\t{\n\t\t\tname: \"success table\",\n\t\t\targs: args(\"stats domain-inspector --service-id 123\"),\n\t\t\tapi: mock.API{\n\t\t\t\tGetDomainMetricsForServiceFn: getDomainMetricsOK,\n\t\t\t},\n\t\t\twantOutput: \"REQUESTS\",\n\t\t},\n\t\t{\n\t\t\tname: \"success json\",\n\t\t\targs: args(\"stats domain-inspector --service-id 123 --format=json\"),\n\t\t\tapi: mock.API{\n\t\t\t\tGetDomainMetricsForServiceJSONFn: getDomainMetricsJSONOK,\n\t\t\t},\n\t\t\twantOutput: \"status\",\n\t\t},\n\t\t{\n\t\t\tname: \"success json alias\",\n\t\t\targs: args(\"stats domain-inspector --service-id 123 --json\"),\n\t\t\tapi: mock.API{\n\t\t\t\tGetDomainMetricsForServiceJSONFn: getDomainMetricsJSONOK,\n\t\t\t},\n\t\t\twantOutput: \"status\",\n\t\t},\n\t\t{\n\t\t\tname:      \"verbose json combo\",\n\t\t\targs:      args(\"stats domain-inspector --service-id 123 --json --verbose\"),\n\t\t\tapi:       mock.API{},\n\t\t\twantError: \"invalid flag combination\",\n\t\t},\n\t\t{\n\t\t\tname: \"non-success status\",\n\t\t\targs: args(\"stats domain-inspector --service-id 123\"),\n\t\t\tapi: mock.API{\n\t\t\t\tGetDomainMetricsForServiceFn: getDomainMetricsNonSuccess,\n\t\t\t},\n\t\t\twantError: \"non-success response\",\n\t\t},\n\t\t{\n\t\t\tname: \"missing service ID\",\n\t\t\targs: args(\"stats domain-inspector\"),\n\t\t\tapi: mock.API{\n\t\t\t\tGetDomainMetricsForServiceFn: getDomainMetricsOK,\n\t\t\t},\n\t\t\twantError: \"error reading service\",\n\t\t},\n\t\t{\n\t\t\tname: \"non-success status json\",\n\t\t\targs: args(\"stats domain-inspector --service-id 123 --format=json\"),\n\t\t\tapi: mock.API{\n\t\t\t\tGetDomainMetricsForServiceJSONFn: getDomainMetricsJSONNonSuccess,\n\t\t\t},\n\t\t\twantError: \"non-success response\",\n\t\t},\n\t\t{\n\t\t\tname: \"api error\",\n\t\t\targs: args(\"stats domain-inspector --service-id 123\"),\n\t\t\tapi: mock.API{\n\t\t\t\tGetDomainMetricsForServiceFn: getDomainMetricsError,\n\t\t\t},\n\t\t\twantError: errTest.Error(),\n\t\t},\n\t\t{\n\t\t\tname: \"from RFC3339 maps to Start\",\n\t\t\targs: args(\"stats domain-inspector --service-id 123 --from 2024-01-15T10:00:00Z\"),\n\t\t\tapi: mock.API{\n\t\t\t\tGetDomainMetricsForServiceFn: getDomainMetricsAssertStart(time.Date(2024, 1, 15, 10, 0, 0, 0, time.UTC)),\n\t\t\t},\n\t\t\twantOutput: \"REQUESTS\",\n\t\t},\n\t\t{\n\t\t\tname: \"from Unix epoch maps to Start\",\n\t\t\targs: args(\"stats domain-inspector --service-id 123 --from 1705312800\"),\n\t\t\tapi: mock.API{\n\t\t\t\tGetDomainMetricsForServiceFn: getDomainMetricsAssertStart(time.Unix(1705312800, 0)),\n\t\t\t},\n\t\t\twantOutput: \"REQUESTS\",\n\t\t},\n\t\t{\n\t\t\tname: \"to RFC3339 maps to End\",\n\t\t\targs: args(\"stats domain-inspector --service-id 123 --to 2024-01-15T11:00:00Z\"),\n\t\t\tapi: mock.API{\n\t\t\t\tGetDomainMetricsForServiceFn: getDomainMetricsAssertEnd(time.Date(2024, 1, 15, 11, 0, 0, 0, time.UTC)),\n\t\t\t},\n\t\t\twantOutput: \"REQUESTS\",\n\t\t},\n\t\t{\n\t\t\tname: \"from invalid format error\",\n\t\t\targs: args(\"stats domain-inspector --service-id 123 --from not-a-time\"),\n\t\t\tapi: mock.API{\n\t\t\t\tGetDomainMetricsForServiceFn: getDomainMetricsOK,\n\t\t\t},\n\t\t\twantError: \"invalid --from value\",\n\t\t},\n\t\t{\n\t\t\tname: \"to invalid format error\",\n\t\t\targs: args(\"stats domain-inspector --service-id 123 --to not-a-time\"),\n\t\t\tapi: mock.API{\n\t\t\t\tGetDomainMetricsForServiceFn: getDomainMetricsOK,\n\t\t\t},\n\t\t\twantError: \"invalid --to value\",\n\t\t},\n\t}\n\tfor _, tc := range scenarios {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\t// Clear FASTLY_SERVICE_ID for tests that validate missing service ID\n\t\t\tif tc.name == \"missing service ID\" {\n\t\t\t\tt.Setenv(\"FASTLY_SERVICE_ID\", \"\")\n\t\t\t}\n\t\t\tvar stdout bytes.Buffer\n\t\t\tapp.Init = func(_ []string, _ io.Reader) (*global.Data, error) {\n\t\t\t\topts := testutil.MockGlobalData(tc.args, &stdout)\n\t\t\t\topts.APIClientFactory = mock.APIClient(tc.api)\n\t\t\t\treturn opts, nil\n\t\t\t}\n\t\t\terr := app.Run(tc.args, nil)\n\t\t\ttestutil.AssertErrorContains(t, err, tc.wantError)\n\t\t\ttestutil.AssertStringContains(t, stdout.String(), tc.wantOutput)\n\t\t})\n\t}\n}\n\nfunc domainMetricsOKResult() (*fastly.DomainInspector, error) {\n\treturn &fastly.DomainInspector{\n\t\tStatus: fastly.ToPointer(\"success\"),\n\t\tMeta: &fastly.DomainMeta{\n\t\t\tStart: fastly.ToPointer(\"2024-01-15T10:00:00Z\"),\n\t\t\tEnd:   fastly.ToPointer(\"2024-01-15T11:00:00Z\"),\n\t\t},\n\t\tData: []*fastly.DomainData{\n\t\t\t{\n\t\t\t\tValues: []*fastly.DomainMetrics{\n\t\t\t\t\t{\n\t\t\t\t\t\tRequests:  fastly.ToPointer(uint64(100)),\n\t\t\t\t\t\tBandwidth: fastly.ToPointer(uint64(5000)),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}, nil\n}\n\nfunc getDomainMetricsOK(_ context.Context, _ *fastly.GetDomainMetricsInput) (*fastly.DomainInspector, error) {\n\treturn domainMetricsOKResult()\n}\n\nfunc getDomainMetricsJSONOK(_ context.Context, _ *fastly.GetDomainMetricsInput, dst any) error {\n\tmsg := []byte(`{\"status\":\"success\",\"data\":[]}`)\n\treturn json.Unmarshal(msg, dst)\n}\n\nfunc getDomainMetricsJSONNonSuccess(_ context.Context, _ *fastly.GetDomainMetricsInput, dst any) error {\n\tmsg := []byte(`{\"status\":\"error\",\"data\":[]}`)\n\treturn json.Unmarshal(msg, dst)\n}\n\nfunc getDomainMetricsNonSuccess(_ context.Context, _ *fastly.GetDomainMetricsInput) (*fastly.DomainInspector, error) {\n\treturn &fastly.DomainInspector{\n\t\tStatus: fastly.ToPointer(\"error\"),\n\t}, nil\n}\n\nfunc getDomainMetricsAssertStart(want time.Time) func(context.Context, *fastly.GetDomainMetricsInput) (*fastly.DomainInspector, error) {\n\treturn func(_ context.Context, i *fastly.GetDomainMetricsInput) (*fastly.DomainInspector, error) {\n\t\tif i.Start == nil {\n\t\t\treturn nil, fmt.Errorf(\"expected Start to be set, got nil\")\n\t\t}\n\t\tif !i.Start.Equal(want) {\n\t\t\treturn nil, fmt.Errorf(\"expected Start %v, got %v\", want, *i.Start)\n\t\t}\n\t\treturn domainMetricsOKResult()\n\t}\n}\n\nfunc getDomainMetricsAssertEnd(want time.Time) func(context.Context, *fastly.GetDomainMetricsInput) (*fastly.DomainInspector, error) {\n\treturn func(_ context.Context, i *fastly.GetDomainMetricsInput) (*fastly.DomainInspector, error) {\n\t\tif i.End == nil {\n\t\t\treturn nil, fmt.Errorf(\"expected End to be set, got nil\")\n\t\t}\n\t\tif !i.End.Equal(want) {\n\t\t\treturn nil, fmt.Errorf(\"expected End %v, got %v\", want, *i.End)\n\t\t}\n\t\treturn domainMetricsOKResult()\n\t}\n}\n\nfunc getDomainMetricsError(_ context.Context, _ *fastly.GetDomainMetricsInput) (*fastly.DomainInspector, error) {\n\treturn nil, errTest\n}\n"
  },
  {
    "path": "pkg/commands/stats/historical.go",
    "content": "package stats\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\nconst statusSuccess = \"success\"\n\n// HistoricalCommand exposes the Historical Stats API.\ntype HistoricalCommand struct {\n\targparser.Base\n\n\tby          string\n\tfield       string\n\tformatFlag  string\n\tfrom        string\n\tjsonFlag    bool\n\tregion      string\n\tserviceName argparser.OptionalServiceNameID\n\tto          string\n}\n\n// NewHistoricalCommand is the \"stats historical\" subcommand.\nfunc NewHistoricalCommand(parent argparser.Registerer, g *global.Data) *HistoricalCommand {\n\tvar c HistoricalCommand\n\tc.Globals = g\n\n\tc.CmdClause = parent.Command(\"historical\", \"View historical stats for a Fastly service\")\n\n\t// Optional.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\n\tc.CmdClause.Flag(\"field\", \"Filter to a single stats field (e.g. bandwidth, requests)\").StringVar(&c.field)\n\tc.CmdClause.Flag(\"from\", \"From time, accepted formats at https://fastly.dev/reference/api/metrics-stats/historical-stats\").StringVar(&c.from)\n\tc.CmdClause.Flag(\"to\", \"To time\").StringVar(&c.to)\n\tc.CmdClause.Flag(\"by\", \"Aggregation period (minute/hour/day)\").EnumVar(&c.by, \"minute\", \"hour\", \"day\")\n\tc.CmdClause.Flag(\"region\", \"Filter by region ('stats regions' to list)\").StringVar(&c.region)\n\n\tc.CmdClause.Flag(\"format\", \"Output format (json)\").Hidden().EnumVar(&c.formatFlag, \"json\")\n\tc.CmdClause.Flag(\"json\", argparser.FlagJSONDesc).Short('j').BoolVar(&c.jsonFlag)\n\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *HistoricalCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif err := resolveJSONFormat(&c.formatFlag, c.jsonFlag, c.Globals); err != nil {\n\t\treturn err\n\t}\n\n\tserviceID, source, flag, err := argparser.ServiceID(c.serviceName, *c.Globals.Manifest, c.Globals.APIClient, c.Globals.ErrLog)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif c.Globals.Verbose() {\n\t\targparser.DisplayServiceID(serviceID, flag, source, out)\n\t}\n\n\tinput := fastly.GetStatsInput{\n\t\tService: fastly.ToPointer(serviceID),\n\t}\n\tif c.by != \"\" {\n\t\tinput.By = &c.by\n\t}\n\tif c.field != \"\" {\n\t\tinput.Field = &c.field\n\t}\n\tif c.from != \"\" {\n\t\tinput.From = &c.from\n\t}\n\tif c.region != \"\" {\n\t\tinput.Region = &c.region\n\t}\n\tif c.to != \"\" {\n\t\tinput.To = &c.to\n\t}\n\n\tvar envelope statsResponse\n\terr = c.Globals.APIClient.GetStatsJSON(context.TODO(), &input, &envelope)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Service ID\": serviceID,\n\t\t})\n\t\treturn err\n\t}\n\n\tif envelope.Status != statusSuccess {\n\t\treturn fmt.Errorf(\"non-success response: %s\", envelope.Msg)\n\t}\n\n\tswitch c.formatFlag {\n\tcase \"json\":\n\t\tif err := writeBlocksJSON(out, envelope.Data); err != nil {\n\t\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\t\"Service ID\": serviceID,\n\t\t\t})\n\t\t\treturn err\n\t\t}\n\n\tdefault:\n\t\twriteHeader(out, envelope.Meta)\n\t\tif c.field != \"\" {\n\t\t\tfor _, block := range envelope.Data {\n\t\t\t\tif err := fmtFieldLine(out, c.field, block); err != nil {\n\t\t\t\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\t\t\t\"Service ID\": serviceID,\n\t\t\t\t\t})\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\t\t} else if err := writeBlocks(out, serviceID, envelope.Data); err != nil {\n\t\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\t\"Service ID\": serviceID,\n\t\t\t})\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc writeHeader(out io.Writer, meta statsResponseMeta) {\n\tfmt.Fprintf(out, \"From: %s\\n\", meta.From)\n\tfmt.Fprintf(out, \"To: %s\\n\", meta.To)\n\tfmt.Fprintf(out, \"By: %s\\n\", meta.By)\n\tfmt.Fprintf(out, \"Region: %s\\n\", meta.Region)\n\tfmt.Fprintf(out, \"---\\n\")\n}\n\nfunc writeBlocks(out io.Writer, service string, blocks []statsResponseData) error {\n\tfor _, block := range blocks {\n\t\tif err := fmtBlock(out, service, block); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc writeBlocksJSON(out io.Writer, blocks []statsResponseData) error {\n\tfor _, block := range blocks {\n\t\tif err := json.NewEncoder(out).Encode(block); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/stats/historical_test.go",
    "content": "package stats_test\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"testing\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\troot \"github.com/fastly/cli/pkg/commands/stats\"\n\t\"github.com/fastly/cli/pkg/mock\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n)\n\nfunc TestHistorical(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:       \"success\",\n\t\t\tArgs:       \"--service-id=123\",\n\t\t\tAPI:        &mock.API{GetStatsJSONFn: getStatsJSONOK},\n\t\t\tWantOutput: historicalOK,\n\t\t},\n\t\t{\n\t\t\tName:      \"api failure\",\n\t\t\tArgs:      \"--service-id=123\",\n\t\t\tAPI:       &mock.API{GetStatsJSONFn: getStatsJSONError},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t\t{\n\t\t\tName:       \"success with json format\",\n\t\t\tArgs:       \"--service-id=123 --format=json\",\n\t\t\tAPI:        &mock.API{GetStatsJSONFn: getStatsJSONOK},\n\t\t\tWantOutput: historicalJSONOK,\n\t\t},\n\t\t{\n\t\t\tName:       \"success with json alias\",\n\t\t\tArgs:       \"--service-id=123 --json\",\n\t\t\tAPI:        &mock.API{GetStatsJSONFn: getStatsJSONOK},\n\t\t\tWantOutput: historicalJSONOK,\n\t\t},\n\t\t{\n\t\t\tName:      \"verbose json combo\",\n\t\t\tArgs:      \"--service-id=123 --json --verbose\",\n\t\t\tAPI:       &mock.API{GetStatsJSONFn: getStatsJSONOK},\n\t\t\tWantError: \"invalid flag combination\",\n\t\t},\n\t\t{\n\t\t\tName:            \"success with field filter\",\n\t\t\tArgs:            \"--service-id=123 --field=bandwidth\",\n\t\t\tAPI:             &mock.API{GetStatsJSONFn: getStatsJSONFieldOK},\n\t\t\tWantOutput:      \"bandwidth: 123\",\n\t\t\tDontWantOutputs: []string{\"Service ID:\", \"Hit Rate:\", \"Avg Hit Time:\"},\n\t\t},\n\t\t{\n\t\t\tName:       \"success with field filter and json format\",\n\t\t\tArgs:       \"--service-id=123 --field=bandwidth --format=json\",\n\t\t\tAPI:        &mock.API{GetStatsJSONFn: getStatsJSONFieldOK},\n\t\t\tWantOutput: `\"bandwidth\":123`,\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, \"historical\"}, scenarios)\n}\n\nvar historicalOK = `From: Wed May 15 20:08:35 UTC 2013\nTo: Thu May 16 20:08:35 UTC 2013\nBy: day\nRegion: all\n---\nService ID:                                    123\nStart Time:          1970-01-01 00:00:00 +0000 UTC\n--------------------------------------------------\nHit Rate:                                    0.00%\nAvg Hit Time:                               0.00µs\nAvg Miss Time:                              0.00µs\n\nRequest BW:                                      0\n  Headers:                                       0\n  Body:                                          0\n\nResponse BW:                                     0\n  Headers:                                       0\n  Body:                                          0\n\nRequests:                                        0\n  Hit:                                           0\n  Miss:                                          0\n  Pass:                                          0\n  Synth:                                         0\n  Error:                                         0\n  Uncacheable:                                   0\n`\n\nvar historicalJSONOK = `{\"start_time\":0}\n`\n\nfunc unmarshalStatsJSON(o any) error {\n\tmsg := []byte(`\n{\n  \"status\": \"success\",\n  \"meta\": {\n    \"to\": \"Thu May 16 20:08:35 UTC 2013\",\n    \"from\": \"Wed May 15 20:08:35 UTC 2013\",\n    \"by\": \"day\",\n    \"region\": \"all\"\n  },\n  \"msg\": null,\n  \"data\": [{\"start_time\": 0}]\n}`)\n\n\treturn json.Unmarshal(msg, o)\n}\n\nfunc getStatsJSONOK(_ context.Context, _ *fastly.GetStatsInput, o any) error {\n\treturn unmarshalStatsJSON(o)\n}\n\nfunc unmarshalStatsFieldJSON(o any) error {\n\tmsg := []byte(`\n{\n  \"status\": \"success\",\n  \"meta\": {\n    \"to\": \"Thu May 16 20:08:35 UTC 2013\",\n    \"from\": \"Wed May 15 20:08:35 UTC 2013\",\n    \"by\": \"day\",\n    \"region\": \"all\"\n  },\n  \"msg\": null,\n  \"data\": [{\"start_time\": 0, \"bandwidth\": 123}]\n}`)\n\treturn json.Unmarshal(msg, o)\n}\n\nfunc getStatsJSONFieldOK(_ context.Context, i *fastly.GetStatsInput, o any) error {\n\tif i.Field == nil || *i.Field != \"bandwidth\" {\n\t\treturn errTest\n\t}\n\treturn unmarshalStatsFieldJSON(o)\n}\n\nfunc getStatsJSONError(_ context.Context, _ *fastly.GetStatsInput, _ any) error {\n\treturn errTest\n}\n"
  },
  {
    "path": "pkg/commands/stats/obj.go",
    "content": "package stats\n\n// The structs in this file are similar to those in go-fastly, but\n// intended for json use rather than mapstructure.\n\ntype statsResponse struct {\n\tStatus string            `json:\"status\"`\n\tMsg    string            `json:\"msg\"`\n\tMeta   statsResponseMeta `json:\"meta\"`\n\n\tData []statsResponseData `json:\"data\"`\n}\n\ntype statsResponseMeta struct {\n\tFrom   string `json:\"from\"`\n\tTo     string `json:\"to\"`\n\tBy     string `json:\"by\"`\n\tRegion string `json:\"region\"`\n}\n\ntype statsResponseData map[string]any\n\ntype realtimeResponse struct {\n\tTimestamp uint64                 `json:\"timestamp\"`\n\tData      []realtimeResponseData `json:\"data\"`\n}\n\ntype realtimeResponseData struct {\n\tRecorded   float64           `json:\"recorded\"`\n\tAggregated statsResponseData `json:\"aggregated\"`\n}\n"
  },
  {
    "path": "pkg/commands/stats/origin_inspector.go",
    "content": "package stats\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// OriginInspectorCommand exposes the Origin Inspector API.\ntype OriginInspectorCommand struct {\n\targparser.Base\n\n\tcursor      string\n\tdatacenters []string\n\tdownsample  string\n\tformatFlag  string\n\tfrom        string\n\tgroupBy     []string\n\thosts       []string\n\tjsonFlag    bool\n\tlimit       int\n\tmetrics     []string\n\tregions     []string\n\tserviceName argparser.OptionalServiceNameID\n\tto          string\n}\n\n// NewOriginInspectorCommand is the \"stats origin-inspector\" subcommand.\nfunc NewOriginInspectorCommand(parent argparser.Registerer, g *global.Data) *OriginInspectorCommand {\n\tvar c OriginInspectorCommand\n\tc.Globals = g\n\n\tc.CmdClause = parent.Command(\"origin-inspector\", \"View origin metrics for a Fastly service\")\n\n\t// Optional.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\n\tc.CmdClause.Flag(\"from\", \"Start time (RFC3339 or Unix timestamp)\").StringVar(&c.from)\n\tc.CmdClause.Flag(\"to\", \"End time (RFC3339 or Unix timestamp)\").StringVar(&c.to)\n\tc.CmdClause.Flag(\"downsample\", \"Sample window (minute/hour/day)\").EnumVar(&c.downsample, \"minute\", \"hour\", \"day\")\n\tc.CmdClause.Flag(\"metric\", \"Metrics to retrieve (repeatable, up to 10)\").StringsVar(&c.metrics)\n\tc.CmdClause.Flag(\"host\", \"Filter by origin host (repeatable)\").StringsVar(&c.hosts)\n\tc.CmdClause.Flag(\"datacenter\", \"Filter by POP (repeatable)\").StringsVar(&c.datacenters)\n\tc.CmdClause.Flag(\"region\", \"Filter by region (repeatable)\").StringsVar(&c.regions)\n\tc.CmdClause.Flag(\"group-by\", \"Dimensions to group by (repeatable)\").StringsVar(&c.groupBy)\n\tc.CmdClause.Flag(\"limit\", \"Max entries to return\").IntVar(&c.limit)\n\tc.CmdClause.Flag(\"cursor\", \"Pagination cursor from a previous response\").StringVar(&c.cursor)\n\tc.CmdClause.Flag(\"format\", \"Output format (json)\").Hidden().EnumVar(&c.formatFlag, \"json\")\n\tc.CmdClause.Flag(\"json\", argparser.FlagJSONDesc).Short('j').BoolVar(&c.jsonFlag)\n\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *OriginInspectorCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif err := resolveJSONFormat(&c.formatFlag, c.jsonFlag, c.Globals); err != nil {\n\t\treturn err\n\t}\n\n\tserviceID, source, flag, err := argparser.ServiceID(c.serviceName, *c.Globals.Manifest, c.Globals.APIClient, c.Globals.ErrLog)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif c.Globals.Verbose() {\n\t\targparser.DisplayServiceID(serviceID, flag, source, out)\n\t}\n\n\tinput := fastly.GetOriginMetricsInput{\n\t\tServiceID:   serviceID,\n\t\tDatacenters: c.datacenters,\n\t\tGroupBy:     c.groupBy,\n\t\tHosts:       c.hosts,\n\t\tMetrics:     c.metrics,\n\t\tRegions:     c.regions,\n\t}\n\tif c.cursor != \"\" {\n\t\tinput.Cursor = &c.cursor\n\t}\n\tif c.downsample != \"\" {\n\t\tinput.Downsample = &c.downsample\n\t}\n\tif c.from != \"\" {\n\t\tt, err := parseTime(c.from)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"invalid --from value: %w\", err)\n\t\t}\n\t\tinput.Start = &t\n\t}\n\tif c.to != \"\" {\n\t\tt, err := parseTime(c.to)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"invalid --to value: %w\", err)\n\t\t}\n\t\tinput.End = &t\n\t}\n\tif c.limit > 0 {\n\t\tinput.Limit = &c.limit\n\t}\n\n\tswitch c.formatFlag {\n\tcase \"json\":\n\t\tvar envelope struct {\n\t\t\tStatus *string `json:\"status\"`\n\t\t}\n\t\tvar raw json.RawMessage\n\t\tif err := c.Globals.APIClient.GetOriginMetricsForServiceJSON(context.TODO(), &input, &raw); err != nil {\n\t\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\"Service ID\": serviceID})\n\t\t\treturn err\n\t\t}\n\t\tif err := json.Unmarshal(raw, &envelope); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif fastly.ToValue(envelope.Status) != statusSuccess {\n\t\t\treturn fmt.Errorf(\"non-success response: %s\", fastly.ToValue(envelope.Status))\n\t\t}\n\t\t_, err := out.Write(raw)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tfmt.Fprintln(out)\n\t\treturn nil\n\n\tdefault:\n\t\tresp, err := c.Globals.APIClient.GetOriginMetricsForService(context.TODO(), &input)\n\t\tif err != nil {\n\t\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\"Service ID\": serviceID})\n\t\t\treturn err\n\t\t}\n\t\tif fastly.ToValue(resp.Status) != statusSuccess {\n\t\t\treturn fmt.Errorf(\"non-success response: %s\", fastly.ToValue(resp.Status))\n\t\t}\n\t\ttext.PrintOriginInspectorTbl(out, resp)\n\t\treturn nil\n\t}\n}\n"
  },
  {
    "path": "pkg/commands/stats/origin_inspector_test.go",
    "content": "package stats_test\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/app\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/mock\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n)\n\nfunc TestOriginInspector(t *testing.T) {\n\targs := testutil.SplitArgs\n\tscenarios := []struct {\n\t\tname       string\n\t\targs       []string\n\t\tapi        mock.API\n\t\twantError  string\n\t\twantOutput string\n\t}{\n\t\t{\n\t\t\tname: \"success table\",\n\t\t\targs: args(\"stats origin-inspector --service-id 123\"),\n\t\t\tapi: mock.API{\n\t\t\t\tGetOriginMetricsForServiceFn: getOriginMetricsOK,\n\t\t\t},\n\t\t\twantOutput: \"RESPONSES\",\n\t\t},\n\t\t{\n\t\t\tname: \"success json\",\n\t\t\targs: args(\"stats origin-inspector --service-id 123 --format=json\"),\n\t\t\tapi: mock.API{\n\t\t\t\tGetOriginMetricsForServiceJSONFn: getOriginMetricsJSONOK,\n\t\t\t},\n\t\t\twantOutput: \"status\",\n\t\t},\n\t\t{\n\t\t\tname: \"success json alias\",\n\t\t\targs: args(\"stats origin-inspector --service-id 123 --json\"),\n\t\t\tapi: mock.API{\n\t\t\t\tGetOriginMetricsForServiceJSONFn: getOriginMetricsJSONOK,\n\t\t\t},\n\t\t\twantOutput: \"status\",\n\t\t},\n\t\t{\n\t\t\tname:      \"verbose json combo\",\n\t\t\targs:      args(\"stats origin-inspector --service-id 123 --json --verbose\"),\n\t\t\tapi:       mock.API{},\n\t\t\twantError: \"invalid flag combination\",\n\t\t},\n\t\t{\n\t\t\tname: \"non-success status\",\n\t\t\targs: args(\"stats origin-inspector --service-id 123\"),\n\t\t\tapi: mock.API{\n\t\t\t\tGetOriginMetricsForServiceFn: getOriginMetricsNonSuccess,\n\t\t\t},\n\t\t\twantError: \"non-success response\",\n\t\t},\n\t\t{\n\t\t\tname:      \"missing service ID\",\n\t\t\targs:      args(\"stats origin-inspector\"),\n\t\t\tapi:       mock.API{},\n\t\t\twantError: \"error reading service\",\n\t\t},\n\t\t{\n\t\t\tname: \"non-success status json\",\n\t\t\targs: args(\"stats origin-inspector --service-id 123 --format=json\"),\n\t\t\tapi: mock.API{\n\t\t\t\tGetOriginMetricsForServiceJSONFn: getOriginMetricsJSONNonSuccess,\n\t\t\t},\n\t\t\twantError: \"non-success response\",\n\t\t},\n\t\t{\n\t\t\tname: \"api error\",\n\t\t\targs: args(\"stats origin-inspector --service-id 123\"),\n\t\t\tapi: mock.API{\n\t\t\t\tGetOriginMetricsForServiceFn: getOriginMetricsError,\n\t\t\t},\n\t\t\twantError: errTest.Error(),\n\t\t},\n\t\t{\n\t\t\tname: \"from RFC3339 maps to Start\",\n\t\t\targs: args(\"stats origin-inspector --service-id 123 --from 2024-01-15T10:00:00Z\"),\n\t\t\tapi: mock.API{\n\t\t\t\tGetOriginMetricsForServiceFn: getOriginMetricsAssertStart(time.Date(2024, 1, 15, 10, 0, 0, 0, time.UTC)),\n\t\t\t},\n\t\t\twantOutput: \"RESPONSES\",\n\t\t},\n\t\t{\n\t\t\tname: \"from Unix epoch maps to Start\",\n\t\t\targs: args(\"stats origin-inspector --service-id 123 --from 1705312800\"),\n\t\t\tapi: mock.API{\n\t\t\t\tGetOriginMetricsForServiceFn: getOriginMetricsAssertStart(time.Unix(1705312800, 0)),\n\t\t\t},\n\t\t\twantOutput: \"RESPONSES\",\n\t\t},\n\t\t{\n\t\t\tname: \"to RFC3339 maps to End\",\n\t\t\targs: args(\"stats origin-inspector --service-id 123 --to 2024-01-15T11:00:00Z\"),\n\t\t\tapi: mock.API{\n\t\t\t\tGetOriginMetricsForServiceFn: getOriginMetricsAssertEnd(time.Date(2024, 1, 15, 11, 0, 0, 0, time.UTC)),\n\t\t\t},\n\t\t\twantOutput: \"RESPONSES\",\n\t\t},\n\t\t{\n\t\t\tname: \"from invalid format error\",\n\t\t\targs: args(\"stats origin-inspector --service-id 123 --from not-a-time\"),\n\t\t\tapi: mock.API{\n\t\t\t\tGetOriginMetricsForServiceFn: getOriginMetricsOK,\n\t\t\t},\n\t\t\twantError: \"invalid --from value\",\n\t\t},\n\t\t{\n\t\t\tname: \"to invalid format error\",\n\t\t\targs: args(\"stats origin-inspector --service-id 123 --to not-a-time\"),\n\t\t\tapi: mock.API{\n\t\t\t\tGetOriginMetricsForServiceFn: getOriginMetricsOK,\n\t\t\t},\n\t\t\twantError: \"invalid --to value\",\n\t\t},\n\t}\n\tfor _, tc := range scenarios {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\t// Clear FASTLY_SERVICE_ID for tests that validate missing service ID\n\t\t\tif tc.name == \"missing service ID\" {\n\t\t\t\tt.Setenv(\"FASTLY_SERVICE_ID\", \"\")\n\t\t\t}\n\t\t\tvar stdout bytes.Buffer\n\t\t\tapp.Init = func(_ []string, _ io.Reader) (*global.Data, error) {\n\t\t\t\topts := testutil.MockGlobalData(tc.args, &stdout)\n\t\t\t\topts.APIClientFactory = mock.APIClient(tc.api)\n\t\t\t\treturn opts, nil\n\t\t\t}\n\t\t\terr := app.Run(tc.args, nil)\n\t\t\ttestutil.AssertErrorContains(t, err, tc.wantError)\n\t\t\ttestutil.AssertStringContains(t, stdout.String(), tc.wantOutput)\n\t\t})\n\t}\n}\n\nfunc originMetricsOKResult() (*fastly.OriginInspector, error) {\n\treturn &fastly.OriginInspector{\n\t\tStatus: fastly.ToPointer(\"success\"),\n\t\tMeta: &fastly.OriginMeta{\n\t\t\tStart: fastly.ToPointer(\"2024-01-15T10:00:00Z\"),\n\t\t\tEnd:   fastly.ToPointer(\"2024-01-15T11:00:00Z\"),\n\t\t},\n\t\tData: []*fastly.OriginData{\n\t\t\t{\n\t\t\t\tValues: []*fastly.OriginMetrics{\n\t\t\t\t\t{\n\t\t\t\t\t\tResponses: fastly.ToPointer(uint64(200)),\n\t\t\t\t\t\tStatus2xx: fastly.ToPointer(uint64(180)),\n\t\t\t\t\t\tStatus4xx: fastly.ToPointer(uint64(15)),\n\t\t\t\t\t\tStatus5xx: fastly.ToPointer(uint64(5)),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}, nil\n}\n\nfunc getOriginMetricsOK(_ context.Context, _ *fastly.GetOriginMetricsInput) (*fastly.OriginInspector, error) {\n\treturn originMetricsOKResult()\n}\n\nfunc getOriginMetricsJSONOK(_ context.Context, _ *fastly.GetOriginMetricsInput, dst any) error {\n\tmsg := []byte(`{\"status\":\"success\",\"data\":[]}`)\n\treturn json.Unmarshal(msg, dst)\n}\n\nfunc getOriginMetricsJSONNonSuccess(_ context.Context, _ *fastly.GetOriginMetricsInput, dst any) error {\n\tmsg := []byte(`{\"status\":\"error\",\"data\":[]}`)\n\treturn json.Unmarshal(msg, dst)\n}\n\nfunc getOriginMetricsNonSuccess(_ context.Context, _ *fastly.GetOriginMetricsInput) (*fastly.OriginInspector, error) {\n\treturn &fastly.OriginInspector{\n\t\tStatus: fastly.ToPointer(\"error\"),\n\t}, nil\n}\n\nfunc getOriginMetricsAssertStart(want time.Time) func(context.Context, *fastly.GetOriginMetricsInput) (*fastly.OriginInspector, error) {\n\treturn func(_ context.Context, i *fastly.GetOriginMetricsInput) (*fastly.OriginInspector, error) {\n\t\tif i.Start == nil {\n\t\t\treturn nil, fmt.Errorf(\"expected Start to be set, got nil\")\n\t\t}\n\t\tif !i.Start.Equal(want) {\n\t\t\treturn nil, fmt.Errorf(\"expected Start %v, got %v\", want, *i.Start)\n\t\t}\n\t\treturn originMetricsOKResult()\n\t}\n}\n\nfunc getOriginMetricsAssertEnd(want time.Time) func(context.Context, *fastly.GetOriginMetricsInput) (*fastly.OriginInspector, error) {\n\treturn func(_ context.Context, i *fastly.GetOriginMetricsInput) (*fastly.OriginInspector, error) {\n\t\tif i.End == nil {\n\t\t\treturn nil, fmt.Errorf(\"expected End to be set, got nil\")\n\t\t}\n\t\tif !i.End.Equal(want) {\n\t\t\treturn nil, fmt.Errorf(\"expected End %v, got %v\", want, *i.End)\n\t\t}\n\t\treturn originMetricsOKResult()\n\t}\n}\n\nfunc getOriginMetricsError(_ context.Context, _ *fastly.GetOriginMetricsInput) (*fastly.OriginInspector, error) {\n\treturn nil, errTest\n}\n"
  },
  {
    "path": "pkg/commands/stats/realtime.go",
    "content": "package stats\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/api\"\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// RealtimeCommand exposes the Realtime Metrics API.\ntype RealtimeCommand struct {\n\targparser.Base\n\n\tformatFlag  string\n\tjsonFlag    bool\n\tserviceName argparser.OptionalServiceNameID\n}\n\n// NewRealtimeCommand is the \"stats realtime\" subcommand.\nfunc NewRealtimeCommand(parent argparser.Registerer, g *global.Data) *RealtimeCommand {\n\tvar c RealtimeCommand\n\tc.Globals = g\n\n\tc.CmdClause = parent.Command(\"realtime\", \"View realtime stats for a Fastly service\")\n\n\t// Optional.\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagServiceIDName,\n\t\tDescription: argparser.FlagServiceIDDesc,\n\t\tDst:         &g.Manifest.Flag.ServiceID,\n\t\tShort:       's',\n\t})\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tAction:      c.serviceName.Set,\n\t\tName:        argparser.FlagServiceName,\n\t\tDescription: argparser.FlagServiceNameDesc,\n\t\tDst:         &c.serviceName.Value,\n\t})\n\n\tc.CmdClause.Flag(\"format\", \"Output format (json)\").Hidden().EnumVar(&c.formatFlag, \"json\")\n\tc.CmdClause.Flag(\"json\", argparser.FlagJSONDesc).Short('j').BoolVar(&c.jsonFlag)\n\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RealtimeCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif err := resolveJSONFormat(&c.formatFlag, c.jsonFlag, c.Globals); err != nil {\n\t\treturn err\n\t}\n\n\tserviceID, source, flag, err := argparser.ServiceID(c.serviceName, *c.Globals.Manifest, c.Globals.APIClient, c.Globals.ErrLog)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif c.Globals.Verbose() {\n\t\targparser.DisplayServiceID(serviceID, flag, source, out)\n\t}\n\n\tswitch c.formatFlag {\n\tcase \"json\":\n\t\tif err := loopJSON(c.Globals.RTSClient, serviceID, out); err != nil {\n\t\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\t\"Service ID\": serviceID,\n\t\t\t})\n\t\t\treturn err\n\t\t}\n\n\tdefault:\n\t\tif err := loopText(c.Globals.RTSClient, serviceID, out); err != nil {\n\t\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\t\"Service ID\": serviceID,\n\t\t\t})\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc loopJSON(client api.RealtimeStatsInterface, service string, out io.Writer) error {\n\tvar timestamp uint64\n\tfor {\n\t\tvar envelope struct {\n\t\t\tTimestamp uint64            `json:\"timestamp\"`\n\t\t\tData      []json.RawMessage `json:\"data\"`\n\t\t}\n\n\t\terr := client.GetRealtimeStatsJSON(context.TODO(), &fastly.GetRealtimeStatsInput{\n\t\t\tServiceID: service,\n\t\t\tTimestamp: timestamp,\n\t\t}, &envelope)\n\t\tif err != nil {\n\t\t\ttext.Error(out, \"fetching stats: %w\", err)\n\t\t\tcontinue\n\t\t}\n\t\ttimestamp = envelope.Timestamp\n\n\t\tfor _, data := range envelope.Data {\n\t\t\t_, err = out.Write(data)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"error: unable to write data to stdout: %w\", err)\n\t\t\t}\n\t\t\ttext.Break(out)\n\t\t}\n\t}\n}\n\nfunc loopText(client api.RealtimeStatsInterface, service string, out io.Writer) error {\n\tvar timestamp uint64\n\tfor {\n\t\tvar envelope realtimeResponse\n\n\t\terr := client.GetRealtimeStatsJSON(context.TODO(), &fastly.GetRealtimeStatsInput{\n\t\t\tServiceID: service,\n\t\t\tTimestamp: timestamp,\n\t\t}, &envelope)\n\t\tif err != nil {\n\t\t\ttext.Error(out, \"fetching stats: %w\", err)\n\t\t\tcontinue\n\t\t}\n\t\ttimestamp = envelope.Timestamp\n\n\t\tfor _, block := range envelope.Data {\n\t\t\tagg := block.Aggregated\n\n\t\t\t// FIXME: These are heavy-handed compatibility\n\t\t\t// fixes for stats vs realtime, so we can use\n\t\t\t// fmtBlock for both.\n\t\t\tagg[\"start_time\"] = block.Recorded\n\t\t\tdelete(agg, \"miss_histogram\")\n\n\t\t\tif err := fmtBlock(out, service, agg); err != nil {\n\t\t\t\ttext.Error(out, \"formatting stats: %w\", err)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "pkg/commands/stats/realtime_test.go",
    "content": "package stats_test\n\nimport (\n\t\"bytes\"\n\t\"io\"\n\t\"testing\"\n\n\t\"github.com/fastly/cli/pkg/app\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/mock\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n)\n\nfunc TestRealtime(t *testing.T) {\n\targs := testutil.SplitArgs\n\tscenarios := []struct {\n\t\tname      string\n\t\targs      []string\n\t\tapi       mock.API\n\t\twantError string\n\t}{\n\t\t{\n\t\t\tname:      \"verbose json combo\",\n\t\t\targs:      args(\"stats realtime --service-id 123 --json --verbose\"),\n\t\t\tapi:       mock.API{},\n\t\t\twantError: \"invalid flag combination\",\n\t\t},\n\t\t{\n\t\t\tname:      \"verbose format json combo\",\n\t\t\targs:      args(\"stats realtime --service-id 123 --format=json --verbose\"),\n\t\t\tapi:       mock.API{},\n\t\t\twantError: \"invalid flag combination\",\n\t\t},\n\t}\n\tfor _, tc := range scenarios {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tvar stdout bytes.Buffer\n\t\t\tapp.Init = func(_ []string, _ io.Reader) (*global.Data, error) {\n\t\t\t\topts := testutil.MockGlobalData(tc.args, &stdout)\n\t\t\t\topts.APIClientFactory = mock.APIClient(tc.api)\n\t\t\t\treturn opts, nil\n\t\t\t}\n\t\t\terr := app.Run(tc.args, nil)\n\t\t\ttestutil.AssertErrorContains(t, err, tc.wantError)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/commands/stats/regions.go",
    "content": "package stats\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// RegionsCommand exposes the Stats Regions API.\ntype RegionsCommand struct {\n\targparser.Base\n}\n\n// NewRegionsCommand returns a new command registered under parent.\nfunc NewRegionsCommand(parent argparser.Registerer, g *global.Data) *RegionsCommand {\n\tvar c RegionsCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(\"regions\", \"List stats regions\")\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RegionsCommand) Exec(_ io.Reader, out io.Writer) error {\n\tresp, err := c.Globals.APIClient.GetRegions(context.TODO())\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn fmt.Errorf(\"fetching regions: %w\", err)\n\t}\n\n\tfor _, region := range resp.Data {\n\t\ttext.Output(out, \"%s\", region)\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/stats/regions_test.go",
    "content": "package stats_test\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"testing\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\troot \"github.com/fastly/cli/pkg/commands/stats\"\n\t\"github.com/fastly/cli/pkg/mock\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n)\n\nfunc TestRegions(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:       \"success\",\n\t\t\tArgs:       \"\",\n\t\t\tAPI:        &mock.API{GetRegionsFn: getRegionsOK},\n\t\t\tWantOutput: \"foo\\nbar\\nbaz\\n\",\n\t\t},\n\t\t{\n\t\t\tName:      \"api error\",\n\t\t\tArgs:      \"\",\n\t\t\tAPI:       &mock.API{GetRegionsFn: getRegionsError},\n\t\t\tWantError: errTest.Error(),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, \"regions\"}, scenarios)\n}\n\nfunc getRegionsOK(_ context.Context) (*fastly.RegionsResponse, error) {\n\treturn &fastly.RegionsResponse{\n\t\tData: []string{\"foo\", \"bar\", \"baz\"},\n\t}, nil\n}\n\nvar errTest = errors.New(\"fixture error\")\n\nfunc getRegionsError(_ context.Context) (*fastly.RegionsResponse, error) {\n\treturn nil, errTest\n}\n"
  },
  {
    "path": "pkg/commands/stats/root.go",
    "content": "package stats\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand dispatches all \"stats\" commands.\ntype RootCommand struct {\n\targparser.Base\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"stats\"\n\n// NewRootCommand returns a new top level \"stats\" command.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"View statistics for Fastly services: historical, realtime, aggregate, usage, domain and origin inspection\")\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n\nfunc resolveJSONFormat(formatFlag *string, jsonFlag bool, g *global.Data) error {\n\tif jsonFlag {\n\t\t*formatFlag = \"json\"\n\t}\n\tif *formatFlag == \"json\" {\n\t\tg.Flags.JSON = true\n\t\tif g.Verbose() {\n\t\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t\t}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/stats/template.go",
    "content": "package stats\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"text/template\"\n\t\"time\"\n\n\t\"github.com/mitchellh/mapstructure\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n)\n\nvar blockTemplate = template.Must(template.New(\"stats_block\").Parse(\n\t`Service ID:         {{ .ServiceID }}\nStart Time:         {{ .StartTime }}\n--------------------------------------------------\nHit Rate:           {{ .HitRate }}\nAvg Hit Time:       {{ .AvgHitTime }}\nAvg Miss Time:      {{ .AvgMissTime }}\n\nRequest BW:         {{ .RequestBytes }}\n  Headers:          {{ .RequestHeaderBytes }}\n  Body:             {{ .RequestBodyBytes }}\n\nResponse BW:        {{ .ResponseBytes }}\n  Headers:          {{ .ResponseHeaderBytes }}\n  Body:             {{ .ResponseBodyBytes }}\n\nRequests:           {{ .RequestCount }}\n  Hit:              {{ .Hits }}\n  Miss:             {{ .Miss }}\n  Pass:             {{ .Pass }}\n  Synth:            {{ .Synth }}\n  Error:            {{ .Errors }}\n  Uncacheable:      {{ .Uncacheable }}\n\n`))\n\nfunc fmtFieldLine(out io.Writer, field string, block statsResponseData) error {\n\tst, ok := block[\"start_time\"].(float64)\n\tif !ok {\n\t\treturn fmt.Errorf(\"failed to type assert '%v' to a float64\", block[\"start_time\"])\n\t}\n\tstartTime := time.Unix(int64(st), 0).UTC()\n\n\tval, ok := block[field]\n\tif !ok {\n\t\treturn fmt.Errorf(\"field %q not found in stats response\", field)\n\t}\n\t_, err := fmt.Fprintf(out, \"%s\\t%s: %v\\n\", startTime.Format(time.RFC3339), field, val)\n\treturn err\n}\n\nfunc fmtBlock(out io.Writer, service string, block statsResponseData) error {\n\tvar agg fastly.Stats\n\tif err := mapstructure.Decode(block, &agg); err != nil {\n\t\treturn err\n\t}\n\n\taggHits := fastly.ToValue(agg.Hits)\n\taggMiss := fastly.ToValue(agg.Miss)\n\taggErrs := fastly.ToValue(agg.Errors)\n\n\t// TODO: parse the JSON more strictly so this doesn't need to be dynamic.\n\tst, ok := block[\"start_time\"].(float64)\n\tif !ok {\n\t\treturn fmt.Errorf(\"failed to type assert '%v' to a float64\", block[\"start_time\"])\n\t}\n\tstartTime := time.Unix(int64(st), 0).UTC()\n\n\tvalues := map[string]string{\n\t\t\"ServiceID\":   fmt.Sprintf(\"%30s\", service),\n\t\t\"StartTime\":   fmt.Sprintf(\"%30s\", startTime),\n\t\t\"HitRate\":     fmt.Sprintf(\"%29.2f%%\", fastly.ToValue(agg.HitRatio)*100),\n\t\t\"AvgHitTime\":  fmt.Sprintf(\"%28.2f\\u00b5s\", fastly.ToValue(agg.HitsTime)*1000),\n\t\t\"AvgMissTime\": fmt.Sprintf(\"%28.2f\\u00b5s\", fastly.ToValue(agg.MissTime)*1000),\n\n\t\t\"RequestBytes\":        fmt.Sprintf(\"%30d\", fastly.ToValue(agg.RequestHeaderBytes)+fastly.ToValue(agg.RequestBodyBytes)),\n\t\t\"RequestHeaderBytes\":  fmt.Sprintf(\"%30d\", fastly.ToValue(agg.RequestHeaderBytes)),\n\t\t\"RequestBodyBytes\":    fmt.Sprintf(\"%30d\", fastly.ToValue(agg.RequestBodyBytes)),\n\t\t\"ResponseBytes\":       fmt.Sprintf(\"%30d\", fastly.ToValue(agg.ResponseHeaderBytes)+fastly.ToValue(agg.ResponseBodyBytes)),\n\t\t\"ResponseHeaderBytes\": fmt.Sprintf(\"%30d\", fastly.ToValue(agg.ResponseHeaderBytes)),\n\t\t\"ResponseBodyBytes\":   fmt.Sprintf(\"%30d\", fastly.ToValue(agg.ResponseBodyBytes)),\n\n\t\t\"RequestCount\": fmt.Sprintf(\"%30d\", fastly.ToValue(agg.Requests)),\n\t\t\"Hits\":         fmt.Sprintf(\"%30d\", aggHits),\n\t\t\"Miss\":         fmt.Sprintf(\"%30d\", aggMiss),\n\t\t\"Pass\":         fmt.Sprintf(\"%30d\", fastly.ToValue(agg.Pass)),\n\t\t\"Synth\":        fmt.Sprintf(\"%30d\", fastly.ToValue(agg.Synth)),\n\t\t\"Errors\":       fmt.Sprintf(\"%30d\", aggErrs),\n\t\t\"Uncacheable\":  fmt.Sprintf(\"%30d\", fastly.ToValue(agg.Uncachable)),\n\t}\n\n\treturn blockTemplate.Execute(out, values)\n}\n"
  },
  {
    "path": "pkg/commands/stats/usage.go",
    "content": "package stats\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// UsageCommand exposes the Usage Stats API.\ntype UsageCommand struct {\n\targparser.Base\n\n\tby         string\n\tbyService  bool\n\tformatFlag string\n\tfrom       string\n\tjsonFlag   bool\n\tregion     string\n\tto         string\n}\n\n// NewUsageCommand is the \"stats usage\" subcommand.\nfunc NewUsageCommand(parent argparser.Registerer, g *global.Data) *UsageCommand {\n\tvar c UsageCommand\n\tc.Globals = g\n\n\tc.CmdClause = parent.Command(\"usage\", \"View usage stats (bandwidth, requests)\")\n\n\t// Optional.\n\tc.CmdClause.Flag(\"from\", \"Start time\").StringVar(&c.from)\n\tc.CmdClause.Flag(\"to\", \"End time\").StringVar(&c.to)\n\tc.CmdClause.Flag(\"by\", \"Aggregation period (minute/hour/day)\").EnumVar(&c.by, \"minute\", \"hour\", \"day\")\n\tc.CmdClause.Flag(\"region\", \"Filter by region ('stats regions' to list)\").StringVar(&c.region)\n\tc.CmdClause.Flag(\"by-service\", \"Break down usage by service\").BoolVar(&c.byService)\n\tc.CmdClause.Flag(\"format\", \"Output format (json)\").Hidden().EnumVar(&c.formatFlag, \"json\")\n\tc.CmdClause.Flag(\"json\", argparser.FlagJSONDesc).Short('j').BoolVar(&c.jsonFlag)\n\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *UsageCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif err := resolveJSONFormat(&c.formatFlag, c.jsonFlag, c.Globals); err != nil {\n\t\treturn err\n\t}\n\n\tinput := fastly.GetUsageInput{}\n\tif c.by != \"\" {\n\t\tinput.By = &c.by\n\t}\n\tif c.from != \"\" {\n\t\tinput.From = &c.from\n\t}\n\tif c.region != \"\" {\n\t\tinput.Region = &c.region\n\t}\n\tif c.to != \"\" {\n\t\tinput.To = &c.to\n\t}\n\n\tif c.byService {\n\t\treturn c.execByService(out, &input)\n\t}\n\treturn c.execPlain(out, &input)\n}\n\nfunc (c *UsageCommand) execPlain(out io.Writer, input *fastly.GetUsageInput) error {\n\tresp, err := c.Globals.APIClient.GetUsage(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif fastly.ToValue(resp.Status) != statusSuccess {\n\t\treturn fmt.Errorf(\"non-success response: %s\", fastly.ToValue(resp.Message))\n\t}\n\n\tfilterUsageByRegion(resp.Data, c.region)\n\n\tswitch c.formatFlag {\n\tcase \"json\":\n\t\treturn writeUsageJSON(out, resp.Data)\n\tdefault:\n\t\ttext.PrintUsageTbl(out, resp.Data)\n\t\treturn nil\n\t}\n}\n\nfunc (c *UsageCommand) execByService(out io.Writer, input *fastly.GetUsageInput) error {\n\tresp, err := c.Globals.APIClient.GetUsageByService(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif fastly.ToValue(resp.Status) != statusSuccess {\n\t\treturn fmt.Errorf(\"non-success response: %s\", fastly.ToValue(resp.Message))\n\t}\n\n\tfilterUsageByServiceByRegion(resp.Data, c.region)\n\n\tswitch c.formatFlag {\n\tcase \"json\":\n\t\treturn writeUsageByServiceJSON(out, resp.Data)\n\tdefault:\n\t\ttext.PrintUsageByServiceTbl(out, resp.Data)\n\t\treturn nil\n\t}\n}\n\nfunc writeUsageJSON(out io.Writer, data *fastly.RegionsUsage) error {\n\tif data == nil {\n\t\treturn json.NewEncoder(out).Encode(map[string]any{})\n\t}\n\treturn json.NewEncoder(out).Encode(usageToMap(*data))\n}\n\nfunc writeUsageByServiceJSON(out io.Writer, data *fastly.ServicesByRegionsUsage) error {\n\tif data == nil {\n\t\treturn json.NewEncoder(out).Encode(map[string]any{})\n\t}\n\tresult := make(map[string]any)\n\tfor region, services := range *data {\n\t\tif services == nil {\n\t\t\tcontinue\n\t\t}\n\t\tregionMap := make(map[string]any)\n\t\tfor svcID, usage := range *services {\n\t\t\tregionMap[svcID] = usageEntry(usage)\n\t\t}\n\t\tresult[region] = regionMap\n\t}\n\treturn json.NewEncoder(out).Encode(result)\n}\n\nfunc usageToMap(data fastly.RegionsUsage) map[string]any {\n\tresult := make(map[string]any)\n\tfor region, usage := range data {\n\t\tresult[region] = usageEntry(usage)\n\t}\n\treturn result\n}\n\nfunc filterUsageByRegion(data *fastly.RegionsUsage, region string) {\n\tif region == \"\" || data == nil {\n\t\treturn\n\t}\n\tfor k := range *data {\n\t\tif k != region {\n\t\t\tdelete(*data, k)\n\t\t}\n\t}\n}\n\nfunc filterUsageByServiceByRegion(data *fastly.ServicesByRegionsUsage, region string) {\n\tif region == \"\" || data == nil {\n\t\treturn\n\t}\n\tfor k := range *data {\n\t\tif k != region {\n\t\t\tdelete(*data, k)\n\t\t}\n\t}\n}\n\nfunc usageEntry(u *fastly.Usage) map[string]any {\n\tif u == nil {\n\t\treturn map[string]any{\n\t\t\t\"bandwidth\":        uint64(0),\n\t\t\t\"requests\":         uint64(0),\n\t\t\t\"compute_requests\": uint64(0),\n\t\t}\n\t}\n\treturn map[string]any{\n\t\t\"bandwidth\":        fastly.ToValue(u.Bandwidth),\n\t\t\"requests\":         fastly.ToValue(u.Requests),\n\t\t\"compute_requests\": fastly.ToValue(u.ComputeRequests),\n\t}\n}\n"
  },
  {
    "path": "pkg/commands/stats/usage_test.go",
    "content": "package stats_test\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"io\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/app\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/mock\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n)\n\nfunc TestUsage(t *testing.T) {\n\targs := testutil.SplitArgs\n\tscenarios := []struct {\n\t\tname       string\n\t\targs       []string\n\t\tapi        mock.API\n\t\twantError  string\n\t\twantOutput string\n\t\twantAbsent string\n\t}{\n\t\t{\n\t\t\tname:       \"success plain\",\n\t\t\targs:       args(\"stats usage\"),\n\t\t\tapi:        mock.API{GetUsageFn: getUsageOK},\n\t\t\twantOutput: \"usa\",\n\t\t},\n\t\t{\n\t\t\tname:       \"success json\",\n\t\t\targs:       args(\"stats usage --format=json\"),\n\t\t\tapi:        mock.API{GetUsageFn: getUsageOK},\n\t\t\twantOutput: \"bandwidth\",\n\t\t},\n\t\t{\n\t\t\tname:       \"success json alias\",\n\t\t\targs:       args(\"stats usage --json\"),\n\t\t\tapi:        mock.API{GetUsageFn: getUsageOK},\n\t\t\twantOutput: \"bandwidth\",\n\t\t},\n\t\t{\n\t\t\tname:      \"verbose json combo\",\n\t\t\targs:      args(\"stats usage --json --verbose\"),\n\t\t\tapi:       mock.API{GetUsageFn: getUsageOK},\n\t\t\twantError: \"invalid flag combination\",\n\t\t},\n\t\t{\n\t\t\tname:       \"success by-service\",\n\t\t\targs:       args(\"stats usage --by-service\"),\n\t\t\tapi:        mock.API{GetUsageByServiceFn: getUsageByServiceOK},\n\t\t\twantOutput: \"svc123\",\n\t\t},\n\t\t{\n\t\t\tname:       \"success by-service json\",\n\t\t\targs:       args(\"stats usage --by-service --format=json\"),\n\t\t\tapi:        mock.API{GetUsageByServiceFn: getUsageByServiceOK},\n\t\t\twantOutput: \"svc123\",\n\t\t},\n\t\t{\n\t\t\tname:       \"nil usage entry json\",\n\t\t\targs:       args(\"stats usage --format=json\"),\n\t\t\tapi:        mock.API{GetUsageFn: getUsageNilEntry},\n\t\t\twantOutput: `\"bandwidth\"`,\n\t\t},\n\t\t{\n\t\t\tname:       \"nil usage entry table skipped\",\n\t\t\targs:       args(\"stats usage\"),\n\t\t\tapi:        mock.API{GetUsageFn: getUsageWithNilEntry},\n\t\t\twantOutput: \"europe\",\n\t\t},\n\t\t{\n\t\t\tname:       \"region filter plain\",\n\t\t\targs:       args(\"stats usage --region=europe\"),\n\t\t\tapi:        mock.API{GetUsageFn: getUsageMultiRegion},\n\t\t\twantOutput: \"europe\",\n\t\t\twantAbsent: \"usa\",\n\t\t},\n\t\t{\n\t\t\tname:       \"region filter by-service\",\n\t\t\targs:       args(\"stats usage --by-service --region=europe\"),\n\t\t\tapi:        mock.API{GetUsageByServiceFn: getUsageByServiceMultiRegion},\n\t\t\twantOutput: \"svc456\",\n\t\t\twantAbsent: \"usa\",\n\t\t},\n\t\t{\n\t\t\tname:      \"non-success status\",\n\t\t\targs:      args(\"stats usage\"),\n\t\t\tapi:       mock.API{GetUsageFn: getUsageNonSuccess},\n\t\t\twantError: \"non-success response\",\n\t\t},\n\t\t{\n\t\t\tname:      \"api error\",\n\t\t\targs:      args(\"stats usage\"),\n\t\t\tapi:       mock.API{GetUsageFn: getUsageError},\n\t\t\twantError: errTest.Error(),\n\t\t},\n\t}\n\tfor _, tc := range scenarios {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tvar stdout bytes.Buffer\n\t\t\tapp.Init = func(_ []string, _ io.Reader) (*global.Data, error) {\n\t\t\t\topts := testutil.MockGlobalData(tc.args, &stdout)\n\t\t\t\topts.APIClientFactory = mock.APIClient(tc.api)\n\t\t\t\treturn opts, nil\n\t\t\t}\n\t\t\terr := app.Run(tc.args, nil)\n\t\t\ttestutil.AssertErrorContains(t, err, tc.wantError)\n\t\t\ttestutil.AssertStringContains(t, stdout.String(), tc.wantOutput)\n\t\t\tif tc.wantAbsent != \"\" && strings.Contains(stdout.String(), tc.wantAbsent) {\n\t\t\t\tt.Errorf(\"output should not contain %q, got: %s\", tc.wantAbsent, stdout.String())\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc getUsageOK(_ context.Context, _ *fastly.GetUsageInput) (*fastly.UsageResponse, error) {\n\treturn &fastly.UsageResponse{\n\t\tStatus: fastly.ToPointer(\"success\"),\n\t\tData: &fastly.RegionsUsage{\n\t\t\t\"usa\": &fastly.Usage{\n\t\t\t\tBandwidth:       fastly.ToPointer(uint64(1000)),\n\t\t\t\tRequests:        fastly.ToPointer(uint64(500)),\n\t\t\t\tComputeRequests: fastly.ToPointer(uint64(100)),\n\t\t\t},\n\t\t},\n\t}, nil\n}\n\nfunc getUsageByServiceOK(_ context.Context, _ *fastly.GetUsageInput) (*fastly.UsageByServiceResponse, error) {\n\treturn &fastly.UsageByServiceResponse{\n\t\tStatus: fastly.ToPointer(\"success\"),\n\t\tData: &fastly.ServicesByRegionsUsage{\n\t\t\t\"usa\": &fastly.ServicesUsage{\n\t\t\t\t\"svc123\": &fastly.Usage{\n\t\t\t\t\tBandwidth:       fastly.ToPointer(uint64(1000)),\n\t\t\t\t\tRequests:        fastly.ToPointer(uint64(500)),\n\t\t\t\t\tComputeRequests: fastly.ToPointer(uint64(100)),\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}, nil\n}\n\nfunc getUsageNilEntry(_ context.Context, _ *fastly.GetUsageInput) (*fastly.UsageResponse, error) {\n\treturn &fastly.UsageResponse{\n\t\tStatus: fastly.ToPointer(\"success\"),\n\t\tData: &fastly.RegionsUsage{\n\t\t\t\"empty_region\": nil,\n\t\t},\n\t}, nil\n}\n\nfunc getUsageWithNilEntry(_ context.Context, _ *fastly.GetUsageInput) (*fastly.UsageResponse, error) {\n\treturn &fastly.UsageResponse{\n\t\tStatus: fastly.ToPointer(\"success\"),\n\t\tData: &fastly.RegionsUsage{\n\t\t\t\"empty_region\": nil,\n\t\t\t\"europe\": &fastly.Usage{\n\t\t\t\tBandwidth:       fastly.ToPointer(uint64(2000)),\n\t\t\t\tRequests:        fastly.ToPointer(uint64(300)),\n\t\t\t\tComputeRequests: fastly.ToPointer(uint64(50)),\n\t\t\t},\n\t\t},\n\t}, nil\n}\n\nfunc getUsageMultiRegion(_ context.Context, _ *fastly.GetUsageInput) (*fastly.UsageResponse, error) {\n\treturn &fastly.UsageResponse{\n\t\tStatus: fastly.ToPointer(\"success\"),\n\t\tData: &fastly.RegionsUsage{\n\t\t\t\"usa\": &fastly.Usage{\n\t\t\t\tBandwidth:       fastly.ToPointer(uint64(1000)),\n\t\t\t\tRequests:        fastly.ToPointer(uint64(500)),\n\t\t\t\tComputeRequests: fastly.ToPointer(uint64(100)),\n\t\t\t},\n\t\t\t\"europe\": &fastly.Usage{\n\t\t\t\tBandwidth:       fastly.ToPointer(uint64(2000)),\n\t\t\t\tRequests:        fastly.ToPointer(uint64(300)),\n\t\t\t\tComputeRequests: fastly.ToPointer(uint64(50)),\n\t\t\t},\n\t\t},\n\t}, nil\n}\n\nfunc getUsageByServiceMultiRegion(_ context.Context, _ *fastly.GetUsageInput) (*fastly.UsageByServiceResponse, error) {\n\treturn &fastly.UsageByServiceResponse{\n\t\tStatus: fastly.ToPointer(\"success\"),\n\t\tData: &fastly.ServicesByRegionsUsage{\n\t\t\t\"usa\": &fastly.ServicesUsage{\n\t\t\t\t\"svc123\": &fastly.Usage{\n\t\t\t\t\tBandwidth:       fastly.ToPointer(uint64(1000)),\n\t\t\t\t\tRequests:        fastly.ToPointer(uint64(500)),\n\t\t\t\t\tComputeRequests: fastly.ToPointer(uint64(100)),\n\t\t\t\t},\n\t\t\t},\n\t\t\t\"europe\": &fastly.ServicesUsage{\n\t\t\t\t\"svc456\": &fastly.Usage{\n\t\t\t\t\tBandwidth:       fastly.ToPointer(uint64(2000)),\n\t\t\t\t\tRequests:        fastly.ToPointer(uint64(300)),\n\t\t\t\t\tComputeRequests: fastly.ToPointer(uint64(50)),\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}, nil\n}\n\nfunc getUsageNonSuccess(_ context.Context, _ *fastly.GetUsageInput) (*fastly.UsageResponse, error) {\n\treturn &fastly.UsageResponse{\n\t\tStatus:  fastly.ToPointer(\"error\"),\n\t\tMessage: fastly.ToPointer(\"bad request\"),\n\t}, nil\n}\n\nfunc getUsageError(_ context.Context, _ *fastly.GetUsageInput) (*fastly.UsageResponse, error) {\n\treturn nil, errTest\n}\n"
  },
  {
    "path": "pkg/commands/tls/config/config_test.go",
    "content": "package config_test\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\troot \"github.com/fastly/cli/pkg/commands/tls/config\"\n\t\"github.com/fastly/cli/pkg/mock\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n)\n\nconst (\n\tvalidateAPIError   = \"validate API error\"\n\tvalidateAPISuccess = \"validate API success\"\n\tmockResponseID     = \"123\"\n)\n\nfunc TestDescribe(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --id flag\",\n\t\t\tWantError: \"error parsing arguments: required flag --id not provided\",\n\t\t},\n\t\t{\n\t\t\tName: validateAPIError,\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetCustomTLSConfigurationFn: func(_ context.Context, _ *fastly.GetCustomTLSConfigurationInput) (*fastly.CustomTLSConfiguration, error) {\n\t\t\t\t\treturn nil, testutil.Err\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:      \"--id example\",\n\t\t\tWantError: testutil.Err.Error(),\n\t\t},\n\t\t{\n\t\t\tName: validateAPISuccess,\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetCustomTLSConfigurationFn: func(_ context.Context, _ *fastly.GetCustomTLSConfigurationInput) (*fastly.CustomTLSConfiguration, error) {\n\t\t\t\t\tt := testutil.Date\n\t\t\t\t\treturn &fastly.CustomTLSConfiguration{\n\t\t\t\t\t\tID:   mockResponseID,\n\t\t\t\t\t\tName: \"Foo\",\n\t\t\t\t\t\tDNSRecords: []*fastly.DNSRecord{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tID:         \"456\",\n\t\t\t\t\t\t\t\tRecordType: \"Bar\",\n\t\t\t\t\t\t\t\tRegion:     \"Baz\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tBulk:          true,\n\t\t\t\t\t\tDefault:       true,\n\t\t\t\t\t\tHTTPProtocols: []string{\"1.1\"},\n\t\t\t\t\t\tTLSProtocols:  []string{\"1.3\"},\n\t\t\t\t\t\tCreatedAt:     &t,\n\t\t\t\t\t\tUpdatedAt:     &t,\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:       \"--id example\",\n\t\t\tWantOutput: \"\\nID: \" + mockResponseID + \"\\nName: Foo\\nDNS Record ID: 456\\nDNS Record Type: Bar\\nDNS Record Region: Baz\\nBulk: true\\nDefault: true\\nHTTP Protocol: 1.1\\nTLS Protocol: 1.3\\nCreated at: 2021-06-15 23:00:00 +0000 UTC\\nUpdated at: 2021-06-15 23:00:00 +0000 UTC\\n\",\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, \"describe\"}, scenarios)\n}\n\nfunc TestList(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName: validateAPIError,\n\t\t\tAPI: &mock.API{\n\t\t\t\tListCustomTLSConfigurationsFn: func(_ context.Context, _ *fastly.ListCustomTLSConfigurationsInput) ([]*fastly.CustomTLSConfiguration, error) {\n\t\t\t\t\treturn nil, testutil.Err\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: testutil.Err.Error(),\n\t\t},\n\t\t{\n\t\t\tName: validateAPISuccess,\n\t\t\tAPI: &mock.API{\n\t\t\t\tListCustomTLSConfigurationsFn: func(_ context.Context, _ *fastly.ListCustomTLSConfigurationsInput) ([]*fastly.CustomTLSConfiguration, error) {\n\t\t\t\t\tt := testutil.Date\n\t\t\t\t\treturn []*fastly.CustomTLSConfiguration{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tID:   mockResponseID,\n\t\t\t\t\t\t\tName: \"Foo\",\n\t\t\t\t\t\t\tDNSRecords: []*fastly.DNSRecord{\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tID:         \"456\",\n\t\t\t\t\t\t\t\t\tRecordType: \"Bar\",\n\t\t\t\t\t\t\t\t\tRegion:     \"Baz\",\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tBulk:          true,\n\t\t\t\t\t\t\tDefault:       true,\n\t\t\t\t\t\t\tHTTPProtocols: []string{\"1.1\"},\n\t\t\t\t\t\t\tTLSProtocols:  []string{\"1.3\"},\n\t\t\t\t\t\t\tCreatedAt:     &t,\n\t\t\t\t\t\t\tUpdatedAt:     &t,\n\t\t\t\t\t\t},\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:       \"--verbose\",\n\t\t\tWantOutput: \"\\nID: \" + mockResponseID + \"\\nName: Foo\\nDNS Record ID: 456\\nDNS Record Type: Bar\\nDNS Record Region: Baz\\nBulk: true\\nDefault: true\\nHTTP Protocol: 1.1\\nTLS Protocol: 1.3\\nCreated at: 2021-06-15 23:00:00 +0000 UTC\\nUpdated at: 2021-06-15 23:00:00 +0000 UTC\\n\",\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, \"list\"}, scenarios)\n}\n\nfunc TestUpdate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --id flag\",\n\t\t\tArgs:      \"--name example\",\n\t\t\tWantError: \"error parsing arguments: required flag --id not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --name flag\",\n\t\t\tArgs:      \"--id 123\",\n\t\t\tWantError: \"error parsing arguments: required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tName: validateAPIError,\n\t\t\tAPI: &mock.API{\n\t\t\t\tUpdateCustomTLSConfigurationFn: func(_ context.Context, _ *fastly.UpdateCustomTLSConfigurationInput) (*fastly.CustomTLSConfiguration, error) {\n\t\t\t\t\treturn nil, testutil.Err\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:      \"--id example --name example\",\n\t\t\tWantError: testutil.Err.Error(),\n\t\t},\n\t\t{\n\t\t\tName: validateAPISuccess,\n\t\t\tAPI: &mock.API{\n\t\t\t\tUpdateCustomTLSConfigurationFn: func(_ context.Context, _ *fastly.UpdateCustomTLSConfigurationInput) (*fastly.CustomTLSConfiguration, error) {\n\t\t\t\t\treturn &fastly.CustomTLSConfiguration{\n\t\t\t\t\t\tID: mockResponseID,\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:       \"--id example --name example\",\n\t\t\tWantOutput: fmt.Sprintf(\"Updated TLS Configuration '%s'\", mockResponseID),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, \"update\"}, scenarios)\n}\n"
  },
  {
    "path": "pkg/commands/tls/config/describe.go",
    "content": "package config\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\nconst include = \"dns_records\"\n\n// NewDescribeCommand returns a usable command registered under the parent.\nfunc NewDescribeCommand(parent argparser.Registerer, g *global.Data) *DescribeCommand {\n\tvar c DescribeCommand\n\tc.CmdClause = parent.Command(\"describe\", \"Show a TLS configuration\").Alias(\"get\")\n\tc.Globals = g\n\n\t// Required.\n\tc.CmdClause.Flag(\"id\", \"Alphanumeric string identifying a TLS configuration\").Required().StringVar(&c.id)\n\n\t// Optional.\n\tc.CmdClause.Flag(\"include\", \"Include related objects (comma-separated values)\").HintOptions(include).EnumVar(&c.include, include)\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\n\treturn &c\n}\n\n// DescribeCommand calls the Fastly API to describe an appropriate resource.\ntype DescribeCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tid      string\n\tinclude string\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DescribeCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tinput := c.constructInput()\n\n\to, err := c.Globals.APIClient.GetCustomTLSConfiguration(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"TLS Configuration ID\": c.id,\n\t\t})\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\treturn c.print(out, o)\n}\n\n// constructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *DescribeCommand) constructInput() *fastly.GetCustomTLSConfigurationInput {\n\tvar input fastly.GetCustomTLSConfigurationInput\n\n\tinput.ID = c.id\n\n\tif c.include != \"\" {\n\t\tinput.Include = c.include\n\t}\n\n\treturn &input\n}\n\n// print displays the information returned from the API.\nfunc (c *DescribeCommand) print(out io.Writer, r *fastly.CustomTLSConfiguration) error {\n\tfmt.Fprintf(out, \"\\nID: %s\\n\", r.ID)\n\tfmt.Fprintf(out, \"Name: %s\\n\", r.Name)\n\n\tif len(r.DNSRecords) > 0 {\n\t\tfor _, v := range r.DNSRecords {\n\t\t\tif v != nil {\n\t\t\t\tfmt.Fprintf(out, \"DNS Record ID: %s\\n\", v.ID)\n\t\t\t\tfmt.Fprintf(out, \"DNS Record Type: %s\\n\", v.RecordType)\n\t\t\t\tfmt.Fprintf(out, \"DNS Record Region: %s\\n\", v.Region)\n\t\t\t}\n\t\t}\n\t}\n\n\tfmt.Fprintf(out, \"Bulk: %t\\n\", r.Bulk)\n\tfmt.Fprintf(out, \"Default: %t\\n\", r.Default)\n\n\tif len(r.HTTPProtocols) > 0 {\n\t\tfor _, v := range r.HTTPProtocols {\n\t\t\tfmt.Fprintf(out, \"HTTP Protocol: %s\\n\", v)\n\t\t}\n\t}\n\n\tif len(r.TLSProtocols) > 0 {\n\t\tfor _, v := range r.TLSProtocols {\n\t\t\tfmt.Fprintf(out, \"TLS Protocol: %s\\n\", v)\n\t\t}\n\t}\n\n\tif r.CreatedAt != nil {\n\t\tfmt.Fprintf(out, \"Created at: %s\\n\", r.CreatedAt)\n\t}\n\tif r.UpdatedAt != nil {\n\t\tfmt.Fprintf(out, \"Updated at: %s\\n\", r.UpdatedAt)\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/tls/config/doc.go",
    "content": "// Package config contains commands to inspect and manipulate Fastly TLS\n// configuration.\npackage config\n"
  },
  {
    "path": "pkg/commands/tls/config/list.go",
    "content": "package config\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"strings\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tvar c ListCommand\n\tc.CmdClause = parent.Command(\"list\", \"List all TLS configurations\")\n\tc.Globals = g\n\n\t// Optional.\n\tc.CmdClause.Flag(\"filter-bulk\", \"Optionally filter by the bulk attribute\").Action(c.filterBulk.Set).BoolVar(&c.filterBulk.Value)\n\tc.CmdClause.Flag(\"include\", \"Include related objects (comma-separated values)\").HintOptions(include).EnumVar(&c.include, include)\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\tc.CmdClause.Flag(\"page\", \"Page number of data set to fetch\").IntVar(&c.pageNumber)\n\tc.CmdClause.Flag(\"per-page\", \"Number of records per page\").IntVar(&c.pageSize)\n\n\treturn &c\n}\n\n// ListCommand calls the Fastly API to list appropriate resources.\ntype ListCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tfilterBulk argparser.OptionalBool\n\tinclude    string\n\tpageNumber int\n\tpageSize   int\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *ListCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tinput := c.constructInput()\n\n\to, err := c.Globals.APIClient.ListCustomTLSConfigurations(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Filter Bulk\": c.filterBulk,\n\t\t\t\"Include\":     c.include,\n\t\t\t\"Page Number\": c.pageNumber,\n\t\t\t\"Page Size\":   c.pageSize,\n\t\t})\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\tif c.Globals.Verbose() {\n\t\tc.printVerbose(out, o)\n\t} else {\n\t\terr = c.printSummary(out, o)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\n// constructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *ListCommand) constructInput() *fastly.ListCustomTLSConfigurationsInput {\n\tvar input fastly.ListCustomTLSConfigurationsInput\n\n\tif c.filterBulk.WasSet {\n\t\tinput.FilterBulk = c.filterBulk.Value\n\t}\n\tif c.include != \"\" {\n\t\tinput.Include = c.include\n\t}\n\tif c.pageNumber > 0 {\n\t\tinput.PageNumber = c.pageNumber\n\t}\n\tif c.pageSize > 0 {\n\t\tinput.PageSize = c.pageSize\n\t}\n\n\treturn &input\n}\n\n// printVerbose displays the information returned from the API in a verbose\n// format.\nfunc (c *ListCommand) printVerbose(out io.Writer, rs []*fastly.CustomTLSConfiguration) {\n\tfor _, r := range rs {\n\t\tfmt.Fprintf(out, \"ID: %s\\n\", r.ID)\n\t\tfmt.Fprintf(out, \"Name: %s\\n\", r.Name)\n\n\t\tif len(r.DNSRecords) > 0 {\n\t\t\tfor _, v := range r.DNSRecords {\n\t\t\t\tif v != nil {\n\t\t\t\t\tfmt.Fprintf(out, \"DNS Record ID: %s\\n\", v.ID)\n\t\t\t\t\tfmt.Fprintf(out, \"DNS Record Type: %s\\n\", v.RecordType)\n\t\t\t\t\tfmt.Fprintf(out, \"DNS Record Region: %s\\n\", v.Region)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tfmt.Fprintf(out, \"Bulk: %t\\n\", r.Bulk)\n\t\tfmt.Fprintf(out, \"Default: %t\\n\", r.Default)\n\n\t\tif len(r.HTTPProtocols) > 0 {\n\t\t\tfor _, v := range r.HTTPProtocols {\n\t\t\t\tfmt.Fprintf(out, \"HTTP Protocol: %s\\n\", v)\n\t\t\t}\n\t\t}\n\n\t\tif len(r.TLSProtocols) > 0 {\n\t\t\tfor _, v := range r.TLSProtocols {\n\t\t\t\tfmt.Fprintf(out, \"TLS Protocol: %s\\n\", v)\n\t\t\t}\n\t\t}\n\n\t\tif r.CreatedAt != nil {\n\t\t\tfmt.Fprintf(out, \"Created at: %s\\n\", r.CreatedAt)\n\t\t}\n\t\tif r.UpdatedAt != nil {\n\t\t\tfmt.Fprintf(out, \"Updated at: %s\\n\", r.UpdatedAt)\n\t\t}\n\n\t\tfmt.Fprintf(out, \"\\n\")\n\t}\n}\n\n// printSummary displays the information returned from the API in a summarised\n// format.\nfunc (c *ListCommand) printSummary(out io.Writer, rs []*fastly.CustomTLSConfiguration) error {\n\tt := text.NewTable(out)\n\tt.AddHeader(\"NAME\", \"ID\", \"BULK\", \"DEFAULT\", \"TLS PROTOCOLS\", \"HTTP PROTOCOLS\", \"DNS RECORDS\")\n\tfor _, r := range rs {\n\t\tdrs := make([]string, len(r.DNSRecords))\n\t\tfor i, v := range r.DNSRecords {\n\t\t\tif v != nil {\n\t\t\t\tdrs[i] = v.ID\n\t\t\t}\n\t\t}\n\t\tt.AddLine(\n\t\t\tr.Name,\n\t\t\tr.ID,\n\t\t\tr.Bulk,\n\t\t\tr.Default,\n\t\t\tstrings.Join(r.TLSProtocols, \", \"),\n\t\t\tstrings.Join(r.HTTPProtocols, \", \"),\n\t\t\tstrings.Join(drs, \", \"),\n\t\t)\n\t}\n\tt.Print()\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/tls/config/root.go",
    "content": "package config\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"tls-config\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Apply configuration options for each TLS enabled domain\")\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/tls/config/update.go",
    "content": "package config\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tvar c UpdateCommand\n\tc.CmdClause = parent.Command(\"update\", \"Update a TLS configuration\")\n\tc.Globals = g\n\n\t// Required.\n\tc.CmdClause.Flag(\"id\", \"Alphanumeric string identifying a TLS configuration\").Required().StringVar(&c.id)\n\tc.CmdClause.Flag(\"name\", \"A custom name for your TLS configuration\").Required().StringVar(&c.name)\n\treturn &c\n}\n\n// UpdateCommand calls the Fastly API to update an appropriate resource.\ntype UpdateCommand struct {\n\targparser.Base\n\n\tid   string\n\tname string\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *UpdateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tinput := c.constructInput()\n\n\tr, err := c.Globals.APIClient.UpdateCustomTLSConfiguration(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"TLS Configuration ID\": c.id,\n\t\t})\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Updated TLS Configuration '%s'\", r.ID)\n\treturn nil\n}\n\n// constructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *UpdateCommand) constructInput() *fastly.UpdateCustomTLSConfigurationInput {\n\tvar input fastly.UpdateCustomTLSConfigurationInput\n\n\tinput.ID = c.id\n\tinput.Name = c.name\n\n\treturn &input\n}\n"
  },
  {
    "path": "pkg/commands/tls/custom/activation/activation_test.go",
    "content": "package activation_test\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\troot \"github.com/fastly/cli/pkg/commands/tls/custom\"\n\tsub \"github.com/fastly/cli/pkg/commands/tls/custom/activation\"\n\t\"github.com/fastly/cli/pkg/mock\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n)\n\nconst (\n\tmockResponseID        = \"123\"\n\tmockResponseCertID    = \"456\"\n\tmockResponseConfigID  = \"789\"\n\tmockResponseDomain    = \"tls.example.com\"\n\tvalidateAPIError      = \"validate API error\"\n\tvalidateAPISuccess    = \"validate API success\"\n\tvalidateMissingIDFlag = \"validate missing --id flag\"\n)\n\nfunc TestTLSCustomActivationEnable(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing CertID flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--tls-config-id %s --tls-domain %s\", mockResponseConfigID, mockResponseDomain),\n\t\t\tWantError: \"required flag --cert-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing ConfigID flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--cert-id %s --tls-domain %s\", mockResponseCertID, mockResponseDomain),\n\t\t\tWantError: \"required flag --tls-config-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing Domain Flag\",\n\t\t\tArgs:      fmt.Sprintf(\"--cert-id %s --tls-config-id %s\", mockResponseCertID, mockResponseConfigID),\n\t\t\tWantError: \"required flag --tls-domain not provided\",\n\t\t},\n\t\t{\n\t\t\tName: validateAPIError,\n\t\t\tAPI: &mock.API{\n\t\t\t\tCreateTLSActivationFn: func(_ context.Context, _ *fastly.CreateTLSActivationInput) (*fastly.TLSActivation, error) {\n\t\t\t\t\treturn nil, testutil.Err\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:      fmt.Sprintf(\"--cert-id %s --tls-config-id %s --tls-domain %s\", mockResponseCertID, mockResponseConfigID, mockResponseDomain),\n\t\t\tWantError: testutil.Err.Error(),\n\t\t},\n\t\t{\n\t\t\tName: validateAPISuccess,\n\t\t\tAPI: &mock.API{\n\t\t\t\tCreateTLSActivationFn: func(_ context.Context, _ *fastly.CreateTLSActivationInput) (*fastly.TLSActivation, error) {\n\t\t\t\t\treturn &fastly.TLSActivation{\n\t\t\t\t\t\tID: mockResponseID,\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:       fmt.Sprintf(\"--cert-id %s --tls-config-id %s --tls-domain %s\", mockResponseCertID, mockResponseConfigID, mockResponseDomain),\n\t\t\tWantOutput: fmt.Sprintf(\"SUCCESS: Enabled TLS Activation '%s' (Certificate '%s', Configuration '%s')\", mockResponseID, mockResponseCertID, mockResponseConfigID),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"enable\"}, scenarios)\n}\n\nfunc TestTLSCustomActivationDisable(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      validateMissingIDFlag,\n\t\t\tWantError: \"error parsing arguments: required flag --id not provided\",\n\t\t},\n\t\t{\n\t\t\tName: validateAPIError,\n\t\t\tAPI: &mock.API{\n\t\t\t\tDeleteTLSActivationFn: func(_ context.Context, _ *fastly.DeleteTLSActivationInput) error {\n\t\t\t\t\treturn testutil.Err\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:      \"--id example\",\n\t\t\tWantError: testutil.Err.Error(),\n\t\t},\n\t\t{\n\t\t\tName: validateAPISuccess,\n\t\t\tAPI: &mock.API{\n\t\t\t\tDeleteTLSActivationFn: func(_ context.Context, _ *fastly.DeleteTLSActivationInput) error {\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:       \"--id example\",\n\t\t\tWantOutput: \"Disabled TLS Activation 'example'\",\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"disable\"}, scenarios)\n}\n\nfunc TestTLSCustomActivationDescribe(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      validateMissingIDFlag,\n\t\t\tWantError: \"error parsing arguments: required flag --id not provided\",\n\t\t},\n\t\t{\n\t\t\tName: validateAPIError,\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetTLSActivationFn: func(_ context.Context, _ *fastly.GetTLSActivationInput) (*fastly.TLSActivation, error) {\n\t\t\t\t\treturn nil, testutil.Err\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:      \"--id example\",\n\t\t\tWantError: testutil.Err.Error(),\n\t\t},\n\t\t{\n\t\t\tName: validateAPISuccess,\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetTLSActivationFn: func(_ context.Context, _ *fastly.GetTLSActivationInput) (*fastly.TLSActivation, error) {\n\t\t\t\t\tt := testutil.Date\n\t\t\t\t\treturn &fastly.TLSActivation{\n\t\t\t\t\t\tID:        mockResponseID,\n\t\t\t\t\t\tCreatedAt: &t,\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:       \"--id example\",\n\t\t\tWantOutput: \"\\nID: \" + mockResponseID + \"\\nCreated at: 2021-06-15 23:00:00 +0000 UTC\\n\",\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"describe\"}, scenarios)\n}\n\nfunc TestTLSCustomActivationList(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName: validateAPIError,\n\t\t\tAPI: &mock.API{\n\t\t\t\tListTLSActivationsFn: func(_ context.Context, _ *fastly.ListTLSActivationsInput) ([]*fastly.TLSActivation, error) {\n\t\t\t\t\treturn nil, testutil.Err\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: testutil.Err.Error(),\n\t\t},\n\t\t{\n\t\t\tName: validateAPISuccess,\n\t\t\tAPI: &mock.API{\n\t\t\t\tListTLSActivationsFn: func(_ context.Context, _ *fastly.ListTLSActivationsInput) ([]*fastly.TLSActivation, error) {\n\t\t\t\t\tt := testutil.Date\n\t\t\t\t\treturn []*fastly.TLSActivation{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tID:        mockResponseID,\n\t\t\t\t\t\t\tCreatedAt: &t,\n\t\t\t\t\t\t},\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:       \"--verbose\",\n\t\t\tWantOutput: \"\\nID: \" + mockResponseID + \"\\nCreated at: 2021-06-15 23:00:00 +0000 UTC\\n\",\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"list\"}, scenarios)\n}\n\nfunc TestTLSCustomActivationUpdate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      validateMissingIDFlag,\n\t\t\tArgs:      \"--cert-id example\",\n\t\t\tWantError: \"required flag --id not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      validateMissingIDFlag,\n\t\t\tArgs:      \"--id example\",\n\t\t\tWantError: \"required flag --cert-id not provided\",\n\t\t},\n\t\t{\n\t\t\tName: validateAPIError,\n\t\t\tAPI: &mock.API{\n\t\t\t\tUpdateTLSActivationFn: func(_ context.Context, _ *fastly.UpdateTLSActivationInput) (*fastly.TLSActivation, error) {\n\t\t\t\t\treturn nil, testutil.Err\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:      \"--cert-id example --id example\",\n\t\t\tWantError: testutil.Err.Error(),\n\t\t},\n\t\t{\n\t\t\tName: validateAPISuccess,\n\t\t\tAPI: &mock.API{\n\t\t\t\tUpdateTLSActivationFn: func(_ context.Context, _ *fastly.UpdateTLSActivationInput) (*fastly.TLSActivation, error) {\n\t\t\t\t\treturn &fastly.TLSActivation{\n\t\t\t\t\t\tID: mockResponseID,\n\t\t\t\t\t\tCertificate: &fastly.CustomTLSCertificate{\n\t\t\t\t\t\t\tID: mockResponseCertID,\n\t\t\t\t\t\t},\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:       \"--cert-id example --id example\",\n\t\t\tWantOutput: fmt.Sprintf(\"Updated TLS Activation Certificate '%s' (previously: 'example')\", mockResponseCertID),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"update\"}, scenarios)\n}\n"
  },
  {
    "path": "pkg/commands/tls/custom/activation/create.go",
    "content": "package activation\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tvar c CreateCommand\n\tc.CmdClause = parent.Command(\"enable\", \"Enable TLS for a particular TLS domain and certificate combination\").Alias(\"add\")\n\tc.Globals = g\n\n\t// Required.\n\tc.CmdClause.Flag(\"cert-id\", \"Alphanumeric string identifying a TLS certificate\").Required().StringVar(&c.certID)\n\tc.CmdClause.Flag(\"tls-config-id\", \"Alphanumeric string identifying a TLS configuration\").Required().StringVar(&c.tlsConfigID)\n\tc.CmdClause.Flag(\"tls-domain\", \"The domain name associated with the TLS certificate\").Required().StringVar(&c.tlsDomain)\n\n\treturn &c\n}\n\n// CreateCommand calls the Fastly API to create an appropriate resource.\ntype CreateCommand struct {\n\targparser.Base\n\n\tcertID      string\n\ttlsConfigID string\n\ttlsDomain   string\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *CreateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tinput := c.constructInput()\n\n\tr, err := c.Globals.APIClient.CreateTLSActivation(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"TLS Configuration ID\":          c.tlsConfigID,\n\t\t\t\"TLS Activation Certificate ID\": c.certID,\n\t\t\t\"TLS Domain\":                    c.tlsDomain,\n\t\t})\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Enabled TLS Activation '%s' (Certificate '%s', Configuration '%s')\", r.ID, c.certID, c.tlsConfigID)\n\treturn nil\n}\n\n// constructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *CreateCommand) constructInput() *fastly.CreateTLSActivationInput {\n\tvar input fastly.CreateTLSActivationInput\n\n\tinput.Configuration = &fastly.TLSConfiguration{ID: c.tlsConfigID}\n\tinput.Certificate = &fastly.CustomTLSCertificate{ID: c.certID}\n\tinput.Domain = &fastly.TLSDomain{ID: c.tlsDomain}\n\n\treturn &input\n}\n"
  },
  {
    "path": "pkg/commands/tls/custom/activation/delete.go",
    "content": "package activation\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tvar c DeleteCommand\n\tc.CmdClause = parent.Command(\"disable\", \"Disable TLS on the domain associated with this TLS activation\").Alias(\"remove\")\n\tc.Globals = g\n\n\t// Required.\n\tc.CmdClause.Flag(\"id\", \"Alphanumeric string identifying a TLS activation\").Required().StringVar(&c.id)\n\n\treturn &c\n}\n\n// DeleteCommand calls the Fastly API to delete an appropriate resource.\ntype DeleteCommand struct {\n\targparser.Base\n\n\tid string\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DeleteCommand) Exec(_ io.Reader, out io.Writer) error {\n\tinput := c.constructInput()\n\n\terr := c.Globals.APIClient.DeleteTLSActivation(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"TLS Activation ID\": c.id,\n\t\t})\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Disabled TLS Activation '%s'\", c.id)\n\treturn nil\n}\n\n// constructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *DeleteCommand) constructInput() *fastly.DeleteTLSActivationInput {\n\tvar input fastly.DeleteTLSActivationInput\n\n\tinput.ID = c.id\n\n\treturn &input\n}\n"
  },
  {
    "path": "pkg/commands/tls/custom/activation/describe.go",
    "content": "package activation\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\nvar include = []string{\"tls_certificate\", \"tls_configuration\", \"tls_domain\"}\n\n// NewDescribeCommand returns a usable command registered under the parent.\nfunc NewDescribeCommand(parent argparser.Registerer, g *global.Data) *DescribeCommand {\n\tvar c DescribeCommand\n\tc.CmdClause = parent.Command(\"describe\", \"Show a TLS configuration\").Alias(\"get\")\n\tc.Globals = g\n\n\t// Required.\n\tc.CmdClause.Flag(\"id\", \"Alphanumeric string identifying a TLS activation\").Required().StringVar(&c.id)\n\n\t// Optional.\n\tc.CmdClause.Flag(\"include\", \"Include related objects (comma-separated values)\").HintOptions(include...).EnumVar(&c.include, include...)\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\n\treturn &c\n}\n\n// DescribeCommand calls the Fastly API to describe an appropriate resource.\ntype DescribeCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tid      string\n\tinclude string\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DescribeCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tinput := c.constructInput()\n\n\to, err := c.Globals.APIClient.GetTLSActivation(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"TLS Activation ID\": c.id,\n\t\t})\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\treturn c.print(out, o)\n}\n\n// constructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *DescribeCommand) constructInput() *fastly.GetTLSActivationInput {\n\tvar input fastly.GetTLSActivationInput\n\n\tinput.ID = c.id\n\n\tif c.include != \"\" {\n\t\tinput.Include = &c.include\n\t}\n\n\treturn &input\n}\n\n// print displays the information returned from the API.\nfunc (c *DescribeCommand) print(out io.Writer, r *fastly.TLSActivation) error {\n\tfmt.Fprintf(out, \"\\nID: %s\\n\", r.ID)\n\n\tif r.CreatedAt != nil {\n\t\tfmt.Fprintf(out, \"Created at: %s\\n\", r.CreatedAt)\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/tls/custom/activation/doc.go",
    "content": "// Package activation contains commands to inspect and manipulate Fastly custom\n// TLS activations.\npackage activation\n"
  },
  {
    "path": "pkg/commands/tls/custom/activation/list.go",
    "content": "package activation\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tvar c ListCommand\n\tc.CmdClause = parent.Command(\"list\", \"List all TLS activations\")\n\tc.Globals = g\n\n\t// Optional.\n\tc.CmdClause.Flag(\"filter-cert\", \"Limit the returned activations to a specific certificate\").StringVar(&c.filterTLSCertID)\n\tc.CmdClause.Flag(\"filter-config\", \"Limit the returned activations to a specific TLS configuration\").StringVar(&c.filterTLSConfigID)\n\tc.CmdClause.Flag(\"filter-domain\", \"Limit the returned rules to a specific domain name\").StringVar(&c.filterTLSDomainID)\n\tc.CmdClause.Flag(\"include\", \"Include related objects (comma-separated values)\").HintOptions(include...).EnumVar(&c.include, include...)\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\tc.CmdClause.Flag(\"page\", \"Page number of data set to fetch\").IntVar(&c.pageNumber)\n\tc.CmdClause.Flag(\"per-page\", \"Number of records per page\").IntVar(&c.pageSize)\n\n\treturn &c\n}\n\n// ListCommand calls the Fastly API to list appropriate resources.\ntype ListCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tfilterTLSCertID   string\n\tfilterTLSConfigID string\n\tfilterTLSDomainID string\n\tinclude           string\n\tpageNumber        int\n\tpageSize          int\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *ListCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tinput := c.constructInput()\n\n\to, err := c.Globals.APIClient.ListTLSActivations(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Filter TLS Certificate ID\":   c.filterTLSCertID,\n\t\t\t\"Filter TLS Configuration ID\": c.filterTLSConfigID,\n\t\t\t\"Filter TLS Domain ID\":        c.filterTLSDomainID,\n\t\t\t\"Include\":                     c.include,\n\t\t\t\"Page Number\":                 c.pageNumber,\n\t\t\t\"Page Size\":                   c.pageSize,\n\t\t})\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\tif c.Globals.Verbose() {\n\t\tc.printVerbose(out, o)\n\t} else {\n\t\terr = c.printSummary(out, o)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\n// constructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *ListCommand) constructInput() *fastly.ListTLSActivationsInput {\n\tvar input fastly.ListTLSActivationsInput\n\n\tif c.filterTLSCertID != \"\" {\n\t\tinput.FilterTLSCertificateID = c.filterTLSCertID\n\t}\n\tif c.filterTLSConfigID != \"\" {\n\t\tinput.FilterTLSConfigurationID = c.filterTLSConfigID\n\t}\n\tif c.filterTLSDomainID != \"\" {\n\t\tinput.FilterTLSDomainID = c.filterTLSDomainID\n\t}\n\tif c.include != \"\" {\n\t\tinput.Include = c.include\n\t}\n\tif c.pageNumber > 0 {\n\t\tinput.PageNumber = c.pageNumber\n\t}\n\tif c.pageSize > 0 {\n\t\tinput.PageSize = c.pageSize\n\t}\n\n\treturn &input\n}\n\n// printVerbose displays the information returned from the API in a verbose\n// format.\nfunc (c *ListCommand) printVerbose(out io.Writer, rs []*fastly.TLSActivation) {\n\tfor _, r := range rs {\n\t\tfmt.Fprintf(out, \"\\nID: %s\\n\", r.ID)\n\n\t\tif r.CreatedAt != nil {\n\t\t\tfmt.Fprintf(out, \"Created at: %s\\n\", r.CreatedAt)\n\t\t}\n\n\t\tfmt.Fprintf(out, \"\\n\")\n\t}\n}\n\n// printSummary displays the information returned from the API in a summarised\n// format.\nfunc (c *ListCommand) printSummary(out io.Writer, rs []*fastly.TLSActivation) error {\n\tt := text.NewTable(out)\n\tt.AddHeader(\"ID\", \"CREATED_AT\")\n\tfor _, r := range rs {\n\t\tt.AddLine(r.ID, r.CreatedAt)\n\t}\n\tt.Print()\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/tls/custom/activation/root.go",
    "content": "package activation\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"activation\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Upload and manage TLS activations\")\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/tls/custom/activation/update.go",
    "content": "package activation\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tvar c UpdateCommand\n\tc.CmdClause = parent.Command(\"update\", \"Update the certificate used to terminate TLS traffic for the domain associated with this TLS activation\")\n\tc.Globals = g\n\n\t// Required.\n\tc.CmdClause.Flag(\"cert-id\", \"Alphanumeric string identifying a TLS certificate\").Required().StringVar(&c.certID)\n\tc.CmdClause.Flag(\"id\", \"Alphanumeric string identifying a TLS activation\").Required().StringVar(&c.id)\n\treturn &c\n}\n\n// UpdateCommand calls the Fastly API to update an appropriate resource.\ntype UpdateCommand struct {\n\targparser.Base\n\n\tcertID string\n\tid     string\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *UpdateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tinput := c.constructInput()\n\n\tr, err := c.Globals.APIClient.UpdateTLSActivation(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"TLS Activation ID\":             c.id,\n\t\t\t\"TLS Activation Certificate ID\": c.certID,\n\t\t})\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Updated TLS Activation Certificate '%s' (previously: '%s')\", r.Certificate.ID, input.Certificate.ID)\n\treturn nil\n}\n\n// constructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *UpdateCommand) constructInput() *fastly.UpdateTLSActivationInput {\n\tvar input fastly.UpdateTLSActivationInput\n\n\tinput.ID = c.id\n\tinput.Certificate = &fastly.CustomTLSCertificate{ID: c.certID}\n\n\treturn &input\n}\n"
  },
  {
    "path": "pkg/commands/tls/custom/certificate/certificate_test.go",
    "content": "package certificate_test\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\troot \"github.com/fastly/cli/pkg/commands/tls/custom\"\n\tsub \"github.com/fastly/cli/pkg/commands/tls/custom/certificate\"\n\t\"github.com/fastly/cli/pkg/mock\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n)\n\nconst (\n\tmockResponseID        = \"123\"\n\tmockFieldValue        = \"example\"\n\tvalidateAPIError      = \"validate API error\"\n\tvalidateAPISuccess    = \"validate API success\"\n\tvalidateMissingIDFlag = \"validate missing --id flag\"\n)\n\nfunc TestTLSCustomCertCreate(t *testing.T) {\n\tvar content string\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --cert-blob and --cert-path flags\",\n\t\t\tWantError: \"neither --cert-path or --cert-blob provided, one must be provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate specifying both --cert-blob and --cert-path flags\",\n\t\t\tArgs:      \"--cert-blob foo --cert-path bar\",\n\t\t\tWantError: \"cert-path and cert-blob provided, only one can be specified\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate invalid --cert-path arg\",\n\t\t\tArgs:      \"--cert-path ............\",\n\t\t\tWantError: \"error reading cert-path\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate custom cert is submitted\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tCreateCustomTLSCertificateFn: func(_ context.Context, certInput *fastly.CreateCustomTLSCertificateInput) (*fastly.CustomTLSCertificate, error) {\n\t\t\t\t\tcontent = certInput.CertBlob\n\t\t\t\t\treturn &fastly.CustomTLSCertificate{\n\t\t\t\t\t\tID: mockResponseID,\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:            \"--cert-path ./testdata/certificate.crt\",\n\t\t\tWantOutput:      fmt.Sprintf(\"Created TLS Certificate '%s'\", mockResponseID),\n\t\t\tPathContentFlag: &testutil.PathContentFlag{Flag: \"cert-path\", Fixture: \"certificate.crt\", Content: func() string { return content }},\n\t\t},\n\t\t{\n\t\t\tName: validateAPIError,\n\t\t\tAPI: &mock.API{\n\t\t\t\tCreateCustomTLSCertificateFn: func(_ context.Context, certInput *fastly.CreateCustomTLSCertificateInput) (*fastly.CustomTLSCertificate, error) {\n\t\t\t\t\tcontent = certInput.CertBlob\n\t\t\t\t\treturn nil, testutil.Err\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:            \"--cert-blob example\",\n\t\t\tWantError:       testutil.Err.Error(),\n\t\t\tPathContentFlag: &testutil.PathContentFlag{Flag: \"cert-path\", Fixture: \"certificate.crt\", Content: func() string { return content }},\n\t\t},\n\t\t{\n\t\t\tName: validateAPISuccess,\n\t\t\tAPI: &mock.API{\n\t\t\t\tCreateCustomTLSCertificateFn: func(_ context.Context, certInput *fastly.CreateCustomTLSCertificateInput) (*fastly.CustomTLSCertificate, error) {\n\t\t\t\t\tcontent = certInput.CertBlob\n\t\t\t\t\treturn &fastly.CustomTLSCertificate{\n\t\t\t\t\t\tID: mockResponseID,\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:            \"--cert-blob example\",\n\t\t\tWantOutput:      fmt.Sprintf(\"Created TLS Certificate '%s'\", mockResponseID),\n\t\t\tPathContentFlag: &testutil.PathContentFlag{Flag: \"cert-path\", Fixture: \"certificate.crt\", Content: func() string { return content }},\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"create\"}, scenarios)\n}\n\nfunc TestTLSCustomCertDelete(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      validateMissingIDFlag,\n\t\t\tWantError: \"error parsing arguments: required flag --id not provided\",\n\t\t},\n\t\t{\n\t\t\tName: validateAPIError,\n\t\t\tAPI: &mock.API{\n\t\t\t\tDeleteCustomTLSCertificateFn: func(_ context.Context, _ *fastly.DeleteCustomTLSCertificateInput) error {\n\t\t\t\t\treturn testutil.Err\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:      \"--id example\",\n\t\t\tWantError: testutil.Err.Error(),\n\t\t},\n\t\t{\n\t\t\tName: validateAPISuccess,\n\t\t\tAPI: &mock.API{\n\t\t\t\tDeleteCustomTLSCertificateFn: func(_ context.Context, _ *fastly.DeleteCustomTLSCertificateInput) error {\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:       \"--id example\",\n\t\t\tWantOutput: \"Deleted TLS Certificate 'example'\",\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"delete\"}, scenarios)\n}\n\nfunc TestTLSCustomCertDescribe(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      validateMissingIDFlag,\n\t\t\tWantError: \"error parsing arguments: required flag --id not provided\",\n\t\t},\n\t\t{\n\t\t\tName: validateAPIError,\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetCustomTLSCertificateFn: func(_ context.Context, _ *fastly.GetCustomTLSCertificateInput) (*fastly.CustomTLSCertificate, error) {\n\t\t\t\t\treturn nil, testutil.Err\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:      \"--id example\",\n\t\t\tWantError: testutil.Err.Error(),\n\t\t},\n\t\t{\n\t\t\tName: validateAPISuccess,\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetCustomTLSCertificateFn: func(_ context.Context, _ *fastly.GetCustomTLSCertificateInput) (*fastly.CustomTLSCertificate, error) {\n\t\t\t\t\tt := testutil.Date\n\t\t\t\t\treturn &fastly.CustomTLSCertificate{\n\t\t\t\t\t\tID:                 mockResponseID,\n\t\t\t\t\t\tIssuedTo:           mockFieldValue,\n\t\t\t\t\t\tIssuer:             mockFieldValue,\n\t\t\t\t\t\tName:               mockFieldValue,\n\t\t\t\t\t\tReplace:            true,\n\t\t\t\t\t\tSerialNumber:       mockFieldValue,\n\t\t\t\t\t\tSignatureAlgorithm: mockFieldValue,\n\t\t\t\t\t\tCreatedAt:          &t,\n\t\t\t\t\t\tUpdatedAt:          &t,\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:       \"--id example\",\n\t\t\tWantOutput: \"\\nID: \" + mockResponseID + \"\\nIssued to: \" + mockFieldValue + \"\\nIssuer: \" + mockFieldValue + \"\\nName: \" + mockFieldValue + \"\\nReplace: true\\nSerial number: \" + mockFieldValue + \"\\nSignature algorithm: \" + mockFieldValue + \"\\nCreated at: 2021-06-15 23:00:00 +0000 UTC\\nUpdated at: 2021-06-15 23:00:00 +0000 UTC\\n\",\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"describe\"}, scenarios)\n}\n\nfunc TestTLSCustomCertList(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName: validateAPIError,\n\t\t\tAPI: &mock.API{\n\t\t\t\tListCustomTLSCertificatesFn: func(_ context.Context, _ *fastly.ListCustomTLSCertificatesInput) ([]*fastly.CustomTLSCertificate, error) {\n\t\t\t\t\treturn nil, testutil.Err\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: testutil.Err.Error(),\n\t\t},\n\t\t{\n\t\t\tName: validateAPISuccess,\n\t\t\tAPI: &mock.API{\n\t\t\t\tListCustomTLSCertificatesFn: func(_ context.Context, _ *fastly.ListCustomTLSCertificatesInput) ([]*fastly.CustomTLSCertificate, error) {\n\t\t\t\t\tt := testutil.Date\n\t\t\t\t\treturn []*fastly.CustomTLSCertificate{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tID:                 mockResponseID,\n\t\t\t\t\t\t\tIssuedTo:           mockFieldValue,\n\t\t\t\t\t\t\tIssuer:             mockFieldValue,\n\t\t\t\t\t\t\tName:               mockFieldValue,\n\t\t\t\t\t\t\tReplace:            true,\n\t\t\t\t\t\t\tSerialNumber:       mockFieldValue,\n\t\t\t\t\t\t\tSignatureAlgorithm: mockFieldValue,\n\t\t\t\t\t\t\tCreatedAt:          &t,\n\t\t\t\t\t\t\tUpdatedAt:          &t,\n\t\t\t\t\t\t},\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:       \"--verbose\",\n\t\t\tWantOutput: \"Fastly API endpoint: https://api.fastly.com\\nFastly API token provided via config file (auth: user)\\n\\nID: \" + mockResponseID + \"\\nIssued to: \" + mockFieldValue + \"\\nIssuer: \" + mockFieldValue + \"\\nName: \" + mockFieldValue + \"\\nReplace: true\\nSerial number: \" + mockFieldValue + \"\\nSignature algorithm: \" + mockFieldValue + \"\\nCreated at: 2021-06-15 23:00:00 +0000 UTC\\nUpdated at: 2021-06-15 23:00:00 +0000 UTC\\n\",\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"list\"}, scenarios)\n}\n\nfunc TestTLSCustomCertUpdate(t *testing.T) {\n\tvar content string\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      validateMissingIDFlag,\n\t\t\tArgs:      \"--cert-blob example\",\n\t\t\tWantError: \"required flag --id not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --cert-blob and --cert-path flags\",\n\t\t\tArgs:      \"--id example\",\n\t\t\tWantError: \"neither --cert-path or --cert-blob provided, one must be provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate specifying both --cert-blob and --cert-path flags\",\n\t\t\tArgs:      \"--id example --cert-blob foo --cert-path bar\",\n\t\t\tWantError: \"cert-path and cert-blob provided, only one can be specified\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate invalid --cert-path arg\",\n\t\t\tArgs:      \"--id example --cert-path ............\",\n\t\t\tWantError: \"error reading cert-path\",\n\t\t},\n\t\t{\n\t\t\tName: validateAPIError,\n\t\t\tAPI: &mock.API{\n\t\t\t\tUpdateCustomTLSCertificateFn: func(_ context.Context, certInput *fastly.UpdateCustomTLSCertificateInput) (*fastly.CustomTLSCertificate, error) {\n\t\t\t\t\tcontent = certInput.CertBlob\n\t\t\t\t\treturn nil, testutil.Err\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:            \"--cert-blob example --id example\",\n\t\t\tWantError:       testutil.Err.Error(),\n\t\t\tPathContentFlag: &testutil.PathContentFlag{Flag: \"cert-path\", Fixture: \"certificate.crt\", Content: func() string { return content }},\n\t\t},\n\t\t{\n\t\t\tName: validateAPISuccess,\n\t\t\tAPI: &mock.API{\n\t\t\t\tUpdateCustomTLSCertificateFn: func(_ context.Context, certInput *fastly.UpdateCustomTLSCertificateInput) (*fastly.CustomTLSCertificate, error) {\n\t\t\t\t\tcontent = certInput.CertBlob\n\t\t\t\t\treturn &fastly.CustomTLSCertificate{\n\t\t\t\t\t\tID: mockResponseID,\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:            \"--cert-blob example --id example\",\n\t\t\tWantOutput:      fmt.Sprintf(\"Updated TLS Certificate '%s'\", mockResponseID),\n\t\t\tPathContentFlag: &testutil.PathContentFlag{Flag: \"cert-path\", Fixture: \"certificate.crt\", Content: func() string { return content }},\n\t\t},\n\t\t{\n\t\t\tName: validateAPISuccess + \" with --name for different output\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tUpdateCustomTLSCertificateFn: func(_ context.Context, certInput *fastly.UpdateCustomTLSCertificateInput) (*fastly.CustomTLSCertificate, error) {\n\t\t\t\t\tcontent = certInput.CertBlob\n\t\t\t\t\treturn &fastly.CustomTLSCertificate{\n\t\t\t\t\t\tID:   mockResponseID,\n\t\t\t\t\t\tName: \"Updated\",\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:            \"--cert-blob example --id example --name example\",\n\t\t\tWantOutput:      \"Updated TLS Certificate 'Updated' (previously: 'example')\",\n\t\t\tPathContentFlag: &testutil.PathContentFlag{Flag: \"cert-path\", Fixture: \"certificate.crt\", Content: func() string { return content }},\n\t\t},\n\t\t{\n\t\t\tName: \"validate custom cert is submitted\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tUpdateCustomTLSCertificateFn: func(_ context.Context, certInput *fastly.UpdateCustomTLSCertificateInput) (*fastly.CustomTLSCertificate, error) {\n\t\t\t\t\tcontent = certInput.CertBlob\n\t\t\t\t\treturn &fastly.CustomTLSCertificate{\n\t\t\t\t\t\tID: mockResponseID,\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:            \"--id example --cert-path ./testdata/certificate.crt\",\n\t\t\tWantOutput:      \"SUCCESS: Updated TLS Certificate '123'\",\n\t\t\tPathContentFlag: &testutil.PathContentFlag{Flag: \"cert-path\", Fixture: \"certificate.crt\", Content: func() string { return content }},\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"update\"}, scenarios)\n}\n"
  },
  {
    "path": "pkg/commands/tls/custom/certificate/create.go",
    "content": "package certificate\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"path/filepath\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tvar c CreateCommand\n\tc.CmdClause = parent.Command(\"create\", \"Create a TLS certificate\").Alias(\"add\")\n\tc.Globals = g\n\n\t// Required\n\t// cert-blob and cert-path are mutually exclusive. One is required.\n\tc.CmdClause.Flag(\"cert-blob\", \"The PEM-formatted certificate blob, mutually exclusive with --cert-path\").StringVar(&c.certBlob)\n\tc.CmdClause.Flag(\"cert-path\", \"Filepath to a PEM-formatted certificate, mutually exclusive with --cert-blob\").StringVar(&c.certPath)\n\n\t// Optional.\n\tc.CmdClause.Flag(\"id\", \"Alphanumeric string identifying a TLS certificate\").StringVar(&c.id)\n\tc.CmdClause.Flag(\"name\", \"A customizable name for your certificate. Defaults to the certificate's Common Name or first Subject Alternative Names (SAN) entry\").StringVar(&c.name)\n\n\treturn &c\n}\n\n// CreateCommand calls the Fastly API to create an appropriate resource.\ntype CreateCommand struct {\n\targparser.Base\n\n\tcertBlob string\n\tcertPath string\n\tid       string\n\tname     string\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *CreateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tinput, err := c.constructInput()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tr, err := c.Globals.APIClient.CreateCustomTLSCertificate(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"TLS Certificate ID\":   c.id,\n\t\t\t\"TLS Certificate Name\": c.name,\n\t\t})\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Created TLS Certificate '%s'\", r.ID)\n\treturn nil\n}\n\n// constructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *CreateCommand) constructInput() (*fastly.CreateCustomTLSCertificateInput, error) {\n\tvar input fastly.CreateCustomTLSCertificateInput\n\n\tif c.certPath == \"\" && c.certBlob == \"\" {\n\t\treturn nil, fmt.Errorf(\"neither --cert-path or --cert-blob provided, one must be provided\")\n\t}\n\n\tif c.certPath != \"\" && c.certBlob != \"\" {\n\t\treturn nil, fmt.Errorf(\"cert-path and cert-blob provided, only one can be specified\")\n\t}\n\n\tif c.id != \"\" {\n\t\tinput.ID = c.id\n\t}\n\n\tif c.certBlob != \"\" {\n\t\tinput.CertBlob = c.certBlob\n\t}\n\n\tif c.certPath != \"\" {\n\t\tpath, err := filepath.Abs(c.certPath)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"error parsing cert-path '%s': %q\", c.certPath, err)\n\t\t}\n\n\t\tdata, err := os.ReadFile(path) // #nosec\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"error reading cert-path '%s': %q\", c.certPath, err)\n\t\t}\n\n\t\tinput.CertBlob = string(data)\n\t}\n\n\tif c.name != \"\" {\n\t\tinput.Name = c.name\n\t}\n\n\treturn &input, nil\n}\n"
  },
  {
    "path": "pkg/commands/tls/custom/certificate/delete.go",
    "content": "package certificate\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tvar c DeleteCommand\n\tc.CmdClause = parent.Command(\"delete\", \"Destroy a TLS certificate. TLS certificates already enabled for a domain cannot be destroyed\").Alias(\"remove\")\n\tc.Globals = g\n\n\t// Required.\n\tc.CmdClause.Flag(\"id\", \"Alphanumeric string identifying a TLS certificate\").Required().StringVar(&c.id)\n\n\treturn &c\n}\n\n// DeleteCommand calls the Fastly API to delete an appropriate resource.\ntype DeleteCommand struct {\n\targparser.Base\n\n\tid string\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DeleteCommand) Exec(_ io.Reader, out io.Writer) error {\n\tinput := c.constructInput()\n\n\terr := c.Globals.APIClient.DeleteCustomTLSCertificate(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"TLS Certificate ID\": c.id,\n\t\t})\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Deleted TLS Certificate '%s'\", c.id)\n\treturn nil\n}\n\n// constructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *DeleteCommand) constructInput() *fastly.DeleteCustomTLSCertificateInput {\n\tvar input fastly.DeleteCustomTLSCertificateInput\n\n\tinput.ID = c.id\n\n\treturn &input\n}\n"
  },
  {
    "path": "pkg/commands/tls/custom/certificate/describe.go",
    "content": "package certificate\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// NewDescribeCommand returns a usable command registered under the parent.\nfunc NewDescribeCommand(parent argparser.Registerer, g *global.Data) *DescribeCommand {\n\tvar c DescribeCommand\n\tc.CmdClause = parent.Command(\"describe\", \"Show a TLS certificate\").Alias(\"get\")\n\tc.Globals = g\n\n\t// Required.\n\tc.CmdClause.Flag(\"id\", \"Alphanumeric string identifying a TLS certificate\").Required().StringVar(&c.id)\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\n\treturn &c\n}\n\n// DescribeCommand calls the Fastly API to describe an appropriate resource.\ntype DescribeCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tid string\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DescribeCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tinput := c.constructInput()\n\n\to, err := c.Globals.APIClient.GetCustomTLSCertificate(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"TLS Certificate ID\": c.id,\n\t\t})\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\treturn c.print(out, o)\n}\n\n// constructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *DescribeCommand) constructInput() *fastly.GetCustomTLSCertificateInput {\n\tvar input fastly.GetCustomTLSCertificateInput\n\n\tinput.ID = c.id\n\n\treturn &input\n}\n\n// print displays the information returned from the API.\nfunc (c *DescribeCommand) print(out io.Writer, r *fastly.CustomTLSCertificate) error {\n\tfmt.Fprintf(out, \"\\nID: %s\\n\", r.ID)\n\tfmt.Fprintf(out, \"Issued to: %s\\n\", r.IssuedTo)\n\tfmt.Fprintf(out, \"Issuer: %s\\n\", r.Issuer)\n\tfmt.Fprintf(out, \"Name: %s\\n\", r.Name)\n\n\tif r.NotAfter != nil {\n\t\tfmt.Fprintf(out, \"Not after: %s\\n\", r.NotAfter)\n\t}\n\tif r.NotBefore != nil {\n\t\tfmt.Fprintf(out, \"Not before: %s\\n\", r.NotBefore)\n\t}\n\n\tfmt.Fprintf(out, \"Replace: %t\\n\", r.Replace)\n\tfmt.Fprintf(out, \"Serial number: %s\\n\", r.SerialNumber)\n\tfmt.Fprintf(out, \"Signature algorithm: %s\\n\", r.SignatureAlgorithm)\n\n\tif r.CreatedAt != nil {\n\t\tfmt.Fprintf(out, \"Created at: %s\\n\", r.CreatedAt)\n\t}\n\tif r.UpdatedAt != nil {\n\t\tfmt.Fprintf(out, \"Updated at: %s\\n\", r.UpdatedAt)\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/tls/custom/certificate/doc.go",
    "content": "// Package certificate contains commands to inspect and manipulate Fastly\n// custom TLS certificates.\npackage certificate\n"
  },
  {
    "path": "pkg/commands/tls/custom/certificate/list.go",
    "content": "package certificate\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\nconst emptyString = \"\"\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tvar c ListCommand\n\tc.CmdClause = parent.Command(\"list\", \"List all TLS certificates\")\n\tc.Globals = g\n\n\t// Optional.\n\tc.CmdClause.Flag(\"filter-not-after\", \"Limit the returned certificates to those that expire prior to the specified date in UTC\").StringVar(&c.filterNotAfter)\n\tc.CmdClause.Flag(\"filter-domain\", \"Limit the returned certificates to those that include the specific domain\").StringVar(&c.filterTLSDomainID)\n\tc.CmdClause.Flag(\"include\", \"Include related objects (comma-separated values)\").HintOptions(\"tls_activations\").EnumVar(&c.include, \"tls_activations\")\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\tc.CmdClause.Flag(\"page\", \"Page number of data set to fetch\").IntVar(&c.pageNumber)\n\tc.CmdClause.Flag(\"per-page\", \"Number of records per page\").IntVar(&c.pageSize)\n\tc.CmdClause.Flag(\"sort\", \"The order in which to list the results by creation date\").StringVar(&c.sort)\n\n\treturn &c\n}\n\n// ListCommand calls the Fastly API to list appropriate resources.\ntype ListCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tfilterNotAfter    string\n\tfilterTLSDomainID string\n\tinclude           string\n\tpageNumber        int\n\tpageSize          int\n\tsort              string\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *ListCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tinput := c.constructInput()\n\n\to, err := c.Globals.APIClient.ListCustomTLSCertificates(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Filter Not After\":     c.filterNotAfter,\n\t\t\t\"Filter TLS Domain ID\": c.filterTLSDomainID,\n\t\t\t\"Include\":              c.include,\n\t\t\t\"Page Number\":          c.pageNumber,\n\t\t\t\"Page Size\":            c.pageSize,\n\t\t\t\"Sort\":                 c.sort,\n\t\t})\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\tif c.Globals.Verbose() {\n\t\tprintVerbose(out, o)\n\t} else {\n\t\terr = c.printSummary(out, o)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\n// constructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *ListCommand) constructInput() *fastly.ListCustomTLSCertificatesInput {\n\tvar input fastly.ListCustomTLSCertificatesInput\n\n\tif c.filterNotAfter != emptyString {\n\t\tinput.FilterNotAfter = c.filterNotAfter\n\t}\n\tif c.filterTLSDomainID != emptyString {\n\t\tinput.FilterTLSDomainsID = c.filterTLSDomainID\n\t}\n\tif c.include != emptyString {\n\t\tinput.Include = c.include\n\t}\n\tif c.pageNumber > 0 {\n\t\tinput.PageNumber = c.pageNumber\n\t}\n\tif c.pageSize > 0 {\n\t\tinput.PageSize = c.pageSize\n\t}\n\tif c.sort != \"\" {\n\t\tinput.Sort = c.sort\n\t}\n\n\treturn &input\n}\n\n// printVerbose displays the information returned from the API in a verbose\n// format.\nfunc printVerbose(out io.Writer, rs []*fastly.CustomTLSCertificate) {\n\tfor _, r := range rs {\n\t\tfmt.Fprintf(out, \"ID: %s\\n\", r.ID)\n\t\tfmt.Fprintf(out, \"Issued to: %s\\n\", r.IssuedTo)\n\t\tfmt.Fprintf(out, \"Issuer: %s\\n\", r.Issuer)\n\t\tfmt.Fprintf(out, \"Name: %s\\n\", r.Name)\n\n\t\tif r.NotAfter != nil {\n\t\t\tfmt.Fprintf(out, \"Not after: %s\\n\", r.NotAfter)\n\t\t}\n\t\tif r.NotBefore != nil {\n\t\t\tfmt.Fprintf(out, \"Not before: %s\\n\", r.NotBefore)\n\t\t}\n\n\t\tfmt.Fprintf(out, \"Replace: %t\\n\", r.Replace)\n\t\tfmt.Fprintf(out, \"Serial number: %s\\n\", r.SerialNumber)\n\t\tfmt.Fprintf(out, \"Signature algorithm: %s\\n\", r.SignatureAlgorithm)\n\n\t\tif r.CreatedAt != nil {\n\t\t\tfmt.Fprintf(out, \"Created at: %s\\n\", r.CreatedAt)\n\t\t}\n\t\tif r.UpdatedAt != nil {\n\t\t\tfmt.Fprintf(out, \"Updated at: %s\\n\", r.UpdatedAt)\n\t\t}\n\n\t\tfmt.Fprintf(out, \"\\n\")\n\t}\n}\n\n// printSummary displays the information returned from the API in a summarised\n// format.\nfunc (c *ListCommand) printSummary(out io.Writer, rs []*fastly.CustomTLSCertificate) error {\n\tt := text.NewTable(out)\n\tt.AddHeader(\"ID\", \"ISSUED TO\", \"NAME\", \"REPLACE\", \"SIGNATURE ALGORITHM\")\n\tfor _, r := range rs {\n\t\tt.AddLine(r.ID, r.IssuedTo, r.Name, r.Replace, r.SignatureAlgorithm)\n\t}\n\tt.Print()\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/tls/custom/certificate/root.go",
    "content": "package certificate\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"certificate\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Upload and manage TLS certificates\")\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/tls/custom/certificate/testdata/certificate.crt",
    "content": "this is a fake cert"
  },
  {
    "path": "pkg/commands/tls/custom/certificate/update.go",
    "content": "package certificate\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"path/filepath\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tvar c UpdateCommand\n\tc.CmdClause = parent.Command(\"update\", \"Replace a TLS certificate with a newly reissued TLS certificate, or update a TLS certificate's name\")\n\tc.Globals = g\n\n\t// Required\n\t// cert-blob and cert-path are mutually exclusive. One is required.\n\tc.CmdClause.Flag(\"cert-blob\", \"The PEM-formatted certificate blob, mutually exclusive with --cert-path\").StringVar(&c.certBlob)\n\tc.CmdClause.Flag(\"cert-path\", \"Filepath to a PEM-formatted certificate, mutually exclusive with --cert-blob\").StringVar(&c.certPath)\n\tc.CmdClause.Flag(\"id\", \"Alphanumeric string identifying a TLS certificate\").Required().StringVar(&c.id)\n\n\t// Optional.\n\tc.CmdClause.Flag(\"name\", \"A customizable name for your certificate. Defaults to the certificate's Common Name or first Subject Alternative Names (SAN) entry\").StringVar(&c.name)\n\treturn &c\n}\n\n// UpdateCommand calls the Fastly API to update an appropriate resource.\ntype UpdateCommand struct {\n\targparser.Base\n\n\tcertBlob string\n\tcertPath string\n\tid       string\n\tname     string\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *UpdateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tinput, err := c.constructInput()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tr, err := c.Globals.APIClient.UpdateCustomTLSCertificate(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"TLS Certificate ID\":   c.id,\n\t\t\t\"TLS Certificate Name\": c.name,\n\t\t})\n\t\treturn err\n\t}\n\n\tif c.name != \"\" {\n\t\ttext.Success(out, \"Updated TLS Certificate '%s' (previously: '%s')\", r.Name, input.Name)\n\t} else {\n\t\ttext.Success(out, \"Updated TLS Certificate '%s'\", r.ID)\n\t}\n\treturn nil\n}\n\n// constructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *UpdateCommand) constructInput() (*fastly.UpdateCustomTLSCertificateInput, error) {\n\tvar input fastly.UpdateCustomTLSCertificateInput\n\n\tif c.certPath == \"\" && c.certBlob == \"\" {\n\t\treturn nil, fmt.Errorf(\"neither --cert-path or --cert-blob provided, one must be provided\")\n\t}\n\n\tif c.certPath != \"\" && c.certBlob != \"\" {\n\t\treturn nil, fmt.Errorf(\"cert-path and cert-blob provided, only one can be specified\")\n\t}\n\n\tinput.ID = c.id\n\n\tif c.certBlob != \"\" {\n\t\tinput.CertBlob = c.certBlob\n\t}\n\n\tif c.certPath != \"\" {\n\t\tpath, err := filepath.Abs(c.certPath)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"error parsing cert-path '%s': %q\", c.certPath, err)\n\t\t}\n\n\t\tdata, err := os.ReadFile(path) // #nosec\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"error reading cert-path '%s': %q\", c.certPath, err)\n\t\t}\n\n\t\tinput.CertBlob = string(data)\n\t}\n\n\tif c.name != \"\" {\n\t\tinput.Name = c.name\n\t}\n\n\treturn &input, nil\n}\n"
  },
  {
    "path": "pkg/commands/tls/custom/doc.go",
    "content": "// Package custom contains commands to inspect and manipulate Fastly custom TLS\n// certificates.\npackage custom\n"
  },
  {
    "path": "pkg/commands/tls/custom/domain/doc.go",
    "content": "// Package domain contains commands to inspect and manipulate Fastly custom TLS\n// domains.\npackage domain\n"
  },
  {
    "path": "pkg/commands/tls/custom/domain/domain_test.go",
    "content": "package domain_test\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\troot \"github.com/fastly/cli/pkg/commands/tls/custom\"\n\tsub \"github.com/fastly/cli/pkg/commands/tls/custom/domain\"\n\t\"github.com/fastly/cli/pkg/mock\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n)\n\nconst (\n\tmockResponseID     = \"123\"\n\tvalidateAPIError   = \"validate API error\"\n\tvalidateAPISuccess = \"validate API success\"\n)\n\nfunc TestList(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName: validateAPIError,\n\t\t\tAPI: &mock.API{\n\t\t\t\tListTLSDomainsFn: func(_ context.Context, _ *fastly.ListTLSDomainsInput) ([]*fastly.TLSDomain, error) {\n\t\t\t\t\treturn nil, testutil.Err\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: testutil.Err.Error(),\n\t\t},\n\t\t{\n\t\t\tName: validateAPISuccess,\n\t\t\tAPI: &mock.API{\n\t\t\t\tListTLSDomainsFn: func(_ context.Context, _ *fastly.ListTLSDomainsInput) ([]*fastly.TLSDomain, error) {\n\t\t\t\t\treturn []*fastly.TLSDomain{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tID:   mockResponseID,\n\t\t\t\t\t\t\tType: \"example\",\n\t\t\t\t\t\t},\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:       \"--verbose\",\n\t\t\tWantOutput: \"\\nID: \" + mockResponseID + \"\\nType: example\\n\\n\",\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"list\"}, scenarios)\n}\n"
  },
  {
    "path": "pkg/commands/tls/custom/domain/list.go",
    "content": "package domain\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\nconst emptyString = \"\"\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tvar c ListCommand\n\tc.CmdClause = parent.Command(\"list\", \"List all TLS domains\")\n\tc.Globals = g\n\n\t// Optional.\n\tc.CmdClause.Flag(\"filter-cert\", \"Limit the returned domains to those listed in the given TLS certificate's SAN list\").StringVar(&c.filterTLSCertsID)\n\tc.CmdClause.Flag(\"filter-in-use\", \"Limit the returned domains to those currently using Fastly to terminate TLS with SNI\").Action(c.filterInUse.Set).BoolVar(&c.filterInUse.Value)\n\tc.CmdClause.Flag(\"filter-subscription\", \"Limit the returned domains to those for a given TLS subscription\").StringVar(&c.filterTLSSubsID)\n\tc.CmdClause.Flag(\"include\", \"Include related objects (comma-separated values)\").HintOptions(\"tls_activations\").EnumVar(&c.include, \"tls_activations\")\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\tc.CmdClause.Flag(\"page\", \"Page number of data set to fetch\").IntVar(&c.pageNumber)\n\tc.CmdClause.Flag(\"per-page\", \"Number of records per page\").IntVar(&c.pageSize)\n\tc.CmdClause.Flag(\"sort\", \"The order in which to list the results by creation date\").StringVar(&c.sort)\n\n\treturn &c\n}\n\n// ListCommand calls the Fastly API to list appropriate resources.\ntype ListCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tfilterInUse      argparser.OptionalBool\n\tfilterTLSCertsID string\n\tfilterTLSSubsID  string\n\tinclude          string\n\tpageNumber       int\n\tpageSize         int\n\tsort             string\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *ListCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tinput := c.constructInput()\n\n\to, err := c.Globals.APIClient.ListTLSDomains(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Filter In Use\":            c.filterInUse,\n\t\t\t\"Filter TLS Certificates\":  c.filterTLSCertsID,\n\t\t\t\"Filter TLS Subscriptions\": c.filterTLSSubsID,\n\t\t\t\"Include\":                  c.include,\n\t\t\t\"Page Number\":              c.pageNumber,\n\t\t\t\"Page Size\":                c.pageSize,\n\t\t\t\"Sort\":                     c.sort,\n\t\t})\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\tif c.Globals.Verbose() {\n\t\tprintVerbose(out, o)\n\t} else {\n\t\terr = c.printSummary(out, o)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\n// constructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *ListCommand) constructInput() *fastly.ListTLSDomainsInput {\n\tvar input fastly.ListTLSDomainsInput\n\n\tif c.filterInUse.WasSet {\n\t\tinput.FilterInUse = &c.filterInUse.Value\n\t}\n\tif c.filterTLSCertsID != emptyString {\n\t\tinput.FilterTLSCertificateID = c.filterTLSCertsID\n\t}\n\tif c.filterTLSSubsID != emptyString {\n\t\tinput.FilterTLSSubscriptionID = c.filterTLSSubsID\n\t}\n\tif c.include != emptyString {\n\t\tinput.Include = c.include\n\t}\n\tif c.pageNumber > 0 {\n\t\tinput.PageNumber = c.pageNumber\n\t}\n\tif c.pageSize > 0 {\n\t\tinput.PageSize = c.pageSize\n\t}\n\tif c.sort != \"\" {\n\t\tinput.Sort = c.sort\n\t}\n\n\treturn &input\n}\n\n// printVerbose displays the information returned from the API in a verbose\n// format.\nfunc printVerbose(out io.Writer, rs []*fastly.TLSDomain) {\n\tfor _, r := range rs {\n\t\tfmt.Fprintf(out, \"\\nID: %s\\n\", r.ID)\n\t\tfmt.Fprintf(out, \"Type: %s\\n\", r.Type)\n\t\tfmt.Fprintf(out, \"\\n\")\n\t}\n}\n\n// printSummary displays the information returned from the API in a summarised\n// format.\nfunc (c *ListCommand) printSummary(out io.Writer, rs []*fastly.TLSDomain) error {\n\tt := text.NewTable(out)\n\tt.AddHeader(\"ID\", \"TYPE\")\n\tfor _, r := range rs {\n\t\tt.AddLine(r.ID, r.Type)\n\t}\n\tt.Print()\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/tls/custom/domain/root.go",
    "content": "package domain\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"domain\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manage TLS domains\")\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/tls/custom/privatekey/create.go",
    "content": "package privatekey\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"path/filepath\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tvar c CreateCommand\n\tc.CmdClause = parent.Command(\"create\", \"Create a TLS private key\").Alias(\"add\")\n\tc.Globals = g\n\n\t// Required.\n\tc.CmdClause.Flag(\"key\", \"The contents of the private key. Must be a PEM-formatted key, mutually exclusive with --key-path\").StringVar(&c.key)\n\tc.CmdClause.Flag(\"key-path\", \"Filepath to a PEM-formatted key, mutually exclusive with --key\").StringVar(&c.keyPath)\n\tc.CmdClause.Flag(\"name\", \"A customizable name for your private key\").Required().StringVar(&c.name)\n\n\treturn &c\n}\n\n// CreateCommand calls the Fastly API to create an appropriate resource.\ntype CreateCommand struct {\n\targparser.Base\n\n\tkey     string\n\tkeyPath string\n\tname    string\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *CreateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tinput, err := c.constructInput()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tr, err := c.Globals.APIClient.CreatePrivateKey(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Private Key Name\": c.name,\n\t\t})\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Created TLS Private Key '%s'\", r.Name)\n\treturn nil\n}\n\n// constructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *CreateCommand) constructInput() (*fastly.CreatePrivateKeyInput, error) {\n\tvar input fastly.CreatePrivateKeyInput\n\n\tif c.keyPath == \"\" && c.key == \"\" {\n\t\treturn nil, fmt.Errorf(\"neither --key-path or --key provided, one must be provided\")\n\t}\n\n\tif c.keyPath != \"\" && c.key != \"\" {\n\t\treturn nil, fmt.Errorf(\"--key-path and --key provided, only one can be specified\")\n\t}\n\n\tif c.key != \"\" {\n\t\tinput.Key = c.key\n\t}\n\n\tif c.keyPath != \"\" {\n\t\tpath, err := filepath.Abs(c.keyPath)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"error parsing key-path '%s': %q\", c.keyPath, err)\n\t\t}\n\n\t\tdata, err := os.ReadFile(path) // #nosec\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"error reading key-path '%s': %q\", c.keyPath, err)\n\t\t}\n\n\t\tinput.Key = string(data)\n\t}\n\n\tinput.Name = c.name\n\n\treturn &input, nil\n}\n"
  },
  {
    "path": "pkg/commands/tls/custom/privatekey/delete.go",
    "content": "package privatekey\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tvar c DeleteCommand\n\tc.CmdClause = parent.Command(\"delete\", \"Destroy a TLS private key. Only private keys not already matched to any certificates can be deleted\").Alias(\"remove\")\n\tc.Globals = g\n\n\t// Required.\n\tc.CmdClause.Flag(\"id\", \"Alphanumeric string identifying a private Key\").Required().StringVar(&c.id)\n\n\treturn &c\n}\n\n// DeleteCommand calls the Fastly API to delete an appropriate resource.\ntype DeleteCommand struct {\n\targparser.Base\n\n\tid string\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DeleteCommand) Exec(_ io.Reader, out io.Writer) error {\n\tinput := c.constructInput()\n\n\terr := c.Globals.APIClient.DeletePrivateKey(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"TLS Private Key ID\": c.id,\n\t\t})\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Deleted TLS Private Key '%s'\", c.id)\n\treturn nil\n}\n\n// constructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *DeleteCommand) constructInput() *fastly.DeletePrivateKeyInput {\n\tvar input fastly.DeletePrivateKeyInput\n\n\tinput.ID = c.id\n\n\treturn &input\n}\n"
  },
  {
    "path": "pkg/commands/tls/custom/privatekey/describe.go",
    "content": "package privatekey\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// NewDescribeCommand returns a usable command registered under the parent.\nfunc NewDescribeCommand(parent argparser.Registerer, g *global.Data) *DescribeCommand {\n\tvar c DescribeCommand\n\tc.CmdClause = parent.Command(\"describe\", \"Show a TLS private key\").Alias(\"get\")\n\tc.Globals = g\n\n\t// Required.\n\tc.CmdClause.Flag(\"id\", \"Alphanumeric string identifying a private Key\").Required().StringVar(&c.id)\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\n\treturn &c\n}\n\n// DescribeCommand calls the Fastly API to describe an appropriate resource.\ntype DescribeCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tid string\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DescribeCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tinput := c.constructInput()\n\n\to, err := c.Globals.APIClient.GetPrivateKey(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"TLS Certificate ID\": c.id,\n\t\t})\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\treturn c.print(out, o)\n}\n\n// constructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *DescribeCommand) constructInput() *fastly.GetPrivateKeyInput {\n\tvar input fastly.GetPrivateKeyInput\n\n\tinput.ID = c.id\n\n\treturn &input\n}\n\n// print displays the information returned from the API.\nfunc (c *DescribeCommand) print(out io.Writer, r *fastly.PrivateKey) error {\n\tfmt.Fprintf(out, \"\\nID: %s\\n\", r.ID)\n\tfmt.Fprintf(out, \"Name: %s\\n\", r.Name)\n\tfmt.Fprintf(out, \"Key Length: %d\\n\", r.KeyLength)\n\tfmt.Fprintf(out, \"Key Type: %s\\n\", r.KeyType)\n\tfmt.Fprintf(out, \"Public Key SHA1: %s\\n\", r.PublicKeySHA1)\n\n\tif r.CreatedAt != nil {\n\t\tfmt.Fprintf(out, \"Created at: %s\\n\", r.CreatedAt)\n\t}\n\n\tfmt.Fprintf(out, \"Replace: %t\\n\", r.Replace)\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/tls/custom/privatekey/doc.go",
    "content": "// Package privatekey contains commands to inspect and manipulate Fastly custom\n// TLS private keys.\npackage privatekey\n"
  },
  {
    "path": "pkg/commands/tls/custom/privatekey/list.go",
    "content": "package privatekey\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tvar c ListCommand\n\tc.CmdClause = parent.Command(\"list\", \"List all TLS private keys\")\n\tc.Globals = g\n\n\t// Optional.\n\tc.CmdClause.Flag(\"filter-in-use\", \"Limit the returned keys to those without any matching TLS certificates\").HintOptions(\"false\").EnumVar(&c.filterInUse, \"false\")\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\tc.CmdClause.Flag(\"page\", \"Page number of data set to fetch\").IntVar(&c.pageNumber)\n\tc.CmdClause.Flag(\"per-page\", \"Number of records per page\").IntVar(&c.pageSize)\n\n\treturn &c\n}\n\n// ListCommand calls the Fastly API to list appropriate resources.\ntype ListCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tfilterInUse string\n\tpageNumber  int\n\tpageSize    int\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *ListCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tinput := c.constructInput()\n\n\to, err := c.Globals.APIClient.ListPrivateKeys(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Filter In Use\": c.filterInUse,\n\t\t\t\"Page Number\":   c.pageNumber,\n\t\t\t\"Page Size\":     c.pageSize,\n\t\t})\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\tif c.Globals.Verbose() {\n\t\tprintVerbose(out, o)\n\t} else {\n\t\terr = c.printSummary(out, o)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\n// constructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *ListCommand) constructInput() *fastly.ListPrivateKeysInput {\n\tvar input fastly.ListPrivateKeysInput\n\n\tif c.filterInUse != \"\" {\n\t\tinput.FilterInUse = c.filterInUse\n\t}\n\tif c.pageNumber > 0 {\n\t\tinput.PageNumber = c.pageNumber\n\t}\n\tif c.pageSize > 0 {\n\t\tinput.PageSize = c.pageSize\n\t}\n\n\treturn &input\n}\n\n// printVerbose displays the information returned from the API in a verbose\n// format.\nfunc printVerbose(out io.Writer, rs []*fastly.PrivateKey) {\n\tfor _, r := range rs {\n\t\tfmt.Fprintf(out, \"\\nID: %s\\n\", r.ID)\n\t\tfmt.Fprintf(out, \"Name: %s\\n\", r.Name)\n\t\tfmt.Fprintf(out, \"Key Length: %d\\n\", r.KeyLength)\n\t\tfmt.Fprintf(out, \"Key Type: %s\\n\", r.KeyType)\n\t\tfmt.Fprintf(out, \"Public Key SHA1: %s\\n\", r.PublicKeySHA1)\n\n\t\tif r.CreatedAt != nil {\n\t\t\tfmt.Fprintf(out, \"Created at: %s\\n\", r.CreatedAt)\n\t\t}\n\n\t\tfmt.Fprintf(out, \"Replace: %t\\n\", r.Replace)\n\t\tfmt.Fprintf(out, \"\\n\")\n\t}\n}\n\n// printSummary displays the information returned from the API in a summarised\n// format.\nfunc (c *ListCommand) printSummary(out io.Writer, rs []*fastly.PrivateKey) error {\n\tt := text.NewTable(out)\n\tt.AddHeader(\"ID\", \"NAME\", \"KEY LENGTH\", \"KEY TYPE\", \"PUBLIC KEY SHA1\", \"REPLACE\")\n\tfor _, r := range rs {\n\t\tt.AddLine(r.ID, r.Name, r.KeyLength, r.KeyType, r.PublicKeySHA1, r.Replace)\n\t}\n\tt.Print()\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/tls/custom/privatekey/privatekey_test.go",
    "content": "package privatekey_test\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\troot \"github.com/fastly/cli/pkg/commands/tls/custom\"\n\tsub \"github.com/fastly/cli/pkg/commands/tls/custom/privatekey\"\n\t\"github.com/fastly/cli/pkg/mock\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n)\n\nconst (\n\tmockFieldValue        = \"example\"\n\tmockKeyLength         = 123\n\tmockResponseID        = \"123\"\n\tvalidateAPIError      = \"validate API error\"\n\tvalidateAPISuccess    = \"validate API success\"\n\tvalidateMissingIDFlag = \"validate missing --id flag\"\n)\n\nfunc TestTLSCustomPrivateKeyCreate(t *testing.T) {\n\tvar content string\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --key and --key-path flags\",\n\t\t\tArgs:      \"--name example\",\n\t\t\tWantError: \"neither --key-path or --key provided, one must be provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate using both --key and --key-path flags\",\n\t\t\tArgs:      \"--name example --key example --key-path foobar\",\n\t\t\tWantError: \"--key-path and --key provided, only one can be specified\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --name flag\",\n\t\t\tArgs:      \"--key example\",\n\t\t\tWantError: \"required flag --name not provided\",\n\t\t},\n\t\t{\n\t\t\tName: validateAPIError,\n\t\t\tAPI: &mock.API{\n\t\t\t\tCreatePrivateKeyFn: func(_ context.Context, i *fastly.CreatePrivateKeyInput) (*fastly.PrivateKey, error) {\n\t\t\t\t\tcontent = i.Key\n\t\t\t\t\treturn nil, testutil.Err\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:            \"--key example --name example\",\n\t\t\tWantError:       testutil.Err.Error(),\n\t\t\tPathContentFlag: &testutil.PathContentFlag{Flag: \"key-path\", Fixture: \"testkey.pem\", Content: func() string { return content }},\n\t\t},\n\t\t{\n\t\t\tName: validateAPISuccess,\n\t\t\tAPI: &mock.API{\n\t\t\t\tCreatePrivateKeyFn: func(_ context.Context, i *fastly.CreatePrivateKeyInput) (*fastly.PrivateKey, error) {\n\t\t\t\t\tcontent = i.Key\n\t\t\t\t\treturn &fastly.PrivateKey{\n\t\t\t\t\t\tID:   mockResponseID,\n\t\t\t\t\t\tName: i.Name,\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:            \"--key example --name example\",\n\t\t\tWantOutput:      \"Created TLS Private Key 'example'\",\n\t\t\tPathContentFlag: &testutil.PathContentFlag{Flag: \"key-path\", Fixture: \"testkey.pem\", Content: func() string { return content }},\n\t\t},\n\t\t{\n\t\t\tName: \"validate custom key is submitted\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tCreatePrivateKeyFn: func(_ context.Context, i *fastly.CreatePrivateKeyInput) (*fastly.PrivateKey, error) {\n\t\t\t\t\tcontent = i.Key\n\t\t\t\t\treturn &fastly.PrivateKey{\n\t\t\t\t\t\tID:   mockResponseID,\n\t\t\t\t\t\tName: i.Name,\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:            \"--name example --key-path ./testdata/testkey.pem\",\n\t\t\tWantOutput:      \"Created TLS Private Key 'example'\",\n\t\t\tPathContentFlag: &testutil.PathContentFlag{Flag: \"key-path\", Fixture: \"testkey.pem\", Content: func() string { return content }},\n\t\t},\n\t\t{\n\t\t\tName:      \"validate invalid --key-path arg\",\n\t\t\tArgs:      \"--name example --key-path ............\",\n\t\t\tWantError: \"error reading key-path\",\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"create\"}, scenarios)\n}\n\nfunc TestTLSCustomPrivateKeyDelete(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      validateMissingIDFlag,\n\t\t\tWantError: \"error parsing arguments: required flag --id not provided\",\n\t\t},\n\t\t{\n\t\t\tName: validateAPIError,\n\t\t\tAPI: &mock.API{\n\t\t\t\tDeletePrivateKeyFn: func(_ context.Context, _ *fastly.DeletePrivateKeyInput) error {\n\t\t\t\t\treturn testutil.Err\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:      \"--id example\",\n\t\t\tWantError: testutil.Err.Error(),\n\t\t},\n\t\t{\n\t\t\tName: validateAPISuccess,\n\t\t\tAPI: &mock.API{\n\t\t\t\tDeletePrivateKeyFn: func(_ context.Context, _ *fastly.DeletePrivateKeyInput) error {\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:       \"--id example\",\n\t\t\tWantOutput: \"Deleted TLS Private Key 'example'\",\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"delete\"}, scenarios)\n}\n\nfunc TestTLSCustomPrivateKeyDescribe(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      validateMissingIDFlag,\n\t\t\tWantError: \"error parsing arguments: required flag --id not provided\",\n\t\t},\n\t\t{\n\t\t\tName: validateAPIError,\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetPrivateKeyFn: func(_ context.Context, _ *fastly.GetPrivateKeyInput) (*fastly.PrivateKey, error) {\n\t\t\t\t\treturn nil, testutil.Err\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:      \"--id example\",\n\t\t\tWantError: testutil.Err.Error(),\n\t\t},\n\t\t{\n\t\t\tName: validateAPISuccess,\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetPrivateKeyFn: func(_ context.Context, _ *fastly.GetPrivateKeyInput) (*fastly.PrivateKey, error) {\n\t\t\t\t\tt := testutil.Date\n\t\t\t\t\treturn &fastly.PrivateKey{\n\t\t\t\t\t\tID:            mockResponseID,\n\t\t\t\t\t\tName:          mockFieldValue,\n\t\t\t\t\t\tKeyLength:     mockKeyLength,\n\t\t\t\t\t\tKeyType:       mockFieldValue,\n\t\t\t\t\t\tPublicKeySHA1: mockFieldValue,\n\t\t\t\t\t\tCreatedAt:     &t,\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:       \"--id example\",\n\t\t\tWantOutput: \"\\nID: \" + mockResponseID + \"\\nName: example\\nKey Length: 123\\nKey Type: example\\nPublic Key SHA1: example\\nCreated at: 2021-06-15 23:00:00 +0000 UTC\\nReplace: false\\n\",\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"describe\"}, scenarios)\n}\n\nfunc TestTLSCustomPrivateKeyList(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName: validateAPIError,\n\t\t\tAPI: &mock.API{\n\t\t\t\tListPrivateKeysFn: func(_ context.Context, _ *fastly.ListPrivateKeysInput) ([]*fastly.PrivateKey, error) {\n\t\t\t\t\treturn nil, testutil.Err\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: testutil.Err.Error(),\n\t\t},\n\t\t{\n\t\t\tName: validateAPISuccess,\n\t\t\tAPI: &mock.API{\n\t\t\t\tListPrivateKeysFn: func(_ context.Context, _ *fastly.ListPrivateKeysInput) ([]*fastly.PrivateKey, error) {\n\t\t\t\t\tt := testutil.Date\n\t\t\t\t\treturn []*fastly.PrivateKey{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tID:            mockResponseID,\n\t\t\t\t\t\t\tName:          mockFieldValue,\n\t\t\t\t\t\t\tKeyLength:     mockKeyLength,\n\t\t\t\t\t\t\tKeyType:       mockFieldValue,\n\t\t\t\t\t\t\tPublicKeySHA1: mockFieldValue,\n\t\t\t\t\t\t\tCreatedAt:     &t,\n\t\t\t\t\t\t},\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:       \"--verbose\",\n\t\t\tWantOutput: \"\\nID: \" + mockResponseID + \"\\nName: example\\nKey Length: 123\\nKey Type: example\\nPublic Key SHA1: example\\nCreated at: 2021-06-15 23:00:00 +0000 UTC\\nReplace: false\\n\",\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, sub.CommandName, \"list\"}, scenarios)\n}\n"
  },
  {
    "path": "pkg/commands/tls/custom/privatekey/root.go",
    "content": "package privatekey\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"private-key\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, globals *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = globals\n\tc.CmdClause = parent.Command(CommandName, \"Upload and manage private keys used to sign certificates\")\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/tls/custom/privatekey/testdata/testkey.pem",
    "content": "this is a test key"
  },
  {
    "path": "pkg/commands/tls/custom/root.go",
    "content": "package custom\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"tls-custom\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manage custom keys and certs used to enable TLS\")\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/tls/platform/create.go",
    "content": "package platform\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tvar c CreateCommand\n\tc.CmdClause = parent.Command(\"upload\", \"Upload a new certificate\")\n\tc.Globals = g\n\n\t// Required.\n\tc.CmdClause.Flag(\"cert-blob\", \"The PEM-formatted certificate blob\").Required().StringVar(&c.certBlob)\n\tc.CmdClause.Flag(\"intermediates-blob\", \"The PEM-formatted chain of intermediate blobs\").Required().StringVar(&c.intermediatesBlob)\n\n\t// Optional.\n\tc.CmdClause.Flag(\"allow-untrusted\", \"Allow certificates that chain to untrusted roots\").Action(c.allowUntrusted.Set).BoolVar(&c.allowUntrusted.Value)\n\tc.CmdClause.Flag(\"config\", \"Alphanumeric string identifying a TLS configuration (set flag once per Configuration ID)\").StringsVar(&c.config)\n\n\treturn &c\n}\n\n// CreateCommand calls the Fastly API to update an appropriate resource.\ntype CreateCommand struct {\n\targparser.Base\n\n\tallowUntrusted    argparser.OptionalBool\n\tcertBlob          string\n\tconfig            []string\n\tintermediatesBlob string\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *CreateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tinput := c.constructInput()\n\n\tr, err := c.Globals.APIClient.CreateBulkCertificate(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Allow Untrusted\": c.allowUntrusted.Value,\n\t\t\t\"Configs\":         c.config,\n\t\t})\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Uploaded TLS Bulk Certificate '%s'\", r.ID)\n\treturn nil\n}\n\n// constructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *CreateCommand) constructInput() *fastly.CreateBulkCertificateInput {\n\tvar input fastly.CreateBulkCertificateInput\n\n\tinput.CertBlob = c.certBlob\n\tinput.IntermediatesBlob = c.intermediatesBlob\n\n\tif c.allowUntrusted.WasSet {\n\t\tinput.AllowUntrusted = c.allowUntrusted.Value\n\t}\n\n\tvar configs []*fastly.TLSConfiguration\n\tfor _, v := range c.config {\n\t\tconfigs = append(configs, &fastly.TLSConfiguration{ID: v})\n\t}\n\tinput.Configurations = configs\n\n\treturn &input\n}\n"
  },
  {
    "path": "pkg/commands/tls/platform/delete.go",
    "content": "package platform\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tvar c DeleteCommand\n\tc.CmdClause = parent.Command(\"delete\", \"Destroy a certificate. This disables TLS for all domains listed as SAN entries\").Alias(\"remove\")\n\tc.Globals = g\n\n\t// Required.\n\tc.CmdClause.Flag(\"id\", \"Alphanumeric string identifying a TLS bulk certificate\").Required().StringVar(&c.id)\n\n\treturn &c\n}\n\n// DeleteCommand calls the Fastly API to delete an appropriate resource.\ntype DeleteCommand struct {\n\targparser.Base\n\n\tid string\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DeleteCommand) Exec(_ io.Reader, out io.Writer) error {\n\tinput := c.constructInput()\n\n\terr := c.Globals.APIClient.DeleteBulkCertificate(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"TLS Bulk Certificate ID\": c.id,\n\t\t})\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Deleted TLS Bulk Certificate '%s'\", c.id)\n\treturn nil\n}\n\n// constructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *DeleteCommand) constructInput() *fastly.DeleteBulkCertificateInput {\n\tvar input fastly.DeleteBulkCertificateInput\n\n\tinput.ID = c.id\n\n\treturn &input\n}\n"
  },
  {
    "path": "pkg/commands/tls/platform/describe.go",
    "content": "package platform\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// NewDescribeCommand returns a usable command registered under the parent.\nfunc NewDescribeCommand(parent argparser.Registerer, g *global.Data) *DescribeCommand {\n\tvar c DescribeCommand\n\tc.CmdClause = parent.Command(\"describe\", \"Retrieve a single certificate\").Alias(\"get\")\n\tc.Globals = g\n\n\t// Required.\n\tc.CmdClause.Flag(\"id\", \"Alphanumeric string identifying a TLS bulk certificate\").Required().StringVar(&c.id)\n\n\t// Optional.\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\n\treturn &c\n}\n\n// DescribeCommand calls the Fastly API to describe an appropriate resource.\ntype DescribeCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tid string\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DescribeCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tinput := c.constructInput()\n\n\to, err := c.Globals.APIClient.GetBulkCertificate(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"TLS Bulk Certificate ID\": c.id,\n\t\t})\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\treturn c.print(out, o)\n}\n\n// constructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *DescribeCommand) constructInput() *fastly.GetBulkCertificateInput {\n\tvar input fastly.GetBulkCertificateInput\n\n\tinput.ID = c.id\n\n\treturn &input\n}\n\n// print displays the information returned from the API.\nfunc (c *DescribeCommand) print(out io.Writer, r *fastly.BulkCertificate) error {\n\tfmt.Fprintf(out, \"\\nID: %s\\n\", r.ID)\n\n\tif r.NotAfter != nil {\n\t\tfmt.Fprintf(out, \"Not after: %s\\n\", r.NotAfter)\n\t}\n\tif r.NotBefore != nil {\n\t\tfmt.Fprintf(out, \"Not before: %s\\n\", r.NotBefore)\n\t}\n\tif r.CreatedAt != nil {\n\t\tfmt.Fprintf(out, \"Created at: %s\\n\", r.CreatedAt)\n\t}\n\tif r.UpdatedAt != nil {\n\t\tfmt.Fprintf(out, \"Updated at: %s\\n\", r.UpdatedAt)\n\t}\n\n\tfmt.Fprintf(out, \"Replace: %t\\n\", r.Replace)\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/tls/platform/doc.go",
    "content": "// Package platform contains commands to inspect and manipulate Fastly batch TLS\n// certificates.\npackage platform\n"
  },
  {
    "path": "pkg/commands/tls/platform/list.go",
    "content": "package platform\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tvar c ListCommand\n\tc.CmdClause = parent.Command(\"list\", \"List all certificates\")\n\tc.Globals = g\n\n\t// Optional.\n\tc.CmdClause.Flag(\"filter-domain\", \"Optionally filter by the bulk attribute\").StringVar(&c.filterTLSDomainID)\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\tc.CmdClause.Flag(\"page\", \"Page number of data set to fetch\").IntVar(&c.pageNumber)\n\tc.CmdClause.Flag(\"per-page\", \"Number of records per page\").IntVar(&c.pageSize)\n\tc.CmdClause.Flag(\"sort\", \"The order in which to list the results by creation date\").StringVar(&c.sort)\n\n\treturn &c\n}\n\n// ListCommand calls the Fastly API to list appropriate resources.\ntype ListCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tfilterTLSDomainID string\n\tpageNumber        int\n\tpageSize          int\n\tsort              string\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *ListCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tinput := c.constructInput()\n\n\to, err := c.Globals.APIClient.ListBulkCertificates(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Filter TLS Domain ID\": c.filterTLSDomainID,\n\t\t\t\"Page Number\":          c.pageNumber,\n\t\t\t\"Page Size\":            c.pageSize,\n\t\t\t\"Sort\":                 c.sort,\n\t\t})\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\tif c.Globals.Verbose() {\n\t\tprintVerbose(out, o)\n\t} else {\n\t\terr = c.printSummary(out, o)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\n// constructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *ListCommand) constructInput() *fastly.ListBulkCertificatesInput {\n\tvar input fastly.ListBulkCertificatesInput\n\n\tif c.filterTLSDomainID != \"\" {\n\t\tinput.FilterTLSDomainsIDMatch = c.filterTLSDomainID\n\t}\n\tif c.pageNumber > 0 {\n\t\tinput.PageNumber = c.pageNumber\n\t}\n\tif c.pageSize > 0 {\n\t\tinput.PageSize = c.pageSize\n\t}\n\tif c.sort != \"\" {\n\t\tinput.Sort = c.sort\n\t}\n\n\treturn &input\n}\n\n// printVerbose displays the information returned from the API in a verbose\n// format.\nfunc printVerbose(out io.Writer, rs []*fastly.BulkCertificate) {\n\tfor _, r := range rs {\n\t\tfmt.Fprintf(out, \"ID: %s\\n\", r.ID)\n\n\t\tif r.NotAfter != nil {\n\t\t\tfmt.Fprintf(out, \"Not after: %s\\n\", r.NotAfter)\n\t\t}\n\t\tif r.NotBefore != nil {\n\t\t\tfmt.Fprintf(out, \"Not before: %s\\n\", r.NotBefore)\n\t\t}\n\t\tif r.CreatedAt != nil {\n\t\t\tfmt.Fprintf(out, \"Created at: %s\\n\", r.CreatedAt)\n\t\t}\n\t\tif r.UpdatedAt != nil {\n\t\t\tfmt.Fprintf(out, \"Updated at: %s\\n\", r.UpdatedAt)\n\t\t}\n\n\t\tfmt.Fprintf(out, \"Replace: %t\\n\", r.Replace)\n\t\tfmt.Fprintf(out, \"\\n\")\n\t}\n}\n\n// printSummary displays the information returned from the API in a summarised\n// format.\nfunc (c *ListCommand) printSummary(out io.Writer, rs []*fastly.BulkCertificate) error {\n\tt := text.NewTable(out)\n\tt.AddHeader(\"ID\", \"REPLACE\", \"NOT BEFORE\", \"NOT AFTER\", \"CREATED\")\n\tfor _, r := range rs {\n\t\tt.AddLine(r.ID, r.Replace, r.NotBefore, r.NotAfter, r.CreatedAt)\n\t}\n\tt.Print()\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/tls/platform/platform_test.go",
    "content": "package platform_test\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\troot \"github.com/fastly/cli/pkg/commands/tls/platform\"\n\t\"github.com/fastly/cli/pkg/mock\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n)\n\nconst (\n\tvalidateAPIError      = \"validate API error\"\n\tvalidateAPISuccess    = \"validate API success\"\n\tvalidateMissingIDFlag = \"validate missing --id flag\"\n\tmockResponseID        = \"123\"\n)\n\nfunc TestTLSPlatformUpload(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --cert-blob flag\",\n\t\t\tArgs:      \"--intermediates-blob example\",\n\t\t\tWantError: \"required flag --cert-blob not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --intermediates-blob flag\",\n\t\t\tArgs:      \"--cert-blob example\",\n\t\t\tWantError: \"required flag --intermediates-blob not provided\",\n\t\t},\n\t\t{\n\t\t\tName: validateAPIError,\n\t\t\tAPI: &mock.API{\n\t\t\t\tCreateBulkCertificateFn: func(_ context.Context, _ *fastly.CreateBulkCertificateInput) (*fastly.BulkCertificate, error) {\n\t\t\t\t\treturn nil, testutil.Err\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:      \"--cert-blob example --intermediates-blob example\",\n\t\t\tWantError: testutil.Err.Error(),\n\t\t},\n\t\t{\n\t\t\tName: validateAPISuccess,\n\t\t\tAPI: &mock.API{\n\t\t\t\tCreateBulkCertificateFn: func(_ context.Context, _ *fastly.CreateBulkCertificateInput) (*fastly.BulkCertificate, error) {\n\t\t\t\t\treturn &fastly.BulkCertificate{\n\t\t\t\t\t\tID: mockResponseID,\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:       \"--cert-blob example --intermediates-blob example\",\n\t\t\tWantOutput: fmt.Sprintf(\"Uploaded TLS Bulk Certificate '%s'\", mockResponseID),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, \"upload\"}, scenarios)\n}\n\nfunc TestTLSPlatformDelete(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      validateMissingIDFlag,\n\t\t\tWantError: \"error parsing arguments: required flag --id not provided\",\n\t\t},\n\t\t{\n\t\t\tName: validateAPIError,\n\t\t\tAPI: &mock.API{\n\t\t\t\tDeleteBulkCertificateFn: func(_ context.Context, _ *fastly.DeleteBulkCertificateInput) error {\n\t\t\t\t\treturn testutil.Err\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:      \"--id example\",\n\t\t\tWantError: testutil.Err.Error(),\n\t\t},\n\t\t{\n\t\t\tName: validateAPISuccess,\n\t\t\tAPI: &mock.API{\n\t\t\t\tDeleteBulkCertificateFn: func(_ context.Context, _ *fastly.DeleteBulkCertificateInput) error {\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:       \"--id example\",\n\t\t\tWantOutput: \"Deleted TLS Bulk Certificate 'example'\",\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, \"delete\"}, scenarios)\n}\n\nfunc TestTLSPlatformDescribe(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      validateMissingIDFlag,\n\t\t\tWantError: \"error parsing arguments: required flag --id not provided\",\n\t\t},\n\t\t{\n\t\t\tName: validateAPIError,\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetBulkCertificateFn: func(_ context.Context, _ *fastly.GetBulkCertificateInput) (*fastly.BulkCertificate, error) {\n\t\t\t\t\treturn nil, testutil.Err\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:      \"--id example\",\n\t\t\tWantError: testutil.Err.Error(),\n\t\t},\n\t\t{\n\t\t\tName: validateAPISuccess,\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetBulkCertificateFn: func(_ context.Context, _ *fastly.GetBulkCertificateInput) (*fastly.BulkCertificate, error) {\n\t\t\t\t\tt := testutil.Date\n\t\t\t\t\treturn &fastly.BulkCertificate{\n\t\t\t\t\t\tID:        \"123\",\n\t\t\t\t\t\tCreatedAt: &t,\n\t\t\t\t\t\tUpdatedAt: &t,\n\t\t\t\t\t\tReplace:   true,\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:       \"--id example\",\n\t\t\tWantOutput: \"\\nID: 123\\nCreated at: 2021-06-15 23:00:00 +0000 UTC\\nUpdated at: 2021-06-15 23:00:00 +0000 UTC\\nReplace: true\\n\",\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, \"describe\"}, scenarios)\n}\n\nfunc TestTLSPlatformList(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName: validateAPIError,\n\t\t\tAPI: &mock.API{\n\t\t\t\tListBulkCertificatesFn: func(_ context.Context, _ *fastly.ListBulkCertificatesInput) ([]*fastly.BulkCertificate, error) {\n\t\t\t\t\treturn nil, testutil.Err\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: testutil.Err.Error(),\n\t\t},\n\t\t{\n\t\t\tName: validateAPISuccess,\n\t\t\tAPI: &mock.API{\n\t\t\t\tListBulkCertificatesFn: func(_ context.Context, _ *fastly.ListBulkCertificatesInput) ([]*fastly.BulkCertificate, error) {\n\t\t\t\t\tt := testutil.Date\n\t\t\t\t\treturn []*fastly.BulkCertificate{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tID:        mockResponseID,\n\t\t\t\t\t\t\tCreatedAt: &t,\n\t\t\t\t\t\t\tUpdatedAt: &t,\n\t\t\t\t\t\t\tReplace:   true,\n\t\t\t\t\t\t},\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:       \"--verbose\",\n\t\t\tWantOutput: \"\\nID: \" + mockResponseID + \"\\nCreated at: 2021-06-15 23:00:00 +0000 UTC\\nUpdated at: 2021-06-15 23:00:00 +0000 UTC\\nReplace: true\\n\",\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, \"list\"}, scenarios)\n}\n\nfunc TestTLSPlatformUpdate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      validateMissingIDFlag,\n\t\t\tArgs:      \"--cert-blob example --intermediates-blob example\",\n\t\t\tWantError: \"required flag --id not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --cert-blob flag\",\n\t\t\tArgs:      \"--id example --intermediates-blob example\",\n\t\t\tWantError: \"required flag --cert-blob not provided\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --intermediates-blob flag\",\n\t\t\tArgs:      \"--id example --cert-blob example\",\n\t\t\tWantError: \"required flag --intermediates-blob not provided\",\n\t\t},\n\t\t{\n\t\t\tName: validateAPIError,\n\t\t\tAPI: &mock.API{\n\t\t\t\tUpdateBulkCertificateFn: func(_ context.Context, _ *fastly.UpdateBulkCertificateInput) (*fastly.BulkCertificate, error) {\n\t\t\t\t\treturn nil, testutil.Err\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:      \"--id example --cert-blob example --intermediates-blob example\",\n\t\t\tWantError: testutil.Err.Error(),\n\t\t},\n\t\t{\n\t\t\tName: validateAPISuccess,\n\t\t\tAPI: &mock.API{\n\t\t\t\tUpdateBulkCertificateFn: func(_ context.Context, _ *fastly.UpdateBulkCertificateInput) (*fastly.BulkCertificate, error) {\n\t\t\t\t\treturn &fastly.BulkCertificate{\n\t\t\t\t\t\tID: mockResponseID,\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:       \"--id example --cert-blob example --intermediates-blob example\",\n\t\t\tWantOutput: \"Updated TLS Bulk Certificate '123'\",\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, \"update\"}, scenarios)\n}\n"
  },
  {
    "path": "pkg/commands/tls/platform/root.go",
    "content": "package platform\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"tls-platform\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manage large numbers of TLS certificates\")\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/tls/platform/update.go",
    "content": "package platform\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tvar c UpdateCommand\n\tc.CmdClause = parent.Command(\n\t\t\"update\", \"Replace a certificate with a newly reissued certificate\",\n\t)\n\tc.Globals = g\n\n\t// Required.\n\n\tc.CmdClause.Flag(\n\t\t\"id\", \"Alphanumeric string identifying a TLS bulk certificate\",\n\t).Required().StringVar(&c.id)\n\n\tc.CmdClause.Flag(\n\t\t\"cert-blob\", \"The PEM-formatted certificate blob\",\n\t).Required().StringVar(&c.certBlob)\n\n\tc.CmdClause.Flag(\n\t\t\"intermediates-blob\", \"The PEM-formatted chain of intermediate blobs\",\n\t).Required().StringVar(&c.intermediatesBlob)\n\n\t// Optional.\n\n\tc.CmdClause.Flag(\n\t\t\"allow-untrusted\", \"Allow certificates that chain to untrusted roots\",\n\t).Action(c.allowUntrusted.Set).BoolVar(&c.allowUntrusted.Value)\n\n\treturn &c\n}\n\n// UpdateCommand calls the Fastly API to update an appropriate resource.\ntype UpdateCommand struct {\n\targparser.Base\n\n\tallowUntrusted    argparser.OptionalBool\n\tcertBlob          string\n\tid                string\n\tintermediatesBlob string\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *UpdateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tinput := c.constructInput()\n\n\tr, err := c.Globals.APIClient.UpdateBulkCertificate(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"TLS Bulk Certificate ID\": c.id,\n\t\t\t\"Allow Untrusted\":         c.allowUntrusted.Value,\n\t\t})\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Updated TLS Bulk Certificate '%s'\", r.ID)\n\treturn nil\n}\n\n// constructInput transforms values parsed from CLI flags into an object to be\n// used by the API client library.\nfunc (c *UpdateCommand) constructInput() *fastly.UpdateBulkCertificateInput {\n\tvar input fastly.UpdateBulkCertificateInput\n\n\tinput.ID = c.id\n\tinput.CertBlob = c.certBlob\n\tinput.IntermediatesBlob = c.intermediatesBlob\n\n\tif c.allowUntrusted.WasSet {\n\t\tinput.AllowUntrusted = c.allowUntrusted.Value\n\t}\n\n\treturn &input\n}\n"
  },
  {
    "path": "pkg/commands/tls/subscription/create.go",
    "content": "package subscription\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\nconst emptyString = \"\"\n\nvar certAuth = []string{\"certainly\", \"lets-encrypt\", \"globalsign\"}\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tvar c CreateCommand\n\tc.CmdClause = parent.Command(\"create\", \"Create a new TLS subscription\").Alias(\"add\")\n\tc.Globals = g\n\n\t// Required.\n\tc.CmdClause.Flag(\"domain\", \"Domain(s) to add to the TLS certificates generated for the subscription (set flag once per domain)\").Required().StringsVar(&c.domains)\n\n\t// Optional.\n\tc.CmdClause.Flag(\"cert-auth\", \"The entity that issues and certifies the TLS certificates for your subscription. Valid values are certainly, lets-encrypt, and globalsign\").HintOptions(certAuth...).EnumVar(&c.certAuth, certAuth...)\n\tc.CmdClause.Flag(\"common-name\", \"The domain name associated with the subscription. Default to the first domain specified by --domain\").StringVar(&c.commonName)\n\tc.CmdClause.Flag(\"config\", \"Alphanumeric string identifying a TLS configuration\").StringVar(&c.config)\n\n\treturn &c\n}\n\n// CreateCommand calls the Fastly API to create an appropriate resource.\ntype CreateCommand struct {\n\targparser.Base\n\n\tcertAuth   string\n\tcommonName string\n\tconfig     string\n\tdomains    []string\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *CreateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tinput := c.constructInput()\n\n\tr, err := c.Globals.APIClient.CreateTLSSubscription(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"TLS Domains\":               c.domains,\n\t\t\t\"TLS Common Name\":           c.commonName,\n\t\t\t\"TLS Configuration ID\":      c.config,\n\t\t\t\"TLS Certificate Authority\": c.certAuth,\n\t\t})\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Created TLS Subscription '%s' (Authority: %s, Common Name: %s)\", r.ID, r.CertificateAuthority, r.CommonName.ID)\n\treturn nil\n}\n\n// constructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *CreateCommand) constructInput() *fastly.CreateTLSSubscriptionInput {\n\tvar input fastly.CreateTLSSubscriptionInput\n\n\tdomains := make([]*fastly.TLSDomain, len(c.domains))\n\tfor i, v := range c.domains {\n\t\tdomains[i] = &fastly.TLSDomain{ID: v}\n\t}\n\tinput.Domains = domains\n\n\tif c.commonName != emptyString {\n\t\tinput.CommonName = &fastly.TLSDomain{ID: c.commonName}\n\t}\n\tif c.certAuth != emptyString {\n\t\tinput.CertificateAuthority = c.certAuth\n\t}\n\tif c.config != emptyString {\n\t\tinput.Configuration = &fastly.TLSConfiguration{ID: c.config}\n\t}\n\n\treturn &input\n}\n"
  },
  {
    "path": "pkg/commands/tls/subscription/delete.go",
    "content": "package subscription\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {\n\tvar c DeleteCommand\n\tc.CmdClause = parent.Command(\"delete\", \"Destroy a TLS subscription. A subscription cannot be destroyed if there are domains in the TLS enabled state\").Alias(\"remove\")\n\tc.Globals = g\n\n\t// Required.\n\tc.CmdClause.Flag(\"id\", \"Alphanumeric string identifying a TLS subscription\").Required().StringVar(&c.id)\n\n\t// Optional.\n\tc.CmdClause.Flag(\"force\", \"A flag that allows you to edit and delete a subscription with active domains\").Action(c.force.Set).BoolVar(&c.force.Value)\n\n\treturn &c\n}\n\n// DeleteCommand calls the Fastly API to delete an appropriate resource.\ntype DeleteCommand struct {\n\targparser.Base\n\n\tforce argparser.OptionalBool\n\tid    string\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DeleteCommand) Exec(_ io.Reader, out io.Writer) error {\n\tinput := c.constructInput()\n\n\terr := c.Globals.APIClient.DeleteTLSSubscription(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"TLS Subscription ID\": c.id,\n\t\t\t\"Force\":               c.force.Value,\n\t\t})\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Deleted TLS Subscription '%s' (force: %t)\", c.id, c.force.Value)\n\treturn nil\n}\n\n// constructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *DeleteCommand) constructInput() *fastly.DeleteTLSSubscriptionInput {\n\tvar input fastly.DeleteTLSSubscriptionInput\n\n\tinput.ID = c.id\n\n\tif c.force.WasSet {\n\t\tinput.Force = c.force.Value\n\t}\n\n\treturn &input\n}\n"
  },
  {
    "path": "pkg/commands/tls/subscription/describe.go",
    "content": "package subscription\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\nvar include = []string{\"tls_authorizations\", \"tls_authorizations.globalsign_email_challenge\"}\n\n// NewDescribeCommand returns a usable command registered under the parent.\nfunc NewDescribeCommand(parent argparser.Registerer, g *global.Data) *DescribeCommand {\n\tvar c DescribeCommand\n\tc.CmdClause = parent.Command(\"describe\", \"Show a TLS subscription\").Alias(\"get\")\n\tc.Globals = g\n\n\t// Required.\n\tc.CmdClause.Flag(\"id\", \"Alphanumeric string identifying a TLS subscription\").Required().StringVar(&c.id)\n\n\t// Optional.\n\tc.CmdClause.Flag(\"include\", \"Include related objects (comma-separated values)\").HintOptions(include...).EnumVar(&c.include, include...)\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\n\treturn &c\n}\n\n// DescribeCommand calls the Fastly API to describe an appropriate resource.\ntype DescribeCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tid      string\n\tinclude string\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DescribeCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tinput := c.constructInput()\n\n\to, err := c.Globals.APIClient.GetTLSSubscription(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"TLS Subscription ID\": c.id,\n\t\t\t\"Include\":             c.include,\n\t\t})\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\treturn c.print(out, o)\n}\n\n// constructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *DescribeCommand) constructInput() *fastly.GetTLSSubscriptionInput {\n\tvar input fastly.GetTLSSubscriptionInput\n\n\tinput.ID = c.id\n\n\tif c.include != \"\" {\n\t\tinput.Include = &c.include\n\t}\n\n\treturn &input\n}\n\n// print displays the information returned from the API.\nfunc (c *DescribeCommand) print(out io.Writer, r *fastly.TLSSubscription) error {\n\tfmt.Fprintf(out, \"\\nID: %s\\n\", r.ID)\n\tfmt.Fprintf(out, \"Certificate Authority: %s\\n\", r.CertificateAuthority)\n\tfmt.Fprintf(out, \"State: %s\\n\", r.State)\n\n\tif r.CreatedAt != nil {\n\t\tfmt.Fprintf(out, \"Created at: %s\\n\", r.CreatedAt)\n\t}\n\tif r.UpdatedAt != nil {\n\t\tfmt.Fprintf(out, \"Updated at: %s\\n\", r.UpdatedAt)\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/tls/subscription/doc.go",
    "content": "// Package subscription contains commands to inspect and manipulate Fastly\n// procured TLS certificates.\npackage subscription\n"
  },
  {
    "path": "pkg/commands/tls/subscription/list.go",
    "content": "package subscription\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\nvar states = []string{\"pending\", \"processing\", \"issued\", \"renewing\"}\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tvar c ListCommand\n\tc.CmdClause = parent.Command(\"list\", \"List all TLS subscriptions\")\n\tc.Globals = g\n\n\t// Optional.\n\tc.CmdClause.Flag(\"filter-active\", \"Limit the returned subscriptions to those that have currently active orders\").BoolVar(&c.filterHasActiveOrder)\n\tc.CmdClause.Flag(\"filter-domain\", \"Limit the returned subscriptions to those that include the specific domain\").StringVar(&c.filterTLSDomainID)\n\tc.CmdClause.Flag(\"filter-state\", \"Limit the returned subscriptions by state\").HintOptions(states...).EnumVar(&c.filterState, states...)\n\tc.CmdClause.Flag(\"include\", \"Include related objects (comma-separated values)\").HintOptions(include...).EnumVar(&c.include, include...) // include is defined in ./describe.go\n\tc.RegisterFlagBool(c.JSONFlag())                                                                                                        // --json\n\tc.CmdClause.Flag(\"page\", \"Page number of data set to fetch\").IntVar(&c.pageNumber)\n\tc.CmdClause.Flag(\"per-page\", \"Number of records per page\").IntVar(&c.pageSize)\n\tc.CmdClause.Flag(\"sort\", \"The order in which to list the results by creation date\").StringVar(&c.sort)\n\n\treturn &c\n}\n\n// ListCommand calls the Fastly API to list appropriate resources.\ntype ListCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tfilterHasActiveOrder bool\n\tfilterState          string\n\tfilterTLSDomainID    string\n\tinclude              string\n\tpageNumber           int\n\tpageSize             int\n\tsort                 string\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *ListCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tinput := c.constructInput()\n\n\to, err := c.Globals.APIClient.ListTLSSubscriptions(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Filter Active\":        c.filterHasActiveOrder,\n\t\t\t\"Filter State\":         c.filterState,\n\t\t\t\"Filter TLS Domain ID\": c.filterTLSDomainID,\n\t\t\t\"Include\":              c.include,\n\t\t\t\"Page Number\":          c.pageNumber,\n\t\t\t\"Page Size\":            c.pageSize,\n\t\t\t\"Sort\":                 c.sort,\n\t\t})\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\tif c.Globals.Verbose() {\n\t\tc.printVerbose(out, o)\n\t} else {\n\t\terr = c.printSummary(out, o)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\n// constructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *ListCommand) constructInput() *fastly.ListTLSSubscriptionsInput {\n\tvar input fastly.ListTLSSubscriptionsInput\n\n\tif c.filterHasActiveOrder {\n\t\tinput.FilterActiveOrders = c.filterHasActiveOrder\n\t}\n\tif c.filterState != \"\" {\n\t\tinput.FilterState = c.filterState\n\t}\n\tif c.filterTLSDomainID != \"\" {\n\t\tinput.FilterTLSDomainsID = c.filterTLSDomainID\n\t}\n\tif c.include != \"\" {\n\t\tinput.Include = c.include\n\t}\n\tif c.pageNumber > 0 {\n\t\tinput.PageNumber = c.pageNumber\n\t}\n\tif c.pageSize > 0 {\n\t\tinput.PageSize = c.pageSize\n\t}\n\tif c.sort != \"\" {\n\t\tinput.Sort = c.sort\n\t}\n\n\treturn &input\n}\n\n// printVerbose displays the information returned from the API in a verbose\n// format.\nfunc (c *ListCommand) printVerbose(out io.Writer, rs []*fastly.TLSSubscription) {\n\tfor _, r := range rs {\n\t\tfmt.Fprintf(out, \"ID: %s\\n\", r.ID)\n\t\tfmt.Fprintf(out, \"Certificate Authority: %s\\n\", r.CertificateAuthority)\n\t\tfmt.Fprintf(out, \"State: %s\\n\", r.State)\n\n\t\tif r.CreatedAt != nil {\n\t\t\tfmt.Fprintf(out, \"Created at: %s\\n\", r.CreatedAt)\n\t\t}\n\t\tif r.UpdatedAt != nil {\n\t\t\tfmt.Fprintf(out, \"Updated at: %s\\n\", r.UpdatedAt)\n\t\t}\n\n\t\tfmt.Fprintf(out, \"\\n\")\n\t}\n}\n\n// printSummary displays the information returned from the API in a summarised\n// format.\nfunc (c *ListCommand) printSummary(out io.Writer, rs []*fastly.TLSSubscription) error {\n\tt := text.NewTable(out)\n\tt.AddHeader(\"ID\", \"CERT AUTHORITY\", \"STATE\", \"CREATED\")\n\tfor _, r := range rs {\n\t\tt.AddLine(r.ID, r.CertificateAuthority, r.State, r.CreatedAt)\n\t}\n\tt.Print()\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/tls/subscription/root.go",
    "content": "package subscription\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"tls-subscription\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Generate TLS certificates procured and renewed by Fastly\")\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/tls/subscription/subscription_test.go",
    "content": "package subscription_test\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\troot \"github.com/fastly/cli/pkg/commands/tls/subscription\"\n\t\"github.com/fastly/cli/pkg/mock\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n)\n\nconst (\n\tcertificateAuthority  = \"lets-encrypt\"\n\tmockResponseID        = \"123\"\n\tvalidateAPIError      = \"validate API error\"\n\tvalidateAPISuccess    = \"validate API success\"\n\tvalidateMissingIDFlag = \"validate missing --id flag\"\n)\n\nfunc TestTLSSubscriptionCreate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --domain flag\",\n\t\t\tWantError: \"required flag --domain not provided\",\n\t\t},\n\t\t{\n\t\t\tName: validateAPIError,\n\t\t\tAPI: &mock.API{\n\t\t\t\tCreateTLSSubscriptionFn: func(_ context.Context, _ *fastly.CreateTLSSubscriptionInput) (*fastly.TLSSubscription, error) {\n\t\t\t\t\treturn nil, testutil.Err\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:      \"--domain example.com\",\n\t\t\tWantError: testutil.Err.Error(),\n\t\t},\n\t\t{\n\t\t\tName: validateAPISuccess,\n\t\t\tAPI: &mock.API{\n\t\t\t\tCreateTLSSubscriptionFn: func(_ context.Context, _ *fastly.CreateTLSSubscriptionInput) (*fastly.TLSSubscription, error) {\n\t\t\t\t\treturn &fastly.TLSSubscription{\n\t\t\t\t\t\tID:                   mockResponseID,\n\t\t\t\t\t\tCertificateAuthority: certificateAuthority,\n\t\t\t\t\t\tCommonName: &fastly.TLSDomain{\n\t\t\t\t\t\t\tID: \"example.com\",\n\t\t\t\t\t\t},\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:       \"--domain example.com\",\n\t\t\tWantOutput: fmt.Sprintf(\"Created TLS Subscription '%s' (Authority: %s, Common Name: example.com)\", mockResponseID, certificateAuthority),\n\t\t},\n\t\t{\n\t\t\tName: \"validate cert-auth == certainly\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tCreateTLSSubscriptionFn: func(_ context.Context, i *fastly.CreateTLSSubscriptionInput) (*fastly.TLSSubscription, error) {\n\t\t\t\t\treturn &fastly.TLSSubscription{\n\t\t\t\t\t\tID:                   mockResponseID,\n\t\t\t\t\t\tCertificateAuthority: i.CertificateAuthority,\n\t\t\t\t\t\tCommonName:           i.Domains[0],\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:       \"--domain example.com --cert-auth certainly\",\n\t\t\tWantOutput: fmt.Sprintf(\"Created TLS Subscription '%s' (Authority: certainly, Common Name: example.com)\", mockResponseID),\n\t\t},\n\t\t{\n\t\t\tName: \"validate cert-auth == lets-encrypt\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tCreateTLSSubscriptionFn: func(_ context.Context, i *fastly.CreateTLSSubscriptionInput) (*fastly.TLSSubscription, error) {\n\t\t\t\t\treturn &fastly.TLSSubscription{\n\t\t\t\t\t\tID:                   mockResponseID,\n\t\t\t\t\t\tCertificateAuthority: i.CertificateAuthority,\n\t\t\t\t\t\tCommonName:           i.Domains[0],\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:       \"--domain example.com --cert-auth lets-encrypt\",\n\t\t\tWantOutput: fmt.Sprintf(\"Created TLS Subscription '%s' (Authority: lets-encrypt, Common Name: example.com)\", mockResponseID),\n\t\t},\n\t\t{\n\t\t\tName: \"validate cert-auth == globalsign\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tCreateTLSSubscriptionFn: func(_ context.Context, i *fastly.CreateTLSSubscriptionInput) (*fastly.TLSSubscription, error) {\n\t\t\t\t\treturn &fastly.TLSSubscription{\n\t\t\t\t\t\tID:                   mockResponseID,\n\t\t\t\t\t\tCertificateAuthority: i.CertificateAuthority,\n\t\t\t\t\t\tCommonName:           i.Domains[0],\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:       \"--domain example.com --cert-auth globalsign\",\n\t\t\tWantOutput: fmt.Sprintf(\"Created TLS Subscription '%s' (Authority: globalsign, Common Name: example.com)\", mockResponseID),\n\t\t},\n\t\t{\n\t\t\tName: \"validate cert-auth is invalid\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tCreateTLSSubscriptionFn: func(_ context.Context, i *fastly.CreateTLSSubscriptionInput) (*fastly.TLSSubscription, error) {\n\t\t\t\t\treturn &fastly.TLSSubscription{\n\t\t\t\t\t\tID:                   mockResponseID,\n\t\t\t\t\t\tCertificateAuthority: i.CertificateAuthority,\n\t\t\t\t\t\tCommonName:           i.Domains[0],\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:      \"--domain example.com --cert-auth not-valid\",\n\t\t\tWantError: \"enum value must be one of certainly,lets-encrypt,globalsign\",\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, \"create\"}, scenarios)\n}\n\nfunc TestTLSSubscriptionDelete(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      validateMissingIDFlag,\n\t\t\tWantError: \"error parsing arguments: required flag --id not provided\",\n\t\t},\n\t\t{\n\t\t\tName: validateAPIError,\n\t\t\tAPI: &mock.API{\n\t\t\t\tDeleteTLSSubscriptionFn: func(_ context.Context, _ *fastly.DeleteTLSSubscriptionInput) error {\n\t\t\t\t\treturn testutil.Err\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:      \"--id example\",\n\t\t\tWantError: testutil.Err.Error(),\n\t\t},\n\t\t{\n\t\t\tName: validateAPISuccess,\n\t\t\tAPI: &mock.API{\n\t\t\t\tDeleteTLSSubscriptionFn: func(_ context.Context, _ *fastly.DeleteTLSSubscriptionInput) error {\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:       \"--id example\",\n\t\t\tWantOutput: \"Deleted TLS Subscription 'example' (force: false)\",\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, \"delete\"}, scenarios)\n}\n\nfunc TestTLSSubscriptionDescribe(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      validateMissingIDFlag,\n\t\t\tWantError: \"error parsing arguments: required flag --id not provided\",\n\t\t},\n\t\t{\n\t\t\tName: validateAPIError,\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetTLSSubscriptionFn: func(_ context.Context, _ *fastly.GetTLSSubscriptionInput) (*fastly.TLSSubscription, error) {\n\t\t\t\t\treturn nil, testutil.Err\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:      \"--id example\",\n\t\t\tWantError: testutil.Err.Error(),\n\t\t},\n\t\t{\n\t\t\tName: validateAPISuccess,\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetTLSSubscriptionFn: func(_ context.Context, _ *fastly.GetTLSSubscriptionInput) (*fastly.TLSSubscription, error) {\n\t\t\t\t\tt := testutil.Date\n\t\t\t\t\treturn &fastly.TLSSubscription{\n\t\t\t\t\t\tID:                   mockResponseID,\n\t\t\t\t\t\tCertificateAuthority: certificateAuthority,\n\t\t\t\t\t\tState:                \"pending\",\n\t\t\t\t\t\tCreatedAt:            &t,\n\t\t\t\t\t\tUpdatedAt:            &t,\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:       \"--id example\",\n\t\t\tWantOutput: \"\\nID: \" + mockResponseID + \"\\nCertificate Authority: \" + certificateAuthority + \"\\nState: pending\\nCreated at: 2021-06-15 23:00:00 +0000 UTC\\nUpdated at: 2021-06-15 23:00:00 +0000 UTC\\n\",\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, \"describe\"}, scenarios)\n}\n\nfunc TestTLSSubscriptionList(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName: validateAPIError,\n\t\t\tAPI: &mock.API{\n\t\t\t\tListTLSSubscriptionsFn: func(_ context.Context, _ *fastly.ListTLSSubscriptionsInput) ([]*fastly.TLSSubscription, error) {\n\t\t\t\t\treturn nil, testutil.Err\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantError: testutil.Err.Error(),\n\t\t},\n\t\t{\n\t\t\tName: validateAPISuccess,\n\t\t\tAPI: &mock.API{\n\t\t\t\tListTLSSubscriptionsFn: func(_ context.Context, _ *fastly.ListTLSSubscriptionsInput) ([]*fastly.TLSSubscription, error) {\n\t\t\t\t\tt := testutil.Date\n\t\t\t\t\treturn []*fastly.TLSSubscription{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tID:                   mockResponseID,\n\t\t\t\t\t\t\tCertificateAuthority: certificateAuthority,\n\t\t\t\t\t\t\tState:                \"pending\",\n\t\t\t\t\t\t\tCreatedAt:            &t,\n\t\t\t\t\t\t\tUpdatedAt:            &t,\n\t\t\t\t\t\t},\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:       \"--verbose\",\n\t\t\tWantOutput: \"\\nID: \" + mockResponseID + \"\\nCertificate Authority: \" + certificateAuthority + \"\\nState: pending\\nCreated at: 2021-06-15 23:00:00 +0000 UTC\\nUpdated at: 2021-06-15 23:00:00 +0000 UTC\\n\",\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, \"list\"}, scenarios)\n}\n\nfunc TestTLSSubscriptionUpdate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      validateMissingIDFlag,\n\t\t\tWantError: \"required flag --id not provided\",\n\t\t},\n\t\t{\n\t\t\tName: validateAPIError,\n\t\t\tAPI: &mock.API{\n\t\t\t\tUpdateTLSSubscriptionFn: func(_ context.Context, _ *fastly.UpdateTLSSubscriptionInput) (*fastly.TLSSubscription, error) {\n\t\t\t\t\treturn nil, testutil.Err\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:      \"--id example\",\n\t\t\tWantError: testutil.Err.Error(),\n\t\t},\n\t\t{\n\t\t\tName: validateAPISuccess,\n\t\t\tAPI: &mock.API{\n\t\t\t\tUpdateTLSSubscriptionFn: func(_ context.Context, _ *fastly.UpdateTLSSubscriptionInput) (*fastly.TLSSubscription, error) {\n\t\t\t\t\treturn &fastly.TLSSubscription{\n\t\t\t\t\t\tID:                   mockResponseID,\n\t\t\t\t\t\tCertificateAuthority: certificateAuthority,\n\t\t\t\t\t\tCommonName: &fastly.TLSDomain{\n\t\t\t\t\t\t\tID: \"example.com\",\n\t\t\t\t\t\t},\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:       \"--id example\",\n\t\t\tWantOutput: fmt.Sprintf(\"Updated TLS Subscription '%s' (Authority: %s, Common Name: example.com)\", mockResponseID, certificateAuthority),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, \"update\"}, scenarios)\n}\n"
  },
  {
    "path": "pkg/commands/tls/subscription/update.go",
    "content": "package subscription\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tvar c UpdateCommand\n\tc.CmdClause = parent.Command(\"update\", \"Change the TLS domains or common name associated with this subscription, or update the TLS configuration for this set of domains\")\n\tc.Globals = g\n\n\t// Required.\n\tc.CmdClause.Flag(\"id\", \"Alphanumeric string identifying a TLS subscription\").Required().StringVar(&c.id)\n\n\t// Optional.\n\tc.CmdClause.Flag(\"common-name\", \"The domain name associated with the subscription\").StringVar(&c.commonName)\n\tc.CmdClause.Flag(\"config\", \"Alphanumeric string identifying a TLS configuration\").StringVar(&c.config)\n\tc.CmdClause.Flag(\"domain\", \"Domain(s) to add to the TLS certificates generated for the subscription (set flag once per domain)\").StringsVar(&c.domains)\n\tc.CmdClause.Flag(\"force\", \"A flag that allows you to edit and delete a subscription with active domains\").Action(c.force.Set).BoolVar(&c.force.Value)\n\n\treturn &c\n}\n\n// UpdateCommand calls the Fastly API to update an appropriate resource.\ntype UpdateCommand struct {\n\targparser.Base\n\n\tcommonName string\n\tconfig     string\n\tdomains    []string\n\tforce      argparser.OptionalBool\n\tid         string\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *UpdateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tinput := c.constructInput()\n\n\tr, err := c.Globals.APIClient.UpdateTLSSubscription(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"TLS Subscription ID\": c.id,\n\t\t\t\"Force\":               c.force.Value,\n\t\t})\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Updated TLS Subscription '%s' (Authority: %s, Common Name: %s)\", r.ID, r.CertificateAuthority, r.CommonName.ID)\n\treturn nil\n}\n\n// constructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *UpdateCommand) constructInput() *fastly.UpdateTLSSubscriptionInput {\n\tvar input fastly.UpdateTLSSubscriptionInput\n\n\tinput.ID = c.id\n\n\tdomains := make([]*fastly.TLSDomain, len(c.domains))\n\tfor i, v := range c.domains {\n\t\tdomains[i] = &fastly.TLSDomain{ID: v}\n\t}\n\tinput.Domains = domains\n\n\tif c.commonName != \"\" {\n\t\tinput.CommonName = &fastly.TLSDomain{ID: c.commonName}\n\t}\n\tif c.config != \"\" {\n\t\tinput.Configuration = &fastly.TLSConfiguration{ID: c.config}\n\t}\n\tif c.force.WasSet {\n\t\tinput.Force = c.force.Value\n\t}\n\n\treturn &input\n}\n"
  },
  {
    "path": "pkg/commands/tools/doc.go",
    "content": "// Package tools contains tools for working with the Fastly platform.\npackage tools\n"
  },
  {
    "path": "pkg/commands/tools/domain/doc.go",
    "content": "// Package domain contains Domain Discovery API tools.\npackage domain\n"
  },
  {
    "path": "pkg/commands/tools/domain/root.go",
    "content": "package domain\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all tool subcommands in this package.\n// It should be installed under the primary `tools` root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"domain\"\n\n// NewRootCommand returns a new tools command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Domain Discovery API tools\")\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/tools/domain/status.go",
    "content": "package domain\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/domainmanagement/v1/tools/status\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// GetDomainStatusCommand calls the Fastly API to check the availability of a domain name.\ntype GetDomainStatusCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\t// Required.\n\tdomain string\n\t// Optional.\n\tscope argparser.OptionalString\n}\n\n// NewDomainStatusCommand returns a usable DomainStatusCommand registered under the parent.\nfunc NewDomainStatusCommand(parent argparser.Registerer, g *global.Data) *GetDomainStatusCommand {\n\tcmd := GetDomainStatusCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\n\tcmd.CmdClause = parent.Command(\"status\", \"Check domain name availability\")\n\t// Required.\n\tcmd.CmdClause.Arg(\"domain\", \"Domain name to check\").Required().StringVar(&cmd.domain)\n\t// Optional.\n\tcmd.RegisterFlagBool(cmd.JSONFlag())\n\tcmd.CmdClause.Flag(\"scope\", \"Specify `--scope=estimate` to perform an “estimated” availability check, which checks the DNS and domain aftermarkets, not domain registries\").Action(cmd.scope.Set).StringVar(&cmd.scope.Value)\n\n\treturn &cmd\n}\n\n// Exec invokes the application logic for the command.\nfunc (g *GetDomainStatusCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif g.Globals.Verbose() && g.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tinput := &status.GetInput{\n\t\tDomain: g.domain,\n\t}\n\n\tif g.scope.WasSet {\n\t\tscope := status.Scope(g.scope.Value)\n\t\tif scope != status.ScopeEstimate {\n\t\t\treturn fsterr.RemediationError{\n\t\t\t\tInner:       errors.New(\"invalid scope provided\"),\n\t\t\t\tRemediation: \"Use `--scope=estimate` for an estimated status check\",\n\t\t\t}\n\t\t}\n\t\tinput.Scope = fastly.ToPointer(scope)\n\t}\n\n\tfc, ok := g.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to acquire the Fastly API client\")\n\t}\n\n\tst, err := status.Get(context.TODO(), fc, input)\n\tif err != nil {\n\t\tg.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif ok, err := g.WriteJSON(out, st); ok {\n\t\treturn err\n\t}\n\n\tprintStatusSummary(out, st)\n\treturn nil\n}\n\n// printStatusSummary displays the information returned from the API in a summarized format.\nfunc printStatusSummary(w io.Writer, st *status.Status) {\n\tfmt.Fprintf(w, \"Domain: %s\\n\", st.Domain)\n\tfmt.Fprintf(w, \"Zone: %s\\n\", st.Zone)\n\tfmt.Fprintf(w, \"Status: %s\\n\", st.Status)\n\tfmt.Fprintf(w, \"Tags: %s\\n\", st.Tags)\n\n\tif st.Scope != nil {\n\t\tfmt.Fprintf(w, \"Scope: %s\\n\", *st.Scope)\n\t}\n\n\tif len(st.Offers) > 0 {\n\t\tfmt.Fprintf(w, \"Offers:\\n\")\n\t\tfor _, o := range st.Offers {\n\t\t\tfmt.Fprintf(w, \"  - Vendor: %s\\n\", o.Vendor)\n\t\t\tfmt.Fprintf(w, \"    Currency: %s\\n\", o.Currency)\n\t\t\tfmt.Fprintf(w, \"    Price: %s\\n\", o.Price)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "pkg/commands/tools/domain/status_test.go",
    "content": "package domain_test\n\nimport (\n\t\"bytes\"\n\t\"io\"\n\t\"net/http\"\n\t\"testing\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/domainmanagement/v1/tools/status\"\n\n\t\"github.com/fastly/cli/pkg/commands/tools\"\n\t\"github.com/fastly/cli/pkg/commands/tools/domain\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n)\n\nfunc TestNewDomainsV1ToolsStatusCommand(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"\",\n\t\t\tWantError: \"error parsing arguments: required argument 'domain' not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs:      \"fastly-cli-testing.com --scope not-estimate\",\n\t\t\tWantError: \"invalid scope provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"fastly-cli-testing.com\",\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(status.Status{\n\t\t\t\t\t\t\tDomain: \"fastly-cli-testing.com\",\n\t\t\t\t\t\t\tZone:   \"com\",\n\t\t\t\t\t\t\tStatus: \"undelegated inactive\",\n\t\t\t\t\t\t\tTags:   \"generic\",\n\t\t\t\t\t\t}))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: `Domain: fastly-cli-testing.com\nZone: com\nStatus: undelegated inactive\nTags: generic\n`,\n\t\t},\n\t\t{\n\t\t\tArgs: \"--scope estimate fastly-cli-testing-offers.com\",\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(status.Status{\n\t\t\t\t\t\t\tDomain: \"fastly-cli-testing-offers.com\",\n\t\t\t\t\t\t\tZone:   \"com\",\n\t\t\t\t\t\t\tStatus: \"marketed priced transferable active\",\n\t\t\t\t\t\t\tTags:   \"generic\",\n\t\t\t\t\t\t\tScope:  fastly.ToPointer(status.ScopeEstimate),\n\t\t\t\t\t\t\tOffers: []status.Offer{\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tVendor:   \"example.com\",\n\t\t\t\t\t\t\t\t\tCurrency: \"USD\",\n\t\t\t\t\t\t\t\t\tPrice:    \"20000.00\",\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t}))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: `Domain: fastly-cli-testing-offers.com\nZone: com\nStatus: marketed priced transferable active\nTags: generic\nScope: estimate\nOffers:\n  - Vendor: example.com\n    Currency: USD\n    Price: 20000.00\n`,\n\t\t},\n\t\t{\n\t\t\tArgs: \"-j --scope estimate fastly-cli-testing-offers.com\",\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(status.Status{\n\t\t\t\t\t\t\tDomain: \"fastly-cli-testing-offers.com\",\n\t\t\t\t\t\t\tZone:   \"com\",\n\t\t\t\t\t\t\tStatus: \"marketed priced transferable active\",\n\t\t\t\t\t\t\tTags:   \"generic\",\n\t\t\t\t\t\t\tScope:  fastly.ToPointer(status.ScopeEstimate),\n\t\t\t\t\t\t\tOffers: []status.Offer{\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tVendor:   \"example.com\",\n\t\t\t\t\t\t\t\t\tCurrency: \"USD\",\n\t\t\t\t\t\t\t\t\tPrice:    \"20000.00\",\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t}))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: `{\n  \"domain\": \"fastly-cli-testing-offers.com\",\n  \"zone\": \"com\",\n  \"status\": \"marketed priced transferable active\",\n  \"scope\": \"estimate\",\n  \"tags\": \"generic\",\n  \"offers\": [\n    {\n      \"vendor\": \"example.com\",\n      \"price\": \"20000.00\",\n      \"currency\": \"USD\"\n    }\n  ]\n}\n`,\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{tools.CommandName, domain.CommandName, \"status\"}, scenarios)\n}\n"
  },
  {
    "path": "pkg/commands/tools/domain/suggest.go",
    "content": "package domain\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"strings\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/domainmanagement/v1/tools/suggest\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// GetDomainSuggestionsCommand calls the Fastly API and results domain search results for a given query.\ntype GetDomainSuggestionsCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\t// Required.\n\tquery []string\n\t// Optional.\n\tdefaults argparser.OptionalString\n\tkeywords argparser.OptionalString\n\tlocation argparser.OptionalString\n\tvendor   argparser.OptionalString\n}\n\n// NewDomainSuggestionsCommand returns a usable DomainSuggestionCommand registered under the parent.\nfunc NewDomainSuggestionsCommand(parent argparser.Registerer, g *global.Data) *GetDomainSuggestionsCommand {\n\tcmd := GetDomainSuggestionsCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\n\tcmd.CmdClause = parent.Command(\"suggest\", \"Request domain search results for a given query\")\n\t// Required.\n\tcmd.CmdClause.Arg(\"query\", \"Search query, e.g. “acme coffee shop”\").Required().StringsVar(&cmd.query)\n\t// Optional.\n\tcmd.CmdClause.Flag(\"defaults\", \"Comma-separated list of default zones to include in the search results response, e.g. `--defaults=uk,co.uk`\").Action(cmd.defaults.Set).StringVar(&cmd.defaults.Value)\n\tcmd.RegisterFlagBool(cmd.JSONFlag())\n\tcmd.CmdClause.Flag(\"keywords\", \"Comma-separated list of keywords for seeding the search results, e.g. `--keywords=dance,party`\").Action(cmd.keywords.Set).StringVar(&cmd.keywords.Value)\n\tcmd.CmdClause.Flag(\"location\", \"Override IP geolocation with a two-character country code, e.g. `--location=in` to include Indian zones in the search results\").Action(cmd.location.Set).StringVar(&cmd.location.Value)\n\tcmd.CmdClause.Flag(\"vendor\", \"The domain name of a specific registrar or vendor, to filter the search results to the list of zones they support\").Action(cmd.vendor.Set).StringVar(&cmd.vendor.Value)\n\n\treturn &cmd\n}\n\n// Exec invokes the application logic for the command.\nfunc (g *GetDomainSuggestionsCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif g.Globals.Verbose() && g.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tinput := &suggest.GetInput{\n\t\tQuery: strings.Join(g.query, \" \"),\n\t}\n\n\tif g.defaults.WasSet {\n\t\tinput.Defaults = fastly.ToPointer(g.defaults.Value)\n\t}\n\n\tif g.keywords.WasSet {\n\t\tinput.Keywords = fastly.ToPointer(g.keywords.Value)\n\t}\n\n\tif g.location.WasSet {\n\t\tinput.Location = fastly.ToPointer(g.location.Value)\n\t}\n\n\tif g.vendor.WasSet {\n\t\tinput.Vendor = fastly.ToPointer(g.vendor.Value)\n\t}\n\n\tfc, ok := g.Globals.APIClient.(*fastly.Client)\n\tif !ok {\n\t\treturn errors.New(\"failed to acquire the Fastly API client\")\n\t}\n\n\tsuggestions, err := suggest.Get(context.TODO(), fc, input)\n\tif err != nil {\n\t\tg.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif ok, err := g.WriteJSON(out, suggestions); ok {\n\t\treturn err\n\t}\n\n\tif g.Globals.Verbose() {\n\t\tprintSuggestionsVerbose(out, suggestions)\n\t} else {\n\t\tprintSuggestionsSummary(out, suggestions)\n\t}\n\n\treturn nil\n}\n\n// printSuggestionsSummary displays the information returned from the API in a summarized\n// format.\nfunc printSuggestionsSummary(out io.Writer, suggestions *suggest.Suggestions) {\n\tt := text.NewTable(out)\n\tt.AddHeader(\"Domain\", \"Subdomain\", \"Zone\", \"Path\")\n\tfor _, suggestion := range suggestions.Results {\n\t\tvar path string\n\t\tif suggestion.Path != nil {\n\t\t\tpath = *suggestion.Path\n\t\t}\n\t\tt.AddLine(suggestion.Domain, suggestion.Subdomain, suggestion.Zone, path)\n\t}\n\n\tt.Print()\n}\n\n// printSuggestionsVerbose displays the information returned from the API in a verbose\n// format.\nfunc printSuggestionsVerbose(out io.Writer, suggestions *suggest.Suggestions) {\n\tfor _, suggestion := range suggestions.Results {\n\t\tfmt.Fprintf(out, \"Domain: %s\\n\", suggestion.Domain)\n\t\tfmt.Fprintf(out, \"Subdomain: %s\\n\", suggestion.Subdomain)\n\t\tfmt.Fprintf(out, \"Zone: %s\\n\", suggestion.Zone)\n\n\t\tif suggestion.Path != nil {\n\t\t\tfmt.Fprintf(out, \"Path: %s\\n\", *suggestion.Path)\n\t\t}\n\t\tfmt.Fprintf(out, \"\\n\")\n\t}\n}\n"
  },
  {
    "path": "pkg/commands/tools/domain/suggest_test.go",
    "content": "package domain_test\n\nimport (\n\t\"bytes\"\n\t\"io\"\n\t\"net/http\"\n\t\"testing\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\t\"github.com/fastly/go-fastly/v15/fastly/domainmanagement/v1/tools/suggest\"\n\n\t\"github.com/fastly/cli/pkg/commands/tools\"\n\t\"github.com/fastly/cli/pkg/commands/tools/domain\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n)\n\nfunc TestNewDomainsV1ToolsSuggestCommand(t *testing.T) {\n\ttestSuggestions := suggest.Suggestions{\n\t\tResults: []suggest.Suggestion{\n\t\t\t{\n\t\t\t\tDomain:    \"fastlytest.ing\",\n\t\t\t\tSubdomain: \"fastlytest.\",\n\t\t\t\tZone:      \"ing\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tDomain:    \"fastlytesti.ng\",\n\t\t\t\tSubdomain: \"fastlytesti.\",\n\t\t\t\tZone:      \"ng\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tDomain:    \"fastlytesting.com\",\n\t\t\t\tSubdomain: \"fastlytesting.\",\n\t\t\t\tZone:      \"com\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tDomain:    \"fastlytesting.net\",\n\t\t\t\tSubdomain: \"fastlytesting.\",\n\t\t\t\tZone:      \"net\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tDomain:    \"fastlytest.in\",\n\t\t\t\tSubdomain: \"fastlytest.\",\n\t\t\t\tZone:      \"in\",\n\t\t\t\tPath:      fastly.ToPointer(\"/g\"),\n\t\t\t},\n\t\t},\n\t}\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tArgs:      \"\",\n\t\t\tWantError: \"error parsing arguments: required argument 'query' not provided\",\n\t\t},\n\t\t{\n\t\t\tArgs: \"fastly testing\",\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(testSuggestions))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: `Domain             Subdomain       Zone  Path\nfastlytest.ing     fastlytest.     ing   \nfastlytesti.ng     fastlytesti.    ng    \nfastlytesting.com  fastlytesting.  com   \nfastlytesting.net  fastlytesting.  net   \nfastlytest.in      fastlytest.     in    /g\n`,\n\t\t},\n\t\t{\n\t\t\tArgs: \"--keywords=food,kitchen --defaults=club foo\",\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody: io.NopCloser(bytes.NewReader(testutil.GenJSON(suggest.Suggestions{\n\t\t\t\t\t\t\tResults: []suggest.Suggestion{\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tDomain:    \"foo.eat\",\n\t\t\t\t\t\t\t\t\tSubdomain: \"foo.\",\n\t\t\t\t\t\t\t\t\tZone:      \"eat\",\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tDomain:    \"foo.cafe\",\n\t\t\t\t\t\t\t\t\tSubdomain: \"foo.\",\n\t\t\t\t\t\t\t\t\tZone:      \"cafe\",\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tDomain:    \"foo.menu\",\n\t\t\t\t\t\t\t\t\tSubdomain: \"foo.\",\n\t\t\t\t\t\t\t\t\tZone:      \"menu\",\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tDomain:    \"foo.kitchen\",\n\t\t\t\t\t\t\t\t\tSubdomain: \"foo.\",\n\t\t\t\t\t\t\t\t\tZone:      \"kitchen\",\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tDomain:    \"foo.club\",\n\t\t\t\t\t\t\t\t\tSubdomain: \"foo.\",\n\t\t\t\t\t\t\t\t\tZone:      \"club\",\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t}))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: `Domain       Subdomain  Zone     Path\nfoo.eat      foo.       eat      \nfoo.cafe     foo.       cafe     \nfoo.menu     foo.       menu     \nfoo.kitchen  foo.       kitchen  \nfoo.club     foo.       club     \n`,\n\t\t},\n\t\t{\n\t\t\tArgs: \"-j fastly testing\",\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(testSuggestions))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: `{\n  \"results\": [\n    {\n      \"domain\": \"fastlytest.ing\",\n      \"subdomain\": \"fastlytest.\",\n      \"zone\": \"ing\"\n    },\n    {\n      \"domain\": \"fastlytesti.ng\",\n      \"subdomain\": \"fastlytesti.\",\n      \"zone\": \"ng\"\n    },\n    {\n      \"domain\": \"fastlytesting.com\",\n      \"subdomain\": \"fastlytesting.\",\n      \"zone\": \"com\"\n    },\n    {\n      \"domain\": \"fastlytesting.net\",\n      \"subdomain\": \"fastlytesting.\",\n      \"zone\": \"net\"\n    },\n    {\n      \"domain\": \"fastlytest.in\",\n      \"subdomain\": \"fastlytest.\",\n      \"zone\": \"in\",\n      \"path\": \"/g\"\n    }\n  ]\n}\n`,\n\t\t},\n\t\t{\n\t\t\tArgs: \"-v fastly testing\",\n\t\t\tClient: &http.Client{\n\t\t\t\tTransport: &testutil.MockRoundTripper{\n\t\t\t\t\tResponse: &http.Response{\n\t\t\t\t\t\tStatusCode: http.StatusOK,\n\t\t\t\t\t\tStatus:     http.StatusText(http.StatusOK),\n\t\t\t\t\t\tBody:       io.NopCloser(bytes.NewReader(testutil.GenJSON(testSuggestions))),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tWantOutput: `Fastly API endpoint: https://api.fastly.com\nFastly API token provided via config file (auth: user)\n\nDomain: fastlytest.ing\nSubdomain: fastlytest.\nZone: ing\n\nDomain: fastlytesti.ng\nSubdomain: fastlytesti.\nZone: ng\n\nDomain: fastlytesting.com\nSubdomain: fastlytesting.\nZone: com\n\nDomain: fastlytesting.net\nSubdomain: fastlytesting.\nZone: net\n\nDomain: fastlytest.in\nSubdomain: fastlytest.\nZone: in\nPath: /g\n`,\n\t\t},\n\t}\n\ttestutil.RunCLIScenarios(t, []string{tools.CommandName, domain.CommandName, \"suggest\"}, scenarios)\n}\n"
  },
  {
    "path": "pkg/commands/tools/root.go",
    "content": "package tools\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all tool subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"tools\"\n\n// NewRootCommand returns a new tools command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Tools for working with the Fastly platform\")\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/update/check.go",
    "content": "package update\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"strings\"\n\n\t\"github.com/blang/semver\"\n\n\t\"github.com/fastly/cli/pkg/github\"\n)\n\n// Check if the CLI can be updated.\nfunc Check(currentVersion string, av github.AssetVersioner) (current, latest semver.Version, shouldUpdate bool) {\n\t// nosemgrep (invalid-usage-of-modified-variable)\n\tcurrent, err := semver.Parse(strings.TrimPrefix(currentVersion, \"v\"))\n\tif err != nil {\n\t\treturn current, latest, false\n\t}\n\n\tv, err := av.LatestVersion()\n\tif err != nil {\n\t\treturn current, latest, false\n\t}\n\n\t// nosemgrep (invalid-usage-of-modified-variable)\n\tlatest, err = semver.Parse(v)\n\tif err != nil {\n\t\treturn current, latest, false\n\t}\n\n\treturn current, latest, latest.GT(current)\n}\n\ntype checkResult struct {\n\tcurrent      semver.Version\n\tlatest       semver.Version\n\tshouldUpdate bool\n}\n\n// CheckAsync is a helper function for running Check asynchronously.\n//\n// Launches a goroutine to perform a check for the latest CLI version and\n// returns a function that will print an informative message to the writer if\n// there is a newer version available.\n//\n// Callers should invoke CheckAsync via\n//\n//\tf := CheckAsync(...)\n//\tdefer f(w)\nfunc CheckAsync(\n\tcurrentVersion string,\n\tav github.AssetVersioner,\n) (printResults func(io.Writer)) {\n\tresults := make(chan checkResult, 1)\n\tgo func() {\n\t\tcurrent, latest, shouldUpdate := Check(currentVersion, av)\n\t\tresults <- checkResult{current, latest, shouldUpdate}\n\t}()\n\n\treturn func(w io.Writer) {\n\t\tresult := <-results\n\t\tif result.shouldUpdate {\n\t\t\tfmt.Fprintf(w, \"\\n\")\n\t\t\tfmt.Fprintf(w, \"A new version of the Fastly CLI is available.\\n\")\n\t\t\tfmt.Fprintf(w, \"Current version: %s\\n\", result.current)\n\t\t\tfmt.Fprintf(w, \"Latest version: %s\\n\", result.latest)\n\t\t\tif result.latest.Major != result.current.Major {\n\t\t\t\tfmt.Fprintf(w, \"\\nNote: Please review the release notes for the major version(s) listed below before upgrading.\\n\")\n\t\t\t\tfor ver := result.current.Major + 1; ver <= result.latest.Major; ver++ {\n\t\t\t\t\tfmt.Fprintf(w, \"Version %d.0.0: https://github.com/fastly/cli/releases/tag/v%d.0.0\\n\", ver, ver)\n\t\t\t\t}\n\t\t\t}\n\t\t\tfmt.Fprintf(w, \"Run `fastly update` to get the latest version.\\n\")\n\t\t\tfmt.Fprintf(w, \"\\n\")\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "pkg/commands/update/check_test.go",
    "content": "package update_test\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/blang/semver\"\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/fastly/cli/pkg/commands/update\"\n\t\"github.com/fastly/cli/pkg/config\"\n\t\"github.com/fastly/cli/pkg/github\"\n\t\"github.com/fastly/cli/pkg/mock\"\n)\n\nfunc TestCheck(t *testing.T) {\n\tfor _, testcase := range []struct {\n\t\tname        string\n\t\tcurrent     string\n\t\tav          github.AssetVersioner\n\t\twantCurrent semver.Version\n\t\twantLatest  semver.Version\n\t\twantUpdate  bool\n\t}{\n\t\t{\n\t\t\tname:    \"empty current version\",\n\t\t\tcurrent: \"\",\n\t\t\tav:      mock.AssetVersioner{},\n\t\t},\n\t\t{\n\t\t\tname:    \"invalid current version\",\n\t\t\tcurrent: \"unknown\",\n\t\t\tav:      mock.AssetVersioner{},\n\t\t},\n\t\t{\n\t\t\tname:        \"same version\",\n\t\t\tcurrent:     \"v1.2.3\",\n\t\t\tav:          mock.AssetVersioner{AssetVersion: \"1.2.3\"},\n\t\t\twantCurrent: semver.MustParse(\"1.2.3\"),\n\t\t\twantLatest:  semver.MustParse(\"1.2.3\"),\n\t\t\twantUpdate:  false,\n\t\t},\n\t\t{\n\t\t\tname:        \"new version\",\n\t\t\tcurrent:     \"v1.2.3\",\n\t\t\tav:          mock.AssetVersioner{AssetVersion: \"1.2.4\"},\n\t\t\twantCurrent: semver.MustParse(\"1.2.3\"),\n\t\t\twantLatest:  semver.MustParse(\"1.2.4\"),\n\t\t\twantUpdate:  true,\n\t\t},\n\t} {\n\t\tt.Run(testcase.name, func(t *testing.T) {\n\t\t\tcurrent, latest, shouldUpdate := update.Check(testcase.current, testcase.av)\n\t\t\tif want, have := testcase.wantCurrent, current; !want.Equals(have) {\n\t\t\t\tt.Fatalf(\"current version: want %s, have %s\", want, have)\n\t\t\t}\n\t\t\tif want, have := testcase.wantLatest, latest; !want.Equals(have) {\n\t\t\t\tt.Fatalf(\"latest version: want %s, have %s\", want, have)\n\t\t\t}\n\t\t\tif want, have := testcase.wantUpdate, shouldUpdate; want != have {\n\t\t\t\tt.Fatalf(\"should update: want %v, have %v\", want, have)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestCheckAsync(t *testing.T) {\n\tfor _, testcase := range []struct {\n\t\tname           string\n\t\tfile           config.File\n\t\tcurrentVersion string\n\t\tav             github.AssetVersioner\n\t\twantOutput     string\n\t}{\n\t\t{\n\t\t\tname:           \"no last_check same version\",\n\t\t\tcurrentVersion: \"0.0.1\",\n\t\t\tav:             mock.AssetVersioner{AssetVersion: \"0.0.1\"},\n\t\t},\n\t\t{\n\t\t\tname:           \"no last_check new version\",\n\t\t\tcurrentVersion: \"0.0.1\",\n\t\t\tav:             mock.AssetVersioner{AssetVersion: \"0.0.2\"},\n\t\t\twantOutput:     \"\\nA new version of the Fastly CLI is available.\\nCurrent version: 0.0.1\\nLatest version: 0.0.2\\nRun `fastly update` to get the latest version.\\n\\n\",\n\t\t},\n\t\t{\n\t\t\tname:           \"recent last_check new version\",\n\t\t\tcurrentVersion: \"0.0.1\",\n\t\t\tav:             mock.AssetVersioner{AssetVersion: \"0.0.2\"},\n\t\t\twantOutput:     \"\\nA new version of the Fastly CLI is available.\\nCurrent version: 0.0.1\\nLatest version: 0.0.2\\nRun `fastly update` to get the latest version.\\n\\n\",\n\t\t},\n\t} {\n\t\tt.Run(testcase.name, func(t *testing.T) {\n\t\t\tconfigFilePath := filepath.Join(os.TempDir(), fmt.Sprintf(\"fastly_TestCheckAsync_%d\", time.Now().UnixNano()))\n\t\t\tdefer os.RemoveAll(configFilePath)\n\n\t\t\tvar buf bytes.Buffer\n\t\t\tf := update.CheckAsync(\n\t\t\t\ttestcase.currentVersion,\n\t\t\t\ttestcase.av,\n\t\t\t)\n\t\t\tf(&buf)\n\n\t\t\tif want, have := testcase.wantOutput, buf.String(); want != have {\n\t\t\t\tt.Error(cmp.Diff(want, have))\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/commands/update/doc.go",
    "content": "// Package update contains functions for checking the current CLI version\n// against the latest version release.\npackage update\n"
  },
  {
    "path": "pkg/commands/update/root.go",
    "content": "package update\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"path/filepath\"\n\n\t\"github.com/blang/semver\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/filesystem\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/revision\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"update\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Update the CLI to the latest version\")\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, out io.Writer) error {\n\tspinner, err := text.NewSpinner(out)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tvar (\n\t\tcurrent, latest semver.Version\n\t\tshouldUpdate    bool\n\t)\n\n\terr = spinner.Process(\"Updating versioning information\", func(_ *text.SpinnerWrapper) error {\n\t\tcurrent, latest, shouldUpdate = Check(revision.AppVersion, c.Globals.Versioners.CLI)\n\t\treturn nil\n\t})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\ttext.Break(out)\n\ttext.Output(out, \"Current version: %s\", current)\n\ttext.Output(out, \"Latest version: %s\", latest)\n\ttext.Break(out)\n\n\tif !shouldUpdate {\n\t\ttext.Output(out, \"No update required.\")\n\t\treturn nil\n\t}\n\n\tvar downloadedBin string\n\terr = spinner.Process(\"Fetching latest release\", func(_ *text.SpinnerWrapper) error {\n\t\tdownloadedBin, err = c.Globals.Versioners.CLI.DownloadLatest()\n\t\tif err != nil {\n\t\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\t\"Current CLI version\": current,\n\t\t\t\t\"Latest CLI version\":  latest,\n\t\t\t})\n\t\t\treturn fmt.Errorf(\"error downloading latest release: %w\", err)\n\t\t}\n\t\treturn nil\n\t})\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer os.RemoveAll(downloadedBin)\n\n\tvar currentBin string\n\terr = spinner.Process(\"Replacing binary\", func(_ *text.SpinnerWrapper) error {\n\t\texecPath, err := os.Executable()\n\t\tif err != nil {\n\t\t\tc.Globals.ErrLog.Add(err)\n\t\t\treturn fmt.Errorf(\"error determining executable path: %w\", err)\n\t\t}\n\n\t\tcurrentBin, err = filepath.Abs(execPath)\n\t\tif err != nil {\n\t\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\t\"Executable path\": execPath,\n\t\t\t})\n\t\t\treturn fmt.Errorf(\"error determining absolute target path: %w\", err)\n\t\t}\n\n\t\t// Windows does not permit replacing a running executable, however it will\n\t\t// permit it if you first move the original executable. So we first move the\n\t\t// running executable to a new location, then we move the executable that we\n\t\t// downloaded to the same location as the original.\n\t\t// I've also tested this approach on nix systems and it works fine.\n\t\t//\n\t\t// Reference:\n\t\t// https://github.com/golang/go/issues/21997#issuecomment-331744930\n\n\t\tbackup := currentBin + \".bak\"\n\t\tif err := os.Rename(currentBin, backup); err != nil {\n\t\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\t\"Executable (source)\":      downloadedBin,\n\t\t\t\t\"Executable (destination)\": currentBin,\n\t\t\t})\n\t\t\treturn fmt.Errorf(\"error moving the current executable: %w\", err)\n\t\t}\n\n\t\tif err = os.Remove(backup); err != nil {\n\t\t\tc.Globals.ErrLog.Add(err)\n\t\t}\n\n\t\t// Move the downloaded binary to the same location as the current executable.\n\t\tif err := os.Rename(downloadedBin, currentBin); err != nil {\n\t\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\t\"Executable (source)\":      downloadedBin,\n\t\t\t\t\"Executable (destination)\": currentBin,\n\t\t\t})\n\t\t\trenameErr := err\n\n\t\t\t// Failing that we'll try to io.Copy downloaded binary to the current binary.\n\t\t\tif err := filesystem.CopyFile(downloadedBin, currentBin); err != nil {\n\t\t\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\t\t\"Executable (source)\":      downloadedBin,\n\t\t\t\t\t\"Executable (destination)\": currentBin,\n\t\t\t\t})\n\t\t\t\treturn fmt.Errorf(\"error 'copying' latest binary in place: %w (following an error 'moving': %w)\", err, renameErr)\n\t\t\t}\n\n\t\t\t// G302 (CWE-276): Expect file permissions to be 0600 or less\n\t\t\t// gosec flagged this:\n\t\t\t// Disabling as the file was not executable without it and we need all users\n\t\t\t// to be able to execute the binary.\n\t\t\t// #nosec\n\t\t\terr := os.Chmod(currentBin, 0o755)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"failed to modify permissions after 'copying' latest binary: %w\", err)\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"\\nUpdated %s to %s.\", currentBin, latest)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/user/create.go",
    "content": "package user\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// NewCreateCommand returns a usable command registered under the parent.\nfunc NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {\n\tvar c CreateCommand\n\tc.CmdClause = parent.Command(\"create\", \"Create a user of the Fastly API and web interface\").Alias(\"add\")\n\tc.Globals = g\n\n\t// Required.\n\tc.CmdClause.Flag(\"login\", \"The login associated with the user (typically, an email address)\").Action(c.login.Set).StringVar(&c.login.Value)\n\tc.CmdClause.Flag(\"name\", \"The real life name of the user\").Action(c.name.Set).StringVar(&c.name.Value)\n\n\t// Optional.\n\tc.CmdClause.Flag(\"role\", \"The permissions role assigned to the user. Can be user, billing, engineer, or superuser\").Action(c.role.Set).EnumVar(&c.role.Value, \"user\", \"billing\", \"engineer\", \"superuser\")\n\n\treturn &c\n}\n\n// CreateCommand calls the Fastly API to create an appropriate resource.\ntype CreateCommand struct {\n\targparser.Base\n\n\tlogin argparser.OptionalString\n\tname  argparser.OptionalString\n\trole  argparser.OptionalString\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *CreateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tinput := c.constructInput()\n\n\tr, err := c.Globals.APIClient.CreateUser(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"User Login\": c.login,\n\t\t\t\"User Name\":  c.name,\n\t\t})\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Created user '%s' (role: %s)\", fastly.ToValue(r.Name), fastly.ToValue(r.Role))\n\treturn nil\n}\n\n// constructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *CreateCommand) constructInput() *fastly.CreateUserInput {\n\tvar input fastly.CreateUserInput\n\tif c.login.WasSet {\n\t\tinput.Login = &c.login.Value\n\t}\n\tif c.role.WasSet {\n\t\tinput.Role = &c.role.Value\n\t}\n\tif c.name.WasSet {\n\t\tinput.Name = &c.name.Value\n\t}\n\n\treturn &input\n}\n"
  },
  {
    "path": "pkg/commands/user/delete.go",
    "content": "package user\n\nimport (\n\t\"context\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// NewDeleteCommand returns a usable command registered under the parent.\nfunc NewDeleteCommand(parent argparser.Registerer, globals *global.Data) *DeleteCommand {\n\tvar c DeleteCommand\n\tc.CmdClause = parent.Command(\"delete\", \"Delete a user of the Fastly API and web interface\").Alias(\"remove\")\n\tc.Globals = globals\n\tc.CmdClause.Flag(\"id\", \"Alphanumeric string identifying the user\").Required().StringVar(&c.id)\n\treturn &c\n}\n\n// DeleteCommand calls the Fastly API to delete an appropriate resource.\ntype DeleteCommand struct {\n\targparser.Base\n\n\tid string\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DeleteCommand) Exec(_ io.Reader, out io.Writer) error {\n\tinput := c.constructInput()\n\n\terr := c.Globals.APIClient.DeleteUser(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"User ID\": c.id,\n\t\t})\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Deleted user (id: %s)\", c.id)\n\treturn nil\n}\n\n// constructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *DeleteCommand) constructInput() *fastly.DeleteUserInput {\n\tvar input fastly.DeleteUserInput\n\tinput.UserID = c.id\n\treturn &input\n}\n"
  },
  {
    "path": "pkg/commands/user/describe.go",
    "content": "package user\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// NewDescribeCommand returns a usable command registered under the parent.\nfunc NewDescribeCommand(parent argparser.Registerer, g *global.Data) *DescribeCommand {\n\tvar c DescribeCommand\n\tc.CmdClause = parent.Command(\"describe\", \"Get a specific user of the Fastly API and web interface\").Alias(\"get\")\n\tc.Globals = g\n\tc.CmdClause.Flag(\"current\", \"Get the logged in user\").BoolVar(&c.current)\n\tc.CmdClause.Flag(\"id\", \"Alphanumeric string identifying the user\").StringVar(&c.id)\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\treturn &c\n}\n\n// DescribeCommand calls the Fastly API to describe an appropriate resource.\ntype DescribeCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tcurrent bool\n\tid      string\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *DescribeCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\n\tif c.current {\n\t\to, err := c.Globals.APIClient.GetCurrentUser(context.TODO())\n\t\tif err != nil {\n\t\t\tc.Globals.ErrLog.Add(err)\n\t\t\treturn err\n\t\t}\n\n\t\tif ok, err := c.WriteJSON(out, o); ok {\n\t\t\treturn err\n\t\t}\n\n\t\tc.print(out, o)\n\t\treturn nil\n\t}\n\n\tinput, err := c.constructInput()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\to, err := c.Globals.APIClient.GetUser(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\tc.print(out, o)\n\treturn nil\n}\n\n// constructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *DescribeCommand) constructInput() (*fastly.GetUserInput, error) {\n\tvar input fastly.GetUserInput\n\n\tif c.id == \"\" {\n\t\treturn nil, fsterr.RemediationError{\n\t\t\tInner:       fmt.Errorf(\"error parsing arguments: must provide --id flag\"),\n\t\t\tRemediation: \"Alternatively pass --current to validate the logged in user.\",\n\t\t}\n\t}\n\tinput.UserID = c.id\n\n\treturn &input, nil\n}\n\n// print displays the information returned from the API.\nfunc (c *DescribeCommand) print(out io.Writer, r *fastly.User) {\n\tfmt.Fprintf(out, \"\\nID: %s\\n\", fastly.ToValue(r.UserID))\n\tfmt.Fprintf(out, \"Login: %s\\n\", fastly.ToValue(r.Login))\n\tfmt.Fprintf(out, \"Name: %s\\n\", fastly.ToValue(r.Name))\n\tfmt.Fprintf(out, \"Role: %s\\n\", fastly.ToValue(r.Role))\n\tfmt.Fprintf(out, \"Customer ID: %s\\n\", fastly.ToValue(r.CustomerID))\n\tfmt.Fprintf(out, \"Email Hash: %s\\n\", fastly.ToValue(r.EmailHash))\n\tfmt.Fprintf(out, \"Limit Services: %t\\n\", fastly.ToValue(r.LimitServices))\n\tfmt.Fprintf(out, \"Locked: %t\\n\", fastly.ToValue(r.Locked))\n\tfmt.Fprintf(out, \"Require New Password: %t\\n\", fastly.ToValue(r.RequireNewPassword))\n\tfmt.Fprintf(out, \"Two Factor Auth Enabled: %t\\n\", fastly.ToValue(r.TwoFactorAuthEnabled))\n\tfmt.Fprintf(out, \"Two Factor Setup Required: %t\\n\\n\", fastly.ToValue(r.TwoFactorSetupRequired))\n\n\tif r.CreatedAt != nil {\n\t\tfmt.Fprintf(out, \"Created at: %s\\n\", r.CreatedAt)\n\t}\n\tif r.UpdatedAt != nil {\n\t\tfmt.Fprintf(out, \"Updated at: %s\\n\", r.UpdatedAt)\n\t}\n\tif r.DeletedAt != nil {\n\t\tfmt.Fprintf(out, \"Deleted at: %s\\n\", r.DeletedAt)\n\t}\n}\n"
  },
  {
    "path": "pkg/commands/user/doc.go",
    "content": "// Package user contains commands to inspect and manipulate Fastly user\n// accounts.\npackage user\n"
  },
  {
    "path": "pkg/commands/user/list.go",
    "content": "package user\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// NewListCommand returns a usable command registered under the parent.\nfunc NewListCommand(parent argparser.Registerer, g *global.Data) *ListCommand {\n\tvar c ListCommand\n\tc.CmdClause = parent.Command(\"list\", \"List all users from a specified customer id\")\n\tc.Globals = g\n\tc.RegisterFlag(argparser.StringFlagOpts{\n\t\tName:        argparser.FlagCustomerIDName,\n\t\tDescription: argparser.FlagCustomerIDDesc,\n\t\tDst:         &c.customerID.Value,\n\t\tAction:      c.customerID.Set,\n\t})\n\tc.RegisterFlagBool(c.JSONFlag()) // --json\n\treturn &c\n}\n\n// ListCommand calls the Fastly API to list appropriate resources.\ntype ListCommand struct {\n\targparser.Base\n\targparser.JSONOutput\n\n\tcustomerID argparser.OptionalCustomerID\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *ListCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.Globals.Verbose() && c.JSONOutput.Enabled {\n\t\treturn fsterr.ErrInvalidVerboseJSONCombo\n\t}\n\tif err := c.customerID.Parse(); err != nil {\n\t\treturn err\n\t}\n\n\tinput := c.constructInput()\n\n\to, err := c.Globals.APIClient.ListCustomerUsers(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"Customer ID\": c.customerID.Value,\n\t\t})\n\t\treturn err\n\t}\n\n\tif ok, err := c.WriteJSON(out, o); ok {\n\t\treturn err\n\t}\n\n\tif c.Globals.Verbose() {\n\t\tc.printVerbose(out, o)\n\t} else {\n\t\terr = c.printSummary(out, o)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\n// constructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *ListCommand) constructInput() *fastly.ListCustomerUsersInput {\n\tvar input fastly.ListCustomerUsersInput\n\n\tinput.CustomerID = c.customerID.Value\n\n\treturn &input\n}\n\n// printVerbose displays the information returned from the API in a verbose\n// format.\nfunc (c *ListCommand) printVerbose(out io.Writer, us []*fastly.User) {\n\tfor _, u := range us {\n\t\tfmt.Fprintf(out, \"\\nID: %s\\n\", fastly.ToValue(u.UserID))\n\t\tfmt.Fprintf(out, \"Login: %s\\n\", fastly.ToValue(u.Login))\n\t\tfmt.Fprintf(out, \"Name: %s\\n\", fastly.ToValue(u.Name))\n\t\tfmt.Fprintf(out, \"Role: %s\\n\", fastly.ToValue(u.Role))\n\t\tfmt.Fprintf(out, \"Customer ID: %s\\n\", fastly.ToValue(u.CustomerID))\n\t\tfmt.Fprintf(out, \"Email Hash: %s\\n\", fastly.ToValue(u.EmailHash))\n\t\tfmt.Fprintf(out, \"Limit Services: %t\\n\", fastly.ToValue(u.LimitServices))\n\t\tfmt.Fprintf(out, \"Locked: %t\\n\", fastly.ToValue(u.Locked))\n\t\tfmt.Fprintf(out, \"Require New Password: %t\\n\", fastly.ToValue(u.RequireNewPassword))\n\t\tfmt.Fprintf(out, \"Two Factor Auth Enabled: %t\\n\", fastly.ToValue(u.TwoFactorAuthEnabled))\n\t\tfmt.Fprintf(out, \"Two Factor Setup Required: %t\\n\\n\", fastly.ToValue(u.TwoFactorSetupRequired))\n\n\t\tif u.CreatedAt != nil {\n\t\t\tfmt.Fprintf(out, \"Created at: %s\\n\", u.CreatedAt)\n\t\t}\n\t\tif u.UpdatedAt != nil {\n\t\t\tfmt.Fprintf(out, \"Updated at: %s\\n\", u.UpdatedAt)\n\t\t}\n\t\tif u.DeletedAt != nil {\n\t\t\tfmt.Fprintf(out, \"Deleted at: %s\\n\", u.DeletedAt)\n\t\t}\n\t}\n}\n\n// printSummary displays the information returned from the API in a summarised\n// format.\nfunc (c *ListCommand) printSummary(out io.Writer, us []*fastly.User) error {\n\tt := text.NewTable(out)\n\tt.AddHeader(\"LOGIN\", \"NAME\", \"ROLE\", \"LOCKED\", \"ID\")\n\tfor _, u := range us {\n\t\tt.AddLine(\n\t\t\tfastly.ToValue(u.Login),\n\t\t\tfastly.ToValue(u.Name),\n\t\t\tfastly.ToValue(u.Role),\n\t\t\tfastly.ToValue(u.Locked),\n\t\t\tfastly.ToValue(u.UserID),\n\t\t)\n\t}\n\tt.Print()\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/commands/user/root.go",
    "content": "package user\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n\t// no flags\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"user\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Manipulate users of the Fastly API and web interface\")\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {\n\tpanic(\"unreachable\")\n}\n"
  },
  {
    "path": "pkg/commands/user/update.go",
    "content": "package user\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// NewUpdateCommand returns a usable command registered under the parent.\nfunc NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {\n\tvar c UpdateCommand\n\tc.CmdClause = parent.Command(\"update\", \"Update a user of the Fastly API and web interface\")\n\tc.Globals = g\n\tc.CmdClause.Flag(\"id\", \"Alphanumeric string identifying the user\").StringVar(&c.id)\n\tc.CmdClause.Flag(\"login\", \"The login associated with the user (typically, an email address)\").StringVar(&c.login)\n\tc.CmdClause.Flag(\"name\", \"The real life name of the user\").StringVar(&c.name)\n\tc.CmdClause.Flag(\"password-reset\", \"Requests a password reset for the specified user\").BoolVar(&c.reset)\n\tc.CmdClause.Flag(\"role\", \"The permissions role assigned to the user. Can be user, billing, engineer, or superuser\").EnumVar(&c.role, \"user\", \"billing\", \"engineer\", \"superuser\")\n\n\treturn &c\n}\n\n// UpdateCommand calls the Fastly API to update an appropriate resource.\ntype UpdateCommand struct {\n\targparser.Base\n\n\tid    string\n\tlogin string\n\tname  string\n\treset bool\n\trole  string\n}\n\n// Exec invokes the application logic for the command.\nfunc (c *UpdateCommand) Exec(_ io.Reader, out io.Writer) error {\n\tif c.reset {\n\t\tinput, err := c.constructInputReset()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\terr = c.Globals.APIClient.ResetUserPassword(context.TODO(), input)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\ttext.Success(out, \"Reset user password (login: %s)\", c.login)\n\t\treturn nil\n\t}\n\n\tinput, err := c.constructInput()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tr, err := c.Globals.APIClient.UpdateUser(context.TODO(), input)\n\tif err != nil {\n\t\tc.Globals.ErrLog.AddWithContext(err, map[string]any{\n\t\t\t\"User ID\": c.id,\n\t\t})\n\t\treturn err\n\t}\n\n\ttext.Success(out, \"Updated user '%s' (role: %s)\", fastly.ToValue(r.Name), fastly.ToValue(r.Role))\n\treturn nil\n}\n\n// constructInput transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *UpdateCommand) constructInput() (*fastly.UpdateUserInput, error) {\n\tvar input fastly.UpdateUserInput\n\n\tif c.id == \"\" {\n\t\treturn nil, fmt.Errorf(\"error parsing arguments: must provide --id flag\")\n\t}\n\tinput.UserID = c.id\n\n\tif c.name == \"\" && c.role == \"\" {\n\t\treturn nil, fmt.Errorf(\"error parsing arguments: must provide either the --name or --role with the --id flag\")\n\t}\n\n\tif c.name != \"\" {\n\t\tinput.Name = &c.name\n\t}\n\tif c.role != \"\" {\n\t\tinput.Role = &c.role\n\t}\n\n\treturn &input, nil\n}\n\n// constructInputReset transforms values parsed from CLI flags into an object to be used by the API client library.\nfunc (c *UpdateCommand) constructInputReset() (*fastly.ResetUserPasswordInput, error) {\n\tvar input fastly.ResetUserPasswordInput\n\n\tif c.login == \"\" {\n\t\treturn nil, fmt.Errorf(\"error parsing arguments: must provide --login when requesting a password reset\")\n\t}\n\tinput.Login = c.login\n\n\treturn &input, nil\n}\n"
  },
  {
    "path": "pkg/commands/user/user_test.go",
    "content": "package user_test\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\troot \"github.com/fastly/cli/pkg/commands/user\"\n\t\"github.com/fastly/cli/pkg/mock\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n)\n\nfunc TestUserCreate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName: \"validate CreateUser API error\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tCreateUserFn: func(_ context.Context, _ *fastly.CreateUserInput) (*fastly.User, error) {\n\t\t\t\t\treturn nil, testutil.Err\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:      \"--login foo@example.com --name foobar\",\n\t\t\tWantError: testutil.Err.Error(),\n\t\t},\n\t\t{\n\t\t\tName: \"validate CreateUser API success\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tCreateUserFn: func(_ context.Context, i *fastly.CreateUserInput) (*fastly.User, error) {\n\t\t\t\t\treturn &fastly.User{\n\t\t\t\t\t\tName: i.Name,\n\t\t\t\t\t\tRole: fastly.ToPointer(\"user\"),\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:       \"--login foo@example.com --name foobar\",\n\t\t\tWantOutput: \"Created user 'foobar' (role: user)\",\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, \"create\"}, scenarios)\n}\n\nfunc TestUserDelete(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --id flag\",\n\t\t\tWantError: \"error parsing arguments: required flag --id not provided\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate DeleteUser API error\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tDeleteUserFn: func(_ context.Context, _ *fastly.DeleteUserInput) error {\n\t\t\t\t\treturn testutil.Err\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:      \"--id foo123\",\n\t\t\tWantError: testutil.Err.Error(),\n\t\t},\n\t\t{\n\t\t\tName: \"validate DeleteUser API success\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tDeleteUserFn: func(_ context.Context, _ *fastly.DeleteUserInput) error {\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:       \"--id foo123\",\n\t\t\tWantOutput: \"Deleted user (id: foo123)\",\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, \"delete\"}, scenarios)\n}\n\nfunc TestUserDescribe(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --id flag\",\n\t\t\tWantError: \"error parsing arguments: must provide --id flag\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate GetUser API error\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetUserFn: func(_ context.Context, _ *fastly.GetUserInput) (*fastly.User, error) {\n\t\t\t\t\treturn nil, testutil.Err\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:      \"--id 123\",\n\t\t\tWantError: testutil.Err.Error(),\n\t\t},\n\t\t{\n\t\t\tName: \"validate GetCurrentUser API error\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetCurrentUserFn: func(_ context.Context) (*fastly.User, error) {\n\t\t\t\t\treturn nil, testutil.Err\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:      \"--current\",\n\t\t\tWantError: testutil.Err.Error(),\n\t\t},\n\t\t{\n\t\t\tName: \"validate GetUser API success\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetUserFn: getUser,\n\t\t\t},\n\t\t\tArgs:       \"--id 123\",\n\t\t\tWantOutput: describeUserOutput(),\n\t\t},\n\t\t{\n\t\t\tName: \"validate GetCurrentUser API success\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tGetCurrentUserFn: getCurrentUser,\n\t\t\t},\n\t\t\tArgs:       \"--current\",\n\t\t\tWantOutput: describeCurrentUserOutput(),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, \"describe\"}, scenarios)\n}\n\nfunc TestUserList(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --customer-id flag\",\n\t\t\tWantError: \"error reading customer ID: no customer ID found\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate ListUsers API error\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tListCustomerUsersFn: func(_ context.Context, _ *fastly.ListCustomerUsersInput) ([]*fastly.User, error) {\n\t\t\t\t\treturn nil, testutil.Err\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:      \"--customer-id abc\",\n\t\t\tWantError: testutil.Err.Error(),\n\t\t},\n\t\t{\n\t\t\tName: \"validate ListUsers API success\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tListCustomerUsersFn: listUsers,\n\t\t\t},\n\t\t\tArgs:       \"--customer-id abc\",\n\t\t\tWantOutput: listOutput(),\n\t\t},\n\t\t{\n\t\t\tName: \"validate ListUsers API success with verbose mode\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tListCustomerUsersFn: listUsers,\n\t\t\t},\n\t\t\tArgs:       \"--customer-id abc --verbose\",\n\t\t\tWantOutput: listVerboseOutput(),\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, \"list\"}, scenarios)\n}\n\nfunc TestUserUpdate(t *testing.T) {\n\tscenarios := []testutil.CLIScenario{\n\t\t{\n\t\t\tName:      \"validate missing --id flag\",\n\t\t\tWantError: \"error parsing arguments: must provide --id flag\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --name and --role flags\",\n\t\t\tArgs:      \"--id 123\",\n\t\t\tWantError: \"error parsing arguments: must provide either the --name or --role with the --id flag\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate missing --login flag with --password-reset\",\n\t\t\tArgs:      \"--password-reset\",\n\t\t\tWantError: \"error parsing arguments: must provide --login when requesting a password reset\",\n\t\t},\n\t\t{\n\t\t\tName:      \"validate invalid --role value\",\n\t\t\tArgs:      \"--id 123 --role foobar\",\n\t\t\tWantError: \"error parsing arguments: enum value must be one of user,billing,engineer,superuser, got 'foobar'\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate UpdateUser API error\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tUpdateUserFn: func(_ context.Context, _ *fastly.UpdateUserInput) (*fastly.User, error) {\n\t\t\t\t\treturn nil, testutil.Err\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:      \"--id 123 --name foo\",\n\t\t\tWantError: testutil.Err.Error(),\n\t\t},\n\t\t{\n\t\t\tName: \"validate ResetUserPassword API error\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tResetUserPasswordFn: func(_ context.Context, _ *fastly.ResetUserPasswordInput) error {\n\t\t\t\t\treturn testutil.Err\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:      \"--id 123 --login foo@example.com --password-reset\",\n\t\t\tWantError: testutil.Err.Error(),\n\t\t},\n\t\t{\n\t\t\tName: \"validate UpdateUser API success\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tUpdateUserFn: func(_ context.Context, i *fastly.UpdateUserInput) (*fastly.User, error) {\n\t\t\t\t\treturn &fastly.User{\n\t\t\t\t\t\tUserID: fastly.ToPointer(i.UserID),\n\t\t\t\t\t\tName:   i.Name,\n\t\t\t\t\t\tRole:   i.Role,\n\t\t\t\t\t}, nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:       \"--id 123 --name foo --role engineer\",\n\t\t\tWantOutput: \"Updated user 'foo' (role: engineer)\",\n\t\t},\n\t\t{\n\t\t\tName: \"validate ResetUserPassword API success\",\n\t\t\tAPI: &mock.API{\n\t\t\t\tResetUserPasswordFn: func(_ context.Context, _ *fastly.ResetUserPasswordInput) error {\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t},\n\t\t\tArgs:       \"--id 123 --login foo@example.com --password-reset\",\n\t\t\tWantOutput: \"Reset user password (login: foo@example.com)\",\n\t\t},\n\t}\n\n\ttestutil.RunCLIScenarios(t, []string{root.CommandName, \"update\"}, scenarios)\n}\n\nfunc getUser(_ context.Context, i *fastly.GetUserInput) (*fastly.User, error) {\n\tt := testutil.Date\n\n\treturn &fastly.User{\n\t\tUserID:                 fastly.ToPointer(i.UserID),\n\t\tLogin:                  fastly.ToPointer(\"foo@example.com\"),\n\t\tName:                   fastly.ToPointer(\"foo\"),\n\t\tRole:                   fastly.ToPointer(\"user\"),\n\t\tCustomerID:             fastly.ToPointer(\"abc\"),\n\t\tEmailHash:              fastly.ToPointer(\"example-hash\"),\n\t\tLimitServices:          fastly.ToPointer(true),\n\t\tLocked:                 fastly.ToPointer(true),\n\t\tRequireNewPassword:     fastly.ToPointer(true),\n\t\tTwoFactorAuthEnabled:   fastly.ToPointer(true),\n\t\tTwoFactorSetupRequired: fastly.ToPointer(true),\n\t\tCreatedAt:              &t,\n\t\tDeletedAt:              &t,\n\t\tUpdatedAt:              &t,\n\t}, nil\n}\n\nfunc getCurrentUser(_ context.Context) (*fastly.User, error) {\n\tt := testutil.Date\n\n\treturn &fastly.User{\n\t\tUserID:                 fastly.ToPointer(\"current123\"),\n\t\tLogin:                  fastly.ToPointer(\"bar@example.com\"),\n\t\tName:                   fastly.ToPointer(\"bar\"),\n\t\tRole:                   fastly.ToPointer(\"superuser\"),\n\t\tCustomerID:             fastly.ToPointer(\"abc\"),\n\t\tEmailHash:              fastly.ToPointer(\"example-hash2\"),\n\t\tLimitServices:          fastly.ToPointer(false),\n\t\tLocked:                 fastly.ToPointer(false),\n\t\tRequireNewPassword:     fastly.ToPointer(false),\n\t\tTwoFactorAuthEnabled:   fastly.ToPointer(false),\n\t\tTwoFactorSetupRequired: fastly.ToPointer(false),\n\t\tCreatedAt:              &t,\n\t\tDeletedAt:              &t,\n\t\tUpdatedAt:              &t,\n\t}, nil\n}\n\nfunc listUsers(ctx context.Context, _ *fastly.ListCustomerUsersInput) ([]*fastly.User, error) {\n\tuser, _ := getUser(ctx, &fastly.GetUserInput{UserID: \"123\"})\n\tuserCurrent, _ := getCurrentUser(ctx)\n\tvs := []*fastly.User{\n\t\tuser,\n\t\tuserCurrent,\n\t}\n\treturn vs, nil\n}\n\nfunc describeUserOutput() string {\n\treturn `\nID: 123\nLogin: foo@example.com\nName: foo\nRole: user\nCustomer ID: abc\nEmail Hash: example-hash\nLimit Services: true\nLocked: true\nRequire New Password: true\nTwo Factor Auth Enabled: true\nTwo Factor Setup Required: true\n\nCreated at: 2021-06-15 23:00:00 +0000 UTC\nUpdated at: 2021-06-15 23:00:00 +0000 UTC\nDeleted at: 2021-06-15 23:00:00 +0000 UTC\n`\n}\n\nfunc describeCurrentUserOutput() string {\n\treturn `\nID: current123\nLogin: bar@example.com\nName: bar\nRole: superuser\nCustomer ID: abc\nEmail Hash: example-hash2\nLimit Services: false\nLocked: false\nRequire New Password: false\nTwo Factor Auth Enabled: false\nTwo Factor Setup Required: false\n\nCreated at: 2021-06-15 23:00:00 +0000 UTC\nUpdated at: 2021-06-15 23:00:00 +0000 UTC\nDeleted at: 2021-06-15 23:00:00 +0000 UTC\n`\n}\n\nfunc listOutput() string {\n\treturn `LOGIN            NAME  ROLE       LOCKED  ID\nfoo@example.com  foo   user       true    123\nbar@example.com  bar   superuser  false   current123\n`\n}\n\nfunc listVerboseOutput() string {\n\treturn fmt.Sprintf(`Fastly API endpoint: https://api.fastly.com\nFastly API token provided via config file (auth: user)\n\n%s%s`, describeUserOutput(), describeCurrentUserOutput())\n}\n"
  },
  {
    "path": "pkg/commands/version/doc.go",
    "content": "// Package version contains commands to inspect the Fastly CLI version.\npackage version\n"
  },
  {
    "path": "pkg/commands/version/root.go",
    "content": "package version\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/github\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/revision\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"version\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tc := RootCommand{\n\t\tBase: argparser.Base{\n\t\t\tGlobals: g,\n\t\t},\n\t}\n\tc.CmdClause = parent.Command(CommandName, \"Display version information for the Fastly CLI\")\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, out io.Writer) error {\n\tfmt.Fprintf(out, \"Fastly CLI version %s (%s)\\n\", revision.AppVersion, revision.GitCommit)\n\tfmt.Fprintf(out, \"Built with %s (%s)\\n\", revision.GoVersion, Now().Format(\"2006-01-02\"))\n\n\tviceroy := filepath.Join(github.InstallDir, c.Globals.Versioners.Viceroy.BinaryName())\n\t// gosec flagged this:\n\t// G204 (CWE-78): Subprocess launched with variable\n\t// Disabling as we lookup the binary in a trusted location. For this to be a\n\t// concern the user would need to have an already compromised system where an\n\t// attacker could swap the actual viceroy executable for something malicious.\n\t/* #nosec */\n\t// nosemgrep\n\tcommand := exec.Command(viceroy, \"--version\")\n\tif stdoutStderr, err := command.CombinedOutput(); err == nil {\n\t\tfmt.Fprintf(out, \"Viceroy version: %s\", stdoutStderr)\n\t}\n\n\treturn nil\n}\n\n// IsPreRelease determines if the given app version is a pre-release.\n//\n// NOTE: this is indicated by the presence of a hyphen, e.g. `v1.0.0-rc.1`.\nfunc IsPreRelease(version string) bool {\n\treturn strings.Contains(version, \"-\")\n}\n\n// Now is exposed so that we may mock it from our test file.\nvar Now = time.Now\n"
  },
  {
    "path": "pkg/commands/version/version_test.go",
    "content": "package version_test\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"runtime\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/fastly/cli/pkg/app\"\n\t\"github.com/fastly/cli/pkg/commands/version\"\n\t\"github.com/fastly/cli/pkg/github\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n)\n\nfunc TestVersion(t *testing.T) {\n\tif runtime.GOOS != \"darwin\" && runtime.GOOS != \"linux\" {\n\t\tt.Skip(\"skipping test due to unix specific mock shell script\")\n\t}\n\n\t// We're going to chdir to a temp environment,\n\t// so save the PWD to return to, afterwards.\n\tpwd, err := os.Getwd()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t// Create test environment\n\trootdir := testutil.NewEnv(testutil.EnvOpts{\n\t\tT: t,\n\t\tWrite: []testutil.FileIO{\n\t\t\t{Src: `#!/bin/bash\n\t\t\techo viceroy 0.0.0`, Dst: \"viceroy\"},\n\t\t},\n\t})\n\tdefer os.RemoveAll(rootdir)\n\n\t// Ensure the viceroy file we created can be executed.\n\t//\n\t// G302 (CWE-276): Expect file permissions to be 0600 or less\n\t// gosec flagged this:\n\t// Disabling as this is for test suite purposes only.\n\t// #nosec\n\terr = os.Chmod(filepath.Join(rootdir, \"viceroy\"), 0o777)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t// Override the InstallDir where the viceroy binary is looked up.\n\torgInstallDir := github.InstallDir\n\tgithub.InstallDir = rootdir\n\tdefer func() {\n\t\tgithub.InstallDir = orgInstallDir\n\t}()\n\n\t// Before running the test, chdir into the temp environment.\n\t// When we're done, chdir back to our original location.\n\t// This is so we can reliably assert file structure.\n\tif err := os.Chdir(rootdir); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer func() {\n\t\t_ = os.Chdir(pwd)\n\t}()\n\n\t// Mock the time output to be zero value\n\tversion.Now = func() (t time.Time) {\n\t\treturn t\n\t}\n\n\tvar stdout bytes.Buffer\n\targs := testutil.SplitArgs(\"version\")\n\topts := testutil.MockGlobalData(args, &stdout)\n\topts.Versioners = global.Versioners{\n\t\tViceroy: github.New(github.Opts{\n\t\t\tOrg:    \"fastly\",\n\t\t\tRepo:   \"viceroy\",\n\t\t\tBinary: \"viceroy\",\n\t\t}),\n\t}\n\tapp.Init = func(_ []string, _ io.Reader) (*global.Data, error) {\n\t\treturn opts, nil\n\t}\n\terr = app.Run(args, nil)\n\n\tt.Log(stdout.String())\n\n\tvar mockTime time.Time\n\ttestutil.AssertNoError(t, err)\n\ttestutil.AssertString(t, strings.Join([]string{\n\t\t\"Fastly CLI version v0.0.0-unknown (unknown)\",\n\t\tfmt.Sprintf(\"Built with go version %s %s/%s (%s)\", runtime.Version(), runtime.GOOS, runtime.GOARCH, mockTime.Format(\"2006-01-02\")),\n\t\t\"Viceroy version: viceroy 0.0.0\",\n\t\t\"\",\n\t}, \"\\n\"), stdout.String())\n}\n"
  },
  {
    "path": "pkg/commands/whoami/doc.go",
    "content": "// Package whoami contains commands to inspect the currently authenticated user.\npackage whoami\n"
  },
  {
    "path": "pkg/commands/whoami/root.go",
    "content": "package whoami\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"sort\"\n\t\"strconv\"\n\n\t\"github.com/fastly/cli/pkg/api/undocumented\"\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/useragent\"\n)\n\n// RootCommand is the parent command for all subcommands in this package.\n// It should be installed under the primary root command.\ntype RootCommand struct {\n\targparser.Base\n}\n\n// CommandName is the string to be used to invoke this command.\nconst CommandName = \"whoami\"\n\n// NewRootCommand returns a new command registered in the parent.\nfunc NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {\n\tvar c RootCommand\n\tc.Globals = g\n\tc.CmdClause = parent.Command(CommandName, \"Get information about the currently authenticated account\")\n\treturn &c\n}\n\n// Exec implements the command interface.\nfunc (c *RootCommand) Exec(_ io.Reader, out io.Writer) error {\n\tdebugMode, _ := strconv.ParseBool(c.Globals.Env.DebugMode)\n\ttoken, _ := c.Globals.Token()\n\tapiEndpoint, _ := c.Globals.APIEndpoint()\n\tdata, err := undocumented.Call(undocumented.CallOptions{\n\t\tAPIEndpoint: apiEndpoint,\n\t\tHTTPClient:  c.Globals.HTTPClient,\n\t\tHTTPHeaders: []undocumented.HTTPHeader{\n\t\t\t{\n\t\t\t\tKey:   \"Accept\",\n\t\t\t\tValue: \"application/json\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tKey:   \"User-Agent\",\n\t\t\t\tValue: useragent.Name,\n\t\t\t},\n\t\t},\n\t\tMethod: http.MethodGet,\n\t\tPath:   \"/verify\",\n\t\tToken:  token,\n\t\tDebug:  debugMode,\n\t})\n\tif err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn fmt.Errorf(\"error executing API request: %w\", err)\n\t}\n\n\tvar response VerifyResponse\n\tif err := json.Unmarshal(data, &response); err != nil {\n\t\tc.Globals.ErrLog.Add(err)\n\t\treturn fmt.Errorf(\"error decoding API response: %w\", err)\n\t}\n\n\tif !c.Globals.Verbose() {\n\t\tfmt.Fprintf(out, \"%s <%s>\\n\", response.User.Name, response.User.Login)\n\t\treturn nil\n\t}\n\n\tkeys := make([]string, 0, len(response.Services))\n\tfor k := range response.Services {\n\t\tkeys = append(keys, k)\n\t}\n\tsort.Strings(keys)\n\n\tfmt.Fprintf(out, \"Customer ID: %s\\n\", response.Customer.ID)\n\tfmt.Fprintf(out, \"Customer name: %s\\n\", response.Customer.Name)\n\tfmt.Fprintf(out, \"User ID: %s\\n\", response.User.ID)\n\tfmt.Fprintf(out, \"User name: %s\\n\", response.User.Name)\n\tfmt.Fprintf(out, \"User login: %s\\n\", response.User.Login)\n\tfmt.Fprintf(out, \"Token ID: %s\\n\", response.Token.ID)\n\tfmt.Fprintf(out, \"Token name: %s\\n\", response.Token.Name)\n\tfmt.Fprintf(out, \"Token created at: %s\\n\", response.Token.CreatedAt)\n\tif response.Token.ExpiresAt != \"\" {\n\t\tfmt.Fprintf(out, \"Token expires at: %s\\n\", response.Token.ExpiresAt)\n\t}\n\tfmt.Fprintf(out, \"Token scope: %s\\n\", response.Token.Scope)\n\tfmt.Fprintf(out, \"Service count: %d\\n\", len(response.Services))\n\tfor _, k := range keys {\n\t\tfmt.Fprintf(out, \"\\t%s (%s)\\n\", response.Services[k], k)\n\t}\n\n\treturn nil\n}\n\n// VerifyResponse models the Fastly API response for the whoami command.\ntype VerifyResponse struct {\n\tCustomer Customer          `json:\"customer\"`\n\tUser     User              `json:\"user\"`\n\tServices map[string]string `json:\"services\"`\n\tToken    Token             `json:\"token\"`\n}\n\n// Customer is part of the Fastly API response for the whoami command.\ntype Customer struct {\n\tID   string `json:\"id\"`\n\tName string `json:\"name\"`\n}\n\n// User is part of the Fastly API response for the whoami command.\ntype User struct {\n\tID    string `json:\"id\"`\n\tName  string `json:\"name\"`\n\tLogin string `json:\"login\"`\n}\n\n// Token is part of the Fastly API response for the whoami command.\ntype Token struct {\n\tID        string `json:\"id\"`\n\tName      string `json:\"name\"`\n\tCreatedAt string `json:\"created_at\"`\n\tExpiresAt string `json:\"expires_at\"`\n\tScope     string `json:\"scope\"`\n}\n"
  },
  {
    "path": "pkg/commands/whoami/whoami_test.go",
    "content": "package whoami_test\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/fastly/cli/pkg/api\"\n\t\"github.com/fastly/cli/pkg/app\"\n\t\"github.com/fastly/cli/pkg/config\"\n\t\"github.com/fastly/cli/pkg/env\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n)\n\nfunc TestWhoami(t *testing.T) {\n\targs := testutil.SplitArgs\n\tfor _, testcase := range []struct {\n\t\tname       string\n\t\targs       []string\n\t\tenv        config.Environment\n\t\tclient     api.HTTPClient\n\t\twantError  string\n\t\twantOutput string\n\t}{\n\t\t{\n\t\t\tname:       \"basic response\",\n\t\t\targs:       args(\"whoami\"),\n\t\t\tclient:     testutil.WhoamiVerifyClient(testutil.WhoamiBasicResponse),\n\t\t\twantOutput: basicOutput,\n\t\t},\n\t\t{\n\t\t\tname:       \"basic response verbose\",\n\t\t\targs:       args(\"whoami -v\"),\n\t\t\tclient:     testutil.WhoamiVerifyClient(testutil.WhoamiBasicResponse),\n\t\t\twantOutput: basicOutputVerbose,\n\t\t},\n\t\t{\n\t\t\tname:      \"401 from API\",\n\t\t\targs:      args(\"whoami\"),\n\t\t\tclient:    codeClient{code: http.StatusUnauthorized},\n\t\t\twantError: \"error executing API request: error response\",\n\t\t},\n\t\t{\n\t\t\tname:      \"500 from API\",\n\t\t\targs:      args(\"whoami\"),\n\t\t\tclient:    codeClient{code: http.StatusInternalServerError},\n\t\t\twantError: \"error executing API request: error response\",\n\t\t},\n\t\t{\n\t\t\tname:      \"local error\",\n\t\t\targs:      args(\"whoami\"),\n\t\t\tclient:    errorClient{err: errors.New(\"some network failure\")},\n\t\t\twantError: \"error executing API request: some network failure\",\n\t\t},\n\t\t{\n\t\t\tname:   \"alternative endpoint from flag\",\n\t\t\targs:   args(\"whoami --api=https://staging.fastly.com -v\"),\n\t\t\tclient: testutil.WhoamiVerifyClient(testutil.WhoamiBasicResponse),\n\t\t\twantOutput: strings.ReplaceAll(basicOutputVerbose,\n\t\t\t\t\"Fastly API endpoint: https://api.fastly.com\",\n\t\t\t\t\"Fastly API endpoint (via --api): https://staging.fastly.com\",\n\t\t\t),\n\t\t},\n\t\t{\n\t\t\tname:   \"alternative endpoint from environment\",\n\t\t\targs:   args(\"whoami -v\"),\n\t\t\tenv:    config.Environment{APIEndpoint: \"https://alternative.example.com\"},\n\t\t\tclient: testutil.WhoamiVerifyClient(testutil.WhoamiBasicResponse),\n\t\t\twantOutput: strings.ReplaceAll(basicOutputVerbose,\n\t\t\t\t\"Fastly API endpoint: https://api.fastly.com\",\n\t\t\t\tfmt.Sprintf(\"Fastly API endpoint (via %s): https://alternative.example.com\", env.APIEndpoint),\n\t\t\t),\n\t\t},\n\t} {\n\t\tt.Run(testcase.name, func(t *testing.T) {\n\t\t\tvar stdout bytes.Buffer\n\t\t\topts := testutil.MockGlobalData(testcase.args, &stdout)\n\t\t\topts.Env = testcase.env\n\t\t\topts.HTTPClient = testcase.client\n\t\t\tapp.Init = func(_ []string, _ io.Reader) (*global.Data, error) {\n\t\t\t\treturn opts, nil\n\t\t\t}\n\t\t\terr := app.Run(testcase.args, nil)\n\t\t\topts.Config = config.File{}\n\t\t\tt.Log(stdout.String())\n\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\ttestutil.AssertStringContains(t, stdout.String(), testcase.wantOutput)\n\t\t})\n\t}\n}\n\ntype codeClient struct {\n\tcode int\n}\n\nfunc (c codeClient) Do(*http.Request) (*http.Response, error) {\n\trec := httptest.NewRecorder()\n\trec.WriteHeader(c.code)\n\treturn rec.Result(), nil\n}\n\ntype errorClient struct {\n\terr error\n}\n\nfunc (c errorClient) Do(*http.Request) (*http.Response, error) {\n\treturn nil, c.err\n}\n\nvar basicOutput = \"Alice Programmer <alice@example.com>\\n\"\n\nvar basicOutputVerbose = strings.TrimSpace(`\nFastly API endpoint: https://api.fastly.com\nFastly API token provided via config file (auth: user)\n\nCustomer ID: abc\nCustomer name: Computer Company\nUser ID: 123\nUser name: Alice Programmer\nUser login: alice@example.com\nToken ID: abcdefg\nToken name: Token name\nToken created at: 2019-01-01T12:00:00Z\nToken scope: global\nService count: 2\n\tFirst service (1xxaa)\n\tSecond service (2baba)\n`) + \"\\n\"\n"
  },
  {
    "path": "pkg/config/auth.go",
    "content": "package config\n\n// Auth represents the new auth configuration section.\n// It stores named tokens and tracks which one is the default.\ntype Auth struct {\n\tDefault string     `toml:\"default\" json:\"default\"`\n\tTokens  AuthTokens `toml:\"tokens\" json:\"tokens\"`\n}\n\n// AuthTokens is a map of token name to token entry.\ntype AuthTokens map[string]*AuthToken\n\n// AuthToken represents a single stored credential.\ntype AuthToken struct {\n\tType      string `toml:\"type\" json:\"type\"`\n\tToken     string `toml:\"token\" json:\"token\"`\n\tLabel     string `toml:\"label,omitempty\" json:\"label,omitempty\"`\n\tAccountID string `toml:\"account_id,omitempty\" json:\"account_id,omitempty\"`\n\tEmail     string `toml:\"email,omitempty\" json:\"email,omitempty\"`\n\n\t// API token metadata (populated from /tokens/self when available).\n\n\tAPITokenName      string `toml:\"api_token_name,omitempty\" json:\"api_token_name,omitempty\"`\n\tAPITokenScope     string `toml:\"api_token_scope,omitempty\" json:\"api_token_scope,omitempty\"`\n\tAPITokenExpiresAt string `toml:\"api_token_expires_at,omitempty\" json:\"api_token_expires_at,omitempty\"`\n\tAPITokenID        string `toml:\"api_token_id,omitempty\" json:\"api_token_id,omitempty\"`\n\n\t// SSO-specific fields (only populated when Type == \"sso\").\n\n\tRefreshToken     string `toml:\"refresh_token,omitempty\" json:\"refresh_token,omitempty\"`\n\tAccessExpiresAt  string `toml:\"access_expires_at,omitempty\" json:\"access_expires_at,omitempty\"`\n\tRefreshExpiresAt string `toml:\"refresh_expires_at,omitempty\" json:\"refresh_expires_at,omitempty\"`\n\tAccessToken      string `toml:\"access_token,omitempty\" json:\"access_token,omitempty\"`\n\tNeedsReauth      bool   `toml:\"needs_reauth,omitempty\" json:\"needs_reauth,omitempty\"`\n}\n\nconst AuthTokenTypeStatic = \"static\"\n\nconst AuthTokenTypeSSO = \"sso\"\n"
  },
  {
    "path": "pkg/config/config.go",
    "content": "package config\n\nimport (\n\t_ \"embed\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"path/filepath\"\n\n\ttoml \"github.com/pelletier/go-toml\"\n\n\t\"github.com/fastly/cli/pkg/env\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/filesystem\"\n\t\"github.com/fastly/cli/pkg/revision\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\nconst (\n\t// DirectoryPermissions is the default directory permissions for the config file directory.\n\tDirectoryPermissions = 0o700\n\n\t// FilePermissions is the default file permissions for the config file.\n\tFilePermissions = 0o600\n)\n\nvar (\n\t// CurrentConfigVersion indicates the present config version.\n\tCurrentConfigVersion int\n\n\t// ErrLegacyConfig indicates that the local configuration file is using the\n\t// legacy format.\n\tErrLegacyConfig = errors.New(\"the configuration file is in the legacy format\")\n\n\t// ErrInvalidConfig indicates that the configuration file used was invalid.\n\tErrInvalidConfig = errors.New(\"the configuration file is invalid\")\n\n\t// RemediationManualFix indicates that the configuration file used was invalid\n\t// and that the user rejected the use of the static config embedded into the\n\t// compiled CLI binary and so the user must resolve their invalid config.\n\tRemediationManualFix = \"You'll need to manually fix any invalid configuration syntax.\"\n)\n\n// LegacyUser represents the old toml configuration format.\n//\n// NOTE: this exists to catch situations where an existing CLI user upgrades\n// their version of the CLI and ends up trying to use the latest iteration of\n// the toml configuration. We don't want them to have to re-enter their email\n// or token, so we'll decode the existing config file into the LegacyUser type\n// and then extract those details later when constructing the proper File type.\n//\n// I had tried to make this an unexported type but it seemed the toml decoder\n// would fail to unmarshal the configuration unless it was an exported type.\ntype LegacyUser struct {\n\tEmail string `toml:\"email\"`\n\tToken string `toml:\"token\"`\n}\n\n// Fastly represents fastly specific configuration.\ntype Fastly struct {\n\tAPIEndpoint     string `toml:\"api_endpoint\"`\n\tAccountEndpoint string `toml:\"account_endpoint\"`\n}\n\n// WasmMetadata represents what metadata will be collected.\ntype WasmMetadata struct {\n\t// BuildInfo represents information regarding the time taken for builds and\n\t// compilation processes, helping us identify bottlenecks and optimize\n\t// performance (enable/disable).\n\tBuildInfo string `toml:\"build_info\"`\n\t// MachineInfo represents general, non-identifying system specifications (CPU,\n\t// RAM, operating system) to better understand the hardware landscape our CLI\n\t// operates in (enable/disable).\n\tMachineInfo string `toml:\"machine_info\"`\n\t// PackageInfo represents packages and libraries utilized by your source code,\n\t// enabling us to prioritize support for the most commonly used components\n\t// (enable/disable).\n\tPackageInfo string `toml:\"package_info\"`\n\t// ScriptInfo represents the [scripts] section from the fastly.toml manifest.\n\tScriptInfo string `toml:\"script_info\"`\n}\n\n// CLI represents CLI specific configuration.\ntype CLI struct {\n\t// MetadataNoticeDisplayed indicates if the user has been notified of the\n\t// metadata behaviours being enabled by default and how they can opt-out.\n\tMetadataNoticeDisplayed bool `toml:\"metadata_notice_displayed\"`\n\t// Version indicates the CLI configuration version.\n\t// It is updated each time a change is made to the config structure.\n\tVersion string `toml:\"version\"`\n}\n\n// Versioner represents GitHub assets configuration.\n// e.g. viceroy, wasm-tools etc.\ntype Versioner struct {\n\t// LastChecked is when the asset version was last checked.\n\tLastChecked string `toml:\"last_checked\"`\n\t// LatestVersion is the latest asset version at the time it is set.\n\tLatestVersion string `toml:\"latest_version\"`\n\t// TTL is how long the CLI waits before considering the asset version stale.\n\tTTL string `toml:\"ttl\"`\n}\n\n// Language represents Compute language specific configuration.\ntype Language struct {\n\tCPP  CPP  `toml:\"cpp\"`\n\tGo   Go   `toml:\"go\"`\n\tRust Rust `toml:\"rust\"`\n}\n\n// Go represents Go Compute language specific configuration.\ntype Go struct {\n\t// TinyGoConstraint is the `tinygo` version that we support.\n\tTinyGoConstraint string `toml:\"tinygo_constraint\"`\n\n\t// TinyGoConstraintFallback is a fallback `tinygo` version for users who have\n\t// a pre-existing project with a 0.1.x Fastly Go SDK specified.\n\tTinyGoConstraintFallback string `toml:\"tinygo_constraint_fallback\"`\n\n\t// ToolchainConstraint is the `go` version that we support with WASI.\n\tToolchainConstraint string `toml:\"toolchain_constraint\"`\n\n\t// ToolchainConstraintTinyGo is the `go` version that we support with TinyGo.\n\t//\n\t// We aim for go versions that support go modules by default.\n\t// https://go.dev/blog/using-go-modules\n\tToolchainConstraintTinyGo string `toml:\"toolchain_constraint_tinygo\"`\n}\n\n// Rust represents Rust Compute language specific configuration.\ntype Rust struct {\n\t// ToolchainConstraint is the `rustup` toolchain constraint for the compiler\n\t// that we support (a range is expected, e.g. >= 1.49.0 < 2.0.0).\n\tToolchainConstraint string `toml:\"toolchain_constraint\"`\n\n\t// WasmWasiTarget is the Rust compilation target for Wasi capable Wasm.\n\tWasmWasiTarget string `toml:\"wasm_wasi_target\"`\n}\n\n// CPP represents C++ Compute language specific configuration.\ntype CPP struct {\n\t// ToolchainConstraint is the `clang++` toolchain constraint for the compiler\n\t// that we support (a range is expected, e.g. >= 14.0.0).\n\tToolchainConstraint string `toml:\"toolchain_constraint\"`\n\n\t// WasmWasiTarget is the C++ compilation target for Wasi capable Wasm.\n\tWasmWasiTarget string `toml:\"wasm_wasi_target\"`\n}\n\n// Profiles represents multiple profile accounts.\ntype Profiles map[string]*Profile\n\n// Profile represents a specific profile account.\ntype Profile struct {\n\t// AccessToken is used to acquire an API token.\n\tAccessToken string `toml:\"access_token\" json:\"access_token\"`\n\t// AccessTokenCreated indicates when the access token was created.\n\tAccessTokenCreated int64 `toml:\"access_token_created\" json:\"access_token_created\"`\n\t// AccessTokenTTL indicates when the access token needs to be replaced.\n\tAccessTokenTTL int `toml:\"access_token_ttl\" json:\"access_token_ttl\"`\n\t// CustomerID is the customer ID associated with the profile.\n\tCustomerID string `toml:\"customer_id\" json:\"customer_id\"`\n\t// CustomerName is the customer name associated with the profile.\n\tCustomerName string `toml:\"customer_name\" json:\"customer_name\"`\n\t// Default indicates if the profile is the default profile to use.\n\tDefault bool `toml:\"default\" json:\"default\"`\n\t// Email is the email address associated with the token.\n\tEmail string `toml:\"email\" json:\"email\"`\n\t// RefreshToken is used to acquire a new access token when it expires.\n\tRefreshToken string `toml:\"refresh_token\" json:\"refresh_token\"`\n\t// RefreshTokenCreated indicates when the refresh token was created.\n\tRefreshTokenCreated int64 `toml:\"refresh_token_created\" json:\"refresh_token_created\"`\n\t// RefreshTokenTTL indicates when the refresh token needs to be replaced.\n\tRefreshTokenTTL int `toml:\"refresh_token_ttl\" json:\"refresh_token_ttl\"`\n\t// Token is a temporary token used to interact with the Fastly API.\n\tToken string `toml:\"token\" json:\"token\"`\n}\n\n// StarterKitLanguages represents language specific starter kits.\ntype StarterKitLanguages struct {\n\tCPP        []StarterKit `toml:\"cpp\"`\n\tGo         []StarterKit `toml:\"go\"`\n\tJavaScript []StarterKit `toml:\"javascript\"`\n\tRust       []StarterKit `toml:\"rust\"`\n}\n\n// StarterKit represents starter kit specific configuration.\ntype StarterKit struct {\n\tName        string `toml:\"name\"`\n\tDescription string `toml:\"description\"`\n\tPath        string `toml:\"path\"`\n\tTag         string `toml:\"tag\"`\n\tBranch      string `toml:\"branch\"`\n}\n\n// ensureConfigDirExists creates the application configuration directory if it\n// doesn't already exist.\nfunc ensureConfigDirExists(path string) error {\n\tbasePath := filepath.Dir(path)\n\treturn filesystem.MakeDirectoryIfNotExists(basePath)\n}\n\n// File represents our application toml configuration.\ntype File struct {\n\t// Auth represents the new auth token storage.\n\tAuth Auth `toml:\"auth,omitempty\"`\n\t// CLI represents CLI specific configuration.\n\tCLI CLI `toml:\"cli\"`\n\t// ConfigVersion is the version of the config.\n\tConfigVersion int `toml:\"config_version\"`\n\t// Fastly represents fastly specific configuration.\n\tFastly Fastly `toml:\"fastly\"`\n\t// Language represents C@E language specific configuration.\n\tLanguage Language `toml:\"language\"`\n\t// Profiles represents legacy profile accounts (migrated to [auth]).\n\tProfiles Profiles `toml:\"profile,omitempty\"`\n\t// StarterKitLanguages represents language specific starter kits.\n\tStarterKits StarterKitLanguages `toml:\"starter-kits\"`\n\t// Viceroy represents viceroy specific configuration.\n\tViceroy Versioner `toml:\"viceroy\"`\n\t// WasmMetadata represents what metadata will be collected.\n\tWasmMetadata WasmMetadata `toml:\"wasm-metadata\"`\n\t// WasmTools represents wasm-tools specific configuration.\n\tWasmTools Versioner `toml:\"wasm-tools\"`\n\n\t// We store off a possible legacy configuration so that we can later extract\n\t// the relevant email and token values that may pre-exist.\n\t//\n\t// NOTE: We set omitempty so when we write the in-memory data back to disk\n\t// we'll cause the [user] block to be removed. If we didn't do this, then\n\t// every time we run a command with --verbose we would see a message telling\n\t// us our config.toml was in a legacy format, even though we would have\n\t// already migrated the user data to the [profile] section.\n\tLegacyUser LegacyUser `toml:\"user,omitempty\"`\n\n\t// Store the flag values for --auto-yes/--non-interactive as at the time of\n\t// the config File construction we need these values and need to be stored so\n\t// that other callers of File.Read() don't need to have the values passed\n\t// around in function arguments.\n\t//\n\t// NOTE: These fields are private to prevent them being written back to disk,\n\t// but it means we need to expose Setter methods.\n\tautoYes        bool\n\tnonInteractive bool\n}\n\n// SetAutoYes sets the associated flag value.\n// This controls how the interactive prompts are handled.\nfunc (f *File) SetAutoYes(v bool) {\n\tf.autoYes = v\n}\n\n// SetNonInteractive sets the associated flag value.\n// This controls how the interactive prompts are handled.\nfunc (f *File) SetNonInteractive(v bool) {\n\tf.nonInteractive = v\n}\n\n// NOTE: Static 👇 is public for the sake of the test suite.\n\n// Static is the embedded configuration file used by the CLI.\n//\n//go:embed config.toml\nvar Static []byte\n\n// Read decodes a disk file into an in-memory data structure.\n//\n// NOTE: If user local configuration can't be read, then we'll ask the user to\n// confirm whether to use the static config embedded in the CLI binary. If the\n// user local configuration is deemed to be invalid, then we'll automatically\n// switch to the static config and migrate the user's profile data (if any).\nfunc (f *File) Read(\n\tpath string,\n\tin io.Reader,\n\tout io.Writer,\n\terrLog fsterr.LogInterface,\n\tverbose bool,\n) error {\n\t// Ensure the static config is sound. This should never happen (tm).\n\t// We are checking this earlier to simplify the code later on.\n\tvar staticConfig File\n\terr := toml.Unmarshal(Static, &staticConfig)\n\tif err != nil {\n\t\terrLog.Add(err)\n\t\treturn invalidStaticConfigErr(err)\n\t}\n\n\tCurrentConfigVersion = staticConfig.ConfigVersion\n\n\t// G304 (CWE-22): Potential file inclusion via variable.\n\t// gosec flagged this:\n\t// Disabling as we need to load the config.toml from the user's file system.\n\t// This file is decoded into a predefined struct, any unrecognised fields are dropped.\n\t/* #nosec */\n\t// nosemgrep: trailofbits.go.invalid-usage-of-modified-variable.invalid-usage-of-modified-variable\n\tdata, err := os.ReadFile(path)\n\tif err != nil {\n\t\tdata = Static\n\t}\n\n\tunmarshalErr := toml.Unmarshal(data, f)\n\tif unmarshalErr != nil {\n\t\terrLog.Add(unmarshalErr)\n\n\t\t// If the local disk config failed to be unmarshalled, then\n\t\t// ask the user if they would like us to replace their config with the\n\t\t// version embedded into the CLI binary.\n\n\t\ttext.Break(out)\n\n\t\tif !f.autoYes {\n\t\t\treplacement := \"Replace it with a valid version? (any existing email/token data will be lost) [y/N] \"\n\t\t\tlabel := fmt.Sprintf(\"Your configuration file (%s) is invalid. %s\", path, replacement)\n\t\t\tcont, err := text.AskYesNo(out, label, in)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"error reading input: %w\", err)\n\t\t\t}\n\t\t\tif !cont {\n\t\t\t\terr := fsterr.RemediationError{\n\t\t\t\t\tInner:       fmt.Errorf(\"%v: %v\", ErrInvalidConfig, unmarshalErr),\n\t\t\t\t\tRemediation: RemediationManualFix,\n\t\t\t\t}\n\t\t\t\terrLog.Add(err)\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\tf = &staticConfig\n\t}\n\n\terr = ensureConfigDirExists(path)\n\tif err != nil {\n\t\terrLog.Add(err)\n\t\treturn err\n\t}\n\n\tif f.NeedsUpdating(data, out, errLog, verbose) {\n\t\treturn f.UseStatic(path)\n\t}\n\n\treturn nil\n}\n\n// MigrateLegacy ensures legacy data is transitioned to config new format.\nfunc (f *File) MigrateLegacy() {\n\tif f.LegacyUser.Email != \"\" || f.LegacyUser.Token != \"\" {\n\t\tif f.Profiles == nil {\n\t\t\tf.Profiles = make(Profiles)\n\t\t}\n\n\t\t// We keep the assignment separate just in case the user somehow has a\n\t\t// config.toml with BOTH a populated [user] + [profile] section, and\n\t\t// possibly even already has a default account of \"user\".\n\n\t\tkey := \"user\"\n\t\tif _, ok := f.Profiles[key]; ok {\n\t\t\tkey = \"legacy\" // avoid overriding the default\n\t\t}\n\n\t\tf.Profiles[key] = &Profile{\n\t\t\tDefault: true,\n\t\t\tEmail:   f.LegacyUser.Email,\n\t\t\tToken:   f.LegacyUser.Token,\n\t\t}\n\t\tf.LegacyUser = LegacyUser{}\n\t}\n}\n\n// NeedsUpdating indicates if the application config needs updating.\nfunc (f *File) NeedsUpdating(data []byte, out io.Writer, errLog fsterr.LogInterface, verbose bool) bool {\n\ttree, err := toml.LoadBytes(data)\n\tif err != nil {\n\t\t// NOTE: We do not expect this error block to ever be hit because if we've\n\t\t// already successfully called toml.Unmarshal, then calling toml.LoadBytes\n\t\t// should equally be successful.\n\t\tpanic(\"LoadBytes failed but Unmarshal succeeded\")\n\t}\n\n\tswitch {\n\tcase tree.Get(\"user\") != nil:\n\t\t// The top-level 'user' section is what we're using to identify whether the\n\t\t// local config.toml file is using a legacy format. If we find that key, then\n\t\t// we must delete the file and return an error so that the calling code can\n\t\t// take the appropriate action of creating the file anew.\n\t\terrLog.Add(ErrLegacyConfig)\n\n\t\tif verbose {\n\t\t\ttext.Output(out, `\n\t\t\t\tFound your local configuration file (required to use the CLI) was using a legacy format.\n\t\t\t\tFile will be updated to the latest format.\n\t\t\t`)\n\t\t\ttext.Break(out)\n\t\t}\n\t\treturn true\n\tcase f.ConfigVersion != CurrentConfigVersion:\n\t\t// If the ConfigVersion doesn't match, then this suggests a breaking change\n\t\t// divergence in either the user's config or the CLI's config.\n\t\tif verbose {\n\t\t\ttext.Output(out, \"Found your local configuration file (required to use the CLI) to be incompatible with the current CLI version. Your configuration will be migrated to a compatible configuration format.\")\n\t\t\ttext.Break(out)\n\t\t}\n\t\treturn true\n\tcase f.CLI.Version != revision.SemVer(revision.AppVersion):\n\t\t// If the CLI.Version doesn't match the CLI binary version, then this suggests\n\t\t// a version update. This _might_ include a breaking change in the CLI's\n\t\t// logic/implementation, or a new starter kit, for example.\n\t\t// In this case we update the config regardless to ensure the\n\t\t// CLI.Version is up to date.\n\t\treturn true\n\t}\n\n\treturn false\n}\n\n// UseStatic switches the in-memory configuration with the static version\n// embedded into the CLI binary and writes it back to disk.\n//\n// NOTE: We will attempt to migrate the profile data.\nfunc (f *File) UseStatic(path string) error {\n\terr := toml.Unmarshal(Static, f)\n\tif err != nil {\n\t\treturn invalidStaticConfigErr(err)\n\t}\n\n\tf.CLI.Version = revision.SemVer(revision.AppVersion)\n\tf.MigrateLegacy()\n\n\terr = ensureConfigDirExists(path)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn f.Write(path)\n}\n\n// Write encodes in-memory data to disk.\nfunc (f *File) Write(path string) error {\n\t// gosec flagged this:\n\t// G304 (CWE-22): Potential file inclusion via variable\n\t//\n\t// Disabling as in most cases the input is determined by our own package.\n\t// In other cases we want to control the input for testing purposes.\n\t/* #nosec */\n\tfp, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, FilePermissions)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error creating config file: %w\", err)\n\t}\n\tencoder := toml.NewEncoder(fp)\n\t// Remove leading spaces from the TOML file.\n\tencoder.Indentation(\"\")\n\tif err := encoder.Encode(f); err != nil {\n\t\treturn fmt.Errorf(\"error writing to config file: %w\", err)\n\t}\n\tif err := fp.Close(); err != nil {\n\t\treturn fmt.Errorf(\"error saving config file changes: %w\", err)\n\t}\n\n\treturn nil\n}\n\n// Environment represents all of the configuration parameters that can come\n// from environment variables.\ntype Environment struct {\n\t// AccountEndpoint is the env var we look in for the Accounts endpoint.\n\tAccountEndpoint string\n\t// APIEndpoint is the API endpoint to call.\n\tAPIEndpoint string\n\t// APIToken is the env var we look in for the Fastly API token.\n\tAPIToken string\n\t// DebugMode indicates to the CLI it can display debug information.\n\tDebugMode string\n\t// UseSSO indicates if user wants to use SSO/OAuth token flow.\n\t// 1: enabled, 0: disabled.\n\tUseSSO string\n\t// UserAgentExtension is the string we'll add to the UserAgent\n\t// we send in API requests.\n\tUserAgentExtension string\n\t// WasmMetadataDisable is the env var we look in to disable\n\t// all data collection related to a Wasm binary.  Set to\n\t// \"true\" to disable all forms of data collection.\n\tWasmMetadataDisable string\n}\n\n// Read populates the fields from the provided environment.\nfunc (e *Environment) Read(state map[string]string) {\n\te.AccountEndpoint = state[env.AccountEndpoint]\n\te.APIEndpoint = state[env.APIEndpoint]\n\te.APIToken = state[env.APIToken]\n\te.DebugMode = state[env.DebugMode]\n\te.UseSSO = state[env.UseSSO]\n\te.UserAgentExtension = state[env.UserAgentExtension]\n\te.WasmMetadataDisable = state[env.WasmMetadataDisable]\n}\n\n// invalidStaticConfigErr generates an error to alert the user to an issue with\n// the CLI's internal configuration.\nfunc invalidStaticConfigErr(err error) error {\n\treturn fsterr.RemediationError{\n\t\tInner:       fmt.Errorf(\"%v: %v\", ErrInvalidConfig, err),\n\t\tRemediation: fsterr.InvalidStaticConfigRemediation,\n\t}\n}\n\n// FileName is the name of the application configuration file.\nconst FileName = \"config.toml\"\n\n// FilePath is the location of the fastly CLI application config file.\nvar FilePath = func() string {\n\tif dir, err := os.UserConfigDir(); err == nil {\n\t\treturn filepath.Join(dir, \"fastly\", FileName)\n\t}\n\tif dir, err := os.UserHomeDir(); err == nil {\n\t\treturn filepath.Join(dir, \".fastly\", FileName)\n\t}\n\tpanic(\"unable to deduce user config dir or user home dir\")\n}()\n"
  },
  {
    "path": "pkg/config/config_test.go",
    "content": "package config_test\n\nimport (\n\t\"bytes\"\n\t_ \"embed\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"testing\"\n\n\ttoml \"github.com/pelletier/go-toml\"\n\n\t\"github.com/fastly/cli/pkg/config\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n)\n\n//go:embed testdata/static/config.toml\nvar staticConfig []byte\n\n//go:embed testdata/static/config-invalid.toml\nvar staticConfigInvalid []byte\n\ntype testReadScenario struct {\n\tname                 string\n\tremediation          bool\n\tstaticConfig         []byte\n\tuserConfigFilename   string\n\tuserResponseToPrompt string\n\twantError            string\n}\n\n// TestConfigRead validates all logic flows within config.File.Read().\nfunc TestConfigRead(t *testing.T) {\n\tscenarios := []testReadScenario{\n\t\t{\n\t\t\tname:                 \"static config should be used when there is no local user config\",\n\t\t\tuserResponseToPrompt: \"yes\", // prompts asks user to confirm they want a static fallback\n\t\t\tstaticConfig:         staticConfig,\n\t\t},\n\t\t{\n\t\t\tname:                 \"static config should return an error when invalid\",\n\t\t\tuserResponseToPrompt: \"yes\", // prompts asks user to confirm they want a static fallback\n\t\t\tstaticConfig:         staticConfigInvalid,\n\t\t\twantError:            config.ErrInvalidConfig.Error(),\n\t\t},\n\t\t{\n\t\t\tname:                 \"when user config is invalid (and the user accepts static config) but static config is also invalid, it should return an error\",\n\t\t\tstaticConfig:         staticConfigInvalid,\n\t\t\tuserConfigFilename:   \"config-invalid.toml\",\n\t\t\tuserResponseToPrompt: \"yes\",\n\t\t\twantError:            config.ErrInvalidConfig.Error(),\n\t\t},\n\t\t{\n\t\t\tname:                 \"when user config is invalid (and the user rejects static config), it should return a specific remediation error\",\n\t\t\tremediation:          true,\n\t\t\tstaticConfig:         staticConfig,\n\t\t\tuserConfigFilename:   \"config-invalid.toml\",\n\t\t\tuserResponseToPrompt: \"no\",\n\t\t\twantError:            config.RemediationManualFix,\n\t\t},\n\t\t{\n\t\t\tname:                 \"when user config is in the legacy format, it should use static config\",\n\t\t\tstaticConfig:         staticConfig,\n\t\t\tuserConfigFilename:   \"config-legacy.toml\",\n\t\t\tuserResponseToPrompt: \"no\",\n\t\t},\n\t\t{\n\t\t\tname:               \"when user config is valid, it should return no error\",\n\t\t\tstaticConfig:       staticConfig,\n\t\t\tuserConfigFilename: \"config.toml\",\n\t\t},\n\t}\n\n\tfor _, testcase := range scenarios {\n\t\tt.Run(testcase.name, func(t *testing.T) {\n\t\t\t// We're going to chdir to a temp environment,\n\t\t\t// so save the PWD to return to, afterwards.\n\t\t\tpwd, err := os.Getwd()\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\t// Create test environment\n\t\t\tbackupStatic := config.Static\n\t\t\tdefer func() {\n\t\t\t\tconfig.Static = backupStatic\n\t\t\t}()\n\t\t\tconfig.Static = testcase.staticConfig\n\t\t\topts := testutil.EnvOpts{T: t}\n\t\t\tif testcase.userConfigFilename != \"\" {\n\t\t\t\tb, err := os.ReadFile(filepath.Join(\"testdata\", testcase.userConfigFilename))\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatal(err)\n\t\t\t\t}\n\t\t\t\topts.Write = []testutil.FileIO{\n\t\t\t\t\t{Src: string(b), Dst: \"user-config.toml\"},\n\t\t\t\t}\n\t\t\t}\n\t\t\trootdir := testutil.NewEnv(opts)\n\t\t\tconfigPath := filepath.Join(rootdir, \"user-config.toml\")\n\t\t\tdefer os.RemoveAll(rootdir)\n\n\t\t\t// Before running the test, chdir into the temp environment.\n\t\t\t// When we're done, chdir back to our original location.\n\t\t\t// This is so we can reliably assert file structure.\n\t\t\tif err := os.Chdir(rootdir); err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tdefer func() {\n\t\t\t\t_ = os.Chdir(pwd)\n\t\t\t}()\n\n\t\t\tif testcase.userConfigFilename == \"\" {\n\t\t\t\tif fi, err := os.Stat(configPath); err == nil {\n\t\t\t\t\tt.Fatalf(\"expected the user config to NOT exist at this point: %+v\", fi)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif _, err := os.Stat(configPath); err != nil {\n\t\t\t\t\tt.Fatalf(\"expected the user config to exist at this point: %v\", err)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tvar out bytes.Buffer\n\t\t\tin := strings.NewReader(testcase.userResponseToPrompt)\n\n\t\t\tmockLog := fsterr.MockLog{}\n\n\t\t\tvar f config.File\n\t\t\terr = f.Read(configPath, in, &out, mockLog, false)\n\n\t\t\tif testcase.remediation {\n\t\t\t\te, ok := err.(fsterr.RemediationError)\n\t\t\t\tif !ok {\n\t\t\t\t\tt.Fatalf(\"unexpected error asserting returned error (%T) to a RemediationError type\", err)\n\t\t\t\t}\n\t\t\t\tif testcase.wantError != e.Remediation {\n\t\t\t\t\tt.Fatalf(\"want %v, have %v\", testcase.wantError, e.Remediation)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\ttestutil.AssertErrorContains(t, err, testcase.wantError)\n\t\t\t}\n\n\t\t\tif testcase.wantError == \"\" {\n\t\t\t\t// If we're not expecting an error, then we're expecting the user\n\t\t\t\t// configuration file to exist...\n\n\t\t\t\tif _, err := os.Stat(configPath); err == nil {\n\t\t\t\t\tbs, err := os.ReadFile(configPath)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tt.Fatalf(\"unexpected err: %v\", err)\n\t\t\t\t\t}\n\n\t\t\t\t\terr = toml.Unmarshal(bs, &f)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tt.Fatalf(\"unexpected err: %v\", err)\n\t\t\t\t\t}\n\n\t\t\t\t\tif f.CLI.Version == \"\" {\n\t\t\t\t\t\tt.Fatalf(\"expected CLI.Version to be set: %+v\", f)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\n// TestUseStatic validates legacy user data is migrated successfully.\nfunc TestUseStatic(t *testing.T) {\n\t// We're going to chdir to a temp environment,\n\t// so save the PWD to return to, afterwards.\n\tpwd, err := os.Getwd()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t// Create test environment\n\tb, err := os.ReadFile(filepath.Join(\"testdata\", \"config-legacy.toml\"))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\trootdir := testutil.NewEnv(testutil.EnvOpts{\n\t\tT: t,\n\t\tWrite: []testutil.FileIO{\n\t\t\t{Src: string(b), Dst: \"user-config.toml\"},\n\t\t},\n\t})\n\tlegacyUserConfigPath := filepath.Join(rootdir, \"user-config.toml\")\n\tdefer os.RemoveAll(rootdir)\n\n\t// Before running the test, chdir into the temp environment.\n\t// When we're done, chdir back to our original location.\n\t// This is so we can reliably assert file structure.\n\tif err := os.Chdir(rootdir); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer func() {\n\t\t_ = os.Chdir(pwd)\n\t}()\n\n\tvar out bytes.Buffer\n\n\t// Validate that legacy configuration can be migrated to the static one\n\t// embedded in the CLI binary.\n\tf := config.File{}\n\terr = f.Read(legacyUserConfigPath, strings.NewReader(\"\"), &out, fsterr.MockLog{}, false)\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected err: %v\", err)\n\t}\n\n\tif f.CLI.Version == \"\" {\n\t\tt.Fatalf(\"expected CLI.Version to be set: %+v\", f)\n\t}\n\tif f.Profiles[\"user\"].Token != \"foobar\" {\n\t\tt.Fatalf(\"wanted token: %s, got: %s\", \"foobar\", f.LegacyUser.Token)\n\t}\n\tif f.Profiles[\"user\"].Email != \"testing@fastly.com\" {\n\t\tt.Fatalf(\"wanted email: %s, got: %s\", \"testing@fastly.com\", f.LegacyUser.Email)\n\t}\n\tif !f.Profiles[\"user\"].Default {\n\t\tt.Fatal(\"expected the migrated user to become the default\")\n\t}\n\n\t// We validate both the in-memory data structure (above) AND the file on disk (below).\n\tdata, err := os.ReadFile(legacyUserConfigPath)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tif strings.Contains(string(data), \"[user]\") {\n\t\tt.Error(\"expected legacy [user] section to be removed\")\n\t}\n\tif !strings.Contains(string(data), `[profile.user]\naccess_token = \"\"\naccess_token_created = 0\naccess_token_ttl = 0\ncustomer_id = \"\"\ncustomer_name = \"\"\ndefault = true\nemail = \"testing@fastly.com\"\nrefresh_token = \"\"\nrefresh_token_created = 0\nrefresh_token_ttl = 0\ntoken = \"foobar\"`) {\n\t\tt.Errorf(\"expected legacy [user] section to be migrated to [profile.user]: %s\", string(data))\n\t}\n\n\t// Validate that invalid static configuration returns a specific error.\n\t//\n\t// NOTE: By providing a legacy config, we'll cause the static config embedded\n\t// into the CLI to be used, and we'll migrate the legacy data to the new\n\t// format, but by specifying the static config as being invalid we expect the\n\t// CLI to return the error.\n\tbackupStatic := config.Static\n\tdefer func() {\n\t\tconfig.Static = backupStatic\n\t}()\n\tconfig.Static = staticConfigInvalid\n\tf = config.File{}\n\terr = f.Read(legacyUserConfigPath, strings.NewReader(\"\"), &out, fsterr.MockLog{}, false)\n\tif err == nil {\n\t\tt.Fatal(\"expected an error, but got nil\")\n\t} else {\n\t\ttestutil.AssertErrorContains(t, err, config.ErrInvalidConfig.Error())\n\t}\n}\n\ntype testInvalidConfigScenario struct {\n\ttestutil.CLIScenario\n\n\tinvalid      bool\n\tstaticConfig []byte\n\tuserConfig   string\n}\n\n// TestInvalidConfig validates all logic flows within config.File.ValidConfig()\n//\n// NOTE: Even with invalid config we expect the static config embedded with the\n// CLI to be utilised.\nfunc TestInvalidConfig(t *testing.T) {\n\ts1 := testInvalidConfigScenario{}\n\ts1.Name = \"invalid config version, invalid cli version\"\n\ts1.invalid = true\n\ts1.staticConfig = staticConfig\n\ts1.userConfig = \"config-incompatible-config-version.toml\"\n\n\ts2 := testInvalidConfigScenario{}\n\ts2.Name = \"valid config version, invalid cli version\"\n\ts2.invalid = false\n\ts2.staticConfig = staticConfig\n\ts2.userConfig = \"config.toml\"\n\n\tscenarios := []testInvalidConfigScenario{s1, s2}\n\n\tfor testcaseIdx := range scenarios {\n\t\ttestcase := &scenarios[testcaseIdx]\n\t\tt.Run(testcase.Name, func(t *testing.T) {\n\t\t\t// We're going to chdir to a temp environment,\n\t\t\t// so save the PWD to return to, afterwards.\n\t\t\tpwd, err := os.Getwd()\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\t// Create test environment\n\t\t\trootdir := testutil.NewEnv(testutil.EnvOpts{\n\t\t\t\tT: t,\n\t\t\t\tCopy: []testutil.FileIO{\n\t\t\t\t\t{\n\t\t\t\t\t\tSrc: filepath.Join(\"testdata\", testcase.userConfig),\n\t\t\t\t\t\tDst: \"config.toml\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t})\n\t\t\tconfigPath := filepath.Join(rootdir, \"config.toml\")\n\t\t\tdefer os.RemoveAll(rootdir)\n\n\t\t\t// Before running the test, chdir into the temp environment.\n\t\t\t// When we're done, chdir back to our original location.\n\t\t\t// This is so we can reliably assert file structure.\n\t\t\tif err := os.Chdir(rootdir); err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tdefer func() {\n\t\t\t\t_ = os.Chdir(pwd)\n\t\t\t}()\n\n\t\t\tvar f config.File\n\t\t\tvar stdout bytes.Buffer\n\t\t\tconfig.Static = testcase.staticConfig\n\n\t\t\tin := strings.NewReader(\"\") // these tests won't trigger a user prompt\n\t\t\terr = f.Read(configPath, in, &stdout, nil, true)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t\t}\n\n\t\t\toutput := strings.ReplaceAll(stdout.String(), \"\\n\", \" \")\n\t\t\tif testcase.invalid {\n\t\t\t\ttestutil.AssertStringContains(t, output, \"incompatible with the current CLI version\")\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestNeedsUpdating(t *testing.T) {\n\tt.Parallel()\n\n\tconfig.CurrentConfigVersion = 2\n\n\ttests := []struct {\n\t\tname     string\n\t\tfilename string\n\t\twant     bool\n\t}{\n\t\t{\n\t\t\t\"legacy config should be updated\",\n\t\t\t\"config-legacy.toml\",\n\t\t\ttrue,\n\t\t},\n\t\t{\n\t\t\t\"outdated config_version config should be updated\",\n\t\t\t\"config.toml\",\n\t\t\ttrue,\n\t\t},\n\t\t{\n\t\t\t\"mismatching CLI version config should be updated\",\n\t\t\t\"config-outdated-cli-version.toml\",\n\t\t\ttrue,\n\t\t},\n\t\t{\n\t\t\t\"current config should not be updated\",\n\t\t\t\"config-current.toml\",\n\t\t\tfalse,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tdata, err := os.ReadFile(filepath.Join(\"testdata\", tt.filename))\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t\t}\n\t\t\tvar f config.File\n\t\t\tif err = toml.Unmarshal(data, &f); err != nil {\n\t\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t\t}\n\t\t\tvar stdout bytes.Buffer\n\t\t\tmockLog := fsterr.MockLog{}\n\t\t\tresult := f.NeedsUpdating(data, &stdout, mockLog, true)\n\t\t\tif result != tt.want {\n\t\t\t\tt.Fatalf(\"expected %v got %v\", tt.want, result)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/config/doc.go",
    "content": "// Package config manages global configuration parameters. It has a data type\n// and helpers for getting information out of the runtime environment, and\n// making it available to commands that need it.\npackage config\n"
  },
  {
    "path": "pkg/config/migrate_auth.go",
    "content": "package config\n\nimport (\n\t\"fmt\"\n\t\"time\"\n)\n\n// MigrateProfilesToAuth migrates existing profile entries into the new [auth]\n// config section. It is safe to call multiple times; it only migrates profiles\n// that do not already have a corresponding auth token entry.\n//\n// Returns true if any migration was performed.\nfunc (f *File) MigrateProfilesToAuth() bool {\n\tif len(f.Profiles) == 0 {\n\t\treturn false\n\t}\n\n\tif f.Auth.Tokens == nil {\n\t\tf.Auth.Tokens = make(AuthTokens)\n\t}\n\n\tmigrated := false\n\tfor name, p := range f.Profiles {\n\t\tif _, exists := f.Auth.Tokens[name]; exists {\n\t\t\tcontinue\n\t\t}\n\n\t\tentry := profileToAuthToken(p)\n\t\tf.Auth.Tokens[name] = entry\n\n\t\tif p.Default && f.Auth.Default == \"\" {\n\t\t\tf.Auth.Default = name\n\t\t}\n\n\t\tmigrated = true\n\t}\n\n\t// If no default was set but we migrated something, pick the first one.\n\tif f.Auth.Default == \"\" && len(f.Auth.Tokens) > 0 {\n\t\tfor name := range f.Auth.Tokens {\n\t\t\tf.Auth.Default = name\n\t\t\tbreak\n\t\t}\n\t}\n\n\treturn migrated\n}\n\n// profileToAuthToken converts a legacy Profile to the new AuthToken format.\nfunc profileToAuthToken(p *Profile) *AuthToken {\n\tentry := &AuthToken{\n\t\tToken:     p.Token,\n\t\tEmail:     p.Email,\n\t\tAccountID: p.CustomerID,\n\t}\n\n\t// Determine if this is an SSO-backed profile.\n\tif p.AccessToken != \"\" || p.RefreshToken != \"\" || p.AccessTokenCreated != 0 || p.RefreshTokenCreated != 0 {\n\t\tentry.Type = AuthTokenTypeSSO\n\t\tentry.AccessToken = p.AccessToken\n\t\tentry.RefreshToken = p.RefreshToken\n\n\t\tif p.AccessTokenCreated != 0 && p.AccessTokenTTL != 0 {\n\t\t\texpiresAt := time.Unix(p.AccessTokenCreated, 0).Add(time.Duration(p.AccessTokenTTL) * time.Second)\n\t\t\tentry.AccessExpiresAt = expiresAt.Format(time.RFC3339)\n\t\t}\n\t\tif p.RefreshTokenCreated != 0 && p.RefreshTokenTTL != 0 {\n\t\t\texpiresAt := time.Unix(p.RefreshTokenCreated, 0).Add(time.Duration(p.RefreshTokenTTL) * time.Second)\n\t\t\tentry.RefreshExpiresAt = expiresAt.Format(time.RFC3339)\n\t\t}\n\n\t\t// Check if the SSO session is expired and cannot be auto-refreshed.\n\t\tif entry.RefreshExpiresAt != \"\" {\n\t\t\tt, err := time.Parse(time.RFC3339, entry.RefreshExpiresAt)\n\t\t\tif err == nil && time.Now().After(t) {\n\t\t\t\tentry.NeedsReauth = true\n\t\t\t}\n\t\t}\n\t} else {\n\t\tentry.Type = AuthTokenTypeStatic\n\t}\n\n\tif p.CustomerName != \"\" {\n\t\tentry.Label = fmt.Sprintf(\"%s (%s)\", p.CustomerName, p.Email)\n\t}\n\n\treturn entry\n}\n\nfunc (f *File) AuthInitialized() bool {\n\treturn len(f.Auth.Tokens) > 0\n}\n\nfunc (f *File) GetAuthToken(name string) *AuthToken {\n\treturn f.Auth.Tokens[name]\n}\n\nfunc (f *File) GetDefaultAuthToken() (string, *AuthToken) {\n\tif f.Auth.Default == \"\" {\n\t\treturn \"\", nil\n\t}\n\tif t := f.Auth.Tokens[f.Auth.Default]; t != nil {\n\t\treturn f.Auth.Default, t\n\t}\n\treturn \"\", nil\n}\n\nfunc (f *File) SetAuthToken(name string, token *AuthToken) {\n\tif f.Auth.Tokens == nil {\n\t\tf.Auth.Tokens = make(AuthTokens)\n\t}\n\tf.Auth.Tokens[name] = token\n}\n\nfunc (f *File) DeleteAuthToken(name string) bool {\n\tif f.Auth.Tokens == nil {\n\t\treturn false\n\t}\n\tif _, ok := f.Auth.Tokens[name]; !ok {\n\t\treturn false\n\t}\n\tdelete(f.Auth.Tokens, name)\n\tif f.Auth.Default == name {\n\t\tf.Auth.Default = \"\"\n\t\t// Set a new default if possible.\n\t\tfor n := range f.Auth.Tokens {\n\t\t\tf.Auth.Default = n\n\t\t\tbreak\n\t\t}\n\t}\n\treturn true\n}\n\nfunc (f *File) SetDefaultAuthToken(name string) error {\n\tif f.Auth.Tokens == nil {\n\t\treturn fmt.Errorf(\"no auth tokens configured\")\n\t}\n\tif _, ok := f.Auth.Tokens[name]; !ok {\n\t\treturn fmt.Errorf(\"token %q not found\", name)\n\t}\n\tf.Auth.Default = name\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/config/migrate_auth_test.go",
    "content": "package config\n\nimport (\n\t\"testing\"\n\t\"time\"\n)\n\nfunc TestMigrateProfilesToAuth_EmptyProfiles(t *testing.T) {\n\tt.Parallel()\n\n\tf := &File{}\n\tmigrated := f.MigrateProfilesToAuth()\n\n\tif migrated {\n\t\tt.Fatal(\"expected no migration when profiles are empty\")\n\t}\n\tif f.Auth.Tokens != nil {\n\t\tt.Fatalf(\"expected Auth.Tokens to remain nil, got %v\", f.Auth.Tokens)\n\t}\n\tif f.Auth.Default != \"\" {\n\t\tt.Fatalf(\"expected Auth.Default to be empty, got %q\", f.Auth.Default)\n\t}\n}\n\nfunc TestMigrateProfilesToAuth_StaticToken(t *testing.T) {\n\tt.Parallel()\n\n\tf := &File{\n\t\tProfiles: Profiles{\n\t\t\t\"work\": {\n\t\t\t\tToken:      \"tok_abc123\",\n\t\t\t\tEmail:      \"dev@example.com\",\n\t\t\t\tCustomerID: \"cust_42\",\n\t\t\t},\n\t\t},\n\t}\n\n\tmigrated := f.MigrateProfilesToAuth()\n\tif !migrated {\n\t\tt.Fatal(\"expected migration to occur\")\n\t}\n\n\ttok := f.Auth.Tokens[\"work\"]\n\tif tok == nil {\n\t\tt.Fatal(\"expected auth token 'work' to exist after migration\")\n\t}\n\tif tok.Type != AuthTokenTypeStatic {\n\t\tt.Fatalf(\"expected type %q, got %q\", AuthTokenTypeStatic, tok.Type)\n\t}\n\tif tok.Token != \"tok_abc123\" {\n\t\tt.Fatalf(\"expected token %q, got %q\", \"tok_abc123\", tok.Token)\n\t}\n\tif tok.Email != \"dev@example.com\" {\n\t\tt.Fatalf(\"expected email %q, got %q\", \"dev@example.com\", tok.Email)\n\t}\n\tif tok.AccountID != \"cust_42\" {\n\t\tt.Fatalf(\"expected account_id %q, got %q\", \"cust_42\", tok.AccountID)\n\t}\n\tif tok.AccessToken != \"\" {\n\t\tt.Fatalf(\"expected empty access_token for static type, got %q\", tok.AccessToken)\n\t}\n\tif tok.RefreshToken != \"\" {\n\t\tt.Fatalf(\"expected empty refresh_token for static type, got %q\", tok.RefreshToken)\n\t}\n}\n\nfunc TestMigrateProfilesToAuth_SSOToken(t *testing.T) {\n\tt.Parallel()\n\n\t// Use timestamps far in the future so NeedsReauth stays false.\n\tnow := time.Now()\n\tcreated := now.Unix()\n\tttlSeconds := 86400 // 24 hours\n\n\tf := &File{\n\t\tProfiles: Profiles{\n\t\t\t\"sso-user\": {\n\t\t\t\tToken:               \"tok_sso\",\n\t\t\t\tEmail:               \"sso@example.com\",\n\t\t\t\tCustomerID:          \"cust_99\",\n\t\t\t\tCustomerName:        \"Acme Corp\",\n\t\t\t\tAccessToken:         \"access_xyz\",\n\t\t\t\tAccessTokenCreated:  created,\n\t\t\t\tAccessTokenTTL:      ttlSeconds,\n\t\t\t\tRefreshToken:        \"refresh_xyz\",\n\t\t\t\tRefreshTokenCreated: created,\n\t\t\t\tRefreshTokenTTL:     ttlSeconds * 30, // 30 days\n\t\t\t},\n\t\t},\n\t}\n\n\tmigrated := f.MigrateProfilesToAuth()\n\tif !migrated {\n\t\tt.Fatal(\"expected migration to occur\")\n\t}\n\n\ttok := f.Auth.Tokens[\"sso-user\"]\n\tif tok == nil {\n\t\tt.Fatal(\"expected auth token 'sso-user' to exist after migration\")\n\t}\n\tif tok.Type != AuthTokenTypeSSO {\n\t\tt.Fatalf(\"expected type %q, got %q\", AuthTokenTypeSSO, tok.Type)\n\t}\n\tif tok.Token != \"tok_sso\" {\n\t\tt.Fatalf(\"expected token %q, got %q\", \"tok_sso\", tok.Token)\n\t}\n\tif tok.AccessToken != \"access_xyz\" {\n\t\tt.Fatalf(\"expected access_token %q, got %q\", \"access_xyz\", tok.AccessToken)\n\t}\n\tif tok.RefreshToken != \"refresh_xyz\" {\n\t\tt.Fatalf(\"expected refresh_token %q, got %q\", \"refresh_xyz\", tok.RefreshToken)\n\t}\n\tif tok.Label != \"Acme Corp (sso@example.com)\" {\n\t\tt.Fatalf(\"expected label %q, got %q\", \"Acme Corp (sso@example.com)\", tok.Label)\n\t}\n\n\t// Verify expiry timestamps were computed.\n\texpectedAccessExpiry := time.Unix(created, 0).Add(time.Duration(ttlSeconds) * time.Second).Format(time.RFC3339)\n\tif tok.AccessExpiresAt != expectedAccessExpiry {\n\t\tt.Fatalf(\"expected access_expires_at %q, got %q\", expectedAccessExpiry, tok.AccessExpiresAt)\n\t}\n\texpectedRefreshExpiry := time.Unix(created, 0).Add(time.Duration(ttlSeconds*30) * time.Second).Format(time.RFC3339)\n\tif tok.RefreshExpiresAt != expectedRefreshExpiry {\n\t\tt.Fatalf(\"expected refresh_expires_at %q, got %q\", expectedRefreshExpiry, tok.RefreshExpiresAt)\n\t}\n\tif tok.NeedsReauth {\n\t\tt.Fatal(\"expected NeedsReauth to be false for a token with a future refresh expiry\")\n\t}\n}\n\nfunc TestMigrateProfilesToAuth_SSOToken_NeedsReauth(t *testing.T) {\n\tt.Parallel()\n\n\t// Use timestamps in the past so the refresh token is expired.\n\tpastCreated := time.Now().Add(-48 * time.Hour).Unix()\n\tttlSeconds := 3600 // 1 hour -- well in the past\n\n\tf := &File{\n\t\tProfiles: Profiles{\n\t\t\t\"expired-sso\": {\n\t\t\t\tToken:               \"tok_old\",\n\t\t\t\tEmail:               \"old@example.com\",\n\t\t\t\tAccessToken:         \"access_old\",\n\t\t\t\tAccessTokenCreated:  pastCreated,\n\t\t\t\tAccessTokenTTL:      ttlSeconds,\n\t\t\t\tRefreshToken:        \"refresh_old\",\n\t\t\t\tRefreshTokenCreated: pastCreated,\n\t\t\t\tRefreshTokenTTL:     ttlSeconds,\n\t\t\t},\n\t\t},\n\t}\n\n\tf.MigrateProfilesToAuth()\n\n\ttok := f.Auth.Tokens[\"expired-sso\"]\n\tif tok == nil {\n\t\tt.Fatal(\"expected auth token to exist\")\n\t}\n\tif tok.Type != AuthTokenTypeSSO {\n\t\tt.Fatalf(\"expected type %q, got %q\", AuthTokenTypeSSO, tok.Type)\n\t}\n\tif !tok.NeedsReauth {\n\t\tt.Fatal(\"expected NeedsReauth to be true for an expired refresh token\")\n\t}\n}\n\nfunc TestMigrateProfilesToAuth_DefaultPreserved(t *testing.T) {\n\tt.Parallel()\n\n\tf := &File{\n\t\tProfiles: Profiles{\n\t\t\t\"alpha\": {\n\t\t\t\tToken: \"tok_alpha\",\n\t\t\t\tEmail: \"alpha@example.com\",\n\t\t\t},\n\t\t\t\"beta\": {\n\t\t\t\tToken:   \"tok_beta\",\n\t\t\t\tEmail:   \"beta@example.com\",\n\t\t\t\tDefault: true,\n\t\t\t},\n\t\t\t\"gamma\": {\n\t\t\t\tToken: \"tok_gamma\",\n\t\t\t\tEmail: \"gamma@example.com\",\n\t\t\t},\n\t\t},\n\t}\n\n\tmigrated := f.MigrateProfilesToAuth()\n\tif !migrated {\n\t\tt.Fatal(\"expected migration to occur\")\n\t}\n\n\tif f.Auth.Default != \"beta\" {\n\t\tt.Fatalf(\"expected default to be %q, got %q\", \"beta\", f.Auth.Default)\n\t}\n\n\t// All three should be migrated.\n\tfor _, name := range []string{\"alpha\", \"beta\", \"gamma\"} {\n\t\tif f.Auth.Tokens[name] == nil {\n\t\t\tt.Fatalf(\"expected auth token %q to exist\", name)\n\t\t}\n\t}\n}\n\nfunc TestMigrateProfilesToAuth_NoDefaultPicksOne(t *testing.T) {\n\tt.Parallel()\n\n\tf := &File{\n\t\tProfiles: Profiles{\n\t\t\t\"only\": {\n\t\t\t\tToken: \"tok_only\",\n\t\t\t\tEmail: \"only@example.com\",\n\t\t\t\t// Default is false\n\t\t\t},\n\t\t},\n\t}\n\n\tf.MigrateProfilesToAuth()\n\n\t// When no profile has Default=true, migration should still pick a default.\n\tif f.Auth.Default == \"\" {\n\t\tt.Fatal(\"expected a default to be assigned when none was explicitly set\")\n\t}\n\tif f.Auth.Tokens[f.Auth.Default] == nil {\n\t\tt.Fatalf(\"expected the assigned default %q to exist in tokens\", f.Auth.Default)\n\t}\n}\n\nfunc TestMigrateProfilesToAuth_AlreadyMigrated(t *testing.T) {\n\tt.Parallel()\n\n\texisting := &AuthToken{\n\t\tType:  AuthTokenTypeStatic,\n\t\tToken: \"original_token\",\n\t\tEmail: \"original@example.com\",\n\t}\n\n\tf := &File{\n\t\tAuth: Auth{\n\t\t\tDefault: \"existing\",\n\t\t\tTokens: AuthTokens{\n\t\t\t\t\"existing\": existing,\n\t\t\t},\n\t\t},\n\t\tProfiles: Profiles{\n\t\t\t\"existing\": {\n\t\t\t\tToken: \"profile_token_different\",\n\t\t\t\tEmail: \"different@example.com\",\n\t\t\t},\n\t\t},\n\t}\n\n\tmigrated := f.MigrateProfilesToAuth()\n\n\t// The profile name matches an existing auth token, so it should be skipped.\n\t// Since no new tokens were added, migrated should be false.\n\tif migrated {\n\t\tt.Fatal(\"expected no migration when all profiles already have corresponding auth tokens\")\n\t}\n\n\t// The original auth token should be untouched.\n\ttok := f.Auth.Tokens[\"existing\"]\n\tif tok.Token != \"original_token\" {\n\t\tt.Fatalf(\"expected original token %q to be preserved, got %q\", \"original_token\", tok.Token)\n\t}\n\tif tok.Email != \"original@example.com\" {\n\t\tt.Fatalf(\"expected original email to be preserved, got %q\", tok.Email)\n\t}\n}\n\nfunc TestMigrateProfilesToAuth_Idempotent(t *testing.T) {\n\tt.Parallel()\n\n\tf := &File{\n\t\tProfiles: Profiles{\n\t\t\t\"user1\": {\n\t\t\t\tToken:   \"tok_1\",\n\t\t\t\tEmail:   \"user1@example.com\",\n\t\t\t\tDefault: true,\n\t\t\t},\n\t\t\t\"user2\": {\n\t\t\t\tToken: \"tok_2\",\n\t\t\t\tEmail: \"user2@example.com\",\n\t\t\t},\n\t\t},\n\t}\n\n\t// First migration.\n\tfirst := f.MigrateProfilesToAuth()\n\tif !first {\n\t\tt.Fatal(\"expected first migration to return true\")\n\t}\n\n\tif len(f.Auth.Tokens) != 2 {\n\t\tt.Fatalf(\"expected 2 auth tokens after first migration, got %d\", len(f.Auth.Tokens))\n\t}\n\n\t// Capture state after first migration.\n\ttok1Token := f.Auth.Tokens[\"user1\"].Token\n\ttok2Token := f.Auth.Tokens[\"user2\"].Token\n\tdefaultName := f.Auth.Default\n\n\t// Second migration: should be a no-op.\n\tsecond := f.MigrateProfilesToAuth()\n\tif second {\n\t\tt.Fatal(\"expected second migration to return false (no new tokens added)\")\n\t}\n\n\tif len(f.Auth.Tokens) != 2 {\n\t\tt.Fatalf(\"expected 2 auth tokens after second migration, got %d\", len(f.Auth.Tokens))\n\t}\n\tif f.Auth.Tokens[\"user1\"].Token != tok1Token {\n\t\tt.Fatal(\"user1 token was modified on second migration\")\n\t}\n\tif f.Auth.Tokens[\"user2\"].Token != tok2Token {\n\t\tt.Fatal(\"user2 token was modified on second migration\")\n\t}\n\tif f.Auth.Default != defaultName {\n\t\tt.Fatalf(\"default changed from %q to %q on second migration\", defaultName, f.Auth.Default)\n\t}\n}\n\nfunc TestAuthToken_CRUD(t *testing.T) {\n\tt.Parallel()\n\n\tf := &File{}\n\n\t// Initially there are no tokens.\n\tif f.AuthInitialized() {\n\t\tt.Fatal(\"expected AuthInitialized to return false on empty File\")\n\t}\n\tif tok := f.GetAuthToken(\"anything\"); tok != nil {\n\t\tt.Fatalf(\"expected nil from GetAuthToken on empty File, got %v\", tok)\n\t}\n\n\t// Set a token.\n\ttoken1 := &AuthToken{\n\t\tType:  AuthTokenTypeStatic,\n\t\tToken: \"tok_crud_1\",\n\t\tEmail: \"crud1@example.com\",\n\t}\n\tf.SetAuthToken(\"first\", token1)\n\n\tif !f.AuthInitialized() {\n\t\tt.Fatal(\"expected AuthInitialized to return true after SetAuthToken\")\n\t}\n\n\tgot := f.GetAuthToken(\"first\")\n\tif got == nil {\n\t\tt.Fatal(\"expected to get auth token 'first'\")\n\t}\n\tif got.Token != \"tok_crud_1\" {\n\t\tt.Fatalf(\"expected token %q, got %q\", \"tok_crud_1\", got.Token)\n\t}\n\n\t// Set another token.\n\ttoken2 := &AuthToken{\n\t\tType:  AuthTokenTypeSSO,\n\t\tToken: \"tok_crud_2\",\n\t\tEmail: \"crud2@example.com\",\n\t}\n\tf.SetAuthToken(\"second\", token2)\n\n\tif len(f.Auth.Tokens) != 2 {\n\t\tt.Fatalf(\"expected 2 tokens, got %d\", len(f.Auth.Tokens))\n\t}\n\n\t// Overwrite an existing token.\n\ttoken1Updated := &AuthToken{\n\t\tType:  AuthTokenTypeStatic,\n\t\tToken: \"tok_crud_1_updated\",\n\t\tEmail: \"crud1_updated@example.com\",\n\t}\n\tf.SetAuthToken(\"first\", token1Updated)\n\n\tgot = f.GetAuthToken(\"first\")\n\tif got.Token != \"tok_crud_1_updated\" {\n\t\tt.Fatalf(\"expected updated token %q, got %q\", \"tok_crud_1_updated\", got.Token)\n\t}\n\tif len(f.Auth.Tokens) != 2 {\n\t\tt.Fatalf(\"expected 2 tokens after overwrite, got %d\", len(f.Auth.Tokens))\n\t}\n\n\t// Set a default.\n\terr := f.SetDefaultAuthToken(\"second\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error setting default: %v\", err)\n\t}\n\tif f.Auth.Default != \"second\" {\n\t\tt.Fatalf(\"expected default %q, got %q\", \"second\", f.Auth.Default)\n\t}\n\n\t// Try to set default to a non-existent token.\n\terr = f.SetDefaultAuthToken(\"nonexistent\")\n\tif err == nil {\n\t\tt.Fatal(\"expected error when setting default to non-existent token\")\n\t}\n\n\t// Delete a non-default token.\n\tdeleted := f.DeleteAuthToken(\"first\")\n\tif !deleted {\n\t\tt.Fatal(\"expected DeleteAuthToken to return true\")\n\t}\n\tif f.GetAuthToken(\"first\") != nil {\n\t\tt.Fatal(\"expected 'first' to be deleted\")\n\t}\n\tif len(f.Auth.Tokens) != 1 {\n\t\tt.Fatalf(\"expected 1 token after deletion, got %d\", len(f.Auth.Tokens))\n\t}\n\t// Default should still be \"second\".\n\tif f.Auth.Default != \"second\" {\n\t\tt.Fatalf(\"expected default to remain %q, got %q\", \"second\", f.Auth.Default)\n\t}\n\n\t// Delete a non-existent token.\n\tdeleted = f.DeleteAuthToken(\"nonexistent\")\n\tif deleted {\n\t\tt.Fatal(\"expected DeleteAuthToken to return false for non-existent token\")\n\t}\n\n\t// Delete from nil tokens map.\n\temptyFile := &File{}\n\tdeleted = emptyFile.DeleteAuthToken(\"anything\")\n\tif deleted {\n\t\tt.Fatal(\"expected DeleteAuthToken to return false on nil tokens map\")\n\t}\n}\n\nfunc TestDeleteAuthToken_ReassignsDefault(t *testing.T) {\n\tt.Parallel()\n\n\tf := &File{}\n\n\tf.SetAuthToken(\"primary\", &AuthToken{\n\t\tType:  AuthTokenTypeStatic,\n\t\tToken: \"tok_primary\",\n\t})\n\tf.SetAuthToken(\"secondary\", &AuthToken{\n\t\tType:  AuthTokenTypeStatic,\n\t\tToken: \"tok_secondary\",\n\t})\n\n\terr := f.SetDefaultAuthToken(\"primary\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\t// Delete the default token.\n\tdeleted := f.DeleteAuthToken(\"primary\")\n\tif !deleted {\n\t\tt.Fatal(\"expected DeleteAuthToken to return true\")\n\t}\n\n\t// The default should be reassigned to the remaining token.\n\tif f.Auth.Default == \"\" {\n\t\tt.Fatal(\"expected default to be reassigned after deleting the default token\")\n\t}\n\tif f.Auth.Default == \"primary\" {\n\t\tt.Fatal(\"expected default to no longer be the deleted token\")\n\t}\n\tif f.Auth.Tokens[f.Auth.Default] == nil {\n\t\tt.Fatalf(\"expected reassigned default %q to reference an existing token\", f.Auth.Default)\n\t}\n}\n\nfunc TestDeleteAuthToken_LastToken(t *testing.T) {\n\tt.Parallel()\n\n\tf := &File{}\n\n\tf.SetAuthToken(\"only\", &AuthToken{\n\t\tType:  AuthTokenTypeStatic,\n\t\tToken: \"tok_only\",\n\t})\n\terr := f.SetDefaultAuthToken(\"only\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\tdeleted := f.DeleteAuthToken(\"only\")\n\tif !deleted {\n\t\tt.Fatal(\"expected DeleteAuthToken to return true\")\n\t}\n\n\t// With no tokens remaining, default should be empty.\n\tif f.Auth.Default != \"\" {\n\t\tt.Fatalf(\"expected default to be empty after deleting the only token, got %q\", f.Auth.Default)\n\t}\n\tif len(f.Auth.Tokens) != 0 {\n\t\tt.Fatalf(\"expected 0 tokens, got %d\", len(f.Auth.Tokens))\n\t}\n}\n\nfunc TestGetDefaultAuthToken(t *testing.T) {\n\tt.Parallel()\n\n\t// No default set, no tokens.\n\tf := &File{}\n\tname, tok := f.GetDefaultAuthToken()\n\tif name != \"\" || tok != nil {\n\t\tt.Fatalf(\"expected empty name and nil token, got name=%q tok=%v\", name, tok)\n\t}\n\n\t// Tokens exist but no default is set.\n\tf.SetAuthToken(\"orphan\", &AuthToken{\n\t\tType:  AuthTokenTypeStatic,\n\t\tToken: \"tok_orphan\",\n\t})\n\tname, tok = f.GetDefaultAuthToken()\n\tif name != \"\" || tok != nil {\n\t\tt.Fatalf(\"expected empty name and nil token when no default is set, got name=%q tok=%v\", name, tok)\n\t}\n\n\t// Set a default.\n\terr := f.SetDefaultAuthToken(\"orphan\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tname, tok = f.GetDefaultAuthToken()\n\tif name != \"orphan\" {\n\t\tt.Fatalf(\"expected default name %q, got %q\", \"orphan\", name)\n\t}\n\tif tok == nil {\n\t\tt.Fatal(\"expected non-nil token for default\")\n\t}\n\tif tok.Token != \"tok_orphan\" {\n\t\tt.Fatalf(\"expected token value %q, got %q\", \"tok_orphan\", tok.Token)\n\t}\n\n\t// Default points to a token that has been removed out-of-band\n\t// (e.g. by direct map manipulation).\n\tf.Auth.Default = \"ghost\"\n\tname, tok = f.GetDefaultAuthToken()\n\tif name != \"\" || tok != nil {\n\t\tt.Fatalf(\"expected empty name and nil token when default references non-existent token, got name=%q tok=%v\", name, tok)\n\t}\n}\n\nfunc TestSetDefaultAuthToken_NoTokens(t *testing.T) {\n\tt.Parallel()\n\n\tf := &File{}\n\terr := f.SetDefaultAuthToken(\"anything\")\n\tif err == nil {\n\t\tt.Fatal(\"expected error when setting default with nil tokens map\")\n\t}\n}\n\nfunc TestProfileToAuthToken_StaticMinimal(t *testing.T) {\n\tt.Parallel()\n\n\tp := &Profile{\n\t\tToken: \"minimal_tok\",\n\t\tEmail: \"min@example.com\",\n\t}\n\n\ttok := profileToAuthToken(p)\n\n\tif tok.Type != AuthTokenTypeStatic {\n\t\tt.Fatalf(\"expected type %q, got %q\", AuthTokenTypeStatic, tok.Type)\n\t}\n\tif tok.Token != \"minimal_tok\" {\n\t\tt.Fatalf(\"expected token %q, got %q\", \"minimal_tok\", tok.Token)\n\t}\n\tif tok.Email != \"min@example.com\" {\n\t\tt.Fatalf(\"expected email %q, got %q\", \"min@example.com\", tok.Email)\n\t}\n\tif tok.Label != \"\" {\n\t\tt.Fatalf(\"expected empty label when CustomerName is empty, got %q\", tok.Label)\n\t}\n\tif tok.AccessToken != \"\" || tok.RefreshToken != \"\" {\n\t\tt.Fatal(\"expected empty SSO fields for static token\")\n\t}\n\tif tok.AccessExpiresAt != \"\" || tok.RefreshExpiresAt != \"\" {\n\t\tt.Fatal(\"expected empty expiry fields for static token\")\n\t}\n}\n\nfunc TestProfileToAuthToken_SSOWithLabel(t *testing.T) {\n\tt.Parallel()\n\n\tnow := time.Now()\n\tcreated := now.Unix()\n\n\tp := &Profile{\n\t\tToken:               \"sso_tok\",\n\t\tEmail:               \"sso@example.com\",\n\t\tCustomerID:          \"cust_sso\",\n\t\tCustomerName:        \"SSO Corp\",\n\t\tAccessToken:         \"at_123\",\n\t\tAccessTokenCreated:  created,\n\t\tAccessTokenTTL:      3600,\n\t\tRefreshToken:        \"rt_456\",\n\t\tRefreshTokenCreated: created,\n\t\tRefreshTokenTTL:     86400,\n\t}\n\n\ttok := profileToAuthToken(p)\n\n\tif tok.Type != AuthTokenTypeSSO {\n\t\tt.Fatalf(\"expected type %q, got %q\", AuthTokenTypeSSO, tok.Type)\n\t}\n\tif tok.Label != \"SSO Corp (sso@example.com)\" {\n\t\tt.Fatalf(\"expected label %q, got %q\", \"SSO Corp (sso@example.com)\", tok.Label)\n\t}\n\tif tok.AccountID != \"cust_sso\" {\n\t\tt.Fatalf(\"expected account_id %q, got %q\", \"cust_sso\", tok.AccountID)\n\t}\n\tif tok.AccessToken != \"at_123\" {\n\t\tt.Fatalf(\"expected access_token %q, got %q\", \"at_123\", tok.AccessToken)\n\t}\n\tif tok.RefreshToken != \"rt_456\" {\n\t\tt.Fatalf(\"expected refresh_token %q, got %q\", \"rt_456\", tok.RefreshToken)\n\t}\n\tif tok.AccessExpiresAt == \"\" {\n\t\tt.Fatal(\"expected access_expires_at to be set\")\n\t}\n\tif tok.RefreshExpiresAt == \"\" {\n\t\tt.Fatal(\"expected refresh_expires_at to be set\")\n\t}\n}\n\nfunc TestProfileToAuthToken_SSOPartialFields(t *testing.T) {\n\tt.Parallel()\n\n\t// Only AccessToken is set, no timestamps -- should still be classified as SSO\n\t// but without computed expiry.\n\tp := &Profile{\n\t\tToken:       \"partial_tok\",\n\t\tEmail:       \"partial@example.com\",\n\t\tAccessToken: \"at_partial\",\n\t}\n\n\ttok := profileToAuthToken(p)\n\n\tif tok.Type != AuthTokenTypeSSO {\n\t\tt.Fatalf(\"expected type %q, got %q\", AuthTokenTypeSSO, tok.Type)\n\t}\n\tif tok.AccessExpiresAt != \"\" {\n\t\tt.Fatalf(\"expected empty access_expires_at when created/ttl are zero, got %q\", tok.AccessExpiresAt)\n\t}\n\tif tok.RefreshExpiresAt != \"\" {\n\t\tt.Fatalf(\"expected empty refresh_expires_at when created/ttl are zero, got %q\", tok.RefreshExpiresAt)\n\t}\n\tif tok.NeedsReauth {\n\t\tt.Fatal(\"expected NeedsReauth to be false when RefreshExpiresAt is empty\")\n\t}\n}\n\nfunc TestMigrateProfilesToAuth_MixedPartial(t *testing.T) {\n\tt.Parallel()\n\n\texisting := &AuthToken{\n\t\tType:  AuthTokenTypeStatic,\n\t\tToken: \"existing_tok\",\n\t}\n\n\tf := &File{\n\t\tAuth: Auth{\n\t\t\tDefault: \"existing\",\n\t\t\tTokens: AuthTokens{\n\t\t\t\t\"existing\": existing,\n\t\t\t},\n\t\t},\n\t\tProfiles: Profiles{\n\t\t\t\"existing\": {\n\t\t\t\tToken: \"should_be_skipped\",\n\t\t\t\tEmail: \"skipped@example.com\",\n\t\t\t},\n\t\t\t\"new-profile\": {\n\t\t\t\tToken:   \"new_tok\",\n\t\t\t\tEmail:   \"new@example.com\",\n\t\t\t\tDefault: true,\n\t\t\t},\n\t\t},\n\t}\n\n\tmigrated := f.MigrateProfilesToAuth()\n\tif !migrated {\n\t\tt.Fatal(\"expected migration for the new profile\")\n\t}\n\n\t// The existing token should be untouched.\n\tif f.Auth.Tokens[\"existing\"].Token != \"existing_tok\" {\n\t\tt.Fatal(\"existing token was overwritten during partial migration\")\n\t}\n\n\t// The new profile should be migrated.\n\tnewTok := f.Auth.Tokens[\"new-profile\"]\n\tif newTok == nil {\n\t\tt.Fatal(\"expected 'new-profile' to be migrated\")\n\t}\n\tif newTok.Token != \"new_tok\" {\n\t\tt.Fatalf(\"expected token %q, got %q\", \"new_tok\", newTok.Token)\n\t}\n\n\t// Default should remain \"existing\" because it was already set, even though\n\t// new-profile has Default=true in the profile. The migration only sets\n\t// Auth.Default when Auth.Default is empty.\n\tif f.Auth.Default != \"existing\" {\n\t\tt.Fatalf(\"expected default to remain %q, got %q\", \"existing\", f.Auth.Default)\n\t}\n\n\tif len(f.Auth.Tokens) != 2 {\n\t\tt.Fatalf(\"expected 2 tokens, got %d\", len(f.Auth.Tokens))\n\t}\n}\n"
  },
  {
    "path": "pkg/config/testdata/config-current.toml",
    "content": "config_version = 2\n\n[fastly]\napi_endpoint = \"https://api.fastly.com\"\n\n[cli]\nversion = \"0.0.0\" # this matches the dev version\n"
  },
  {
    "path": "pkg/config/testdata/config-incompatible-config-version.toml",
    "content": "config_version = 0 # we expect the embedded config to be >= 1\n\n[fastly]\napi_endpoint = \"https://api.fastly.com\"\n\n[cli]\nremote_config = \"https://developer.fastly.com/api/internal/cli-config\"\nttl = \"5m\"\nlast_checked = \"2021-06-18T15:13:34+01:00\"\nversion = \"0.0.1\"\n\n[language]\n[language.rust]\n# we're missing the 'toolchain_constraint' property\nwasm_wasi_target = \"wasm32-wasip1\"\n\n[starter-kits]\n[[starter-kits.rust]]\nname = \"Default\"\npath = \"https://github.com/fastly/compute-starter-kit-rust-default.git\"\nbranch = \"0.7\"\n[[starter-kits.rust]]\nname = \"Beacon\"\npath = \"https://github.com/fastly/compute-starter-kit-rust-beacon-termination.git\"\n[[starter-kits.rust]]\nname = \"Static\"\npath = \"https://github.com/fastly/compute-starter-kit-rust-static-content.git\"\n"
  },
  {
    "path": "pkg/config/testdata/config-invalid.toml",
    "content": "[fastly]\napi_endpoint = \"https://api.fastly.com # missing end quote\n"
  },
  {
    "path": "pkg/config/testdata/config-legacy.toml",
    "content": "[fastly]\napi_endpoint = \"https://api.fastly.com\"\n\n[user]\n  email = \"testing@fastly.com\"\n  token = \"foobar\"\n"
  },
  {
    "path": "pkg/config/testdata/config-outdated-cli-version.toml",
    "content": "config_version = 2\n\n[fastly]\napi_endpoint = \"https://api.fastly.com\"\n\n[cli]\nversion = \"1.2.3\"\n"
  },
  {
    "path": "pkg/config/testdata/config.toml",
    "content": "config_version = 1\n\n[fastly]\napi_endpoint = \"https://api.fastly.com\"\n\n[cli]\nremote_config = \"https://developer.fastly.com/api/internal/cli-config\"\nttl = \"5m\"\nlast_checked = \"2021-06-18T15:13:34+01:00\"\nversion = \"0.0.1\"\n\n[language]\n  [language.rust]\n  toolchain_constraint = \">= 1.78.0\"\n  wasm_wasi_target = \"wasm32-wasip1\"\n\n[starter-kits]\n[[starter-kits.javascript]]\nname = \"Default\"\ndescription = \"A basic starter kit that demonstrates routing and simple synthetic responses.\"\npath = \"https://github.com/fastly/compute-starter-kit-javascript-default\"\n[[starter-kits.rust]]\nname = \"Default\"\ndescription = \"A basic starter kit that demonstrates routing, simple synthetic responses and overriding caching rules.\"\npath = \"https://github.com/fastly/compute-starter-kit-rust-default\"\n[[starter-kits.rust]]\nname = \"Beacon\"\ndescription = \"Capture beacon data from the browser, divert beacon request payloads to a log endpoint, and avoid putting load on your own infrastructure.\"\npath = \"https://github.com/fastly/compute-starter-kit-rust-beacon-termination\"\n[[starter-kits.rust]]\nname = \"Static\"\ndescription = \"Apply performance, security and usability upgrades to static bucket services such as Google Cloud Storage or AWS S3.\"\npath = \"https://github.com/fastly/compute-starter-kit-rust-static-content\"\n"
  },
  {
    "path": "pkg/config/testdata/static/config-invalid.toml",
    "content": "[fastly]\napi_endpoint = \"https://api.fastly.com # missing end quote\n"
  },
  {
    "path": "pkg/config/testdata/static/config.toml",
    "content": "config_version = 1\n\n[fastly]\napi_endpoint = \"https://api.fastly.com\"\n\n[cli]\nremote_config = \"https://developer.fastly.com/api/internal/cli-config\"\nttl = \"5m\"\n\n[language]\n[language.rust]\ntoolchain_constraint = \">= 1.49.0 < 2.0.0\"\nwasm_wasi_target = \"wasm32-wasip1\"\n\n[starter-kits]\n[[starter-kits.javascript]]\nname = \"Default\"\ndescription = \"A basic starter kit that demonstrates routing and simple synthetic responses.\"\npath = \"https://github.com/fastly/compute-starter-kit-javascript-default\"\n[[starter-kits.rust]]\nname = \"Default\"\ndescription = \"A basic starter kit that demonstrates routing, simple synthetic responses and overriding caching rules.\"\npath = \"https://github.com/fastly/compute-starter-kit-rust-default\"\n[[starter-kits.rust]]\nname = \"Beacon\"\ndescription = \"Capture beacon data from the browser, divert beacon request payloads to a log endpoint, and avoid putting load on your own infrastructure.\"\npath = \"https://github.com/fastly/compute-starter-kit-rust-beacon-termination\"\n[[starter-kits.rust]]\nname = \"Static\"\ndescription = \"Apply performance, security and usability upgrades to static bucket services such as Google Cloud Storage or AWS S3.\"\npath = \"https://github.com/fastly/compute-starter-kit-rust-static-content\"\n"
  },
  {
    "path": "pkg/debug/debug.go",
    "content": "package debug\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"net/http/httputil\"\n)\n\n// PrintStruct pretty prints the given struct.\nfunc PrintStruct(v any) error {\n\tb, err := json.MarshalIndent(v, \"\", \"  \")\n\tif err == nil {\n\t\tfmt.Println(string(b))\n\t}\n\treturn err\n}\n\n// DumpHTTPRequest dumps the HTTP network request if --debug-mode is set.\nfunc DumpHTTPRequest(r *http.Request) {\n\treq := r.Clone(context.Background())\n\tif req.Header.Get(\"Fastly-Key\") != \"\" {\n\t\treq.Header.Set(\"Fastly-Key\", \"REDACTED\")\n\t}\n\tdump, _ := httputil.DumpRequest(r, true)\n\tfmt.Printf(\"\\n\\nhttp.Request (dump): %q\\n\\n\", dump)\n}\n\n// DumpHTTPResponse dumps the HTTP network response if --debug-mode is set.\nfunc DumpHTTPResponse(r *http.Response) {\n\tif r != nil {\n\t\tdump, _ := httputil.DumpResponse(r, true)\n\t\tfmt.Printf(\"\\n\\nhttp.Response (dump): %q\\n\\n\", dump)\n\t}\n}\n"
  },
  {
    "path": "pkg/debug/doc.go",
    "content": "// Package debug contains functions to ease development of the Fastly CLI.\npackage debug\n"
  },
  {
    "path": "pkg/env/doc.go",
    "content": "// Package env contains environment variable constants.\npackage env\n"
  },
  {
    "path": "pkg/env/env.go",
    "content": "package env\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/fastly/cli/pkg/runtime\"\n)\n\nconst (\n\t// AccountEndpoint is the env var we look in for the Accounts endpoint.\n\t// e.g. https://accounts.fastly.com\n\tAccountEndpoint = \"FASTLY_ACCOUNT_ENDPOINT\"\n\n\t// APIEndpoint is the env var we look in for the API endpoint.\n\t// e.g. https://api.fastly.com\n\tAPIEndpoint = \"FASTLY_API_ENDPOINT\"\n\n\t// APIToken is the env var we look in for the Fastly API token.\n\t// gosec flagged this:\n\t// G101 (CWE-798): Potential hardcoded credentials\n\t// Disabling as we use the value in the command help output.\n\t// #nosec\n\tAPIToken = \"FASTLY_API_TOKEN\"\n\n\t// CustomerID is the env var we look in for a Customer ID.\n\tCustomerID = \"FASTLY_CUSTOMER_ID\"\n\n\t// DebugMode indicates to the CLI it can display debug information.\n\t// Set to \"true\" to enable debug mode.\n\tDebugMode = \"FASTLY_DEBUG_MODE\"\n\n\t// ServiceID is the env var we look in for the required Service ID.\n\tServiceID = \"FASTLY_SERVICE_ID\"\n\n\t// UseSSO enables the CLI to validate the token as an OAuth token.\n\t// These tokens aren't traditional tokens generated by the UI.\n\t// Instead they generated via an OAuth flow (producing access/refresh tokens).\n\t// Assigned value should be a boolean 1/0 (enable/disable).\n\tUseSSO = \"FASTLY_USE_SSO\"\n\n\t// UserAgentExtension informs the CLI of an additional string\n\t// which should be added to the UserAgent included in\n\t// requests made by the CLI.\n\tUserAgentExtension = \"FASTLY_USER_AGENT_EXTENSION\"\n\n\t// WasmMetadataDisable is the env var we look in to disable all data\n\t// collection related to a Wasm binary.\n\t// Set to \"true\" to disable all forms of data collection.\n\tWasmMetadataDisable = \"FASTLY_WASM_METADATA_DISABLE\"\n\n\t// WorkspaceID is the env we look for in Workspace related commands if none is provided.\n\tWorkspaceID = \"FASTLY_WORKSPACE_ID\"\n\n\t// DisableAuthCommand hides all authentication-related commands (auth,\n\t// auth-token, sso, profile, whoami) and the --token flag when set.\n\tDisableAuthCommand = \"FASTLY_DISABLE_AUTH_COMMAND\"\n)\n\n// AuthCommandDisabled reports whether FASTLY_DISABLE_AUTH_COMMAND is set to a\n// non-empty value.\nfunc AuthCommandDisabled() bool {\n\treturn os.Getenv(DisableAuthCommand) != \"\"\n}\n\n// Parse transforms the local environment data structure into a map type.\nfunc Parse(environ []string) map[string]string {\n\tenv := map[string]string{}\n\tfor _, kv := range environ {\n\t\tk, v, ok := strings.Cut(kv, \"=\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tenv[k] = v\n\t}\n\treturn env\n}\n\n// Vars returns a slice of environment variables appropriate to platform.\n// *nix: $HOME, $USER, ...\n// Windows: %HOME%, %USER%, ...\nfunc Vars() []string {\n\tvars := []string{}\n\tfor _, e := range os.Environ() {\n\t\tpair := strings.SplitN(e, \"=\", 2)\n\t\tvars = append(vars, toVar(pair[0]))\n\t}\n\treturn vars\n}\n\nfunc toVar(v string) string {\n\tif runtime.Windows {\n\t\treturn toWin(v)\n\t}\n\treturn toNix(v)\n}\n\nfunc toNix(v string) string {\n\treturn fmt.Sprintf(\"\\\\$%s\", v)\n}\n\nfunc toWin(v string) string {\n\treturn fmt.Sprintf(\"%%%s%%\", v)\n}\n"
  },
  {
    "path": "pkg/env/env_test.go",
    "content": "package env\n\nimport (\n\t\"runtime\"\n\t\"testing\"\n\n\t\"golang.org/x/exp/slices\"\n)\n\nfunc TestVars(t *testing.T) {\n\ttcs := []struct {\n\t\tos       string\n\t\tvars     map[string]string\n\t\texpected []string\n\t}{\n\t\t{\n\t\t\tos:       \"windows\",\n\t\t\texpected: []string{\"%HOME%\", \"%PATH%\"},\n\t\t},\n\t\t{\n\t\t\tos:       \"darwin\",\n\t\t\texpected: []string{\"\\\\$HOME\", \"\\\\$PATH\"},\n\t\t},\n\t\t{\n\t\t\tos:       \"linux\",\n\t\t\texpected: []string{\"\\\\$HOME\", \"\\\\$PATH\"},\n\t\t},\n\t}\n\tfor _, tc := range tcs {\n\t\tt.Run(tc.os, func(t *testing.T) {\n\t\t\tvars := Vars()\n\t\t\tif runtime.GOOS == tc.os {\n\t\t\t\tfor _, v := range tc.expected {\n\t\t\t\t\tif !slices.Contains(vars, v) {\n\t\t\t\t\t\tt.Errorf(\"expected %s in %v\", v, vars)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tt.Skip()\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/errors/deduce.go",
    "content": "package errors\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n)\n\n// httpStatusError is satisfied by any error that carries an HTTP status code.\n// This avoids importing concrete types (like undocumented.APIError) and the\n// import cycles that would create.\ntype httpStatusError interface {\n\terror\n\tHTTPStatusCode() int\n}\n\n// Deduce attempts to deduce a RemediationError from a plain error. If the error\n// is already a RemediationError it is returned directly. Certain deep error\n// types, like a Fastly SDK HTTPError, are detected and converted in appropriate\n// cases to e.g. AuthRemediation. If no specific remediation can be suggested, a\n// remediation to file a bug is used.\nfunc Deduce(err error) RemediationError {\n\tvar re RemediationError\n\tif errors.As(err, &re) {\n\t\treturn re // assume the useful suggestion is already baked-in\n\t}\n\n\tvar httpError *fastly.HTTPError\n\tif errors.As(err, &httpError) {\n\t\tremediation := BugRemediation\n\t\tswitch httpError.StatusCode {\n\t\tcase http.StatusUnauthorized:\n\t\t\tremediation = AuthRemediation()\n\t\tcase http.StatusForbidden:\n\t\t\tremediation = ForbiddenRemediation()\n\t\t}\n\t\treturn RemediationError{Inner: SimplifyFastlyError(*httpError), Remediation: remediation}\n\t}\n\n\tvar statusErr httpStatusError\n\tif errors.As(err, &statusErr) {\n\t\tremediation := BugRemediation\n\t\tswitch statusErr.HTTPStatusCode() {\n\t\tcase http.StatusUnauthorized:\n\t\t\tremediation = AuthRemediation()\n\t\tcase http.StatusForbidden:\n\t\t\tremediation = ForbiddenRemediation()\n\t\t}\n\t\treturn RemediationError{Inner: err, Remediation: remediation}\n\t}\n\n\tif errors.Is(err, os.ErrNotExist) {\n\t\treturn RemediationError{Inner: err, Remediation: HostRemediation}\n\t}\n\n\tif t, ok := err.(interface{ Temporary() bool }); ok && t.Temporary() {\n\t\treturn RemediationError{Inner: err, Remediation: NetworkRemediation}\n\t}\n\n\treturn RemediationError{Inner: err, Remediation: BugRemediation}\n}\n\n// SimplifyFastlyError reduces the potentially complex and multi-line Error\n// rendering of a fastly.HTTPError to something more palatable for a CLI.\nfunc SimplifyFastlyError(httpError fastly.HTTPError) error {\n\tswitch len(httpError.Errors) {\n\tcase 1:\n\t\ts := fmt.Sprintf(\n\t\t\t\"the Fastly API returned %d %s: %s\",\n\t\t\thttpError.StatusCode,\n\t\t\thttp.StatusText(httpError.StatusCode),\n\t\t\tstrings.TrimSpace(httpError.Errors[0].Title),\n\t\t)\n\t\tif detail := httpError.Errors[0].Detail; detail != \"\" {\n\t\t\ts += fmt.Sprintf(\" (%s)\", detail)\n\t\t}\n\t\treturn errors.New(s)\n\n\tdefault:\n\t\treturn fmt.Errorf(\n\t\t\t\"the Fastly API returned %d %s\",\n\t\t\thttpError.StatusCode,\n\t\t\thttp.StatusText(httpError.StatusCode),\n\t\t)\n\t}\n}\n"
  },
  {
    "path": "pkg/errors/deduce_test.go",
    "content": "package errors_test\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/fastly/cli/pkg/api/undocumented\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n)\n\nfunc TestDeduce(t *testing.T) {\n\tvar (\n\t\tre1             = errors.RemediationError{Inner: fmt.Errorf(\"foo\")}\n\t\tre2             = errors.RemediationError{Inner: fmt.Errorf(\"bar\"), Remediation: \"Reticulate your splines.\"}\n\t\thttp503         = &fastly.HTTPError{StatusCode: http.StatusInternalServerError}\n\t\thttp401         = &fastly.HTTPError{StatusCode: http.StatusUnauthorized}\n\t\thttp403         = &fastly.HTTPError{StatusCode: http.StatusForbidden}\n\t\twrappedNotExist = fmt.Errorf(\"couldn't do the thing: %w\", os.ErrNotExist)\n\t\tundoc401        = undocumented.NewError(fmt.Errorf(\"error response\"), http.StatusUnauthorized)\n\t\tundoc403        = undocumented.NewError(fmt.Errorf(\"error response\"), http.StatusForbidden)\n\t\tundoc500        = undocumented.NewError(fmt.Errorf(\"error response\"), http.StatusInternalServerError)\n\t\twrappedUndoc401 = fmt.Errorf(\"call failed: %w\", undoc401)\n\t)\n\n\tfor _, testcase := range []struct {\n\t\tname  string\n\t\tinput error\n\t\twant  errors.RemediationError\n\t}{\n\t\t{\n\t\t\tname:  \"RemediationError with no remediation\",\n\t\t\tinput: re1,\n\t\t\twant:  re1,\n\t\t},\n\t\t{\n\t\t\tname:  \"RemediationError with remediation\",\n\t\t\tinput: re2,\n\t\t\twant:  re2,\n\t\t},\n\t\t{\n\t\t\tname:  \"fastly.HTTPError 503\",\n\t\t\tinput: http503,\n\t\t\twant:  errors.RemediationError{Inner: errors.SimplifyFastlyError(*http503), Remediation: errors.BugRemediation},\n\t\t},\n\t\t{\n\t\t\tname:  \"fastly.HTTPError 401\",\n\t\t\tinput: http401,\n\t\t\twant:  errors.RemediationError{Inner: errors.SimplifyFastlyError(*http401), Remediation: errors.AuthRemediation()},\n\t\t},\n\t\t{\n\t\t\tname:  \"fastly.HTTPError 403\",\n\t\t\tinput: http403,\n\t\t\twant:  errors.RemediationError{Inner: errors.SimplifyFastlyError(*http403), Remediation: errors.ForbiddenRemediation()},\n\t\t},\n\t\t{\n\t\t\tname:  \"undocumented APIError 401\",\n\t\t\tinput: undoc401,\n\t\t\twant:  errors.RemediationError{Inner: undoc401, Remediation: errors.AuthRemediation()},\n\t\t},\n\t\t{\n\t\t\tname:  \"undocumented APIError 403\",\n\t\t\tinput: undoc403,\n\t\t\twant:  errors.RemediationError{Inner: undoc403, Remediation: errors.ForbiddenRemediation()},\n\t\t},\n\t\t{\n\t\t\tname:  \"undocumented APIError 500\",\n\t\t\tinput: undoc500,\n\t\t\twant:  errors.RemediationError{Inner: undoc500, Remediation: errors.BugRemediation},\n\t\t},\n\t\t{\n\t\t\tname:  \"wrapped undocumented APIError 401\",\n\t\t\tinput: wrappedUndoc401,\n\t\t\twant:  errors.RemediationError{Inner: wrappedUndoc401, Remediation: errors.AuthRemediation()},\n\t\t},\n\t\t{\n\t\t\tname:  \"wrapped os.ErrNotExist\",\n\t\t\tinput: wrappedNotExist,\n\t\t\twant:  errors.RemediationError{Inner: wrappedNotExist, Remediation: errors.HostRemediation},\n\t\t},\n\t\t{\n\t\t\tname:  \"temporary network error\",\n\t\t\tinput: isTemporary{fmt.Errorf(\"baz\")},\n\t\t\twant:  errors.RemediationError{Inner: fmt.Errorf(\"baz\"), Remediation: errors.NetworkRemediation},\n\t\t},\n\t} {\n\t\tt.Run(testcase.name, func(t *testing.T) {\n\t\t\thave := errors.Deduce(testcase.input)\n\t\t\ttestutil.AssertString(t, testcase.want.Error(), have.Error())\n\t\t\ttestutil.AssertString(t, testcase.want.Remediation, have.Remediation)\n\t\t})\n\t}\n}\n\ntype isTemporary struct{ error }\n\nfunc (isTemporary) Temporary() bool { return true }\n"
  },
  {
    "path": "pkg/errors/doc.go",
    "content": "// Package errors contains functions to handle Fastly error types.\npackage errors\n"
  },
  {
    "path": "pkg/errors/errors.go",
    "content": "package errors\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n)\n\n// ErrSignalInterrupt means a SIGINT was received.\nvar ErrSignalInterrupt = fmt.Errorf(\"a SIGINT was received\")\n\n// ErrSignalKilled means a SIGTERM was received.\nvar ErrSignalKilled = fmt.Errorf(\"a SIGTERM was received\")\n\n// ErrViceroyRestart means the viceroy binary needs to be restarted due to a\n// file modification noticed while running `compute serve --watch`.\nvar ErrViceroyRestart = fmt.Errorf(\"a RESTART was initiated\")\n\n// ErrDontContinue means the user said \"NO\" when prompted whether to continue.\nvar ErrDontContinue = fmt.Errorf(\"will not continue\")\n\n// ErrIncompatibleServeFlags means no --skip-build can't be used with --watch\n// because it defeats the purpose of --watch which is designed to restart\n// Viceroy whenever changes are detected (those changes would not be seen if we\n// allowed --skip-build with --watch).\nvar ErrIncompatibleServeFlags = RemediationError{\n\tInner:       fmt.Errorf(\"--skip-build shouldn't be used with --watch\"),\n\tRemediation: ComputeServeRemediation,\n}\n\n// ErrNoToken returns a RemediationError for when no --token has been provided.\nfunc ErrNoToken() RemediationError {\n\treturn RemediationError{\n\t\tInner:       fmt.Errorf(\"no token provided\"),\n\t\tRemediation: AuthRemediation(),\n\t}\n}\n\n// ErrNonInteractiveNoToken returns an error indicating no token is available\n// and the session cannot prompt interactively (e.g. --auto-yes or --accept-defaults).\nfunc ErrNonInteractiveNoToken() RemediationError {\n\treturn RemediationError{\n\t\tInner:       fmt.Errorf(\"no token provided\"),\n\t\tRemediation: NonInteractiveAuthRemediation(),\n\t}\n}\n\n// ErrNoServiceID means no --service-id or service_id fastly.toml value has\n// been provided.\nvar ErrNoServiceID = RemediationError{\n\tInner:       fmt.Errorf(\"error reading service: no service ID found\"),\n\tRemediation: ServiceIDRemediation,\n}\n\n// ErrNoCustomerID means no --customer-id or FASTLY_CUSTOMER_ID environment\n// variable found.\nvar ErrNoCustomerID = RemediationError{\n\tInner:       fmt.Errorf(\"error reading customer ID: no customer ID found\"),\n\tRemediation: CustomerIDRemediation,\n}\n\n// ErrNoWorkspaceID means no --workspace-id or FASTLY_WORKSPACE_ID environment\n// variable found.\nvar ErrNoWorkspaceID = RemediationError{\n\tInner:       fmt.Errorf(\"error reading workspace ID: no workspace ID found\"),\n\tRemediation: WorkspaceIDRemediation,\n}\n\n// ErrMissingManifestVersion means an invalid manifest (fastly.toml) has been used.\nvar ErrMissingManifestVersion = RemediationError{\n\tInner:       fmt.Errorf(\"no manifest_version found in the fastly.toml\"),\n\tRemediation: BugRemediation,\n}\n\n// ErrUnrecognisedManifestVersion means an invalid manifest (fastly.toml)\n// version has been specified.\nvar ErrUnrecognisedManifestVersion = RemediationError{\n\tInner:       fmt.Errorf(\"unrecognised manifest_version found in the fastly.toml\"),\n\tRemediation: UnrecognisedManifestVersionRemediation,\n}\n\n// ErrIncompatibleManifestVersion means the manifest_version defined is no\n// longer compatible with the current CLI version.\nvar ErrIncompatibleManifestVersion = RemediationError{\n\tInner:       fmt.Errorf(\"the fastly.toml contains an incompatible manifest_version number\"),\n\tRemediation: \"Update the `manifest_version` in the fastly.toml and refer to https://www.fastly.com/documentation/reference/compute/fastly-toml for changes to the manifest structure\",\n}\n\n// ErrNoID means no --id value has been provided.\nvar ErrNoID = RemediationError{\n\tInner:       fmt.Errorf(\"no ID found\"),\n\tRemediation: IDRemediation,\n}\n\n// ErrReadingManifest means there was a problem reading the fastly.toml.\nvar ErrReadingManifest = RemediationError{\n\tInner:       fmt.Errorf(\"error reading fastly.toml: file not found\"),\n\tRemediation: \"Ensure the Fastly CLI is being run within a directory containing a fastly.toml file. \" + ComputeInitRemediation,\n}\n\n// ErrParsingManifest means there was a problem unmarshalling the fastly.toml.\nvar ErrParsingManifest = RemediationError{\n\tInner:       fmt.Errorf(\"error parsing fastly.toml\"),\n\tRemediation: ComputeInitRemediation,\n}\n\n// ErrStopWalk is used to indicate to filepath.WalkDir that it should stop\n// walking the directory tree.\nvar ErrStopWalk = errors.New(\"stop directory walking\")\n\n// ErrInvalidArchive means the package archive didn't contain a recognised\n// directory structure.\nvar ErrInvalidArchive = RemediationError{\n\tInner:       fmt.Errorf(\"invalid package archive structure\"),\n\tRemediation: \"Ensure the archive contains all required package files (such as a 'fastly.toml' manifest, and a 'src' folder etc).\",\n}\n\n// ErrPostInitStopped means the user stopped the init process because they were\n// unhappy with the custom post_init defined in the fastly.toml manifest file.\nvar ErrPostInitStopped = RemediationError{\n\tInner:       fmt.Errorf(\"init process stopped by user\"),\n\tRemediation: \"Check the [scripts.post_init] in the fastly.toml manifest is safe to execute or skip this prompt using either `--auto-yes` or `--non-interactive`.\",\n}\n\n// ErrPostBuildStopped means the user stopped the build because they were unhappy\n// with the custom build defined in the fastly.toml manifest file.\nvar ErrPostBuildStopped = RemediationError{\n\tInner:       fmt.Errorf(\"build process stopped by user\"),\n\tRemediation: \"Check the [scripts.post_build] in the fastly.toml manifest is safe to execute or skip this prompt using either `--auto-yes` or `--non-interactive`.\",\n}\n\n// ErrInvalidContentOutputCombo means the user provided --content along with the\n// --verbose or --json flags, which are mutually exclusive behaviours.\nvar ErrInvalidContentOutputCombo = RemediationError{\n\tInner:       fmt.Errorf(\"invalid flag combination, --content cannot be used together with --json or --verbose\"),\n\tRemediation: \"Use either --content, --verbose or --json separately.\",\n}\n\n// ErrInvalidVerboseJSONCombo means the user provided both a --verbose and\n// --json flag which are mutually exclusive behaviours.\nvar ErrInvalidVerboseJSONCombo = RemediationError{\n\tInner:       fmt.Errorf(\"invalid flag combination, --verbose and --json\"),\n\tRemediation: \"Use either --verbose or --json, not both.\",\n}\n\n// ErrInvalidDeleteAllJSONKeyCombo means the user provided both a --all and\n// --json flag which are mutually exclusive behaviours.\nvar ErrInvalidDeleteAllJSONKeyCombo = RemediationError{\n\tInner:       fmt.Errorf(\"invalid flag combination, --all and --json\"),\n\tRemediation: \"Use either --all or --json, not both.\",\n}\n\n// ErrInvalidDeleteAllKeyCombo means the user provided both a --all and --key\n// flag which are mutually exclusive behaviours.\nvar ErrInvalidDeleteAllKeyCombo = RemediationError{\n\tInner:       fmt.Errorf(\"invalid flag combination, --all and --key\"),\n\tRemediation: \"Use either --all or --key, not both.\",\n}\n\n// ErrMissingDeleteAllKeyCombo means the user omitted both the --all and --key\n// flags and we need at least one of them.\nvar ErrMissingDeleteAllKeyCombo = RemediationError{\n\tInner:       fmt.Errorf(\"invalid command, neither --all or --key provided\"),\n\tRemediation: \"Provide at least one of: --all or --key, not both.\",\n}\n\n// ErrNoSTDINData indicates the --stdin flag was specified but no data was piped\n// into stdin.\nvar ErrNoSTDINData = RemediationError{\n\tInner:       fmt.Errorf(\"unable to read from STDIN\"),\n\tRemediation: \"Provide data to STDIN, or use --file to read from a file\",\n}\n\n// ErrInvalidKVCombo means the user omitted either the key or value flag.\nvar ErrInvalidKVCombo = RemediationError{\n\tInner:       fmt.Errorf(\"--key and --value are required\"),\n\tRemediation: \"Please add both flags or alternatively use either --stdin or --file.\",\n}\n\n// ErrInvalidStdinFileDirCombo means the user provided more than one of --stdin,\n// --file or --dir flags, which are mutually exclusive behaviours.\nvar ErrInvalidStdinFileDirCombo = RemediationError{\n\tInner:       fmt.Errorf(\"invalid flag combination\"),\n\tRemediation: \"Use only one of --stdin, --file or --dir.\",\n}\n\n// ErrInvalidProfileSSOCombo means the user specified both --sso and\n// --automation-token and only one should be set.\nvar ErrInvalidProfileSSOCombo = RemediationError{\n\tInner:       fmt.Errorf(\"invalid command, both --sso and --automation-token provided\"),\n\tRemediation: \"Provide at only one of: --sso or --automation-token, not both.\",\n}\n\n// ErrInvalidEnableDisableFlagCombo means the user provided both a --enable\n// and --disable flag which are mutually exclusive behaviours.\nvar ErrInvalidEnableDisableFlagCombo = RemediationError{\n\tInner:       fmt.Errorf(\"invalid flag combination: --enable and --disable\"),\n\tRemediation: \"Use either --enable or --disable, not both.\",\n}\n\n// ErrInvalidComputeACLCombo means the user omitted either the operation, prefix, or action flag.\nvar ErrInvalidComputeACLCombo = RemediationError{\n\tInner:       fmt.Errorf(\"--operation, --prefix, and --action are required\"),\n\tRemediation: \"Please add all three flags or or alternatively use --file.\",\n}\n\n// ErrInvalidComputeACLCombo means the user omitted either the operation, prefix, or action flag.\nvar ErrInvalidNGWAFScopeType = RemediationError{\n\tInner:       fmt.Errorf(\"--scope must be either `account` or `workspace`\"),\n\tRemediation: \"please set the account flag to either `account` or `workspace`.\",\n}\n"
  },
  {
    "path": "pkg/errors/exit_error.go",
    "content": "package errors\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// SkipExitError is an error that can cause the os.Exit(1) to be skipped.\n// An example is 'help' output (e.g. --help).\ntype SkipExitError struct {\n\tSkip bool\n\tErr  error\n}\n\n// Unwrap returns the inner error.\nfunc (ee SkipExitError) Unwrap() error {\n\treturn ee.Err\n}\n\n// Error prints the inner error string.\nfunc (ee SkipExitError) Error() string {\n\tif ee.Err == nil {\n\t\treturn \"\"\n\t}\n\treturn ee.Err.Error()\n}\n\n// Print the error to the io.Writer for human consumption.\n// The inner error is always printed via text.Output with an \"Error: \" prefix\n// and a \".\" suffix.\nfunc (ee SkipExitError) Print(w io.Writer) {\n\tif ee.Err != nil {\n\t\ttext.Error(w, \"%s.\", ee.Err.Error())\n\t}\n}\n"
  },
  {
    "path": "pkg/errors/log.go",
    "content": "package errors\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"regexp\"\n\t\"runtime\"\n\t\"strings\"\n\t\"sync\"\n\t\"text/template\"\n\t\"time\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n)\n\n// LogPath is the location of the fastly CLI error log.\nvar LogPath = func() string {\n\tif dir, err := os.UserConfigDir(); err == nil {\n\t\treturn filepath.Join(dir, \"fastly\", \"errors.log\")\n\t}\n\tif dir, err := os.UserHomeDir(); err == nil {\n\t\treturn filepath.Join(dir, \".fastly\", \"errors.log\")\n\t}\n\tpanic(\"unable to deduce user config dir or user home dir\")\n}()\n\n// LogInterface represents the LogEntries behaviours.\ntype LogInterface interface {\n\tAdd(err error)\n\tAddWithContext(err error, ctx map[string]any)\n\tPersist(logPath string, args []string) error\n}\n\n// MockLog is a no-op Log type.\ntype MockLog struct{}\n\n// Add adds an error to the mock log.\nfunc (ml MockLog) Add(_ error) {}\n\n// AddWithContext adds an error and context to the mock log.\nfunc (ml MockLog) AddWithContext(_ error, _ map[string]any) {}\n\n// Persist writes the error data to logPath.\nfunc (ml MockLog) Persist(_ string, _ []string) error {\n\treturn nil\n}\n\n// Log is the primary interface for consumers.\nvar Log = new(LogEntries)\n\n// LogEntries represents a list of recorded log entries.\ntype LogEntries []LogEntry\n\n// Add adds a new log entry.\nfunc (l *LogEntries) Add(err error) {\n\tlogMutex.Lock()\n\t*l = append(*l, createLogEntry(err))\n\tlogMutex.Unlock()\n}\n\n// AddWithContext adds a new log entry with extra contextual data.\nfunc (l *LogEntries) AddWithContext(err error, ctx map[string]any) {\n\tle := createLogEntry(err)\n\tle.Context = ctx\n\n\tlogMutex.Lock()\n\t*l = append(*l, le)\n\tlogMutex.Unlock()\n}\n\n// Persist persists recorded log entries to disk.\nfunc (l LogEntries) Persist(logPath string, args []string) error {\n\tif len(l) == 0 {\n\t\treturn nil\n\t}\n\tcmd := \"fastly \" + strings.Join(args, \" \")\n\terrMsg := \"error accessing audit log file: %w\"\n\n\t// gosec flagged this:\n\t// G304 (CWE-22): Potential file inclusion via variable\n\t//\n\t// Disabling as the input is determined from our own package.\n\t/* #nosec */\n\tf, err := os.OpenFile(logPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o600)\n\tif err != nil {\n\t\treturn fmt.Errorf(errMsg, err)\n\t}\n\n\tif fi, err := f.Stat(); err == nil {\n\t\tif fi.Size() >= FileRotationSize {\n\t\t\terr = f.Close()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\t// gosec flagged this:\n\t\t\t// G304 (CWE-22): Potential file inclusion via variable\n\t\t\t//\n\t\t\t// Disabling as the input is determined from our own package.\n\t\t\t/* #nosec */\n\t\t\tf, err = os.Create(logPath)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(errMsg, err)\n\t\t\t}\n\t\t}\n\t}\n\n\t// G307 (CWE-): Deferring unsafe method \"*os.File\" on type \"Close\".\n\t// gosec flagged this:\n\t// Disabling because this file isn't critical to the functioning of the CLI\n\t// and we only attempt to close it at the end of the user's execution flow.\n\t/* #nosec */\n\tdefer f.Close()\n\n\tcmd = \"\\nCOMMAND:\\n\" + cmd + \"\\n\\n\"\n\tif _, err := f.Write([]byte(cmd)); err != nil {\n\t\treturn err\n\t}\n\n\trecord := `TIMESTAMP:\n{{.Time}}\n\nERROR:\n{{.Err}}\n{{ range $key, $value := .Caller }}\n{{ $key }}:\n{{ $value }}\n{{ end }}\n{{ range $key, $value := .Context }}\n  {{ $key }}: {{ $value }}\n{{ end }}\n`\n\tt := template.Must(template.New(\"record\").Parse(record))\n\tfor _, entry := range l {\n\t\terr := t.Execute(f, entry)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tif _, err := f.Write([]byte(\"------------------------------\\n\\n\")); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nvar (\n\t// TokenRegEx matches a Token as part of the error output (https://regex101.com/r/ulIw1m/1)\n\tTokenRegEx = regexp.MustCompile(`Token ([\\w-]+)`)\n\t// TokenFlagRegEx matches the token flag (https://regex101.com/r/YNr78Q/1)\n\tTokenFlagRegEx = regexp.MustCompile(`(-t|--token)(\\s*=?\\s*['\"]?)([\\w-]+)(['\"]?)`)\n)\n\n// FilterToken replaces any matched patterns with \"REDACTED\".\n//\n// EXAMPLE: https://go.dev/play/p/cT4BwIh9Asa\nfunc FilterToken(input string) (inputFiltered string) {\n\tinputFiltered = TokenRegEx.ReplaceAllString(input, \"Token REDACTED\")\n\tinputFiltered = TokenFlagRegEx.ReplaceAllString(inputFiltered, \"${1}${2}REDACTED${4}\")\n\treturn inputFiltered\n}\n\n// createLogEntry generates the boilerplate of a LogEntry.\nfunc createLogEntry(err error) LogEntry {\n\tle := LogEntry{\n\t\tTime: Now(),\n\t\tErr:  err,\n\t}\n\n\t_, file, line, ok := runtime.Caller(2)\n\tif ok {\n\t\tidx := strings.Index(file, \"/pkg/\")\n\t\tif idx == -1 {\n\t\t\tidx = 0\n\t\t}\n\t\tle.Caller = map[string]any{\n\t\t\t\"FILE\": file[idx:],\n\t\t\t\"LINE\": line,\n\t\t}\n\t}\n\n\treturn le\n}\n\n// LogEntry represents a single error log entry.\ntype LogEntry struct {\n\tTime    time.Time\n\tErr     error\n\tCaller  map[string]any\n\tContext map[string]any\n}\n\n// Caller represents where an error occurred.\ntype Caller struct {\n\tFile string\n\tLine int\n}\n\n// Appending to a slice isn't threadsafe, and although we currently don't\n// expect this to be a problem we can't predict future logic requirements that\n// might result in more asynchronous operations, so we play it safe and utilise\n// a lock before updating the LogEntries.\nvar logMutex sync.Mutex\n\n// Now is exposed so that we may mock it from our test file.\n//\n// NOTE: The ideal way to deal with time is to inject it as a dependency and\n// then the caller can provide a stubbed value, but in this case we don't want\n// to have the CLI's business logic littered with lots of calls to time.Now()\n// when that call can be handled internally by the .Add() method.\nvar Now = time.Now\n\n// FileRotationSize represents the size the log file needs to be before we\n// truncate it.\n//\n// NOTE: To enable easier testing of the log rotation logic, we don't define\n// this as a constant but as a variable so the test file can mutate the value\n// to something much smaller, meaning we can commit a small test file as part\n// of the testing logic that will trigger a 'over the threshold' scenario.\nvar FileRotationSize int64 = 5242880 // 5mb\n\n// ServiceVersion returns an integer regardless of whether the given argument\n// is a nil pointer or not. It helps to reduce the boilerplate found across the\n// codebase when tracking errors related to `argparser.ServiceDetails`.\nfunc ServiceVersion(v *fastly.Version) int {\n\tvar sv int\n\tif v != nil {\n\t\tsv = fastly.ToValue(v.Number)\n\t}\n\treturn sv\n}\n"
  },
  {
    "path": "pkg/errors/log_test.go",
    "content": "package errors_test\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n)\n\nfunc TestLogAdd(t *testing.T) {\n\tle := new(errors.LogEntries)\n\tle.Add(fmt.Errorf(\"foo\"))\n\tle.Add(fmt.Errorf(\"bar\"))\n\tle.Add(fmt.Errorf(\"baz\"))\n\n\tm := make(map[string]any)\n\tm[\"beep\"] = \"boop\"\n\tm[\"this\"] = \"that\"\n\tm[\"nums\"] = 123\n\tle.AddWithContext(fmt.Errorf(\"qux\"), m)\n\n\twant := 4\n\tgot := len(*le)\n\tif got != want {\n\t\tt.Fatalf(\"want length %d, got: %d\", want, got)\n\t}\n}\n\nfunc TestLogPersist(t *testing.T) {\n\tvar path string\n\n\t// Create temp environment to run test code within.\n\t{\n\t\twd, err := os.Getwd()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\trootdir := testutil.NewEnv(testutil.EnvOpts{\n\t\t\tT: t,\n\t\t\tWrite: []testutil.FileIO{\n\t\t\t\t{Src: string(\"\"), Dst: \"errors.log\"},\n\t\t\t},\n\t\t\tCopy: []testutil.FileIO{\n\t\t\t\t{\n\t\t\t\t\tSrc: filepath.Join(\"testdata\", \"errors-expected.log\"),\n\t\t\t\t\tDst: \"errors-expected.log\",\n\t\t\t\t},\n\t\t\t},\n\t\t})\n\t\tpath = filepath.Join(rootdir, \"errors.log\")\n\t\tdefer os.RemoveAll(rootdir)\n\n\t\tif err := os.Chdir(rootdir); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tdefer func() {\n\t\t\t_ = os.Chdir(wd)\n\t\t}()\n\t}\n\n\terrors.Now = func() (t time.Time) {\n\t\treturn t\n\t}\n\n\tle := new(errors.LogEntries)\n\tle.Add(fmt.Errorf(\"foo\"))\n\tle.Add(fmt.Errorf(\"bar\"))\n\tle.Add(fmt.Errorf(\"baz\"))\n\n\tm := make(map[string]any)\n\tm[\"beep\"] = \"boop\"\n\tm[\"this\"] = \"that\"\n\tm[\"nums\"] = 123\n\tle.AddWithContext(fmt.Errorf(\"qux\"), m)\n\n\terr := le.Persist(path, []string{\"command\", \"one\", \"--example\"})\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\terr = le.Persist(path, []string{\"command\", \"two\", \"--example\"})\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\thave, err := os.ReadFile(path)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\twantPath, err := filepath.Abs(\"errors-expected.log\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\twant, err := os.ReadFile(wantPath)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tr := strings.NewReplacer(\"\\n\", \"\", \"\\r\", \"\")\n\twanttrim := r.Replace(string(want))\n\thavetrim := r.Replace(string(have))\n\n\ttestutil.AssertEqual(t, wanttrim, havetrim)\n}\n\n// TestLogPersistLogRotation validates that if an audit log file exceeds the\n// specified threshold, then the file will be deleted and recreated.\n//\n// The way this is achieved is by creating an errors.log file that has a\n// specific size, and then overriding the package level variable that\n// determines the threshold so that it matches the size of the file we created.\n// This means we can be sure our logic will trigger the file to be replaced\n// with a new empty file, to which we'll then write our log content into.\nfunc TestLogPersistLogRotation(t *testing.T) {\n\tvar (\n\t\tfi   os.FileInfo\n\t\tpath string\n\t)\n\n\t// Create temp environment to run test code within.\n\t{\n\t\twd, err := os.Getwd()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\t// We want to start off with an existing audit log file that we expect to\n\t\t// be rotated because it exceeded our defined threshold.\n\t\tseedPath, err := filepath.Abs(filepath.Join(\"testdata\", \"errors-expected.log\"))\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tseed, err := os.ReadFile(seedPath)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tf, err := os.Open(seedPath)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tdefer f.Close()\n\t\tfi, err = f.Stat()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\trootdir := testutil.NewEnv(testutil.EnvOpts{\n\t\t\tT: t,\n\t\t\tWrite: []testutil.FileIO{\n\t\t\t\t{Src: string(seed), Dst: \"errors.log\"},\n\t\t\t},\n\t\t\tCopy: []testutil.FileIO{\n\t\t\t\t{\n\t\t\t\t\tSrc: filepath.Join(\"testdata\", \"errors-expected-rotation.log\"),\n\t\t\t\t\tDst: \"errors-expected-rotation.log\",\n\t\t\t\t},\n\t\t\t},\n\t\t})\n\t\tpath = filepath.Join(rootdir, \"errors.log\")\n\t\tdefer os.RemoveAll(rootdir)\n\n\t\tif err := os.Chdir(rootdir); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tdefer func() {\n\t\t\t_ = os.Chdir(wd)\n\t\t}()\n\t}\n\n\terrors.Now = func() (t time.Time) {\n\t\treturn t\n\t}\n\terrors.FileRotationSize = fi.Size()\n\n\tle := new(errors.LogEntries)\n\tle.Add(fmt.Errorf(\"foo\"))\n\tle.Add(fmt.Errorf(\"bar\"))\n\tle.Add(fmt.Errorf(\"baz\"))\n\n\tm := make(map[string]any)\n\tm[\"beep\"] = \"boop\"\n\tm[\"this\"] = \"that\"\n\tm[\"nums\"] = 123\n\tle.AddWithContext(fmt.Errorf(\"qux\"), m)\n\n\terr := le.Persist(path, []string{\"command\", \"one\", \"--example\"})\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\thave, err := os.ReadFile(path)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\twantPath, err := filepath.Abs(\"errors-expected-rotation.log\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\twant, err := os.ReadFile(wantPath)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tr := strings.NewReplacer(\"\\n\", \"\", \"\\r\", \"\")\n\twanttrim := r.Replace(string(want))\n\thavetrim := r.Replace(string(have))\n\n\ttestutil.AssertEqual(t, wanttrim, havetrim)\n}\n"
  },
  {
    "path": "pkg/errors/process.go",
    "content": "package errors\n\nimport (\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/fatih/color\"\n\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// Process persists the error log to disk and deduces the error type.\nfunc Process(err error, args []string, out io.Writer) (skipExit bool) {\n\ttext.Break(out)\n\n\t// NOTE: We persist any error log entries to disk before attempting to handle\n\t// a possible error response from app.Run as there could be errors recorded\n\t// during the execution flow but were otherwise handled without bubbling an\n\t// error back the call stack, and so if the user still experiences something\n\t// unexpected we will have a record of any errors that happened along the way.\n\tlogErr := Log.Persist(LogPath, args[1:])\n\tif logErr != nil {\n\t\tDeduce(logErr).Print(color.Error)\n\t}\n\n\t// IMPORTANT: Deduce/Print needs to happen before checking for Skip.\n\t// This is so the help output can be printed.\n\tDeduce(err).Print(color.Error)\n\n\texitError := SkipExitError{}\n\tif errors.As(err, &exitError) {\n\t\treturn exitError.Skip\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "pkg/errors/remediation_error.go",
    "content": "package errors\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"strings\"\n\n\t\"github.com/fastly/cli/pkg/env\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// RemediationError wraps a normal error with a suggested remediation.\ntype RemediationError struct {\n\t// Prefix is a custom message displayed without modification.\n\tPrefix string\n\t// Inner is the root error.\n\tInner error\n\t// Remediation provides more context and helpful references.\n\tRemediation string\n}\n\n// Unwrap returns the inner error.\nfunc (re RemediationError) Unwrap() error {\n\treturn re.Inner\n}\n\n// Error prints the inner error string without any remediation suggestion.\nfunc (re RemediationError) Error() string {\n\tif re.Inner == nil {\n\t\treturn \"\"\n\t}\n\treturn re.Inner.Error()\n}\n\n// Print the error to the io.Writer for human consumption. If a prefix is\n// provided, it will be written without modification. The inner error is always\n// printed via text.Output with an \"Error: \" prefix and a \".\" suffix. If a\n// remediation is provided, it's printed via text.Output.\nfunc (re RemediationError) Print(w io.Writer) {\n\tif re.Prefix != \"\" {\n\t\tfmt.Fprintf(w, \"%s\\n\\n\", strings.TrimRight(re.Prefix, \"\\r\\n\"))\n\t}\n\tif re.Inner != nil {\n\t\ttext.Error(w, \"%s.\\n\\n\", re.Inner.Error()) // single \"\\n\" ensured by text.Error\n\t}\n\tif re.Remediation != \"\" {\n\t\tfmt.Fprintf(w, \"%s\\n\", strings.TrimRight(re.Remediation, \"\\r\\n\"))\n\t}\n}\n\n// FormatTemplate represents a generic error message prefix.\nvar FormatTemplate = \"To fix this error, run the following command:\\n\\n\\t$ %s\"\n\n// AuthRemediation suggests checking the provided --token.\nfunc AuthRemediation() string {\n\tvar parts []string\n\tif env.AuthCommandDisabled() {\n\t\tparts = []string{\n\t\t\t\"This error is likely caused by a missing, incorrect, or expired Fastly API token.\",\n\t\t\tfmt.Sprintf(\"Token precedence: %s > fastly.toml profile > default auth token.\", env.APIToken),\n\t\t\tfmt.Sprintf(\"Supply a token via %s.\", env.APIToken),\n\t\t}\n\t} else {\n\t\tparts = []string{\n\t\t\t\"This error is likely caused by a missing, incorrect, or expired Fastly API token.\",\n\t\t\tfmt.Sprintf(\"Token precedence: --token (raw or stored name) > %s > fastly.toml profile > default auth token.\", env.APIToken),\n\t\t\tfmt.Sprintf(\"Run `fastly auth login` to authenticate, or supply a token via --token or %s.\", env.APIToken),\n\t\t}\n\t}\n\tparts = append(parts, \"Learn more: fastly.help/cli/cli-auth\")\n\treturn strings.Join(parts, \" \")\n}\n\n// ForbiddenRemediation suggests the token may lack required permissions.\nfunc ForbiddenRemediation() string {\n\tparts := []string{\n\t\t\"This error may indicate insufficient token permissions, an incorrect account context,\",\n\t\t\"or restricted access to the requested resource.\",\n\t\t\"Check that your token has the required scope for this operation.\",\n\t}\n\tif env.AuthCommandDisabled() {\n\t\tparts = append(parts, fmt.Sprintf(\"Verify your token has the required scope via %s or the Fastly dashboard.\", env.APIToken))\n\t} else {\n\t\tparts = append(parts, \"You can re-authenticate with `fastly auth login` or check your current identity with `fastly whoami`.\")\n\t}\n\tparts = append(parts, \"Learn more: fastly.help/cli/cli-auth\")\n\treturn strings.Join(parts, \" \")\n}\n\n// NetworkRemediation suggests, somewhat unhelpfully, to try again later.\nvar NetworkRemediation = strings.Join([]string{\n\t\"This error may be caused by transient network issues.\",\n\t\"Please verify your network connection and DNS configuration, and try again.\",\n}, \" \")\n\n// HostRemediation suggests there might be an issue with the local host.\nvar HostRemediation = strings.Join([]string{\n\t\"This error may be caused by a problem with your host environment, for example\",\n\t\"too-restrictive file permissions, files that already exist, or a full disk.\",\n}, \" \")\n\n// BugRemediation suggests filing a bug on the GitHub repo. It's good to include\n// as the final suggested remediation in many errors.\nvar BugRemediation = strings.Join([]string{\n\t\"If you believe this error is the result of a bug, please file an issue:\",\n\t\"https://github.com/fastly/cli/issues/new?labels=bug&template=bug_report.md\",\n}, \" \")\n\n// ConfigRemediation informs the user that an error with loading the config\n// isn't a breaking error and the CLI can still be used.\nvar ConfigRemediation = strings.Join([]string{\n\t\"There is a fallback version of the configuration provided with the CLI install\",\n\t\"(run `fastly config` to view the config) which enables the CLI to continue to be usable even though the config couldn't be updated.\",\n}, \" \")\n\n// ServiceIDRemediation suggests provide a service ID via --service-id flag or\n// fastly.toml.\nvar ServiceIDRemediation = strings.Join([]string{\n\t\"Please provide one via the --service-id or --service-name flag, or by setting the FASTLY_SERVICE_ID environment variable, or within your fastly.toml\",\n}, \" \")\n\n// CustomerIDRemediation suggests provide a customer ID via --customer-id flag\n// or via environment variable.\nvar CustomerIDRemediation = strings.Join([]string{\n\t\"Please provide one via the --customer-id flag, or by setting the FASTLY_CUSTOMER_ID environment variable\",\n}, \" \")\n\n// WorkspaceIDRemediation suggests provide a customer ID via --workspace-id flag\n// or via environment variable.\nvar WorkspaceIDRemediation = strings.Join([]string{\n\t\"Please provide one via the --workspace-id flag, or by setting the FASTLY_WORKSPACE_ID environment variable\",\n}, \" \")\n\n// ExistingDirRemediation suggests moving to another directory and retrying.\nvar ExistingDirRemediation = strings.Join([]string{\n\t\"Please create a new directory and initialize a new project using:\",\n\t\"`fastly compute init`.\",\n}, \" \")\n\n// AutoCloneRemediation suggests provide an --autoclone flag.\nvar AutoCloneRemediation = strings.Join([]string{\n\t\"Repeat the command with the --autoclone flag to allow the version to be cloned\",\n}, \" \")\n\n// IDRemediation suggests an ID via --id flag should be provided.\nvar IDRemediation = strings.Join([]string{\n\t\"Please provide one via the --id flag\",\n}, \" \")\n\n// PackageSizeRemediation suggests checking the resources documentation for the\n// current package size limit.\nvar PackageSizeRemediation = strings.Join([]string{\n\t\"Please check our Compute resource limits:\",\n\t\"https://www.fastly.com/documentation/guides/compute#limitations-and-constraints\",\n}, \" \")\n\n// UnrecognisedManifestVersionRemediation suggests steps to resolve an issue\n// where the project contains a manifest_version that is larger than what the\n// current CLI version supports.\nvar UnrecognisedManifestVersionRemediation = strings.Join([]string{\n\t\"Please try updating the installed CLI version using: `fastly update`.\",\n\t\"See also https://www.fastly.com/documentation/reference/compute/fastly-toml to check your fastly.toml manifest is up-to-date with the latest data model.\",\n\tBugRemediation,\n}, \" \")\n\n// ComputeInitRemediation suggests re-running `compute init` to resolve\n// manifest issue.\nvar ComputeInitRemediation = strings.Join([]string{\n\t\"Run `fastly compute init` to ensure a correctly configured manifest.\",\n\t\"See more at https://www.fastly.com/documentation/reference/compute/fastly-toml\",\n}, \" \")\n\n// ComputeServeRemediation suggests re-running `compute serve` with one of the\n// incompatible flags removed.\nvar ComputeServeRemediation = strings.Join([]string{\n\t\"The --watch flag enables hot reloading of your project to support a faster feedback loop during local development, and subsequently conflicts with the --skip-build flag which avoids rebuilding your project altogether.\",\n\t\"Remove one of the flags based on the outcome you require.\",\n}, \" \")\n\n// ComputeBuildRemediation suggests configuring a `[scripts.build]` setting in\n// the fastly.toml manifest.\nvar ComputeBuildRemediation = strings.Join([]string{\n\t\"Add a [scripts] section with `build = \\\"%s\\\"`.\",\n\t\"See more at https://www.fastly.com/documentation/reference/compute/fastly-toml\",\n}, \" \")\n\n// ErrProfileFlagNotFound is returned when --profile names an unknown auth token.\nfunc ErrProfileFlagNotFound(name string) RemediationError {\n\treturn RemediationError{\n\t\tInner:       fmt.Errorf(\"profile %q (from --profile) not found in auth config\", name),\n\t\tRemediation: ProfileRemediation(),\n\t}\n}\n\n// ProfileRemediation suggests running auth commands.\nfunc ProfileRemediation() string {\n\tif env.AuthCommandDisabled() {\n\t\treturn fmt.Sprintf(\"Supply a token via the %s environment variable.\", env.APIToken)\n\t}\n\treturn \"Run `fastly auth login` to authenticate, or `fastly auth list` to view stored tokens.\"\n}\n\n// InvalidStaticConfigRemediation indicates an unexpected error occurred when\n// deserialising the CLI's internal configuration.\nvar InvalidStaticConfigRemediation = strings.Join([]string{\n\t\"The Fastly CLI attempted to parse an internal configuration file but failed.\",\n\t\"Run `fastly update` to upgrade your current CLI version.\",\n\t\"If this does not resolve the issue, then please file an issue:\",\n\t\"https://github.com/fastly/cli/issues/new?labels=bug&template=bug_report.md\",\n}, \" \")\n\n// TokenExpirationRemediation indicates that a stored OIDC token has expired.\nfunc TokenExpirationRemediation() string {\n\tif env.AuthCommandDisabled() {\n\t\treturn fmt.Sprintf(\"Supply a fresh token via the %s environment variable.\", env.APIToken)\n\t}\n\treturn \"Run 'fastly auth login --sso --token <name>' to refresh the token.\"\n}\n\n// TokenExpirationRemediationForType returns remediation text appropriate for\n// the given token type (\"static\", \"sso\", or \"\" for unknown/default).\n//\n// NOTE: tokenType values must match config.AuthTokenTypeStatic / config.AuthTokenTypeSSO.\n// We cannot import pkg/config here (pkg/errors is a foundational package), so the\n// string literals are used directly. Callers should pass config.AuthToken.Type.\nfunc TokenExpirationRemediationForType(tokenType string) string {\n\tif env.AuthCommandDisabled() {\n\t\treturn fmt.Sprintf(\"Supply a fresh token via the %s environment variable.\", env.APIToken)\n\t}\n\tif tokenType == \"static\" {\n\t\treturn \"Generate a new token from the Fastly dashboard or run 'fastly auth add'.\"\n\t}\n\treturn \"Run 'fastly auth login --sso --token <name>' to refresh the token.\"\n}\n\n// NonInteractiveAuthRemediation tells the user how to supply a token when\n// interactive prompts are suppressed.\nfunc NonInteractiveAuthRemediation() string {\n\tparts := []string{\"Interactive authentication is not available in this mode.\"}\n\tif env.AuthCommandDisabled() {\n\t\tparts = append(parts, fmt.Sprintf(\"Supply a token via the %s environment variable.\", env.APIToken))\n\t} else {\n\t\tparts = append(parts, fmt.Sprintf(\"Supply a token via --token or the %s environment variable.\", env.APIToken))\n\t}\n\tparts = append(parts, \"Learn more: fastly.help/cli/cli-auth\")\n\treturn strings.Join(parts, \" \")\n}\n"
  },
  {
    "path": "pkg/errors/remediation_test.go",
    "content": "package errors_test\n\nimport (\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/fastly/cli/pkg/errors\"\n)\n\nfunc TestRemediationDisableAuthCommand(t *testing.T) {\n\tt.Setenv(\"FASTLY_DISABLE_AUTH_COMMAND\", \"\")\n\tt.Run(\"AuthRemediation includes auth login by default\", func(t *testing.T) {\n\t\tmsg := errors.AuthRemediation()\n\t\tif !strings.Contains(msg, \"fastly auth login\") {\n\t\t\tt.Errorf(\"expected AuthRemediation to mention 'fastly auth login', got: %s\", msg)\n\t\t}\n\t})\n\n\tt.Run(\"AuthRemediation omits auth login and --token when env var set\", func(t *testing.T) {\n\t\tt.Setenv(\"FASTLY_DISABLE_AUTH_COMMAND\", \"1\")\n\t\tmsg := errors.AuthRemediation()\n\t\tif strings.Contains(msg, \"fastly auth login\") {\n\t\t\tt.Errorf(\"expected AuthRemediation to omit 'fastly auth login', got: %s\", msg)\n\t\t}\n\t\tif strings.Contains(msg, \"--token\") {\n\t\t\tt.Errorf(\"expected AuthRemediation to omit --token, got: %s\", msg)\n\t\t}\n\t\tif !strings.Contains(msg, \"FASTLY_API_TOKEN\") {\n\t\t\tt.Errorf(\"expected AuthRemediation to mention FASTLY_API_TOKEN, got: %s\", msg)\n\t\t}\n\t})\n\n\tt.Run(\"ForbiddenRemediation includes auth login by default\", func(t *testing.T) {\n\t\tmsg := errors.ForbiddenRemediation()\n\t\tif !strings.Contains(msg, \"fastly auth login\") {\n\t\t\tt.Errorf(\"expected ForbiddenRemediation to mention 'fastly auth login', got: %s\", msg)\n\t\t}\n\t})\n\n\tt.Run(\"ForbiddenRemediation omits auth login and whoami when env var set\", func(t *testing.T) {\n\t\tt.Setenv(\"FASTLY_DISABLE_AUTH_COMMAND\", \"1\")\n\t\tmsg := errors.ForbiddenRemediation()\n\t\tif strings.Contains(msg, \"fastly auth login\") {\n\t\t\tt.Errorf(\"expected ForbiddenRemediation to omit 'fastly auth login', got: %s\", msg)\n\t\t}\n\t\tif strings.Contains(msg, \"fastly whoami\") {\n\t\t\tt.Errorf(\"expected ForbiddenRemediation to omit 'fastly whoami', got: %s\", msg)\n\t\t}\n\t\tif !strings.Contains(msg, \"FASTLY_API_TOKEN\") {\n\t\t\tt.Errorf(\"expected ForbiddenRemediation to mention FASTLY_API_TOKEN, got: %s\", msg)\n\t\t}\n\t})\n\n\tt.Run(\"ErrNoToken remediation responds to env var\", func(t *testing.T) {\n\t\tt.Setenv(\"FASTLY_DISABLE_AUTH_COMMAND\", \"1\")\n\t\tre := errors.ErrNoToken()\n\t\tif strings.Contains(re.Remediation, \"fastly auth login\") {\n\t\t\tt.Errorf(\"expected ErrNoToken remediation to omit 'fastly auth login', got: %s\", re.Remediation)\n\t\t}\n\t})\n\n\tt.Run(\"NonInteractiveAuthRemediation includes --token by default\", func(t *testing.T) {\n\t\tt.Setenv(\"FASTLY_DISABLE_AUTH_COMMAND\", \"\")\n\t\tmsg := errors.NonInteractiveAuthRemediation()\n\t\tif !strings.Contains(msg, \"--token\") {\n\t\t\tt.Errorf(\"expected NonInteractiveAuthRemediation to mention --token, got: %s\", msg)\n\t\t}\n\t})\n\n\tt.Run(\"NonInteractiveAuthRemediation omits --token when env var set\", func(t *testing.T) {\n\t\tt.Setenv(\"FASTLY_DISABLE_AUTH_COMMAND\", \"1\")\n\t\tmsg := errors.NonInteractiveAuthRemediation()\n\t\tif strings.Contains(msg, \"--token\") {\n\t\t\tt.Errorf(\"expected NonInteractiveAuthRemediation to omit --token, got: %s\", msg)\n\t\t}\n\t\tif !strings.Contains(msg, \"FASTLY_API_TOKEN\") {\n\t\t\tt.Errorf(\"expected NonInteractiveAuthRemediation to mention FASTLY_API_TOKEN, got: %s\", msg)\n\t\t}\n\t})\n\n\tt.Run(\"ErrNonInteractiveNoToken omits --token when env var set\", func(t *testing.T) {\n\t\tt.Setenv(\"FASTLY_DISABLE_AUTH_COMMAND\", \"1\")\n\t\tre := errors.ErrNonInteractiveNoToken()\n\t\tif strings.Contains(re.Remediation, \"--token\") {\n\t\t\tt.Errorf(\"expected ErrNonInteractiveNoToken remediation to omit --token, got: %s\", re.Remediation)\n\t\t}\n\t\tif !strings.Contains(re.Remediation, \"FASTLY_API_TOKEN\") {\n\t\t\tt.Errorf(\"expected ErrNonInteractiveNoToken remediation to mention FASTLY_API_TOKEN, got: %s\", re.Remediation)\n\t\t}\n\t})\n\n\tt.Run(\"ProfileRemediation omits --token when env var set\", func(t *testing.T) {\n\t\tt.Setenv(\"FASTLY_DISABLE_AUTH_COMMAND\", \"1\")\n\t\tmsg := errors.ProfileRemediation()\n\t\tif strings.Contains(msg, \"--token\") {\n\t\t\tt.Errorf(\"expected ProfileRemediation to omit --token, got: %s\", msg)\n\t\t}\n\t\tif !strings.Contains(msg, \"FASTLY_API_TOKEN\") {\n\t\t\tt.Errorf(\"expected ProfileRemediation to mention FASTLY_API_TOKEN, got: %s\", msg)\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "pkg/errors/testdata/errors-expected-rotation.log",
    "content": "\nCOMMAND:\nfastly command one --example\n\nTIMESTAMP:\n0001-01-01 00:00:00 +0000 UTC\n\nERROR:\nfoo\n\nFILE:\n/pkg/errors/log_test.go\n\nLINE:\n182\n\n\nTIMESTAMP:\n0001-01-01 00:00:00 +0000 UTC\n\nERROR:\nbar\n\nFILE:\n/pkg/errors/log_test.go\n\nLINE:\n183\n\n\nTIMESTAMP:\n0001-01-01 00:00:00 +0000 UTC\n\nERROR:\nbaz\n\nFILE:\n/pkg/errors/log_test.go\n\nLINE:\n184\n\n\nTIMESTAMP:\n0001-01-01 00:00:00 +0000 UTC\n\nERROR:\nqux\n\nFILE:\n/pkg/errors/log_test.go\n\nLINE:\n190\n\n\n  beep: boop\n\n  nums: 123\n\n  this: that\n\n\n------------------------------\n"
  },
  {
    "path": "pkg/errors/testdata/errors-expected.log",
    "content": "\nCOMMAND:\nfastly command one --example\n\nTIMESTAMP:\n0001-01-01 00:00:00 +0000 UTC\n\nERROR:\nfoo\n\nFILE:\n/pkg/errors/log_test.go\n\nLINE:\n72\n\n\nTIMESTAMP:\n0001-01-01 00:00:00 +0000 UTC\n\nERROR:\nbar\n\nFILE:\n/pkg/errors/log_test.go\n\nLINE:\n73\n\n\nTIMESTAMP:\n0001-01-01 00:00:00 +0000 UTC\n\nERROR:\nbaz\n\nFILE:\n/pkg/errors/log_test.go\n\nLINE:\n74\n\n\nTIMESTAMP:\n0001-01-01 00:00:00 +0000 UTC\n\nERROR:\nqux\n\nFILE:\n/pkg/errors/log_test.go\n\nLINE:\n80\n\n  beep: boop\n\n  nums: 123\n\n  this: that\n\n\n------------------------------\n\nCOMMAND:\nfastly command two --example\n\nTIMESTAMP:\n0001-01-01 00:00:00 +0000 UTC\n\nERROR:\nfoo\n\nFILE:\n/pkg/errors/log_test.go\n\nLINE:\n72\n\n\nTIMESTAMP:\n0001-01-01 00:00:00 +0000 UTC\n\nERROR:\nbar\n\nFILE:\n/pkg/errors/log_test.go\n\nLINE:\n73\n\n\nTIMESTAMP:\n0001-01-01 00:00:00 +0000 UTC\n\nERROR:\nbaz\n\nFILE:\n/pkg/errors/log_test.go\n\nLINE:\n74\n\n\nTIMESTAMP:\n0001-01-01 00:00:00 +0000 UTC\n\nERROR:\nqux\n\nFILE:\n/pkg/errors/log_test.go\n\nLINE:\n80\n\n\n  beep: boop\n\n  nums: 123\n\n  this: that\n\n\n------------------------------\n"
  },
  {
    "path": "pkg/exec/doc.go",
    "content": "// Package exec contains helper abstractions for working with external commands.\npackage exec\n"
  },
  {
    "path": "pkg/exec/exec.go",
    "content": "package exec\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"os/exec\"\n\t\"os/signal\"\n\t\"syscall\"\n\t\"time\"\n\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/text\"\n\t\"github.com/fastly/cli/pkg/threadsafe\"\n)\n\n// divider is used as separator lines around shell output.\nconst divider = \"--------------------------------------------------------------------------------\"\n\n// Streaming models a generic command execution that consumers can use to\n// execute commands and stream their output to an io.Writer. For example\n// compute commands can use this to standardize the flow control for each\n// compiler toolchain.\ntype Streaming struct {\n\t// Args are the command positional arguments.\n\tArgs []string\n\t// Command is the command to be executed.\n\tCommand string\n\t// Env is the environment variables to set.\n\tEnv []string\n\t// ForceOutput ensures output is displayed (default: only display on error).\n\tForceOutput bool\n\t// Output is where to write output (e.g. stdout)\n\tOutput io.Writer\n\t// Process is the process to terminal if signal received.\n\tProcess *os.Process\n\t// SignalCh is a channel handling signal events.\n\tSignalCh chan os.Signal\n\t// Spinner is a specific spinner instance.\n\tSpinner text.Spinner\n\t// SpinnerMessage is the messaging to use.\n\tSpinnerMessage string\n\t// Timeout is the command timeout.\n\tTimeout time.Duration\n\t// Verbose outputs additional information.\n\tVerbose bool\n}\n\n// MonitorSignals spawns a goroutine that configures signal handling so that\n// the long running subprocess can be killed using SIGINT/SIGTERM.\nfunc (s *Streaming) MonitorSignals() {\n\tgo s.MonitorSignalsAsync()\n}\n\n// MonitorSignalsAsync configures the signal notifications.\nfunc (s *Streaming) MonitorSignalsAsync() {\n\tsignals := []os.Signal{\n\t\tsyscall.SIGINT,\n\t\tsyscall.SIGTERM,\n\t}\n\n\tsignal.Notify(s.SignalCh, signals...)\n\n\t<-s.SignalCh\n\tsignal.Stop(s.SignalCh)\n\n\t// NOTE: We don't do error handling here because the user might be doing local\n\t// development with the --watch flag and that workflow will have already\n\t// killed the process. The reason this line still exists is for users running\n\t// their application locally without the --watch flag and who then execute\n\t// Ctrl-C to kill the process.\n\t_ = s.Signal(os.Kill)\n}\n\n// Exec executes the compiler command and pipes the child process stdout and\n// stderr output to the supplied io.Writer, it waits for the command to exit\n// cleanly or returns an error.\nfunc (s *Streaming) Exec() error {\n\t// Construct the command with given arguments and environment.\n\tvar cmd *exec.Cmd\n\tif s.Timeout > 0 {\n\t\tctx, cancel := context.WithTimeout(context.Background(), s.Timeout)\n\t\tdefer cancel()\n\t\t// gosec flagged this:\n\t\t// G204 (CWE-78): Subprocess launched with variable\n\t\t// Disabling as the variables come from trusted sources.\n\t\t// #nosec\n\t\t// nosemgrep\n\t\tcmd = exec.CommandContext(ctx, s.Command, s.Args...)\n\t} else {\n\t\t// gosec flagged this:\n\t\t// G204 (CWE-78): Subprocess launched with variable\n\t\t// Disabling as the variables come from trusted sources.\n\t\t// #nosec\n\t\t// nosemgrep\n\t\tcmd = exec.Command(s.Command, s.Args...)\n\t}\n\tcmd.Env = append(os.Environ(), s.Env...)\n\n\t// We store all output in a buffer to hide it unless there was an error.\n\tvar buf threadsafe.Buffer\n\tvar output io.Writer\n\toutput = &buf\n\n\t// We only display the stored output if there is an error.\n\t// But some commands like `compute serve` expect the full output regardless.\n\t// So for those scenarios they can force all output.\n\tif s.ForceOutput {\n\t\toutput = s.Output\n\t}\n\n\tif !s.Verbose {\n\t\ttext.Break(output)\n\t}\n\ttext.Info(output, \"Command output:\")\n\ttext.Output(output, divider)\n\n\tcmd.Stdout = output\n\tcmd.Stderr = output\n\n\tif err := cmd.Start(); err != nil {\n\t\ttext.Output(output, divider)\n\t\treturn err\n\t}\n\n\t// Store off os.Process so it can be killed by signal listener.\n\t//\n\t// NOTE: argparser.Process is nil until exec.Start() returns successfully.\n\ts.Process = cmd.Process\n\n\tif err := cmd.Wait(); err != nil {\n\t\t// IMPORTANT: We MUST wrap the original error.\n\t\t// This is because the `compute serve` command requires it for --watch\n\t\t// Specifically we need to check the error message for \"killed\".\n\t\t// This enables the watching logic to restart the Viceroy binary.\n\t\terr = fmt.Errorf(\"error during execution process (see 'command output' above): %w\", err)\n\n\t\ttext.Output(output, divider)\n\n\t\t// If we're in verbose mode, the build output is shown.\n\t\t// So in that case we don't want to have a spinner as it'll interweave output.\n\t\t// In non-verbose mode we have a spinner running while the build is happening.\n\t\tif !s.Verbose && s.Spinner != nil {\n\t\t\ts.Spinner.StopFailMessage(s.SpinnerMessage)\n\t\t\tif spinErr := s.Spinner.StopFail(); spinErr != nil {\n\t\t\t\treturn fmt.Errorf(text.SpinnerErrWrapper, spinErr, err)\n\t\t\t}\n\t\t}\n\n\t\t// Display the buffer stored output as we have an error.\n\t\tfmt.Fprintf(s.Output, \"%s\", buf.String())\n\n\t\treturn err\n\t}\n\n\ttext.Output(output, divider)\n\treturn nil\n}\n\n// Signal enables spawned subprocess to accept given signal.\nfunc (s *Streaming) Signal(sig os.Signal) error {\n\tif s.Process != nil {\n\t\terr := s.Process.Signal(sig)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\n// CommandOpts are arguments for executing a streaming command.\ntype CommandOpts struct {\n\t// Args are the command positional arguments.\n\tArgs []string\n\t// Command is the command to be executed.\n\tCommand string\n\t// Env is the environment variables to set.\n\tEnv []string\n\t// ErrLog provides an interface for recording errors to disk.\n\tErrLog fsterr.LogInterface\n\t// Output is where to write output (e.g. stdout)\n\tOutput io.Writer\n\t// Spinner is a specific spinner instance.\n\tSpinner text.Spinner\n\t// SpinnerMessage is the messaging to use.\n\tSpinnerMessage string\n\t// Timeout is the command timeout.\n\tTimeout int\n\t// Verbose outputs additional information.\n\tVerbose bool\n}\n\n// Command is an abstraction over a Streaming type. It is used by both the\n// `compute init` and `compute build` commands to run post init/build scripts.\nfunc Command(opts CommandOpts) error {\n\ts := Streaming{\n\t\tCommand:        opts.Command,\n\t\tArgs:           opts.Args,\n\t\tEnv:            opts.Env,\n\t\tOutput:         opts.Output,\n\t\tSpinner:        opts.Spinner,\n\t\tSpinnerMessage: opts.SpinnerMessage,\n\t\tVerbose:        opts.Verbose,\n\t}\n\tif opts.Verbose {\n\t\ts.ForceOutput = true\n\t}\n\tif opts.Timeout > 0 {\n\t\ts.Timeout = time.Duration(opts.Timeout) * time.Second\n\t}\n\tif err := s.Exec(); err != nil {\n\t\topts.ErrLog.Add(err)\n\t\treturn err\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/file/archive.go",
    "content": "package file\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"io/fs\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"github.com/mholt/archives\"\n\n\t\"github.com/fastly/cli/pkg/errors\"\n)\n\n// Archives is a collection of supported archive formats.\nvar Archives = []Archive{TarGz, Zip}\n\n// Archive represents the associated behaviour for a collection of files\n// contained inside an archive format.\ntype Archive interface {\n\tExtensions() []string\n\tExtract() error\n\tFilename() string\n\tMimeTypes() []string\n\tSetDestination(d string)\n\tSetFilename(n string)\n}\n\n// TarGz represents an instance of a tar.gz archive file.\nvar TarGz = &ArchiveGzip{\n\tArchiveBase{\n\t\tExts:  []string{\".tgz\", \".gz\"},\n\t\tMimes: []string{\"application/gzip\", \"application/x-gzip\", \"application/x-tar\"},\n\t},\n}\n\n// Zip represents an instance of a zip archive file.\nvar Zip = &ArchiveZip{\n\tArchiveBase{\n\t\tExts:  []string{\".zip\"},\n\t\tMimes: []string{\"application/zip\", \"application/x-zip\"},\n\t},\n}\n\n// ArchiveGzip represents a container for the .tar.gz file format.\ntype ArchiveGzip struct {\n\tArchiveBase\n}\n\n// ArchiveZip represents a container for the .zip file format.\ntype ArchiveZip struct {\n\tArchiveBase\n}\n\n// ArchiveBase represents a container for a collection of files.\ntype ArchiveBase struct {\n\tDst   string\n\tExts  []string\n\tFile  io.ReadSeeker\n\tMimes []string\n\tName  string\n}\n\n// Extensions returns the accepted file extensions.\nfunc (a ArchiveBase) Extensions() []string {\n\treturn a.Exts\n}\n\n// MimeTypes returns all valid  mime types for the format.\nfunc (a ArchiveBase) MimeTypes() []string {\n\treturn a.Mimes\n}\n\n// Filename returns the file name.\nfunc (a ArchiveBase) Filename() string {\n\treturn a.Name\n}\n\n// SetDestination sets the destination for where files should be extracted.\nfunc (a *ArchiveBase) SetDestination(d string) {\n\ta.Dst = d\n}\n\n// SetFilename sets the name of the local archive file.\n//\n// NOTE: This archive file is the 'container' of the archived files that will\n// be extracted separately.\nfunc (a *ArchiveBase) SetFilename(n string) {\n\ta.Name = n\n}\n\n// ExtractArchive extracts an archive file to a destination directory.\n// The filter function, if provided, determines which files to extract based on their path in the archive.\n// If filter returns false for a file, it will be skipped.\nfunc ExtractArchive(archivePath, destDir string, filter func(string) bool) error {\n\tctx := context.Background()\n\n\tinput, err := os.Open(archivePath)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error opening archive: %w\", err)\n\t}\n\tdefer input.Close()\n\n\tformat, stream, err := archives.Identify(ctx, archivePath, input)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error identifying archive format: %w\", err)\n\t}\n\n\tif ex, ok := format.(archives.Extractor); ok {\n\t\terr = ex.Extract(ctx, stream, func(_ context.Context, f archives.FileInfo) error {\n\t\t\t// Apply filter if provided\n\t\t\tif filter != nil && !filter(f.NameInArchive) {\n\t\t\t\treturn nil\n\t\t\t}\n\n\t\t\tdestPath := filepath.Join(destDir, f.NameInArchive)\n\n\t\t\tif f.IsDir() {\n\t\t\t\treturn os.MkdirAll(destPath, 0o755)\n\t\t\t}\n\n\t\t\tif err := os.MkdirAll(filepath.Dir(destPath), 0o755); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\toutFile, err := os.OpenFile(destPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, f.Mode())\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tdefer outFile.Close()\n\n\t\t\trc, err := f.Open()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tdefer rc.Close()\n\n\t\t\t_, err = io.Copy(outFile, rc)\n\t\t\treturn err\n\t\t})\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"error extracting contents from archive: %w\", err)\n\t\t}\n\t} else {\n\t\treturn fmt.Errorf(\"format does not support extraction\")\n\t}\n\n\treturn nil\n}\n\n// Extract all files and folders from the collection.\nfunc (a ArchiveBase) Extract() error {\n\tif err := ExtractArchive(a.Filename(), a.Dst, nil); err != nil {\n\t\treturn err\n\t}\n\n\tif _, err := os.Stat(\"fastly.toml\"); err == nil {\n\t\treturn nil\n\t}\n\n\t// Looks like the package files are contained within a top-level directory\n\t// that now need to be extracted.\n\twd, err := os.Getwd()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error determining current directory: %w\", err)\n\t}\n\n\tvar dirContentToMove string\n\n\terr = filepath.WalkDir(wd, func(path string, entry fs.DirEntry, err error) error {\n\t\t// WalkDir() triggered an error\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\t// We already check if the current directory had a manifest so skip it\n\t\tif entry.IsDir() && path == wd {\n\t\t\treturn nil\n\t\t}\n\t\t// We expect there to be a directory that contains the manifest\n\t\tif entry.IsDir() {\n\t\t\tif _, err := os.Stat(filepath.Join(path, \"fastly.toml\")); err == nil {\n\t\t\t\tdirContentToMove = path\n\t\t\t\treturn errors.ErrStopWalk\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t})\n\n\tif err != nil && err != errors.ErrStopWalk {\n\t\treturn err\n\t}\n\tif dirContentToMove == \"\" {\n\t\treturn errors.ErrInvalidArchive\n\t}\n\n\tfiles, err := filepath.Glob(filepath.Join(dirContentToMove, \"*\"))\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Move files from within package directory into its parent directory\n\tfor _, path := range files {\n\t\tdir, file := filepath.Split(path)\n\t\tif strings.HasSuffix(dir, string(os.PathSeparator)) {\n\t\t\tdir = dir[:len(dir)-1]\n\t\t}\n\t\tparent := filepath.Dir(dir)\n\t\terr := os.Rename(path, filepath.Join(parent, file))\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn os.RemoveAll(dirContentToMove)\n}\n"
  },
  {
    "path": "pkg/file/doc.go",
    "content": "// Package file contains functions to handle different file formats.\npackage file\n"
  },
  {
    "path": "pkg/filesystem/directory.go",
    "content": "package filesystem\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"io/fs\"\n\t\"os\"\n\t\"path/filepath\"\n)\n\n// FileExists asserts whether a file path exists.\nfunc FileExists(path string) bool {\n\t_, err := os.Stat(path)\n\treturn !errors.Is(err, fs.ErrNotExist)\n}\n\n// CopyFile copies a file from src to dst. If src and dst files exist, and are\n// the same, then return successfully. Otherwise, attempt to copy the file\n// contents from src to dst. The file will be created if it does not already\n// exist. If the destination file exists, all it's contents will be replaced by\n// the contents of the source file.\nfunc CopyFile(src, dst string) (err error) {\n\t// Get source file stats.\n\tss, err := os.Stat(src)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"cannot read source file: %s\", src)\n\t}\n\n\t// Assert that source file is regular file.\n\tif !ss.Mode().IsRegular() {\n\t\t// Cannot copy non-regular files (e.g., directories,\n\t\t// symlinks, devices, etc.)\n\t\treturn fmt.Errorf(\"non-regular source file: %s\", src) // #nosec G307\n\t}\n\n\t// Get destination file stats.\n\tds, err := os.Stat(dst)\n\tif err != nil {\n\t\tif !errors.Is(err, fs.ErrNotExist) {\n\t\t\treturn fmt.Errorf(\"cannot read destination file: %s\", dst)\n\t\t}\n\t} else {\n\t\t// Assert that source file is regular file.\n\t\tif !ds.Mode().IsRegular() {\n\t\t\treturn fmt.Errorf(\"non-regular destination file: %s\", src)\n\t\t}\n\n\t\t// If same file, return successfully.\n\t\tif os.SameFile(ss, ds) {\n\t\t\treturn nil\n\t\t}\n\t}\n\n\t// Open source file for reading.\n\tin, err := os.Open(filepath.Clean(src))\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error reading source file: %w\", err)\n\t}\n\tdefer in.Close() // #nosec G307\n\n\t// Create all directories of destination\n\tif err = os.MkdirAll(filepath.Dir(dst), 0o700); err != nil {\n\t\treturn fmt.Errorf(\"creating destination directory: %w\", err)\n\t}\n\n\t// Create destination file for writing.\n\t//\n\t// gosec flagged this:\n\t// G304 (CWE-22): Potential file inclusion via variable\n\t//\n\t// Disabling as we require a user to configure their own environment.\n\t/* #nosec */\n\tout, err := os.Create(dst)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error creating destination file: %w\", err)\n\t}\n\n\tdefer func() {\n\t\tcerr := out.Close()\n\t\tif err == nil {\n\t\t\terr = cerr\n\t\t}\n\t}()\n\n\tif _, err = io.Copy(out, in); err != nil {\n\t\treturn fmt.Errorf(\"error copying file contents: %w\", err)\n\t}\n\n\treturn out.Sync()\n}\n\n// MakeDirectoryIfNotExists asserts whether a directory exists and makes it\n// if not. Returns nil if exists or successfully made.\nfunc MakeDirectoryIfNotExists(path string) error {\n\tfi, err := os.Stat(path)\n\tswitch {\n\tcase err == nil && fi.IsDir():\n\t\treturn nil\n\tcase err == nil && !fi.IsDir():\n\t\treturn fmt.Errorf(\"%s already exists as a regular file\", path)\n\tcase errors.Is(err, fs.ErrNotExist):\n\t\treturn os.MkdirAll(path, 0o750)\n\tcase err != nil:\n\t\treturn err\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/filesystem/doc.go",
    "content": "// Package filesystem contains functions to handle file operations.\npackage filesystem\n"
  },
  {
    "path": "pkg/filesystem/home.go",
    "content": "package filesystem\n\nimport (\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n)\n\nconst (\n\t// UnixHome is the home directory for a unix system.\n\tUnixHome = \"$HOME\"\n\t// UnixHomeShort is the 'short' home directory for a unix system.\n\tUnixHomeShort = \"~\"\n\t// WindowsHome is the home directory for a Windows system.\n\tWindowsHome = \"%USERPROFILE%\"\n)\n\n// ResolveAbs returns an absolute path with the user home directory resolved.\n//\n// EXAMPLE (unix):\n// $HOME/.gitignore -> /Users/<USER>/.gitignore\n// ~/.gitignore     -> /Users/<USER>/.gitignore\n// .\nfunc ResolveAbs(path string) string {\n\tvar uhd string\n\tif strings.HasPrefix(path, UnixHome) {\n\t\tuhd = UnixHome\n\t}\n\tif strings.HasPrefix(path, UnixHomeShort) {\n\t\tuhd = UnixHomeShort\n\t}\n\tif strings.HasPrefix(path, WindowsHome) {\n\t\tuhd = WindowsHome\n\t}\n\n\tif uhd != \"\" {\n\t\thome, err := os.UserHomeDir()\n\t\tif err != nil {\n\t\t\treturn path\n\t\t}\n\t\tpath = strings.Replace(path, uhd, \"\", 1)\n\t\treturn filepath.Join(home, path)\n\t}\n\n\ts, err := filepath.Abs(path)\n\tif err != nil {\n\t\treturn path\n\t}\n\treturn s\n}\n"
  },
  {
    "path": "pkg/fmt/doc.go",
    "content": "// Package fmt contains helper functions for formatting text.\npackage fmt\n"
  },
  {
    "path": "pkg/fmt/fmt.go",
    "content": "package fmt\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"fmt\"\n\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// Success is a test helper used to generate output for asserting against.\nfunc Success(format string, args ...any) string {\n\tvar b bytes.Buffer\n\ttext.Success(&b, format, args...)\n\treturn b.String()\n}\n\n// Info is a test helper used to generate output for asserting against.\nfunc Info(format string, args ...any) string {\n\tvar b bytes.Buffer\n\ttext.Info(&b, format, args...)\n\treturn b.String()\n}\n\n// JSON decodes then re-encodes back to JSON, with indentation matching\n// that of ../cmd/argparser.go's argparser.WriteJSON.\nfunc JSON(format string, args ...any) string {\n\tvar r json.RawMessage\n\tif err := json.Unmarshal([]byte(fmt.Sprintf(format, args...)), &r); err != nil {\n\t\tpanic(err)\n\t}\n\treturn EncodeJSON(r)\n}\n\n// EncodeJSON is a test helper that encodes any Go type into JSON.\nfunc EncodeJSON(value any) string {\n\tvar b bytes.Buffer\n\tenc := json.NewEncoder(&b)\n\tenc.SetIndent(\"\", \"  \")\n\t_ = enc.Encode(value)\n\treturn b.String()\n}\n"
  },
  {
    "path": "pkg/github/doc.go",
    "content": "// Package github contains functions for checking the latest software\n// versions hosted by GitHub.\npackage github\n"
  },
  {
    "path": "pkg/github/github.go",
    "content": "package github\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"regexp\"\n\t\"runtime\"\n\t\"strings\"\n\n\t\"github.com/blang/semver\"\n\n\t\"github.com/fastly/cli/pkg/api\"\n\t\"github.com/fastly/cli/pkg/debug\"\n\t\"github.com/fastly/cli/pkg/file\"\n\tfstruntime \"github.com/fastly/cli/pkg/runtime\"\n)\n\nconst (\n\t// metadataURL takes a GitHub repo (e.g. cli or viceroy), an OS (e.g. darwin or linux), and an arch (e.g. amd64 or arm64).\n\tmetadataURL = \"https://developer.fastly.com/api/internal/releases/meta/%s/%s/%s\"\n)\n\n// InstallDir represents the directory where the assets should be installed.\n//\n// NOTE: This is a package level variable as it makes testing the behaviour of\n// the package easier because the test code can replace the value when running\n// the test suite.\nvar InstallDir = func() string {\n\tif dir, err := os.UserConfigDir(); err == nil {\n\t\treturn filepath.Join(dir, \"fastly\")\n\t}\n\tif dir, err := os.UserHomeDir(); err == nil {\n\t\treturn filepath.Join(dir, \".fastly\")\n\t}\n\tpanic(\"unable to deduce user config dir or user home dir\")\n}()\n\n// New returns a usable asset.\nfunc New(opts Opts) *Asset {\n\tbinary := opts.Binary\n\tif fstruntime.Windows && filepath.Ext(binary) == \"\" {\n\t\tbinary += \".exe\"\n\t}\n\n\treturn &Asset{\n\t\tbinary:           binary,\n\t\tdebug:            opts.DebugMode,\n\t\texternal:         opts.External,\n\t\thttpClient:       opts.HTTPClient,\n\t\tnested:           opts.Nested,\n\t\torg:              opts.Org,\n\t\trepo:             opts.Repo,\n\t\tversionRequested: opts.Version,\n\t}\n}\n\n// Opts represents options to be passed to NewGitHub.\ntype Opts struct {\n\t// Binary is the name of the executable binary.\n\tBinary string\n\t// DebugMode indicates the user has set debug-mode.\n\tDebugMode bool\n\t// External indicates the repository is a non-Fastly repo.\n\t// This means we need a custom metadata fetcher (i.e. dont use metadataURL).\n\tExternal bool\n\t// HTTPClient is able to make HTTP requests.\n\tHTTPClient api.HTTPClient\n\t// Nested indicates if the binary is at the root of the archive or not.\n\t// e.g. wasm-tools archive contains a folder which contains the binary.\n\t// Where as Viceroy and CLI archives directly contain the binary.\n\tNested bool\n\t// Org is a GitHub organisation.\n\tOrg string\n\t// Repo is a GitHub repository.\n\tRepo string\n\t// Version is the asset's release version to download.\n\t// The value is the semver format (example: \"0.1.0\").\n\t// If not set, then the latest version is implied.\n\tVersion string\n}\n\n// Asset is a versioner that uses Asset releases.\ntype Asset struct {\n\t// binary is the name of the executable binary.\n\tbinary string\n\t// debug indicates if the user is running in debug-mode.\n\tdebug bool\n\t// external indicates the repository is a non-Fastly repo.\n\texternal bool\n\t// httpClient is able to make HTTP requests.\n\thttpClient api.HTTPClient\n\t// nested indicates if the binary is at the root of the archive or not.\n\tnested bool\n\t// org is a GitHub organisation.\n\torg string\n\t// repo is a GitHub repository.\n\trepo string\n\t// url is the endpoint for downloading the release asset.\n\turl string\n\t// version is the release version of the asset.\n\tversion string\n\t// versionRequested is the requested release version of the asset.\n\tversionRequested string\n}\n\n// BinaryName returns the configured binary output name.\n//\n// NOTE: For some operating systems this might include a file extension, such\n// as .exe for Windows.\nfunc (g Asset) BinaryName() string {\n\treturn g.binary\n}\n\n// DownloadLatest retrieves the latest binary version.\nfunc (g *Asset) DownloadLatest() (bin string, err error) {\n\tendpoint, err := g.URL()\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn g.Download(endpoint)\n}\n\n// DownloadVersion retrieves the specified binary version.\nfunc (g *Asset) DownloadVersion(version string) (bin string, err error) {\n\t_, err = semver.Parse(version)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tendpoint, err := g.URL()\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tendpoint = strings.ReplaceAll(endpoint, g.version, version)\n\n\treturn g.Download(endpoint)\n}\n\n// Download retrieves the binary archive format from the specified endpoint.\nfunc (g *Asset) Download(endpoint string) (bin string, err error) {\n\treq, err := http.NewRequest(http.MethodGet, endpoint, nil)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to create a HTTP request: %w\", err)\n\t}\n\n\tif g.httpClient == nil {\n\t\tg.httpClient = http.DefaultClient\n\t}\n\tif g.debug {\n\t\tdebug.DumpHTTPRequest(req)\n\t}\n\tres, err := g.httpClient.Do(req)\n\tif g.debug {\n\t\tdebug.DumpHTTPResponse(res)\n\t}\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to request GitHub release asset: %w\", err)\n\t}\n\tif res.StatusCode != http.StatusOK {\n\t\treturn \"\", fmt.Errorf(\"failed to request GitHub release asset: %s\", res.Status)\n\t}\n\tdefer res.Body.Close() // #nosec G307\n\n\ttmpDir, err := os.MkdirTemp(\"\", \"fastly-download\")\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to create temp release directory: %w\", err)\n\t}\n\tdefer os.RemoveAll(tmpDir)\n\n\tassetBase := filepath.Base(endpoint)\n\tarchive, err := createArchive(assetBase, tmpDir, res.Body)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\textractedBinary, err := extractBinary(archive, g.binary, tmpDir, assetBase, g.nested)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\treturn moveExtractedBinary(g.binary, extractedBinary)\n}\n\n// URL returns the downloadable asset URL if set, otherwise calls the API metadata endpoint.\nfunc (g *Asset) URL() (url string, err error) {\n\tif g.url != \"\" {\n\t\treturn g.url, nil\n\t}\n\n\tm, err := g.metadata()\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tg.url = m.URL\n\tg.version = m.Version\n\n\treturn g.url, nil\n}\n\n// LatestVersion returns the asset LatestVersion if set, otherwise calls the API metadata endpoint.\nfunc (g *Asset) LatestVersion() (version string, err error) {\n\tif g.version != \"\" {\n\t\treturn g.version, nil\n\t}\n\n\tm, err := g.metadata()\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tg.url = m.URL\n\tg.version = m.Version\n\n\treturn g.version, nil\n}\n\n// RequestedVersion returns the version of the asset defined in the fastly.toml.\n// NOTE: This is only relevant for `compute serve` with viceroy_version pinning.\nfunc (g *Asset) RequestedVersion() string {\n\treturn g.versionRequested\n}\n\n// SetRequestedVersion sets the version of the asset to be downloaded.\n// This is typically used by `compute serve` when an `--env` flag is set.\nfunc (g *Asset) SetRequestedVersion(version string) {\n\tg.versionRequested = version\n}\n\n// metadata acquires GitHub metadata.\nfunc (g *Asset) metadata() (m DevHubMetadata, err error) {\n\tendpoint := fmt.Sprintf(metadataURL, g.repo, runtime.GOOS, runtime.GOARCH)\n\tif g.external {\n\t\tendpoint = fmt.Sprintf(\"https://api.github.com/repos/%s/%s/releases/latest\", g.org, g.repo)\n\t}\n\n\treq, err := http.NewRequest(http.MethodGet, endpoint, nil)\n\tif err != nil {\n\t\treturn m, fmt.Errorf(\"failed to create a HTTP request: %w\", err)\n\t}\n\n\tif g.httpClient == nil {\n\t\tg.httpClient = http.DefaultClient\n\t}\n\tif g.debug {\n\t\tdebug.DumpHTTPRequest(req)\n\t}\n\tres, err := g.httpClient.Do(req)\n\tif g.debug {\n\t\tdebug.DumpHTTPResponse(res)\n\t}\n\tif err != nil {\n\t\treturn m, fmt.Errorf(\"failed to request GitHub metadata: %w\", err)\n\t}\n\tdefer res.Body.Close()\n\tif res.StatusCode != http.StatusOK {\n\t\treturn m, fmt.Errorf(\"failed to request GitHub metadata: %s\", res.Status)\n\t}\n\n\tdata, err := io.ReadAll(res.Body)\n\tif err != nil {\n\t\treturn m, fmt.Errorf(\"failed to read GitHub's metadata response: %w\", err)\n\t}\n\n\tif g.external {\n\t\treturn g.parseExternalMetadata(data)\n\t}\n\n\terr = json.Unmarshal(data, &m)\n\tif err != nil {\n\t\treturn m, fmt.Errorf(\"failed to parse GitHub's metadata: %w\", err)\n\t}\n\n\treturn m, nil\n}\n\n// InstallPath returns the location of where the asset should be installed.\nfunc (g *Asset) InstallPath() string {\n\treturn filepath.Join(InstallDir, g.BinaryName())\n}\n\n// DevHubMetadata represents the DevHub API response for software metadata.\ntype DevHubMetadata struct {\n\t// URL is the endpoint for downloading the release asset.\n\tURL string `json:\"url\"`\n\t// Version is the release version of the asset (e.g. 10.1.0).\n\tVersion string `json:\"version\"`\n}\n\n// AssetVersioner describes a source of CLI release artifacts.\ntype AssetVersioner interface {\n\t// BinaryName returns the configured binary output name.\n\tBinaryName() string\n\t// Download downloads the asset from the specified endpoint.\n\tDownload(endpoint string) (bin string, err error)\n\t// DownloadLatest downloads the latest version of the asset.\n\tDownloadLatest() (bin string, err error)\n\t// DownloadVersion downloads the specified version of the asset.\n\tDownloadVersion(version string) (bin string, err error)\n\t// InstallPath returns the location of where the binary should be installed.\n\tInstallPath() string\n\t// RequestedVersion returns the version defined in the fastly.toml file.\n\tRequestedVersion() (version string)\n\t// SetRequestedVersion sets the version of the asset to be downloaded.\n\tSetRequestedVersion(version string)\n\t// URL returns the asset URL if set, otherwise calls the API metadata endpoint.\n\tURL() (url string, err error)\n\t// LatestVersion returns the latest version.\n\tLatestVersion() (version string, err error)\n}\n\n// createArchive copies the DevHub response body data into a temporary archive\n// file and returns the path to the file.\nfunc createArchive(assetBase, tmpDir string, data io.ReadCloser) (path string, err error) {\n\t// gosec flagged this:\n\t// G304 (CWE-22): Potential file inclusion via variable\n\t//\n\t// Disabling as the inputs need to be dynamically determined.\n\t// #nosec\n\tarchive, err := os.Create(filepath.Join(tmpDir, assetBase))\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to create a temporary file: %w\", err)\n\t}\n\n\t_, err = io.Copy(archive, data)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to copy the release asset response body: %w\", err)\n\t}\n\n\tif err := archive.Close(); err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to close release asset file: %w\", err)\n\t}\n\n\treturn archive.Name(), nil\n}\n\n// extractBinary extracts the executable binary (e.g. fastly, viceroy,\n// wasm-tools) from the specified archive file, modifies its permissions and\n// returns the path.\n//\n// NOTE: wasm-tools binary is within a nested directory.\n// So we have to account for that by extracting the directory from the archive\n// and then correct the path before attempting to modify the permissions.\nfunc extractBinary(archive, binaryName, dst, assetBase string, nested bool) (bin string, err error) {\n\textractPath := binaryName\n\tif nested {\n\t\textension := \".tar.gz\"\n\t\tif fstruntime.Windows {\n\t\t\textension = \".zip\"\n\t\t}\n\t\t// e.g. extract the nested directory \"wasm-tools-1.0.42-aarch64-macos\"\n\t\t// which itself contains the `wasm-tools` binary\n\t\textractPath = strings.TrimSuffix(assetBase, extension)\n\t}\n\n\t// Extract using shared utility function\n\tif err := file.ExtractArchive(archive, dst, func(name string) bool {\n\t\treturn strings.HasPrefix(name, extractPath)\n\t}); err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to extract binary: %w\", err)\n\t}\n\n\textractedBinary := filepath.Join(dst, binaryName)\n\tif nested {\n\t\t// e.g. reference the binary from within the nested directory\n\t\textractedBinary = filepath.Join(dst, extractPath, binaryName)\n\t}\n\n\t// G302 (CWE-276): Expect file permissions to be 0600 or less\n\t// gosec flagged this:\n\t// Disabling as the file was not executable without it and we need all users\n\t// to be able to execute the binary.\n\t/* #nosec */\n\terr = os.Chmod(extractedBinary, 0o755)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to modify permissions on extracted binary: %w\", err)\n\t}\n\n\treturn extractedBinary, nil\n}\n\n// moveExtractedBinary creates a temporary file (representing the final\n// executable binary) and moves the oldpath to it and returns its path.\nfunc moveExtractedBinary(binName, oldpath string) (path string, err error) {\n\ttmpBin, err := os.CreateTemp(\"\", binName)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to create temp file: %w\", err)\n\t}\n\n\tdefer func(name string) {\n\t\tif err != nil {\n\t\t\t_ = os.Remove(name)\n\t\t}\n\t}(tmpBin.Name())\n\n\tif err := tmpBin.Close(); err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to close temp file: %w\", err)\n\t}\n\n\tif err := os.Rename(oldpath, tmpBin.Name()); err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to rename release asset file: %w\", err)\n\t}\n\n\treturn tmpBin.Name(), nil\n}\n\n// SetBinPerms ensures 0777 perms are set on the binary.\nfunc SetBinPerms(bin string) error {\n\t// G302 (CWE-276): Expect file permissions to be 0600 or less\n\t// gosec flagged this:\n\t// Disabling as the file was not executable without it and we need all users\n\t// to be able to execute the binary.\n\t// #nosec\n\terr := os.Chmod(bin, 0o777)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error setting executable permissions for %s: %w\", bin, err)\n\t}\n\treturn nil\n}\n\n// RawAsset represents a GitHub release asset.\ntype RawAsset struct {\n\t// BrowserDownloadURL is a fully qualified URL to download the release asset.\n\tBrowserDownloadURL string `json:\"browser_download_url\"`\n}\n\n// Metadata represents the GitHub API metadata response for releases.\ntype Metadata struct {\n\t// Name is the release name.\n\tName string `json:\"name\"`\n\t// Assets a list of all available assets within the release.\n\tAssets []RawAsset `json:\"assets\"`\n\n\torg, repo, binary string\n}\n\n// Version parses a semver from the name field.\nfunc (m Metadata) Version() string {\n\tr := regexp.MustCompile(`[0-9]+\\.[0-9]+\\.[0-9]+(-(.*))?`)\n\treturn r.FindString(m.Name)\n}\n\n// URL filters the assets for a platform correct asset.\n//\n// NOTE: This only works with wasm-tools naming conventions.\n// If we add more tools to download in future then we can abstract as necessary.\nfunc (m Metadata) URL() string {\n\tplatform := runtime.GOOS\n\tif platform == \"darwin\" {\n\t\tplatform = \"macos\"\n\t}\n\n\tarch := runtime.GOARCH\n\tswitch arch {\n\tcase \"arm64\":\n\t\tarch = \"aarch64\"\n\tcase \"amd64\":\n\t\tarch = \"x86_64\"\n\t}\n\n\textension := \"tar.gz\"\n\tif fstruntime.Windows {\n\t\textension = \"zip\"\n\t}\n\n\tfor _, a := range m.Assets {\n\t\tversion := m.Version()\n\t\t// NOTE: We use `m.repo` for wasm-tools instead of `m.binary`.\n\t\t// This is because we append `.exe` to `m.binary` on Windows.\n\t\t// Instead of filtering the extension we just use `m.repo` instead.\n\t\tpattern := fmt.Sprintf(\"https://github.com/%s/%s/releases/download/v%s/%s-%s-%s-%s.%s\", m.org, m.repo, version, m.repo, version, arch, platform, extension)\n\t\tif matched, _ := regexp.MatchString(pattern, a.BrowserDownloadURL); matched {\n\t\t\treturn a.BrowserDownloadURL\n\t\t}\n\t}\n\n\treturn \"\"\n}\n\n// parseExternalMetadata takes the raw GitHub metadata and coerces it into a\n// DevHub specific metadata format.\nfunc (g *Asset) parseExternalMetadata(data []byte) (DevHubMetadata, error) {\n\tvar (\n\t\tdhm DevHubMetadata\n\t\tm   Metadata\n\t)\n\n\terr := json.Unmarshal(data, &m)\n\tif err != nil {\n\t\treturn dhm, fmt.Errorf(\"failed to parse GitHub's metadata: %w\", err)\n\t}\n\n\tm.org = g.org\n\tm.repo = g.repo\n\tm.binary = g.binary\n\n\tdhm.Version = m.Version()\n\tdhm.URL = m.URL()\n\n\treturn dhm, nil\n}\n"
  },
  {
    "path": "pkg/github/github_test.go",
    "content": "package github\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"runtime\"\n\t\"testing\"\n\n\tfstruntime \"github.com/fastly/cli/pkg/runtime\"\n)\n\n// TestDownloadArchiveExtract validates both Windows and Unix release assets.\nfunc TestDownloadArchiveExtract(t *testing.T) {\n\tscenarios := []struct {\n\t\tPlatform string\n\t\tArch     string\n\t\tExt      string\n\t}{\n\t\t{\n\t\t\tPlatform: \"darwin\",\n\t\t\tArch:     \"arm64\",\n\t\t\tExt:      \".tar.gz\",\n\t\t},\n\t\t{\n\t\t\tPlatform: \"darwin\",\n\t\t\tArch:     \"amd64\",\n\t\t\tExt:      \".tar.gz\",\n\t\t},\n\t\t{\n\t\t\tPlatform: \"windows\",\n\t\t\tArch:     \"amd64\",\n\t\t\tExt:      \".zip\",\n\t\t},\n\t}\n\n\tfor _, testcase := range scenarios {\n\t\tname := fmt.Sprintf(\"%s_%s\", testcase.Platform, testcase.Arch)\n\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\t// Avoid, for example, running the Windows OS scenario on non Windows OS.\n\t\t\t// Otherwise, the Windows OS scenario would show on Darwin an error like:\n\t\t\t// no asset found for your OS (darwin) and architecture (amd64)\n\t\t\tif runtime.GOOS != testcase.Platform || runtime.GOARCH != testcase.Arch {\n\t\t\t\tt.Skip()\n\t\t\t}\n\n\t\t\tbinary := \"fastly\"\n\t\t\tif fstruntime.Windows {\n\t\t\t\tbinary += \".exe\"\n\t\t\t}\n\n\t\t\ta := Asset{\n\t\t\t\tbinary: binary,\n\t\t\t\torg:    \"fastly\",\n\t\t\t\trepo:   \"cli\",\n\t\t\t}\n\n\t\t\t// IMPORTANT: This is a real network end-to-end integration test.\n\t\t\t// Meaning, we are making a real request to the DevHub endpoint.\n\t\t\tbin, err := a.DownloadLatest()\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t\t\t}\n\n\t\t\tif err := os.RemoveAll(bin); err != nil {\n\t\t\t\tt.Fatalf(\"unexpected error: %s\", err)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/global/doc.go",
    "content": "// Package global exposes a type to contain global'ish data.\n// Effectively we use it to avoid an unfortunate import loop issue.\npackage global\n"
  },
  {
    "path": "pkg/global/global.go",
    "content": "package global\n\nimport (\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/api\"\n\t\"github.com/fastly/cli/pkg/auth\"\n\t\"github.com/fastly/cli/pkg/config\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/github\"\n\t\"github.com/fastly/cli/pkg/lookup\"\n\t\"github.com/fastly/cli/pkg/manifest\"\n)\n\n// DefaultAPIEndpoint is the default Fastly API endpoint.\nconst DefaultAPIEndpoint = \"https://api.fastly.com\"\n\n// DefaultAccountEndpoint is the default Fastly Accounts endpoint.\nconst DefaultAccountEndpoint = \"https://accounts.fastly.com\"\n\n// APIClientFactory creates a Fastly API client (modeled as an api.Interface)\n// from a user-provided API token. It exists as a type in order to parameterize\n// the Run helper with it: in the real CLI, we can use NewClient from the Fastly\n// API client library via RealClient; in tests, we can provide a mock API\n// interface via MockClient.\ntype APIClientFactory func(token, apiEndpoint string, debugMode bool) (api.Interface, error)\n\n// Versioners represents all supported versioner types.\ntype Versioners struct {\n\tCLI       github.AssetVersioner\n\tViceroy   github.AssetVersioner\n\tWasmTools github.AssetVersioner\n}\n\n// Data holds global-ish configuration data from all sources: environment\n// variables, config files, and flags. It has methods to give each parameter to\n// the components that need it, including the place the parameter came from,\n// which is a requirement.\n//\n// If the same parameter is defined in multiple places, it is resolved according\n// to the following priority order: the config file (lowest priority), env vars,\n// and then explicit flags (highest priority).\n//\n// This package and its types are only meant for parameters that are applicable\n// to most/all subcommands (e.g. API token) and are consistent for a given user\n// (e.g. an email address). Otherwise, parameters should be defined in specific\n// command structs, and parsed as flags.\ntype Data struct {\n\t// APIClient is a Fastly API client instance.\n\tAPIClient api.Interface\n\t// APIClientFactory is a factory function for creating an api.Interface type.\n\tAPIClientFactory APIClientFactory\n\t// Args are the command line arguments provided by the user.\n\tArgs []string\n\t// AuthServer is an instance of the authentication server type.\n\t// Used for interacting with Fastly's SSO/OAuth authentication provider.\n\tAuthServer auth.Runner\n\t// Config is an instance of the CLI configuration data.\n\tConfig config.File\n\t// ConfigPath is the path to the CLI's application configuration.\n\tConfigPath string\n\t// Env is all the data that is provided by the environment.\n\tEnv config.Environment\n\t// ErrLog provides an interface for recording errors to disk.\n\tErrLog    fsterr.LogInterface\n\tErrOutput io.Writer\n\t// ExecuteWasmTools is a function that executes the wasm-tools binary.\n\tExecuteWasmTools func(bin string, args []string, global *Data) error\n\t// Flags are all the global CLI flags.\n\tFlags Flags\n\t// HTTPClient is a HTTP client.\n\tHTTPClient api.HTTPClient\n\t// Input is the standard input for accepting input from the user.\n\tInput io.Reader\n\t// Manifest represents the fastly.toml manifest file and associated flags.\n\tManifest *manifest.Data\n\t// Opener is a function that can open a browser window.\n\tOpener func(string) error\n\t// Output is the output for displaying information (typically os.Stdout)\n\tOutput io.Writer\n\t// RTSClient is a Fastly API client instance for the Real Time Stats endpoints.\n\tRTSClient api.RealtimeStatsInterface\n\t// SkipAuthPrompt is used to indicate to the `sso` command that the\n\t// interactive prompt can be skipped. This is for scenarios where the command\n\t// is executed directly by the user.\n\tSkipAuthPrompt bool\n\t// SSORunner runs the SSO authentication flow. It is set by commands.Define()\n\t// so that app/run.go can invoke SSO without a registered command.\n\tSSORunner func(in io.Reader, out io.Writer, forceReAuth bool, skipPrompt bool) error\n\t// Versioners contains multiple software versioning checkers.\n\t// e.g. Check for latest CLI or Viceroy version.\n\tVersioners Versioners\n}\n\n// Token yields the Fastly API token.\n//\n// Order of precedence:\n//   - The --token flag (if it matches a stored auth token name, use that token).\n//   - The --token flag (treated as a raw API token).\n//   - The --profile/-o flag (must match a stored auth token name).\n//   - The FASTLY_API_TOKEN environment variable.\n//   - The `profile` manifest field mapped to an auth token name.\n//   - The default [auth] token (if configured).\nfunc (d *Data) Token() (string, lookup.Source) {\n\tif d.Flags.Token != \"\" {\n\t\tif at := d.Config.GetAuthToken(d.Flags.Token); at != nil && at.Token != \"\" {\n\t\t\treturn at.Token, lookup.SourceAuth\n\t\t}\n\t\treturn d.Flags.Token, lookup.SourceFlag\n\t}\n\n\tif d.Flags.Profile != \"\" {\n\t\tif at, ok := d.profileFlagToken(); ok {\n\t\t\treturn at.Token, lookup.SourceAuth\n\t\t}\n\t\treturn \"\", lookup.SourceUndefined\n\t}\n\n\tif d.Env.APIToken != \"\" {\n\t\treturn d.Env.APIToken, lookup.SourceEnvironment\n\t}\n\n\tif d.Manifest != nil && d.Manifest.File.Profile != \"\" {\n\t\tif at := d.Config.GetAuthToken(d.Manifest.File.Profile); at != nil && at.Token != \"\" {\n\t\t\treturn at.Token, lookup.SourceAuth\n\t\t}\n\t}\n\n\tif _, at := d.Config.GetDefaultAuthToken(); at != nil && at.Token != \"\" {\n\t\treturn at.Token, lookup.SourceAuth\n\t}\n\n\treturn \"\", lookup.SourceUndefined\n}\n\nfunc (d *Data) profileFlagToken() (*config.AuthToken, bool) {\n\tif d.Flags.Profile == \"\" {\n\t\treturn nil, false\n\t}\n\tat := d.Config.GetAuthToken(d.Flags.Profile)\n\tif at == nil || at.Token == \"\" {\n\t\treturn nil, false\n\t}\n\treturn at, true\n}\n\n// ValidateProfileFlag returns an error if --profile/-o is set to a name that\n// does not resolve to a stored auth token. --token outranks --profile and\n// short-circuits the check.\nfunc (d *Data) ValidateProfileFlag() error {\n\tif d.Flags.Token != \"\" || d.Flags.Profile == \"\" {\n\t\treturn nil\n\t}\n\tif _, ok := d.profileFlagToken(); ok {\n\t\treturn nil\n\t}\n\treturn fsterr.ErrProfileFlagNotFound(d.Flags.Profile)\n}\n\n// AuthTokenName returns the name of the auth token being used, if any.\n// This is used for display purposes and for SSO refresh of named tokens.\nfunc (d *Data) AuthTokenName() string {\n\tif d.Flags.Token != \"\" {\n\t\tif at := d.Config.GetAuthToken(d.Flags.Token); at != nil {\n\t\t\treturn d.Flags.Token\n\t\t}\n\t\treturn \"\"\n\t}\n\n\tif d.Flags.Profile != \"\" {\n\t\tif _, ok := d.profileFlagToken(); ok {\n\t\t\treturn d.Flags.Profile\n\t\t}\n\t\treturn \"\"\n\t}\n\n\tif d.Manifest != nil && d.Manifest.File.Profile != \"\" {\n\t\tif at := d.Config.GetAuthToken(d.Manifest.File.Profile); at != nil {\n\t\t\treturn d.Manifest.File.Profile\n\t\t}\n\t}\n\tname, _ := d.Config.GetDefaultAuthToken()\n\treturn name\n}\n\n// Verbose yields the verbose flag, which can only be set via flags.\nfunc (d *Data) Verbose() bool {\n\treturn d.Flags.Verbose\n}\n\n// APIEndpoint yields the API endpoint.\nfunc (d *Data) APIEndpoint() (string, lookup.Source) {\n\tif d.Flags.APIEndpoint != \"\" {\n\t\treturn d.Flags.APIEndpoint, lookup.SourceFlag\n\t}\n\n\tif d.Env.APIEndpoint != \"\" {\n\t\treturn d.Env.APIEndpoint, lookup.SourceEnvironment\n\t}\n\n\tif d.Config.Fastly.APIEndpoint != DefaultAPIEndpoint && d.Config.Fastly.APIEndpoint != \"\" {\n\t\treturn d.Config.Fastly.APIEndpoint, lookup.SourceFile\n\t}\n\n\treturn DefaultAPIEndpoint, lookup.SourceDefault // this method should not fail\n}\n\n// AccountEndpoint yields the Accounts endpoint.\nfunc (d *Data) AccountEndpoint() (string, lookup.Source) {\n\tif d.Flags.AccountEndpoint != \"\" {\n\t\treturn d.Flags.AccountEndpoint, lookup.SourceFlag\n\t}\n\n\tif d.Env.AccountEndpoint != \"\" {\n\t\treturn d.Env.AccountEndpoint, lookup.SourceEnvironment\n\t}\n\n\tif d.Config.Fastly.AccountEndpoint != DefaultAccountEndpoint && d.Config.Fastly.AccountEndpoint != \"\" {\n\t\treturn d.Config.Fastly.AccountEndpoint, lookup.SourceFile\n\t}\n\n\treturn DefaultAccountEndpoint, lookup.SourceDefault // this method should not fail\n}\n\n// Flags represents all of the configuration parameters that can be set with\n// explicit flags. Consumers should bind their flag values to these fields\n// directly.\n//\n// IMPORTANT: Kingpin doesn't support global flags.\n// We hack a solution in ../app/run.go (`configureKingpin` function).\ntype Flags struct {\n\t// AcceptDefaults auto-resolves prompts with a default defined.\n\tAcceptDefaults bool\n\t// AccountEndpoint is the authentication host address.\n\tAccountEndpoint string\n\t// APIEndpoint is the Fastly API address.\n\tAPIEndpoint string\n\t// AutoYes auto-resolves Yes/No prompts by answering \"Yes\".\n\tAutoYes bool\n\t// Debug enables the CLI's debug mode.\n\tDebug bool\n\t// JSON indicates --json output was requested. Detected automatically by\n\t// Exec. Unlike Quiet, JSON mode does not suppress stderr warnings.\n\tJSON bool\n\t// NonInteractive auto-resolves all prompts.\n\tNonInteractive bool\n\t// Profile indicates the profile to use (consequently the 'token' used).\n\tProfile string\n\t// Quiet silences all output except direct command output.\n\tQuiet bool\n\t// SSO enables SSO authentication tokens for the current profile.\n\tSSO bool\n\t// Token is an override for a profile (when passed SSO is disabled).\n\tToken string\n\t// Verbose prints additional output.\n\tVerbose bool\n}\n"
  },
  {
    "path": "pkg/global/global_test.go",
    "content": "package global_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/fastly/cli/pkg/config\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/lookup\"\n\t\"github.com/fastly/cli/pkg/manifest\"\n\t\"github.com/fastly/cli/pkg/threadsafe\"\n)\n\nfunc TestToken(t *testing.T) {\n\ttests := []struct {\n\t\tname       string\n\t\tdata       *global.Data\n\t\twantToken  string\n\t\twantSource lookup.Source\n\t}{\n\t\t{\n\t\t\tname: \"token flag matches stored auth token name\",\n\t\t\tdata: &global.Data{\n\t\t\t\tFlags: global.Flags{Token: \"myname\"},\n\t\t\t\tConfig: config.File{\n\t\t\t\t\tAuth: config.Auth{\n\t\t\t\t\t\tDefault: \"myname\",\n\t\t\t\t\t\tTokens: config.AuthTokens{\n\t\t\t\t\t\t\t\"myname\": &config.AuthToken{\n\t\t\t\t\t\t\t\tType:  config.AuthTokenTypeStatic,\n\t\t\t\t\t\t\t\tToken: \"stored-token-value\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantToken:  \"stored-token-value\",\n\t\t\twantSource: lookup.SourceAuth,\n\t\t},\n\t\t{\n\t\t\tname: \"token flag raw value when no stored name matches\",\n\t\t\tdata: &global.Data{\n\t\t\t\tFlags: global.Flags{Token: \"raw-api-token\"},\n\t\t\t\tConfig: config.File{\n\t\t\t\t\tAuth: config.Auth{\n\t\t\t\t\t\tDefault: \"user\",\n\t\t\t\t\t\tTokens: config.AuthTokens{\n\t\t\t\t\t\t\t\"user\": &config.AuthToken{\n\t\t\t\t\t\t\t\tType:  config.AuthTokenTypeStatic,\n\t\t\t\t\t\t\t\tToken: \"other-token\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantToken:  \"raw-api-token\",\n\t\t\twantSource: lookup.SourceFlag,\n\t\t},\n\t\t{\n\t\t\tname: \"manifest profile selects stored auth token\",\n\t\t\tdata: &global.Data{\n\t\t\t\tManifest: &manifest.Data{\n\t\t\t\t\tFile: manifest.File{Profile: \"proj\"},\n\t\t\t\t},\n\t\t\t\tConfig: config.File{\n\t\t\t\t\tAuth: config.Auth{\n\t\t\t\t\t\tDefault: \"default-user\",\n\t\t\t\t\t\tTokens: config.AuthTokens{\n\t\t\t\t\t\t\t\"default-user\": &config.AuthToken{\n\t\t\t\t\t\t\t\tType:  config.AuthTokenTypeStatic,\n\t\t\t\t\t\t\t\tToken: \"default-token\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\"proj\": &config.AuthToken{\n\t\t\t\t\t\t\t\tType:  config.AuthTokenTypeStatic,\n\t\t\t\t\t\t\t\tToken: \"project-token\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantToken:  \"project-token\",\n\t\t\twantSource: lookup.SourceAuth,\n\t\t},\n\t\t{\n\t\t\tname: \"manifest profile falls through when no matching auth token\",\n\t\t\tdata: &global.Data{\n\t\t\t\tManifest: &manifest.Data{\n\t\t\t\t\tFile: manifest.File{Profile: \"missing\"},\n\t\t\t\t},\n\t\t\t\tConfig: config.File{\n\t\t\t\t\tAuth: config.Auth{\n\t\t\t\t\t\tDefault: \"user\",\n\t\t\t\t\t\tTokens: config.AuthTokens{\n\t\t\t\t\t\t\t\"user\": &config.AuthToken{\n\t\t\t\t\t\t\t\tType:  config.AuthTokenTypeStatic,\n\t\t\t\t\t\t\t\tToken: \"default-token\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantToken:  \"default-token\",\n\t\t\twantSource: lookup.SourceAuth,\n\t\t},\n\t\t{\n\t\t\tname: \"env var takes precedence over manifest profile\",\n\t\t\tdata: &global.Data{\n\t\t\t\tEnv: config.Environment{APIToken: \"env-token\"},\n\t\t\t\tManifest: &manifest.Data{\n\t\t\t\t\tFile: manifest.File{Profile: \"proj\"},\n\t\t\t\t},\n\t\t\t\tConfig: config.File{\n\t\t\t\t\tAuth: config.Auth{\n\t\t\t\t\t\tDefault: \"user\",\n\t\t\t\t\t\tTokens: config.AuthTokens{\n\t\t\t\t\t\t\t\"proj\": &config.AuthToken{\n\t\t\t\t\t\t\t\tType:  config.AuthTokenTypeStatic,\n\t\t\t\t\t\t\t\tToken: \"project-token\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantToken:  \"env-token\",\n\t\t\twantSource: lookup.SourceEnvironment,\n\t\t},\n\t\t{\n\t\t\tname: \"no token sources returns undefined\",\n\t\t\tdata: &global.Data{\n\t\t\t\tConfig: config.File{},\n\t\t\t},\n\t\t\twantToken:  \"\",\n\t\t\twantSource: lookup.SourceUndefined,\n\t\t},\n\t\t{\n\t\t\tname: \"profile flag matches stored auth token\",\n\t\t\tdata: &global.Data{\n\t\t\t\tFlags: global.Flags{Profile: \"alt\"},\n\t\t\t\tConfig: config.File{\n\t\t\t\t\tAuth: config.Auth{\n\t\t\t\t\t\tDefault: \"user\",\n\t\t\t\t\t\tTokens: config.AuthTokens{\n\t\t\t\t\t\t\t\"user\": &config.AuthToken{Type: config.AuthTokenTypeStatic, Token: \"default-token\"},\n\t\t\t\t\t\t\t\"alt\":  &config.AuthToken{Type: config.AuthTokenTypeStatic, Token: \"alt-token\"},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantToken:  \"alt-token\",\n\t\t\twantSource: lookup.SourceAuth,\n\t\t},\n\t\t{\n\t\t\tname: \"profile flag unknown does not fall back even when env/manifest/default exist\",\n\t\t\tdata: &global.Data{\n\t\t\t\tFlags: global.Flags{Profile: \"missing\"},\n\t\t\t\tEnv:   config.Environment{APIToken: \"env-token\"},\n\t\t\t\tManifest: &manifest.Data{\n\t\t\t\t\tFile: manifest.File{Profile: \"proj\"},\n\t\t\t\t},\n\t\t\t\tConfig: config.File{\n\t\t\t\t\tAuth: config.Auth{\n\t\t\t\t\t\tDefault: \"user\",\n\t\t\t\t\t\tTokens: config.AuthTokens{\n\t\t\t\t\t\t\t\"user\": &config.AuthToken{Type: config.AuthTokenTypeStatic, Token: \"default-token\"},\n\t\t\t\t\t\t\t\"proj\": &config.AuthToken{Type: config.AuthTokenTypeStatic, Token: \"project-token\"},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantToken:  \"\",\n\t\t\twantSource: lookup.SourceUndefined,\n\t\t},\n\t\t{\n\t\t\tname: \"profile flag matches stored entry with empty token returns undefined\",\n\t\t\tdata: &global.Data{\n\t\t\t\tFlags: global.Flags{Profile: \"blank\"},\n\t\t\t\tConfig: config.File{\n\t\t\t\t\tAuth: config.Auth{\n\t\t\t\t\t\tDefault: \"user\",\n\t\t\t\t\t\tTokens: config.AuthTokens{\n\t\t\t\t\t\t\t\"user\":  &config.AuthToken{Type: config.AuthTokenTypeStatic, Token: \"default-token\"},\n\t\t\t\t\t\t\t\"blank\": &config.AuthToken{Type: config.AuthTokenTypeStatic, Token: \"\"},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantToken:  \"\",\n\t\t\twantSource: lookup.SourceUndefined,\n\t\t},\n\t\t{\n\t\t\tname: \"token flag raw value wins over profile flag\",\n\t\t\tdata: &global.Data{\n\t\t\t\tFlags: global.Flags{Token: \"raw-xyz\", Profile: \"alt\"},\n\t\t\t\tConfig: config.File{\n\t\t\t\t\tAuth: config.Auth{\n\t\t\t\t\t\tTokens: config.AuthTokens{\n\t\t\t\t\t\t\t\"alt\": &config.AuthToken{Type: config.AuthTokenTypeStatic, Token: \"alt-token\"},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantToken:  \"raw-xyz\",\n\t\t\twantSource: lookup.SourceFlag,\n\t\t},\n\t\t{\n\t\t\tname: \"token flag matching stored name wins over profile flag\",\n\t\t\tdata: &global.Data{\n\t\t\t\tFlags: global.Flags{Token: \"primary\", Profile: \"alt\"},\n\t\t\t\tConfig: config.File{\n\t\t\t\t\tAuth: config.Auth{\n\t\t\t\t\t\tTokens: config.AuthTokens{\n\t\t\t\t\t\t\t\"primary\": &config.AuthToken{Type: config.AuthTokenTypeStatic, Token: \"primary-token\"},\n\t\t\t\t\t\t\t\"alt\":     &config.AuthToken{Type: config.AuthTokenTypeStatic, Token: \"alt-token\"},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantToken:  \"primary-token\",\n\t\t\twantSource: lookup.SourceAuth,\n\t\t},\n\t\t{\n\t\t\tname: \"profile flag wins over env var\",\n\t\t\tdata: &global.Data{\n\t\t\t\tFlags: global.Flags{Profile: \"alt\"},\n\t\t\t\tEnv:   config.Environment{APIToken: \"env-token\"},\n\t\t\t\tConfig: config.File{\n\t\t\t\t\tAuth: config.Auth{\n\t\t\t\t\t\tTokens: config.AuthTokens{\n\t\t\t\t\t\t\t\"alt\": &config.AuthToken{Type: config.AuthTokenTypeStatic, Token: \"alt-token\"},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantToken:  \"alt-token\",\n\t\t\twantSource: lookup.SourceAuth,\n\t\t},\n\t\t{\n\t\t\tname: \"profile flag wins over manifest profile\",\n\t\t\tdata: &global.Data{\n\t\t\t\tFlags: global.Flags{Profile: \"alt\"},\n\t\t\t\tManifest: &manifest.Data{\n\t\t\t\t\tFile: manifest.File{Profile: \"proj\"},\n\t\t\t\t},\n\t\t\t\tConfig: config.File{\n\t\t\t\t\tAuth: config.Auth{\n\t\t\t\t\t\tTokens: config.AuthTokens{\n\t\t\t\t\t\t\t\"alt\":  &config.AuthToken{Type: config.AuthTokenTypeStatic, Token: \"alt-token\"},\n\t\t\t\t\t\t\t\"proj\": &config.AuthToken{Type: config.AuthTokenTypeStatic, Token: \"project-token\"},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantToken:  \"alt-token\",\n\t\t\twantSource: lookup.SourceAuth,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgotToken, gotSource := tt.data.Token()\n\t\t\tif gotToken != tt.wantToken {\n\t\t\t\tt.Errorf(\"Token() token = %q, want %q\", gotToken, tt.wantToken)\n\t\t\t}\n\t\t\tif gotSource != tt.wantSource {\n\t\t\t\tt.Errorf(\"Token() source = %v, want %v\", gotSource, tt.wantSource)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestAuthTokenName(t *testing.T) {\n\ttests := []struct {\n\t\tname     string\n\t\tdata     *global.Data\n\t\twantName string\n\t}{\n\t\t{\n\t\t\tname: \"token flag matches stored name\",\n\t\t\tdata: &global.Data{\n\t\t\t\tFlags: global.Flags{Token: \"myname\"},\n\t\t\t\tConfig: config.File{\n\t\t\t\t\tAuth: config.Auth{\n\t\t\t\t\t\tTokens: config.AuthTokens{\n\t\t\t\t\t\t\t\"myname\": &config.AuthToken{Token: \"t\"},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantName: \"myname\",\n\t\t},\n\t\t{\n\t\t\tname: \"token flag raw value returns empty\",\n\t\t\tdata: &global.Data{\n\t\t\t\tFlags: global.Flags{Token: \"raw-value\"},\n\t\t\t\tConfig: config.File{\n\t\t\t\t\tAuth: config.Auth{\n\t\t\t\t\t\tTokens: config.AuthTokens{\n\t\t\t\t\t\t\t\"user\": &config.AuthToken{Token: \"t\"},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantName: \"\",\n\t\t},\n\t\t{\n\t\t\tname: \"manifest profile returns profile name\",\n\t\t\tdata: &global.Data{\n\t\t\t\tManifest: &manifest.Data{\n\t\t\t\t\tFile: manifest.File{Profile: \"proj\"},\n\t\t\t\t},\n\t\t\t\tConfig: config.File{\n\t\t\t\t\tAuth: config.Auth{\n\t\t\t\t\t\tDefault: \"default-user\",\n\t\t\t\t\t\tTokens: config.AuthTokens{\n\t\t\t\t\t\t\t\"default-user\": &config.AuthToken{Token: \"t\"},\n\t\t\t\t\t\t\t\"proj\":         &config.AuthToken{Token: \"t2\"},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantName: \"proj\",\n\t\t},\n\t\t{\n\t\t\tname: \"manifest profile missing falls through to default\",\n\t\t\tdata: &global.Data{\n\t\t\t\tManifest: &manifest.Data{\n\t\t\t\t\tFile: manifest.File{Profile: \"missing\"},\n\t\t\t\t},\n\t\t\t\tConfig: config.File{\n\t\t\t\t\tAuth: config.Auth{\n\t\t\t\t\t\tDefault: \"user\",\n\t\t\t\t\t\tTokens: config.AuthTokens{\n\t\t\t\t\t\t\t\"user\": &config.AuthToken{Token: \"t\"},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantName: \"user\",\n\t\t},\n\t\t{\n\t\t\tname: \"default auth token name\",\n\t\t\tdata: &global.Data{\n\t\t\t\tConfig: config.File{\n\t\t\t\t\tAuth: config.Auth{\n\t\t\t\t\t\tDefault: \"user\",\n\t\t\t\t\t\tTokens: config.AuthTokens{\n\t\t\t\t\t\t\t\"user\": &config.AuthToken{Token: \"t\"},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantName: \"user\",\n\t\t},\n\t\t{\n\t\t\tname: \"profile flag matches stored name with non-empty token\",\n\t\t\tdata: &global.Data{\n\t\t\t\tFlags: global.Flags{Profile: \"alt\"},\n\t\t\t\tConfig: config.File{\n\t\t\t\t\tAuth: config.Auth{\n\t\t\t\t\t\tDefault: \"user\",\n\t\t\t\t\t\tTokens: config.AuthTokens{\n\t\t\t\t\t\t\t\"user\": &config.AuthToken{Token: \"t\"},\n\t\t\t\t\t\t\t\"alt\":  &config.AuthToken{Token: \"alt-token\"},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantName: \"alt\",\n\t\t},\n\t\t{\n\t\t\tname: \"profile flag matches stored entry with empty token returns empty\",\n\t\t\tdata: &global.Data{\n\t\t\t\tFlags: global.Flags{Profile: \"blank\"},\n\t\t\t\tConfig: config.File{\n\t\t\t\t\tAuth: config.Auth{\n\t\t\t\t\t\tDefault: \"user\",\n\t\t\t\t\t\tTokens: config.AuthTokens{\n\t\t\t\t\t\t\t\"user\":  &config.AuthToken{Token: \"t\"},\n\t\t\t\t\t\t\t\"blank\": &config.AuthToken{Token: \"\"},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantName: \"\",\n\t\t},\n\t\t{\n\t\t\tname: \"profile flag unknown returns empty without falling through\",\n\t\t\tdata: &global.Data{\n\t\t\t\tFlags: global.Flags{Profile: \"missing\"},\n\t\t\t\tManifest: &manifest.Data{\n\t\t\t\t\tFile: manifest.File{Profile: \"proj\"},\n\t\t\t\t},\n\t\t\t\tConfig: config.File{\n\t\t\t\t\tAuth: config.Auth{\n\t\t\t\t\t\tDefault: \"user\",\n\t\t\t\t\t\tTokens: config.AuthTokens{\n\t\t\t\t\t\t\t\"user\": &config.AuthToken{Token: \"t\"},\n\t\t\t\t\t\t\t\"proj\": &config.AuthToken{Token: \"t2\"},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantName: \"\",\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot := tt.data.AuthTokenName()\n\t\t\tif got != tt.wantName {\n\t\t\t\tt.Errorf(\"AuthTokenName() = %q, want %q\", got, tt.wantName)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTokenManifestProfileMissingNoSideEffect(t *testing.T) {\n\tvar buf threadsafe.Buffer\n\td := &global.Data{\n\t\tManifest: &manifest.Data{\n\t\t\tFile: manifest.File{Profile: \"missing\"},\n\t\t},\n\t\tConfig: config.File{\n\t\t\tAuth: config.Auth{\n\t\t\t\tDefault: \"user\",\n\t\t\t\tTokens: config.AuthTokens{\n\t\t\t\t\t\"user\": &config.AuthToken{\n\t\t\t\t\t\tType:  config.AuthTokenTypeStatic,\n\t\t\t\t\t\tToken: \"default-token\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tOutput: &buf,\n\t}\n\n\ttoken, source := d.Token()\n\tif token != \"default-token\" {\n\t\tt.Errorf(\"Token() = %q, want %q\", token, \"default-token\")\n\t}\n\tif source != lookup.SourceAuth {\n\t\tt.Errorf(\"Token() source = %v, want %v\", source, lookup.SourceAuth)\n\t}\n\tif buf.String() != \"\" {\n\t\tt.Errorf(\"Token() should not write to output, got: %q\", buf.String())\n\t}\n}\n"
  },
  {
    "path": "pkg/internal/beacon/beacon.go",
    "content": "package beacon\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net/http\"\n\n\t\"github.com/fastly/cli/pkg/api/undocumented\"\n\t\"github.com/fastly/cli/pkg/global\"\n)\n\n// Common event statuses or results.\nconst (\n\tStatusSuccess = \"success\"\n\tStatusFail    = \"fail\"\n)\n\n// Event represents something that happened that we need to signal to\n// the notification relay.\ntype Event struct {\n\tName    string         `json:\"event\"`\n\tStatus  string         `json:\"status\"`\n\tPayload map[string]any `json:\"payload\"`\n}\n\nconst beaconNotify = \"/cli/%s/notify\"\n\n// Notify emits an Event for the given serviceID to the notification\n// relay.\nfunc Notify(g *global.Data, serviceID string, e Event) error {\n\theaders := []undocumented.HTTPHeader{\n\t\t{\n\t\t\tKey:   \"Content-Type\",\n\t\t\tValue: \"application/json\",\n\t\t},\n\t}\n\n\tbody, err := json.Marshal(e)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tco := undocumented.CallOptions{\n\t\tAPIEndpoint: \"https://fastly-notification-relay.edgecompute.app\",\n\t\tPath:        fmt.Sprintf(beaconNotify, serviceID),\n\t\tMethod:      http.MethodPost,\n\t\tHTTPHeaders: headers,\n\t\tHTTPClient:  g.HTTPClient,\n\t\tBody:        bytes.NewReader(body),\n\t}\n\n\t_, err = undocumented.Call(co)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/internal/beacon/beacon_test.go",
    "content": "package beacon_test\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/fastly/cli/pkg/internal/beacon\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n)\n\nfunc TestNotify(t *testing.T) {\n\targs := testutil.SplitArgs(\"compute deploy\")\n\tout := bytes.NewBuffer(nil)\n\tg := testutil.MockGlobalData(args, out)\n\tm := &mockHTTPClient{\n\t\tresp: &http.Response{\n\t\t\tStatusCode: http.StatusNoContent,\n\t\t\tStatus:     http.StatusText(http.StatusNoContent),\n\t\t\tBody:       io.NopCloser(strings.NewReader(\"\")),\n\t\t},\n\t}\n\tg.HTTPClient = m\n\n\terr := beacon.Notify(g, \"service-id\", beacon.Event{\n\t\tName:   \"test-event\",\n\t\tStatus: beacon.StatusSuccess,\n\t})\n\n\ttestutil.AssertNoError(t, err)\n\ttestutil.AssertEqual(t, \"/cli/service-id/notify\", m.req.URL.Path)\n\ttestutil.AssertEqual(t, \"fastly-notification-relay.edgecompute.app\", m.req.URL.Host)\n\n\trawData, err := io.ReadAll(m.req.Body)\n\ttestutil.AssertNoError(t, err)\n\tdefer m.req.Body.Close()\n\n\tvar data map[string]any\n\terr = json.Unmarshal(rawData, &data)\n\ttestutil.AssertNoError(t, err)\n\n\tname, ok := data[\"event\"].(string)\n\ttestutil.AssertBool(t, true, ok)\n\ttestutil.AssertEqual(t, \"test-event\", name)\n\n\tresult, ok := data[\"status\"].(string)\n\ttestutil.AssertBool(t, true, ok)\n\ttestutil.AssertEqual(t, \"success\", result)\n}\n\ntype mockHTTPClient struct {\n\treq  *http.Request\n\tresp *http.Response\n\terr  error\n}\n\nfunc (m *mockHTTPClient) Do(r *http.Request) (*http.Response, error) {\n\tm.req = r\n\treturn m.resp, m.err\n}\n"
  },
  {
    "path": "pkg/internal/beacon/doc.go",
    "content": "// Package beacon sends notifications of events to the\n// fastly-notification-relay, which we use to synchronize state between\n// the UI and the CLI.\npackage beacon\n"
  },
  {
    "path": "pkg/lookup/doc.go",
    "content": "// Package lookup defines an enum that identifies a parameter's source.\npackage lookup\n"
  },
  {
    "path": "pkg/lookup/lookup.go",
    "content": "package lookup\n\n// Source enumerates where the parameter is taken from.\ntype Source uint8\n\nconst (\n\t// SourceUndefined indicates the parameter isn't provided in any of the\n\t// available sources, similar to \"not found\".\n\tSourceUndefined Source = iota\n\n\t// SourceFile indicates the parameter came from a config file.\n\tSourceFile\n\n\t// SourceEnvironment indicates the parameter came from an env var.\n\tSourceEnvironment\n\n\t// SourceFlag indicates the parameter came from an explicit flag.\n\tSourceFlag\n\n\t// SourceDefault indicates the parameter came from a program default.\n\tSourceDefault\n\n\t// SourceAuth indicates the parameter came from the [auth] config section.\n\tSourceAuth\n)\n"
  },
  {
    "path": "pkg/manifest/data.go",
    "content": "package manifest\n\nimport (\n\t\"os\"\n\n\t\"github.com/fastly/cli/pkg/env\"\n)\n\n// Data holds global-ish manifest data from manifest files, and flag sources.\n// It has methods to give each parameter to the components that need it,\n// including the place the parameter came from, which is a requirement.\n//\n// If the same parameter is defined in multiple places, it is resolved according\n// to the following priority order: the manifest file (lowest priority) and then\n// environment variables (where applicable), and explicit flags (highest priority).\ntype Data struct {\n\tFile File\n\tFlag Flag\n}\n\n// Authors yields an Authors.\nfunc (d *Data) Authors() ([]string, Source) {\n\tif len(d.Flag.Authors) > 0 {\n\t\treturn d.Flag.Authors, SourceFlag\n\t}\n\n\tif len(d.File.Authors) > 0 {\n\t\treturn d.File.Authors, SourceFile\n\t}\n\n\treturn []string{}, SourceUndefined\n}\n\n// Description yields a Description.\nfunc (d *Data) Description() (string, Source) {\n\tif d.File.Description != \"\" {\n\t\treturn d.File.Description, SourceFile\n\t}\n\n\treturn \"\", SourceUndefined\n}\n\n// Name yields a Name.\nfunc (d *Data) Name() (string, Source) {\n\tif d.File.Name != \"\" {\n\t\treturn d.File.Name, SourceFile\n\t}\n\n\treturn \"\", SourceUndefined\n}\n\n// ServiceID yields a ServiceID.\nfunc (d *Data) ServiceID() (string, Source) {\n\tif d.Flag.ServiceID != \"\" {\n\t\treturn d.Flag.ServiceID, SourceFlag\n\t}\n\n\tif sid := os.Getenv(env.ServiceID); sid != \"\" {\n\t\treturn sid, SourceEnv\n\t}\n\n\tif d.File.ServiceID != \"\" {\n\t\treturn d.File.ServiceID, SourceFile\n\t}\n\n\treturn \"\", SourceUndefined\n}\n"
  },
  {
    "path": "pkg/manifest/doc.go",
    "content": "// Package manifest contains functions and objects for managing interactions\n// with the Fastly manifest file.\npackage manifest\n"
  },
  {
    "path": "pkg/manifest/file.go",
    "content": "package manifest\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\ttoml \"github.com/pelletier/go-toml\"\n\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// File represents all of the configuration parameters in the fastly.toml\n// manifest file schema.\ntype File struct {\n\t// Args is necessary to track the subcommand called (see: File.Read method).\n\tArgs []string `toml:\"-\"`\n\t// Authors is a list of project authors (typically an email).\n\tAuthors []string `toml:\"authors\"`\n\t// ClonedFrom indicates the GitHub repo the starter kit was cloned from.\n\t// This could be an empty value if the user doesn't use `compute init`.\n\tClonedFrom string `toml:\"cloned_from,omitempty\"`\n\t// Description is the project description.\n\tDescription string `toml:\"description\"`\n\t// Language is the programming language used for the project.\n\tLanguage string `toml:\"language\"`\n\t// LocalServer describes the configuration for the local server built into the Fastly CLI.\n\tLocalServer LocalServer `toml:\"local_server,omitempty\"`\n\t// ManifestVersion is the manifest schema version number.\n\tManifestVersion Version `toml:\"manifest_version\"`\n\t// Name is the package name.\n\tName string `toml:\"name\"`\n\t// Profile selects a named auth token from the CLI config for this project.\n\tProfile string `toml:\"profile,omitempty\"`\n\t// Scripts describes customisation options for the Fastly CLI build step.\n\tScripts Scripts `toml:\"scripts,omitempty\"`\n\t// ServiceID is the Fastly Service ID to deploy the package to.\n\tServiceID string `toml:\"service_id\"`\n\t// Setup describes a set of service configuration that works with the code in the package.\n\tSetup Setup `toml:\"setup,omitempty\"`\n\n\tquiet     bool\n\terrLog    fsterr.LogInterface\n\texists    bool\n\toutput    io.Writer\n\treadError error\n}\n\n// MarshalTOML performs custom marshalling to TOML for objects of File type.\nfunc (f *File) MarshalTOML() ([]byte, error) {\n\tlocalServer := make(map[string]any)\n\n\tif f.LocalServer.Backends != nil {\n\t\tlocalServer[\"backends\"] = f.LocalServer.Backends\n\t}\n\n\tif f.LocalServer.ConfigStores != nil {\n\t\tlocalServer[\"config_stores\"] = f.LocalServer.ConfigStores\n\t}\n\n\tif f.LocalServer.KVStores != nil {\n\t\tkvStores := make(map[string]any)\n\t\tfor key, entry := range f.LocalServer.KVStores {\n\t\t\tif entry.External != nil {\n\t\t\t\tkvStores[key] = map[string]any{\n\t\t\t\t\t\"file\":   entry.External.File,\n\t\t\t\t\t\"format\": entry.External.Format,\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\titems := make([]map[string]any, 0, len(entry.Array))\n\t\t\t\tfor _, e := range entry.Array {\n\t\t\t\t\tobj := map[string]any{\"key\": e.Key}\n\t\t\t\t\tif e.File != \"\" {\n\t\t\t\t\t\tobj[\"file\"] = e.File\n\t\t\t\t\t}\n\t\t\t\t\tif e.Data != \"\" {\n\t\t\t\t\t\tobj[\"data\"] = e.Data\n\t\t\t\t\t}\n\t\t\t\t\tif e.Metadata != \"\" {\n\t\t\t\t\t\tobj[\"metadata\"] = e.Metadata\n\t\t\t\t\t}\n\t\t\t\t\titems = append(items, obj)\n\t\t\t\t}\n\t\t\t\tkvStores[key] = items\n\t\t\t}\n\t\t}\n\t\tlocalServer[\"kv_stores\"] = kvStores\n\t}\n\n\tif f.LocalServer.Pushpin != nil {\n\t\tpushpin := make(map[string]any)\n\t\tif f.LocalServer.Pushpin.EnablePushpin != nil {\n\t\t\tpushpin[\"enable\"] = *f.LocalServer.Pushpin.EnablePushpin\n\t\t}\n\t\tif f.LocalServer.Pushpin.PushpinPath != nil {\n\t\t\tpushpin[\"pushpin_path\"] = *f.LocalServer.Pushpin.PushpinPath\n\t\t}\n\t\tif f.LocalServer.Pushpin.PushpinProxyPort != nil {\n\t\t\tpushpin[\"proxy_port\"] = *f.LocalServer.Pushpin.PushpinProxyPort\n\t\t}\n\t\tif f.LocalServer.Pushpin.PushpinPublishPort != nil {\n\t\t\tpushpin[\"publish_port\"] = *f.LocalServer.Pushpin.PushpinPublishPort\n\t\t}\n\t\tlocalServer[\"pushpin\"] = pushpin\n\t}\n\n\tif f.LocalServer.SecretStores != nil {\n\t\tsecretStores := make(map[string]any)\n\t\tfor key, entry := range f.LocalServer.SecretStores {\n\t\t\tif entry.External != nil {\n\t\t\t\tsecretStores[key] = map[string]any{\n\t\t\t\t\t\"file\":   entry.External.File,\n\t\t\t\t\t\"format\": entry.External.Format,\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\titems := make([]map[string]any, 0, len(entry.Array))\n\t\t\t\tfor _, e := range entry.Array {\n\t\t\t\t\tobj := map[string]any{\"key\": e.Key}\n\t\t\t\t\tif e.File != \"\" {\n\t\t\t\t\t\tobj[\"file\"] = e.File\n\t\t\t\t\t}\n\t\t\t\t\tif e.Data != \"\" {\n\t\t\t\t\t\tobj[\"data\"] = e.Data\n\t\t\t\t\t}\n\t\t\t\t\tif e.Env != \"\" {\n\t\t\t\t\t\tobj[\"env\"] = e.Env\n\t\t\t\t\t}\n\t\t\t\t\titems = append(items, obj)\n\t\t\t\t}\n\t\t\t\tsecretStores[key] = items\n\t\t\t}\n\t\t}\n\t\tlocalServer[\"secret_stores\"] = secretStores\n\t}\n\n\tif f.LocalServer.ViceroyVersion != \"\" {\n\t\tlocalServer[\"viceroy_version\"] = f.LocalServer.ViceroyVersion\n\t}\n\n\tout := struct {\n\t\tAuthors         []string `toml:\"authors\"`\n\t\tClonedFrom      string   `toml:\"cloned_from,omitempty\"`\n\t\tDescription     string   `toml:\"description\"`\n\t\tLanguage        string   `toml:\"language\"`\n\t\tLocalServer     any      `toml:\"local_server\"` // override this field\n\t\tManifestVersion Version  `toml:\"manifest_version\"`\n\t\tName            string   `toml:\"name\"`\n\t\tProfile         string   `toml:\"profile,omitempty\"`\n\t\tScripts         Scripts  `toml:\"scripts,omitempty\"`\n\t\tServiceID       string   `toml:\"service_id\"`\n\t\tSetup           Setup    `toml:\"setup,omitempty\"`\n\t}{\n\t\tAuthors:         f.Authors,\n\t\tClonedFrom:      f.ClonedFrom,\n\t\tDescription:     f.Description,\n\t\tLanguage:        f.Language,\n\t\tLocalServer:     localServer,\n\t\tManifestVersion: f.ManifestVersion,\n\t\tName:            f.Name,\n\t\tProfile:         f.Profile,\n\t\tScripts:         f.Scripts,\n\t\tServiceID:       f.ServiceID,\n\t\tSetup:           f.Setup,\n\t}\n\n\tvar buf bytes.Buffer\n\terr := toml.NewEncoder(&buf).Encode(out)\n\treturn buf.Bytes(), err\n}\n\n// Exists yields whether the manifest exists.\n//\n// Specifically, it indicates that a toml.Unmarshal() of the toml disk content\n// to data in memory was successful without error.\nfunc (f *File) Exists() bool {\n\treturn f.exists\n}\n\n// Read loads the manifest file content from disk.\nfunc (f *File) Read(path string) (err error) {\n\tdefer func() {\n\t\tif err != nil {\n\t\t\tf.readError = err\n\t\t}\n\t}()\n\n\t// gosec flagged this:\n\t// G304 (CWE-22): Potential file inclusion via variable.\n\t// Disabling as we need to load the fastly.toml from the user's file system.\n\t// This file is decoded into a predefined struct, any unrecognised fields are dropped.\n\t// #nosec\n\ttree, err := toml.LoadFile(path)\n\tif err != nil {\n\t\t// IMPORTANT: Only `fastly compute` references the fastly.toml file.\n\t\tif len(f.Args) > 0 && f.Args[0] == \"compute\" {\n\t\t\tf.logErr(err) // only log error if user executed `compute` subcommand.\n\t\t}\n\t\treturn err\n\t}\n\n\terr = tree.Unmarshal(f)\n\tif err != nil {\n\t\t// IMPORTANT: go-toml consumes our error type within its own.\n\t\t//\n\t\t// This means we need to manually parse the return error to see if it\n\t\t// contains our specific error message. If we don't do this, then the\n\t\t// remediation information we pass back will be lost and a generic 'bug'\n\t\t// remediation (which is set by logic in main.go) is used instead.\n\t\tif strings.Contains(err.Error(), fsterr.ErrUnrecognisedManifestVersion.Inner.Error()) {\n\t\t\terr = fsterr.ErrUnrecognisedManifestVersion\n\t\t}\n\t\tf.logErr(err)\n\t\treturn err\n\t}\n\n\tif f.Scripts.EnvFile != \"\" {\n\t\tif err := f.ParseEnvFile(); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tif f.ManifestVersion == 0 {\n\t\tf.ManifestVersion = ManifestLatestVersion\n\n\t\tif !f.quiet {\n\t\t\ttext.Warning(f.output, fmt.Sprintf(\"The fastly.toml was missing a `manifest_version` field. A default schema version of `%d` will be used.\\n\\n\", ManifestLatestVersion))\n\t\t\ttext.Output(f.output, fmt.Sprintf(\"Refer to the fastly.toml package manifest format: %s\\n\\n\", SpecURL))\n\t\t}\n\t\terr = f.Write(path)\n\t\tif err != nil {\n\t\t\tf.logErr(err)\n\t\t\treturn fmt.Errorf(\"unable to save fastly.toml manifest change: %w\", err)\n\t\t}\n\t}\n\n\tif dt := tree.Get(\"setup.dictionaries\"); dt != nil {\n\t\ttext.Warning(f.output, \"Your fastly.toml manifest contains `[setup.dictionaries]`, which should be updated to `[setup.config_stores]`. Refer to the documentation at https://www.fastly.com/documentation/reference/compute/fastly-toml\\n\\n\")\n\t}\n\n\tf.exists = true\n\treturn nil\n}\n\n// ParseEnvFile reads the environment file `env_file` and appends all KEY=VALUE\n// pairs to the existing `f.Scripts.EnvVars`.\nfunc (f *File) ParseEnvFile() error {\n\t// IMPORTANT: Avoid persisting potentially secret values to disk.\n\t// We do this by keeping a copy of EnvVars before they're appended to.\n\t// Inside of File.Write() we'll reassign EnvVars the original values.\n\tmanifestDefinedEnvVars := make([]string, len(f.Scripts.EnvVars))\n\tcopy(manifestDefinedEnvVars, f.Scripts.EnvVars)\n\tf.Scripts.manifestDefinedEnvVars = manifestDefinedEnvVars\n\n\tpath, err := filepath.Abs(f.Scripts.EnvFile)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to generate absolute path for '%s': %w\", f.Scripts.EnvFile, err)\n\t}\n\tr, err := os.Open(path) // #nosec G304 (CWE-22)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to open path '%s': %w\", path, err)\n\t}\n\tdefer r.Close()\n\tscanner := bufio.NewScanner(r)\n\tfor scanner.Scan() {\n\t\tparts := strings.SplitN(scanner.Text(), \"=\", 2)\n\t\tif len(parts) != 2 {\n\t\t\treturn fmt.Errorf(\"failed to scan env_file '%s': invalid KEY=VALUE format: %#v\", path, parts)\n\t\t}\n\t\tparts[1] = strings.Trim(parts[1], `\"'`)\n\t\tf.Scripts.EnvVars = append(f.Scripts.EnvVars, strings.Join(parts, \"=\"))\n\t}\n\tif err := scanner.Err(); err != nil {\n\t\treturn fmt.Errorf(\"failed to scan env_file '%s': %w\", path, err)\n\t}\n\treturn nil\n}\n\n// ReadError yields the error returned from Read().\n//\n// NOTE: We no longer call Read() from every command. We only call it once\n// within app.Run() but we don't handle any errors that are returned from the\n// Read() method. This is because failing to read the manifest is fine if the\n// error is caused by the file not existing in a directory where the user is\n// working on a non-Compute project. This will enable code elsewhere in the CLI to\n// understand why the Read() failed. For example, we can use errors.Is() to\n// allow returning a specific remediation error from a Compute related command.\nfunc (f *File) ReadError() error {\n\treturn f.readError\n}\n\n// SetErrLog sets an instance of errors.LogInterface.\nfunc (f *File) SetErrLog(errLog fsterr.LogInterface) {\n\tf.errLog = errLog\n}\n\n// SetOutput sets the output stream for any messages.\nfunc (f *File) SetOutput(output io.Writer) {\n\tf.output = output\n}\n\n// SetQuiet sets the associated flag value.\nfunc (f *File) SetQuiet(v bool) {\n\tf.quiet = v\n}\n\n// Write persists the manifest content to disk.\nfunc (f *File) Write(path string) error {\n\tfp, err := os.Create(path) // #nosec G304 (CWE-22)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif err := appendSpecRef(fp); err != nil {\n\t\treturn err\n\t}\n\n\t// IMPORTANT: Avoid persisting potentially secret values to disk.\n\t// We do this by keeping a copy of EnvVars before they're appended to.\n\t// i.e. f.Scripts.manifestDefinedEnvVars\n\t// We now reassign EnvVars the original values (pre-EnvFile modification).\n\t// But we also need to account for the in-memory representation.\n\t//\n\t// i.e. we call File.Write() at different times but still need EnvVars data.\n\t//\n\t// So once we've persisted the correct data back to disk, we can then revert\n\t// the in-memory data for EnvVars to include the contents from EnvFile\n\t// i.e. combinedEnvVars\n\t// just in case the CLI process is still running and needs to do things with\n\t// environment variables.\n\tif f.Scripts.EnvFile != \"\" {\n\t\tcombinedEnvVars := make([]string, len(f.Scripts.EnvVars))\n\t\tcopy(combinedEnvVars, f.Scripts.EnvVars)\n\t\tf.Scripts.EnvVars = f.Scripts.manifestDefinedEnvVars\n\t\tdefer func() {\n\t\t\tf.Scripts.EnvVars = combinedEnvVars\n\t\t}()\n\t}\n\n\tif err := toml.NewEncoder(fp).Encode(f); err != nil {\n\t\treturn err\n\t}\n\tif err := fp.Sync(); err != nil {\n\t\treturn err\n\t}\n\treturn fp.Close()\n}\n\nfunc (f *File) logErr(err error) {\n\tif f.errLog != nil {\n\t\tf.errLog.Add(err)\n\t}\n}\n\n// appendSpecRef appends the fastly.toml specification URL to the manifest.\nfunc appendSpecRef(w io.Writer) error {\n\ts := fmt.Sprintf(\"# %s\\n# %s\\n\\n\", SpecIntro, SpecURL)\n\t_, err := io.WriteString(w, s)\n\treturn err\n}\n\n// Scripts represents build configuration.\ntype Scripts struct {\n\t// Build is a custom build script.\n\tBuild string `toml:\"build,omitempty\"`\n\t// EnvFile is a path to a file containing build related environment variables.\n\t// Each line should contain a KEY=VALUE.\n\t// Reading the contents of this file will populate the `EnvVars` field.\n\tEnvFile string `toml:\"env_file,omitempty\"`\n\t// EnvVars contains build related environment variables.\n\tEnvVars []string `toml:\"env_vars,omitempty\"`\n\t// PostBuild is executed after the build step.\n\tPostBuild string `toml:\"post_build,omitempty\"`\n\t// PostInit is executed after the init step.\n\tPostInit string `toml:\"post_init,omitempty\"`\n\n\t// Private field used to revert modifications to EnvVars from EnvFile.\n\t// See File.ParseEnvFile() and File.Write() methods for details.\n\t// This will contain the environment variables defined in the manifest file.\n\tmanifestDefinedEnvVars []string\n}\n"
  },
  {
    "path": "pkg/manifest/flags.go",
    "content": "package manifest\n\n// Flag represents all of the manifest parameters that can be set with explicit\n// flags. Consumers should bind their flag values to these fields directly.\ntype Flag struct {\n\tName        string\n\tDescription string\n\tAuthors     []string\n\tServiceID   string\n}\n"
  },
  {
    "path": "pkg/manifest/local_server.go",
    "content": "package manifest\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\n\t\"github.com/pelletier/go-toml\"\n)\n\n// LocalServer represents a list of mocked Viceroy resources.\ntype LocalServer struct {\n\tBackends       map[string]LocalBackend     `toml:\"backends\"`\n\tConfigStores   map[string]LocalConfigStore `toml:\"config_stores,omitempty\"`\n\tKVStores       LocalKVStoreMap             `toml:\"kv_stores,omitempty\"`\n\tSecretStores   LocalSecretStoreMap         `toml:\"secret_stores,omitempty\"`\n\tPushpin        *LocalPushpinMap            `toml:\"pushpin,omitempty\"`\n\tViceroyVersion string                      `toml:\"viceroy_version,omitempty\"`\n}\n\n// LocalBackend represents a backend to be mocked by the local testing server.\ntype LocalBackend struct {\n\tURL          string `toml:\"url\"`\n\tOverrideHost string `toml:\"override_host,omitempty\"`\n\tCertHost     string `toml:\"cert_host,omitempty\"`\n\tUseSNI       bool   `toml:\"use_sni,omitempty\"`\n}\n\n// LocalConfigStore represents a config store to be mocked by the local testing server.\ntype LocalConfigStore struct {\n\tFile     string            `toml:\"file,omitempty\"`\n\tFormat   string            `toml:\"format\"`\n\tContents map[string]string `toml:\"contents,omitempty\"`\n}\n\n// KVStoreArrayEntry represents an array-based key/value store entries.\n// It expects a key plus either a data or file field.\ntype KVStoreArrayEntry struct {\n\tKey      string `toml:\"key\"`\n\tFile     string `toml:\"file,omitempty\"`\n\tData     string `toml:\"data,omitempty\"`\n\tMetadata string `toml:\"metadata,omitempty\"`\n}\n\n// KVStoreExternalFile represents the external key/value store,\n// which must have both a file and a format.\ntype KVStoreExternalFile struct {\n\tFile   string `toml:\"file\"`\n\tFormat string `toml:\"format\"`\n}\n\n// LocalKVStore represents a kv_store to be mocked by the local testing server.\n// It is a union type and can either be an array of KVStoreArrayEntry or a single KVStoreExternalFile.\n// The IsArray flag is used to preserve the original input style.\ntype LocalKVStore struct {\n\tIsArray  bool                 `toml:\"-\"`\n\tArray    []KVStoreArrayEntry  `toml:\"-\"`\n\tExternal *KVStoreExternalFile `toml:\"-\"`\n}\n\n// LocalKVStoreMap is a map of kv_store names to the local kv_store representation.\ntype LocalKVStoreMap map[string]LocalKVStore\n\n// UnmarshalTOML performs custom unmarshalling of TOML data for LocalKVStoreMap.\nfunc (m *LocalKVStoreMap) UnmarshalTOML(v any) error {\n\traw, ok := v.(map[string]any)\n\tif !ok {\n\t\treturn fmt.Errorf(\"expected kv_stores to be a TOML table\")\n\t}\n\n\tresult := make(LocalKVStoreMap)\n\n\tfor key, val := range raw {\n\t\tswitch typed := val.(type) {\n\t\tcase []any:\n\t\t\tvar entries []KVStoreArrayEntry\n\t\t\tfor _, item := range typed {\n\t\t\t\tobj, ok := item.(map[string]any)\n\t\t\t\tif !ok {\n\t\t\t\t\treturn fmt.Errorf(\"invalid item in array for key %q\", key)\n\t\t\t\t}\n\t\t\t\tvar arrayEntry KVStoreArrayEntry\n\t\t\t\tif err := decodeTOMLMap(obj, &arrayEntry); err != nil {\n\t\t\t\t\treturn fmt.Errorf(\"decode failed for array item in key %q: %w\", key, err)\n\t\t\t\t}\n\t\t\t\tentries = append(entries, arrayEntry)\n\t\t\t}\n\t\t\tresult[key] = LocalKVStore{\n\t\t\t\tIsArray: true,\n\t\t\t\tArray:   entries,\n\t\t\t}\n\n\t\tcase map[string]any:\n\t\t\tfile, hasFile := typed[\"file\"].(string)\n\t\t\tformat, hasFormat := typed[\"format\"].(string)\n\n\t\t\tif !hasFile || !hasFormat {\n\t\t\t\treturn fmt.Errorf(\"key %q must have both file and format\", key)\n\t\t\t}\n\t\t\tresult[key] = LocalKVStore{\n\t\t\t\tIsArray: false,\n\t\t\t\tExternal: &KVStoreExternalFile{\n\t\t\t\t\tFile:   file,\n\t\t\t\t\tFormat: format,\n\t\t\t\t},\n\t\t\t}\n\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"unsupported value type for key %q: %T\", key, typed)\n\t\t}\n\t}\n\n\t*m = result\n\treturn nil\n}\n\n// SecretStoreArrayEntry represents an array-based key/value store entries.\n// It expects a key plus either a data or file field.\ntype SecretStoreArrayEntry struct {\n\tKey  string `toml:\"key\"`\n\tFile string `toml:\"file,omitempty\"`\n\tData string `toml:\"data,omitempty\"`\n\tEnv  string `toml:\"env,omitempty\"`\n}\n\n// SecretStoreExternalFile represents the external key/value store,\n// which must have both a file and a format.\ntype SecretStoreExternalFile struct {\n\tFile   string `toml:\"file\"`\n\tFormat string `toml:\"format\"`\n}\n\n// LocalSecretStore represents a secret_store to be mocked by the local testing server.\n// It is a union type and can either be an array of SecretStoreArrayEntry or a single SecretStoreExternalFile.\n// The IsArray flag is used to preserve the original input style.\ntype LocalSecretStore struct {\n\tIsArray  bool                     `toml:\"-\"`\n\tArray    []SecretStoreArrayEntry  `toml:\"-\"`\n\tExternal *SecretStoreExternalFile `toml:\"-\"`\n}\n\n// LocalSecretStoreMap is a map of secret_store names to the local secret_store representation.\ntype LocalSecretStoreMap map[string]LocalSecretStore\n\n// UnmarshalTOML performs custom unmarshalling of TOML data for LocalSecretStoreMap.\nfunc (m *LocalSecretStoreMap) UnmarshalTOML(v any) error {\n\traw, ok := v.(map[string]any)\n\tif !ok {\n\t\treturn fmt.Errorf(\"expected secret_stores to be a TOML table\")\n\t}\n\n\tresult := make(LocalSecretStoreMap)\n\n\tfor key, val := range raw {\n\t\tswitch typed := val.(type) {\n\t\tcase []any:\n\t\t\tvar entries []SecretStoreArrayEntry\n\t\t\tfor _, item := range typed {\n\t\t\t\tobj, ok := item.(map[string]any)\n\t\t\t\tif !ok {\n\t\t\t\t\treturn fmt.Errorf(\"invalid item in array for key %q\", key)\n\t\t\t\t}\n\t\t\t\tvar arrayEntry SecretStoreArrayEntry\n\t\t\t\tif err := decodeTOMLMap(obj, &arrayEntry); err != nil {\n\t\t\t\t\treturn fmt.Errorf(\"decode failed for array item in key %q: %w\", key, err)\n\t\t\t\t}\n\t\t\t\tentries = append(entries, arrayEntry)\n\t\t\t}\n\t\t\tresult[key] = LocalSecretStore{\n\t\t\t\tIsArray: true,\n\t\t\t\tArray:   entries,\n\t\t\t}\n\n\t\tcase map[string]any:\n\t\t\tfile, hasFile := typed[\"file\"].(string)\n\t\t\tformat, hasFormat := typed[\"format\"].(string)\n\n\t\t\tif !hasFile || !hasFormat {\n\t\t\t\treturn fmt.Errorf(\"key %q must have both file and format\", key)\n\t\t\t}\n\t\t\tresult[key] = LocalSecretStore{\n\t\t\t\tIsArray: false,\n\t\t\t\tExternal: &SecretStoreExternalFile{\n\t\t\t\t\tFile:   file,\n\t\t\t\t\tFormat: format,\n\t\t\t\t},\n\t\t\t}\n\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"unsupported value type for key %q: %T\", key, typed)\n\t\t}\n\t}\n\n\t*m = result\n\treturn nil\n}\n\n// LocalPushpinMap represents configuration of a local instance of Pushpin,\n// used for local experimentation and testing of handoff_fanout.\ntype LocalPushpinMap struct {\n\tEnablePushpin      *bool   `toml:\"enable,omitempty\"`\n\tPushpinPath        *string `toml:\"pushpin_path,omitempty\"`\n\tPushpinProxyPort   *uint16 `toml:\"proxy_port,omitempty\"`\n\tPushpinPublishPort *uint16 `toml:\"publish_port,omitempty\"`\n}\n\nfunc decodeTOMLMap(m map[string]any, out any) error {\n\tbuf := new(bytes.Buffer)\n\tenc := toml.NewEncoder(buf)\n\tif err := enc.Encode(m); err != nil {\n\t\treturn err\n\t}\n\treturn toml.NewDecoder(buf).Decode(out)\n}\n"
  },
  {
    "path": "pkg/manifest/local_server_test.go",
    "content": "package manifest\n\nimport (\n\t\"reflect\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/pelletier/go-toml\"\n)\n\nfunc TestLocalKVStores_UnmarshalTOML(t *testing.T) {\n\ttests := []struct {\n\t\tname        string\n\t\tinputTOML   string\n\t\texpectError bool\n\t\texpected    LocalKVStore\n\t}{\n\t\t{\n\t\t\tname: \"legacy array format\",\n\t\t\tinputTOML: `\n[[kv_stores.my-kv]]\nkey = \"kv\"\nfile = \"kv.json\"\nmetadata = \"metadata\"\n`,\n\t\t\texpected: LocalKVStore{\n\t\t\t\tIsArray: true,\n\t\t\t\tArray: []KVStoreArrayEntry{\n\t\t\t\t\t{\n\t\t\t\t\t\tKey:      \"kv\",\n\t\t\t\t\t\tFile:     \"kv.json\",\n\t\t\t\t\t\tMetadata: \"metadata\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"external file format\",\n\t\t\tinputTOML: `\n[kv_stores]\nmy-kv = { file = \"kv.json\", format = \"json\" }\n`,\n\t\t\texpected: LocalKVStore{\n\t\t\t\tIsArray: false,\n\t\t\t\tExternal: &KVStoreExternalFile{\n\t\t\t\t\tFile:   \"kv.json\",\n\t\t\t\t\tFormat: \"json\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"invalid format\",\n\t\t\tinputTOML: `\n[kv_stores]\nmy-kv = \"not-a-valid-entry\"\n`,\n\t\t\texpectError: true,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tvar m struct {\n\t\t\t\tKVStores LocalKVStoreMap `toml:\"kv_stores\"`\n\t\t\t}\n\n\t\t\tdecoder := toml.NewDecoder(strings.NewReader(tt.inputTOML))\n\t\t\terr := decoder.Decode(&m)\n\n\t\t\tif tt.expectError {\n\t\t\t\tif err == nil {\n\t\t\t\t\tt.Fatal(\"Expected error for invalid format, but got none\")\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t} else if err != nil {\n\t\t\t\tt.Fatalf(\"Failed to parse TOML: %v\", err)\n\t\t\t}\n\n\t\t\tgot, ok := m.KVStores[\"my-kv\"]\n\t\t\tif !ok {\n\t\t\t\tt.Fatalf(\"Expected key 'my-kv' not found\")\n\t\t\t}\n\n\t\t\tif !reflect.DeepEqual(got, tt.expected) {\n\t\t\t\tt.Errorf(\"Mismatch!\\nGot:  %+v\\nWant: %+v\", got, tt.expected)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestLocalSecretStores_UnmarshalTOML(t *testing.T) {\n\ttests := []struct {\n\t\tname        string\n\t\tinputTOML   string\n\t\texpectError bool\n\t\texpected    LocalSecretStore\n\t}{\n\t\t{\n\t\t\tname: \"legacy array format\",\n\t\t\tinputTOML: `\n[[secret_stores.my-secret-store]]\nkey = \"secret\"\nfile = \"secret.json\"\n`,\n\t\t\texpected: LocalSecretStore{\n\t\t\t\tIsArray: true,\n\t\t\t\tArray: []SecretStoreArrayEntry{\n\t\t\t\t\t{\n\t\t\t\t\t\tKey:  \"secret\",\n\t\t\t\t\t\tFile: \"secret.json\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"external file format\",\n\t\t\tinputTOML: `\n[secret_stores]\nmy-secret-store = { file = \"secret.json\", format = \"json\" }\n`,\n\t\t\texpected: LocalSecretStore{\n\t\t\t\tIsArray: false,\n\t\t\t\tExternal: &SecretStoreExternalFile{\n\t\t\t\t\tFile:   \"secret.json\",\n\t\t\t\t\tFormat: \"json\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"invalid format\",\n\t\t\tinputTOML: `\n[secret_stores]\nmy-secret-store = \"not-a-valid-entry\"\n`,\n\t\t\texpectError: true,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tvar m struct {\n\t\t\t\tSecretStores LocalSecretStoreMap `toml:\"secret_stores\"`\n\t\t\t}\n\n\t\t\tdecoder := toml.NewDecoder(strings.NewReader(tt.inputTOML))\n\t\t\terr := decoder.Decode(&m)\n\n\t\t\tif tt.expectError {\n\t\t\t\tif err == nil {\n\t\t\t\t\tt.Fatal(\"Expected error for invalid format, but got none\")\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t} else if err != nil {\n\t\t\t\tt.Fatalf(\"Failed to parse TOML: %v\", err)\n\t\t\t}\n\n\t\t\tgot, ok := m.SecretStores[\"my-secret-store\"]\n\t\t\tif !ok {\n\t\t\t\tt.Fatalf(\"Expected key 'my-secret-store' not found\")\n\t\t\t}\n\n\t\t\tif !reflect.DeepEqual(got, tt.expected) {\n\t\t\t\tt.Errorf(\"Mismatch!\\nGot:  %+v\\nWant: %+v\", got, tt.expected)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/manifest/manifest.go",
    "content": "package manifest\n\n// Source enumerates where a manifest parameter is taken from.\ntype Source uint8\n\nconst (\n\t// Filename is the name of the package manifest file.\n\t// It is expected to be a project specific configuration file.\n\tFilename = \"fastly.toml\"\n\n\t// ManifestLatestVersion represents the latest known manifest schema version\n\t// supported by the CLI.\n\t//\n\t// NOTE: The CLI is the primary consumer of the fastly.toml manifest so its\n\t// code is typically coupled to the specification.\n\tManifestLatestVersion = 3\n\n\t// FilePermissions represents a read/write file mode.\n\tFilePermissions = 0o666\n\n\t// SourceUndefined indicates the parameter isn't provided in any of the\n\t// available sources, similar to \"not found\".\n\tSourceUndefined Source = iota\n\n\t// SourceFile indicates the parameter came from a manifest file.\n\tSourceFile\n\n\t// SourceEnv indicates the parameter came from the user's shell environment.\n\tSourceEnv\n\n\t// SourceFlag indicates the parameter came from an explicit flag.\n\tSourceFlag\n\n\t// SpecIntro informs the user of what the manifest file is for.\n\tSpecIntro = \"This file describes a Fastly Compute package. To learn more visit:\"\n\n\t// SpecURL points to the fastly.toml manifest specification reference.\n\tSpecURL = \"https://www.fastly.com/documentation/reference/compute/fastly-toml\"\n)\n"
  },
  {
    "path": "pkg/manifest/manifest_test.go",
    "content": "package manifest_test\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\ttoml \"github.com/pelletier/go-toml\"\n\n\t\"github.com/fastly/cli/pkg/env\"\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/manifest\"\n\t\"github.com/fastly/cli/pkg/testutil\"\n\t\"github.com/fastly/cli/pkg/threadsafe\"\n)\n\nfunc TestManifest(t *testing.T) {\n\ttests := map[string]struct {\n\t\tmanifest             string\n\t\tvalid                bool\n\t\texpectedError        error\n\t\twantRemediationError string\n\t\texpectedOutput       string\n\t}{\n\t\t\"valid: semver\": {\n\t\t\tmanifest: \"fastly-valid-semver.toml\",\n\t\t\tvalid:    true,\n\t\t},\n\t\t\"valid: integer\": {\n\t\t\tmanifest: \"fastly-valid-integer.toml\",\n\t\t\tvalid:    true,\n\t\t},\n\t\t\"invalid: missing manifest_version\": {\n\t\t\tmanifest: \"fastly-invalid-missing-version.toml\",\n\t\t\tvalid:    true, // expect manifest_version to be set to latest version\n\t\t},\n\t\t\"invalid: manifest_version Atoi error\": {\n\t\t\tmanifest:      \"fastly-invalid-unrecognised.toml\",\n\t\t\tvalid:         false,\n\t\t\texpectedError: fmt.Errorf(\"error parsing manifest_version 'abc'\"),\n\t\t},\n\t\t\"unrecognised: manifest_version exceeded limit\": {\n\t\t\tmanifest:      \"fastly-invalid-version-exceeded.toml\",\n\t\t\tvalid:         false,\n\t\t\texpectedError: fsterr.ErrUnrecognisedManifestVersion,\n\t\t},\n\t\t\"warning: dictionaries now replaced with config_stores\": {\n\t\t\tmanifest:       \"fastly-warning-dictionaries.toml\",\n\t\t\tvalid:          true, // we display a warning but we don't exit command execution\n\t\t\texpectedOutput: \"WARNING: Your fastly.toml manifest contains `[setup.dictionaries]`\",\n\t\t},\n\t}\n\n\t// NOTE: some of the fixture files are overwritten by the application logic\n\t// and so to ensure future test runs can complete successfully we do an\n\t// initial read of the data and then write it back to disk once the tests\n\t// have completed.\n\n\tprefix := filepath.Join(\"./\", \"testdata\")\n\n\tfor _, fpath := range []string{\n\t\t\"fastly-valid-semver.toml\",\n\t\t\"fastly-valid-integer.toml\",\n\t\t\"fastly-invalid-missing-version.toml\",\n\t\t\"fastly-invalid-unrecognised.toml\",\n\t\t\"fastly-invalid-version-exceeded.toml\",\n\t} {\n\t\tpath, err := filepath.Abs(filepath.Join(prefix, fpath))\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tb, err := os.ReadFile(path)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tdefer func(path string, b []byte) {\n\t\t\terr := os.WriteFile(path, b, 0o600)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t}(path, b)\n\t}\n\n\tfor name, tc := range tests {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tvar (\n\t\t\t\tm      manifest.File\n\t\t\t\tstdout threadsafe.Buffer\n\t\t\t)\n\t\t\tm.SetErrLog(fsterr.Log)\n\t\t\tm.SetOutput(&stdout)\n\n\t\t\tpath, err := filepath.Abs(filepath.Join(prefix, tc.manifest))\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\terr = m.Read(path)\n\n\t\t\toutput := stdout.String()\n\t\t\tt.Log(output)\n\n\t\t\t// If we expect an invalid config, then assert we get the right error.\n\t\t\tif !tc.valid {\n\t\t\t\ttestutil.AssertErrorContains(t, err, tc.expectedError.Error())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// Otherwise, if we expect the manifest to be valid and we get an error,\n\t\t\t// then that's unexpected behaviour.\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\tif m.ManifestVersion != manifest.ManifestLatestVersion {\n\t\t\t\tt.Fatalf(\"manifest_version '%d' doesn't match latest '%d'\", m.ManifestVersion, manifest.ManifestLatestVersion)\n\t\t\t}\n\n\t\t\tif tc.expectedOutput != \"\" && !strings.Contains(output, tc.expectedOutput) {\n\t\t\t\tt.Fatalf(\"got: %s, want: %s\", output, tc.expectedOutput)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestManifestPrepend(t *testing.T) {\n\tvar (\n\t\tmanifestBody []byte\n\t\tmanifestPath string\n\t)\n\n\t// NOTE: the fixture file \"fastly-missing-spec-url.toml\" will be\n\t// overwritten by the test as the internal logic is supposed to add into the\n\t// manifest a reference to the fastly.toml specification.\n\t//\n\t// To ensure future test runs complete successfully we do an initial read of\n\t// the data and then write it back out when the tests have completed.\n\t{\n\t\tpath, err := filepath.Abs(filepath.Join(\"./\", \"testdata\", \"fastly-missing-spec-url.toml\"))\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tmanifestBody, err = os.ReadFile(path)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tdefer func(path string, b []byte) {\n\t\t\terr := os.WriteFile(path, b, 0o600)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t}(path, manifestBody)\n\t}\n\n\t// Create temp environment to run test code within.\n\t{\n\t\twd, err := os.Getwd()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\trootdir := testutil.NewEnv(testutil.EnvOpts{\n\t\t\tT: t,\n\t\t\tWrite: []testutil.FileIO{\n\t\t\t\t{Src: string(manifestBody), Dst: \"fastly.toml\"},\n\t\t\t},\n\t\t})\n\t\tmanifestPath = filepath.Join(rootdir, \"fastly.toml\")\n\t\tdefer os.RemoveAll(rootdir)\n\n\t\tif err := os.Chdir(rootdir); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tdefer func() {\n\t\t\t_ = os.Chdir(wd)\n\t\t}()\n\t}\n\n\tvar f manifest.File\n\terr := f.Read(manifestPath)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\terr = f.Write(manifestPath)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tupdatedManifest, err := os.ReadFile(manifestPath)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tcontent := string(updatedManifest)\n\n\tif !strings.Contains(content, manifest.SpecIntro) || !strings.Contains(content, manifest.SpecURL) {\n\t\tt.Fatal(\"missing fastly.toml specification reference link\")\n\t}\n}\n\nfunc TestDataServiceID(t *testing.T) {\n\tt.Setenv(env.ServiceID, \"001\")\n\n\t// SourceFlag\n\td := manifest.Data{\n\t\tFlag: manifest.Flag{ServiceID: \"123\"},\n\t\tFile: manifest.File{ServiceID: \"456\"},\n\t}\n\t_, src := d.ServiceID()\n\tif src != manifest.SourceFlag {\n\t\tt.Fatal(\"expected SourceFlag\")\n\t}\n\n\t// SourceEnv\n\td.Flag = manifest.Flag{}\n\t_, src = d.ServiceID()\n\tif src != manifest.SourceEnv {\n\t\tt.Fatal(\"expected SourceEnv\")\n\t}\n\n\t// SourceFile\n\tt.Setenv(env.ServiceID, \"\")\n\t_, src = d.ServiceID()\n\tif src != manifest.SourceFile {\n\t\tt.Fatal(\"expected SourceFile\")\n\t}\n}\n\n// This test validates that manually added changes, such as the toml\n// syntax for Viceroy local testing, are not accidentally deleted after\n// decoding and encoding flows.\nfunc TestManifestPersistsLocalServerSection(t *testing.T) {\n\tfpath := filepath.Join(\"./\", \"testdata\", \"fastly-viceroy-update.toml\")\n\n\tb, err := os.ReadFile(fpath)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tdefer func(fpath string, b []byte) {\n\t\terr := os.WriteFile(fpath, b, 0o600)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t}(fpath, b)\n\n\toriginal, err := toml.LoadFile(fpath)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tot := original.Get(\"local_server\")\n\tif ot == nil {\n\t\tt.Fatal(\"expected [local_server] block to exist in fastly.toml but is missing\")\n\t}\n\n\tosid := original.Get(\"service_id\")\n\tif osid != nil {\n\t\tt.Fatal(\"did not expect service_id key to exist in fastly.toml but is present\")\n\t}\n\n\tvar m manifest.File\n\n\terr = m.Read(fpath)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tm.ServiceID = \"a change occurred to the data structure\"\n\n\terr = m.Write(fpath)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tlatest, err := toml.LoadFile(fpath)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tlsid := latest.Get(\"service_id\")\n\tif lsid == nil {\n\t\tt.Fatal(\"expected service_id key to exist in fastly.toml but is missing\")\n\t}\n\n\tlt := latest.Get(\"local_server\")\n\tif lt == nil {\n\t\tt.Fatal(\"expected [local_server] block to exist in fastly.toml but is missing\")\n\t}\n\n\tlocalTree, ok := lt.(*toml.Tree)\n\tif !ok {\n\t\tt.Fatal(\"failed to convert 'local' interface{} to toml.Tree\")\n\t}\n\toriginalTree, ok := ot.(*toml.Tree)\n\tif !ok {\n\t\tt.Fatal(\"failed to convert 'original' interface{} to toml.Tree\")\n\t}\n\twant, got := originalTree.String(), localTree.String()\n\tif diff := cmp.Diff(want, got); diff != \"\" {\n\t\tt.Fatalf(\"testing section between original and updated fastly.toml do not match (-want +got):\\n%s\", diff)\n\t}\n}\n\nfunc TestParseEnvFile(t *testing.T) {\n\ttests := map[string]struct {\n\t\tcontent  string\n\t\twantVars []string\n\t\twantErr  bool\n\t}{\n\t\t\"simple key=value\": {\n\t\t\tcontent:  \"FOO=bar\\n\",\n\t\t\twantVars: []string{\"FOO=bar\"},\n\t\t},\n\t\t\"value contains equals sign\": {\n\t\t\tcontent:  \"SECRET=dGVzdA==\\n\",\n\t\t\twantVars: []string{\"SECRET=dGVzdA==\"},\n\t\t},\n\t\t\"multiple entries with equals in values\": {\n\t\t\tcontent:  \"A=1\\nB=x=y=z\\n\",\n\t\t\twantVars: []string{\"A=1\", \"B=x=y=z\"},\n\t\t},\n\t\t\"invalid line without equals\": {\n\t\t\tcontent: \"BADLINE\\n\",\n\t\t\twantErr: true,\n\t\t},\n\t}\n\n\tfor name, tc := range tests {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\ttmp := t.TempDir()\n\t\t\tenvPath := filepath.Join(tmp, \".env\")\n\t\t\tif err := os.WriteFile(envPath, []byte(tc.content), 0o600); err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\tf := &manifest.File{}\n\t\t\tf.Scripts.EnvFile = envPath\n\n\t\t\terr := f.ParseEnvFile()\n\t\t\tif tc.wantErr {\n\t\t\t\tif err == nil {\n\t\t\t\t\tt.Fatal(\"expected error, got nil\")\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t\t}\n\t\t\tif diff := cmp.Diff(tc.wantVars, f.Scripts.EnvVars); diff != \"\" {\n\t\t\t\tt.Fatalf(\"EnvVars mismatch (-want +got):\\n%s\", diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/manifest/setup.go",
    "content": "package manifest\n\n// Setup represents a set of service configuration that works with the code in\n// the package. See https://www.fastly.com/documentation/reference/compute/fastly-toml.\ntype Setup struct {\n\tBackends     map[string]*SetupBackend     `toml:\"backends,omitempty\"`\n\tConfigStores map[string]*SetupConfigStore `toml:\"config_stores,omitempty\"`\n\tLoggers      map[string]*SetupLogger      `toml:\"log_endpoints,omitempty\"`\n\tObjectStores map[string]*SetupKVStore     `toml:\"object_stores,omitempty\"`\n\tKVStores     map[string]*SetupKVStore     `toml:\"kv_stores,omitempty\"`\n\tSecretStores map[string]*SetupSecretStore `toml:\"secret_stores,omitempty\"`\n}\n\n// Defined indicates if there is any [setup] configuration in the manifest.\nfunc (s Setup) Defined() bool {\n\tvar defined bool\n\n\tif len(s.Backends) > 0 {\n\t\tdefined = true\n\t}\n\tif len(s.ConfigStores) > 0 {\n\t\tdefined = true\n\t}\n\tif len(s.Loggers) > 0 {\n\t\tdefined = true\n\t}\n\tif len(s.ObjectStores) > 0 {\n\t\tdefined = true\n\t}\n\tif len(s.KVStores) > 0 {\n\t\tdefined = true\n\t}\n\tif len(s.SecretStores) > 0 {\n\t\tdefined = true\n\t}\n\n\treturn defined\n}\n\n// SetupBackend represents a '[setup.backends.<T>]' instance.\ntype SetupBackend struct {\n\tAddress     string `toml:\"address,omitempty\"`\n\tPort        int    `toml:\"port,omitempty\"`\n\tDescription string `toml:\"description,omitempty\"`\n}\n\n// SetupConfigStore represents a '[setup.dictionaries.<T>]' instance.\ntype SetupConfigStore struct {\n\tItems       map[string]SetupConfigStoreItems `toml:\"items,omitempty\"`\n\tDescription string                           `toml:\"description,omitempty\"`\n}\n\n// SetupConfigStoreItems represents a '[setup.dictionaries.<T>.items]' instance.\ntype SetupConfigStoreItems struct {\n\tValue       string `toml:\"value,omitempty\"`\n\tDescription string `toml:\"description,omitempty\"`\n}\n\n// SetupLogger represents a '[setup.log_endpoints.<T>]' instance.\ntype SetupLogger struct {\n\tProvider string `toml:\"provider,omitempty\"`\n}\n\n// SetupKVStore represents a '[setup.kv_stores.<T>]' instance.\ntype SetupKVStore struct {\n\tFile        string                       `toml:\"file,omitempty\"`\n\tItems       map[string]SetupKVStoreItems `toml:\"items,omitempty\"`\n\tDescription string                       `toml:\"description,omitempty\"`\n}\n\n// SetupKVStoreItems represents a '[setup.kv_stores.<T>.items]' instance.\ntype SetupKVStoreItems struct {\n\tFile        string `toml:\"file,omitempty\"`\n\tValue       string `toml:\"value,omitempty\"`\n\tDescription string `toml:\"description,omitempty\"`\n}\n\n// SetupSecretStore represents a '[setup.secret_stores.<T>]' instance.\ntype SetupSecretStore struct {\n\tEntries     map[string]SetupSecretStoreEntry `toml:\"entries,omitempty\"`\n\tDescription string                           `toml:\"description,omitempty\"`\n}\n\n// SetupSecretStoreEntry represents a '[setup.secret_stores.<T>.entries]' instance.\ntype SetupSecretStoreEntry struct {\n\t// The secret value is intentionally omitted to avoid secrets\n\t// from being included in the manifest. Instead, secret\n\t// values are input during setup.\n\tDescription string `toml:\"description,omitempty\"`\n}\n"
  },
  {
    "path": "pkg/manifest/testdata/fastly-invalid-missing-version.toml",
    "content": "name = \"Default Rust template\"\ndescription = \"Default package template for Rust based edge compute projects.\"\nauthors = [\"phamann <patrick@fastly.com>\"]\nlanguage = \"rust\"\n"
  },
  {
    "path": "pkg/manifest/testdata/fastly-invalid-unrecognised.toml",
    "content": "# invalid: manifest_version is not a number\nmanifest_version = \"abc\"\nname = \"Default Rust template\"\ndescription = \"Default package template for Rust based edge compute projects.\"\nauthors = [\"phamann <patrick@fastly.com>\"]\nlanguage = \"rust\"\n"
  },
  {
    "path": "pkg/manifest/testdata/fastly-invalid-version-exceeded.toml",
    "content": "manifest_version = \"99.0.0\" # latest supported manifest_version is less than 99\nname = \"Default Rust template\"\ndescription = \"Default package template for Rust based edge compute projects.\"\nauthors = [\"phamann <patrick@fastly.com>\"]\nlanguage = \"rust\"\n"
  },
  {
    "path": "pkg/manifest/testdata/fastly-missing-spec-url.toml",
    "content": "manifest_version = 3\nname = \"Default Rust template\"\ndescription = \"Default package template for Rust based edge compute projects.\"\nauthors = [\"phamann <patrick@fastly.com>\"]\nlanguage = \"rust\"\n"
  },
  {
    "path": "pkg/manifest/testdata/fastly-valid-integer.toml",
    "content": "manifest_version = 3\nname = \"Default Rust template\"\ndescription = \"Default package template for Rust based edge compute projects.\"\nauthors = [\"phamann <patrick@fastly.com>\"]\nlanguage = \"rust\"\n"
  },
  {
    "path": "pkg/manifest/testdata/fastly-valid-semver.toml",
    "content": "manifest_version = \"0.99.0\" # minor and patch versions are ignored and zero major is bumped to latest\nname = \"Default Rust template\"\ndescription = \"Default package template for Rust based edge compute projects.\"\nauthors = [\"phamann <patrick@fastly.com>\"]\nlanguage = \"rust\"\n"
  },
  {
    "path": "pkg/manifest/testdata/fastly-viceroy-update.toml",
    "content": "# This file describes a Fastly Compute package. To learn more visit:\n# https://www.fastly.com/documentation/reference/compute/fastly-toml\n\nauthors = [\"phamann <patrick@fastly.com>\"]\ndescription = \"Default package template for Rust based edge compute projects.\"\nlanguage = \"rust\"\nmanifest_version = 3\nname = \"Default Rust template\"\n\n[local_server]\n\n[local_server.backends]\n\n[local_server.backends.backend_a]\nurl = \"https://example.com/\"\noverride_host = \"otherexample.com\"\n\n[local_server.backends.foo]\nurl = \"https://foo.com/\"\n\n[local_server.backends.bar]\nurl = \"https://bar.com/\"\n\n[local_server.config_stores]\n\n[local_server.config_stores.strings]\nfile = \"strings.json\"\nformat = \"json\"\n\n[local_server.config_stores.example_store]\nformat = \"inline-toml\"\n\n[local_server.config_stores.example_store.contents]\nfoo = \"bar\"\nbaz = \"\"\"\nqux\"\"\"\n\n[local_server.kv_stores]\nstore_one = [\n  { key = \"first\", data = \"This is some data\", metadata = \"This is some metadata\" },\n  { key = \"second\", file = \"strings.json\" },\n]\nstore_three = { file = \"path/to/kv.json\", format = \"json\" }\n\n[[local_server.kv_stores.store_two]]\nkey = \"first\"\ndata = \"This is some data\"\nmetadata = \"This is some metadata\"\n\n[[local_server.kv_stores.store_two]]\nkey = \"second\"\nfile = \"strings.json\"\n\n[local_server.pushpin]\nenable = true\npushpin_path = \"path/to/pushpin\"\nproxy_port = 7777\npublish_port = 6666\n\n[local_server.secret_stores]\nstore_one = [\n  { key = \"first\", data = \"This is some secret data\" },\n  { key = \"second\", file = \"/path/to/secret.json\" },\n]\nstore_three = { file = \"path/to/secret.json\", format = \"json\" }\n\n[[local_server.secret_stores.store_two]]\nkey = \"first\"\ndata = \"This is also some secret data\"\n\n[[local_server.secret_stores.store_two]]\nkey = \"second\"\nfile = \"/path/to/other/secret.json\"\n\n[[local_server.secret_stores.store_two]]\nkey = \"fourth\"\nenv = \"ENV_FOURTH\"\n"
  },
  {
    "path": "pkg/manifest/testdata/fastly-warning-dictionaries.toml",
    "content": "manifest_version = 3\nname = \"Default Rust template\"\ndescription = \"Default package template for Rust based edge compute projects.\"\nauthors = [\"example <oss@fastly.com>\"]\nlanguage = \"rust\"\n\n[setup.dictionaries]\n\n[setup.dictionaries.service_config]\ndescription = \"Configuration data for my service\"\n\n[setup.dictionaries.service_config.items]\n\n[setup.dictionaries.service_config.items.s3-primary-host]\nvalue = \"eu-west-2\"\n\n[setup.dictionaries.service_config.items.s3-fallback-host]\nvalue = \"us-west-1\"\n"
  },
  {
    "path": "pkg/manifest/version.go",
    "content": "package manifest\n\nimport (\n\t\"fmt\"\n\t\"strconv\"\n\t\"strings\"\n\n\tfsterr \"github.com/fastly/cli/pkg/errors\"\n)\n\n// Version represents the currently supported schema for the fastly.toml\n// manifest file that determines the configuration for a Compute service.\n//\n// NOTE: the File object has a field called ManifestVersion which this type is\n// assigned. The reason we don't name this type ManifestVersion is to appease\n// the static analysis linter which complains re: stutter in the import\n// manifest.ManifestVersion.\ntype Version int\n\n// UnmarshalText manages multiple scenarios where historically the manifest\n// version was a string value and not an integer.\n//\n// Example mappings:\n//\n// \"0.1.0\" -> 1\n// \"1\"     -> 1\n// 1       -> 1\n// \"1.0.0\" -> 1\n// 0.1     -> 1\n// \"0.2.0\" -> 1\n// \"2.0.0\" -> 2\n//\n// We also constrain the version so that if a user has a manifest_version\n// defined as \"99.0.0\" then we won't accidentally store it as the integer 99\n// but instead will return an error because it exceeds the current\n// ManifestLatestVersion version.\nfunc (v *Version) UnmarshalText(txt []byte) error {\n\ts := string(txt)\n\n\t// Presumes semver value (e.g. 1.0.0, 0.1.0 or 0.1)\n\t// Major is converted to integer if != zero.\n\t// Otherwise if Major == zero, then ignore Minor/Patch and set to latest version.\n\tvar (\n\t\terr     error\n\t\tversion int\n\t)\n\tif strings.Contains(s, \".\") {\n\t\tsegs := strings.Split(s, \".\")\n\t\ts = segs[0]\n\t\tif s == \"0\" {\n\t\t\ts = strconv.Itoa(ManifestLatestVersion)\n\t\t}\n\t}\n\n\tversion, err = strconv.Atoi(s)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error parsing manifest_version '%s': %w\", s, err)\n\t}\n\n\tif version > ManifestLatestVersion {\n\t\treturn fsterr.ErrUnrecognisedManifestVersion\n\t}\n\n\t*v = Version(version)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/mock/api.go",
    "content": "package mock\n\nimport (\n\t\"context\"\n\t\"crypto/ed25519\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n)\n\n// API is a mock implementation of api.Interface that's used for testing.\n// The zero value is useful, but will panic on all methods. Provide function\n// implementations for the method(s) your test will call.\ntype API struct {\n\tAllDatacentersFn func(context.Context) ([]fastly.Datacenter, error)\n\tAllIPsFn         func(context.Context) (fastly.IPAddrs, fastly.IPAddrs, error)\n\n\tCreateServiceFn     func(context.Context, *fastly.CreateServiceInput) (*fastly.Service, error)\n\tGetServicesFn       func(context.Context, *fastly.GetServicesInput) *fastly.ListPaginator[fastly.Service]\n\tListServicesFn      func(context.Context, *fastly.ListServicesInput) ([]*fastly.Service, error)\n\tGetServiceFn        func(context.Context, *fastly.GetServiceInput) (*fastly.Service, error)\n\tGetServiceDetailsFn func(context.Context, *fastly.GetServiceDetailsInput) (*fastly.ServiceDetail, error)\n\tUpdateServiceFn     func(context.Context, *fastly.UpdateServiceInput) (*fastly.Service, error)\n\tDeleteServiceFn     func(context.Context, *fastly.DeleteServiceInput) error\n\tSearchServiceFn     func(context.Context, *fastly.SearchServiceInput) (*fastly.Service, error)\n\n\tCloneVersionFn      func(context.Context, *fastly.CloneVersionInput) (*fastly.Version, error)\n\tListVersionsFn      func(context.Context, *fastly.ListVersionsInput) ([]*fastly.Version, error)\n\tGetVersionFn        func(context.Context, *fastly.GetVersionInput) (*fastly.Version, error)\n\tUpdateVersionFn     func(context.Context, *fastly.UpdateVersionInput) (*fastly.Version, error)\n\tActivateVersionFn   func(context.Context, *fastly.ActivateVersionInput) (*fastly.Version, error)\n\tDeactivateVersionFn func(context.Context, *fastly.DeactivateVersionInput) (*fastly.Version, error)\n\tLockVersionFn       func(context.Context, *fastly.LockVersionInput) (*fastly.Version, error)\n\tLatestVersionFn     func(context.Context, *fastly.LatestVersionInput) (*fastly.Version, error)\n\tValidateVersionFn   func(context.Context, *fastly.ValidateVersionInput) (bool, string, error)\n\n\tCreateDomainFn       func(context.Context, *fastly.CreateDomainInput) (*fastly.Domain, error)\n\tListDomainsFn        func(context.Context, *fastly.ListDomainsInput) ([]*fastly.Domain, error)\n\tGetDomainFn          func(context.Context, *fastly.GetDomainInput) (*fastly.Domain, error)\n\tUpdateDomainFn       func(context.Context, *fastly.UpdateDomainInput) (*fastly.Domain, error)\n\tDeleteDomainFn       func(context.Context, *fastly.DeleteDomainInput) error\n\tValidateDomainFn     func(context.Context, *fastly.ValidateDomainInput) (*fastly.DomainValidationResult, error)\n\tValidateAllDomainsFn func(context.Context, *fastly.ValidateAllDomainsInput) ([]*fastly.DomainValidationResult, error)\n\n\tCreateBackendFn func(context.Context, *fastly.CreateBackendInput) (*fastly.Backend, error)\n\tListBackendsFn  func(context.Context, *fastly.ListBackendsInput) ([]*fastly.Backend, error)\n\tGetBackendFn    func(context.Context, *fastly.GetBackendInput) (*fastly.Backend, error)\n\tUpdateBackendFn func(context.Context, *fastly.UpdateBackendInput) (*fastly.Backend, error)\n\tDeleteBackendFn func(context.Context, *fastly.DeleteBackendInput) error\n\n\tCreateHealthCheckFn func(context.Context, *fastly.CreateHealthCheckInput) (*fastly.HealthCheck, error)\n\tListHealthChecksFn  func(context.Context, *fastly.ListHealthChecksInput) ([]*fastly.HealthCheck, error)\n\tGetHealthCheckFn    func(context.Context, *fastly.GetHealthCheckInput) (*fastly.HealthCheck, error)\n\tUpdateHealthCheckFn func(context.Context, *fastly.UpdateHealthCheckInput) (*fastly.HealthCheck, error)\n\tDeleteHealthCheckFn func(context.Context, *fastly.DeleteHealthCheckInput) error\n\n\tGetPackageFn    func(context.Context, *fastly.GetPackageInput) (*fastly.Package, error)\n\tUpdatePackageFn func(context.Context, *fastly.UpdatePackageInput) (*fastly.Package, error)\n\n\tCreateDictionaryFn func(context.Context, *fastly.CreateDictionaryInput) (*fastly.Dictionary, error)\n\tGetDictionaryFn    func(context.Context, *fastly.GetDictionaryInput) (*fastly.Dictionary, error)\n\tDeleteDictionaryFn func(context.Context, *fastly.DeleteDictionaryInput) error\n\tListDictionariesFn func(context.Context, *fastly.ListDictionariesInput) ([]*fastly.Dictionary, error)\n\tUpdateDictionaryFn func(context.Context, *fastly.UpdateDictionaryInput) (*fastly.Dictionary, error)\n\n\tGetDictionaryItemsFn         func(context.Context, *fastly.GetDictionaryItemsInput) *fastly.ListPaginator[fastly.DictionaryItem]\n\tListDictionaryItemsFn        func(context.Context, *fastly.ListDictionaryItemsInput) ([]*fastly.DictionaryItem, error)\n\tGetDictionaryItemFn          func(context.Context, *fastly.GetDictionaryItemInput) (*fastly.DictionaryItem, error)\n\tCreateDictionaryItemFn       func(context.Context, *fastly.CreateDictionaryItemInput) (*fastly.DictionaryItem, error)\n\tUpdateDictionaryItemFn       func(context.Context, *fastly.UpdateDictionaryItemInput) (*fastly.DictionaryItem, error)\n\tDeleteDictionaryItemFn       func(context.Context, *fastly.DeleteDictionaryItemInput) error\n\tBatchModifyDictionaryItemsFn func(context.Context, *fastly.BatchModifyDictionaryItemsInput) error\n\n\tGetDictionaryInfoFn func(context.Context, *fastly.GetDictionaryInfoInput) (*fastly.DictionaryInfo, error)\n\n\tCreateBigQueryFn func(context.Context, *fastly.CreateBigQueryInput) (*fastly.BigQuery, error)\n\tListBigQueriesFn func(context.Context, *fastly.ListBigQueriesInput) ([]*fastly.BigQuery, error)\n\tGetBigQueryFn    func(context.Context, *fastly.GetBigQueryInput) (*fastly.BigQuery, error)\n\tUpdateBigQueryFn func(context.Context, *fastly.UpdateBigQueryInput) (*fastly.BigQuery, error)\n\tDeleteBigQueryFn func(context.Context, *fastly.DeleteBigQueryInput) error\n\n\tCreateS3Fn func(context.Context, *fastly.CreateS3Input) (*fastly.S3, error)\n\tListS3sFn  func(context.Context, *fastly.ListS3sInput) ([]*fastly.S3, error)\n\tGetS3Fn    func(context.Context, *fastly.GetS3Input) (*fastly.S3, error)\n\tUpdateS3Fn func(context.Context, *fastly.UpdateS3Input) (*fastly.S3, error)\n\tDeleteS3Fn func(context.Context, *fastly.DeleteS3Input) error\n\n\tCreateKinesisFn func(context.Context, *fastly.CreateKinesisInput) (*fastly.Kinesis, error)\n\tListKinesisFn   func(context.Context, *fastly.ListKinesisInput) ([]*fastly.Kinesis, error)\n\tGetKinesisFn    func(context.Context, *fastly.GetKinesisInput) (*fastly.Kinesis, error)\n\tUpdateKinesisFn func(context.Context, *fastly.UpdateKinesisInput) (*fastly.Kinesis, error)\n\tDeleteKinesisFn func(context.Context, *fastly.DeleteKinesisInput) error\n\n\tCreateSyslogFn func(context.Context, *fastly.CreateSyslogInput) (*fastly.Syslog, error)\n\tListSyslogsFn  func(context.Context, *fastly.ListSyslogsInput) ([]*fastly.Syslog, error)\n\tGetSyslogFn    func(context.Context, *fastly.GetSyslogInput) (*fastly.Syslog, error)\n\tUpdateSyslogFn func(context.Context, *fastly.UpdateSyslogInput) (*fastly.Syslog, error)\n\tDeleteSyslogFn func(context.Context, *fastly.DeleteSyslogInput) error\n\n\tCreateLogentriesFn func(context.Context, *fastly.CreateLogentriesInput) (*fastly.Logentries, error)\n\tListLogentriesFn   func(context.Context, *fastly.ListLogentriesInput) ([]*fastly.Logentries, error)\n\tGetLogentriesFn    func(context.Context, *fastly.GetLogentriesInput) (*fastly.Logentries, error)\n\tUpdateLogentriesFn func(context.Context, *fastly.UpdateLogentriesInput) (*fastly.Logentries, error)\n\tDeleteLogentriesFn func(context.Context, *fastly.DeleteLogentriesInput) error\n\n\tCreatePapertrailFn func(context.Context, *fastly.CreatePapertrailInput) (*fastly.Papertrail, error)\n\tListPapertrailsFn  func(context.Context, *fastly.ListPapertrailsInput) ([]*fastly.Papertrail, error)\n\tGetPapertrailFn    func(context.Context, *fastly.GetPapertrailInput) (*fastly.Papertrail, error)\n\tUpdatePapertrailFn func(context.Context, *fastly.UpdatePapertrailInput) (*fastly.Papertrail, error)\n\tDeletePapertrailFn func(context.Context, *fastly.DeletePapertrailInput) error\n\n\tCreateSumologicFn func(context.Context, *fastly.CreateSumologicInput) (*fastly.Sumologic, error)\n\tListSumologicsFn  func(context.Context, *fastly.ListSumologicsInput) ([]*fastly.Sumologic, error)\n\tGetSumologicFn    func(context.Context, *fastly.GetSumologicInput) (*fastly.Sumologic, error)\n\tUpdateSumologicFn func(context.Context, *fastly.UpdateSumologicInput) (*fastly.Sumologic, error)\n\tDeleteSumologicFn func(context.Context, *fastly.DeleteSumologicInput) error\n\n\tCreateGCSFn func(context.Context, *fastly.CreateGCSInput) (*fastly.GCS, error)\n\tListGCSsFn  func(context.Context, *fastly.ListGCSsInput) ([]*fastly.GCS, error)\n\tGetGCSFn    func(context.Context, *fastly.GetGCSInput) (*fastly.GCS, error)\n\tUpdateGCSFn func(context.Context, *fastly.UpdateGCSInput) (*fastly.GCS, error)\n\tDeleteGCSFn func(context.Context, *fastly.DeleteGCSInput) error\n\n\tCreateGrafanaCloudLogsFn func(context.Context, *fastly.CreateGrafanaCloudLogsInput) (*fastly.GrafanaCloudLogs, error)\n\tListGrafanaCloudLogsFn   func(context.Context, *fastly.ListGrafanaCloudLogsInput) ([]*fastly.GrafanaCloudLogs, error)\n\tGetGrafanaCloudLogsFn    func(context.Context, *fastly.GetGrafanaCloudLogsInput) (*fastly.GrafanaCloudLogs, error)\n\tUpdateGrafanaCloudLogsFn func(context.Context, *fastly.UpdateGrafanaCloudLogsInput) (*fastly.GrafanaCloudLogs, error)\n\tDeleteGrafanaCloudLogsFn func(context.Context, *fastly.DeleteGrafanaCloudLogsInput) error\n\n\tCreateFTPFn func(context.Context, *fastly.CreateFTPInput) (*fastly.FTP, error)\n\tListFTPsFn  func(context.Context, *fastly.ListFTPsInput) ([]*fastly.FTP, error)\n\tGetFTPFn    func(context.Context, *fastly.GetFTPInput) (*fastly.FTP, error)\n\tUpdateFTPFn func(context.Context, *fastly.UpdateFTPInput) (*fastly.FTP, error)\n\tDeleteFTPFn func(context.Context, *fastly.DeleteFTPInput) error\n\n\tCreateSplunkFn func(context.Context, *fastly.CreateSplunkInput) (*fastly.Splunk, error)\n\tListSplunksFn  func(context.Context, *fastly.ListSplunksInput) ([]*fastly.Splunk, error)\n\tGetSplunkFn    func(context.Context, *fastly.GetSplunkInput) (*fastly.Splunk, error)\n\tUpdateSplunkFn func(context.Context, *fastly.UpdateSplunkInput) (*fastly.Splunk, error)\n\tDeleteSplunkFn func(context.Context, *fastly.DeleteSplunkInput) error\n\n\tCreateScalyrFn func(context.Context, *fastly.CreateScalyrInput) (*fastly.Scalyr, error)\n\tListScalyrsFn  func(context.Context, *fastly.ListScalyrsInput) ([]*fastly.Scalyr, error)\n\tGetScalyrFn    func(context.Context, *fastly.GetScalyrInput) (*fastly.Scalyr, error)\n\tUpdateScalyrFn func(context.Context, *fastly.UpdateScalyrInput) (*fastly.Scalyr, error)\n\tDeleteScalyrFn func(context.Context, *fastly.DeleteScalyrInput) error\n\n\tCreateLogglyFn func(context.Context, *fastly.CreateLogglyInput) (*fastly.Loggly, error)\n\tListLogglyFn   func(context.Context, *fastly.ListLogglyInput) ([]*fastly.Loggly, error)\n\tGetLogglyFn    func(context.Context, *fastly.GetLogglyInput) (*fastly.Loggly, error)\n\tUpdateLogglyFn func(context.Context, *fastly.UpdateLogglyInput) (*fastly.Loggly, error)\n\tDeleteLogglyFn func(context.Context, *fastly.DeleteLogglyInput) error\n\n\tCreateHoneycombFn func(context.Context, *fastly.CreateHoneycombInput) (*fastly.Honeycomb, error)\n\tListHoneycombsFn  func(context.Context, *fastly.ListHoneycombsInput) ([]*fastly.Honeycomb, error)\n\tGetHoneycombFn    func(context.Context, *fastly.GetHoneycombInput) (*fastly.Honeycomb, error)\n\tUpdateHoneycombFn func(context.Context, *fastly.UpdateHoneycombInput) (*fastly.Honeycomb, error)\n\tDeleteHoneycombFn func(context.Context, *fastly.DeleteHoneycombInput) error\n\n\tCreateHerokuFn func(context.Context, *fastly.CreateHerokuInput) (*fastly.Heroku, error)\n\tListHerokusFn  func(context.Context, *fastly.ListHerokusInput) ([]*fastly.Heroku, error)\n\tGetHerokuFn    func(context.Context, *fastly.GetHerokuInput) (*fastly.Heroku, error)\n\tUpdateHerokuFn func(context.Context, *fastly.UpdateHerokuInput) (*fastly.Heroku, error)\n\tDeleteHerokuFn func(context.Context, *fastly.DeleteHerokuInput) error\n\n\tCreateSFTPFn func(context.Context, *fastly.CreateSFTPInput) (*fastly.SFTP, error)\n\tListSFTPsFn  func(context.Context, *fastly.ListSFTPsInput) ([]*fastly.SFTP, error)\n\tGetSFTPFn    func(context.Context, *fastly.GetSFTPInput) (*fastly.SFTP, error)\n\tUpdateSFTPFn func(context.Context, *fastly.UpdateSFTPInput) (*fastly.SFTP, error)\n\tDeleteSFTPFn func(context.Context, *fastly.DeleteSFTPInput) error\n\n\tCreateLogshuttleFn func(context.Context, *fastly.CreateLogshuttleInput) (*fastly.Logshuttle, error)\n\tListLogshuttlesFn  func(context.Context, *fastly.ListLogshuttlesInput) ([]*fastly.Logshuttle, error)\n\tGetLogshuttleFn    func(context.Context, *fastly.GetLogshuttleInput) (*fastly.Logshuttle, error)\n\tUpdateLogshuttleFn func(context.Context, *fastly.UpdateLogshuttleInput) (*fastly.Logshuttle, error)\n\tDeleteLogshuttleFn func(context.Context, *fastly.DeleteLogshuttleInput) error\n\n\tCreateCloudfilesFn func(context.Context, *fastly.CreateCloudfilesInput) (*fastly.Cloudfiles, error)\n\tListCloudfilesFn   func(context.Context, *fastly.ListCloudfilesInput) ([]*fastly.Cloudfiles, error)\n\tGetCloudfilesFn    func(context.Context, *fastly.GetCloudfilesInput) (*fastly.Cloudfiles, error)\n\tUpdateCloudfilesFn func(context.Context, *fastly.UpdateCloudfilesInput) (*fastly.Cloudfiles, error)\n\tDeleteCloudfilesFn func(context.Context, *fastly.DeleteCloudfilesInput) error\n\n\tCreateDigitalOceanFn func(context.Context, *fastly.CreateDigitalOceanInput) (*fastly.DigitalOcean, error)\n\tListDigitalOceansFn  func(context.Context, *fastly.ListDigitalOceansInput) ([]*fastly.DigitalOcean, error)\n\tGetDigitalOceanFn    func(context.Context, *fastly.GetDigitalOceanInput) (*fastly.DigitalOcean, error)\n\tUpdateDigitalOceanFn func(context.Context, *fastly.UpdateDigitalOceanInput) (*fastly.DigitalOcean, error)\n\tDeleteDigitalOceanFn func(context.Context, *fastly.DeleteDigitalOceanInput) error\n\n\tCreateElasticsearchFn func(context.Context, *fastly.CreateElasticsearchInput) (*fastly.Elasticsearch, error)\n\tListElasticsearchFn   func(context.Context, *fastly.ListElasticsearchInput) ([]*fastly.Elasticsearch, error)\n\tGetElasticsearchFn    func(context.Context, *fastly.GetElasticsearchInput) (*fastly.Elasticsearch, error)\n\tUpdateElasticsearchFn func(context.Context, *fastly.UpdateElasticsearchInput) (*fastly.Elasticsearch, error)\n\tDeleteElasticsearchFn func(context.Context, *fastly.DeleteElasticsearchInput) error\n\n\tCreateBlobStorageFn func(context.Context, *fastly.CreateBlobStorageInput) (*fastly.BlobStorage, error)\n\tListBlobStoragesFn  func(context.Context, *fastly.ListBlobStoragesInput) ([]*fastly.BlobStorage, error)\n\tGetBlobStorageFn    func(context.Context, *fastly.GetBlobStorageInput) (*fastly.BlobStorage, error)\n\tUpdateBlobStorageFn func(context.Context, *fastly.UpdateBlobStorageInput) (*fastly.BlobStorage, error)\n\tDeleteBlobStorageFn func(context.Context, *fastly.DeleteBlobStorageInput) error\n\n\tCreateDatadogFn func(context.Context, *fastly.CreateDatadogInput) (*fastly.Datadog, error)\n\tListDatadogFn   func(context.Context, *fastly.ListDatadogInput) ([]*fastly.Datadog, error)\n\tGetDatadogFn    func(context.Context, *fastly.GetDatadogInput) (*fastly.Datadog, error)\n\tUpdateDatadogFn func(context.Context, *fastly.UpdateDatadogInput) (*fastly.Datadog, error)\n\tDeleteDatadogFn func(context.Context, *fastly.DeleteDatadogInput) error\n\n\tCreateHTTPSFn func(context.Context, *fastly.CreateHTTPSInput) (*fastly.HTTPS, error)\n\tListHTTPSFn   func(context.Context, *fastly.ListHTTPSInput) ([]*fastly.HTTPS, error)\n\tGetHTTPSFn    func(context.Context, *fastly.GetHTTPSInput) (*fastly.HTTPS, error)\n\tUpdateHTTPSFn func(context.Context, *fastly.UpdateHTTPSInput) (*fastly.HTTPS, error)\n\tDeleteHTTPSFn func(context.Context, *fastly.DeleteHTTPSInput) error\n\n\tCreateKafkaFn func(context.Context, *fastly.CreateKafkaInput) (*fastly.Kafka, error)\n\tListKafkasFn  func(context.Context, *fastly.ListKafkasInput) ([]*fastly.Kafka, error)\n\tGetKafkaFn    func(context.Context, *fastly.GetKafkaInput) (*fastly.Kafka, error)\n\tUpdateKafkaFn func(context.Context, *fastly.UpdateKafkaInput) (*fastly.Kafka, error)\n\tDeleteKafkaFn func(context.Context, *fastly.DeleteKafkaInput) error\n\n\tCreatePubsubFn func(context.Context, *fastly.CreatePubsubInput) (*fastly.Pubsub, error)\n\tListPubsubsFn  func(context.Context, *fastly.ListPubsubsInput) ([]*fastly.Pubsub, error)\n\tGetPubsubFn    func(context.Context, *fastly.GetPubsubInput) (*fastly.Pubsub, error)\n\tUpdatePubsubFn func(context.Context, *fastly.UpdatePubsubInput) (*fastly.Pubsub, error)\n\tDeletePubsubFn func(context.Context, *fastly.DeletePubsubInput) error\n\n\tCreateOpenstackFn func(context.Context, *fastly.CreateOpenstackInput) (*fastly.Openstack, error)\n\tListOpenstacksFn  func(context.Context, *fastly.ListOpenstackInput) ([]*fastly.Openstack, error)\n\tGetOpenstackFn    func(context.Context, *fastly.GetOpenstackInput) (*fastly.Openstack, error)\n\tUpdateOpenstackFn func(context.Context, *fastly.UpdateOpenstackInput) (*fastly.Openstack, error)\n\tDeleteOpenstackFn func(context.Context, *fastly.DeleteOpenstackInput) error\n\n\tGetRegionsFn   func(context.Context) (*fastly.RegionsResponse, error)\n\tGetStatsJSONFn func(context.Context, *fastly.GetStatsInput, any) error\n\n\tGetAggregateJSONFn               func(context.Context, *fastly.GetAggregateInput, any) error\n\tGetUsageFn                       func(context.Context, *fastly.GetUsageInput) (*fastly.UsageResponse, error)\n\tGetUsageByServiceFn              func(context.Context, *fastly.GetUsageInput) (*fastly.UsageByServiceResponse, error)\n\tGetDomainMetricsForServiceFn     func(context.Context, *fastly.GetDomainMetricsInput) (*fastly.DomainInspector, error)\n\tGetDomainMetricsForServiceJSONFn func(context.Context, *fastly.GetDomainMetricsInput, any) error\n\tGetOriginMetricsForServiceFn     func(context.Context, *fastly.GetOriginMetricsInput) (*fastly.OriginInspector, error)\n\tGetOriginMetricsForServiceJSONFn func(context.Context, *fastly.GetOriginMetricsInput, any) error\n\n\tCreateManagedLoggingFn     func(context.Context, *fastly.CreateManagedLoggingInput) (*fastly.ManagedLogging, error)\n\tGetLoggingEndpointErrorsFn func(context.Context, *fastly.LoggingEndpointErrorsInput) (*fastly.LoggingEndpointErrorsResponse, error)\n\n\tGetGeneratedVCLFn func(context.Context, *fastly.GetGeneratedVCLInput) (*fastly.VCL, error)\n\n\tCreateVCLFn func(context.Context, *fastly.CreateVCLInput) (*fastly.VCL, error)\n\tListVCLsFn  func(context.Context, *fastly.ListVCLsInput) ([]*fastly.VCL, error)\n\tGetVCLFn    func(context.Context, *fastly.GetVCLInput) (*fastly.VCL, error)\n\tUpdateVCLFn func(context.Context, *fastly.UpdateVCLInput) (*fastly.VCL, error)\n\tDeleteVCLFn func(context.Context, *fastly.DeleteVCLInput) error\n\n\tCreateSnippetFn        func(context.Context, *fastly.CreateSnippetInput) (*fastly.Snippet, error)\n\tListSnippetsFn         func(context.Context, *fastly.ListSnippetsInput) ([]*fastly.Snippet, error)\n\tGetSnippetFn           func(context.Context, *fastly.GetSnippetInput) (*fastly.Snippet, error)\n\tGetDynamicSnippetFn    func(context.Context, *fastly.GetDynamicSnippetInput) (*fastly.DynamicSnippet, error)\n\tUpdateSnippetFn        func(context.Context, *fastly.UpdateSnippetInput) (*fastly.Snippet, error)\n\tUpdateDynamicSnippetFn func(context.Context, *fastly.UpdateDynamicSnippetInput) (*fastly.DynamicSnippet, error)\n\tDeleteSnippetFn        func(context.Context, *fastly.DeleteSnippetInput) error\n\n\tPurgeFn     func(context.Context, *fastly.PurgeInput) (*fastly.Purge, error)\n\tPurgeKeyFn  func(context.Context, *fastly.PurgeKeyInput) (*fastly.Purge, error)\n\tPurgeKeysFn func(context.Context, *fastly.PurgeKeysInput) (map[string]string, error)\n\tPurgeAllFn  func(context.Context, *fastly.PurgeAllInput) (*fastly.Purge, error)\n\n\tCreateACLFn func(context.Context, *fastly.CreateACLInput) (*fastly.ACL, error)\n\tDeleteACLFn func(context.Context, *fastly.DeleteACLInput) error\n\tGetACLFn    func(context.Context, *fastly.GetACLInput) (*fastly.ACL, error)\n\tListACLsFn  func(context.Context, *fastly.ListACLsInput) ([]*fastly.ACL, error)\n\tUpdateACLFn func(context.Context, *fastly.UpdateACLInput) (*fastly.ACL, error)\n\n\tCreateACLEntryFn        func(context.Context, *fastly.CreateACLEntryInput) (*fastly.ACLEntry, error)\n\tDeleteACLEntryFn        func(context.Context, *fastly.DeleteACLEntryInput) error\n\tGetACLEntryFn           func(context.Context, *fastly.GetACLEntryInput) (*fastly.ACLEntry, error)\n\tGetACLEntriesFn         func(context.Context, *fastly.GetACLEntriesInput) *fastly.ListPaginator[fastly.ACLEntry]\n\tListACLEntriesFn        func(context.Context, *fastly.ListACLEntriesInput) ([]*fastly.ACLEntry, error)\n\tUpdateACLEntryFn        func(context.Context, *fastly.UpdateACLEntryInput) (*fastly.ACLEntry, error)\n\tBatchModifyACLEntriesFn func(context.Context, *fastly.BatchModifyACLEntriesInput) error\n\n\tCreateNewRelicFn func(context.Context, *fastly.CreateNewRelicInput) (*fastly.NewRelic, error)\n\tDeleteNewRelicFn func(context.Context, *fastly.DeleteNewRelicInput) error\n\tGetNewRelicFn    func(context.Context, *fastly.GetNewRelicInput) (*fastly.NewRelic, error)\n\tListNewRelicFn   func(context.Context, *fastly.ListNewRelicInput) ([]*fastly.NewRelic, error)\n\tUpdateNewRelicFn func(context.Context, *fastly.UpdateNewRelicInput) (*fastly.NewRelic, error)\n\n\tCreateNewRelicOTLPFn func(context.Context, *fastly.CreateNewRelicOTLPInput) (*fastly.NewRelicOTLP, error)\n\tDeleteNewRelicOTLPFn func(context.Context, *fastly.DeleteNewRelicOTLPInput) error\n\tGetNewRelicOTLPFn    func(context.Context, *fastly.GetNewRelicOTLPInput) (*fastly.NewRelicOTLP, error)\n\tListNewRelicOTLPFn   func(context.Context, *fastly.ListNewRelicOTLPInput) ([]*fastly.NewRelicOTLP, error)\n\tUpdateNewRelicOTLPFn func(context.Context, *fastly.UpdateNewRelicOTLPInput) (*fastly.NewRelicOTLP, error)\n\n\tCreateUserFn        func(context.Context, *fastly.CreateUserInput) (*fastly.User, error)\n\tDeleteUserFn        func(context.Context, *fastly.DeleteUserInput) error\n\tGetCurrentUserFn    func(context.Context) (*fastly.User, error)\n\tGetUserFn           func(context.Context, *fastly.GetUserInput) (*fastly.User, error)\n\tListCustomerUsersFn func(context.Context, *fastly.ListCustomerUsersInput) ([]*fastly.User, error)\n\tUpdateUserFn        func(context.Context, *fastly.UpdateUserInput) (*fastly.User, error)\n\tResetUserPasswordFn func(context.Context, *fastly.ResetUserPasswordInput) error\n\n\tBatchDeleteTokensFn  func(context.Context, *fastly.BatchDeleteTokensInput) error\n\tCreateTokenFn        func(context.Context, *fastly.CreateTokenInput) (*fastly.Token, error)\n\tDeleteTokenFn        func(context.Context, *fastly.DeleteTokenInput) error\n\tDeleteTokenSelfFn    func(context.Context) error\n\tGetTokenSelfFn       func(context.Context) (*fastly.Token, error)\n\tListCustomerTokensFn func(context.Context, *fastly.ListCustomerTokensInput) ([]*fastly.Token, error)\n\tListTokensFn         func(context.Context, *fastly.ListTokensInput) ([]*fastly.Token, error)\n\n\tNewListKVStoreKeysPaginatorFn func(context.Context, *fastly.ListKVStoreKeysInput) fastly.PaginatorKVStoreEntries\n\n\tGetCustomTLSConfigurationFn    func(context.Context, *fastly.GetCustomTLSConfigurationInput) (*fastly.CustomTLSConfiguration, error)\n\tListCustomTLSConfigurationsFn  func(context.Context, *fastly.ListCustomTLSConfigurationsInput) ([]*fastly.CustomTLSConfiguration, error)\n\tUpdateCustomTLSConfigurationFn func(context.Context, *fastly.UpdateCustomTLSConfigurationInput) (*fastly.CustomTLSConfiguration, error)\n\tGetTLSActivationFn             func(context.Context, *fastly.GetTLSActivationInput) (*fastly.TLSActivation, error)\n\tListTLSActivationsFn           func(context.Context, *fastly.ListTLSActivationsInput) ([]*fastly.TLSActivation, error)\n\tUpdateTLSActivationFn          func(context.Context, *fastly.UpdateTLSActivationInput) (*fastly.TLSActivation, error)\n\tCreateTLSActivationFn          func(context.Context, *fastly.CreateTLSActivationInput) (*fastly.TLSActivation, error)\n\tDeleteTLSActivationFn          func(context.Context, *fastly.DeleteTLSActivationInput) error\n\n\tCreateCustomTLSCertificateFn func(context.Context, *fastly.CreateCustomTLSCertificateInput) (*fastly.CustomTLSCertificate, error)\n\tDeleteCustomTLSCertificateFn func(context.Context, *fastly.DeleteCustomTLSCertificateInput) error\n\tGetCustomTLSCertificateFn    func(context.Context, *fastly.GetCustomTLSCertificateInput) (*fastly.CustomTLSCertificate, error)\n\tListCustomTLSCertificatesFn  func(context.Context, *fastly.ListCustomTLSCertificatesInput) ([]*fastly.CustomTLSCertificate, error)\n\tUpdateCustomTLSCertificateFn func(context.Context, *fastly.UpdateCustomTLSCertificateInput) (*fastly.CustomTLSCertificate, error)\n\n\tListTLSDomainsFn func(context.Context, *fastly.ListTLSDomainsInput) ([]*fastly.TLSDomain, error)\n\n\tCreatePrivateKeyFn func(context.Context, *fastly.CreatePrivateKeyInput) (*fastly.PrivateKey, error)\n\tDeletePrivateKeyFn func(context.Context, *fastly.DeletePrivateKeyInput) error\n\tGetPrivateKeyFn    func(context.Context, *fastly.GetPrivateKeyInput) (*fastly.PrivateKey, error)\n\tListPrivateKeysFn  func(context.Context, *fastly.ListPrivateKeysInput) ([]*fastly.PrivateKey, error)\n\n\tCreateBulkCertificateFn func(context.Context, *fastly.CreateBulkCertificateInput) (*fastly.BulkCertificate, error)\n\tDeleteBulkCertificateFn func(context.Context, *fastly.DeleteBulkCertificateInput) error\n\tGetBulkCertificateFn    func(context.Context, *fastly.GetBulkCertificateInput) (*fastly.BulkCertificate, error)\n\tListBulkCertificatesFn  func(context.Context, *fastly.ListBulkCertificatesInput) ([]*fastly.BulkCertificate, error)\n\tUpdateBulkCertificateFn func(context.Context, *fastly.UpdateBulkCertificateInput) (*fastly.BulkCertificate, error)\n\n\tCreateTLSSubscriptionFn func(context.Context, *fastly.CreateTLSSubscriptionInput) (*fastly.TLSSubscription, error)\n\tDeleteTLSSubscriptionFn func(context.Context, *fastly.DeleteTLSSubscriptionInput) error\n\tGetTLSSubscriptionFn    func(context.Context, *fastly.GetTLSSubscriptionInput) (*fastly.TLSSubscription, error)\n\tListTLSSubscriptionsFn  func(context.Context, *fastly.ListTLSSubscriptionsInput) ([]*fastly.TLSSubscription, error)\n\tUpdateTLSSubscriptionFn func(context.Context, *fastly.UpdateTLSSubscriptionInput) (*fastly.TLSSubscription, error)\n\n\tListServiceAuthorizationsFn  func(context.Context, *fastly.ListServiceAuthorizationsInput) (*fastly.ServiceAuthorizations, error)\n\tGetServiceAuthorizationFn    func(context.Context, *fastly.GetServiceAuthorizationInput) (*fastly.ServiceAuthorization, error)\n\tCreateServiceAuthorizationFn func(context.Context, *fastly.CreateServiceAuthorizationInput) (*fastly.ServiceAuthorization, error)\n\tUpdateServiceAuthorizationFn func(context.Context, *fastly.UpdateServiceAuthorizationInput) (*fastly.ServiceAuthorization, error)\n\tDeleteServiceAuthorizationFn func(context.Context, *fastly.DeleteServiceAuthorizationInput) error\n\n\tCreateConfigStoreFn       func(context.Context, *fastly.CreateConfigStoreInput) (*fastly.ConfigStore, error)\n\tDeleteConfigStoreFn       func(context.Context, *fastly.DeleteConfigStoreInput) error\n\tGetConfigStoreFn          func(context.Context, *fastly.GetConfigStoreInput) (*fastly.ConfigStore, error)\n\tGetConfigStoreMetadataFn  func(context.Context, *fastly.GetConfigStoreMetadataInput) (*fastly.ConfigStoreMetadata, error)\n\tListConfigStoresFn        func(context.Context, *fastly.ListConfigStoresInput) ([]*fastly.ConfigStore, error)\n\tListConfigStoreServicesFn func(context.Context, *fastly.ListConfigStoreServicesInput) ([]*fastly.Service, error)\n\tUpdateConfigStoreFn       func(context.Context, *fastly.UpdateConfigStoreInput) (*fastly.ConfigStore, error)\n\n\tCreateConfigStoreItemFn func(context.Context, *fastly.CreateConfigStoreItemInput) (*fastly.ConfigStoreItem, error)\n\tDeleteConfigStoreItemFn func(context.Context, *fastly.DeleteConfigStoreItemInput) error\n\tGetConfigStoreItemFn    func(context.Context, *fastly.GetConfigStoreItemInput) (*fastly.ConfigStoreItem, error)\n\tListConfigStoreItemsFn  func(context.Context, *fastly.ListConfigStoreItemsInput) ([]*fastly.ConfigStoreItem, error)\n\tUpdateConfigStoreItemFn func(context.Context, *fastly.UpdateConfigStoreItemInput) (*fastly.ConfigStoreItem, error)\n\n\tCreateKVStoreFn         func(context.Context, *fastly.CreateKVStoreInput) (*fastly.KVStore, error)\n\tGetKVStoreFn            func(context.Context, *fastly.GetKVStoreInput) (*fastly.KVStore, error)\n\tListKVStoresFn          func(context.Context, *fastly.ListKVStoresInput) (*fastly.ListKVStoresResponse, error)\n\tDeleteKVStoreFn         func(context.Context, *fastly.DeleteKVStoreInput) error\n\tListKVStoreKeysFn       func(context.Context, *fastly.ListKVStoreKeysInput) (*fastly.ListKVStoreKeysResponse, error)\n\tGetKVStoreKeyFn         func(context.Context, *fastly.GetKVStoreKeyInput) (string, error)\n\tGetKVStoreItemFn        func(context.Context, *fastly.GetKVStoreItemInput) (fastly.GetKVStoreItemOutput, error)\n\tInsertKVStoreKeyFn      func(context.Context, *fastly.InsertKVStoreKeyInput) error\n\tDeleteKVStoreKeyFn      func(context.Context, *fastly.DeleteKVStoreKeyInput) error\n\tBatchModifyKVStoreKeyFn func(context.Context, *fastly.BatchModifyKVStoreKeyInput) error\n\n\tCreateSecretStoreFn func(context.Context, *fastly.CreateSecretStoreInput) (*fastly.SecretStore, error)\n\tGetSecretStoreFn    func(context.Context, *fastly.GetSecretStoreInput) (*fastly.SecretStore, error)\n\tDeleteSecretStoreFn func(context.Context, *fastly.DeleteSecretStoreInput) error\n\tListSecretStoresFn  func(context.Context, *fastly.ListSecretStoresInput) (*fastly.SecretStores, error)\n\tCreateSecretFn      func(context.Context, *fastly.CreateSecretInput) (*fastly.Secret, error)\n\tGetSecretFn         func(context.Context, *fastly.GetSecretInput) (*fastly.Secret, error)\n\tDeleteSecretFn      func(context.Context, *fastly.DeleteSecretInput) error\n\tListSecretsFn       func(context.Context, *fastly.ListSecretsInput) (*fastly.Secrets, error)\n\tCreateClientKeyFn   func(context.Context) (*fastly.ClientKey, error)\n\tGetSigningKeyFn     func(context.Context) (ed25519.PublicKey, error)\n\n\tCreateResourceFn func(context.Context, *fastly.CreateResourceInput) (*fastly.Resource, error)\n\tDeleteResourceFn func(context.Context, *fastly.DeleteResourceInput) error\n\tGetResourceFn    func(context.Context, *fastly.GetResourceInput) (*fastly.Resource, error)\n\tListResourcesFn  func(context.Context, *fastly.ListResourcesInput) ([]*fastly.Resource, error)\n\tUpdateResourceFn func(context.Context, *fastly.UpdateResourceInput) (*fastly.Resource, error)\n\n\tCreateERLFn func(context.Context, *fastly.CreateERLInput) (*fastly.ERL, error)\n\tDeleteERLFn func(context.Context, *fastly.DeleteERLInput) error\n\tGetERLFn    func(context.Context, *fastly.GetERLInput) (*fastly.ERL, error)\n\tListERLsFn  func(context.Context, *fastly.ListERLsInput) ([]*fastly.ERL, error)\n\tUpdateERLFn func(context.Context, *fastly.UpdateERLInput) (*fastly.ERL, error)\n\n\tCreateConditionFn func(context.Context, *fastly.CreateConditionInput) (*fastly.Condition, error)\n\tDeleteConditionFn func(context.Context, *fastly.DeleteConditionInput) error\n\tGetConditionFn    func(context.Context, *fastly.GetConditionInput) (*fastly.Condition, error)\n\tListConditionsFn  func(context.Context, *fastly.ListConditionsInput) ([]*fastly.Condition, error)\n\tUpdateConditionFn func(context.Context, *fastly.UpdateConditionInput) (*fastly.Condition, error)\n\n\tListAlertDefinitionsFn  func(context.Context, *fastly.ListAlertDefinitionsInput) (*fastly.AlertDefinitionsResponse, error)\n\tCreateAlertDefinitionFn func(context.Context, *fastly.CreateAlertDefinitionInput) (*fastly.AlertDefinition, error)\n\tGetAlertDefinitionFn    func(context.Context, *fastly.GetAlertDefinitionInput) (*fastly.AlertDefinition, error)\n\tUpdateAlertDefinitionFn func(context.Context, *fastly.UpdateAlertDefinitionInput) (*fastly.AlertDefinition, error)\n\tDeleteAlertDefinitionFn func(context.Context, *fastly.DeleteAlertDefinitionInput) error\n\tTestAlertDefinitionFn   func(context.Context, *fastly.TestAlertDefinitionInput) error\n\tListAlertHistoryFn      func(context.Context, *fastly.ListAlertHistoryInput) (*fastly.AlertHistoryResponse, error)\n\n\tListObservabilityCustomDashboardsFn  func(context.Context, *fastly.ListObservabilityCustomDashboardsInput) (*fastly.ListDashboardsResponse, error)\n\tCreateObservabilityCustomDashboardFn func(context.Context, *fastly.CreateObservabilityCustomDashboardInput) (*fastly.ObservabilityCustomDashboard, error)\n\tGetObservabilityCustomDashboardFn    func(context.Context, *fastly.GetObservabilityCustomDashboardInput) (*fastly.ObservabilityCustomDashboard, error)\n\tUpdateObservabilityCustomDashboardFn func(context.Context, *fastly.UpdateObservabilityCustomDashboardInput) (*fastly.ObservabilityCustomDashboard, error)\n\tDeleteObservabilityCustomDashboardFn func(context.Context, *fastly.DeleteObservabilityCustomDashboardInput) error\n\n\tGetImageOptimizerDefaultSettingsFn    func(context.Context, *fastly.GetImageOptimizerDefaultSettingsInput) (*fastly.ImageOptimizerDefaultSettings, error)\n\tUpdateImageOptimizerDefaultSettingsFn func(context.Context, *fastly.UpdateImageOptimizerDefaultSettingsInput) (*fastly.ImageOptimizerDefaultSettings, error)\n}\n\n// AllDatacenters implements Interface.\nfunc (m API) AllDatacenters(ctx context.Context) ([]fastly.Datacenter, error) {\n\treturn m.AllDatacentersFn(ctx)\n}\n\n// AllIPs implements Interface.\nfunc (m API) AllIPs(ctx context.Context) (fastly.IPAddrs, fastly.IPAddrs, error) {\n\treturn m.AllIPsFn(ctx)\n}\n\n// CreateService implements Interface.\nfunc (m API) CreateService(ctx context.Context, i *fastly.CreateServiceInput) (*fastly.Service, error) {\n\treturn m.CreateServiceFn(ctx, i)\n}\n\n// GetServices implements Interface.\nfunc (m API) GetServices(ctx context.Context, i *fastly.GetServicesInput) *fastly.ListPaginator[fastly.Service] {\n\treturn m.GetServicesFn(ctx, i)\n}\n\n// ListServices implements Interface.\nfunc (m API) ListServices(ctx context.Context, i *fastly.ListServicesInput) ([]*fastly.Service, error) {\n\treturn m.ListServicesFn(ctx, i)\n}\n\n// GetService implements Interface.\nfunc (m API) GetService(ctx context.Context, i *fastly.GetServiceInput) (*fastly.Service, error) {\n\treturn m.GetServiceFn(ctx, i)\n}\n\n// GetServiceDetails implements Interface.\nfunc (m API) GetServiceDetails(ctx context.Context, i *fastly.GetServiceDetailsInput) (*fastly.ServiceDetail, error) {\n\treturn m.GetServiceDetailsFn(ctx, i)\n}\n\n// SearchService implements Interface.\nfunc (m API) SearchService(ctx context.Context, i *fastly.SearchServiceInput) (*fastly.Service, error) {\n\treturn m.SearchServiceFn(ctx, i)\n}\n\n// UpdateService implements Interface.\nfunc (m API) UpdateService(ctx context.Context, i *fastly.UpdateServiceInput) (*fastly.Service, error) {\n\treturn m.UpdateServiceFn(ctx, i)\n}\n\n// DeleteService implements Interface.\nfunc (m API) DeleteService(ctx context.Context, i *fastly.DeleteServiceInput) error {\n\treturn m.DeleteServiceFn(ctx, i)\n}\n\n// CloneVersion implements Interface.\nfunc (m API) CloneVersion(ctx context.Context, i *fastly.CloneVersionInput) (*fastly.Version, error) {\n\treturn m.CloneVersionFn(ctx, i)\n}\n\n// ListVersions implements Interface.\nfunc (m API) ListVersions(ctx context.Context, i *fastly.ListVersionsInput) ([]*fastly.Version, error) {\n\treturn m.ListVersionsFn(ctx, i)\n}\n\n// GetVersion implements Interface.\nfunc (m API) GetVersion(ctx context.Context, i *fastly.GetVersionInput) (*fastly.Version, error) {\n\treturn m.GetVersionFn(ctx, i)\n}\n\n// UpdateVersion implements Interface.\nfunc (m API) UpdateVersion(ctx context.Context, i *fastly.UpdateVersionInput) (*fastly.Version, error) {\n\treturn m.UpdateVersionFn(ctx, i)\n}\n\n// ActivateVersion implements Interface.\nfunc (m API) ActivateVersion(ctx context.Context, i *fastly.ActivateVersionInput) (*fastly.Version, error) {\n\treturn m.ActivateVersionFn(ctx, i)\n}\n\n// DeactivateVersion implements Interface.\nfunc (m API) DeactivateVersion(ctx context.Context, i *fastly.DeactivateVersionInput) (*fastly.Version, error) {\n\treturn m.DeactivateVersionFn(ctx, i)\n}\n\n// LockVersion implements Interface.\nfunc (m API) LockVersion(ctx context.Context, i *fastly.LockVersionInput) (*fastly.Version, error) {\n\treturn m.LockVersionFn(ctx, i)\n}\n\n// LatestVersion implements Interface.\nfunc (m API) LatestVersion(ctx context.Context, i *fastly.LatestVersionInput) (*fastly.Version, error) {\n\treturn m.LatestVersionFn(ctx, i)\n}\n\n// ValidateVersion implements Interface.\nfunc (m API) ValidateVersion(ctx context.Context, i *fastly.ValidateVersionInput) (bool, string, error) {\n\treturn m.ValidateVersionFn(ctx, i)\n}\n\n// CreateDomain implements Interface.\nfunc (m API) CreateDomain(ctx context.Context, i *fastly.CreateDomainInput) (*fastly.Domain, error) {\n\treturn m.CreateDomainFn(ctx, i)\n}\n\n// ListDomains implements Interface.\nfunc (m API) ListDomains(ctx context.Context, i *fastly.ListDomainsInput) ([]*fastly.Domain, error) {\n\treturn m.ListDomainsFn(ctx, i)\n}\n\n// GetDomain implements Interface.\nfunc (m API) GetDomain(ctx context.Context, i *fastly.GetDomainInput) (*fastly.Domain, error) {\n\treturn m.GetDomainFn(ctx, i)\n}\n\n// UpdateDomain implements Interface.\nfunc (m API) UpdateDomain(ctx context.Context, i *fastly.UpdateDomainInput) (*fastly.Domain, error) {\n\treturn m.UpdateDomainFn(ctx, i)\n}\n\n// DeleteDomain implements Interface.\nfunc (m API) DeleteDomain(ctx context.Context, i *fastly.DeleteDomainInput) error {\n\treturn m.DeleteDomainFn(ctx, i)\n}\n\n// ValidateDomain implements Interface.\nfunc (m API) ValidateDomain(ctx context.Context, i *fastly.ValidateDomainInput) (*fastly.DomainValidationResult, error) {\n\treturn m.ValidateDomainFn(ctx, i)\n}\n\n// ValidateAllDomains implements Interface.\nfunc (m API) ValidateAllDomains(ctx context.Context, i *fastly.ValidateAllDomainsInput) (results []*fastly.DomainValidationResult, err error) {\n\treturn m.ValidateAllDomainsFn(ctx, i)\n}\n\n// CreateBackend implements Interface.\nfunc (m API) CreateBackend(ctx context.Context, i *fastly.CreateBackendInput) (*fastly.Backend, error) {\n\treturn m.CreateBackendFn(ctx, i)\n}\n\n// ListBackends implements Interface.\nfunc (m API) ListBackends(ctx context.Context, i *fastly.ListBackendsInput) ([]*fastly.Backend, error) {\n\treturn m.ListBackendsFn(ctx, i)\n}\n\n// GetBackend implements Interface.\nfunc (m API) GetBackend(ctx context.Context, i *fastly.GetBackendInput) (*fastly.Backend, error) {\n\treturn m.GetBackendFn(ctx, i)\n}\n\n// UpdateBackend implements Interface.\nfunc (m API) UpdateBackend(ctx context.Context, i *fastly.UpdateBackendInput) (*fastly.Backend, error) {\n\treturn m.UpdateBackendFn(ctx, i)\n}\n\n// DeleteBackend implements Interface.\nfunc (m API) DeleteBackend(ctx context.Context, i *fastly.DeleteBackendInput) error {\n\treturn m.DeleteBackendFn(ctx, i)\n}\n\n// CreateHealthCheck implements Interface.\nfunc (m API) CreateHealthCheck(ctx context.Context, i *fastly.CreateHealthCheckInput) (*fastly.HealthCheck, error) {\n\treturn m.CreateHealthCheckFn(ctx, i)\n}\n\n// ListHealthChecks implements Interface.\nfunc (m API) ListHealthChecks(ctx context.Context, i *fastly.ListHealthChecksInput) ([]*fastly.HealthCheck, error) {\n\treturn m.ListHealthChecksFn(ctx, i)\n}\n\n// GetHealthCheck implements Interface.\nfunc (m API) GetHealthCheck(ctx context.Context, i *fastly.GetHealthCheckInput) (*fastly.HealthCheck, error) {\n\treturn m.GetHealthCheckFn(ctx, i)\n}\n\n// UpdateHealthCheck implements Interface.\nfunc (m API) UpdateHealthCheck(ctx context.Context, i *fastly.UpdateHealthCheckInput) (*fastly.HealthCheck, error) {\n\treturn m.UpdateHealthCheckFn(ctx, i)\n}\n\n// DeleteHealthCheck implements Interface.\nfunc (m API) DeleteHealthCheck(ctx context.Context, i *fastly.DeleteHealthCheckInput) error {\n\treturn m.DeleteHealthCheckFn(ctx, i)\n}\n\n// GetPackage implements Interface.\nfunc (m API) GetPackage(ctx context.Context, i *fastly.GetPackageInput) (*fastly.Package, error) {\n\treturn m.GetPackageFn(ctx, i)\n}\n\n// UpdatePackage implements Interface.\nfunc (m API) UpdatePackage(ctx context.Context, i *fastly.UpdatePackageInput) (*fastly.Package, error) {\n\treturn m.UpdatePackageFn(ctx, i)\n}\n\n// CreateDictionary implements Interface.\nfunc (m API) CreateDictionary(ctx context.Context, i *fastly.CreateDictionaryInput) (*fastly.Dictionary, error) {\n\treturn m.CreateDictionaryFn(ctx, i)\n}\n\n// GetDictionary implements Interface.\nfunc (m API) GetDictionary(ctx context.Context, i *fastly.GetDictionaryInput) (*fastly.Dictionary, error) {\n\treturn m.GetDictionaryFn(ctx, i)\n}\n\n// DeleteDictionary implements Interface.\nfunc (m API) DeleteDictionary(ctx context.Context, i *fastly.DeleteDictionaryInput) error {\n\treturn m.DeleteDictionaryFn(ctx, i)\n}\n\n// ListDictionaries implements Interface.\nfunc (m API) ListDictionaries(ctx context.Context, i *fastly.ListDictionariesInput) ([]*fastly.Dictionary, error) {\n\treturn m.ListDictionariesFn(ctx, i)\n}\n\n// UpdateDictionary implements Interface.\nfunc (m API) UpdateDictionary(ctx context.Context, i *fastly.UpdateDictionaryInput) (*fastly.Dictionary, error) {\n\treturn m.UpdateDictionaryFn(ctx, i)\n}\n\n// GetDictionaryItems implements Interface.\nfunc (m API) GetDictionaryItems(ctx context.Context, i *fastly.GetDictionaryItemsInput) *fastly.ListPaginator[fastly.DictionaryItem] {\n\treturn m.GetDictionaryItemsFn(ctx, i)\n}\n\n// ListDictionaryItems implements Interface.\nfunc (m API) ListDictionaryItems(ctx context.Context, i *fastly.ListDictionaryItemsInput) ([]*fastly.DictionaryItem, error) {\n\treturn m.ListDictionaryItemsFn(ctx, i)\n}\n\n// GetDictionaryItem implements Interface.\nfunc (m API) GetDictionaryItem(ctx context.Context, i *fastly.GetDictionaryItemInput) (*fastly.DictionaryItem, error) {\n\treturn m.GetDictionaryItemFn(ctx, i)\n}\n\n// CreateDictionaryItem implements Interface.\nfunc (m API) CreateDictionaryItem(ctx context.Context, i *fastly.CreateDictionaryItemInput) (*fastly.DictionaryItem, error) {\n\treturn m.CreateDictionaryItemFn(ctx, i)\n}\n\n// UpdateDictionaryItem implements Interface.\nfunc (m API) UpdateDictionaryItem(ctx context.Context, i *fastly.UpdateDictionaryItemInput) (*fastly.DictionaryItem, error) {\n\treturn m.UpdateDictionaryItemFn(ctx, i)\n}\n\n// DeleteDictionaryItem implements Interface.\nfunc (m API) DeleteDictionaryItem(ctx context.Context, i *fastly.DeleteDictionaryItemInput) error {\n\treturn m.DeleteDictionaryItemFn(ctx, i)\n}\n\n// BatchModifyDictionaryItems implements Interface.\nfunc (m API) BatchModifyDictionaryItems(ctx context.Context, i *fastly.BatchModifyDictionaryItemsInput) error {\n\treturn m.BatchModifyDictionaryItemsFn(ctx, i)\n}\n\n// GetDictionaryInfo implements Interface.\nfunc (m API) GetDictionaryInfo(ctx context.Context, i *fastly.GetDictionaryInfoInput) (*fastly.DictionaryInfo, error) {\n\treturn m.GetDictionaryInfoFn(ctx, i)\n}\n\n// CreateBigQuery implements Interface.\nfunc (m API) CreateBigQuery(ctx context.Context, i *fastly.CreateBigQueryInput) (*fastly.BigQuery, error) {\n\treturn m.CreateBigQueryFn(ctx, i)\n}\n\n// ListBigQueries implements Interface.\nfunc (m API) ListBigQueries(ctx context.Context, i *fastly.ListBigQueriesInput) ([]*fastly.BigQuery, error) {\n\treturn m.ListBigQueriesFn(ctx, i)\n}\n\n// GetBigQuery implements Interface.\nfunc (m API) GetBigQuery(ctx context.Context, i *fastly.GetBigQueryInput) (*fastly.BigQuery, error) {\n\treturn m.GetBigQueryFn(ctx, i)\n}\n\n// UpdateBigQuery implements Interface.\nfunc (m API) UpdateBigQuery(ctx context.Context, i *fastly.UpdateBigQueryInput) (*fastly.BigQuery, error) {\n\treturn m.UpdateBigQueryFn(ctx, i)\n}\n\n// DeleteBigQuery implements Interface.\nfunc (m API) DeleteBigQuery(ctx context.Context, i *fastly.DeleteBigQueryInput) error {\n\treturn m.DeleteBigQueryFn(ctx, i)\n}\n\n// CreateS3 implements Interface.\nfunc (m API) CreateS3(ctx context.Context, i *fastly.CreateS3Input) (*fastly.S3, error) {\n\treturn m.CreateS3Fn(ctx, i)\n}\n\n// ListS3s implements Interface.\nfunc (m API) ListS3s(ctx context.Context, i *fastly.ListS3sInput) ([]*fastly.S3, error) {\n\treturn m.ListS3sFn(ctx, i)\n}\n\n// GetS3 implements Interface.\nfunc (m API) GetS3(ctx context.Context, i *fastly.GetS3Input) (*fastly.S3, error) {\n\treturn m.GetS3Fn(ctx, i)\n}\n\n// UpdateS3 implements Interface.\nfunc (m API) UpdateS3(ctx context.Context, i *fastly.UpdateS3Input) (*fastly.S3, error) {\n\treturn m.UpdateS3Fn(ctx, i)\n}\n\n// DeleteS3 implements Interface.\nfunc (m API) DeleteS3(ctx context.Context, i *fastly.DeleteS3Input) error {\n\treturn m.DeleteS3Fn(ctx, i)\n}\n\n// CreateKinesis implements Interface.\nfunc (m API) CreateKinesis(ctx context.Context, i *fastly.CreateKinesisInput) (*fastly.Kinesis, error) {\n\treturn m.CreateKinesisFn(ctx, i)\n}\n\n// ListKinesis implements Interface.\nfunc (m API) ListKinesis(ctx context.Context, i *fastly.ListKinesisInput) ([]*fastly.Kinesis, error) {\n\treturn m.ListKinesisFn(ctx, i)\n}\n\n// GetKinesis implements Interface.\nfunc (m API) GetKinesis(ctx context.Context, i *fastly.GetKinesisInput) (*fastly.Kinesis, error) {\n\treturn m.GetKinesisFn(ctx, i)\n}\n\n// UpdateKinesis implements Interface.\nfunc (m API) UpdateKinesis(ctx context.Context, i *fastly.UpdateKinesisInput) (*fastly.Kinesis, error) {\n\treturn m.UpdateKinesisFn(ctx, i)\n}\n\n// DeleteKinesis implements Interface.\nfunc (m API) DeleteKinesis(ctx context.Context, i *fastly.DeleteKinesisInput) error {\n\treturn m.DeleteKinesisFn(ctx, i)\n}\n\n// CreateSyslog implements Interface.\nfunc (m API) CreateSyslog(ctx context.Context, i *fastly.CreateSyslogInput) (*fastly.Syslog, error) {\n\treturn m.CreateSyslogFn(ctx, i)\n}\n\n// ListSyslogs implements Interface.\nfunc (m API) ListSyslogs(ctx context.Context, i *fastly.ListSyslogsInput) ([]*fastly.Syslog, error) {\n\treturn m.ListSyslogsFn(ctx, i)\n}\n\n// GetSyslog implements Interface.\nfunc (m API) GetSyslog(ctx context.Context, i *fastly.GetSyslogInput) (*fastly.Syslog, error) {\n\treturn m.GetSyslogFn(ctx, i)\n}\n\n// UpdateSyslog implements Interface.\nfunc (m API) UpdateSyslog(ctx context.Context, i *fastly.UpdateSyslogInput) (*fastly.Syslog, error) {\n\treturn m.UpdateSyslogFn(ctx, i)\n}\n\n// DeleteSyslog implements Interface.\nfunc (m API) DeleteSyslog(ctx context.Context, i *fastly.DeleteSyslogInput) error {\n\treturn m.DeleteSyslogFn(ctx, i)\n}\n\n// CreateLogentries implements Interface.\nfunc (m API) CreateLogentries(ctx context.Context, i *fastly.CreateLogentriesInput) (*fastly.Logentries, error) {\n\treturn m.CreateLogentriesFn(ctx, i)\n}\n\n// ListLogentries implements Interface.\nfunc (m API) ListLogentries(ctx context.Context, i *fastly.ListLogentriesInput) ([]*fastly.Logentries, error) {\n\treturn m.ListLogentriesFn(ctx, i)\n}\n\n// GetLogentries implements Interface.\nfunc (m API) GetLogentries(ctx context.Context, i *fastly.GetLogentriesInput) (*fastly.Logentries, error) {\n\treturn m.GetLogentriesFn(ctx, i)\n}\n\n// UpdateLogentries implements Interface.\nfunc (m API) UpdateLogentries(ctx context.Context, i *fastly.UpdateLogentriesInput) (*fastly.Logentries, error) {\n\treturn m.UpdateLogentriesFn(ctx, i)\n}\n\n// DeleteLogentries implements Interface.\nfunc (m API) DeleteLogentries(ctx context.Context, i *fastly.DeleteLogentriesInput) error {\n\treturn m.DeleteLogentriesFn(ctx, i)\n}\n\n// CreatePapertrail implements Interface.\nfunc (m API) CreatePapertrail(ctx context.Context, i *fastly.CreatePapertrailInput) (*fastly.Papertrail, error) {\n\treturn m.CreatePapertrailFn(ctx, i)\n}\n\n// ListPapertrails implements Interface.\nfunc (m API) ListPapertrails(ctx context.Context, i *fastly.ListPapertrailsInput) ([]*fastly.Papertrail, error) {\n\treturn m.ListPapertrailsFn(ctx, i)\n}\n\n// GetPapertrail implements Interface.\nfunc (m API) GetPapertrail(ctx context.Context, i *fastly.GetPapertrailInput) (*fastly.Papertrail, error) {\n\treturn m.GetPapertrailFn(ctx, i)\n}\n\n// UpdatePapertrail implements Interface.\nfunc (m API) UpdatePapertrail(ctx context.Context, i *fastly.UpdatePapertrailInput) (*fastly.Papertrail, error) {\n\treturn m.UpdatePapertrailFn(ctx, i)\n}\n\n// DeletePapertrail implements Interface.\nfunc (m API) DeletePapertrail(ctx context.Context, i *fastly.DeletePapertrailInput) error {\n\treturn m.DeletePapertrailFn(ctx, i)\n}\n\n// CreateSumologic implements Interface.\nfunc (m API) CreateSumologic(ctx context.Context, i *fastly.CreateSumologicInput) (*fastly.Sumologic, error) {\n\treturn m.CreateSumologicFn(ctx, i)\n}\n\n// ListSumologics implements Interface.\nfunc (m API) ListSumologics(ctx context.Context, i *fastly.ListSumologicsInput) ([]*fastly.Sumologic, error) {\n\treturn m.ListSumologicsFn(ctx, i)\n}\n\n// GetSumologic implements Interface.\nfunc (m API) GetSumologic(ctx context.Context, i *fastly.GetSumologicInput) (*fastly.Sumologic, error) {\n\treturn m.GetSumologicFn(ctx, i)\n}\n\n// UpdateSumologic implements Interface.\nfunc (m API) UpdateSumologic(ctx context.Context, i *fastly.UpdateSumologicInput) (*fastly.Sumologic, error) {\n\treturn m.UpdateSumologicFn(ctx, i)\n}\n\n// DeleteSumologic implements Interface.\nfunc (m API) DeleteSumologic(ctx context.Context, i *fastly.DeleteSumologicInput) error {\n\treturn m.DeleteSumologicFn(ctx, i)\n}\n\n// CreateGCS implements Interface.\nfunc (m API) CreateGCS(ctx context.Context, i *fastly.CreateGCSInput) (*fastly.GCS, error) {\n\treturn m.CreateGCSFn(ctx, i)\n}\n\n// ListGCSs implements Interface.\nfunc (m API) ListGCSs(ctx context.Context, i *fastly.ListGCSsInput) ([]*fastly.GCS, error) {\n\treturn m.ListGCSsFn(ctx, i)\n}\n\n// GetGCS implements Interface.\nfunc (m API) GetGCS(ctx context.Context, i *fastly.GetGCSInput) (*fastly.GCS, error) {\n\treturn m.GetGCSFn(ctx, i)\n}\n\n// UpdateGCS implements Interface.\nfunc (m API) UpdateGCS(ctx context.Context, i *fastly.UpdateGCSInput) (*fastly.GCS, error) {\n\treturn m.UpdateGCSFn(ctx, i)\n}\n\n// DeleteGCS implements Interface.\nfunc (m API) DeleteGCS(ctx context.Context, i *fastly.DeleteGCSInput) error {\n\treturn m.DeleteGCSFn(ctx, i)\n}\n\n// CreateGrafanaCloudLogs implements Interface.\nfunc (m API) CreateGrafanaCloudLogs(ctx context.Context, i *fastly.CreateGrafanaCloudLogsInput) (*fastly.GrafanaCloudLogs, error) {\n\treturn m.CreateGrafanaCloudLogsFn(ctx, i)\n}\n\n// ListGrafanaCloudLogs implements Interface.\nfunc (m API) ListGrafanaCloudLogs(ctx context.Context, i *fastly.ListGrafanaCloudLogsInput) ([]*fastly.GrafanaCloudLogs, error) {\n\treturn m.ListGrafanaCloudLogsFn(ctx, i)\n}\n\n// GetGrafanaCloudLogs implements Interface.\nfunc (m API) GetGrafanaCloudLogs(ctx context.Context, i *fastly.GetGrafanaCloudLogsInput) (*fastly.GrafanaCloudLogs, error) {\n\treturn m.GetGrafanaCloudLogsFn(ctx, i)\n}\n\n// UpdateGrafanaCloudLogs implements Interface.\nfunc (m API) UpdateGrafanaCloudLogs(ctx context.Context, i *fastly.UpdateGrafanaCloudLogsInput) (*fastly.GrafanaCloudLogs, error) {\n\treturn m.UpdateGrafanaCloudLogsFn(ctx, i)\n}\n\n// DeleteGrafanaCloudLogs implements Interface.\nfunc (m API) DeleteGrafanaCloudLogs(ctx context.Context, i *fastly.DeleteGrafanaCloudLogsInput) error {\n\treturn m.DeleteGrafanaCloudLogsFn(ctx, i)\n}\n\n// CreateFTP implements Interface.\nfunc (m API) CreateFTP(ctx context.Context, i *fastly.CreateFTPInput) (*fastly.FTP, error) {\n\treturn m.CreateFTPFn(ctx, i)\n}\n\n// ListFTPs implements Interface.\nfunc (m API) ListFTPs(ctx context.Context, i *fastly.ListFTPsInput) ([]*fastly.FTP, error) {\n\treturn m.ListFTPsFn(ctx, i)\n}\n\n// GetFTP implements Interface.\nfunc (m API) GetFTP(ctx context.Context, i *fastly.GetFTPInput) (*fastly.FTP, error) {\n\treturn m.GetFTPFn(ctx, i)\n}\n\n// UpdateFTP implements Interface.\nfunc (m API) UpdateFTP(ctx context.Context, i *fastly.UpdateFTPInput) (*fastly.FTP, error) {\n\treturn m.UpdateFTPFn(ctx, i)\n}\n\n// DeleteFTP implements Interface.\nfunc (m API) DeleteFTP(ctx context.Context, i *fastly.DeleteFTPInput) error {\n\treturn m.DeleteFTPFn(ctx, i)\n}\n\n// CreateSplunk implements Interface.\nfunc (m API) CreateSplunk(ctx context.Context, i *fastly.CreateSplunkInput) (*fastly.Splunk, error) {\n\treturn m.CreateSplunkFn(ctx, i)\n}\n\n// ListSplunks implements Interface.\nfunc (m API) ListSplunks(ctx context.Context, i *fastly.ListSplunksInput) ([]*fastly.Splunk, error) {\n\treturn m.ListSplunksFn(ctx, i)\n}\n\n// GetSplunk implements Interface.\nfunc (m API) GetSplunk(ctx context.Context, i *fastly.GetSplunkInput) (*fastly.Splunk, error) {\n\treturn m.GetSplunkFn(ctx, i)\n}\n\n// UpdateSplunk implements Interface.\nfunc (m API) UpdateSplunk(ctx context.Context, i *fastly.UpdateSplunkInput) (*fastly.Splunk, error) {\n\treturn m.UpdateSplunkFn(ctx, i)\n}\n\n// DeleteSplunk implements Interface.\nfunc (m API) DeleteSplunk(ctx context.Context, i *fastly.DeleteSplunkInput) error {\n\treturn m.DeleteSplunkFn(ctx, i)\n}\n\n// CreateScalyr implements Interface.\nfunc (m API) CreateScalyr(ctx context.Context, i *fastly.CreateScalyrInput) (*fastly.Scalyr, error) {\n\treturn m.CreateScalyrFn(ctx, i)\n}\n\n// ListScalyrs implements Interface.\nfunc (m API) ListScalyrs(ctx context.Context, i *fastly.ListScalyrsInput) ([]*fastly.Scalyr, error) {\n\treturn m.ListScalyrsFn(ctx, i)\n}\n\n// GetScalyr implements Interface.\nfunc (m API) GetScalyr(ctx context.Context, i *fastly.GetScalyrInput) (*fastly.Scalyr, error) {\n\treturn m.GetScalyrFn(ctx, i)\n}\n\n// UpdateScalyr implements Interface.\nfunc (m API) UpdateScalyr(ctx context.Context, i *fastly.UpdateScalyrInput) (*fastly.Scalyr, error) {\n\treturn m.UpdateScalyrFn(ctx, i)\n}\n\n// DeleteScalyr implements Interface.\nfunc (m API) DeleteScalyr(ctx context.Context, i *fastly.DeleteScalyrInput) error {\n\treturn m.DeleteScalyrFn(ctx, i)\n}\n\n// CreateLoggly implements Interface.\nfunc (m API) CreateLoggly(ctx context.Context, i *fastly.CreateLogglyInput) (*fastly.Loggly, error) {\n\treturn m.CreateLogglyFn(ctx, i)\n}\n\n// ListLoggly implements Interface.\nfunc (m API) ListLoggly(ctx context.Context, i *fastly.ListLogglyInput) ([]*fastly.Loggly, error) {\n\treturn m.ListLogglyFn(ctx, i)\n}\n\n// GetLoggly implements Interface.\nfunc (m API) GetLoggly(ctx context.Context, i *fastly.GetLogglyInput) (*fastly.Loggly, error) {\n\treturn m.GetLogglyFn(ctx, i)\n}\n\n// UpdateLoggly implements Interface.\nfunc (m API) UpdateLoggly(ctx context.Context, i *fastly.UpdateLogglyInput) (*fastly.Loggly, error) {\n\treturn m.UpdateLogglyFn(ctx, i)\n}\n\n// DeleteLoggly implements Interface.\nfunc (m API) DeleteLoggly(ctx context.Context, i *fastly.DeleteLogglyInput) error {\n\treturn m.DeleteLogglyFn(ctx, i)\n}\n\n// CreateHoneycomb implements Interface.\nfunc (m API) CreateHoneycomb(ctx context.Context, i *fastly.CreateHoneycombInput) (*fastly.Honeycomb, error) {\n\treturn m.CreateHoneycombFn(ctx, i)\n}\n\n// ListHoneycombs implements Interface.\nfunc (m API) ListHoneycombs(ctx context.Context, i *fastly.ListHoneycombsInput) ([]*fastly.Honeycomb, error) {\n\treturn m.ListHoneycombsFn(ctx, i)\n}\n\n// GetHoneycomb implements Interface.\nfunc (m API) GetHoneycomb(ctx context.Context, i *fastly.GetHoneycombInput) (*fastly.Honeycomb, error) {\n\treturn m.GetHoneycombFn(ctx, i)\n}\n\n// UpdateHoneycomb implements Interface.\nfunc (m API) UpdateHoneycomb(ctx context.Context, i *fastly.UpdateHoneycombInput) (*fastly.Honeycomb, error) {\n\treturn m.UpdateHoneycombFn(ctx, i)\n}\n\n// DeleteHoneycomb implements Interface.\nfunc (m API) DeleteHoneycomb(ctx context.Context, i *fastly.DeleteHoneycombInput) error {\n\treturn m.DeleteHoneycombFn(ctx, i)\n}\n\n// CreateHeroku implements Interface.\nfunc (m API) CreateHeroku(ctx context.Context, i *fastly.CreateHerokuInput) (*fastly.Heroku, error) {\n\treturn m.CreateHerokuFn(ctx, i)\n}\n\n// ListHerokus implements Interface.\nfunc (m API) ListHerokus(ctx context.Context, i *fastly.ListHerokusInput) ([]*fastly.Heroku, error) {\n\treturn m.ListHerokusFn(ctx, i)\n}\n\n// GetHeroku implements Interface.\nfunc (m API) GetHeroku(ctx context.Context, i *fastly.GetHerokuInput) (*fastly.Heroku, error) {\n\treturn m.GetHerokuFn(ctx, i)\n}\n\n// UpdateHeroku implements Interface.\nfunc (m API) UpdateHeroku(ctx context.Context, i *fastly.UpdateHerokuInput) (*fastly.Heroku, error) {\n\treturn m.UpdateHerokuFn(ctx, i)\n}\n\n// DeleteHeroku implements Interface.\nfunc (m API) DeleteHeroku(ctx context.Context, i *fastly.DeleteHerokuInput) error {\n\treturn m.DeleteHerokuFn(ctx, i)\n}\n\n// CreateSFTP implements Interface.\nfunc (m API) CreateSFTP(ctx context.Context, i *fastly.CreateSFTPInput) (*fastly.SFTP, error) {\n\treturn m.CreateSFTPFn(ctx, i)\n}\n\n// ListSFTPs implements Interface.\nfunc (m API) ListSFTPs(ctx context.Context, i *fastly.ListSFTPsInput) ([]*fastly.SFTP, error) {\n\treturn m.ListSFTPsFn(ctx, i)\n}\n\n// GetSFTP implements Interface.\nfunc (m API) GetSFTP(ctx context.Context, i *fastly.GetSFTPInput) (*fastly.SFTP, error) {\n\treturn m.GetSFTPFn(ctx, i)\n}\n\n// UpdateSFTP implements Interface.\nfunc (m API) UpdateSFTP(ctx context.Context, i *fastly.UpdateSFTPInput) (*fastly.SFTP, error) {\n\treturn m.UpdateSFTPFn(ctx, i)\n}\n\n// DeleteSFTP implements Interface.\nfunc (m API) DeleteSFTP(ctx context.Context, i *fastly.DeleteSFTPInput) error {\n\treturn m.DeleteSFTPFn(ctx, i)\n}\n\n// CreateLogshuttle implements Interface.\nfunc (m API) CreateLogshuttle(ctx context.Context, i *fastly.CreateLogshuttleInput) (*fastly.Logshuttle, error) {\n\treturn m.CreateLogshuttleFn(ctx, i)\n}\n\n// ListLogshuttles implements Interface.\nfunc (m API) ListLogshuttles(ctx context.Context, i *fastly.ListLogshuttlesInput) ([]*fastly.Logshuttle, error) {\n\treturn m.ListLogshuttlesFn(ctx, i)\n}\n\n// GetLogshuttle implements Interface.\nfunc (m API) GetLogshuttle(ctx context.Context, i *fastly.GetLogshuttleInput) (*fastly.Logshuttle, error) {\n\treturn m.GetLogshuttleFn(ctx, i)\n}\n\n// UpdateLogshuttle implements Interface.\nfunc (m API) UpdateLogshuttle(ctx context.Context, i *fastly.UpdateLogshuttleInput) (*fastly.Logshuttle, error) {\n\treturn m.UpdateLogshuttleFn(ctx, i)\n}\n\n// DeleteLogshuttle implements Interface.\nfunc (m API) DeleteLogshuttle(ctx context.Context, i *fastly.DeleteLogshuttleInput) error {\n\treturn m.DeleteLogshuttleFn(ctx, i)\n}\n\n// CreateCloudfiles implements Interface.\nfunc (m API) CreateCloudfiles(ctx context.Context, i *fastly.CreateCloudfilesInput) (*fastly.Cloudfiles, error) {\n\treturn m.CreateCloudfilesFn(ctx, i)\n}\n\n// ListCloudfiles implements Interface.\nfunc (m API) ListCloudfiles(ctx context.Context, i *fastly.ListCloudfilesInput) ([]*fastly.Cloudfiles, error) {\n\treturn m.ListCloudfilesFn(ctx, i)\n}\n\n// GetCloudfiles implements Interface.\nfunc (m API) GetCloudfiles(ctx context.Context, i *fastly.GetCloudfilesInput) (*fastly.Cloudfiles, error) {\n\treturn m.GetCloudfilesFn(ctx, i)\n}\n\n// UpdateCloudfiles implements Interface.\nfunc (m API) UpdateCloudfiles(ctx context.Context, i *fastly.UpdateCloudfilesInput) (*fastly.Cloudfiles, error) {\n\treturn m.UpdateCloudfilesFn(ctx, i)\n}\n\n// DeleteCloudfiles implements Interface.\nfunc (m API) DeleteCloudfiles(ctx context.Context, i *fastly.DeleteCloudfilesInput) error {\n\treturn m.DeleteCloudfilesFn(ctx, i)\n}\n\n// CreateDigitalOcean implements Interface.\nfunc (m API) CreateDigitalOcean(ctx context.Context, i *fastly.CreateDigitalOceanInput) (*fastly.DigitalOcean, error) {\n\treturn m.CreateDigitalOceanFn(ctx, i)\n}\n\n// ListDigitalOceans implements Interface.\nfunc (m API) ListDigitalOceans(ctx context.Context, i *fastly.ListDigitalOceansInput) ([]*fastly.DigitalOcean, error) {\n\treturn m.ListDigitalOceansFn(ctx, i)\n}\n\n// GetDigitalOcean implements Interface.\nfunc (m API) GetDigitalOcean(ctx context.Context, i *fastly.GetDigitalOceanInput) (*fastly.DigitalOcean, error) {\n\treturn m.GetDigitalOceanFn(ctx, i)\n}\n\n// UpdateDigitalOcean implements Interface.\nfunc (m API) UpdateDigitalOcean(ctx context.Context, i *fastly.UpdateDigitalOceanInput) (*fastly.DigitalOcean, error) {\n\treturn m.UpdateDigitalOceanFn(ctx, i)\n}\n\n// DeleteDigitalOcean implements Interface.\nfunc (m API) DeleteDigitalOcean(ctx context.Context, i *fastly.DeleteDigitalOceanInput) error {\n\treturn m.DeleteDigitalOceanFn(ctx, i)\n}\n\n// CreateElasticsearch implements Interface.\nfunc (m API) CreateElasticsearch(ctx context.Context, i *fastly.CreateElasticsearchInput) (*fastly.Elasticsearch, error) {\n\treturn m.CreateElasticsearchFn(ctx, i)\n}\n\n// ListElasticsearch implements Interface.\nfunc (m API) ListElasticsearch(ctx context.Context, i *fastly.ListElasticsearchInput) ([]*fastly.Elasticsearch, error) {\n\treturn m.ListElasticsearchFn(ctx, i)\n}\n\n// GetElasticsearch implements Interface.\nfunc (m API) GetElasticsearch(ctx context.Context, i *fastly.GetElasticsearchInput) (*fastly.Elasticsearch, error) {\n\treturn m.GetElasticsearchFn(ctx, i)\n}\n\n// UpdateElasticsearch implements Interface.\nfunc (m API) UpdateElasticsearch(ctx context.Context, i *fastly.UpdateElasticsearchInput) (*fastly.Elasticsearch, error) {\n\treturn m.UpdateElasticsearchFn(ctx, i)\n}\n\n// DeleteElasticsearch implements Interface.\nfunc (m API) DeleteElasticsearch(ctx context.Context, i *fastly.DeleteElasticsearchInput) error {\n\treturn m.DeleteElasticsearchFn(ctx, i)\n}\n\n// CreateBlobStorage implements Interface.\nfunc (m API) CreateBlobStorage(ctx context.Context, i *fastly.CreateBlobStorageInput) (*fastly.BlobStorage, error) {\n\treturn m.CreateBlobStorageFn(ctx, i)\n}\n\n// ListBlobStorages implements Interface.\nfunc (m API) ListBlobStorages(ctx context.Context, i *fastly.ListBlobStoragesInput) ([]*fastly.BlobStorage, error) {\n\treturn m.ListBlobStoragesFn(ctx, i)\n}\n\n// GetBlobStorage implements Interface.\nfunc (m API) GetBlobStorage(ctx context.Context, i *fastly.GetBlobStorageInput) (*fastly.BlobStorage, error) {\n\treturn m.GetBlobStorageFn(ctx, i)\n}\n\n// UpdateBlobStorage implements Interface.\nfunc (m API) UpdateBlobStorage(ctx context.Context, i *fastly.UpdateBlobStorageInput) (*fastly.BlobStorage, error) {\n\treturn m.UpdateBlobStorageFn(ctx, i)\n}\n\n// DeleteBlobStorage implements Interface.\nfunc (m API) DeleteBlobStorage(ctx context.Context, i *fastly.DeleteBlobStorageInput) error {\n\treturn m.DeleteBlobStorageFn(ctx, i)\n}\n\n// CreateDatadog implements Interface.\nfunc (m API) CreateDatadog(ctx context.Context, i *fastly.CreateDatadogInput) (*fastly.Datadog, error) {\n\treturn m.CreateDatadogFn(ctx, i)\n}\n\n// ListDatadog implements Interface.\nfunc (m API) ListDatadog(ctx context.Context, i *fastly.ListDatadogInput) ([]*fastly.Datadog, error) {\n\treturn m.ListDatadogFn(ctx, i)\n}\n\n// GetDatadog implements Interface.\nfunc (m API) GetDatadog(ctx context.Context, i *fastly.GetDatadogInput) (*fastly.Datadog, error) {\n\treturn m.GetDatadogFn(ctx, i)\n}\n\n// UpdateDatadog implements Interface.\nfunc (m API) UpdateDatadog(ctx context.Context, i *fastly.UpdateDatadogInput) (*fastly.Datadog, error) {\n\treturn m.UpdateDatadogFn(ctx, i)\n}\n\n// DeleteDatadog implements Interface.\nfunc (m API) DeleteDatadog(ctx context.Context, i *fastly.DeleteDatadogInput) error {\n\treturn m.DeleteDatadogFn(ctx, i)\n}\n\n// CreateHTTPS implements Interface.\nfunc (m API) CreateHTTPS(ctx context.Context, i *fastly.CreateHTTPSInput) (*fastly.HTTPS, error) {\n\treturn m.CreateHTTPSFn(ctx, i)\n}\n\n// ListHTTPS implements Interface.\nfunc (m API) ListHTTPS(ctx context.Context, i *fastly.ListHTTPSInput) ([]*fastly.HTTPS, error) {\n\treturn m.ListHTTPSFn(ctx, i)\n}\n\n// GetHTTPS implements Interface.\nfunc (m API) GetHTTPS(ctx context.Context, i *fastly.GetHTTPSInput) (*fastly.HTTPS, error) {\n\treturn m.GetHTTPSFn(ctx, i)\n}\n\n// UpdateHTTPS implements Interface.\nfunc (m API) UpdateHTTPS(ctx context.Context, i *fastly.UpdateHTTPSInput) (*fastly.HTTPS, error) {\n\treturn m.UpdateHTTPSFn(ctx, i)\n}\n\n// DeleteHTTPS implements Interface.\nfunc (m API) DeleteHTTPS(ctx context.Context, i *fastly.DeleteHTTPSInput) error {\n\treturn m.DeleteHTTPSFn(ctx, i)\n}\n\n// CreateKafka implements Interface.\nfunc (m API) CreateKafka(ctx context.Context, i *fastly.CreateKafkaInput) (*fastly.Kafka, error) {\n\treturn m.CreateKafkaFn(ctx, i)\n}\n\n// ListKafkas implements Interface.\nfunc (m API) ListKafkas(ctx context.Context, i *fastly.ListKafkasInput) ([]*fastly.Kafka, error) {\n\treturn m.ListKafkasFn(ctx, i)\n}\n\n// GetKafka implements Interface.\nfunc (m API) GetKafka(ctx context.Context, i *fastly.GetKafkaInput) (*fastly.Kafka, error) {\n\treturn m.GetKafkaFn(ctx, i)\n}\n\n// UpdateKafka implements Interface.\nfunc (m API) UpdateKafka(ctx context.Context, i *fastly.UpdateKafkaInput) (*fastly.Kafka, error) {\n\treturn m.UpdateKafkaFn(ctx, i)\n}\n\n// DeleteKafka implements Interface.\nfunc (m API) DeleteKafka(ctx context.Context, i *fastly.DeleteKafkaInput) error {\n\treturn m.DeleteKafkaFn(ctx, i)\n}\n\n// CreatePubsub implements Interface.\nfunc (m API) CreatePubsub(ctx context.Context, i *fastly.CreatePubsubInput) (*fastly.Pubsub, error) {\n\treturn m.CreatePubsubFn(ctx, i)\n}\n\n// ListPubsubs implements Interface.\nfunc (m API) ListPubsubs(ctx context.Context, i *fastly.ListPubsubsInput) ([]*fastly.Pubsub, error) {\n\treturn m.ListPubsubsFn(ctx, i)\n}\n\n// GetPubsub implements Interface.\nfunc (m API) GetPubsub(ctx context.Context, i *fastly.GetPubsubInput) (*fastly.Pubsub, error) {\n\treturn m.GetPubsubFn(ctx, i)\n}\n\n// UpdatePubsub implements Interface.\nfunc (m API) UpdatePubsub(ctx context.Context, i *fastly.UpdatePubsubInput) (*fastly.Pubsub, error) {\n\treturn m.UpdatePubsubFn(ctx, i)\n}\n\n// DeletePubsub implements Interface.\nfunc (m API) DeletePubsub(ctx context.Context, i *fastly.DeletePubsubInput) error {\n\treturn m.DeletePubsubFn(ctx, i)\n}\n\n// CreateOpenstack implements Interface.\nfunc (m API) CreateOpenstack(ctx context.Context, i *fastly.CreateOpenstackInput) (*fastly.Openstack, error) {\n\treturn m.CreateOpenstackFn(ctx, i)\n}\n\n// ListOpenstack implements Interface.\nfunc (m API) ListOpenstack(ctx context.Context, i *fastly.ListOpenstackInput) ([]*fastly.Openstack, error) {\n\treturn m.ListOpenstacksFn(ctx, i)\n}\n\n// GetOpenstack implements Interface.\nfunc (m API) GetOpenstack(ctx context.Context, i *fastly.GetOpenstackInput) (*fastly.Openstack, error) {\n\treturn m.GetOpenstackFn(ctx, i)\n}\n\n// UpdateOpenstack implements Interface.\nfunc (m API) UpdateOpenstack(ctx context.Context, i *fastly.UpdateOpenstackInput) (*fastly.Openstack, error) {\n\treturn m.UpdateOpenstackFn(ctx, i)\n}\n\n// DeleteOpenstack implements Interface.\nfunc (m API) DeleteOpenstack(ctx context.Context, i *fastly.DeleteOpenstackInput) error {\n\treturn m.DeleteOpenstackFn(ctx, i)\n}\n\n// GetRegions implements Interface.\nfunc (m API) GetRegions(ctx context.Context) (*fastly.RegionsResponse, error) {\n\treturn m.GetRegionsFn(ctx)\n}\n\n// GetStatsJSON implements Interface.\nfunc (m API) GetStatsJSON(ctx context.Context, i *fastly.GetStatsInput, dst any) error {\n\treturn m.GetStatsJSONFn(ctx, i, dst)\n}\n\n// GetAggregateJSON implements Interface.\nfunc (m API) GetAggregateJSON(ctx context.Context, i *fastly.GetAggregateInput, dst any) error {\n\treturn m.GetAggregateJSONFn(ctx, i, dst)\n}\n\n// GetUsage implements Interface.\nfunc (m API) GetUsage(ctx context.Context, i *fastly.GetUsageInput) (*fastly.UsageResponse, error) {\n\treturn m.GetUsageFn(ctx, i)\n}\n\n// GetUsageByService implements Interface.\nfunc (m API) GetUsageByService(ctx context.Context, i *fastly.GetUsageInput) (*fastly.UsageByServiceResponse, error) {\n\treturn m.GetUsageByServiceFn(ctx, i)\n}\n\n// GetDomainMetricsForService implements Interface.\nfunc (m API) GetDomainMetricsForService(ctx context.Context, i *fastly.GetDomainMetricsInput) (*fastly.DomainInspector, error) {\n\treturn m.GetDomainMetricsForServiceFn(ctx, i)\n}\n\n// GetDomainMetricsForServiceJSON implements Interface.\nfunc (m API) GetDomainMetricsForServiceJSON(ctx context.Context, i *fastly.GetDomainMetricsInput, dst any) error {\n\treturn m.GetDomainMetricsForServiceJSONFn(ctx, i, dst)\n}\n\n// GetOriginMetricsForService implements Interface.\nfunc (m API) GetOriginMetricsForService(ctx context.Context, i *fastly.GetOriginMetricsInput) (*fastly.OriginInspector, error) {\n\treturn m.GetOriginMetricsForServiceFn(ctx, i)\n}\n\n// GetOriginMetricsForServiceJSON implements Interface.\nfunc (m API) GetOriginMetricsForServiceJSON(ctx context.Context, i *fastly.GetOriginMetricsInput, dst any) error {\n\treturn m.GetOriginMetricsForServiceJSONFn(ctx, i, dst)\n}\n\n// CreateManagedLogging implements Interface.\nfunc (m API) CreateManagedLogging(ctx context.Context, i *fastly.CreateManagedLoggingInput) (*fastly.ManagedLogging, error) {\n\treturn m.CreateManagedLoggingFn(ctx, i)\n}\n\n// GetLoggingEndpointErrors implements Interface.\nfunc (m API) GetLoggingEndpointErrors(ctx context.Context, i *fastly.LoggingEndpointErrorsInput) (*fastly.LoggingEndpointErrorsResponse, error) {\n\treturn m.GetLoggingEndpointErrorsFn(ctx, i)\n}\n\n// GetGeneratedVCL implements Interface.\nfunc (m API) GetGeneratedVCL(ctx context.Context, i *fastly.GetGeneratedVCLInput) (*fastly.VCL, error) {\n\treturn m.GetGeneratedVCLFn(ctx, i)\n}\n\n// CreateVCL implements Interface.\nfunc (m API) CreateVCL(ctx context.Context, i *fastly.CreateVCLInput) (*fastly.VCL, error) {\n\treturn m.CreateVCLFn(ctx, i)\n}\n\n// ListVCLs implements Interface.\nfunc (m API) ListVCLs(ctx context.Context, i *fastly.ListVCLsInput) ([]*fastly.VCL, error) {\n\treturn m.ListVCLsFn(ctx, i)\n}\n\n// GetVCL implements Interface.\nfunc (m API) GetVCL(ctx context.Context, i *fastly.GetVCLInput) (*fastly.VCL, error) {\n\treturn m.GetVCLFn(ctx, i)\n}\n\n// UpdateVCL implements Interface.\nfunc (m API) UpdateVCL(ctx context.Context, i *fastly.UpdateVCLInput) (*fastly.VCL, error) {\n\treturn m.UpdateVCLFn(ctx, i)\n}\n\n// DeleteVCL implements Interface.\nfunc (m API) DeleteVCL(ctx context.Context, i *fastly.DeleteVCLInput) error {\n\treturn m.DeleteVCLFn(ctx, i)\n}\n\n// CreateSnippet implements Interface.\nfunc (m API) CreateSnippet(ctx context.Context, i *fastly.CreateSnippetInput) (*fastly.Snippet, error) {\n\treturn m.CreateSnippetFn(ctx, i)\n}\n\n// ListSnippets implements Interface.\nfunc (m API) ListSnippets(ctx context.Context, i *fastly.ListSnippetsInput) ([]*fastly.Snippet, error) {\n\treturn m.ListSnippetsFn(ctx, i)\n}\n\n// GetSnippet implements Interface.\nfunc (m API) GetSnippet(ctx context.Context, i *fastly.GetSnippetInput) (*fastly.Snippet, error) {\n\treturn m.GetSnippetFn(ctx, i)\n}\n\n// GetDynamicSnippet implements Interface.\nfunc (m API) GetDynamicSnippet(ctx context.Context, i *fastly.GetDynamicSnippetInput) (*fastly.DynamicSnippet, error) {\n\treturn m.GetDynamicSnippetFn(ctx, i)\n}\n\n// UpdateSnippet implements Interface.\nfunc (m API) UpdateSnippet(ctx context.Context, i *fastly.UpdateSnippetInput) (*fastly.Snippet, error) {\n\treturn m.UpdateSnippetFn(ctx, i)\n}\n\n// UpdateDynamicSnippet implements Interface.\nfunc (m API) UpdateDynamicSnippet(ctx context.Context, i *fastly.UpdateDynamicSnippetInput) (*fastly.DynamicSnippet, error) {\n\treturn m.UpdateDynamicSnippetFn(ctx, i)\n}\n\n// DeleteSnippet implements Interface.\nfunc (m API) DeleteSnippet(ctx context.Context, i *fastly.DeleteSnippetInput) error {\n\treturn m.DeleteSnippetFn(ctx, i)\n}\n\n// Purge implements Interface.\nfunc (m API) Purge(ctx context.Context, i *fastly.PurgeInput) (*fastly.Purge, error) {\n\treturn m.PurgeFn(ctx, i)\n}\n\n// PurgeKey implements Interface.\nfunc (m API) PurgeKey(ctx context.Context, i *fastly.PurgeKeyInput) (*fastly.Purge, error) {\n\treturn m.PurgeKeyFn(ctx, i)\n}\n\n// PurgeKeys implements Interface.\nfunc (m API) PurgeKeys(ctx context.Context, i *fastly.PurgeKeysInput) (map[string]string, error) {\n\treturn m.PurgeKeysFn(ctx, i)\n}\n\n// PurgeAll implements Interface.\nfunc (m API) PurgeAll(ctx context.Context, i *fastly.PurgeAllInput) (*fastly.Purge, error) {\n\treturn m.PurgeAllFn(ctx, i)\n}\n\n// CreateACL implements Interface.\nfunc (m API) CreateACL(ctx context.Context, i *fastly.CreateACLInput) (*fastly.ACL, error) {\n\treturn m.CreateACLFn(ctx, i)\n}\n\n// DeleteACL implements Interface.\nfunc (m API) DeleteACL(ctx context.Context, i *fastly.DeleteACLInput) error {\n\treturn m.DeleteACLFn(ctx, i)\n}\n\n// GetACL implements Interface.\nfunc (m API) GetACL(ctx context.Context, i *fastly.GetACLInput) (*fastly.ACL, error) {\n\treturn m.GetACLFn(ctx, i)\n}\n\n// ListACLs implements Interface.\nfunc (m API) ListACLs(ctx context.Context, i *fastly.ListACLsInput) ([]*fastly.ACL, error) {\n\treturn m.ListACLsFn(ctx, i)\n}\n\n// UpdateACL implements Interface.\nfunc (m API) UpdateACL(ctx context.Context, i *fastly.UpdateACLInput) (*fastly.ACL, error) {\n\treturn m.UpdateACLFn(ctx, i)\n}\n\n// CreateACLEntry implements Interface.\nfunc (m API) CreateACLEntry(ctx context.Context, i *fastly.CreateACLEntryInput) (*fastly.ACLEntry, error) {\n\treturn m.CreateACLEntryFn(ctx, i)\n}\n\n// DeleteACLEntry implements Interface.\nfunc (m API) DeleteACLEntry(ctx context.Context, i *fastly.DeleteACLEntryInput) error {\n\treturn m.DeleteACLEntryFn(ctx, i)\n}\n\n// GetACLEntry implements Interface.\nfunc (m API) GetACLEntry(ctx context.Context, i *fastly.GetACLEntryInput) (*fastly.ACLEntry, error) {\n\treturn m.GetACLEntryFn(ctx, i)\n}\n\n// GetACLEntries implements Interface.\nfunc (m API) GetACLEntries(ctx context.Context, i *fastly.GetACLEntriesInput) *fastly.ListPaginator[fastly.ACLEntry] {\n\treturn m.GetACLEntriesFn(ctx, i)\n}\n\n// ListACLEntries implements Interface.\nfunc (m API) ListACLEntries(ctx context.Context, i *fastly.ListACLEntriesInput) ([]*fastly.ACLEntry, error) {\n\treturn m.ListACLEntriesFn(ctx, i)\n}\n\n// UpdateACLEntry implements Interface.\nfunc (m API) UpdateACLEntry(ctx context.Context, i *fastly.UpdateACLEntryInput) (*fastly.ACLEntry, error) {\n\treturn m.UpdateACLEntryFn(ctx, i)\n}\n\n// BatchModifyACLEntries implements Interface.\nfunc (m API) BatchModifyACLEntries(ctx context.Context, i *fastly.BatchModifyACLEntriesInput) error {\n\treturn m.BatchModifyACLEntriesFn(ctx, i)\n}\n\n// CreateNewRelic implements Interface.\nfunc (m API) CreateNewRelic(ctx context.Context, i *fastly.CreateNewRelicInput) (*fastly.NewRelic, error) {\n\treturn m.CreateNewRelicFn(ctx, i)\n}\n\n// DeleteNewRelic implements Interface.\nfunc (m API) DeleteNewRelic(ctx context.Context, i *fastly.DeleteNewRelicInput) error {\n\treturn m.DeleteNewRelicFn(ctx, i)\n}\n\n// GetNewRelic implements Interface.\nfunc (m API) GetNewRelic(ctx context.Context, i *fastly.GetNewRelicInput) (*fastly.NewRelic, error) {\n\treturn m.GetNewRelicFn(ctx, i)\n}\n\n// ListNewRelic implements Interface.\nfunc (m API) ListNewRelic(ctx context.Context, i *fastly.ListNewRelicInput) ([]*fastly.NewRelic, error) {\n\treturn m.ListNewRelicFn(ctx, i)\n}\n\n// UpdateNewRelic implements Interface.\nfunc (m API) UpdateNewRelic(ctx context.Context, i *fastly.UpdateNewRelicInput) (*fastly.NewRelic, error) {\n\treturn m.UpdateNewRelicFn(ctx, i)\n}\n\n// CreateNewRelicOTLP implements Interface.\nfunc (m API) CreateNewRelicOTLP(ctx context.Context, i *fastly.CreateNewRelicOTLPInput) (*fastly.NewRelicOTLP, error) {\n\treturn m.CreateNewRelicOTLPFn(ctx, i)\n}\n\n// DeleteNewRelicOTLP implements Interface.\nfunc (m API) DeleteNewRelicOTLP(ctx context.Context, i *fastly.DeleteNewRelicOTLPInput) error {\n\treturn m.DeleteNewRelicOTLPFn(ctx, i)\n}\n\n// GetNewRelicOTLP implements Interface.\nfunc (m API) GetNewRelicOTLP(ctx context.Context, i *fastly.GetNewRelicOTLPInput) (*fastly.NewRelicOTLP, error) {\n\treturn m.GetNewRelicOTLPFn(ctx, i)\n}\n\n// ListNewRelicOTLP implements Interface.\nfunc (m API) ListNewRelicOTLP(ctx context.Context, i *fastly.ListNewRelicOTLPInput) ([]*fastly.NewRelicOTLP, error) {\n\treturn m.ListNewRelicOTLPFn(ctx, i)\n}\n\n// UpdateNewRelicOTLP implements Interface.\nfunc (m API) UpdateNewRelicOTLP(ctx context.Context, i *fastly.UpdateNewRelicOTLPInput) (*fastly.NewRelicOTLP, error) {\n\treturn m.UpdateNewRelicOTLPFn(ctx, i)\n}\n\n// CreateUser implements Interface.\nfunc (m API) CreateUser(ctx context.Context, i *fastly.CreateUserInput) (*fastly.User, error) {\n\treturn m.CreateUserFn(ctx, i)\n}\n\n// DeleteUser implements Interface.\nfunc (m API) DeleteUser(ctx context.Context, i *fastly.DeleteUserInput) error {\n\treturn m.DeleteUserFn(ctx, i)\n}\n\n// GetCurrentUser implements Interface.\nfunc (m API) GetCurrentUser(ctx context.Context) (*fastly.User, error) {\n\treturn m.GetCurrentUserFn(ctx)\n}\n\n// GetUser implements Interface.\nfunc (m API) GetUser(ctx context.Context, i *fastly.GetUserInput) (*fastly.User, error) {\n\treturn m.GetUserFn(ctx, i)\n}\n\n// ListCustomerUsers implements Interface.\nfunc (m API) ListCustomerUsers(ctx context.Context, i *fastly.ListCustomerUsersInput) ([]*fastly.User, error) {\n\treturn m.ListCustomerUsersFn(ctx, i)\n}\n\n// UpdateUser implements Interface.\nfunc (m API) UpdateUser(ctx context.Context, i *fastly.UpdateUserInput) (*fastly.User, error) {\n\treturn m.UpdateUserFn(ctx, i)\n}\n\n// ResetUserPassword implements Interface.\nfunc (m API) ResetUserPassword(ctx context.Context, i *fastly.ResetUserPasswordInput) error {\n\treturn m.ResetUserPasswordFn(ctx, i)\n}\n\n// BatchDeleteTokens implements Interface.\nfunc (m API) BatchDeleteTokens(ctx context.Context, i *fastly.BatchDeleteTokensInput) error {\n\treturn m.BatchDeleteTokensFn(ctx, i)\n}\n\n// CreateToken implements Interface.\nfunc (m API) CreateToken(ctx context.Context, i *fastly.CreateTokenInput) (*fastly.Token, error) {\n\treturn m.CreateTokenFn(ctx, i)\n}\n\n// DeleteToken implements Interface.\nfunc (m API) DeleteToken(ctx context.Context, i *fastly.DeleteTokenInput) error {\n\treturn m.DeleteTokenFn(ctx, i)\n}\n\n// DeleteTokenSelf implements Interface.\nfunc (m API) DeleteTokenSelf(ctx context.Context) error {\n\treturn m.DeleteTokenSelfFn(ctx)\n}\n\n// GetTokenSelf implements Interface.\nfunc (m API) GetTokenSelf(ctx context.Context) (*fastly.Token, error) {\n\treturn m.GetTokenSelfFn(ctx)\n}\n\n// ListCustomerTokens implements Interface.\nfunc (m API) ListCustomerTokens(ctx context.Context, i *fastly.ListCustomerTokensInput) ([]*fastly.Token, error) {\n\treturn m.ListCustomerTokensFn(ctx, i)\n}\n\n// ListTokens implements Interface.\nfunc (m API) ListTokens(ctx context.Context, i *fastly.ListTokensInput) ([]*fastly.Token, error) {\n\treturn m.ListTokensFn(ctx, i)\n}\n\n// NewListKVStoreKeysPaginator implements Interface.\nfunc (m API) NewListKVStoreKeysPaginator(ctx context.Context, i *fastly.ListKVStoreKeysInput) fastly.PaginatorKVStoreEntries {\n\treturn m.NewListKVStoreKeysPaginatorFn(ctx, i)\n}\n\n// GetKVStoreItem implements Interface.\nfunc (m API) GetKVStoreItem(ctx context.Context, i *fastly.GetKVStoreItemInput) (fastly.GetKVStoreItemOutput, error) {\n\treturn m.GetKVStoreItemFn(ctx, i)\n}\n\n// GetCustomTLSConfiguration implements Interface.\nfunc (m API) GetCustomTLSConfiguration(ctx context.Context, i *fastly.GetCustomTLSConfigurationInput) (*fastly.CustomTLSConfiguration, error) {\n\treturn m.GetCustomTLSConfigurationFn(ctx, i)\n}\n\n// ListCustomTLSConfigurations implements Interface.\nfunc (m API) ListCustomTLSConfigurations(ctx context.Context, i *fastly.ListCustomTLSConfigurationsInput) ([]*fastly.CustomTLSConfiguration, error) {\n\treturn m.ListCustomTLSConfigurationsFn(ctx, i)\n}\n\n// UpdateCustomTLSConfiguration implements Interface.\nfunc (m API) UpdateCustomTLSConfiguration(ctx context.Context, i *fastly.UpdateCustomTLSConfigurationInput) (*fastly.CustomTLSConfiguration, error) {\n\treturn m.UpdateCustomTLSConfigurationFn(ctx, i)\n}\n\n// GetTLSActivation implements Interface.\nfunc (m API) GetTLSActivation(ctx context.Context, i *fastly.GetTLSActivationInput) (*fastly.TLSActivation, error) {\n\treturn m.GetTLSActivationFn(ctx, i)\n}\n\n// ListTLSActivations implements Interface.\nfunc (m API) ListTLSActivations(ctx context.Context, i *fastly.ListTLSActivationsInput) ([]*fastly.TLSActivation, error) {\n\treturn m.ListTLSActivationsFn(ctx, i)\n}\n\n// UpdateTLSActivation implements Interface.\nfunc (m API) UpdateTLSActivation(ctx context.Context, i *fastly.UpdateTLSActivationInput) (*fastly.TLSActivation, error) {\n\treturn m.UpdateTLSActivationFn(ctx, i)\n}\n\n// CreateTLSActivation implements Interface.\nfunc (m API) CreateTLSActivation(ctx context.Context, i *fastly.CreateTLSActivationInput) (*fastly.TLSActivation, error) {\n\treturn m.CreateTLSActivationFn(ctx, i)\n}\n\n// DeleteTLSActivation implements Interface.\nfunc (m API) DeleteTLSActivation(ctx context.Context, i *fastly.DeleteTLSActivationInput) error {\n\treturn m.DeleteTLSActivationFn(ctx, i)\n}\n\n// CreateCustomTLSCertificate implements Interface.\nfunc (m API) CreateCustomTLSCertificate(ctx context.Context, i *fastly.CreateCustomTLSCertificateInput) (*fastly.CustomTLSCertificate, error) {\n\treturn m.CreateCustomTLSCertificateFn(ctx, i)\n}\n\n// DeleteCustomTLSCertificate implements Interface.\nfunc (m API) DeleteCustomTLSCertificate(ctx context.Context, i *fastly.DeleteCustomTLSCertificateInput) error {\n\treturn m.DeleteCustomTLSCertificateFn(ctx, i)\n}\n\n// GetCustomTLSCertificate implements Interface.\nfunc (m API) GetCustomTLSCertificate(ctx context.Context, i *fastly.GetCustomTLSCertificateInput) (*fastly.CustomTLSCertificate, error) {\n\treturn m.GetCustomTLSCertificateFn(ctx, i)\n}\n\n// ListCustomTLSCertificates implements Interface.\nfunc (m API) ListCustomTLSCertificates(ctx context.Context, i *fastly.ListCustomTLSCertificatesInput) ([]*fastly.CustomTLSCertificate, error) {\n\treturn m.ListCustomTLSCertificatesFn(ctx, i)\n}\n\n// UpdateCustomTLSCertificate implements Interface.\nfunc (m API) UpdateCustomTLSCertificate(ctx context.Context, i *fastly.UpdateCustomTLSCertificateInput) (*fastly.CustomTLSCertificate, error) {\n\treturn m.UpdateCustomTLSCertificateFn(ctx, i)\n}\n\n// ListTLSDomains implements Interface.\nfunc (m API) ListTLSDomains(ctx context.Context, i *fastly.ListTLSDomainsInput) ([]*fastly.TLSDomain, error) {\n\treturn m.ListTLSDomainsFn(ctx, i)\n}\n\n// CreatePrivateKey implements Interface.\nfunc (m API) CreatePrivateKey(ctx context.Context, i *fastly.CreatePrivateKeyInput) (*fastly.PrivateKey, error) {\n\treturn m.CreatePrivateKeyFn(ctx, i)\n}\n\n// DeletePrivateKey implements Interface.\nfunc (m API) DeletePrivateKey(ctx context.Context, i *fastly.DeletePrivateKeyInput) error {\n\treturn m.DeletePrivateKeyFn(ctx, i)\n}\n\n// GetPrivateKey implements Interface.\nfunc (m API) GetPrivateKey(ctx context.Context, i *fastly.GetPrivateKeyInput) (*fastly.PrivateKey, error) {\n\treturn m.GetPrivateKeyFn(ctx, i)\n}\n\n// ListPrivateKeys implements Interface.\nfunc (m API) ListPrivateKeys(ctx context.Context, i *fastly.ListPrivateKeysInput) ([]*fastly.PrivateKey, error) {\n\treturn m.ListPrivateKeysFn(ctx, i)\n}\n\n// CreateBulkCertificate implements Interface.\nfunc (m API) CreateBulkCertificate(ctx context.Context, i *fastly.CreateBulkCertificateInput) (*fastly.BulkCertificate, error) {\n\treturn m.CreateBulkCertificateFn(ctx, i)\n}\n\n// DeleteBulkCertificate implements Interface.\nfunc (m API) DeleteBulkCertificate(ctx context.Context, i *fastly.DeleteBulkCertificateInput) error {\n\treturn m.DeleteBulkCertificateFn(ctx, i)\n}\n\n// GetBulkCertificate implements Interface.\nfunc (m API) GetBulkCertificate(ctx context.Context, i *fastly.GetBulkCertificateInput) (*fastly.BulkCertificate, error) {\n\treturn m.GetBulkCertificateFn(ctx, i)\n}\n\n// ListBulkCertificates implements Interface.\nfunc (m API) ListBulkCertificates(ctx context.Context, i *fastly.ListBulkCertificatesInput) ([]*fastly.BulkCertificate, error) {\n\treturn m.ListBulkCertificatesFn(ctx, i)\n}\n\n// UpdateBulkCertificate implements Interface.\nfunc (m API) UpdateBulkCertificate(ctx context.Context, i *fastly.UpdateBulkCertificateInput) (*fastly.BulkCertificate, error) {\n\treturn m.UpdateBulkCertificateFn(ctx, i)\n}\n\n// CreateTLSSubscription implements Interface.\nfunc (m API) CreateTLSSubscription(ctx context.Context, i *fastly.CreateTLSSubscriptionInput) (*fastly.TLSSubscription, error) {\n\treturn m.CreateTLSSubscriptionFn(ctx, i)\n}\n\n// DeleteTLSSubscription implements Interface.\nfunc (m API) DeleteTLSSubscription(ctx context.Context, i *fastly.DeleteTLSSubscriptionInput) error {\n\treturn m.DeleteTLSSubscriptionFn(ctx, i)\n}\n\n// GetTLSSubscription implements Interface.\nfunc (m API) GetTLSSubscription(ctx context.Context, i *fastly.GetTLSSubscriptionInput) (*fastly.TLSSubscription, error) {\n\treturn m.GetTLSSubscriptionFn(ctx, i)\n}\n\n// ListTLSSubscriptions implements Interface.\nfunc (m API) ListTLSSubscriptions(ctx context.Context, i *fastly.ListTLSSubscriptionsInput) ([]*fastly.TLSSubscription, error) {\n\treturn m.ListTLSSubscriptionsFn(ctx, i)\n}\n\n// UpdateTLSSubscription implements Interface.\nfunc (m API) UpdateTLSSubscription(ctx context.Context, i *fastly.UpdateTLSSubscriptionInput) (*fastly.TLSSubscription, error) {\n\treturn m.UpdateTLSSubscriptionFn(ctx, i)\n}\n\n// ListServiceAuthorizations implements Interface.\nfunc (m API) ListServiceAuthorizations(ctx context.Context, i *fastly.ListServiceAuthorizationsInput) (*fastly.ServiceAuthorizations, error) {\n\treturn m.ListServiceAuthorizationsFn(ctx, i)\n}\n\n// GetServiceAuthorization implements Interface.\nfunc (m API) GetServiceAuthorization(ctx context.Context, i *fastly.GetServiceAuthorizationInput) (*fastly.ServiceAuthorization, error) {\n\treturn m.GetServiceAuthorizationFn(ctx, i)\n}\n\n// CreateServiceAuthorization implements Interface.\nfunc (m API) CreateServiceAuthorization(ctx context.Context, i *fastly.CreateServiceAuthorizationInput) (*fastly.ServiceAuthorization, error) {\n\treturn m.CreateServiceAuthorizationFn(ctx, i)\n}\n\n// UpdateServiceAuthorization implements Interface.\nfunc (m API) UpdateServiceAuthorization(ctx context.Context, i *fastly.UpdateServiceAuthorizationInput) (*fastly.ServiceAuthorization, error) {\n\treturn m.UpdateServiceAuthorizationFn(ctx, i)\n}\n\n// DeleteServiceAuthorization implements Interface.\nfunc (m API) DeleteServiceAuthorization(ctx context.Context, i *fastly.DeleteServiceAuthorizationInput) error {\n\treturn m.DeleteServiceAuthorizationFn(ctx, i)\n}\n\n// CreateConfigStore implements Interface.\nfunc (m API) CreateConfigStore(ctx context.Context, i *fastly.CreateConfigStoreInput) (*fastly.ConfigStore, error) {\n\treturn m.CreateConfigStoreFn(ctx, i)\n}\n\n// DeleteConfigStore implements Interface.\nfunc (m API) DeleteConfigStore(ctx context.Context, i *fastly.DeleteConfigStoreInput) error {\n\treturn m.DeleteConfigStoreFn(ctx, i)\n}\n\n// GetConfigStore implements Interface.\nfunc (m API) GetConfigStore(ctx context.Context, i *fastly.GetConfigStoreInput) (*fastly.ConfigStore, error) {\n\treturn m.GetConfigStoreFn(ctx, i)\n}\n\n// GetConfigStoreMetadata implements Interface.\nfunc (m API) GetConfigStoreMetadata(ctx context.Context, i *fastly.GetConfigStoreMetadataInput) (*fastly.ConfigStoreMetadata, error) {\n\treturn m.GetConfigStoreMetadataFn(ctx, i)\n}\n\n// ListConfigStores implements Interface.\nfunc (m API) ListConfigStores(ctx context.Context, i *fastly.ListConfigStoresInput) ([]*fastly.ConfigStore, error) {\n\treturn m.ListConfigStoresFn(ctx, i)\n}\n\n// ListConfigStoreServices implements Interface.\nfunc (m API) ListConfigStoreServices(ctx context.Context, i *fastly.ListConfigStoreServicesInput) ([]*fastly.Service, error) {\n\treturn m.ListConfigStoreServicesFn(ctx, i)\n}\n\n// UpdateConfigStore implements Interface.\nfunc (m API) UpdateConfigStore(ctx context.Context, i *fastly.UpdateConfigStoreInput) (*fastly.ConfigStore, error) {\n\treturn m.UpdateConfigStoreFn(ctx, i)\n}\n\n// CreateConfigStoreItem implements Interface.\nfunc (m API) CreateConfigStoreItem(ctx context.Context, i *fastly.CreateConfigStoreItemInput) (*fastly.ConfigStoreItem, error) {\n\treturn m.CreateConfigStoreItemFn(ctx, i)\n}\n\n// DeleteConfigStoreItem implements Interface.\nfunc (m API) DeleteConfigStoreItem(ctx context.Context, i *fastly.DeleteConfigStoreItemInput) error {\n\treturn m.DeleteConfigStoreItemFn(ctx, i)\n}\n\n// GetConfigStoreItem implements Interface.\nfunc (m API) GetConfigStoreItem(ctx context.Context, i *fastly.GetConfigStoreItemInput) (*fastly.ConfigStoreItem, error) {\n\treturn m.GetConfigStoreItemFn(ctx, i)\n}\n\n// ListConfigStoreItems implements Interface.\nfunc (m API) ListConfigStoreItems(ctx context.Context, i *fastly.ListConfigStoreItemsInput) ([]*fastly.ConfigStoreItem, error) {\n\treturn m.ListConfigStoreItemsFn(ctx, i)\n}\n\n// UpdateConfigStoreItem implements Interface.\nfunc (m API) UpdateConfigStoreItem(ctx context.Context, i *fastly.UpdateConfigStoreItemInput) (*fastly.ConfigStoreItem, error) {\n\treturn m.UpdateConfigStoreItemFn(ctx, i)\n}\n\n// CreateKVStore implements Interface.\nfunc (m API) CreateKVStore(ctx context.Context, i *fastly.CreateKVStoreInput) (*fastly.KVStore, error) {\n\treturn m.CreateKVStoreFn(ctx, i)\n}\n\n// GetKVStore implements Interface.\nfunc (m API) GetKVStore(ctx context.Context, i *fastly.GetKVStoreInput) (*fastly.KVStore, error) {\n\treturn m.GetKVStoreFn(ctx, i)\n}\n\n// ListKVStores implements Interface.\nfunc (m API) ListKVStores(ctx context.Context, i *fastly.ListKVStoresInput) (*fastly.ListKVStoresResponse, error) {\n\treturn m.ListKVStoresFn(ctx, i)\n}\n\n// DeleteKVStore implements Interface.\nfunc (m API) DeleteKVStore(ctx context.Context, i *fastly.DeleteKVStoreInput) error {\n\treturn m.DeleteKVStoreFn(ctx, i)\n}\n\n// ListKVStoreKeys implements Interface.\nfunc (m API) ListKVStoreKeys(ctx context.Context, i *fastly.ListKVStoreKeysInput) (*fastly.ListKVStoreKeysResponse, error) {\n\treturn m.ListKVStoreKeysFn(ctx, i)\n}\n\n// GetKVStoreKey implements Interface.\nfunc (m API) GetKVStoreKey(ctx context.Context, i *fastly.GetKVStoreKeyInput) (string, error) {\n\treturn m.GetKVStoreKeyFn(ctx, i)\n}\n\n// InsertKVStoreKey implements Interface.\nfunc (m API) InsertKVStoreKey(ctx context.Context, i *fastly.InsertKVStoreKeyInput) error {\n\treturn m.InsertKVStoreKeyFn(ctx, i)\n}\n\n// DeleteKVStoreKey implements Interface.\nfunc (m API) DeleteKVStoreKey(ctx context.Context, i *fastly.DeleteKVStoreKeyInput) error {\n\treturn m.DeleteKVStoreKeyFn(ctx, i)\n}\n\n// BatchModifyKVStoreKey implements Interface.\nfunc (m API) BatchModifyKVStoreKey(ctx context.Context, i *fastly.BatchModifyKVStoreKeyInput) error {\n\treturn m.BatchModifyKVStoreKeyFn(ctx, i)\n}\n\n// CreateSecretStore implements Interface.\nfunc (m API) CreateSecretStore(ctx context.Context, i *fastly.CreateSecretStoreInput) (*fastly.SecretStore, error) {\n\treturn m.CreateSecretStoreFn(ctx, i)\n}\n\n// GetSecretStore implements Interface.\nfunc (m API) GetSecretStore(ctx context.Context, i *fastly.GetSecretStoreInput) (*fastly.SecretStore, error) {\n\treturn m.GetSecretStoreFn(ctx, i)\n}\n\n// DeleteSecretStore implements Interface.\nfunc (m API) DeleteSecretStore(ctx context.Context, i *fastly.DeleteSecretStoreInput) error {\n\treturn m.DeleteSecretStoreFn(ctx, i)\n}\n\n// ListSecretStores implements Interface.\nfunc (m API) ListSecretStores(ctx context.Context, i *fastly.ListSecretStoresInput) (*fastly.SecretStores, error) {\n\treturn m.ListSecretStoresFn(ctx, i)\n}\n\n// CreateSecret implements Interface.\nfunc (m API) CreateSecret(ctx context.Context, i *fastly.CreateSecretInput) (*fastly.Secret, error) {\n\treturn m.CreateSecretFn(ctx, i)\n}\n\n// GetSecret implements Interface.\nfunc (m API) GetSecret(ctx context.Context, i *fastly.GetSecretInput) (*fastly.Secret, error) {\n\treturn m.GetSecretFn(ctx, i)\n}\n\n// DeleteSecret implements Interface.\nfunc (m API) DeleteSecret(ctx context.Context, i *fastly.DeleteSecretInput) error {\n\treturn m.DeleteSecretFn(ctx, i)\n}\n\n// ListSecrets implements Interface.\nfunc (m API) ListSecrets(ctx context.Context, i *fastly.ListSecretsInput) (*fastly.Secrets, error) {\n\treturn m.ListSecretsFn(ctx, i)\n}\n\n// CreateClientKey implements Interface.\nfunc (m API) CreateClientKey(ctx context.Context) (*fastly.ClientKey, error) {\n\treturn m.CreateClientKeyFn(ctx)\n}\n\n// GetSigningKey implements Interface.\nfunc (m API) GetSigningKey(ctx context.Context) (ed25519.PublicKey, error) {\n\treturn m.GetSigningKeyFn(ctx)\n}\n\n// CreateResource implements Interface.\nfunc (m API) CreateResource(ctx context.Context, i *fastly.CreateResourceInput) (*fastly.Resource, error) {\n\treturn m.CreateResourceFn(ctx, i)\n}\n\n// DeleteResource implements Interface.\nfunc (m API) DeleteResource(ctx context.Context, i *fastly.DeleteResourceInput) error {\n\treturn m.DeleteResourceFn(ctx, i)\n}\n\n// GetResource implements Interface.\nfunc (m API) GetResource(ctx context.Context, i *fastly.GetResourceInput) (*fastly.Resource, error) {\n\treturn m.GetResourceFn(ctx, i)\n}\n\n// ListResources implements Interface.\nfunc (m API) ListResources(ctx context.Context, i *fastly.ListResourcesInput) ([]*fastly.Resource, error) {\n\treturn m.ListResourcesFn(ctx, i)\n}\n\n// UpdateResource implements Interface.\nfunc (m API) UpdateResource(ctx context.Context, i *fastly.UpdateResourceInput) (*fastly.Resource, error) {\n\treturn m.UpdateResourceFn(ctx, i)\n}\n\n// CreateERL implements Interface.\nfunc (m API) CreateERL(ctx context.Context, i *fastly.CreateERLInput) (*fastly.ERL, error) {\n\treturn m.CreateERLFn(ctx, i)\n}\n\n// DeleteERL implements Interface.\nfunc (m API) DeleteERL(ctx context.Context, i *fastly.DeleteERLInput) error {\n\treturn m.DeleteERLFn(ctx, i)\n}\n\n// GetERL implements Interface.\nfunc (m API) GetERL(ctx context.Context, i *fastly.GetERLInput) (*fastly.ERL, error) {\n\treturn m.GetERLFn(ctx, i)\n}\n\n// ListERLs implements Interface.\nfunc (m API) ListERLs(ctx context.Context, i *fastly.ListERLsInput) ([]*fastly.ERL, error) {\n\treturn m.ListERLsFn(ctx, i)\n}\n\n// UpdateERL implements Interface.\nfunc (m API) UpdateERL(ctx context.Context, i *fastly.UpdateERLInput) (*fastly.ERL, error) {\n\treturn m.UpdateERLFn(ctx, i)\n}\n\n// CreateCondition implements Interface.\nfunc (m API) CreateCondition(ctx context.Context, i *fastly.CreateConditionInput) (*fastly.Condition, error) {\n\treturn m.CreateConditionFn(ctx, i)\n}\n\n// DeleteCondition implements Interface.\nfunc (m API) DeleteCondition(ctx context.Context, i *fastly.DeleteConditionInput) error {\n\treturn m.DeleteConditionFn(ctx, i)\n}\n\n// GetCondition implements Interface.\nfunc (m API) GetCondition(ctx context.Context, i *fastly.GetConditionInput) (*fastly.Condition, error) {\n\treturn m.GetConditionFn(ctx, i)\n}\n\n// ListConditions implements Interface.\nfunc (m API) ListConditions(ctx context.Context, i *fastly.ListConditionsInput) ([]*fastly.Condition, error) {\n\treturn m.ListConditionsFn(ctx, i)\n}\n\n// UpdateCondition implements Interface.\nfunc (m API) UpdateCondition(ctx context.Context, i *fastly.UpdateConditionInput) (*fastly.Condition, error) {\n\treturn m.UpdateConditionFn(ctx, i)\n}\n\n// ListAlertDefinitions implements Interface.\nfunc (m API) ListAlertDefinitions(ctx context.Context, i *fastly.ListAlertDefinitionsInput) (*fastly.AlertDefinitionsResponse, error) {\n\treturn m.ListAlertDefinitionsFn(ctx, i)\n}\n\n// CreateAlertDefinition implements Interface.\nfunc (m API) CreateAlertDefinition(ctx context.Context, i *fastly.CreateAlertDefinitionInput) (*fastly.AlertDefinition, error) {\n\treturn m.CreateAlertDefinitionFn(ctx, i)\n}\n\n// GetAlertDefinition implements Interface.\nfunc (m API) GetAlertDefinition(ctx context.Context, i *fastly.GetAlertDefinitionInput) (*fastly.AlertDefinition, error) {\n\treturn m.GetAlertDefinitionFn(ctx, i)\n}\n\n// UpdateAlertDefinition implements Interface.\nfunc (m API) UpdateAlertDefinition(ctx context.Context, i *fastly.UpdateAlertDefinitionInput) (*fastly.AlertDefinition, error) {\n\treturn m.UpdateAlertDefinitionFn(ctx, i)\n}\n\n// DeleteAlertDefinition implements Interface.\nfunc (m API) DeleteAlertDefinition(ctx context.Context, i *fastly.DeleteAlertDefinitionInput) error {\n\treturn m.DeleteAlertDefinitionFn(ctx, i)\n}\n\n// TestAlertDefinition implements Interface.\nfunc (m API) TestAlertDefinition(ctx context.Context, i *fastly.TestAlertDefinitionInput) error {\n\treturn m.TestAlertDefinitionFn(ctx, i)\n}\n\n// ListAlertHistory implements Interface.\nfunc (m API) ListAlertHistory(ctx context.Context, i *fastly.ListAlertHistoryInput) (*fastly.AlertHistoryResponse, error) {\n\treturn m.ListAlertHistoryFn(ctx, i)\n}\n\n// CreateObservabilityCustomDashboard implements Interface.\nfunc (m API) CreateObservabilityCustomDashboard(ctx context.Context, i *fastly.CreateObservabilityCustomDashboardInput) (*fastly.ObservabilityCustomDashboard, error) {\n\treturn m.CreateObservabilityCustomDashboardFn(ctx, i)\n}\n\n// DeleteObservabilityCustomDashboard implements Interface.\nfunc (m API) DeleteObservabilityCustomDashboard(ctx context.Context, i *fastly.DeleteObservabilityCustomDashboardInput) error {\n\treturn m.DeleteObservabilityCustomDashboardFn(ctx, i)\n}\n\n// GetObservabilityCustomDashboard implements Interface.\nfunc (m API) GetObservabilityCustomDashboard(ctx context.Context, i *fastly.GetObservabilityCustomDashboardInput) (*fastly.ObservabilityCustomDashboard, error) {\n\treturn m.GetObservabilityCustomDashboardFn(ctx, i)\n}\n\n// ListObservabilityCustomDashboards implements Interface.\nfunc (m API) ListObservabilityCustomDashboards(ctx context.Context, i *fastly.ListObservabilityCustomDashboardsInput) (*fastly.ListDashboardsResponse, error) {\n\treturn m.ListObservabilityCustomDashboardsFn(ctx, i)\n}\n\n// UpdateObservabilityCustomDashboard implements Interface.\nfunc (m API) UpdateObservabilityCustomDashboard(ctx context.Context, i *fastly.UpdateObservabilityCustomDashboardInput) (*fastly.ObservabilityCustomDashboard, error) {\n\treturn m.UpdateObservabilityCustomDashboardFn(ctx, i)\n}\n\n// GetImageOptimizerDefaultSettings implements Interface.\nfunc (m API) GetImageOptimizerDefaultSettings(ctx context.Context, i *fastly.GetImageOptimizerDefaultSettingsInput) (*fastly.ImageOptimizerDefaultSettings, error) {\n\treturn m.GetImageOptimizerDefaultSettingsFn(ctx, i)\n}\n\n// UpdateImageOptimizerDefaultSettings implements Interface.\nfunc (m API) UpdateImageOptimizerDefaultSettings(ctx context.Context, i *fastly.UpdateImageOptimizerDefaultSettingsInput) (*fastly.ImageOptimizerDefaultSettings, error) {\n\treturn m.UpdateImageOptimizerDefaultSettingsFn(ctx, i)\n}\n"
  },
  {
    "path": "pkg/mock/client.go",
    "content": "package mock\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/api\"\n)\n\n// APIClient takes a mock.API and returns an app.ClientFactory that uses that\n// mock, ignoring the token and endpoint. It should only be used for tests.\nfunc APIClient(a API) func(string, string, bool) (api.Interface, error) {\n\treturn func(token, endpoint string, debugMode bool) (api.Interface, error) {\n\t\tfmt.Printf(\"token: %s\\n\", token)\n\t\tfmt.Printf(\"endpoint: %s\\n\", endpoint)\n\t\tfmt.Printf(\"debugMode: %t\\n\", debugMode)\n\t\treturn a, nil\n\t}\n}\n\n// HTTPClient is used to mock fastly.Client requests.\ntype HTTPClient struct {\n\t// Index keeps track of which Responses/Errors index to return.\n\tIndex int\n\t// Responses tracks different responses to return.\n\tResponses []*http.Response\n\t// Errors tracks different errors to return.\n\tErrors []error\n\t// SaveRequests toggles recording requests that pass through the\n\t// client.\n\tSaveRequests bool\n\t// Requests stores copies of incoming requests.\n\tRequests []http.Request\n}\n\n// Get mocks a HTTP Client Get request.\nfunc (c *HTTPClient) Get(_ context.Context, p string, _ fastly.RequestOptions) (*http.Response, error) {\n\tfmt.Printf(\"p: %#v\\n\", p)\n\t// IMPORTANT: Have to increment on defer as index is already 0 by this point.\n\t// This is opposite to the Do() method which is -1 at the time it's called.\n\tdefer func() { c.Index++ }()\n\treturn c.Responses[c.Index], c.Errors[c.Index]\n}\n\n// Do mocks a HTTP Client Do operation.\nfunc (c *HTTPClient) Do(r *http.Request) (*http.Response, error) {\n\tfmt.Printf(\"r.URL: %#v\\n\", r.URL.String())\n\tfmt.Printf(\"r: %#v\\n\", r)\n\tif c.SaveRequests {\n\t\tc.Requests = append(c.Requests, *r.Clone(context.Background()))\n\t}\n\tc.Index++\n\treturn c.Responses[c.Index], c.Errors[c.Index]\n}\n\n// HTMLClient returns a mock HTTP Client that returns a stubbed response or\n// error.\nfunc HTMLClient(res []*http.Response, err []error) api.HTTPClient {\n\treturn &HTTPClient{\n\t\tIndex:     -1,\n\t\tResponses: res,\n\t\tErrors:    err,\n\t}\n}\n\n// NewHTTPResponse fills in the boilerplate needed to create a minimal\n// *http.Response.\nfunc NewHTTPResponse(statusCode int, headers map[string]string, body io.ReadCloser) *http.Response {\n\tif body == nil {\n\t\tbody = io.NopCloser(bytes.NewReader(nil))\n\t}\n\th := http.Header{}\n\tfor header, value := range headers {\n\t\th.Add(header, value)\n\t}\n\treturn &http.Response{\n\t\tStatusCode: statusCode,\n\t\tStatus:     http.StatusText(statusCode),\n\t\tBody:       body,\n\t\tHeader:     h,\n\t}\n}\n"
  },
  {
    "path": "pkg/mock/config_file.go",
    "content": "package mock\n\n// ConfigFile is a mock implementation of the toml.ReadWriter interface that's\n// used for testing.\ntype ConfigFile struct {\n\tPathFn   func() string\n\tExistsFn func() bool\n\tReadFn   func(c any) error\n\tWriteFn  func(c any) error\n}\n\n// Path satisfies the toml.ReadWriter interface for testing purposes.\nfunc (c *ConfigFile) Path() string {\n\treturn c.PathFn()\n}\n\n// Exists satisfies the toml.ReadWriter interface for testing purposes.\nfunc (c *ConfigFile) Exists() bool {\n\treturn c.ExistsFn()\n}\n\n// Read satisfies the toml.ReadWriter interface for testing purposes.\nfunc (c *ConfigFile) Read(config any) error {\n\treturn c.ReadFn(config)\n}\n\n// Write satisfies the toml.ReadWriter interface for testing purposes.\nfunc (c *ConfigFile) Write(config any) error {\n\treturn c.WriteFn(config)\n}\n\n// NewNonExistentConfigFile is a test helper function which constructs a new\n// non-existent config file interface.\nfunc NewNonExistentConfigFile() *ConfigFile {\n\treturn &ConfigFile{\n\t\tPathFn: func() string {\n\t\t\treturn \"\"\n\t\t},\n\t\tExistsFn: func() bool {\n\t\t\treturn false\n\t\t},\n\t}\n}\n"
  },
  {
    "path": "pkg/mock/doc.go",
    "content": "// Package mock provides mock implementations of various interfaces.\n// It's designed to be used in tests.\npackage mock\n"
  },
  {
    "path": "pkg/mock/versioner.go",
    "content": "package mock\n\nimport \"fmt\"\n\n// AssetVersioner mocks the github.AssetVersioner interface.\ntype AssetVersioner struct {\n\tAssetVersion    string\n\tBinaryFilename  string\n\tDownloadOK      bool\n\tDownloadedFile  string\n\tInstallFilePath string\n}\n\n// BinaryName implements github.Versioner interface.\nfunc (av AssetVersioner) BinaryName() string {\n\treturn av.BinaryFilename\n}\n\n// DownloadLatest implements github.Versioner interface.\nfunc (av AssetVersioner) DownloadLatest() (string, error) {\n\tif av.DownloadOK {\n\t\treturn av.DownloadedFile, nil\n\t}\n\treturn \"\", fmt.Errorf(\"not implemented\")\n}\n\n// DownloadVersion implements github.Versioner interface.\nfunc (av AssetVersioner) DownloadVersion(_ string) (string, error) {\n\treturn \"\", nil\n}\n\n// Download implements github.Versioner interface.\nfunc (av AssetVersioner) Download(_ string) (string, error) {\n\treturn \"\", nil\n}\n\n// URL implements github.Versioner interface.\nfunc (av AssetVersioner) URL() (string, error) {\n\treturn \"\", nil\n}\n\n// LatestVersion implements github.Versioner interface.\nfunc (av AssetVersioner) LatestVersion() (string, error) {\n\treturn av.AssetVersion, nil\n}\n\n// RequestedVersion implements github.Versioner interface.\nfunc (av AssetVersioner) RequestedVersion() (version string) {\n\treturn \"\"\n}\n\n// SetRequestedVersion implements github.Versioner interface.\nfunc (av AssetVersioner) SetRequestedVersion(_ string) {\n\t// no-op\n}\n\n// InstallPath returns the location of where the binary should be installed.\nfunc (av AssetVersioner) InstallPath() string {\n\treturn av.InstallFilePath\n}\n"
  },
  {
    "path": "pkg/revision/revision.go",
    "content": "// Package revision defines variables that will be populated with values\n// specified at build time via LDFLAGS. goreleaser will prompt for missing env\n// variables.\n// For more details on LDFLAGS:\n// https://github.com/golang/go/wiki/GcToolchainTricks#including-build-information-in-the-executable\npackage revision\n\nimport (\n\t\"fmt\"\n\t\"runtime\"\n\t\"strings\"\n)\n\nvar (\n\t// AppVersion is the semver for this version of the client, or\n\t// \"v0.0.0-unknown\". Handled by goreleaser.\n\tAppVersion string\n\n\t// GitCommit is the short git SHA associated with this build, or\n\t// \"unknown\". Handled by goreleaser.\n\tGitCommit string\n\n\t// GoVersion - Prefer letting the code handle this and set GoHostOS and\n\t// GoHostArc instead. It can be set to the build host's `go version` output.\n\tGoVersion string\n\n\t// GoHostOS is the value from `runtime.GOOS`.\n\tGoHostOS string\n\n\t// GoHostArch is the value from `runtime.GOARCH`.\n\tGoHostArch string\n\n\t// Environment is set to either \"development\" (when working locally) or\n\t// \"release\" when the code being executed is from a published release.\n\t// Handled by goreleaser.\n\tEnvironment string\n)\n\n// None is the AppVersion string for local (unversioned) builds.\nconst None = \"v0.0.0-unknown\"\n\nfunc init() {\n\tif AppVersion == \"\" {\n\t\tAppVersion = None\n\t}\n\tif GitCommit == \"\" {\n\t\tGitCommit = \"unknown\"\n\t}\n\tGoHostOS = runtime.GOOS\n\tGoHostArch = runtime.GOARCH\n\tif GoVersion == \"\" {\n\t\t// runtime.Version() provides the Go tree's version string at build time\n\t\tGoVersion = fmt.Sprintf(\"go version %s %s/%s\", runtime.Version(), GoHostOS, GoHostArch)\n\t}\n\tif Environment == \"\" {\n\t\tEnvironment = \"development\"\n\t}\n}\n\n// SemVer accepts the application revision version, which is prefixed with a\n// `v` and also has a commit hash following the semantic version, and returns\n// just the semantic version.\n//\n// e.g. `v1.0.0-xyz` --> `1.0.0`.\nfunc SemVer(av string) string {\n\tav = strings.TrimPrefix(av, \"v\")\n\tseg := strings.Split(av, \"-\")\n\n\treturn seg[0]\n}\n"
  },
  {
    "path": "pkg/revision/revision_test.go",
    "content": "package revision\n\nimport \"testing\"\n\nfunc TestSemVer(t *testing.T) {\n\tgot := SemVer(\"v1.0.0-xyz\")\n\twant := \"1.0.0\"\n\n\tif got != want {\n\t\tt.Fatalf(\"want %s, got %s\", want, got)\n\t}\n}\n"
  },
  {
    "path": "pkg/runtime/doc.go",
    "content": "// Package runtime contains variables for handling runtime information.\npackage runtime\n"
  },
  {
    "path": "pkg/runtime/runtime.go",
    "content": "package runtime\n\nimport \"runtime\"\n\n// Windows indicates if the CLI binary's runtime OS is Windows.\n//\n// NOTE: We use the same conditional check multiple times across the code base\n// and I noticed I had a typo in a few instances where I had omitted the \"s\" at\n// the end of \"window\" which meant the conditional failed to match when running\n// on Windows. So this avoids that issue in case we need to add more uses of it.\nvar Windows = runtime.GOOS == \"windows\"\n"
  },
  {
    "path": "pkg/sync/doc.go",
    "content": "// Package sync contains abstractions for working with concurrent writers.\npackage sync\n"
  },
  {
    "path": "pkg/sync/sync.go",
    "content": "package sync\n\nimport (\n\t\"io\"\n\t\"sync\"\n)\n\n// Writer protects any io.Writer with a mutex.\ntype Writer struct {\n\tmtx sync.Mutex\n\t// W is public to allow for type checking, but should otherwise not be accessed directly.\n\tW io.Writer\n}\n\n// NewWriter wraps an io.Writer with a mutex.\nfunc NewWriter(w io.Writer) *Writer {\n\treturn &Writer{\n\t\tW: w,\n\t}\n}\n\n// Write implements io.Writer with mutex protection.\nfunc (w *Writer) Write(p []byte) (int, error) {\n\tw.mtx.Lock()\n\tdefer w.mtx.Unlock()\n\treturn w.W.Write(p)\n}\n"
  },
  {
    "path": "pkg/testutil/api.go",
    "content": "package testutil\n\n// Service Version Testing Guide\n//\n// This package provides standard mock functions for testing service version operations.\n// The version mocks follow a consistent pattern across all tests:\n//\n// Version States:\n//   - Version 1: ACTIVE (cannot modify without --autoclone)\n//   - Version 2: LOCKED (cannot modify without --autoclone)\n//   - Version 3: EDITABLE (can modify directly)\n//   - Version 4: STAGING (can modify directly)\n//\n// Test Scenario Guide:\n//\n// 1. Testing --autoclone behavior:\n//      Use: --version 1 or --version 2\n//      Why: Tests that the CLI correctly clones active/locked versions\n//      Example: \"validate --autoclone on locked version\"\n//\n// 2. Testing successful modifications (create/update/delete):\n//      Use: --version 3\n//      Why: Version is editable, so modification succeeds directly\n//      Example: \"validate backend creation\"\n//\n// 3. Testing API errors:\n//      Use: --version 3\n//      Why: Avoids autoclone logic interfering with error testing\n//      Example: \"validate API error when creating backend\"\n//\n// 4. Testing version activation:\n//      Use: --version 3\n//      Why: Version 1 is already active, so test would fail validation\n//      Example: \"validate version activation\"\n//\n// 5. Testing without --version flag:\n//      Use: No --version flag (defaults to active or latest)\n//      Mock: ListVersionsFn only (GetVersionFn not needed)\n//\n// 6. Testing keyword versions (--version active/latest):\n//      Use: --version active or --version latest\n//      Mock: ListVersionsFn only (GetVersionFn not needed)\n//\n// Required Mock Functions:\n//   - GetVersionFn: REQUIRED when using numeric --version flags (--version 1, --version 2, etc.)\n//   - ListVersionsFn: REQUIRED when using keyword versions or no --version flag\n//   - Both: REQUIRED when using --autoclone (needs GetVersion for initial parse, ListVersions for clone check)\n//\n// Example Test Patterns:\n//\n//\t// Pattern 1: Testing --autoclone on locked version\n//\t{\n//\t    Args: \"--service-id 123 --version 2 --name test --autoclone\",\n//\t    API: &mock.API{\n//\t        GetVersionFn:   testutil.GetVersion,           // Parse --version 2\n//\t        ListVersionsFn: testutil.ListVersions,         // Check if locked\n//\t        CloneVersionFn: testutil.CloneVersionResult(4), // Clone to v4\n//\t        UpdateBackendFn: updateBackendOK,               // Modify v4\n//\t    },\n//\t    WantOutput: \"Updated backend (service 123 version 4)\",\n//\t}\n//\n//\t// Pattern 2: Testing API error\n//\t{\n//\t    Args: \"--service-id 123 --version 3 --name test\",\n//\t    API: &mock.API{\n//\t        GetVersionFn:   testutil.GetVersion,  // Parse --version 3\n//\t        ListVersionsFn: testutil.ListVersions, // Check if editable\n//\t        UpdateBackendFn: updateBackendError,   // API fails\n//\t    },\n//\t    WantError: \"API error\",\n//\t}\n//\n//\t// Pattern 3: Testing with keyword version\n//\t{\n//\t    Args: \"--service-id 123 --version active --name test\",\n//\t    API: &mock.API{\n//\t        ListVersionsFn: testutil.ListVersions, // Find active version\n//\t        GetBackendFn:   getBackendOK,\n//\t    },\n//\t    WantOutput: \"Backend details\",\n//\t}\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\tauthcmd \"github.com/fastly/cli/pkg/commands/auth\"\n\t\"github.com/fastly/cli/pkg/commands/whoami\"\n)\n\n// Err represents a generic error.\nvar Err = errors.New(\"test error\")\n\n// ListVersions returns a list of service versions in different states.\n//\n// Versions are returned in descending order by version number (highest first),\n// matching the real Fastly API behavior.\n//\n// Version states:\n//   - Version 4: Staged (can be modified directly)\n//   - Version 3: Editable (can be modified directly)\n//   - Version 2: Locked (cannot be modified without --autoclone)\n//   - Version 1: Active (cannot be modified without --autoclone)\n//\n// Usage guide for test scenarios:\n//   - Testing --autoclone behavior: Use version 1 or 2 (active/locked)\n//   - Testing successful modifications: Use version 3 (editable)\n//   - Testing API errors: Use version 3 (so autoclone logic doesn't interfere)\n//   - Testing version activation: Use version 3 or 4 (not already active)\n//   - Testing staging/unstaging: Use version 4 (staged)\n//\n// NOTE: consult the entire test suite before adding any new entries to the\n// returned type as the tests currently use testutil.CloneVersionResult() as a\n// way of making the test output and expectations as accurate as possible.\n//\n// IMPORTANT: When using numeric --version flags in tests, you must include\n// GetVersionFn: testutil.GetVersion in the mock API, as the CLI now calls\n// GetVersion for numeric versions.\nfunc ListVersions(_ context.Context, i *fastly.ListVersionsInput) ([]*fastly.Version, error) {\n\treturn []*fastly.Version{\n\t\t{\n\t\t\tServiceID: fastly.ToPointer(i.ServiceID),\n\t\t\tNumber:    fastly.ToPointer(4),\n\t\t\tStaging:   fastly.ToPointer(true),\n\t\t\tUpdatedAt: MustParseTimeRFC3339(\"2000-01-04T01:00:00Z\"),\n\t\t},\n\t\t{\n\t\t\tServiceID: fastly.ToPointer(i.ServiceID),\n\t\t\tNumber:    fastly.ToPointer(3),\n\t\t\tUpdatedAt: MustParseTimeRFC3339(\"2000-01-03T01:00:00Z\"),\n\t\t},\n\t\t{\n\t\t\tServiceID: fastly.ToPointer(i.ServiceID),\n\t\t\tNumber:    fastly.ToPointer(2),\n\t\t\tLocked:    fastly.ToPointer(true),\n\t\t\tUpdatedAt: MustParseTimeRFC3339(\"2000-01-02T01:00:00Z\"),\n\t\t},\n\t\t{\n\t\t\tServiceID: fastly.ToPointer(i.ServiceID),\n\t\t\tNumber:    fastly.ToPointer(1),\n\t\t\tActive:    fastly.ToPointer(true),\n\t\t\tUpdatedAt: MustParseTimeRFC3339(\"2000-01-01T01:00:00Z\"),\n\t\t},\n\t}, nil\n}\n\n// ListVersionsError returns a generic error message when attempting to list\n// service versions.\nfunc ListVersionsError(_ context.Context, _ *fastly.ListVersionsInput) ([]*fastly.Version, error) {\n\treturn nil, Err\n}\n\n// GetVersion returns a version matching the requested version number.\n//\n// This function must be included in mock APIs when tests use numeric --version\n// flags (e.g., --version 1, --version 2), as the CLI now calls GetVersion for\n// numeric versions before processing them.\n//\n// Version states returned:\n//   - Version 1: Active (Active=true)\n//   - Version 2: Locked (Locked=true)\n//   - Version 3: Editable (no Active/Locked flags)\n//   - Version 4: Staging (Staging=true)\n//   - Version 5: Generic editable version (commonly used after cloning version 4)\n//   - Version 999: Returns an error (version not found - use this to test error handling)\n//   - Other numbers: Returns a generic editable version\n//\n// This matches the versions returned by ListVersions for versions 1-4.\n//\n// Example test setup:\n//\n//\tAPI: &mock.API{\n//\t    GetVersionFn:   testutil.GetVersion,    // Required for numeric --version flags\n//\t    ListVersionsFn: testutil.ListVersions,  // Required for keyword versions (active/latest)\n//\t    CloneVersionFn: testutil.CloneVersionResult(4),\n//\t}\nfunc GetVersion(_ context.Context, i *fastly.GetVersionInput) (*fastly.Version, error) {\n\tswitch i.ServiceVersion {\n\tcase 1:\n\t\treturn &fastly.Version{\n\t\t\tServiceID: fastly.ToPointer(i.ServiceID),\n\t\t\tNumber:    fastly.ToPointer(1),\n\t\t\tActive:    fastly.ToPointer(true),\n\t\t\tUpdatedAt: MustParseTimeRFC3339(\"2000-01-01T01:00:00Z\"),\n\t\t}, nil\n\tcase 2:\n\t\treturn &fastly.Version{\n\t\t\tServiceID: fastly.ToPointer(i.ServiceID),\n\t\t\tNumber:    fastly.ToPointer(2),\n\t\t\tLocked:    fastly.ToPointer(true),\n\t\t\tUpdatedAt: MustParseTimeRFC3339(\"2000-01-02T01:00:00Z\"),\n\t\t}, nil\n\tcase 3:\n\t\treturn &fastly.Version{\n\t\t\tServiceID: fastly.ToPointer(i.ServiceID),\n\t\t\tNumber:    fastly.ToPointer(3),\n\t\t\tUpdatedAt: MustParseTimeRFC3339(\"2000-01-03T01:00:00Z\"),\n\t\t}, nil\n\tcase 4:\n\t\treturn &fastly.Version{\n\t\t\tServiceID: fastly.ToPointer(i.ServiceID),\n\t\t\tNumber:    fastly.ToPointer(4),\n\t\t\tStaging:   fastly.ToPointer(true),\n\t\t\tUpdatedAt: MustParseTimeRFC3339(\"2000-01-04T01:00:00Z\"),\n\t\t}, nil\n\tcase 5:\n\t\t// Version 5 is commonly used in tests after cloning version 4\n\t\treturn &fastly.Version{\n\t\t\tServiceID: fastly.ToPointer(i.ServiceID),\n\t\t\tNumber:    fastly.ToPointer(5),\n\t\t\tUpdatedAt: MustParseTimeRFC3339(\"2000-01-05T01:00:00Z\"),\n\t\t}, nil\n\tcase 999:\n\t\t// Return an error for test cases that explicitly want to test version not found\n\t\treturn nil, Err\n\tdefault:\n\t\t// Return a generic version for any other number to avoid breaking existing tests\n\t\treturn &fastly.Version{\n\t\t\tServiceID: fastly.ToPointer(i.ServiceID),\n\t\t\tNumber:    fastly.ToPointer(i.ServiceVersion),\n\t\t\tUpdatedAt: MustParseTimeRFC3339(\"2000-01-01T01:00:00Z\"),\n\t\t}, nil\n\t}\n}\n\n// CloneVersionResult returns a function which returns a specific cloned version.\nfunc CloneVersionResult(version int) func(_ context.Context, i *fastly.CloneVersionInput) (*fastly.Version, error) {\n\treturn func(_ context.Context, i *fastly.CloneVersionInput) (*fastly.Version, error) {\n\t\treturn &fastly.Version{\n\t\t\tServiceID: fastly.ToPointer(i.ServiceID),\n\t\t\tNumber:    fastly.ToPointer(version),\n\t\t}, nil\n\t}\n}\n\n// CloneVersionError returns a generic error message when attempting to clone a\n// service version.\nfunc CloneVersionError(_ context.Context, _ *fastly.CloneVersionInput) (*fastly.Version, error) {\n\treturn nil, Err\n}\n\n// WhoamiVerifyClient is used by `whoami` and auth tests.\ntype WhoamiVerifyClient whoami.VerifyResponse\n\n// Do executes the HTTP request.\nfunc (c WhoamiVerifyClient) Do(*http.Request) (*http.Response, error) {\n\trec := httptest.NewRecorder()\n\t_ = json.NewEncoder(rec).Encode(whoami.VerifyResponse(c))\n\treturn rec.Result(), nil\n}\n\n// WhoamiBasicResponse is used by `whoami` and auth tests.\nvar WhoamiBasicResponse = whoami.VerifyResponse{\n\tCustomer: whoami.Customer{\n\t\tID:   \"abc\",\n\t\tName: \"Computer Company\",\n\t},\n\tUser: whoami.User{\n\t\tID:    \"123\",\n\t\tName:  \"Alice Programmer\",\n\t\tLogin: \"alice@example.com\",\n\t},\n\tServices: map[string]string{\n\t\t\"1xxaa\": \"First service\",\n\t\t\"2baba\": \"Second service\",\n\t},\n\tToken: whoami.Token{\n\t\tID:        \"abcdefg\",\n\t\tName:      \"Token name\",\n\t\tCreatedAt: \"2019-01-01T12:00:00Z\",\n\t\t// no ExpiresAt\n\t\tScope: \"global\",\n\t},\n}\n\n// CurrentCustomerClient is used by SSO auth tests.\ntype CurrentCustomerClient authcmd.CurrentCustomerResponse\n\n// Do executes the HTTP request.\nfunc (c CurrentCustomerClient) Do(*http.Request) (*http.Response, error) {\n\trec := httptest.NewRecorder()\n\t_ = json.NewEncoder(rec).Encode(authcmd.CurrentCustomerResponse(c))\n\treturn rec.Result(), nil\n}\n\n// CurrentCustomerResponse is used by SSO auth tests.\nvar CurrentCustomerResponse = authcmd.CurrentCustomerResponse{\n\tID:   \"abc\",\n\tName: \"Computer Company\",\n}\n\n// GetServiceDetails returns service details with versions matching the filter.\n//\n// This function must be included in mock APIs when the CLI calls GetServiceDetails,\n// which happens when using the --version active flag.\n//\n// Filters supported:\n//   - versions.active: Returns version 1 (active)\n//\n// This matches the version states returned by ListVersions and GetVersion.\n//\n// Example test setup:\n//\n//\tAPI: &mock.API{\n//\t    GetServiceDetailsFn: testutil.GetServiceDetails,  // Required for --version active\n//\t    GetVersionFn:        testutil.GetVersion,         // Required for numeric --version flags\n//\t    ListVersionsFn:      testutil.ListVersions,       // Required for --version latest or omitted flag\n//\t}\nfunc GetServiceDetails(_ context.Context, i *fastly.GetServiceDetailsInput) (*fastly.ServiceDetail, error) {\n\tdetail := &fastly.ServiceDetail{\n\t\tServiceID: fastly.ToPointer(i.ServiceID),\n\t\tVersions:  []*fastly.Version{},\n\t}\n\n\t// Check filters to determine which version to return\n\tfor _, filter := range i.Filters {\n\t\tif filter.Key == \"versions.active\" && filter.Value {\n\t\t\tversion := &fastly.Version{\n\t\t\t\tServiceID: fastly.ToPointer(i.ServiceID),\n\t\t\t\tNumber:    fastly.ToPointer(1),\n\t\t\t\tActive:    fastly.ToPointer(true),\n\t\t\t\tUpdatedAt: MustParseTimeRFC3339(\"2000-01-01T01:00:00Z\"),\n\t\t\t}\n\t\t\tdetail.ActiveVersion = version\n\t\t\tdetail.Version = version\n\t\t\tdetail.Versions = append(detail.Versions, version)\n\t\t}\n\t\tif filter.Key == \"versions.staged\" && filter.Value {\n\t\t\tversion := &fastly.Version{\n\t\t\t\tServiceID: fastly.ToPointer(i.ServiceID),\n\t\t\t\tNumber:    fastly.ToPointer(4),\n\t\t\t\tStaging:   fastly.ToPointer(true),\n\t\t\t\tUpdatedAt: MustParseTimeRFC3339(\"2000-01-04T01:00:00Z\"),\n\t\t\t}\n\t\t\tdetail.Version = version\n\t\t\tdetail.Versions = append(detail.Versions, version)\n\t\t}\n\t}\n\n\treturn detail, nil\n}\n"
  },
  {
    "path": "pkg/testutil/args.go",
    "content": "package testutil\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"regexp\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/fastly/cli/pkg/auth\"\n\t\"github.com/fastly/cli/pkg/config\"\n\t\"github.com/fastly/cli/pkg/errors\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/manifest\"\n\t\"github.com/fastly/cli/pkg/mock\"\n\t\"github.com/fastly/cli/pkg/runtime\"\n)\n\nvar argsPattern = regexp.MustCompile(\"`.+`\")\n\n// SplitArgs is a simple wrapper function designed to accept a CLI command\n// (including flags) and return it as a slice for consumption by app.Run().\n//\n// NOTE: One test file (TestBigQueryCreate) passes RSA content inline into the\n// args string which means it has to escape the double quotes (used to infer\n// the content should be considered a single argument) with a backtick. This\n// causes problems when trying to split the args string by a space (as the RSA\n// content has spaces) and so we need to be able to identify when backticks are\n// used and ensure the backtick argument is considered a single argument (i.e.\n// don't incorrectly split by the spaces within the RSA content when converting\n// the arg string into a slice).\n//\n// The logic checks for backticks, and then replaces the content that is\n// surrounded by backticks with --- and then splits the resulting string by\n// spaces. Afterwards if there was a backtick matched, then we re-insert the\n// backticked content into the slice where --- is found.\nfunc SplitArgs(args string) []string {\n\tvar backtickMatch []string\n\n\tif strings.Contains(args, \"`\") {\n\t\tbacktickMatch = argsPattern.FindStringSubmatch(args)\n\t\targs = argsPattern.ReplaceAllString(args, \"---\")\n\t}\n\ts := strings.Split(args, \" \")\n\n\tif len(backtickMatch) > 0 {\n\t\tfor i, v := range s {\n\t\t\tif v == \"---\" {\n\t\t\t\ts[i] = backtickMatch[0]\n\t\t\t}\n\t\t}\n\t}\n\n\treturn s\n}\n\n// MockAuthServer is used to no-op the authentication server.\ntype MockAuthServer struct {\n\tauth.Runner\n\n\tResult chan auth.AuthorizationResult\n}\n\n// SetParam sets the specified parameter for the authorization_endpoint.\nfunc (s MockAuthServer) SetParam(_, _ string) {\n\t// no-op\n}\n\n// AuthURL returns a fully qualified authorization_endpoint.\n// i.e. path + audience + scope + code_challenge etc.\nfunc (s MockAuthServer) AuthURL() (string, error) {\n\treturn \"\", nil // no-op\n}\n\n// GetResult returns the results channel.\nfunc (s MockAuthServer) GetResult() chan auth.AuthorizationResult {\n\treturn s.Result\n}\n\n// SetAPIEndpoint sets the API endpoint.\nfunc (s MockAuthServer) SetAPIEndpoint(_ string) {\n\t// no-op\n}\n\n// Start starts a local server for handling authentication processing.\nfunc (s MockAuthServer) Start() error {\n\treturn nil // no-op\n}\n\n// MockGlobalData returns a struct that can be used to populate a call to app.Exec()\n// while the majority of fields will be pre-populated and only those fields\n// commonly changed for testing purposes will need to be provided.\n//\n// TODO: Move this and other mocks into mocks package.\nfunc MockGlobalData(args []string, stdout io.Writer) *global.Data {\n\tvar md manifest.Data\n\tmd.File.Args = args\n\tmd.File.SetErrLog(errors.Log)\n\tmd.File.SetOutput(stdout)\n\t_ = md.File.Read(manifest.Filename)\n\n\tconfigPath := \"/dev/null\"\n\tif runtime.Windows {\n\t\tconfigPath = \"NUL\"\n\t}\n\n\treturn &global.Data{\n\t\tArgs:             args,\n\t\tAPIClientFactory: mock.APIClient(mock.API{}),\n\t\tAuthServer:       &MockAuthServer{},\n\t\tConfig: config.File{\n\t\t\tAuth: config.Auth{\n\t\t\t\tDefault: \"user\",\n\t\t\t\tTokens: config.AuthTokens{\n\t\t\t\t\t\"user\": &config.AuthToken{\n\t\t\t\t\t\tType:  config.AuthTokenTypeStatic,\n\t\t\t\t\t\tToken: \"mock-token\",\n\t\t\t\t\t\tEmail: \"test@example.com\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tConfigPath: configPath,\n\t\tEnv:        config.Environment{},\n\t\tErrLog:     errors.Log,\n\t\tErrOutput:  stdout,\n\t\tExecuteWasmTools: func(bin string, args []string, d *global.Data) error {\n\t\t\tfmt.Printf(\"bin: %s\\n\", bin)\n\t\t\tfmt.Printf(\"args: %#v\\n\", args)\n\t\t\tfmt.Printf(\"global: %#v\\n\", d)\n\t\t\treturn nil\n\t\t},\n\t\tHTTPClient: &http.Client{Timeout: time.Second * 5},\n\t\tManifest:   &md,\n\t\tOpener: func(input string) error {\n\t\t\tfmt.Printf(\"%s\\n\", input)\n\t\t\treturn nil // no-op\n\t\t},\n\t\tOutput: stdout,\n\t}\n}\n"
  },
  {
    "path": "pkg/testutil/assert.go",
    "content": "package testutil\n\nimport (\n\tstderrors \"errors\"\n\t\"fmt\"\n\t\"reflect\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/fastly/cli/pkg/argparser\"\n\t\"github.com/fastly/cli/pkg/errors\"\n)\n\n// AssertEqual fatals a test if the parameters aren't equal.\nfunc AssertEqual(t *testing.T, want, have any) {\n\tt.Helper()\n\tif diff := cmp.Diff(want, have); diff != \"\" {\n\t\tt.Fatal(diff)\n\t}\n}\n\n// AssertBool fatals a test if the parameters aren't equal.\nfunc AssertBool(t *testing.T, want, have bool) {\n\tt.Helper()\n\tif want != have {\n\t\tt.Fatalf(\"want %v, have %v\", want, have)\n\t}\n}\n\n// AssertString fatals a test if the parameters aren't equal.\nfunc AssertString(t *testing.T, want, have string) {\n\tt.Helper()\n\tif want != have {\n\t\tt.Fatal(cmp.Diff(want, have))\n\t}\n}\n\n// AssertStringContains fatals a test if the string doesn't contain a substring.\nfunc AssertStringContains(t *testing.T, s, substr string) {\n\tt.Helper()\n\tif !strings.Contains(s, substr) {\n\t\tt.Fatalf(\"%q doesn't contain %q\", s, substr)\n\t}\n}\n\n// AssertStringDoesntContain fatals a test if the string does contain a substring.\nfunc AssertStringDoesntContain(t *testing.T, s, substr string) {\n\tt.Helper()\n\tif strings.Contains(s, substr) {\n\t\tt.Fatalf(\"%q contains %q\", s, substr)\n\t}\n}\n\n// AssertNoError fatals a test if the error is not nil.\nfunc AssertNoError(t *testing.T, err error) {\n\tt.Helper()\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n}\n\n// AssertErrorContains fatals a test if the error's Error string doesn't contain\n// target. As a special case, if target is the empty string, we assume the error\n// should be nil.\nfunc AssertErrorContains(t *testing.T, err error, target string) {\n\tt.Helper()\n\tswitch {\n\tcase err == nil && target == \"\":\n\t\treturn // great\n\tcase err == nil && target != \"\":\n\t\tt.Fatalf(\"want %q, have no error\", target)\n\tcase err != nil && target == \"\":\n\t\tt.Fatalf(\"want no error, have %q\", err)\n\tcase err != nil && target != \"\":\n\t\tif want, have := target, err.Error(); !strings.Contains(have, want) {\n\t\t\tt.Fatalf(\"want %q, have %q\", want, have)\n\t\t}\n\t}\n}\n\n// AssertRemediationErrorContains fatals a test if the error's RemediationError\n// remediation string doesn't contain target. As a special case, if target is\n// the empty string, we assume the error should be nil.\nfunc AssertRemediationErrorContains(t *testing.T, err error, target string) {\n\tt.Helper()\n\n\tvar re errors.RemediationError\n\tok := stderrors.As(err, &re)\n\n\tswitch {\n\tcase err == nil && target == \"\":\n\t\treturn // great\n\tcase err == nil && target != \"\":\n\t\tt.Fatalf(\"want %q, have no error\", target)\n\tcase err != nil && target != \"\" && !ok:\n\t\tt.Fatalf(\"have no RemediationError in: %v\", err)\n\tcase err != nil && target != \"\":\n\t\tif want, have := target, re.Remediation; !strings.Contains(have, want) {\n\t\t\tt.Fatalf(\"want remediation containing %q, have %q\", want, have)\n\t\t}\n\t}\n}\n\n// AssertPathContentFlag errors a test scenario if the given flag value hasn't\n// been parsed as expected.\n//\n// Example: Some flags will internally be passed to `argparser.Content` to acquire\n// the value. If passed a file path, then we expect the testdata/<fixture> to\n// have been read, otherwise we expect the given flag value to have been used.\nfunc AssertPathContentFlag(flag string, wantError string, args []string, fixture string, content string, t *testing.T) {\n\tif wantError == \"\" {\n\t\tfor i, a := range args {\n\t\t\tif a == fmt.Sprintf(\"--%s\", flag) {\n\t\t\t\twant := args[i+1]\n\t\t\t\tif want == fmt.Sprintf(\"./testdata/%s\", fixture) {\n\t\t\t\t\twant = argparser.Content(want)\n\t\t\t\t}\n\t\t\t\tif content != want {\n\t\t\t\t\tt.Errorf(\"wanted %s, have %s\", want, content)\n\t\t\t\t}\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n}\n\n// Borrowed from https://github.com/stretchr/testify/blob/v1.9.0/assert/assertions.go#L778-L784\nfunc getLen(x any) (l int, ok bool) {\n\tv := reflect.ValueOf(x)\n\tdefer func() {\n\t\tok = recover() == nil\n\t}()\n\treturn v.Len(), true\n}\n\n// AssertLength fails a test scenario if the given slice or string does\n// not have the expected length.\nfunc AssertLength(t *testing.T, want int, have any) {\n\tt.Helper()\n\tl, ok := getLen(have)\n\n\tif !ok {\n\t\tt.Fatalf(\"cannot get len of type %T\", have)\n\t}\n\n\tif l != want {\n\t\tt.Fatalf(\"wanted %d elements, got %d (%#v)\", want, l, have)\n\t}\n}\n"
  },
  {
    "path": "pkg/testutil/client.go",
    "content": "package testutil\n\nimport \"net/http\"\n\n// MockRoundTripper implements [http.RoundTripper] for mocking HTTP responses.\ntype MockRoundTripper struct {\n\tResponse *http.Response\n\tErr      error\n}\n\n// RoundTrip executes a single HTTP transaction, returning a Response for the\n// provided Request.\nfunc (m *MockRoundTripper) RoundTrip(_ *http.Request) (*http.Response, error) {\n\treturn m.Response, m.Err\n}\n\n// MultiResponseRoundTripper implements [http.RoundTripper] for mocking multiple\n// sequential HTTP responses. This is useful when the code under test makes\n// multiple HTTP calls (e.g., GET then PATCH).\n//\n// When we perform a get and update in go-fastly operations (such as for alerts),\n// we need to be able to parse multiple responses back from the API.\ntype MultiResponseRoundTripper struct {\n\tResponses []*http.Response\n\tindex     int\n}\n\n// RoundTrip executes a single HTTP transaction, returning the next Response\n// in sequence.\nfunc (m *MultiResponseRoundTripper) RoundTrip(_ *http.Request) (*http.Response, error) {\n\tif m.index >= len(m.Responses) {\n\t\treturn m.Responses[len(m.Responses)-1], nil\n\t}\n\tresp := m.Responses[m.index]\n\tm.index++\n\treturn resp, nil\n}\n"
  },
  {
    "path": "pkg/testutil/doc.go",
    "content": "// Package testutil provides helpers for unit tests.\npackage testutil\n"
  },
  {
    "path": "pkg/testutil/env.go",
    "content": "package testutil\n\nimport (\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"testing\"\n)\n\n// FileIO represents a source file and a destination.\ntype FileIO struct {\n\tSrc        string // path to a file inside ./testdata/ OR file content\n\tDst        string // path to a file relative to test environment's root directory\n\tExecutable bool   // if path can be executed as a binary\n}\n\n// EnvOpts represents configuration when creating a new environment.\ntype EnvOpts struct {\n\tT     *testing.T\n\tDirs  []string // expect path to have a trailing slash (will be added if missing)\n\tCopy  []FileIO // .Src expected to be file path\n\tWrite []FileIO // .Src expected to be file content\n\tExec  []string // e.g. []string{\"npm\", \"install\"}\n}\n\n// NewEnv creates a new test environment and returns the root directory.\nfunc NewEnv(opts EnvOpts) (rootdir string) {\n\trootdir, err := os.MkdirTemp(\"\", \"fastly-temp-*\")\n\tif err != nil {\n\t\topts.T.Fatal(err)\n\t}\n\n\tif err := os.MkdirAll(rootdir, 0o750); err != nil {\n\t\topts.T.Fatal(err)\n\t}\n\n\tfor _, d := range opts.Dirs {\n\t\td = strings.TrimRight(d, \"/\") + \"/filename-required.txt\"\n\t\tcreateIntermediaryDirectories(d, rootdir, opts.T)\n\t}\n\n\tfor _, f := range opts.Copy {\n\t\tsrc := f.Src\n\t\tdst := filepath.Join(rootdir, f.Dst)\n\t\tCopyFile(opts.T, src, dst)\n\t}\n\n\tfor _, f := range opts.Write {\n\t\tif f.Src == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tsrc := f.Src\n\t\tdst := filepath.Join(rootdir, f.Dst)\n\n\t\t// Ensure any intermediary directories exist before trying to write the\n\t\t// given file to disk.\n\t\tcreateIntermediaryDirectories(f.Dst, rootdir, opts.T)\n\n\t\tif err := os.WriteFile(dst, []byte(src), 0o777); err != nil /* #nosec */ {\n\t\t\topts.T.Fatal(err)\n\t\t}\n\t\tif f.Executable {\n\t\t\tif err := os.Chmod(dst, os.FileMode(0o755)); err != nil {\n\t\t\t\topts.T.Fatal(err)\n\t\t\t}\n\t\t}\n\t}\n\n\tif len(opts.Exec) > 0 {\n\t\t// gosec flagged this:\n\t\t// G204 (CWE-78): Subprocess launched with function call as argument or cmd arguments\n\t\t// Disabling as we trust the source of the variable.\n\t\t// #nosec\n\t\t// nosemgrep: go.lang.security.audit.dangerous-exec-command.dangerous-exec-command\n\t\tcmd := exec.Command(opts.Exec[0], opts.Exec[1:]...)\n\t\tcmd.Dir = rootdir\n\t\tif err := cmd.Run(); err != nil {\n\t\t\topts.T.Fatal(err)\n\t\t}\n\t}\n\n\treturn rootdir\n}\n\n// createIntermediaryDirectories strips the filename from the given path and\n// appends it to the rootdir so that we can use MkdirAll to create the\n// directory and all its intermediary directories.\n//\n// EXAMPLE: /foo/bar/baz.txt will create the foo and bar directories if they\n// don't already exist.\n//\n// NOTE: If path is just a filename (e.g. config.toml), then this function\n// won't necessarily trigger a test failure because we would end up appending\n// an empty string to the rootdir and so the MkdirAll call still succeeds.\nfunc createIntermediaryDirectories(path, rootdir string, t *testing.T) {\n\tintermediary := strings.Replace(path, filepath.Base(path), \"\", 1)\n\tintermediary = filepath.Join(rootdir, intermediary)\n\tif err := os.MkdirAll(intermediary, 0o750); err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n"
  },
  {
    "path": "pkg/testutil/file.go",
    "content": "package testutil\n\nimport (\n\t\"io\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n)\n\n// MakeTempFile creates a tempfile with the given contents and returns its path.\nfunc MakeTempFile(t *testing.T, contents string) string {\n\tt.Helper()\n\n\ttmpfile, err := os.CreateTemp(\"\", \"fastly-*\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif _, err := tmpfile.Write([]byte(contents)); err != nil {\n\t\tt.Fatal(err)\n\t}\n\treturn tmpfile.Name()\n}\n\n// CopyFile copies a referenced file to a new location.\nfunc CopyFile(t *testing.T, fromFilename, toFilename string) {\n\tt.Helper()\n\n\t// gosec flagged this:\n\t// G304 (CWE-22): Potential file inclusion via variable\n\t// Disabling as we trust the source of the variable.\n\t/* #nosec */\n\tsrc, err := os.Open(fromFilename)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer func() {\n\t\tif err := src.Close(); err != nil {\n\t\t\tt.Errorf(\"Failed to close fromFilename: %v\", err)\n\t\t}\n\t}()\n\n\ttoDir := filepath.Dir(toFilename)\n\tif err := os.MkdirAll(toDir, 0o750); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t// gosec flagged this:\n\t// G304 (CWE-22): Potential file inclusion via variable\n\t// Disabling as we trust the source of the variable.\n\t/* #nosec */\n\tdst, err := os.Create(toFilename)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif _, err := io.Copy(dst, src); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif err := dst.Sync(); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif err := dst.Close(); err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n"
  },
  {
    "path": "pkg/testutil/json.go",
    "content": "package testutil\n\nimport \"encoding/json\"\n\n// GenJSON returns JSON encoding of data, or empty object in case of an error.\nfunc GenJSON(data any) []byte {\n\tb, err := json.MarshalIndent(data, \"\", \"  \")\n\tif err != nil {\n\t\treturn []byte(\"{}\")\n\t}\n\treturn b\n}\n"
  },
  {
    "path": "pkg/testutil/log.go",
    "content": "package testutil\n\nimport \"testing\"\n\n// LogWriter is used to debug issues with our tests.\ntype LogWriter struct{ T *testing.T }\n\nfunc (w LogWriter) Write(p []byte) (int, error) {\n\t// NOTE: text printed only if test fails or -test.v set\n\tw.T.Log(string(p))\n\treturn len(p), nil\n}\n"
  },
  {
    "path": "pkg/testutil/must.go",
    "content": "package testutil\n\nimport (\n\t\"time\"\n)\n\n// MustParseTimeRFC3339 is a small helper to initialize time constants.\nfunc MustParseTimeRFC3339(s string) *time.Time {\n\ttm, err := time.Parse(time.RFC3339, s)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn &tm\n}\n"
  },
  {
    "path": "pkg/testutil/paginator.go",
    "content": "package testutil\n\nimport (\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n)\n\n// ServicesPaginator mocks the behaviour of a paginator for services.\ntype ServicesPaginator struct {\n\tCount         int\n\tMaxPages      int\n\tNumOfPages    int\n\tRequestedPage int\n\tReturnErr     bool\n}\n\n// HasNext indicates if there is another page of data.\nfunc (p *ServicesPaginator) HasNext() bool {\n\tif p.Count > p.MaxPages {\n\t\treturn false\n\t}\n\tp.Count++\n\treturn true\n}\n\n// Remaining returns the count of remaining pages.\nfunc (p ServicesPaginator) Remaining() int {\n\treturn 1\n}\n\n// GetNext returns the next page of data.\nfunc (p *ServicesPaginator) GetNext() (ss []*fastly.Service, err error) {\n\tif p.ReturnErr {\n\t\terr = Err\n\t}\n\tpageOne := fastly.Service{\n\t\tServiceID:     fastly.ToPointer(\"123\"),\n\t\tName:          fastly.ToPointer(\"Foo\"),\n\t\tType:          fastly.ToPointer(\"wasm\"),\n\t\tCustomerID:    fastly.ToPointer(\"mycustomerid\"),\n\t\tActiveVersion: fastly.ToPointer(2),\n\t\tUpdatedAt:     MustParseTimeRFC3339(\"2010-11-15T19:01:02Z\"),\n\t\tVersions: []*fastly.Version{\n\t\t\t{\n\t\t\t\tNumber:    fastly.ToPointer(1),\n\t\t\t\tComment:   fastly.ToPointer(\"a\"),\n\t\t\t\tServiceID: fastly.ToPointer(\"b\"),\n\t\t\t\tCreatedAt: MustParseTimeRFC3339(\"2001-02-03T04:05:06Z\"),\n\t\t\t\tUpdatedAt: MustParseTimeRFC3339(\"2001-02-04T04:05:06Z\"),\n\t\t\t\tDeletedAt: MustParseTimeRFC3339(\"2001-02-05T04:05:06Z\"),\n\t\t\t},\n\t\t\t{\n\t\t\t\tNumber:    fastly.ToPointer(2),\n\t\t\t\tComment:   fastly.ToPointer(\"c\"),\n\t\t\t\tServiceID: fastly.ToPointer(\"d\"),\n\t\t\t\tActive:    fastly.ToPointer(true),\n\t\t\t\tDeployed:  fastly.ToPointer(true),\n\t\t\t\tCreatedAt: MustParseTimeRFC3339(\"2001-03-03T04:05:06Z\"),\n\t\t\t\tUpdatedAt: MustParseTimeRFC3339(\"2001-03-04T04:05:06Z\"),\n\t\t\t},\n\t\t},\n\t}\n\tpageTwo := fastly.Service{\n\t\tServiceID:     fastly.ToPointer(\"456\"),\n\t\tName:          fastly.ToPointer(\"Bar\"),\n\t\tType:          fastly.ToPointer(\"wasm\"),\n\t\tCustomerID:    fastly.ToPointer(\"mycustomerid\"),\n\t\tActiveVersion: fastly.ToPointer(1),\n\t\tUpdatedAt:     MustParseTimeRFC3339(\"2015-03-14T12:59:59Z\"),\n\t}\n\tpageThree := fastly.Service{\n\t\tServiceID:     fastly.ToPointer(\"789\"),\n\t\tName:          fastly.ToPointer(\"Baz\"),\n\t\tType:          fastly.ToPointer(\"vcl\"),\n\t\tCustomerID:    fastly.ToPointer(\"mycustomerid\"),\n\t\tActiveVersion: fastly.ToPointer(1),\n\t}\n\tif p.Count == 1 {\n\t\tss = append(ss, &pageOne)\n\t}\n\tif p.Count == 2 {\n\t\tss = append(ss, &pageTwo)\n\t}\n\tif p.Count == 3 {\n\t\tss = append(ss, &pageThree)\n\t}\n\tif p.RequestedPage > 0 && p.NumOfPages == 1 {\n\t\tp.Count = p.MaxPages + 1 // forces only one result to be displayed\n\t}\n\treturn ss, err\n}\n"
  },
  {
    "path": "pkg/testutil/scenarios.go",
    "content": "package testutil\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"os\"\n\t\"slices\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/api\"\n\t\"github.com/fastly/cli/pkg/app\"\n\t\"github.com/fastly/cli/pkg/config\"\n\t\"github.com/fastly/cli/pkg/global\"\n\t\"github.com/fastly/cli/pkg/mock\"\n\t\"github.com/fastly/cli/pkg/threadsafe\"\n)\n\n// CLIScenario represents a CLI test case to be validated.\n//\n// Most of the fields in this struct are optional; if they are not\n// provided RunCLIScenario will not apply the behavior indicated for\n// those fields.\ntype CLIScenario struct {\n\t// API is a mock API implementation which can be used by the\n\t// command under test\n\tAPI *mock.API\n\t// Args is the input arguments for the command to execute (not\n\t// including the command names themselves).\n\tArgs string\n\t// Client is a mock http.Client that will be used as part of a\n\t// *fastly.Client instance passed into the test code.\n\tClient *http.Client\n\t// ConfigPath will be copied into global.Data.ConfigPath\n\tConfigPath string\n\t// ConfigFile will be copied into global.Data.ConfigFile\n\tConfigFile *config.File\n\t// DontWantOutput will cause the scenario to fail if the\n\t// string appears in stdout\n\tDontWantOutput string\n\t// DontWantOutputs will cause the scenario to fail if any of\n\t// the strings appear in stdout\n\tDontWantOutputs []string\n\tEnv             *EnvConfig\n\t// EnvVars contains environment variables which will be set\n\t// during the execution of the scenario\n\tEnvVars map[string]string\n\t// Name appears in output when tests are executed\n\tName            string\n\tPathContentFlag *PathContentFlag\n\t// Setup function can perform additional setup before the scenario is run\n\tSetup func(t *testing.T, scenario *CLIScenario, opts *global.Data)\n\t// Stdin contains input to be read by the application\n\tStdin []string\n\t// Validator function can perform additional validation on the results\n\t// of the scenario\n\tValidator func(t *testing.T, scenario *CLIScenario, opts *global.Data, stdout *threadsafe.Buffer)\n\t// WantError will cause the scenario to fail if this string\n\t// does not appear in an Error\n\tWantError string\n\t// WantRemediation will cause the scenario to fail if the\n\t// error's RemediationError.Remediation doesn't contain this string\n\tWantRemediation string\n\t// WantOutput will cause the scenario to fail if this string\n\t// does not appear in stdout\n\tWantOutput string\n\t// WantOutputs will cause the scenario to fail if any of the\n\t// strings do not appear in stdout\n\tWantOutputs []string\n}\n\n// PathContentFlag provides the details required to validate that a\n// flag value has been parsed correctly by the argument parser.\ntype PathContentFlag struct {\n\tFlag    string\n\tFixture string\n\tContent func() string\n}\n\n// EnvConfig provides the details required to setup a temporary test\n// environment, and optionally a function to run which accepts the\n// environment directory and can modify fields in the CLIScenario.\ntype EnvConfig struct {\n\tOpts *EnvOpts\n\t// EditScenario holds a function which will be called after\n\t// the temporary environment has been created but before the\n\t// scenario setup (and execution) begin; it can make any\n\t// modifications to the CLIScenario that are needed\n\tEditScenario func(*CLIScenario, string)\n}\n\n// RunCLIScenario executes a CLIScenario struct.\n// The Arg field of the scenario is prepended with the content of the 'command'\n// slice passed in to construct the complete command to be executed.\nfunc RunCLIScenario(t *testing.T, command []string, scenario CLIScenario) {\n\tt.Run(scenario.Name, func(t *testing.T) {\n\t\tvar (\n\t\t\terr      error\n\t\t\tfullargs []string\n\t\t\trootdir  string\n\t\t\tstdout   threadsafe.Buffer\n\t\t)\n\n\t\tif len(scenario.Args) > 0 {\n\t\t\tfullargs = slices.Concat(command, SplitArgs(scenario.Args))\n\t\t} else {\n\t\t\tfullargs = command\n\t\t}\n\n\t\topts := MockGlobalData(fullargs, &stdout)\n\n\t\t// NOTE: The go-fastly API client has changed design.\n\t\t// It has started to move away from methods on the client instance.\n\t\t// Instead it has started to expose functions that accept a client.\n\t\t// This means for test mocking we have to adjust the mock approach.\n\t\tvar acf global.APIClientFactory\n\t\tif scenario.API == nil {\n\t\t\tacf = func(_, _ string, _ bool) (api.Interface, error) {\n\t\t\t\tfc, err := fastly.NewClientForEndpoint(\"no-key\", \"api.example.com\")\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, fmt.Errorf(\"failed to mock fastly.Client: %w\", err)\n\t\t\t\t}\n\t\t\t\tif scenario.Client != nil {\n\t\t\t\t\tfc.HTTPClient = scenario.Client\n\t\t\t\t}\n\t\t\t\treturn fc, nil\n\t\t\t}\n\t\t} else {\n\t\t\tacf = mock.APIClient(*scenario.API)\n\t\t}\n\t\topts.APIClientFactory = acf\n\n\t\tif scenario.Env != nil {\n\t\t\t// We're going to chdir to a deploy environment,\n\t\t\t// so save the PWD to return to, afterwards.\n\t\t\tpwd, err := os.Getwd()\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\t// Create test environment\n\t\t\tscenario.Env.Opts.T = t\n\t\t\trootdir = NewEnv(*scenario.Env.Opts)\n\t\t\tdefer os.RemoveAll(rootdir)\n\n\t\t\t// Before running the test, chdir into the build environment.\n\t\t\t// When we're done, chdir back to our original location.\n\t\t\t// This is so we can reliably copy the testdata/ fixtures.\n\t\t\tif err := os.Chdir(rootdir); err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tdefer func() {\n\t\t\t\t_ = os.Chdir(pwd)\n\t\t\t}()\n\n\t\t\tif scenario.Env.EditScenario != nil {\n\t\t\t\tscenario.Env.EditScenario(&scenario, rootdir)\n\t\t\t}\n\t\t}\n\n\t\tif len(scenario.ConfigPath) > 0 {\n\t\t\topts.ConfigPath = scenario.ConfigPath\n\t\t}\n\n\t\tif scenario.ConfigFile != nil {\n\t\t\topts.Config = *scenario.ConfigFile\n\t\t}\n\n\t\tif scenario.EnvVars != nil {\n\t\t\tfor key, value := range scenario.EnvVars {\n\t\t\t\tif err := os.Setenv(key, value); err != nil {\n\t\t\t\t\tt.Fatal(err)\n\t\t\t\t}\n\t\t\t\tdefer func() {\n\t\t\t\t\tif err := os.Unsetenv(key); err != nil {\n\t\t\t\t\t\tt.Fatal(err)\n\t\t\t\t\t}\n\t\t\t\t}()\n\t\t\t}\n\t\t}\n\n\t\tif scenario.Setup != nil {\n\t\t\tscenario.Setup(t, &scenario, opts)\n\t\t}\n\n\t\tif len(scenario.Stdin) > 1 {\n\t\t\t// To handle multiple prompt input from the user we need to do some\n\t\t\t// coordination around io pipes to mimic the required user behaviour.\n\t\t\tstdin, prompt := io.Pipe()\n\t\t\topts.Input = stdin\n\n\t\t\t// Wait for user input and write it to the prompt\n\t\t\tinputc := make(chan string)\n\t\t\tgo func() {\n\t\t\t\tfor input := range inputc {\n\t\t\t\t\tfmt.Fprintln(prompt, input)\n\t\t\t\t}\n\t\t\t}()\n\n\t\t\t// We need a channel so we wait for `run()` to complete\n\t\t\tdone := make(chan bool)\n\n\t\t\t// Call `app.Run()` and wait for response\n\t\t\tgo func() {\n\t\t\t\tapp.Init = func(_ []string, _ io.Reader) (*global.Data, error) {\n\t\t\t\t\treturn opts, nil\n\t\t\t\t}\n\t\t\t\terr = app.Run(fullargs, nil)\n\t\t\t\tdone <- true\n\t\t\t}()\n\n\t\t\t// User provides input\n\t\t\t//\n\t\t\t// NOTE: Must provide as much input as is expected to be waited on by `run()`.\n\t\t\t//       For example, if `run()` calls `input()` twice, then provide two messages.\n\t\t\t//       Otherwise the select statement will trigger the timeout error.\n\t\t\tfor _, input := range scenario.Stdin {\n\t\t\t\tinputc <- input\n\t\t\t}\n\n\t\t\tselect {\n\t\t\tcase <-done:\n\t\t\t\t// Wait for app.Run() to finish\n\t\t\tcase <-time.After(time.Second):\n\t\t\t\tt.Fatalf(\"unexpected timeout waiting for mocked prompt inputs to be processed\")\n\t\t\t}\n\t\t} else {\n\t\t\tstdin := \"\"\n\t\t\tif len(scenario.Stdin) > 0 {\n\t\t\t\tstdin = scenario.Stdin[0]\n\t\t\t}\n\t\t\topts.Input = strings.NewReader(stdin)\n\t\t\tapp.Init = func(_ []string, _ io.Reader) (*global.Data, error) {\n\t\t\t\treturn opts, nil\n\t\t\t}\n\t\t\terr = app.Run(fullargs, nil)\n\t\t}\n\n\t\tAssertErrorContains(t, err, scenario.WantError)\n\t\tif scenario.WantRemediation != \"\" {\n\t\t\tAssertRemediationErrorContains(t, err, scenario.WantRemediation)\n\t\t}\n\t\tAssertStringContains(t, stdout.String(), scenario.WantOutput)\n\n\t\tfor _, want := range scenario.WantOutputs {\n\t\t\tAssertStringContains(t, stdout.String(), want)\n\t\t}\n\n\t\tif len(scenario.DontWantOutput) > 0 {\n\t\t\tAssertStringDoesntContain(t, stdout.String(), scenario.DontWantOutput)\n\t\t}\n\t\tfor _, want := range scenario.DontWantOutputs {\n\t\t\tAssertStringDoesntContain(t, stdout.String(), want)\n\t\t}\n\n\t\tif scenario.PathContentFlag != nil {\n\t\t\tpcf := *scenario.PathContentFlag\n\t\t\tAssertPathContentFlag(pcf.Flag, scenario.WantError, fullargs, pcf.Fixture, pcf.Content(), t)\n\t\t}\n\n\t\tif scenario.Validator != nil {\n\t\t\tscenario.Validator(t, &scenario, opts, &stdout)\n\t\t}\n\t})\n}\n\n// RunCLIScenarios executes the CLIScenario structs from the slice passed in.\nfunc RunCLIScenarios(t *testing.T, command []string, scenarios []CLIScenario) {\n\tfor _, scenario := range scenarios {\n\t\tRunCLIScenario(t, command, scenario)\n\t}\n}\n"
  },
  {
    "path": "pkg/testutil/string.go",
    "content": "package testutil\n\nimport \"strings\"\n\n// StripNewLines removes all newline delimiters.\nfunc StripNewLines(s string) string {\n\treturn strings.ReplaceAll(s, \"\\n\", \"\")\n}\n"
  },
  {
    "path": "pkg/testutil/time.go",
    "content": "package testutil\n\nimport \"time\"\n\n// Date is a consistent date object used by all tests.\nvar Date = time.Date(2021, time.June, 15, 23, 0, 0, 0, time.UTC)\n"
  },
  {
    "path": "pkg/text/accesskey.go",
    "content": "package text\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/time\"\n\t\"github.com/fastly/go-fastly/v15/fastly/objectstorage/accesskeys\"\n)\n\n// PrintAccessKey displays an access key.\nfunc PrintAccessKey(out io.Writer, accessKey *accesskeys.AccessKey) {\n\tfmt.Fprintf(out, \"ID: %s\\n\", accessKey.AccessKeyID)\n\tfmt.Fprintf(out, \"Secret: %s\\n\", accessKey.SecretKey)\n\tfmt.Fprintf(out, \"Description: %s\\n\", accessKey.Description)\n\tfmt.Fprintf(out, \"Permission: %s\\n\", accessKey.Permission)\n\tfmt.Fprintf(out, \"Buckets: %s\\n\", accessKey.Buckets)\n\tfmt.Fprintf(out, \"Created (UTC): %s\\n\", accessKey.CreatedAt.UTC().Format(time.Format))\n}\n\n// PrintAccessKeyTbl displays access keys in a table format.\nfunc PrintAccessKeyTbl(out io.Writer, accessKeys []accesskeys.AccessKey) {\n\ttbl := NewTable(out)\n\ttbl.AddHeader(\"ID\", \"Secret\", \"Description\", \"Permission\", \"Buckets\", \"Created At\")\n\n\tif accessKeys == nil {\n\t\ttbl.Print()\n\t\treturn\n\t}\n\n\tfor _, accessKey := range accessKeys {\n\t\t// avoid gosec loop aliasing check :/\n\t\taccessKey := accessKey\n\t\tvar buckets string\n\t\tif len(accessKey.Buckets) == 0 {\n\t\t\t// No limitations on buckets\n\t\t\tbuckets = \"all\"\n\t\t} else {\n\t\t\tbuckets = fmt.Sprintf(\"%v\", accessKey.Buckets)\n\t\t}\n\n\t\ttbl.AddLine(accessKey.AccessKeyID, accessKey.SecretKey, accessKey.Description, accessKey.Permission, buckets, accessKey.CreatedAt)\n\t}\n\ttbl.Print()\n}\n"
  },
  {
    "path": "pkg/text/alerts.go",
    "content": "package text\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"strings\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/workspaces/alerts/datadog\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/workspaces/alerts/jira\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/workspaces/alerts/mailinglist\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/workspaces/alerts/microsoftteams\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/workspaces/alerts/opsgenie\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/workspaces/alerts/pagerduty\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/workspaces/alerts/slack\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/workspaces/alerts/webhook\"\n)\n\n// PrintAlert displays a single alert.\n// Accepts any alert type (datadog, slack, webhook, etc.) via any.\nfunc PrintAlert(out io.Writer, alert any) {\n\tvar id, alertType, description, createdAt, createdBy string\n\tvar config any\n\n\t// Extract common fields based on type\n\tswitch a := alert.(type) {\n\tcase *datadog.Alert:\n\t\tid = a.ID\n\t\talertType = a.Type\n\t\tdescription = a.Description\n\t\tcreatedAt = a.CreatedAt\n\t\tcreatedBy = a.CreatedBy\n\t\tconfig = a.Config\n\tcase *jira.Alert:\n\t\tid = a.ID\n\t\talertType = a.Type\n\t\tdescription = a.Description\n\t\tcreatedAt = a.CreatedAt\n\t\tcreatedBy = a.CreatedBy\n\t\tconfig = a.Config\n\tcase *mailinglist.Alert:\n\t\tid = a.ID\n\t\talertType = a.Type\n\t\tdescription = a.Description\n\t\tcreatedAt = a.CreatedAt\n\t\tcreatedBy = a.CreatedBy\n\t\tconfig = a.Config\n\tcase *microsoftteams.Alert:\n\t\tid = a.ID\n\t\talertType = a.Type\n\t\tdescription = a.Description\n\t\tcreatedAt = a.CreatedAt\n\t\tcreatedBy = a.CreatedBy\n\t\tconfig = a.Config\n\tcase *opsgenie.Alert:\n\t\tid = a.ID\n\t\talertType = a.Type\n\t\tdescription = a.Description\n\t\tcreatedAt = a.CreatedAt\n\t\tcreatedBy = a.CreatedBy\n\t\tconfig = a.Config\n\tcase *pagerduty.Alert:\n\t\tid = a.ID\n\t\talertType = a.Type\n\t\tdescription = a.Description\n\t\tcreatedAt = a.CreatedAt\n\t\tcreatedBy = a.CreatedBy\n\t\tconfig = a.Config\n\tcase *slack.Alert:\n\t\tid = a.ID\n\t\talertType = a.Type\n\t\tdescription = a.Description\n\t\tcreatedAt = a.CreatedAt\n\t\tcreatedBy = a.CreatedBy\n\t\tconfig = a.Config\n\tcase *webhook.Alert:\n\t\tid = a.ID\n\t\talertType = a.Type\n\t\tdescription = a.Description\n\t\tcreatedAt = a.CreatedAt\n\t\tcreatedBy = a.CreatedBy\n\t\tconfig = a.Config\n\tdefault:\n\t\tfmt.Fprintf(out, \"Unknown alert type\\n\")\n\t\treturn\n\t}\n\n\tfmt.Fprintf(out, \"ID: %s\\n\", id)\n\tfmt.Fprintf(out, \"Type: %s\\n\", alertType)\n\tfmt.Fprintf(out, \"Description: %s\\n\", description)\n\tfmt.Fprintf(out, \"Created At: %s\\n\", createdAt)\n\tfmt.Fprintf(out, \"Created By: %s\\n\", createdBy)\n\tprintAlertConfig(out, alertType, config)\n}\n\n// printAlertConfig prints alert configuration based on type.\nfunc printAlertConfig(out io.Writer, alertType string, config any) {\n\tfmt.Fprint(out, \"Config:\\n\")\n\tswitch alertType {\n\tcase \"datadog\":\n\t\tprintDatadogConfig(out, config)\n\tcase \"jira\":\n\t\tprintJiraConfig(out, config)\n\tcase \"mailinglist\":\n\t\tprintMailingListConfig(out, config)\n\tcase \"microsoftteams\", \"slack\", \"webhook\":\n\t\tprintWebhookConfig(out, config)\n\tcase \"opsgenie\", \"pagerduty\":\n\t\tprintKeyConfig(out, config)\n\tdefault:\n\t\tfmt.Fprintf(out, \"  (unknown type: %s)\\n\", alertType)\n\t}\n}\n\n// printDatadogConfig prints Datadog-specific configuration.\nfunc printDatadogConfig(out io.Writer, config any) {\n\tif cfg, ok := config.(datadog.ResponseConfig); ok {\n\t\tif cfg.Key != nil {\n\t\t\tfmt.Fprintf(out, \"  Key: <redacted>\\n\")\n\t\t}\n\t\tif cfg.Site != nil {\n\t\t\tfmt.Fprintf(out, \"  Site: %s\\n\", *cfg.Site)\n\t\t}\n\t}\n}\n\n// printWebhookConfig prints webhook-based configuration (slack, webhook, microsoftteams).\nfunc printWebhookConfig(out io.Writer, config any) {\n\tvar hasWebhook bool\n\n\tswitch cfg := config.(type) {\n\tcase slack.ResponseConfig:\n\t\thasWebhook = cfg.Webhook != nil\n\tcase webhook.ResponseConfig:\n\t\thasWebhook = cfg.Webhook != nil\n\tcase microsoftteams.ResponseConfig:\n\t\thasWebhook = cfg.Webhook != nil\n\t}\n\n\tif hasWebhook {\n\t\tfmt.Fprintf(out, \"  Webhook: <redacted>\\n\")\n\t}\n}\n\n// printJiraConfig prints Jira-specific configuration.\nfunc printJiraConfig(out io.Writer, config any) {\n\tif cfg, ok := config.(jira.ResponseConfig); ok {\n\t\tif cfg.Host != nil {\n\t\t\tfmt.Fprintf(out, \"  Host: %s\\n\", *cfg.Host)\n\t\t}\n\t\tif cfg.Username != nil {\n\t\t\tfmt.Fprintf(out, \"  Username: %s\\n\", *cfg.Username)\n\t\t}\n\t\tif cfg.Project != nil {\n\t\t\tfmt.Fprintf(out, \"  Project: %s\\n\", *cfg.Project)\n\t\t}\n\t\tif cfg.IssueType != nil {\n\t\t\tfmt.Fprintf(out, \"  Issue Type: %s\\n\", *cfg.IssueType)\n\t\t}\n\t\tif cfg.Key != nil {\n\t\t\tfmt.Fprintf(out, \"  Key: <redacted>\\n\")\n\t\t}\n\t}\n}\n\n// printMailingListConfig prints Mailing List-specific configuration.\nfunc printMailingListConfig(out io.Writer, config any) {\n\tif cfg, ok := config.(mailinglist.ResponseConfig); ok {\n\t\tif cfg.Address != nil {\n\t\t\tfmt.Fprintf(out, \"  Address: %s\\n\", *cfg.Address)\n\t\t}\n\t}\n}\n\n// printKeyConfig prints key-based configuration (opsgenie, pagerduty).\nfunc printKeyConfig(out io.Writer, config any) {\n\tvar hasKey bool\n\n\tswitch cfg := config.(type) {\n\tcase opsgenie.ResponseConfig:\n\t\thasKey = cfg.Key != nil\n\tcase pagerduty.ResponseConfig:\n\t\thasKey = cfg.Key != nil\n\t}\n\n\tif hasKey {\n\t\tfmt.Fprintf(out, \"  Key: <redacted>\\n\")\n\t}\n}\n\n// getConfigSummary returns a string summary of the alert config with sensitive fields redacted.\nfunc getConfigSummary(alertType string, config any) string {\n\tswitch alertType {\n\tcase \"datadog\":\n\t\tif cfg, ok := config.(datadog.ResponseConfig); ok {\n\t\t\tparts := []string{}\n\t\t\tif cfg.Site != nil {\n\t\t\t\tparts = append(parts, fmt.Sprintf(\"Site: %s\", *cfg.Site))\n\t\t\t}\n\t\t\tif cfg.Key != nil {\n\t\t\t\tparts = append(parts, \"Key: <redacted>\")\n\t\t\t}\n\t\t\treturn strings.Join(parts, \", \")\n\t\t}\n\tcase \"jira\":\n\t\tif cfg, ok := config.(jira.ResponseConfig); ok {\n\t\t\tparts := []string{}\n\t\t\tif cfg.Host != nil {\n\t\t\t\tparts = append(parts, fmt.Sprintf(\"Host: %s\", *cfg.Host))\n\t\t\t}\n\t\t\tif cfg.IssueType != nil {\n\t\t\t\tparts = append(parts, fmt.Sprintf(\"Issue Type: %s\", *cfg.IssueType))\n\t\t\t}\n\t\t\tif cfg.Key != nil {\n\t\t\t\tparts = append(parts, \"Key: <redacted>\")\n\t\t\t}\n\t\t\tif cfg.Project != nil {\n\t\t\t\tparts = append(parts, fmt.Sprintf(\"Project: %s\", *cfg.Project))\n\t\t\t}\n\t\t\tif cfg.Username != nil {\n\t\t\t\tparts = append(parts, fmt.Sprintf(\"Username: %s\", *cfg.Username))\n\t\t\t}\n\t\t\treturn strings.Join(parts, \", \")\n\t\t}\n\tcase \"mailinglist\":\n\t\tif cfg, ok := config.(mailinglist.ResponseConfig); ok {\n\t\t\tif cfg.Address != nil {\n\t\t\t\treturn fmt.Sprintf(\"Address: %s\", *cfg.Address)\n\t\t\t}\n\t\t}\n\tcase \"microsoftteams\", \"slack\", \"webhook\":\n\t\treturn \"Webhook: <redacted>\"\n\tcase \"opsgenie\", \"pagerduty\":\n\t\treturn \"Key: <redacted>\"\n\t}\n\treturn \"\"\n}\n\n// PrintAlertTbl prints a table of alerts.\nfunc PrintAlertTbl(out io.Writer, alerts any) {\n\ttbl := NewTable(out)\n\ttbl.AddHeader(\"ID\", \"Type\", \"Description\", \"Created At\", \"Created By\", \"Config\")\n\n\t// Handle different alert type slices\n\tswitch a := alerts.(type) {\n\tcase []datadog.Alert:\n\t\tfor _, alert := range a {\n\t\t\tconfigSummary := getConfigSummary(alert.Type, alert.Config)\n\t\t\ttbl.AddLine(alert.ID, alert.Type, alert.Description, alert.CreatedAt, alert.CreatedBy, configSummary)\n\t\t}\n\tcase []slack.Alert:\n\t\tfor _, alert := range a {\n\t\t\tconfigSummary := getConfigSummary(alert.Type, alert.Config)\n\t\t\ttbl.AddLine(alert.ID, alert.Type, alert.Description, alert.CreatedAt, alert.CreatedBy, configSummary)\n\t\t}\n\tcase []webhook.Alert:\n\t\tfor _, alert := range a {\n\t\t\tconfigSummary := getConfigSummary(alert.Type, alert.Config)\n\t\t\ttbl.AddLine(alert.ID, alert.Type, alert.Description, alert.CreatedAt, alert.CreatedBy, configSummary)\n\t\t}\n\tcase []jira.Alert:\n\t\tfor _, alert := range a {\n\t\t\tconfigSummary := getConfigSummary(alert.Type, alert.Config)\n\t\t\ttbl.AddLine(alert.ID, alert.Type, alert.Description, alert.CreatedAt, alert.CreatedBy, configSummary)\n\t\t}\n\tcase []mailinglist.Alert:\n\t\tfor _, alert := range a {\n\t\t\tconfigSummary := getConfigSummary(alert.Type, alert.Config)\n\t\t\ttbl.AddLine(alert.ID, alert.Type, alert.Description, alert.CreatedAt, alert.CreatedBy, configSummary)\n\t\t}\n\tcase []microsoftteams.Alert:\n\t\tfor _, alert := range a {\n\t\t\tconfigSummary := getConfigSummary(alert.Type, alert.Config)\n\t\t\ttbl.AddLine(alert.ID, alert.Type, alert.Description, alert.CreatedAt, alert.CreatedBy, configSummary)\n\t\t}\n\tcase []opsgenie.Alert:\n\t\tfor _, alert := range a {\n\t\t\tconfigSummary := getConfigSummary(alert.Type, alert.Config)\n\t\t\ttbl.AddLine(alert.ID, alert.Type, alert.Description, alert.CreatedAt, alert.CreatedBy, configSummary)\n\t\t}\n\tcase []pagerduty.Alert:\n\t\tfor _, alert := range a {\n\t\t\tconfigSummary := getConfigSummary(alert.Type, alert.Config)\n\t\t\ttbl.AddLine(alert.ID, alert.Type, alert.Description, alert.CreatedAt, alert.CreatedBy, configSummary)\n\t\t}\n\t}\n\n\ttbl.Print()\n}\n"
  },
  {
    "path": "pkg/text/backend.go",
    "content": "package text\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/segmentio/textio\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n)\n\n// PrintBackend pretty prints a fastly.Backend structure in verbose format\n// to a given io.Writer. Consumers can provide a prefix string which will\n// be used as a prefix to each line, useful for indentation.\nfunc PrintBackend(out io.Writer, prefix string, b *fastly.Backend) {\n\tout = textio.NewPrefixWriter(out, prefix)\n\n\tfmt.Fprintf(out, \"Name: %s\\n\", fastly.ToValue(b.Name))\n\tfmt.Fprintf(out, \"Comment: %v\\n\", fastly.ToValue(b.Comment))\n\tfmt.Fprintf(out, \"Address: %v\\n\", fastly.ToValue(b.Address))\n\tfmt.Fprintf(out, \"Port: %v\\n\", fastly.ToValue(b.Port))\n\tfmt.Fprintf(out, \"Override host: %v\\n\", fastly.ToValue(b.OverrideHost))\n\tfmt.Fprintf(out, \"Connect timeout: %v\\n\", fastly.ToValue(b.ConnectTimeout))\n\tfmt.Fprintf(out, \"Max connections: %v\\n\", fastly.ToValue(b.MaxConn))\n\tfmt.Fprintf(out, \"Max connection use: %v\\n\", fastly.ToValue(b.MaxUse))\n\tfmt.Fprintf(out, \"Max connection lifetime: %v\\n\", fastly.ToValue(b.MaxLifetime))\n\tfmt.Fprintf(out, \"First byte timeout: %v\\n\", fastly.ToValue(b.FirstByteTimeout))\n\tfmt.Fprintf(out, \"Between bytes timeout: %v\\n\", fastly.ToValue(b.BetweenBytesTimeout))\n\tfmt.Fprintf(out, \"Auto loadbalance: %v\\n\", fastly.ToValue(b.AutoLoadbalance))\n\tfmt.Fprintf(out, \"Weight: %v\\n\", fastly.ToValue(b.Weight))\n\tfmt.Fprintf(out, \"Healthcheck: %v\\n\", fastly.ToValue(b.HealthCheck))\n\tfmt.Fprintf(out, \"Shield: %v\\n\", fastly.ToValue(b.Shield))\n\tfmt.Fprintf(out, \"Use SSL: %v\\n\", fastly.ToValue(b.UseSSL))\n\tfmt.Fprintf(out, \"SSL check cert: %v\\n\", fastly.ToValue(b.SSLCheckCert))\n\tfmt.Fprintf(out, \"SSL CA cert: %v\\n\", fastly.ToValue(b.SSLCACert))\n\tfmt.Fprintf(out, \"SSL client cert: %v\\n\", fastly.ToValue(b.SSLClientCert))\n\tfmt.Fprintf(out, \"SSL client key: %v\\n\", fastly.ToValue(b.SSLClientKey))\n\tfmt.Fprintf(out, \"SSL cert hostname: %v\\n\", fastly.ToValue(b.SSLCertHostname))\n\tfmt.Fprintf(out, \"SSL SNI hostname: %v\\n\", fastly.ToValue(b.SSLSNIHostname))\n\tfmt.Fprintf(out, \"Min TLS version: %v\\n\", fastly.ToValue(b.MinTLSVersion))\n\tfmt.Fprintf(out, \"Max TLS version: %v\\n\", fastly.ToValue(b.MaxTLSVersion))\n\tfmt.Fprintf(out, \"SSL ciphers: %v\\n\", fastly.ToValue(b.SSLCiphers))\n\tfmt.Fprintf(out, \"HTTP KeepAlive Timeout: %v\\n\", fastly.ToValue(b.KeepAliveTime))\n\tif b.TCPKeepAliveEnable == nil {\n\t\tfmt.Fprintf(out, \"TCP KeepAlive Enabled: unset\\n\")\n\t} else {\n\t\tfmt.Fprintf(out, \"TCP KeepAlive Enabled: %v\\n\", fastly.ToValue(b.TCPKeepAliveEnable))\n\t}\n\tfmt.Fprintf(out, \"TCP KeepAlive Interval: %v\\n\", fastly.ToValue(b.TCPKeepAliveIntvl))\n\tfmt.Fprintf(out, \"TCP KeepAlive Probes: %v\\n\", fastly.ToValue(b.TCPKeepAliveProbes))\n\tfmt.Fprintf(out, \"TCP KeepAlive Timeout: %v\\n\", fastly.ToValue(b.TCPKeepAliveTime))\n}\n"
  },
  {
    "path": "pkg/text/color.go",
    "content": "package text\n\nimport (\n\t\"github.com/fatih/color\"\n)\n\n// Bold is a Sprint-class function that makes the arguments bold.\nvar Bold = color.New(color.Bold).SprintFunc()\n\n// BoldCyan is a Sprint-class function that makes the arguments bold and cyan.\nvar BoldCyan = color.New(color.Bold, color.FgCyan).SprintFunc()\n\n// BoldRed is a Sprint-class function that makes the arguments bold and red.\nvar BoldRed = color.New(color.Bold, color.FgRed).SprintFunc()\n\n// BoldYellow is a Sprint-class function that makes the arguments bold and yellow.\nvar BoldYellow = color.New(color.Bold, color.FgYellow).SprintFunc()\n\n// BoldGreen is a Sprint-class function that makes the arguments bold and green.\nvar BoldGreen = color.New(color.Bold, color.FgGreen).SprintFunc()\n\n// Reset is a Sprint-class function that resets the color for the arguments.\nvar Reset = color.New(color.Reset).SprintFunc()\n\n// Prompt is a Sprint-class function that makes the arguments bold and uses the\n// default colour for the terminal.\n//\n// IMPORTANT: Be careful modifying with Black or White as this can break themes.\n// e.g. Black with Solarized Dark makes the text invisible!\nvar Prompt = color.New(color.Bold).SprintFunc()\n\n// ColorFn is a function returned from a color.SprintFunc() call.\ntype ColorFn func(a ...any) string\n"
  },
  {
    "path": "pkg/text/computeacl.go",
    "content": "package text\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/segmentio/textio\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly/computeacls\"\n)\n\n// PrintComputeACL displays a compute ACL.\nfunc PrintComputeACL(out io.Writer, prefix string, acl *computeacls.ComputeACL) {\n\tout = textio.NewPrefixWriter(out, prefix)\n\n\tfmt.Fprintf(out, \"ID: %s\\n\", acl.ComputeACLID)\n\tfmt.Fprintf(out, \"Name: %s\\n\", acl.Name)\n}\n\n// PrintComputeACLsTbl displays compute ACLs in a table format.\nfunc PrintComputeACLsTbl(out io.Writer, acls []computeacls.ComputeACL) {\n\ttbl := NewTable(out)\n\ttbl.AddHeader(\"Name\", \"ID\")\n\n\tif acls == nil {\n\t\ttbl.Print()\n\t\treturn\n\t}\n\n\tfor _, acl := range acls {\n\t\t// avoid gosec loop aliasing check :/\n\t\tacl := acl\n\t\ttbl.AddLine(acl.Name, acl.ComputeACLID)\n\t}\n\ttbl.Print()\n}\n\n// PrintComputeACLEntry displays a compute ACL entry.\nfunc PrintComputeACLEntry(out io.Writer, prefix string, entry *computeacls.ComputeACLEntry) {\n\tout = textio.NewPrefixWriter(out, prefix)\n\n\tfmt.Fprintf(out, \"Prefix: %s\\n\", entry.Prefix)\n\tfmt.Fprintf(out, \"Action: %s\\n\", entry.Action)\n}\n\n// PrintComputeACLEntriesTbl displays compute ACL entries in a table format.\nfunc PrintComputeACLEntriesTbl(out io.Writer, entries []computeacls.ComputeACLEntry) {\n\ttbl := NewTable(out)\n\ttbl.AddHeader(\"Prefix\", \"Action\")\n\n\tif entries == nil {\n\t\ttbl.Print()\n\t\treturn\n\t}\n\n\tfor _, entry := range entries {\n\t\t// avoid gosec loop aliasing check :/\n\t\tentry := entry\n\t\ttbl.AddLine(entry.Prefix, entry.Action)\n\t}\n\ttbl.Print()\n}\n"
  },
  {
    "path": "pkg/text/configstore.go",
    "content": "package text\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"github.com/segmentio/textio\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\tfsttime \"github.com/fastly/cli/pkg/time\"\n)\n\n// PrintConfigStoresTbl displays store data in a table format.\nfunc PrintConfigStoresTbl(out io.Writer, stores []*fastly.ConfigStore) {\n\ttbl := NewTable(out)\n\ttbl.AddHeader(\"Name\", \"ID\", \"Created (UTC)\", \"Updated (UTC)\")\n\n\tif stores == nil {\n\t\ttbl.Print()\n\t\treturn\n\t}\n\n\tfor _, cs := range stores {\n\t\t// avoid gosec loop aliasing check :/\n\t\tcs := cs\n\t\ttbl.AddLine(cs.Name, cs.StoreID, fmtConfigStoreTime(cs.CreatedAt), fmtConfigStoreTime(cs.UpdatedAt))\n\t}\n\ttbl.Print()\n}\n\n// PrintConfigStore displays store data and optional metadata (may be nil).\nfunc PrintConfigStore(out io.Writer, cs *fastly.ConfigStore, csm *fastly.ConfigStoreMetadata) {\n\tout = textio.NewPrefixWriter(out, \"\")\n\n\tfmt.Fprintf(out, \"Name: %s\\n\", cs.Name)\n\tfmt.Fprintf(out, \"ID: %s\\n\", cs.StoreID)\n\tfmt.Fprintf(out, \"Created (UTC): %s\\n\", fmtConfigStoreTime(cs.CreatedAt))\n\tfmt.Fprintf(out, \"Updated (UTC): %s\\n\", fmtConfigStoreTime(cs.UpdatedAt))\n\tif csm != nil {\n\t\tfmt.Fprintf(out, \"Item Count: %d\\n\", csm.ItemCount)\n\t}\n}\n\n// PrintConfigStoreServicesTbl displays table of a config store's services.\nfunc PrintConfigStoreServicesTbl(out io.Writer, s []*fastly.Service) {\n\ttw := NewTable(out)\n\ttw.AddHeader(\"NAME\", \"ID\", \"TYPE\")\n\tfor _, service := range s {\n\t\ttw.AddLine(\n\t\t\tfastly.ToValue(service.Name),\n\t\t\tfastly.ToValue(service.ServiceID),\n\t\t\tfastly.ToValue(service.Type),\n\t\t)\n\t}\n\ttw.Print()\n}\n\nfunc fmtConfigStoreTime(t *time.Time) string {\n\tif t == nil {\n\t\treturn \"n/a\"\n\t}\n\treturn t.UTC().Format(fsttime.Format)\n}\n\n// PrintConfigStoreItemsTbl displays store item data in a table format.\nfunc PrintConfigStoreItemsTbl(out io.Writer, items []*fastly.ConfigStoreItem) {\n\ttbl := NewTable(out)\n\ttbl.AddHeader(\"Key\", \"Value\", \"Created (UTC)\", \"Updated (UTC)\")\n\n\tif items == nil {\n\t\ttbl.Print()\n\t\treturn\n\t}\n\n\tfor _, csi := range items {\n\t\t// avoid gosec loop aliasing check :/\n\t\tcsi := csi\n\n\t\t// Quote and truncate 'value' to an arbitrary length.\n\t\t// Note that this operates on the number of bytes, and not\n\t\t// character or grapheme clusters.\n\t\tvalue := csi.Value\n\t\tvar truncated bool\n\t\tif len(csi.Value) > 64 {\n\t\t\tvalue = value[:64]\n\t\t\ttruncated = true\n\t\t}\n\t\tvalue = strconv.Quote(value)\n\t\tif truncated {\n\t\t\tvalue += \" (truncated)\"\n\t\t}\n\n\t\ttbl.AddLine(csi.Key, value, fmtConfigStoreTime(csi.CreatedAt), fmtConfigStoreTime(csi.UpdatedAt))\n\t}\n\ttbl.Print()\n}\n\n// PrintConfigStoreItem displays store item data.\nfunc PrintConfigStoreItem(out io.Writer, prefix string, csi *fastly.ConfigStoreItem) {\n\tout = textio.NewPrefixWriter(out, prefix)\n\n\tfmt.Fprintf(out, \"StoreID: %s\\n\", csi.StoreID)\n\tfmt.Fprintf(out, \"Key: %s\\n\", csi.Key)\n\tfmt.Fprintf(out, \"Value: %s\\n\", csi.Value)\n\tfmt.Fprintf(out, \"Created (UTC): %s\\n\", fmtConfigStoreTime(csi.CreatedAt))\n\tfmt.Fprintf(out, \"Updated (UTC): %s\\n\", fmtConfigStoreTime(csi.UpdatedAt))\n\tif csi.DeletedAt != nil {\n\t\tfmt.Fprintf(out, \"Deleted (UTC): %s\\n\", fmtConfigStoreTime(csi.DeletedAt))\n\t}\n}\n"
  },
  {
    "path": "pkg/text/customsignal.go",
    "content": "package text\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/time\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/signals\"\n)\n\n// PrintCustomSignal displays an NGWAF custom signal.\nfunc PrintCustomSignal(out io.Writer, customSignalToPrint *signals.Signal) {\n\tfmt.Fprintf(out, \"ID: %s\\n\", customSignalToPrint.SignalID)\n\tfmt.Fprintf(out, \"Name: %s\\n\", customSignalToPrint.Name)\n\tfmt.Fprintf(out, \"Description: %s\\n\", customSignalToPrint.Description)\n\tfmt.Fprintf(out, \"Scope: %s\\n\", customSignalToPrint.Scope.Type)\n\tfmt.Fprintf(out, \"Updated (UTC): %s\\n\", customSignalToPrint.UpdatedAt.UTC().Format(time.Format))\n\tfmt.Fprintf(out, \"Created (UTC): %s\\n\", customSignalToPrint.CreatedAt.UTC().Format(time.Format))\n}\n\n// PrintCustomSignalTbl displays custom signals in a table format.\nfunc PrintCustomSignalTbl(out io.Writer, customSignalsToPrint []signals.Signal) {\n\ttbl := NewTable(out)\n\ttbl.AddHeader(\"ID\", \"Name\", \"Description\", \"Scope\", \"Updated At\", \"Created At\")\n\n\tif customSignalsToPrint == nil {\n\t\ttbl.Print()\n\t\treturn\n\t}\n\n\tfor _, customSignalToPrint := range customSignalsToPrint {\n\t\ttbl.AddLine(\n\t\t\tcustomSignalToPrint.SignalID,\n\t\t\tcustomSignalToPrint.Name,\n\t\t\tcustomSignalToPrint.Description,\n\t\t\tcustomSignalToPrint.Scope.Type,\n\t\t\tcustomSignalToPrint.UpdatedAt,\n\t\t\tcustomSignalToPrint.CreatedAt,\n\t\t)\n\t}\n\ttbl.Print()\n}\n"
  },
  {
    "path": "pkg/text/dictionary.go",
    "content": "package text\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/segmentio/textio\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/time\"\n)\n\n// PrintDictionary pretty prints a fastly.Dictionary structure in verbose\n// format to a given io.Writer. Consumers can provide a prefix string which\n// will be used as a prefix to each line, useful for indentation.\nfunc PrintDictionary(out io.Writer, prefix string, d *fastly.Dictionary) {\n\tout = textio.NewPrefixWriter(out, prefix)\n\n\tfmt.Fprintf(out, \"ID: %s\\n\", fastly.ToValue(d.DictionaryID))\n\tfmt.Fprintf(out, \"Name: %s\\n\", fastly.ToValue(d.Name))\n\tfmt.Fprintf(out, \"Write Only: %t\\n\", fastly.ToValue(d.WriteOnly))\n\tfmt.Fprintf(out, \"Created (UTC): %s\\n\", d.CreatedAt.UTC().Format(time.Format))\n\tfmt.Fprintf(out, \"Last edited (UTC): %s\\n\", d.UpdatedAt.UTC().Format(time.Format))\n\tif d.DeletedAt != nil {\n\t\tfmt.Fprintf(out, \"Deleted (UTC): %s\\n\", d.DeletedAt.UTC().Format(time.Format))\n\t}\n}\n"
  },
  {
    "path": "pkg/text/dictionaryitem.go",
    "content": "package text\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/segmentio/textio\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/time\"\n)\n\n// PrintDictionaryItem pretty prints a fastly.DictionaryInfo structure in verbose\n// format to a given io.Writer. Consumers can provide a prefix string which\n// will be used as a prefix to each line, useful for indentation.\nfunc PrintDictionaryItem(out io.Writer, prefix string, d *fastly.DictionaryItem) {\n\tout = textio.NewPrefixWriter(out, prefix)\n\n\tfmt.Fprintf(out, \"Dictionary ID: %s\\n\", fastly.ToValue(d.DictionaryID))\n\tfmt.Fprintf(out, \"Item Key: %s\\n\", fastly.ToValue(d.ItemKey))\n\tfmt.Fprintf(out, \"Item Value: %s\\n\", fastly.ToValue(d.ItemValue))\n\tif d.CreatedAt != nil {\n\t\tfmt.Fprintf(out, \"Created (UTC): %s\\n\", d.CreatedAt.UTC().Format(time.Format))\n\t}\n\tif d.UpdatedAt != nil {\n\t\tfmt.Fprintf(out, \"Last edited (UTC): %s\\n\", d.UpdatedAt.UTC().Format(time.Format))\n\t}\n\tif d.DeletedAt != nil {\n\t\tfmt.Fprintf(out, \"Deleted (UTC): %s\\n\", d.DeletedAt.UTC().Format(time.Format))\n\t}\n}\n\n// PrintDictionaryItemKV pretty prints only the key/value pairs from a dictionary item.\nfunc PrintDictionaryItemKV(out io.Writer, prefix string, d *fastly.DictionaryItem) {\n\tout = textio.NewPrefixWriter(out, prefix)\n\tfmt.Fprintf(out, \"Item Key: %s\\n\", fastly.ToValue(d.ItemKey))\n\tfmt.Fprintf(out, \"Item Value: %s\\n\", fastly.ToValue(d.ItemValue))\n}\n"
  },
  {
    "path": "pkg/text/dictionaryitem_test.go",
    "content": "package text_test\n\nimport (\n\t\"bytes\"\n\t\"testing\"\n\n\t\"github.com/fastly/cli/pkg/testutil\"\n\t\"github.com/fastly/cli/pkg/text\"\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n)\n\nfunc TestPrintDictionaryItem(t *testing.T) {\n\tfor _, testcase := range []struct {\n\t\tname           string\n\t\tdictionaryItem *fastly.DictionaryItem\n\t\twantOutput     string\n\t}{\n\t\t{\n\t\t\tname:           \"base\",\n\t\t\tdictionaryItem: &fastly.DictionaryItem{},\n\t\t\twantOutput:     \"Dictionary ID: \\nItem Key: \\nItem Value: \\n\",\n\t\t},\n\t} {\n\t\tt.Run(testcase.name, func(t *testing.T) {\n\t\t\tvar buf bytes.Buffer\n\t\t\ttext.PrintDictionaryItem(&buf, \"\", testcase.dictionaryItem)\n\t\t\ttestutil.AssertString(t, testcase.wantOutput, buf.String())\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/text/doc.go",
    "content": "// Package text contains functions for handling the display of text.\npackage text\n"
  },
  {
    "path": "pkg/text/healthcheck.go",
    "content": "package text\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/segmentio/textio\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n)\n\n// PrintHealthCheck pretty prints a fastly.HealthCheck structure in verbose\n// format to a given io.Writer. Consumers can provide a prefix string which\n// will be used as a prefix to each line, useful for indentation.\nfunc PrintHealthCheck(out io.Writer, prefix string, h *fastly.HealthCheck) {\n\tout = textio.NewPrefixWriter(out, prefix)\n\n\tfmt.Fprintf(out, \"Name: %s\\n\", fastly.ToValue(h.Name))\n\tfmt.Fprintf(out, \"Comment: %s\\n\", fastly.ToValue(h.Comment))\n\tfmt.Fprintf(out, \"Method: %s\\n\", fastly.ToValue(h.Method))\n\tfmt.Fprintf(out, \"Host: %s\\n\", fastly.ToValue(h.Host))\n\tfmt.Fprintf(out, \"Path: %s\\n\", fastly.ToValue(h.Path))\n\tfmt.Fprintf(out, \"HTTP version: %s\\n\", fastly.ToValue(h.HTTPVersion))\n\tfmt.Fprintf(out, \"Timeout: %d\\n\", fastly.ToValue(h.Timeout))\n\tfmt.Fprintf(out, \"Check interval: %d\\n\", fastly.ToValue(h.CheckInterval))\n\tfmt.Fprintf(out, \"Expected response: %d\\n\", fastly.ToValue(h.ExpectedResponse))\n\tfmt.Fprintf(out, \"Window: %d\\n\", fastly.ToValue(h.Window))\n\tfmt.Fprintf(out, \"Threshold: %d\\n\", fastly.ToValue(h.Threshold))\n\tfmt.Fprintf(out, \"Initial: %d\\n\", fastly.ToValue(h.Initial))\n}\n"
  },
  {
    "path": "pkg/text/kvstore.go",
    "content": "package text\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/segmentio/textio\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/time\"\n)\n\n// PrintKVStore pretty prints a fastly.Dictionary structure in verbose\n// format to a given io.Writer. Consumers can provide a prefix string which\n// will be used as a prefix to each line, useful for indentation.\nfunc PrintKVStore(out io.Writer, prefix string, k *fastly.KVStore) {\n\tout = textio.NewPrefixWriter(out, prefix)\n\n\tfmt.Fprintf(out, \"\\nID: %s\\n\", k.StoreID)\n\tfmt.Fprintf(out, \"Name: %s\\n\", k.Name)\n\tfmt.Fprintf(out, \"Created (UTC): %s\\n\", k.CreatedAt.UTC().Format(time.Format))\n\tfmt.Fprintf(out, \"Last edited (UTC): %s\\n\", k.UpdatedAt.UTC().Format(time.Format))\n}\n\n// PrintKVStoreKeys pretty prints a list of kv store keys in verbose\n// format to a given io.Writer. Consumers can provide a prefix string which\n// will be used as a prefix to each line, useful for indentation.\nfunc PrintKVStoreKeys(out io.Writer, prefix string, keys []string) {\n\tout = textio.NewPrefixWriter(out, prefix)\n\n\tfor _, k := range keys {\n\t\tfmt.Fprintf(out, \"Key: %s\\n\", k)\n\t}\n}\n\n// PrintKVStoreKeyValue pretty prints a value from a kv store to a\n// given io.Writer. Consumers can provide a prefix string which will be used as\n// a prefix to each line, useful for indentation.\nfunc PrintKVStoreKeyValue(out io.Writer, prefix string, key, value string) {\n\tout = textio.NewPrefixWriter(out, prefix)\n\n\tfmt.Fprintf(out, \"Key: %s\\n\", key)\n\tfmt.Fprintf(out, \"Value: %q\\n\", value)\n}\n"
  },
  {
    "path": "pkg/text/lines.go",
    "content": "package text\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"sort\"\n)\n\n// Lines is the struct that is used by PrintLines.\ntype Lines map[string]any\n\n// PrintLines pretty prints a Lines struct with one item per line.\n// The map is sorted before printing and a newline is added at the beginning.\nfunc PrintLines(out io.Writer, lines Lines) {\n\tkeys := make([]string, 0, len(lines))\n\tfor k := range lines {\n\t\tkeys = append(keys, k)\n\t}\n\tsort.Strings(keys)\n\tfmt.Fprintf(out, \"\\n\")\n\tfor _, k := range keys {\n\t\tfmt.Fprintf(out, \"%s: %+v\\n\", k, lines[k])\n\t}\n}\n"
  },
  {
    "path": "pkg/text/lines_test.go",
    "content": "package text_test\n\nimport (\n\t\"bytes\"\n\t\"testing\"\n\n\t\"github.com/fastly/cli/pkg/testutil\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\nfunc TestPrintLines(t *testing.T) {\n\tfor _, testcase := range []struct {\n\t\tname       string\n\t\tmapItem    text.Lines\n\t\twantOutput string\n\t}{\n\t\t{\n\t\t\tname:       \"base\",\n\t\t\tmapItem:    text.Lines{\"item\": \"value\"},\n\t\t\twantOutput: \"\\nitem: value\\n\",\n\t\t},\n\t\t{\n\t\t\tname:       \"number\",\n\t\t\tmapItem:    text.Lines{\"number\": 2},\n\t\t\twantOutput: \"\\nnumber: 2\\n\",\n\t\t},\n\t\t{\n\t\t\tname:       \"sort\",\n\t\t\tmapItem:    text.Lines{\"b\": 2, \"a\": 1, \"c\": 3},\n\t\t\twantOutput: \"\\na: 1\\nb: 2\\nc: 3\\n\",\n\t\t},\n\t} {\n\t\tt.Run(testcase.name, func(t *testing.T) {\n\t\t\tvar buf bytes.Buffer\n\t\t\ttext.PrintLines(&buf, testcase.mapItem)\n\t\t\ttestutil.AssertString(t, testcase.wantOutput, buf.String())\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/text/list.go",
    "content": "package text\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"strings\"\n\n\t\"github.com/fastly/cli/pkg/time\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/lists\"\n)\n\n// PrintList displays an NGWAF list.\nfunc PrintList(out io.Writer, listToPrint *lists.List) {\n\tfmt.Fprintf(out, \"ID: %s\\n\", listToPrint.ListID)\n\tfmt.Fprintf(out, \"Name: %s\\n\", listToPrint.Name)\n\tfmt.Fprintf(out, \"Description: %s\\n\", listToPrint.Description)\n\tfmt.Fprintf(out, \"Type: %s\\n\", listToPrint.Type)\n\tfmt.Fprintf(out, \"Entries: %s\\n\", strings.Join(listToPrint.Entries, \", \"))\n\tfmt.Fprintf(out, \"Scope: %s\\n\", listToPrint.Scope.Type)\n\tfmt.Fprintf(out, \"Updated (UTC): %s\\n\", listToPrint.UpdatedAt.UTC().Format(time.Format))\n\tfmt.Fprintf(out, \"Created (UTC): %s\\n\", listToPrint.CreatedAt.UTC().Format(time.Format))\n}\n\n// PrintWorkspaceTbl displays workspaces in a table format.\nfunc PrintListTbl(out io.Writer, listsToPrint []lists.List) {\n\ttbl := NewTable(out)\n\ttbl.AddHeader(\"ID\", \"Name\", \"Description\", \"Type\", \"Scope\", \"Entries\", \"Updated At\", \"Created At\")\n\n\tif listsToPrint == nil {\n\t\ttbl.Print()\n\t\treturn\n\t}\n\n\tfor _, listToPrint := range listsToPrint {\n\t\ttbl.AddLine(\n\t\t\tlistToPrint.ListID,\n\t\t\tlistToPrint.Name,\n\t\t\tlistToPrint.Description,\n\t\t\tlistToPrint.Type,\n\t\t\tlistToPrint.Scope.Type,\n\t\t\tstrings.Join(listToPrint.Entries, \", \"),\n\t\t\tlistToPrint.UpdatedAt,\n\t\t\tlistToPrint.CreatedAt,\n\t\t)\n\t}\n\ttbl.Print()\n}\n"
  },
  {
    "path": "pkg/text/redaction.go",
    "content": "package text\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/workspaces/redactions\"\n)\n\n// PrintRedaction displays a single redaction.\nfunc PrintRedaction(out io.Writer, redactionToPrint *redactions.Redaction) {\n\tfmt.Fprintf(out, \"Field: %s\\n\", redactionToPrint.Field)\n\tfmt.Fprintf(out, \"ID: %s\\n\", redactionToPrint.RedactionID)\n\tfmt.Fprintf(out, \"Type: %s\\n\", redactionToPrint.Type)\n\tfmt.Fprintf(out, \"Created At: %s\\n\", redactionToPrint.CreatedAt)\n}\n\n// PrintRedactionTbl prints a table of redactions.\nfunc PrintRedactionTbl(out io.Writer, redactionsToPrint []redactions.Redaction) {\n\ttbl := NewTable(out)\n\ttbl.AddHeader(\"Field\", \"ID\", \"Type\", \"Created At\")\n\n\tif redactionsToPrint == nil {\n\t\ttbl.Print()\n\t\treturn\n\t}\n\n\tfor _, rd := range redactionsToPrint {\n\t\ttbl.AddLine(rd.Field, rd.RedactionID, rd.Type, rd.CreatedAt)\n\t}\n\ttbl.Print()\n}\n"
  },
  {
    "path": "pkg/text/resource.go",
    "content": "package text\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/segmentio/textio\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/time\"\n)\n\n// PrintResource pretty prints a fastly.Resource structure in verbose\n// format to a given io.Writer. Consumers can provide a prefix string which\n// will be used as a prefix to each line, useful for indentation.\nfunc PrintResource(out io.Writer, prefix string, r *fastly.Resource) {\n\tif r == nil {\n\t\treturn\n\t}\n\tout = textio.NewPrefixWriter(out, prefix)\n\n\tfmt.Fprintf(out, \"ID: %s\\n\", fastly.ToValue(r.LinkID))\n\tfmt.Fprintf(out, \"Name: %s\\n\", fastly.ToValue(r.Name))\n\tfmt.Fprintf(out, \"Service ID: %s\\n\", fastly.ToValue(r.ServiceID))\n\tfmt.Fprintf(out, \"Service Version: %d\\n\", fastly.ToValue(r.ServiceVersion))\n\tfmt.Fprintf(out, \"Resource ID: %s\\n\", fastly.ToValue(r.ResourceID))\n\tfmt.Fprintf(out, \"Resource Type: %s\\n\", fastly.ToValue(r.ResourceType))\n\n\tif r.CreatedAt != nil {\n\t\tfmt.Fprintf(out, \"Created (UTC): %s\\n\", r.CreatedAt.UTC().Format(time.Format))\n\t}\n\tif r.UpdatedAt != nil {\n\t\tfmt.Fprintf(out, \"Last edited (UTC): %s\\n\", r.UpdatedAt.UTC().Format(time.Format))\n\t}\n\tif r.DeletedAt != nil {\n\t\tfmt.Fprintf(out, \"Deleted (UTC): %s\\n\", r.DeletedAt.UTC().Format(time.Format))\n\t}\n}\n"
  },
  {
    "path": "pkg/text/rule.go",
    "content": "package text\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/time\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/rules\"\n)\n\n// PrintRule displays an NGWAF rule.\nfunc PrintRule(out io.Writer, ruleToPrint *rules.Rule) {\n\tfmt.Fprintf(out, \"ID: %s\\n\", ruleToPrint.RuleID)\n\tfmt.Fprintf(out, \"Action: %s\\n\", ruleToPrint.Actions[0].Type)\n\tfmt.Fprintf(out, \"Description: %s\\n\", ruleToPrint.Description)\n\tfmt.Fprintf(out, \"Enabled: %v\\n\", ruleToPrint.Enabled)\n\tfmt.Fprintf(out, \"Type: %s\\n\", ruleToPrint.Type)\n\tfmt.Fprintf(out, \"Scope: %s\\n\", ruleToPrint.Scope.Type)\n\tfmt.Fprintf(out, \"Updated (UTC): %s\\n\", ruleToPrint.UpdatedAt.UTC().Format(time.Format))\n\tfmt.Fprintf(out, \"Created (UTC): %s\\n\", ruleToPrint.CreatedAt.UTC().Format(time.Format))\n}\n\n// PrintRuleTbl displays rules in a table format.\nfunc PrintRuleTbl(out io.Writer, rulesToPrint []rules.Rule) {\n\ttbl := NewTable(out)\n\ttbl.AddHeader(\"ID\", \"Action\", \"Description\", \"Enabled\", \"Type\", \"Scope\", \"Updated At\", \"Created At\")\n\n\tif rulesToPrint == nil {\n\t\ttbl.Print()\n\t\treturn\n\t}\n\n\tfor _, ruleToPrint := range rulesToPrint {\n\t\ttbl.AddLine(\n\t\t\truleToPrint.RuleID,\n\t\t\truleToPrint.Actions[0].Type,\n\t\t\truleToPrint.Description,\n\t\t\truleToPrint.Enabled,\n\t\t\truleToPrint.Type,\n\t\t\truleToPrint.Scope.Type,\n\t\t\truleToPrint.UpdatedAt,\n\t\t\truleToPrint.CreatedAt,\n\t\t)\n\t}\n\ttbl.Print()\n}\n"
  },
  {
    "path": "pkg/text/sanitize.go",
    "content": "package text\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n)\n\n// SanitizeTerminalOutput escapes control characters from untrusted content\n// to prevent terminal injection attacks.\nfunc SanitizeTerminalOutput(s string) string {\n\tvar b strings.Builder\n\tb.Grow(len(s))\n\tfor _, r := range s {\n\t\tswitch {\n\t\tcase r == '\\t', r == '\\n', r == '\\r':\n\t\t\tb.WriteRune(r)\n\t\tcase r < 0x20 || r == 0x7F:\n\t\t\tfmt.Fprintf(&b, \"\\\\x%02x\", r)\n\t\tdefault:\n\t\t\tb.WriteRune(r)\n\t\t}\n\t}\n\treturn b.String()\n}\n"
  },
  {
    "path": "pkg/text/sanitize_test.go",
    "content": "package text\n\nimport \"testing\"\n\nfunc TestSanitizeTerminalOutput(t *testing.T) {\n\ttests := []struct {\n\t\tname     string\n\t\tinput    string\n\t\texpected string\n\t}{\n\t\t{\n\t\t\tname:     \"normal text unchanged\",\n\t\t\tinput:    \"Hello, World!\",\n\t\t\texpected: \"Hello, World!\",\n\t\t},\n\t\t{\n\t\t\tname:     \"preserves newlines and tabs\",\n\t\t\tinput:    \"line1\\nline2\\ttabbed\",\n\t\t\texpected: \"line1\\nline2\\ttabbed\",\n\t\t},\n\t\t{\n\t\t\tname:     \"escapes color codes\",\n\t\t\tinput:    \"\\x1b[31mRED\\x1b[0m\",\n\t\t\texpected: \"\\\\x1b[31mRED\\\\x1b[0m\",\n\t\t},\n\t\t{\n\t\t\tname:     \"escapes bold and other SGR codes\",\n\t\t\tinput:    \"\\x1b[1mbold\\x1b[0m \\x1b[4munderline\\x1b[0m\",\n\t\t\texpected: \"\\\\x1b[1mbold\\\\x1b[0m \\\\x1b[4munderline\\\\x1b[0m\",\n\t\t},\n\t\t{\n\t\t\tname:     \"escapes cursor movement\",\n\t\t\tinput:    \"before\\x1b[2Aafter\",\n\t\t\texpected: \"before\\\\x1b[2Aafter\",\n\t\t},\n\t\t{\n\t\t\tname:     \"escapes screen clear\",\n\t\t\tinput:    \"\\x1b[2Jcleared\",\n\t\t\texpected: \"\\\\x1b[2Jcleared\",\n\t\t},\n\t\t{\n\t\t\tname:     \"escapes window title manipulation (OSC)\",\n\t\t\tinput:    \"\\x1b]0;malicious title\\x07content\",\n\t\t\texpected: \"\\\\x1b]0;malicious title\\\\x07content\",\n\t\t},\n\t\t{\n\t\t\tname:     \"escapes multiple escape sequences\",\n\t\t\tinput:    \"\\x1b[31m\\x1b[1mred bold\\x1b[0m normal \\x1b[32mgreen\\x1b[0m\",\n\t\t\texpected: \"\\\\x1b[31m\\\\x1b[1mred bold\\\\x1b[0m normal \\\\x1b[32mgreen\\\\x1b[0m\",\n\t\t},\n\t\t{\n\t\t\tname:     \"escapes VCL content with escape sequences\",\n\t\t\tinput:    \"sub vcl_recv { # \\x1b[31mRED\\x1b[0m }\",\n\t\t\texpected: \"sub vcl_recv { # \\\\x1b[31mRED\\\\x1b[0m }\",\n\t\t},\n\t\t{\n\t\t\tname:     \"empty string unchanged\",\n\t\t\tinput:    \"\",\n\t\t\texpected: \"\",\n\t\t},\n\t\t{\n\t\t\tname:     \"escapes cursor position codes\",\n\t\t\tinput:    \"\\x1b[10;20Htext at position\",\n\t\t\texpected: \"\\\\x1b[10;20Htext at position\",\n\t\t},\n\t\t{\n\t\t\tname:     \"escapes erase codes\",\n\t\t\tinput:    \"\\x1b[Kerase line\\x1b[Jclear below\",\n\t\t\texpected: \"\\\\x1b[Kerase line\\\\x1b[Jclear below\",\n\t\t},\n\t\t{\n\t\t\tname:     \"escapes standalone BEL\",\n\t\t\tinput:    \"before\\x07after\",\n\t\t\texpected: \"before\\\\x07after\",\n\t\t},\n\t\t{\n\t\t\tname:     \"escapes backspace\",\n\t\t\tinput:    \"secret\\x08visible\",\n\t\t\texpected: \"secret\\\\x08visible\",\n\t\t},\n\t\t{\n\t\t\tname:     \"escapes NUL character\",\n\t\t\tinput:    \"before\\x00after\",\n\t\t\texpected: \"before\\\\x00after\",\n\t\t},\n\t\t{\n\t\t\tname:     \"escapes form feed\",\n\t\t\tinput:    \"page1\\x0cpage2\",\n\t\t\texpected: \"page1\\\\x0cpage2\",\n\t\t},\n\t\t{\n\t\t\tname:     \"escapes vertical tab\",\n\t\t\tinput:    \"line1\\x0bline2\",\n\t\t\texpected: \"line1\\\\x0bline2\",\n\t\t},\n\t\t{\n\t\t\tname:     \"escapes DEL character\",\n\t\t\tinput:    \"before\\x7fafter\",\n\t\t\texpected: \"before\\\\x7fafter\",\n\t\t},\n\t\t{\n\t\t\tname:     \"preserves tab newline carriage return\",\n\t\t\tinput:    \"col1\\tcol2\\nline2\\r\\nline3\",\n\t\t\texpected: \"col1\\tcol2\\nline2\\r\\nline3\",\n\t\t},\n\t\t{\n\t\t\tname:     \"escapes mixed control characters\",\n\t\t\tinput:    \"\\x00\\x07\\x08text\\x0b\\x0c\\x1a\\x7f\",\n\t\t\texpected: \"\\\\x00\\\\x07\\\\x08text\\\\x0b\\\\x0c\\\\x1a\\\\x7f\",\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tresult := SanitizeTerminalOutput(tt.input)\n\t\t\tif result != tt.expected {\n\t\t\t\tt.Errorf(\"SanitizeTerminalOutput(%q) = %q, want %q\", tt.input, result, tt.expected)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/text/secretstore.go",
    "content": "package text\n\nimport (\n\t\"encoding/hex\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/segmentio/textio\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n)\n\n// PrintSecretStoresTbl displays store data in a table format.\nfunc PrintSecretStoresTbl(out io.Writer, stores []fastly.SecretStore) {\n\ttbl := NewTable(out)\n\ttbl.AddHeader(\"Name\", \"ID\")\n\n\tfor _, store := range stores {\n\t\ttbl.AddLine(store.Name, store.StoreID)\n\t}\n\ttbl.Print()\n}\n\n// PrintSecretsTbl displays secrets data in a table format.\nfunc PrintSecretsTbl(out io.Writer, secrets *fastly.Secrets) {\n\ttbl := NewTable(out)\n\ttbl.AddHeader(\"Name\", \"Digest\")\n\n\tif secrets == nil {\n\t\ttbl.Print()\n\t\treturn\n\t}\n\n\tfor _, s := range secrets.Data {\n\t\t// avoid gosec loop aliasing check :/\n\t\ts := s\n\t\ttbl.AddLine(s.Name, hex.EncodeToString(s.Digest))\n\t}\n\ttbl.Print()\n\n\tif secrets.Meta.NextCursor != \"\" {\n\t\tfmt.Fprintf(out, \"\\nNext cursor: %s\\n\", secrets.Meta.NextCursor)\n\t}\n}\n\n// PrintSecretStore displays store data.\nfunc PrintSecretStore(out io.Writer, prefix string, s *fastly.SecretStore) {\n\tout = textio.NewPrefixWriter(out, prefix)\n\n\tfmt.Fprintf(out, \"Name: %s\\n\", s.Name)\n\tfmt.Fprintf(out, \"ID: %s\\n\", s.StoreID)\n}\n\n// PrintSecret displays store data.\nfunc PrintSecret(out io.Writer, prefix string, s *fastly.Secret) {\n\tout = textio.NewPrefixWriter(out, prefix)\n\n\tfmt.Fprintf(out, \"Name: %s\\n\", s.Name)\n\tfmt.Fprintf(out, \"Digest: %s\\n\", hex.EncodeToString(s.Digest))\n}\n"
  },
  {
    "path": "pkg/text/service.go",
    "content": "package text\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"regexp\"\n\n\t\"github.com/segmentio/textio\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/time\"\n)\n\n// PrintService pretty prints a fastly.Service structure in verbose format\n// to a given io.Writer. Consumers can provide a prefix string which will\n// be used as a prefix to each line, useful for indentation.\nfunc PrintService(out io.Writer, prefix string, s *fastly.Service) {\n\tout = textio.NewPrefixWriter(out, prefix)\n\n\tif s.ServiceID != nil {\n\t\tfmt.Fprintf(out, \"ID: %s\\n\", fastly.ToValue(s.ServiceID))\n\t}\n\tif s.Name != nil {\n\t\tfmt.Fprintf(out, \"Name: %s\\n\", fastly.ToValue(s.Name))\n\t}\n\tif s.Type != nil {\n\t\tfmt.Fprintf(out, \"Type: %s\\n\", fastly.ToValue(s.Type))\n\t}\n\tif s.Comment != nil {\n\t\tfmt.Fprintf(out, \"Comment: %s\\n\", fastly.ToValue(s.Comment))\n\t}\n\tif s.CustomerID != nil {\n\t\tfmt.Fprintf(out, \"Customer ID: %s\\n\", fastly.ToValue(s.CustomerID))\n\t}\n\tif s.CreatedAt != nil {\n\t\tfmt.Fprintf(out, \"Created (UTC): %s\\n\", s.CreatedAt.UTC().Format(time.Format))\n\t}\n\tif s.UpdatedAt != nil {\n\t\tfmt.Fprintf(out, \"Last edited (UTC): %s\\n\", s.UpdatedAt.UTC().Format(time.Format))\n\t}\n\tif s.DeletedAt != nil {\n\t\tfmt.Fprintf(out, \"Deleted (UTC): %s\\n\", s.DeletedAt.UTC().Format(time.Format))\n\t}\n\tif s.ActiveVersion != nil {\n\t\tfmt.Fprintf(out, \"Active version: %d\\n\", fastly.ToValue(s.ActiveVersion))\n\t}\n\tfmt.Fprintf(out, \"Versions: %d\\n\", len(s.Versions))\n\tfor j, version := range s.Versions {\n\t\tfmt.Fprintf(out, \"\\tVersion %d/%d\\n\", j+1, len(s.Versions))\n\t\tPrintVersion(out, \"\\t\\t\", version)\n\t}\n}\n\n// PrintVersion pretty prints a fastly.Version structure in verbose format to a\n// given io.Writer. Consumers can provide a prefix string which will be used\n// as a prefix to each line, useful for indentation.\nfunc PrintVersion(out io.Writer, indent string, v *fastly.Version) {\n\tout = textio.NewPrefixWriter(out, indent)\n\n\tif v.Number != nil {\n\t\tfmt.Fprintf(out, \"Number: %d\\n\", fastly.ToValue(v.Number))\n\t}\n\tif v.Comment != nil {\n\t\tfmt.Fprintf(out, \"Comment: %s\\n\", fastly.ToValue(v.Comment))\n\t}\n\tif v.ServiceID != nil {\n\t\tfmt.Fprintf(out, \"Service ID: %s\\n\", fastly.ToValue(v.ServiceID))\n\t}\n\tif v.Active != nil {\n\t\tfmt.Fprintf(out, \"Active: %v\\n\", fastly.ToValue(v.Active))\n\t}\n\tif v.Locked != nil {\n\t\tfmt.Fprintf(out, \"Locked: %v\\n\", fastly.ToValue(v.Locked))\n\t}\n\tif v.Deployed != nil {\n\t\tfmt.Fprintf(out, \"Deployed: %v\\n\", fastly.ToValue(v.Deployed))\n\t}\n\tif v.Staging != nil {\n\t\tfmt.Fprintf(out, \"Staged: %v\\n\", fastly.ToValue(v.Staging))\n\t}\n\tif v.Testing != nil {\n\t\tfmt.Fprintf(out, \"Testing: %v\\n\", fastly.ToValue(v.Testing))\n\t}\n\tif v.CreatedAt != nil {\n\t\tfmt.Fprintf(out, \"Created (UTC): %s\\n\", v.CreatedAt.UTC().Format(time.Format))\n\t}\n\tif v.UpdatedAt != nil {\n\t\tfmt.Fprintf(out, \"Last edited (UTC): %s\\n\", v.UpdatedAt.UTC().Format(time.Format))\n\t}\n\tif v.DeletedAt != nil {\n\t\tfmt.Fprintf(out, \"Deleted (UTC): %s\\n\", v.DeletedAt.UTC().Format(time.Format))\n\t}\n}\n\nvar fastlyIDRegEx = regexp.MustCompile(\"^[0-9a-zA-Z]{22}$\")\n\n// IsFastlyID determines if a string looks like a Fastly ID.\nfunc IsFastlyID(s string) bool {\n\treturn fastlyIDRegEx.Match([]byte(s))\n}\n"
  },
  {
    "path": "pkg/text/service_test.go",
    "content": "package text_test\n\nimport (\n\t\"bytes\"\n\t\"testing\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n\n\t\"github.com/fastly/cli/pkg/testutil\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\nfunc TestPrintService(t *testing.T) {\n\tfor _, testcase := range []struct {\n\t\tname       string\n\t\tprefix     string\n\t\tservice    *fastly.Service\n\t\twantOutput string\n\t}{\n\t\t{\n\t\t\tname:   \"without prefix\",\n\t\t\tprefix: \"\",\n\t\t\tservice: &fastly.Service{\n\t\t\t\tServiceID:     fastly.ToPointer(\"1\"),\n\t\t\t\tName:          fastly.ToPointer(\"2\"),\n\t\t\t\tType:          fastly.ToPointer(\"3\"),\n\t\t\t\tCustomerID:    fastly.ToPointer(\"4\"),\n\t\t\t\tActiveVersion: fastly.ToPointer(5),\n\t\t\t},\n\t\t\twantOutput: \"ID: 1\\nName: 2\\nType: 3\\nCustomer ID: 4\\nActive version: 5\\nVersions: 0\\n\",\n\t\t},\n\t\t{\n\t\t\tname:   \"with prefix\",\n\t\t\tprefix: \"\\t\",\n\t\t\tservice: &fastly.Service{\n\t\t\t\tServiceID:     fastly.ToPointer(\"1\"),\n\t\t\t\tName:          fastly.ToPointer(\"2\"),\n\t\t\t\tType:          fastly.ToPointer(\"3\"),\n\t\t\t\tCustomerID:    fastly.ToPointer(\"4\"),\n\t\t\t\tActiveVersion: fastly.ToPointer(5),\n\t\t\t},\n\t\t\twantOutput: \"\\tID: 1\\n\\tName: 2\\n\\tType: 3\\n\\tCustomer ID: 4\\n\\tActive version: 5\\n\\tVersions: 0\\n\",\n\t\t},\n\t} {\n\t\tt.Run(testcase.name, func(t *testing.T) {\n\t\t\tvar buf bytes.Buffer\n\t\t\ttext.PrintService(&buf, testcase.prefix, testcase.service)\n\t\t\ttestutil.AssertString(t, testcase.wantOutput, buf.String())\n\t\t})\n\t}\n}\n\nfunc TestPrintVersion(t *testing.T) {\n\tfor _, testcase := range []struct {\n\t\tname       string\n\t\tprefix     string\n\t\tversion    *fastly.Version\n\t\twantOutput string\n\t}{\n\t\t{\n\t\t\tname:   \"without prefix\",\n\t\t\tprefix: \"\",\n\t\t\tversion: &fastly.Version{\n\t\t\t\tNumber:    fastly.ToPointer(1),\n\t\t\t\tServiceID: fastly.ToPointer(\"example\"),\n\t\t\t\tActive:    fastly.ToPointer(true),\n\t\t\t\tLocked:    fastly.ToPointer(true),\n\t\t\t\tDeployed:  fastly.ToPointer(true),\n\t\t\t\tStaging:   fastly.ToPointer(true),\n\t\t\t\tTesting:   fastly.ToPointer(false),\n\t\t\t},\n\t\t\twantOutput: \"Number: 1\\nService ID: example\\nActive: true\\nLocked: true\\nDeployed: true\\nStaged: true\\nTesting: false\\n\",\n\t\t},\n\t\t{\n\t\t\tname:   \"with\",\n\t\t\tprefix: \"\\t\",\n\t\t\tversion: &fastly.Version{\n\t\t\t\tNumber:    fastly.ToPointer(1),\n\t\t\t\tServiceID: fastly.ToPointer(\"example\"),\n\t\t\t\tActive:    fastly.ToPointer(true),\n\t\t\t\tLocked:    fastly.ToPointer(true),\n\t\t\t\tDeployed:  fastly.ToPointer(true),\n\t\t\t\tStaging:   fastly.ToPointer(true),\n\t\t\t\tTesting:   fastly.ToPointer(false),\n\t\t\t},\n\t\t\twantOutput: \"\\tNumber: 1\\n\\tService ID: example\\n\\tActive: true\\n\\tLocked: true\\n\\tDeployed: true\\n\\tStaged: true\\n\\tTesting: false\\n\",\n\t\t},\n\t} {\n\t\tt.Run(testcase.name, func(t *testing.T) {\n\t\t\tvar buf bytes.Buffer\n\t\t\ttext.PrintVersion(&buf, testcase.prefix, testcase.version)\n\t\t\ttestutil.AssertString(t, testcase.wantOutput, buf.String())\n\t\t})\n\t}\n}\n\nfunc TestIsFastlyID(t *testing.T) {\n\tfor _, testcase := range []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  bool\n\t}{\n\t\t{\n\t\t\tname:  \"looks like an ID\",\n\t\t\tinput: \"XkblwIHmR01sOnDHxusu6a\",\n\t\t\twant:  true,\n\t\t},\n\t\t{\n\t\t\tname:  \"looks like a URL\",\n\t\t\tinput: \"https://github.com/fastly/cli\",\n\t\t\twant:  false,\n\t\t},\n\t\t{\n\t\t\tname:  \"too short\",\n\t\t\tinput: \"Vkzj9WNseT1XN0\",\n\t\t\twant:  false,\n\t\t},\n\t\t{\n\t\t\tname:  \"too long\",\n\t\t\tinput: \"Vkzj9WNseT1XN0GqjYrgQGVkzj9WNseT1\",\n\t\t\twant:  false,\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid characters\",\n\t\t\tinput: \"GLql1:uzgoC-tEK7bdobt5\",\n\t\t\twant:  false,\n\t\t},\n\t} {\n\t\tt.Run(testcase.name, func(t *testing.T) {\n\t\t\ttestutil.AssertBool(t, testcase.want, text.IsFastlyID(testcase.input))\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/text/spinner.go",
    "content": "package text\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"time\"\n\n\t\"github.com/theckman/yacspin\"\n)\n\n// SpinnerErrWrapper is a generic error message the caller can interpolate their\n// own error into.\nconst SpinnerErrWrapper = \"failed to stop spinner (error: %w) when handling the error: %w\"\n\n// Spinner represents a terminal prompt status indicator.\ntype Spinner interface {\n\tStatus() yacspin.SpinnerStatus\n\tStart() error\n\tMessage(message string)\n\tStopFailMessage(message string)\n\tStopFail() error\n\tStopMessage(message string)\n\tStop() error\n\tProcess(msg string, fn SpinnerProcess) error\n}\n\n// SpinnerProcess is the logic to execute in between the spinner start/stop.\n//\n// NOTE: The `sp` SpinnerWrapper is passed in to handle more complex scenarios.\n// For example, the logic inside the SpinnerProcess might want to control the\n// Start/Stop mechanisms outside of the basic flow provided by `Process()`.\ntype SpinnerProcess func(sp *SpinnerWrapper) error\n\n// SpinnerWrapper implements the Spinner interface.\ntype SpinnerWrapper struct {\n\t*yacspin.Spinner\n\terr error\n}\n\n// Process starts/stops the spinner with `msg` and executes `fn` in between.\nfunc (sp *SpinnerWrapper) Process(msg string, fn SpinnerProcess) error {\n\terr := sp.Start()\n\tif err != nil {\n\t\treturn err\n\t}\n\tsp.Message(msg + \"...\")\n\n\terr = fn(sp)\n\tif err != nil {\n\t\tsp.StopFailMessage(msg)\n\t\tspinErr := sp.StopFail()\n\t\tif spinErr != nil {\n\t\t\treturn fmt.Errorf(\"failed to stop spinner (error: %w) when handling the error: %w\", spinErr, err)\n\t\t}\n\t\treturn err\n\t}\n\n\tsp.StopMessage(msg)\n\treturn sp.Stop()\n}\n\n// NewSpinner returns a new instance of a terminal prompt spinner.\nfunc NewSpinner(out io.Writer) (Spinner, error) {\n\tspinner, err := yacspin.New(yacspin.Config{\n\t\tCharSet:           yacspin.CharSets[9],\n\t\tFrequency:         100 * time.Millisecond,\n\t\tStopCharacter:     \"✓\",\n\t\tStopColors:        []string{\"fgGreen\"},\n\t\tStopFailCharacter: \"✗\",\n\t\tStopFailColors:    []string{\"fgRed\"},\n\t\tSuffix:            \" \",\n\t\tWriter:            out,\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &SpinnerWrapper{\n\t\tSpinner: spinner,\n\t\terr:     nil,\n\t}, nil\n}\n"
  },
  {
    "path": "pkg/text/stats.go",
    "content": "package text\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"maps\"\n\t\"slices\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly\"\n)\n\nfunc PrintUsageTbl(out io.Writer, data *fastly.RegionsUsage) {\n\ttbl := NewTable(out)\n\ttbl.AddHeader(\"REGION\", \"BANDWIDTH\", \"REQUESTS\", \"COMPUTE REQUESTS\")\n\tif data == nil {\n\t\ttbl.Print()\n\t\treturn\n\t}\n\tfor _, region := range slices.Sorted(maps.Keys(*data)) {\n\t\tu := (*data)[region]\n\t\tif u == nil {\n\t\t\tcontinue\n\t\t}\n\t\ttbl.AddLine(region, fastly.ToValue(u.Bandwidth), fastly.ToValue(u.Requests), fastly.ToValue(u.ComputeRequests))\n\t}\n\ttbl.Print()\n}\n\nfunc PrintUsageByServiceTbl(out io.Writer, data *fastly.ServicesByRegionsUsage) {\n\ttbl := NewTable(out)\n\ttbl.AddHeader(\"REGION\", \"SERVICE\", \"BANDWIDTH\", \"REQUESTS\", \"COMPUTE REQUESTS\")\n\tif data == nil {\n\t\ttbl.Print()\n\t\treturn\n\t}\n\tfor _, region := range slices.Sorted(maps.Keys(*data)) {\n\t\tservices := (*data)[region]\n\t\tif services == nil {\n\t\t\tcontinue\n\t\t}\n\t\tfor _, svcID := range slices.Sorted(maps.Keys(*services)) {\n\t\t\tu := (*services)[svcID]\n\t\t\tif u == nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\ttbl.AddLine(region, svcID, fastly.ToValue(u.Bandwidth), fastly.ToValue(u.Requests), fastly.ToValue(u.ComputeRequests))\n\t\t}\n\t}\n\ttbl.Print()\n}\n\nfunc PrintDomainInspectorTbl(out io.Writer, resp *fastly.DomainInspector) {\n\tif resp.Meta != nil {\n\t\tif resp.Meta.Start != nil {\n\t\t\tfmt.Fprintf(out, \"Start: %s\\n\", *resp.Meta.Start)\n\t\t}\n\t\tif resp.Meta.End != nil {\n\t\t\tfmt.Fprintf(out, \"End: %s\\n\", *resp.Meta.End)\n\t\t}\n\t\tfmt.Fprintln(out, \"---\")\n\t}\n\n\tdimKeys := domainDimensionKeys(resp.Data)\n\theader := make([]any, 0, len(dimKeys)+5)\n\tfor _, k := range dimKeys {\n\t\theader = append(header, strings.ToUpper(k))\n\t}\n\theader = append(header, \"TIMESTAMP\", \"REQUESTS\", \"BANDWIDTH\", \"EDGE REQUESTS\", \"EDGE HIT RATIO\")\n\n\ttbl := NewTable(out)\n\ttbl.AddHeader(header...)\n\tfor _, d := range resp.Data {\n\t\tfor _, v := range d.Values {\n\t\t\trow := make([]any, 0, len(dimKeys)+5)\n\t\t\tfor _, k := range dimKeys {\n\t\t\t\trow = append(row, fastly.ToValue(d.Dimensions[k]))\n\t\t\t}\n\t\t\tts := \"\"\n\t\t\tif v.Timestamp != nil {\n\t\t\t\tts = time.Unix(int64(*v.Timestamp), 0).UTC().String() //nolint:gosec\n\t\t\t}\n\t\t\trow = append(row, ts, fastly.ToValue(v.Requests), fastly.ToValue(v.Bandwidth), fastly.ToValue(v.EdgeRequests), fmt.Sprintf(\"%.4f\", fastly.ToValue(v.EdgeHitRatio)))\n\t\t\ttbl.AddLine(row...)\n\t\t}\n\t}\n\ttbl.Print()\n\n\tif resp.Meta != nil && resp.Meta.NextCursor != nil {\n\t\tfmt.Fprintf(out, \"Next cursor: %s\\n\", *resp.Meta.NextCursor)\n\t}\n}\n\nfunc PrintOriginInspectorTbl(out io.Writer, resp *fastly.OriginInspector) {\n\tif resp.Meta != nil {\n\t\tif resp.Meta.Start != nil {\n\t\t\tfmt.Fprintf(out, \"Start: %s\\n\", *resp.Meta.Start)\n\t\t}\n\t\tif resp.Meta.End != nil {\n\t\t\tfmt.Fprintf(out, \"End: %s\\n\", *resp.Meta.End)\n\t\t}\n\t\tfmt.Fprintln(out, \"---\")\n\t}\n\n\tdimKeys := originDimensionKeys(resp.Data)\n\theader := make([]any, 0, len(dimKeys)+5)\n\tfor _, k := range dimKeys {\n\t\theader = append(header, strings.ToUpper(k))\n\t}\n\theader = append(header, \"TIMESTAMP\", \"RESPONSES\", \"STATUS 2XX\", \"STATUS 4XX\", \"STATUS 5XX\")\n\n\ttbl := NewTable(out)\n\ttbl.AddHeader(header...)\n\tfor _, d := range resp.Data {\n\t\tfor _, v := range d.Values {\n\t\t\trow := make([]any, 0, len(dimKeys)+5)\n\t\t\tfor _, k := range dimKeys {\n\t\t\t\trow = append(row, d.Dimensions[k])\n\t\t\t}\n\t\t\tts := \"\"\n\t\t\tif v.Timestamp != nil {\n\t\t\t\tts = time.Unix(int64(*v.Timestamp), 0).UTC().String() //nolint:gosec\n\t\t\t}\n\t\t\trow = append(row, ts, fastly.ToValue(v.Responses), fastly.ToValue(v.Status2xx), fastly.ToValue(v.Status4xx), fastly.ToValue(v.Status5xx))\n\t\t\ttbl.AddLine(row...)\n\t\t}\n\t}\n\ttbl.Print()\n\n\tif resp.Meta != nil && resp.Meta.NextCursor != nil {\n\t\tfmt.Fprintf(out, \"Next cursor: %s\\n\", *resp.Meta.NextCursor)\n\t}\n}\n\nfunc domainDimensionKeys(data []*fastly.DomainData) []string {\n\tseen := make(map[string]struct{})\n\tfor _, d := range data {\n\t\tfor k := range d.Dimensions {\n\t\t\tseen[k] = struct{}{}\n\t\t}\n\t}\n\treturn slices.Sorted(maps.Keys(seen))\n}\n\nfunc originDimensionKeys(data []*fastly.OriginData) []string {\n\tseen := make(map[string]struct{})\n\tfor _, d := range data {\n\t\tfor k := range d.Dimensions {\n\t\t\tseen[k] = struct{}{}\n\t\t}\n\t}\n\treturn slices.Sorted(maps.Keys(seen))\n}\n"
  },
  {
    "path": "pkg/text/table.go",
    "content": "package text\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"strings\"\n\t\"text/tabwriter\"\n)\n\nvar (\n\tlineStyle   = Reset\n\theaderStyle = Bold\n)\n\n// Table wraps an instance of a tabwriter and provides helper methods to easily\n// create a table, add a header, add rows and print to the writer.\ntype Table struct {\n\twriter *tabwriter.Writer\n}\n\n// NewTable constructs a new Table.\nfunc NewTable(w io.Writer) *Table {\n\treturn &Table{\n\t\twriter: tabwriter.NewWriter(w, 0, 2, 2, ' ', 0),\n\t}\n}\n\n// AddLine writes a new row to the table.\nfunc (t *Table) AddLine(args ...any) {\n\tvar b strings.Builder\n\tfor i := range args {\n\t\t_, _ = b.WriteString(lineStyle(`%v`))\n\t\tif i+1 != len(args) {\n\t\t\t_, _ = b.WriteString(\"\\t\")\n\t\t}\n\t}\n\t_, _ = b.WriteString(\"\\n\")\n\tfmt.Fprintf(t.writer, b.String(), args...)\n}\n\n// AddHeader writes a table header line.\nfunc (t *Table) AddHeader(args ...any) {\n\tvar b strings.Builder\n\tfor i := range args {\n\t\t_, _ = b.WriteString(headerStyle(`%s`))\n\t\tif i+1 != len(args) {\n\t\t\t_, _ = b.WriteString(\"\\t\")\n\t\t}\n\t}\n\t_, _ = b.WriteString(\"\\n\")\n\tfmt.Fprintf(t.writer, b.String(), args...)\n}\n\n// Print writes the table to the writer.\nfunc (t *Table) Print() {\n\t_ = t.writer.Flush()\n}\n"
  },
  {
    "path": "pkg/text/tag.go",
    "content": "package text\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly/apisecurity/operations\"\n)\n\n// PrintOperationTag displays an operation tag.\nfunc PrintOperationTag(out io.Writer, tag *operations.OperationTag) {\n\tfmt.Fprintf(out, \"ID: %s\\n\", tag.ID)\n\tfmt.Fprintf(out, \"Name: %s\\n\", tag.Name)\n\tif tag.Description != \"\" {\n\t\tfmt.Fprintf(out, \"Description: %s\\n\", tag.Description)\n\t}\n\tif tag.Count > 0 {\n\t\tfmt.Fprintf(out, \"Operation Count: %d\\n\", tag.Count)\n\t}\n\tif tag.CreatedAt != \"\" {\n\t\tfmt.Fprintf(out, \"Created At: %s\\n\", tag.CreatedAt)\n\t}\n\tif tag.UpdatedAt != \"\" {\n\t\tfmt.Fprintf(out, \"Updated At: %s\\n\", tag.UpdatedAt)\n\t}\n}\n\n// PrintOperationTagsTbl displays operation tags in a table format.\nfunc PrintOperationTagsTbl(out io.Writer, tags []operations.OperationTag) {\n\ttbl := NewTable(out)\n\ttbl.AddHeader(\"ID\", \"Name\", \"Description\", \"Operations\", \"Created At\", \"Updated At\")\n\n\tif tags == nil {\n\t\ttbl.Print()\n\t\treturn\n\t}\n\n\tfor _, tag := range tags {\n\t\tdescription := tag.Description\n\t\tif description == \"\" {\n\t\t\tdescription = \"-\"\n\t\t}\n\t\ttbl.AddLine(tag.ID, tag.Name, description, fmt.Sprintf(\"%d\", tag.Count), tag.CreatedAt, tag.UpdatedAt)\n\t}\n\ttbl.Print()\n}\n"
  },
  {
    "path": "pkg/text/text.go",
    "content": "package text\n\nimport (\n\t\"bufio\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"strings\"\n\t\"syscall\"\n\n\t\"github.com/mitchellh/go-wordwrap\"\n\t\"golang.org/x/term\"\n\n\t\"github.com/fastly/cli/pkg/sync\"\n)\n\n// DefaultTextWidth is the width that should be passed to Wrap for most\n// general-purpose blocks of text intended for the user.\nconst DefaultTextWidth = 120\n\n// Wrap a string at word boundaries with a maximum line length of width. Each\n// newline-delimited line in the text is trimmed of whitespace before being\n// added to the block for wrapping, which means strings can be declared in the\n// source code with whatever leading indentation looks best in context. For\n// example,\n//\n//\tWrap(`\n//\t    Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do\n//\t    eiusmod tempor incididunt ut labore et dolore magna aliqua. Dolor\n//\t    sed viverra ipsum nunc aliquet bibendum enim. In massa tempor nec\n//\t    feugiat.\n//\t`, 40)\n//\n// Produces the output string\n//\n//\tLorem ipsum dolor sit amet, consectetur\n//\tadipiscing elit, sed do eiusmod tempor\n//\tincididunt ut labore et dolore magna\n//\taliqua. Dolor sed viverra ipsum nunc\n//\taliquet bibendum enim. In massa tempor\n//\tnec feugiat.\nfunc Wrap(text string, width uint) string {\n\tvar b strings.Builder\n\ts := bufio.NewScanner(strings.NewReader(text))\n\tfor s.Scan() {\n\t\tline := strings.TrimSpace(s.Text())\n\t\tif line == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\t_, _ = b.WriteString(line + \" \")\n\t}\n\treturn wordwrap.WrapString(strings.TrimSpace(b.String()), width)\n}\n\n// WrapIndent a string at word boundaries with a maximum line length of width\n// and indenting the lines by a specified number of spaces.\nfunc WrapIndent(s string, limit uint, indent uint) string {\n\tlimit -= indent\n\twrapped := wordwrap.WrapString(s, limit)\n\tvar result []string\n\tfor _, line := range strings.Split(wrapped, \"\\n\") {\n\t\t// gosec G115 complains about this uint->int cast, but we\n\t\t// know that it is safe here because the valid values\n\t\t// for 'indent' are too small to cause an overflow\n\t\t// #nosec G115\n\t\tresult = append(result, strings.Repeat(\" \", int(indent))+line)\n\t}\n\treturn strings.Join(result, \"\\n\")\n}\n\n// Indent writes the help text to the writer using WrapIndent with\n// DefaultTextWidth, suffixed by a newlines. It's intended to be used to provide\n// detailed information, context, or help to the user.\nfunc Indent(w io.Writer, indent uint, format string, args ...any) {\n\ttext := fmt.Sprintf(format, args...)\n\tfmt.Fprintf(w, \"%s\\n\", WrapIndent(text, DefaultTextWidth, indent))\n}\n\n// Output writes the help text to the writer using Wrap with DefaultTextWidth,\n// suffixed by a newline. It's intended to be used to provide detailed\n// information, context, or help to the user.\nfunc Output(w io.Writer, format string, args ...any) {\n\tprefix, suffix, txt := ParseBreaks(format)\n\tif suffix == 0 {\n\t\tsuffix++\n\t}\n\tfmt.Fprintf(w, strings.Repeat(\"\\n\", prefix)+Wrap(txt, DefaultTextWidth)+strings.Repeat(\"\\n\", suffix), args...)\n}\n\n// Input prints the prefix to the writer, and then reads a single line from the\n// reader, trimming writespace. The received line is passed to the validators,\n// and if any of them return a non-nil error, the error is printed to the\n// writer, and the input process happens again. Otherwise, the line is returned\n// to the caller.\n//\n// Input is intended to be used to take interactive input from the user.\nfunc Input(w io.Writer, prefix string, r io.Reader, validators ...func(string) error) (string, error) {\n\ts := bufio.NewScanner(r)\n\nouter:\n\tfor {\n\t\tfmt.Fprint(w, Bold(prefix))\n\t\tif ok := s.Scan(); !ok {\n\t\t\treturn \"\", s.Err()\n\t\t}\n\n\t\tline := strings.TrimSpace(s.Text())\n\t\tfor _, validate := range validators {\n\t\t\tif err := validate(line); err != nil {\n\t\t\t\tfmt.Fprintln(w, err.Error())\n\t\t\t\tcontinue outer\n\t\t\t}\n\t\t}\n\n\t\treturn line, nil\n\t}\n}\n\n// IsStdin returns true if r is standard input.\nfunc IsStdin(r io.Reader) bool {\n\tif f, ok := r.(*os.File); ok {\n\t\treturn f.Fd() == uintptr(syscall.Stdin)\n\t}\n\treturn false\n}\n\n// IsTTY returns true if fd is a terminal. When used in combination\n// with IsStdin, it can be used to determine whether standard input\n// is being piped data (i.e. IsStdin == true && IsTTY == false).\n// Provide STDOUT as a way to determine whether formatting and/or\n// prompting is acceptable output.\nfunc IsTTY(fd any) bool {\n\tif s, ok := fd.(*sync.Writer); ok {\n\t\t// STDOUT is commonly wrapped in a sync.Writer, so here\n\t\t// we unwrap it to gain access to the underlying Writer/STDOUT.\n\t\tfd = s.W\n\t}\n\tif f, ok := fd.(*os.File); ok {\n\t\treturn term.IsTerminal(int(f.Fd()))\n\t}\n\treturn false\n}\n\n// InputSecure is like Input but doesn't echo input back to the terminal,\n// if and only if r is os.Stdin.\nfunc InputSecure(w io.Writer, prefix string, r io.Reader, validators ...func(string) error) (string, error) {\n\tif !IsStdin(r) {\n\t\treturn Input(w, prefix, r, validators...)\n\t}\n\n\tread := func() (string, error) {\n\t\tfmt.Fprint(w, Bold(prefix))\n\t\t// IMPORTANT: Windows will fail if you remove the `int()` conversion.\n\t\t//\n\t\t// cannot use syscall.Stdin (variable of type syscall.Handle) as int value in argument to term.ReadPassword)\n\t\t//\n\t\t// This is because on *nix systems syscall.Stdin is already an int.\n\t\t// But on Windows it's a Handle type:\n\t\t// https://github.com/golang/go/blob/8d2eb290f83bca7d3b5154c6a7b3ac7546df5e8a/src/syscall/syscall_windows.go#L522\n\t\tp, err := term.ReadPassword(int(syscall.Stdin)) //nolint:unconvert\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t\treturn string(p), nil\n\t}\n\nouter:\n\tfor {\n\t\tline, err := read()\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t\tline = strings.TrimSpace(line)\n\n\t\tfor _, validate := range validators {\n\t\t\tif err := validate(line); err != nil {\n\t\t\t\tfmt.Fprintln(w, err.Error())\n\t\t\t\tcontinue outer\n\t\t\t}\n\t\t}\n\n\t\treturn line, nil\n\t}\n}\n\n// AskYesNo is similar to Input, but the line read is coerced to\n// one of true (yes and its variants) or false (no, its variants and\n// anything else) on success.\nfunc AskYesNo(w io.Writer, prompt string, r io.Reader) (bool, error) {\n\tanswer, err := Input(w, Prompt(prompt), r)\n\tif err != nil {\n\t\treturn false, fmt.Errorf(\"error reading input %w\", err)\n\t}\n\tanswer = strings.ToLower(answer)\n\tif answer == \"y\" || answer == \"yes\" {\n\t\treturn true, nil\n\t}\n\treturn false, nil\n}\n\n// Break simply writes a newline to the writer. It's intended to be used between\n// blocks of text that would otherwise be adjacent, a sort of semantic markup.\nfunc Break(w io.Writer) {\n\tfmt.Fprintln(w)\n}\n\n// BreakN writes n newlines to the writer. It's intended to be used between\n// blocks of text that would otherwise be adjacent, a sort of semantic markup.\nfunc BreakN(w io.Writer, n int) {\n\tif n == 0 {\n\t\treturn\n\t}\n\tfor i := 1; i <= n; i++ {\n\t\tfmt.Fprintln(w)\n\t}\n}\n\n// Deprecated is a wrapper for fmt.Fprintf with a bold red \"DEPRECATED: \" prefix.\n// Deprecation warnings are always written to stderr.\nfunc Deprecated(format string, args ...any) {\n\tprefix, suffix, txt := ParseBreaks(format)\n\tif suffix == 0 {\n\t\tsuffix++\n\t}\n\tfmt.Fprintf(os.Stderr, WrapString(BoldRed, \"DEPRECATED\", txt, prefix, suffix), args...)\n}\n\n// Error is a wrapper for fmt.Fprintf with a bold red \"ERROR: \" prefix.\nfunc Error(w io.Writer, format string, args ...any) {\n\tprefix, suffix, txt := ParseBreaks(format)\n\tif suffix == 0 {\n\t\tsuffix++\n\t}\n\tfmt.Fprintf(w, WrapString(BoldRed, \"ERROR\", txt, prefix, suffix), args...)\n}\n\n// Important is a wrapper for fmt.Fprintf with a bold yellow \"IMPORTANT: \" prefix.\nfunc Important(w io.Writer, format string, args ...any) {\n\tprefix, suffix, txt := ParseBreaks(format)\n\tif suffix == 0 {\n\t\tsuffix++\n\t}\n\tfmt.Fprintf(w, WrapString(BoldYellow, \"IMPORTANT\", txt, prefix, suffix), args...)\n}\n\n// Info is a wrapper for fmt.Fprintf with a bold \"INFO: \" prefix.\nfunc Info(w io.Writer, format string, args ...any) {\n\tprefix, suffix, txt := ParseBreaks(format)\n\tif suffix == 0 {\n\t\tsuffix++\n\t}\n\tfmt.Fprintf(w, WrapString(BoldCyan, \"INFO\", txt, prefix, suffix), args...)\n}\n\n// Success is a wrapper for fmt.Fprintf with a bold green \"SUCCESS: \" prefix.\nfunc Success(w io.Writer, format string, args ...any) {\n\tprefix, suffix, txt := ParseBreaks(format)\n\tif suffix == 0 {\n\t\tsuffix++\n\t}\n\tfmt.Fprintf(w, WrapString(BoldGreen, \"SUCCESS\", txt, prefix, suffix), args...)\n}\n\n// Warning is a wrapper for fmt.Fprintf with a bold yellow \"WARNING: \" prefix.\nfunc Warning(w io.Writer, format string, args ...any) {\n\tprefix, suffix, txt := ParseBreaks(format)\n\tif suffix == 0 {\n\t\tsuffix++\n\t}\n\tfmt.Fprintf(w, WrapString(BoldYellow, \"WARNING\", txt, prefix, suffix), args...)\n}\n\n// WrapString produces string with correct wrapping and prefix/suffix linebreaks.\nfunc WrapString(fn ColorFn, msg, txt string, prefix, suffix int) string {\n\tmsg = fmt.Sprintf(\"%s: \", msg)\n\treturn strings.Repeat(\"\\n\", prefix) + Wrap(fn(msg)+txt, DefaultTextWidth) + strings.Repeat(\"\\n\", suffix)\n}\n\n// Description formats the output of a description item. A description item\n// consists of a `intro` and a `description`. Emphasis is placed on the\n// `description` using Bold(). For example:\n//\n//\tTo compile the package, run:\n//\t    fastly compute build\nfunc Description(w io.Writer, intro, description string) {\n\tfmt.Fprintf(w, \"%s:\\n\\t%s\\n\\n\", intro, Bold(description))\n}\n\n// ParseBreaks returns the linebreak count at the start/end of the input.\n//\n// NOTE: Any line breaks inside the main text will be stripped.\nfunc ParseBreaks(input string) (prefix, suffix int, txt string) {\n\tvar (\n\t\tincrementSuffix bool\n\t\ttxts            []string\n\t)\n\tparts := strings.Split(input, \"\\n\")\n\tfor _, p := range parts {\n\t\tif p == \"\" && !incrementSuffix {\n\t\t\tprefix++\n\t\t\tcontinue\n\t\t}\n\t\tincrementSuffix = true\n\t\tif p == \"\" {\n\t\t\tsuffix++\n\t\t} else {\n\t\t\ttxts = append(txts, p)\n\t\t}\n\t}\n\treturn prefix, suffix, strings.Join(txts, \" \")\n}\n"
  },
  {
    "path": "pkg/text/text_test.go",
    "content": "package text_test\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"io\"\n\t\"os\"\n\t\"strconv\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/fastly/cli/pkg/testutil\"\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\nfunc TestInput(t *testing.T) {\n\tfor _, testcase := range []struct {\n\t\tname       string\n\t\tin         string\n\t\tprefix     string\n\t\tvalidators []func(string) error\n\t\twantOutput string\n\t\twantResult string\n\t}{\n\t\t{\n\t\t\tname:       \"empty\",\n\t\t\tin:         \"\\n\",\n\t\t\tprefix:     \"Press enter \",\n\t\t\twantOutput: \"Press enter \",\n\t\t\twantResult: \"\",\n\t\t},\n\t\t{\n\t\t\tname:       \"single letter\",\n\t\t\tin:         \"a\\n\",\n\t\t\tprefix:     \"> \",\n\t\t\twantOutput: \"> \",\n\t\t\twantResult: \"a\",\n\t\t},\n\t\t{\n\t\t\tname:       \"single letter with whitespace\",\n\t\t\tin:         \" a  \\n\",\n\t\t\tprefix:     \"> \",\n\t\t\twantOutput: \"> \",\n\t\t\twantResult: \"a\",\n\t\t},\n\t\t{\n\t\t\tname:   \"nonempty validator\",\n\t\t\tin:     \"\\n\\nFINE\\n\",\n\t\t\tprefix: \"Tell me something: \",\n\t\t\tvalidators: []func(string) error{\n\t\t\t\tfunc(s string) error {\n\t\t\t\t\tif s == \"\" {\n\t\t\t\t\t\treturn errors.New(\"nothing isn't something\")\n\t\t\t\t\t}\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t},\n\t\t\twantOutput: \"Tell me something: nothing isn't something\\nTell me something: nothing isn't something\\nTell me something: \",\n\t\t\twantResult: \"FINE\",\n\t\t},\n\t} {\n\t\tt.Run(testcase.name, func(t *testing.T) {\n\t\t\tvar buf bytes.Buffer\n\t\t\tresult, err := text.Input(&buf, testcase.prefix, strings.NewReader(testcase.in), testcase.validators...)\n\t\t\ttestutil.AssertNoError(t, err)\n\t\t\ttestutil.AssertString(t, testcase.wantOutput, buf.String())\n\t\t\ttestutil.AssertString(t, testcase.wantResult, result)\n\n\t\t\tbuf.Reset()\n\t\t\tresult, err = text.InputSecure(&buf, testcase.prefix, strings.NewReader(testcase.in), testcase.validators...)\n\t\t\ttestutil.AssertNoError(t, err)\n\t\t\ttestutil.AssertString(t, testcase.wantOutput, buf.String())\n\t\t\ttestutil.AssertString(t, testcase.wantResult, result)\n\t\t})\n\t}\n}\n\nfunc TestAskYesNo(t *testing.T) {\n\tfor _, testcase := range []struct {\n\t\tname       string\n\t\tin         string\n\t\twantResult bool\n\t}{\n\t\t{\n\t\t\tname:       \"empty\",\n\t\t\tin:         \"\\n\",\n\t\t\twantResult: false,\n\t\t},\n\t\t{\n\t\t\tname:       \"uppercase y\",\n\t\t\tin:         \"Y\\n\",\n\t\t\twantResult: true,\n\t\t},\n\t\t{\n\t\t\tname:       \"lowercase y\",\n\t\t\tin:         \"y\\n\",\n\t\t\twantResult: true,\n\t\t},\n\t\t{\n\t\t\tname:       \"mixed case yes\",\n\t\t\tin:         \"yEs\\n\",\n\t\t\twantResult: true,\n\t\t},\n\t\t{\n\t\t\tname:       \"mixed case no\",\n\t\t\tin:         \"nO\\n\",\n\t\t\twantResult: false,\n\t\t},\n\t\t{\n\t\t\tname:       \"anything else\",\n\t\t\tin:         \"whatever\\n\",\n\t\t\twantResult: false,\n\t\t},\n\t} {\n\t\tt.Run(testcase.name, func(t *testing.T) {\n\t\t\tvar buf bytes.Buffer\n\t\t\tresult, err := text.AskYesNo(&buf, \"\", strings.NewReader(testcase.in))\n\t\t\ttestutil.AssertNoError(t, err)\n\t\t\ttestutil.AssertBool(t, testcase.wantResult, result)\n\t\t})\n\t}\n}\n\nfunc TestPrefixes(t *testing.T) {\n\tfor _, testcase := range []struct {\n\t\tname   string\n\t\tf      func(io.Writer, string, ...any)\n\t\tformat string\n\t\targs   []any\n\t\twant   string\n\t}{\n\t\t{\n\t\t\tname:   \"Error\",\n\t\t\tf:      text.Error,\n\t\t\tformat: \"Test string %d.\",\n\t\t\targs:   []any{123},\n\t\t\twant:   \"ERROR: Test string 123.\\n\",\n\t\t},\n\t\t{\n\t\t\tname:   \"Important\",\n\t\t\tf:      text.Important,\n\t\t\tformat: \"Test string %d.\",\n\t\t\targs:   []any{123},\n\t\t\twant:   \"IMPORTANT: Test string 123.\\n\",\n\t\t},\n\t\t{\n\t\t\tname:   \"Info\",\n\t\t\tf:      text.Info,\n\t\t\tformat: \"Test string %d.\",\n\t\t\targs:   []any{123},\n\t\t\twant:   \"INFO: Test string 123.\\n\",\n\t\t},\n\t\t{\n\t\t\tname:   \"Success\",\n\t\t\tf:      text.Success,\n\t\t\tformat: \"%s %q %d.\",\n\t\t\targs:   []any{\"Good\", \"job\", 99},\n\t\t\twant:   \"SUCCESS: Good \\\"job\\\" 99.\\n\",\n\t\t},\n\t\t{\n\t\t\tname:   \"Warning\",\n\t\t\tf:      text.Warning,\n\t\t\tformat: \"\\nTest string %d.\\n\\n\", // notice inline line breaks override the default single suffix line break\n\t\t\targs:   []any{123},\n\t\t\twant:   \"\\nWARNING: Test string 123.\\n\\n\",\n\t\t},\n\t\t{\n\t\t\tname:   \"Info with irregular line breaks and tabs placement\",\n\t\t\tf:      text.Info,\n\t\t\tformat: \"\\n\\nTest string\\n\\t%s\",\n\t\t\targs:   []any{\"anything\"},\n\t\t\twant:   \"\\n\\nINFO: Test string \\tanything\\n\",\n\t\t},\n\t} {\n\t\tt.Run(testcase.name, func(t *testing.T) {\n\t\t\tvar buf bytes.Buffer\n\t\t\ttestcase.f(&buf, testcase.format, testcase.args...)\n\t\t\tif want, have := testcase.want, buf.String(); want != have {\n\t\t\t\tt.Error(cmp.Diff(want, have))\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestWrap(t *testing.T) {\n\tfor i, testcase := range []struct {\n\t\ttext, want string\n\t\tlimit      uint\n\t}{\n\t\t{\n\t\t\ttext:  \"Example text goes here.\",\n\t\t\tlimit: 2,\n\t\t\twant:  \"Example\\ntext\\ngoes\\nhere.\", // notice it won't split individual words\n\t\t},\n\t\t{\n\t\t\ttext:  \"Example text goes here.\",\n\t\t\tlimit: 12,\n\t\t\twant:  \"Example text\\ngoes here.\",\n\t\t},\n\t\t{\n\t\t\ttext:  \"Example text goes here.\",\n\t\t\tlimit: 100,\n\t\t\twant:  \"Example text goes here.\",\n\t\t},\n\t} {\n\t\tt.Run(strconv.Itoa(i), func(t *testing.T) {\n\t\t\toutput := text.Wrap(testcase.text, testcase.limit)\n\t\t\tif want, have := testcase.want, output; want != have {\n\t\t\t\tt.Error(cmp.Diff(want, have))\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestWrapIndent(t *testing.T) {\n\tfor i, testcase := range []struct {\n\t\ttext, want    string\n\t\tlimit, indent uint // internally limit subtracts the indent\n\t}{\n\t\t{\n\t\t\ttext:   \"Example text goes here.\",\n\t\t\tlimit:  2,\n\t\t\tindent: 2,\n\t\t\twant:   \"  Example text goes here.\", // indent causes limit to become zero so we effectively just get an indent.\n\t\t},\n\t\t{\n\t\t\ttext:   \"Example text goes here.\",\n\t\t\tlimit:  20,\n\t\t\tindent: 4,\n\t\t\twant:   \"    Example text\\n    goes here.\",\n\t\t},\n\t\t{\n\t\t\ttext:   \"Example text goes here.\",\n\t\t\tlimit:  100,\n\t\t\tindent: 6,\n\t\t\twant:   \"      Example text goes here.\",\n\t\t},\n\t} {\n\t\tt.Run(strconv.Itoa(i), func(t *testing.T) {\n\t\t\toutput := text.WrapIndent(testcase.text, testcase.limit, testcase.indent)\n\t\t\tif want, have := testcase.want, output; want != have {\n\t\t\t\tt.Error(cmp.Diff(want, have))\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestParseBreaks(t *testing.T) {\n\tfor _, testcase := range []struct {\n\t\tname   string\n\t\tin     string\n\t\tprefix int\n\t\tsuffix int\n\t\ttxt    string\n\t}{\n\t\t{\n\t\t\tname:   \"no line breaks\",\n\t\t\tin:     \"example\",\n\t\t\tprefix: 0,\n\t\t\tsuffix: 0,\n\t\t\ttxt:    \"example\",\n\t\t},\n\t\t{\n\t\t\tname:   \"starting line breaks\",\n\t\t\tin:     \"\\n\\n\\nexample\",\n\t\t\tprefix: 3,\n\t\t\tsuffix: 0,\n\t\t\ttxt:    \"example\",\n\t\t},\n\t\t{\n\t\t\tname:   \"ending line breaks\",\n\t\t\tin:     \"example\\n\\n\\n\",\n\t\t\tprefix: 0,\n\t\t\tsuffix: 3,\n\t\t\ttxt:    \"example\",\n\t\t},\n\t\t{\n\t\t\tname:   \"both ends line breaks\",\n\t\t\tin:     \"\\n\\nexample\\n\\n\\n\",\n\t\t\tprefix: 2,\n\t\t\tsuffix: 3,\n\t\t\ttxt:    \"example\",\n\t\t},\n\t\t{\n\t\t\tname:   \"line breaks in the main text\",\n\t\t\tin:     \"\\n\\nexample message with\\na line break inside\\n\\n\\n\",\n\t\t\tprefix: 2,\n\t\t\tsuffix: 3,\n\t\t\ttxt:    \"example message with a line break inside\",\n\t\t},\n\t} {\n\t\tt.Run(testcase.name, func(t *testing.T) {\n\t\t\tprefix, suffix, txt := text.ParseBreaks(testcase.in)\n\t\t\tif prefix != testcase.prefix {\n\t\t\t\tt.Errorf(\"want: %d, have: %d\", testcase.prefix, prefix)\n\t\t\t}\n\t\t\tif suffix != testcase.suffix {\n\t\t\t\tt.Errorf(\"want: %d, have: %d\", testcase.suffix, suffix)\n\t\t\t}\n\t\t\tif txt != testcase.txt {\n\t\t\t\tt.Errorf(\"want: %s, have: %s\", testcase.txt, txt)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestDeprecated(t *testing.T) {\n\t// Save original stderr and create pipe to capture output\n\torigStderr := os.Stderr\n\tr, w, _ := os.Pipe()\n\tos.Stderr = w\n\n\t// Call Deprecated\n\ttext.Deprecated(\"Test warning message %d.\", 123)\n\n\t// Restore stderr and close writer\n\tos.Stderr = origStderr\n\tw.Close()\n\n\t// Read captured output\n\tvar buf bytes.Buffer\n\tif _, err := io.Copy(&buf, r); err != nil {\n\t\tt.Fatalf(\"failed to read from pipe: %v\", err)\n\t}\n\toutput := buf.String()\n\n\t// Verify output contains expected text\n\twant := \"DEPRECATED: Test warning message 123.\\n\"\n\tif output != want {\n\t\tt.Errorf(\"Deprecated output mismatch:\\ngot:  %q\\nwant: %q\", output, want)\n\t}\n}\n"
  },
  {
    "path": "pkg/text/threshold.go",
    "content": "package text\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"time\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/workspaces/thresholds\"\n)\n\n// PrintThreshold displays a single threshold.\nfunc PrintThreshold(out io.Writer, thresholdToPrint *thresholds.Threshold) {\n\tfmt.Fprintf(out, \"Signal: %s\\n\", thresholdToPrint.Signal)\n\tfmt.Fprintf(out, \"Name: %s\\n\", thresholdToPrint.Name)\n\tfmt.Fprintf(out, \"Action: %s\\n\", thresholdToPrint.Action)\n\tfmt.Fprintf(out, \"Do Not Notify: %t\\n\", thresholdToPrint.DontNotify)\n\tfmt.Fprintf(out, \"Duration: %d\\n\", thresholdToPrint.Duration)\n\tfmt.Fprintf(out, \"Enabled: %t\\n\", thresholdToPrint.Enabled)\n\tfmt.Fprintf(out, \"Interval: %d\\n\", thresholdToPrint.Interval)\n\tfmt.Fprintf(out, \"Limit: %d\\n\", thresholdToPrint.Limit)\n}\n\n// PrintThresholdTbl prints a table of thresholds.\nfunc PrintThresholdTbl(out io.Writer, thresholdsToPrint []thresholds.Threshold) {\n\ttbl := NewTable(out)\n\ttbl.AddHeader(\"Signal\", \"Name\", \"ID\", \"Action\", \"Enabled\", \"Do Not Notify\", \"Limit\", \"Interval\", \"Duration\", \"Created At\")\n\n\tif thresholdsToPrint == nil {\n\t\ttbl.Print()\n\t\treturn\n\t}\n\n\tfor _, ts := range thresholdsToPrint {\n\t\ttbl.AddLine(\n\t\t\tts.Signal,\n\t\t\tts.Name,\n\t\t\tts.ThresholdID,\n\t\t\tts.Action,\n\t\t\tts.Enabled,\n\t\t\tts.DontNotify,\n\t\t\tts.Limit,\n\t\t\tts.Interval,\n\t\t\tts.Duration,\n\t\t\tts.CreatedAt.UTC().Format(time.RFC3339),\n\t\t)\n\t}\n\ttbl.Print()\n}\n"
  },
  {
    "path": "pkg/text/virtualpatch.go",
    "content": "package text\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/workspaces/virtualpatches\"\n)\n\n// PrintVirtualPatch displays a single virtual patch.\nfunc PrintVirtualPatch(out io.Writer, virtualPatchToPrint *virtualpatches.VirtualPatch) {\n\tfmt.Fprintf(out, \"ID: %s\\n\", virtualPatchToPrint.ID)\n\tfmt.Fprintf(out, \"Description: %s\\n\", virtualPatchToPrint.Description)\n\tfmt.Fprintf(out, \"Enabled: %t\\n\", virtualPatchToPrint.Enabled)\n\tfmt.Fprintf(out, \"Mode: %s\\n\", virtualPatchToPrint.Mode)\n}\n\n// PrintVirtualPatchTbl prints a table of virtual patches.\nfunc PrintVirtualPatchTbl(out io.Writer, virtualPatchesToPrint []virtualpatches.VirtualPatch) {\n\ttbl := NewTable(out)\n\ttbl.AddHeader(\"ID\", \"Description\", \"Enabled\", \"Mode\")\n\n\tif virtualPatchesToPrint == nil {\n\t\ttbl.Print()\n\t\treturn\n\t}\n\n\tfor _, vp := range virtualPatchesToPrint {\n\t\ttbl.AddLine(vp.ID, vp.Description, vp.Enabled, vp.Mode)\n\t}\n\ttbl.Print()\n}\n"
  },
  {
    "path": "pkg/text/workspace.go",
    "content": "package text\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"strings\"\n\n\t\"github.com/fastly/cli/pkg/time\"\n\t\"github.com/fastly/go-fastly/v15/fastly/ngwaf/v1/workspaces\"\n)\n\n// PrintWorkspace displays a workspace.\nfunc PrintWorkspace(out io.Writer, workspaceToPrint *workspaces.Workspace) {\n\tfmt.Fprintf(out, \"ID: %s\\n\", workspaceToPrint.WorkspaceID)\n\tfmt.Fprintf(out, \"Name: %s\\n\", workspaceToPrint.Name)\n\tfmt.Fprintf(out, \"Description: %s\\n\", workspaceToPrint.Description)\n\tfmt.Fprintf(out, \"Mode: %s\\n\", workspaceToPrint.Mode)\n\tfmt.Fprint(out, \"Attack Signal Thresholds:\\n\")\n\tfmt.Fprintf(out, \"\tImmediate: %t\\n\", workspaceToPrint.AttackSignalThresholds.Immediate)\n\tfmt.Fprintf(out, \"\tOne Minute: %d\\n\", workspaceToPrint.AttackSignalThresholds.OneMinute)\n\tfmt.Fprintf(out, \"\tTen Minutes: %d\\n\", workspaceToPrint.AttackSignalThresholds.TenMinutes)\n\tfmt.Fprintf(out, \"\tOne Hour: %d\\n\", workspaceToPrint.AttackSignalThresholds.OneHour)\n\tif len(workspaceToPrint.ClientIPHeaders) != 0 {\n\t\tfmt.Fprintf(out, \"Client IP Headers: %s\\n\", strings.Join(workspaceToPrint.ClientIPHeaders, \", \"))\n\t}\n\tif workspaceToPrint.DefaultBlockingResponseCode > 0 {\n\t\tfmt.Fprintf(out, \"Default Blocking Response Code: %d\\n\", workspaceToPrint.DefaultBlockingResponseCode)\n\t}\n\tif workspaceToPrint.DefaultRedirectURL != \"\" {\n\t\tfmt.Fprintf(out, \"Default Redirect URL: %s\\n\", workspaceToPrint.DefaultRedirectURL)\n\t}\n\tif workspaceToPrint.IPAnonymization != \"\" {\n\t\tfmt.Fprintf(out, \"IP Anonymization: %s\\n\", workspaceToPrint.IPAnonymization)\n\t}\n\tfmt.Fprintf(out, \"Updated (UTC): %s\\n\", workspaceToPrint.UpdatedAt.UTC().Format(time.Format))\n}\n\n// PrintWorkspaceTbl displays workspaces in a table format.\nfunc PrintWorkspaceTbl(out io.Writer, workspacesToPrint []workspaces.Workspace) {\n\ttbl := NewTable(out)\n\ttbl.AddHeader(\"ID\", \"Name\", \"Description\", \"Mode\", \"Created At\")\n\n\tif workspacesToPrint == nil {\n\t\ttbl.Print()\n\t\treturn\n\t}\n\n\tfor _, workspaceToPrint := range workspacesToPrint {\n\t\ttbl.AddLine(workspaceToPrint.WorkspaceID, workspaceToPrint.Name, workspaceToPrint.Description, workspaceToPrint.Mode, workspaceToPrint.CreatedAt)\n\t}\n\ttbl.Print()\n}\n"
  },
  {
    "path": "pkg/threadsafe/doc.go",
    "content": "// Package threadsafe contains functions and objects for handling thread-safe\n// code.\npackage threadsafe\n"
  },
  {
    "path": "pkg/threadsafe/threadsafe.go",
    "content": "package threadsafe\n\nimport (\n\t\"bytes\"\n\t\"sync\"\n)\n\n// Buffer is a thread-safe bytes.Buffer instance.\ntype Buffer struct {\n\tb bytes.Buffer\n\tm sync.Mutex\n}\n\n// Read reads the next len(p) bytes from the buffer.\nfunc (b *Buffer) Read(p []byte) (n int, err error) {\n\tb.m.Lock()\n\tdefer b.m.Unlock()\n\treturn b.b.Read(p)\n}\n\n// Write appends the contents of p to the buffer.\nfunc (b *Buffer) Write(p []byte) (n int, err error) {\n\tb.m.Lock()\n\tdefer b.m.Unlock()\n\treturn b.b.Write(p)\n}\n\n// String returns the contents of the unread portion of the buffer\n// as a string.\nfunc (b *Buffer) String() string {\n\tb.m.Lock()\n\tdefer b.m.Unlock()\n\treturn b.b.String()\n}\n\n// Len returns the number of bytes of the unread portion of the buffer.\nfunc (b *Buffer) Len() int {\n\tb.m.Lock()\n\tdefer b.m.Unlock()\n\treturn b.b.Len()\n}\n"
  },
  {
    "path": "pkg/time/doc.go",
    "content": "// Package time contains helper abstractions for working with time formats.\npackage time\n"
  },
  {
    "path": "pkg/time/time.go",
    "content": "package time\n\n// Format is a format string for time.Format that reflects what the Fastly web\n// UI uses.\nconst Format = \"2006-01-02 15:04\"\n"
  },
  {
    "path": "pkg/undo/doc.go",
    "content": "// Package undo contains abstractions for working with a stack of state changes.\npackage undo\n"
  },
  {
    "path": "pkg/undo/undo.go",
    "content": "package undo\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/fastly/cli/pkg/text\"\n)\n\n// Fn is a function with no arguments which returns an error or nil.\ntype Fn func() error\n\n// Stack models a simple undo stack which consumers can use to store undo\n// stateful functions, such as a function to teardown API state if something\n// goes wrong during procedural commands, for example deleting a Fastly service\n// after it's been created.\ntype Stack struct {\n\tstates []Fn\n}\n\n// Stacker represents the API of a Stack.\ntype Stacker interface {\n\tPop() Fn\n\tPush(elem Fn)\n\tLen() int\n\tRunIfError(w io.Writer, err error)\n}\n\n// NewStack constructs a new Stack.\nfunc NewStack() *Stack {\n\ts := make([]Fn, 0, 1)\n\tstack := &Stack{\n\t\tstates: s,\n\t}\n\treturn stack\n}\n\n// Pop method pops last added Fn element off the stack and returns it.\n// If stack is empty Pop() returns nil.\nfunc (s *Stack) Pop() Fn {\n\tn := len(s.states)\n\tif n == 0 {\n\t\treturn nil\n\t}\n\tv := s.states[n-1]\n\ts.states = s.states[:n-1]\n\treturn v\n}\n\n// Push method pushes an element onto the Stack.\nfunc (s *Stack) Push(elem Fn) {\n\ts.states = append(s.states, elem)\n}\n\n// Len method returns the number of elements in the Stack.\nfunc (s *Stack) Len() int {\n\treturn len(s.states)\n}\n\n// RunIfError unwinds the stack if a non-nil error is passed, by serially\n// calling each Fn function state in FIFO order. If any Fn returns an\n// error, it gets logged to the provided writer. Should be deferred, such as:\n//\n//\tundoStack := undo.NewStack()\n//\tdefer func() { undoStack.RunIfError(w, err) }()\nfunc (s *Stack) RunIfError(w io.Writer, err error) {\n\tif err == nil {\n\t\treturn\n\t}\n\tfor i := len(s.states) - 1; i >= 0; i-- {\n\t\tif err := s.states[i](); err != nil {\n\t\t\tfmt.Fprintln(w, err)\n\t\t}\n\t}\n}\n\n// Unwind unwinds the stack by serially calling each Fn function state in FIFO\n// order. If any Fn returns an error, it gets logged to the provided writer.\nfunc (s *Stack) Unwind(w io.Writer) {\n\tfor i := len(s.states) - 1; i >= 0; i-- {\n\t\tif err := s.states[i](); err != nil {\n\t\t\ttext.Error(w, \"failed to execute clean-up task: %s\", err.Error())\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "pkg/useragent/doc.go",
    "content": "// Package useragent contains variables for managing the User-Agent string.\npackage useragent\n"
  },
  {
    "path": "pkg/useragent/useragent.go",
    "content": "package useragent\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/fastly/cli/pkg/revision\"\n)\n\n// Name is the user agent which we report in all HTTP requests.\nvar Name = fmt.Sprintf(\"%s/%s\", \"FastlyCLI\", revision.AppVersion)\n\nfunc SetExtension(extension string) {\n\tName = fmt.Sprintf(\"%s, %s\", Name, extension)\n}\n"
  },
  {
    "path": "scripts/config.sh",
    "content": "#!/usr/bin/env bash\n\nset -e\n\ncp \".fastly/config.toml\" \"pkg/config/config.toml\"\n\nif ! command -v tq &> /dev/null\nthen\n  \tcargo install tomlq\nfi\n\nkits=(\n\tcompute-starter-kit-go-default\n\tcompute-starter-kit-go-tinygo\n\tcompute-starter-kit-javascript-default\n\tcompute-starter-kit-javascript-empty\n\tcompute-starter-kit-rust-default\n\tcompute-starter-kit-rust-empty\n\tcompute-starter-kit-rust-static-content\n\tcompute-starter-kit-rust-websockets\n\tcompute-starter-kit-typescript\n)\n\nfunction parse() {\n\ttq -r -f \"$k.toml\" $1\n}\n\nfunction append() {\n\techo $1 >>pkg/config/config.toml\n}\n\nfor k in ${kits[@]}; do\n\tcurl -s \"https://raw.githubusercontent.com/fastly/$k/main/fastly.toml\" -o \"$k.toml\"\n\n\tappend ''\n\tappend \"[[starter-kits.$(parse language)]]\"\n\tappend \"description = \\\"$(parse description)\\\"\"\n\tappend \"name = \\\"$(parse name)\\\"\"\n\tappend \"path = \\\"https://github.com/fastly/$k\\\"\"\n\n\trm \"$k.toml\"\ndone\n"
  },
  {
    "path": "scripts/documentation.sh",
    "content": "#!/usr/bin/env bash\nset -e\n\n$1 help --format json > dist/usage.json\n"
  },
  {
    "path": "scripts/go-test-cache/go.mod",
    "content": "module go-test-cache\n\ngo 1.20\n"
  },
  {
    "path": "scripts/go-test-cache/main.go",
    "content": "// This code is based on the following script and was generated using AI.\n// https://github.com/airplanedev/blog-examples/blob/main/go-test-caching/update_file_timestamps.py?ref=airplane.ghost.io\n//\n// REFERENCE ARTICLE:\n// https://www.airplane.dev/blog/caching-golang-tests-in-ci#:~:text=fixed%20that%20problem.-,Reading%20fixtures,-A%20third%20issue\npackage main\n\nimport (\n\t\"crypto/sha1\"\n\t\"io\"\n\t\"log\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"sort\"\n\t\"strings\"\n\t\"time\"\n)\n\nconst (\n\tbufSize    = 65536\n\tbaseDate   = 1684178360\n\ttimeFormat = \"2006-01-02 15:04:05\"\n)\n\nfunc main() {\n\trepoRoot := \".\"\n\tallDirs := make([]string, 0)\n\n\terr := filepath.Walk(repoRoot, func(path string, info os.FileInfo, err error) error {\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif info.IsDir() {\n\t\t\tdirPath := filepath.Join(repoRoot, path)\n\t\t\trelPath, _ := filepath.Rel(repoRoot, dirPath)\n\n\t\t\tif strings.HasPrefix(relPath, \".\") {\n\t\t\t\treturn nil\n\t\t\t}\n\n\t\t\tallDirs = append(allDirs, dirPath)\n\t\t} else {\n\t\t\tfilePath := filepath.Join(repoRoot, path)\n\t\t\trelPath, _ := filepath.Rel(repoRoot, filePath)\n\n\t\t\tif strings.HasPrefix(relPath, \".\") {\n\t\t\t\treturn nil\n\t\t\t}\n\n\t\t\tsha1Hash, err := getFileSHA1(filePath)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tmodTime := getModifiedTime(sha1Hash)\n\n\t\t\tlog.Printf(\"Setting modified time of file %s to %s\\n\", relPath, modTime.Format(timeFormat))\n\t\t\terr = os.Chtimes(filePath, modTime, modTime)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\n\t\treturn nil\n\t})\n\tif err != nil {\n\t\tlog.Fatal(\"Error:\", err)\n\t}\n\n\tsort.Slice(allDirs, func(i, j int) bool {\n\t\treturn len(allDirs[i]) > len(allDirs[j]) || (len(allDirs[i]) == len(allDirs[j]) && allDirs[i] < allDirs[j])\n\t})\n\n\tfor _, dirPath := range allDirs {\n\t\trelPath, _ := filepath.Rel(repoRoot, dirPath)\n\n\t\tlog.Printf(\"Setting modified time of directory %s to %s\\n\", relPath, time.Unix(baseDate, 0).Format(timeFormat))\n\t\terr := os.Chtimes(dirPath, time.Unix(baseDate, 0), time.Unix(baseDate, 0))\n\t\tif err != nil {\n\t\t\tlog.Fatal(\"Error:\", err)\n\t\t}\n\t}\n\n\tlog.Println(\"Done\")\n}\n\nfunc getFileSHA1(filePath string) (string, error) {\n\tfile, err := os.Open(filePath)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tdefer file.Close()\n\n\t// G401: Use of weak cryptographic primitive\n\t// Disabling as the hash is used not for security reasons.\n\t// The hash is used as a cache key to improve test run times.\n\t// #nosec\n\t// nosemgrep: go.lang.security.audit.crypto.use_of_weak_crypto.use-of-sha1\n\thash := sha1.New()\n\tif _, err := io.CopyBuffer(hash, file, make([]byte, bufSize)); err != nil {\n\t\treturn \"\", err\n\t}\n\n\treturn string(hash.Sum(nil)), nil\n}\n\nfunc getModifiedTime(sha1Hash string) time.Time {\n\thashBytes := []byte(sha1Hash)\n\tlastFiveBytes := hashBytes[:5]\n\tlastFiveValue := int64(0)\n\n\tfor _, b := range lastFiveBytes {\n\t\tlastFiveValue = (lastFiveValue << 8) + int64(b)\n\t}\n\n\tmodTime := baseDate - (lastFiveValue % 10000)\n\treturn time.Unix(modTime, 0)\n}\n"
  },
  {
    "path": "scripts/scaffold-category.sh",
    "content": "#!/usr/bin/env bash\nset -e\n\nexport CLI_CATEGORY=$1\nexport CLI_CATEGORY_COMMAND=$2\nexport CLI_PACKAGE=$3\nexport CLI_COMMAND=$4\nexport CLI_API=$5\n\nmkdir -p pkg/commands/$CLI_CATEGORY/$CLI_PACKAGE\n\n# CREATE NEW CATEGORY FILES\n#\n# NOTE: We avoid recreating the files if they already exist, which can happen\n# if adding a new command to an existing category (e.g. adding a new logging\n# endpoint to the logging category).\n#\nif [ ! -f \"pkg/commands/$CLI_CATEGORY/doc.go\" ]; then\n\tcat .tmpl/doc_parent.go | envsubst > pkg/commands/$CLI_CATEGORY/doc.go\nfi\nif [ ! -f \"pkg/commands/$CLI_CATEGORY/root.go\" ]; then\n\tcat .tmpl/root_parent.go | envsubst > pkg/commands/$CLI_CATEGORY/root.go\nfi\n\n# CREATE NEW COMMAND FILES\n#\ncat .tmpl/test.go | envsubst > pkg/commands/$CLI_CATEGORY/$CLI_PACKAGE/${CLI_PACKAGE}_test.go\nfilenames=(\"create\" \"delete\" \"describe\" \"doc\" \"list\" \"root\" \"update\")\nfor filename in \"${filenames[@]}\"; do\n\tcat .tmpl/$filename.go | envsubst > pkg/commands/$CLI_CATEGORY/$CLI_PACKAGE/$filename.go\ndone\n\nsource ./scripts/scaffold-update-interfaces.sh\n"
  },
  {
    "path": "scripts/scaffold-update-interfaces.sh",
    "content": "#!/usr/bin/env bash\nset -e\n\n# UPDATE INTERFACE FILE\n#\n# The interface file contains all the API functions we expect to use from the\n# go-fastly SDK. When adding a new command, we want to update this file to\n# reflect any new API functions we're intending to use.\n#\n# The logic in this file is more complex than the other scaffolding scripts\n# because we're manipulating an existing file that isn't code-generated.\n#\n# I use Vim to handle the processing because it's easier for me (@integralist)\n# to write the otherwise complex logic, compared to trying to use bash or some\n# other tool such as Awk.\n#\n# STEPS:\n# - We locate the Interface type.\n# - Copy the last set of interface methods.\n# - Capture line number for start of copied methods (to use in substitution).\n# - Rename the API (three separate places per line).\n#\n# NOTE:\n# Any backslash in the substitution commands (e.g. \\v) need to be double escaped.\n#   - Once because the backslash is inside the :exe command's expected string.\n#   - And then again because of the parent HEREDOC container.\n#\n# CAVEATS:\n# This isn't a perfect process. Its successfulness is based on whether the last\n# set of commands align with our expectations. It will still produce ~95%\n# expected output, but if there's an extra API function (e.g. BatchModify) then\n# that line won't have the relevant API name replaced as we only look for the\n# common CRUD methods (Create, Delete, Get, List, Update).\n#\nvim -E -s pkg/api/interface.go <<-EOF\n\t:g/type Interface interface/norm $%k\n\t:norm V{yP]mk\n\t:norm {\n\t:call setreg('a', line('.'))\n\t:norm ]mk\n\t:exe getreg(\"a\")\",\"line(\".\")\"s/\\\\\\v(Create|Delete|Get|List|Update)[^(]+/\\\\\\1${CLI_API}/\"\n\t:exe getreg(\"a\")\",\"line(\".\")\"s/\\\\\\v(fastly\\\\\\.)(Create|Delete|Get|List|Update)[^)]+(Input)/\\\\\\1\\\\\\2${CLI_API}\\\\\\3/\"\n\t:exe getreg(\"a\")\",\"line(\".\")\"s/\\\\\\v\\\\\\((\\\\\\[\\\\\\])?\\\\\\*(fastly\\\\\\.)[^,]+/(\\\\\\1*\\\\\\2${CLI_API}/\"\n\t:exe getreg(\"a\")\",\"line(\".\")\"s/\\\\\\v(List${CLI_API})/\\\\\\1s/g\"\n\t:update\n\t:quit\nEOF\n\n# The following is essentially the same as above, but we tweak the first :exe\n# substitution a bit to fit the format of the mock interface file.\n#\nvim -E -s pkg/mock/api.go <<-EOF\n\t:g/type API struct/norm $%k\n\t:norm V{yP]mk\n\t:norm {\n\t:call setreg('a', line('.'))\n\t:norm ]mk\n\t:exe getreg(\"a\")\",\"line(\".\")\"s/\\\\\\v(Create|Delete|Get|List|Update)[^(]+/\\\\\\1${CLI_API}Fn func/\"\n\t:exe getreg(\"a\")\",\"line(\".\")\"s/\\\\\\v(fastly\\\\\\.)(Create|Delete|Get|List|Update)[^)]+(Input)/\\\\\\1\\\\\\2${CLI_API}\\\\\\3/\"\n\t:exe getreg(\"a\")\",\"line(\".\")\"s/\\\\\\v\\\\\\((\\\\\\[\\\\\\])?\\\\\\*(fastly\\\\\\.)[^,]+/(\\\\\\1*\\\\\\2${CLI_API}/\"\n\t:exe getreg(\"a\")\",\"line(\".\")\"s/\\\\\\v(List${CLI_API})/\\\\\\1s/g\"\n\t:update\n\t:quit\nEOF\n\n# Additionally, we have to create mock implementations of the CRUD functions,\n# so we have to copy an existing function and then do similar substitutions.\n#\nfunctions=(\"Create\" \"Delete\" \"Get\" \"List\" \"Update\")\nfor fn in \"${functions[@]}\"; do\n\tvim -E -s pkg/mock/api.go <<-EOF\n\t\t:$\n\t\t:norm V{yPG\n\t\t:norm {\n\t\t:call setreg('a', line('.'))\n\t\t:$\n\t\t:exe getreg(\"a\")\",\"line(\".\")\"s/\\\\\\v(return m\\\\\\.)(Create|Delete|Get|List|Update)[^(]+/\\\\\\1${fn}${CLI_API}Fn/\"\n\t\t:exe getreg(\"a\")\",\"line(\".\")\"s/\\\\\\v\\\\\\) (Create|Delete|Get|List|Update)[^(]+/) ${fn}${CLI_API}/\"\n\t\t:exe getreg(\"a\")\",\"line(\".\")\"s/\\\\\\v(fastly\\\\\\.)(Create|Delete|Get|List|Update)[^)]+(Input)/\\\\\\1${fn}${CLI_API}\\\\\\3/\"\n\t\t:exe getreg(\"a\")\",\"line(\".\")\"s/\\\\\\v\\\\\\((\\\\\\*fastly\\\\\\.)[^,]+/(\\\\\\1${CLI_API}/\"\n\t\t:exe getreg(\"a\")\",\"line(\".\")\"s/\\\\\\v^(\\\\\\/\\\\\\/) (Create|Delete|Get|List|Update)(\\\\\\w+)( implements)/\\\\\\1 ${fn}${CLI_API}\\\\\\4/\"\n\t\t:update\n\t\t:quit\n\tEOF\n\n\t# List needs a plural for its name.\n\t# We can't combine this substitution with the above because of the potential\n\t# ordering of commands generated (i.e. it could cause another method to be\n\t# incorrectly updated).\n\tvim -E -s pkg/mock/api.go <<-EOF\n\t\t:$\n\t\t:norm {{\n\t\t:,+4s/\\\\v(List${CLI_API})/\\\\1s/ge\n\t\t:update\n\t\t:quit\n\tEOF\ndone\n\n\n# UPDATE RUN FILE\n#\n# The run file contains all the CLI commands we expect to expose to users.\n# We want to update this file to reflect any new commands we've added.\n#\n# STEPS:\n# - We locate an existing command we want to copy.\n# - Copy the command instantiations.\n# - Rename the package name.\n# - Yank the new commands to the vim register.\n# - Insert the new commands into the list that will be parsed by cmd.Select()\n#\n# The command we copy depends on whether we're creating a top-level command or\n# a category command. If the former we copy the 'backend' command set, if the\n# latter we'll copy the 'vcl' command set as it defines the category as a root\n# command and passes that to the nested root command.\n#\n# NOTE:\n# Any backslash in the substitution commands need to be escaped because of the\n# parent HEREDOC container.\n#\n# Although it looks like the list of commands in run.go is sorted, they are\n# actually manually ordered alphabetically and that's because each commands\n# 'root' command needs to be at the top, and sorting the list would cause that\n# to break. So it's important you don't attempt to sort the list. The purpose of\n# this automation script is to save some manual key strokes. You'll have to\n# manually sort the newly created lines yourself.\n#\nif [[ -z \"${CLI_CATEGORY}\" ]]; then\nvim -E -s pkg/app/commands.go <<-EOF\n  :g/backendCmdRoot :=/norm 0\n  :norm V5jyP\n  :,+5s/backend/${CLI_PACKAGE}/g\n  :norm V5k\"ay\n  :g/return \\\\[]cmd.Command/norm 0\n  :norm \"ap\n  :,+5s/\\\\v :=.+/,/\n  :norm V5k>\n  :update\n  :quit\nEOF\nelse\nvim -E -s pkg/app/commands.go <<-EOF\n  :g/vclCmdRoot :=/norm 0\n  :norm V6jyP\n  :,+6s/vcl/${CLI_CATEGORY}/g\n  :-6\n  :,+6s/custom\\\\./${CLI_PACKAGE}./g\n  :-6\n  :,+6s/Custom/\\\\u${CLI_PACKAGE}/g\n  :-6\n  :norm V6j\"ay\n  :g/return \\\\[]cmd.Command/norm 0\n  :norm \"ap\n  :,+6s/\\\\v :=.+/,/\n  :norm V6k>\n  :update\n  :quit\nEOF\nfi\n"
  },
  {
    "path": "scripts/scaffold.sh",
    "content": "#!/usr/bin/env bash\nset -e\n\nexport CLI_PACKAGE=$1\nexport CLI_COMMAND=$2\nexport CLI_API=$3\n\nmkdir -p pkg/commands/$CLI_PACKAGE\n\n# CREATE NEW COMMAND FILES\n#\ncat .tmpl/test.go | envsubst > pkg/commands/$CLI_PACKAGE/${CLI_PACKAGE}_test.go\nfilenames=(\"create\" \"delete\" \"describe\" \"doc\" \"list\" \"root\" \"update\")\nfor filename in \"${filenames[@]}\"; do\n\tcat .tmpl/$filename.go | envsubst > pkg/commands/$CLI_PACKAGE/$filename.go\ndone\n\nsource ./scripts/scaffold-update-interfaces.sh\n"
  },
  {
    "path": "scripts/tags.sh",
    "content": "#!/usr/bin/env bash\nset -e\n\n# credit: https://github.com/cli/cli/blob/trunk/script/changelog\n\nfunction previous_tag() {\n  current_tag=\"$(git describe --tags HEAD^ --abbrev=0)\"\n  start_ref=\"HEAD\"\n\n  # Find the previous release on the same branch, skipping prereleases if the\n  # current tag is a full release\n  previous_tag=\"\"\n  while [[ -z $previous_tag || ( $previous_tag == *-* && $current_tag != *-* ) ]]; do\n    previous_tag=\"$(git describe --tags \"$start_ref\"^ --abbrev=0)\"\n    start_ref=\"$previous_tag\"\n  done\n  echo $previous_tag\n}\n"
  },
  {
    "path": "tools/go.mod",
    "content": "module github.com/fastly/cli/tools\n\ngo 1.26.2\n\ntool (\n\tgithub.com/goreleaser/goreleaser/v2\n\tgithub.com/ofabry/go-callvis\n)\n\nrequire (\n\tal.essio.dev/pkg/shellescape v1.6.0 // indirect\n\tcel.dev/expr v0.25.2 // indirect\n\tcharm.land/lipgloss/v2 v2.0.3 // indirect\n\tcloud.google.com/go v0.123.0 // indirect\n\tcloud.google.com/go/auth v0.20.0 // indirect\n\tcloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect\n\tcloud.google.com/go/compute/metadata v0.9.0 // indirect\n\tcloud.google.com/go/iam v1.11.0 // indirect\n\tcloud.google.com/go/kms v1.31.0 // indirect\n\tcloud.google.com/go/longrunning v1.0.0 // indirect\n\tcloud.google.com/go/monitoring v1.29.0 // indirect\n\tcloud.google.com/go/storage v1.62.1 // indirect\n\tcode.gitea.io/sdk/gitea v0.25.1 // indirect\n\tdario.cat/mergo v1.0.2 // indirect\n\tgithub.com/42wim/httpsig v1.2.4 // indirect\n\tgithub.com/AlekSi/pointer v1.2.0 // indirect\n\tgithub.com/Azure/azure-sdk-for-go v68.0.0+incompatible // indirect\n\tgithub.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.1 // indirect\n\tgithub.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1 // indirect\n\tgithub.com/Azure/azure-sdk-for-go/sdk/internal v1.12.0 // indirect\n\tgithub.com/Azure/azure-sdk-for-go/sdk/keyvault/azkeys v0.10.0 // indirect\n\tgithub.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.7.1 // indirect\n\tgithub.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.4.0 // indirect\n\tgithub.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.2.0 // indirect\n\tgithub.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.4 // indirect\n\tgithub.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect\n\tgithub.com/Azure/go-autorest v14.2.0+incompatible // indirect\n\tgithub.com/Azure/go-autorest/autorest v0.11.30 // indirect\n\tgithub.com/Azure/go-autorest/autorest/adal v0.9.24 // indirect\n\tgithub.com/Azure/go-autorest/autorest/azure/auth v0.5.13 // indirect\n\tgithub.com/Azure/go-autorest/autorest/azure/cli v0.4.7 // indirect\n\tgithub.com/Azure/go-autorest/autorest/date v0.3.1 // indirect\n\tgithub.com/Azure/go-autorest/autorest/to v0.4.1 // indirect\n\tgithub.com/Azure/go-autorest/logger v0.2.2 // indirect\n\tgithub.com/Azure/go-autorest/tracing v0.6.1 // indirect\n\tgithub.com/AzureAD/microsoft-authentication-library-for-go v1.7.2 // indirect\n\tgithub.com/BurntSushi/toml v1.6.0 // indirect\n\tgithub.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.32.0 // indirect\n\tgithub.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.56.0 // indirect\n\tgithub.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.56.0 // indirect\n\tgithub.com/Masterminds/goutils v1.1.1 // indirect\n\tgithub.com/Masterminds/semver/v3 v3.5.0 // indirect\n\tgithub.com/Masterminds/sprig/v3 v3.3.0 // indirect\n\tgithub.com/Microsoft/go-winio v0.6.2 // indirect\n\tgithub.com/ProtonMail/go-crypto v1.4.1 // indirect\n\tgithub.com/agnivade/levenshtein v1.2.1 // indirect\n\tgithub.com/anchore/go-macholibre v0.1.0 // indirect\n\tgithub.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect\n\tgithub.com/atc0005/go-teams-notify/v2 v2.14.0 // indirect\n\tgithub.com/avast/retry-go/v4 v4.7.0 // indirect\n\tgithub.com/aws/aws-sdk-go-v2 v1.41.7 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.10 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/config v1.32.17 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/credentials v1.19.16 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.23 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/feature/s3/manager v1.22.18 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/internal/configsources v1.4.23 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.23 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/internal/v4a v1.4.24 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/ecr v1.57.2 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/ecrpublic v1.38.15 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.9 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.15 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.23 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.23 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/kms v1.51.1 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/s3 v1.101.0 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/signin v1.0.11 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/sso v1.30.17 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.21 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/sts v1.42.1 // indirect\n\tgithub.com/aws/smithy-go v1.25.1 // indirect\n\tgithub.com/awslabs/amazon-ecr-credential-helper/ecr-login v0.12.0 // indirect\n\tgithub.com/bahlo/generic-list-go v0.2.0 // indirect\n\tgithub.com/blacktop/go-dwarf v1.0.14 // indirect\n\tgithub.com/blacktop/go-macho v1.1.272 // indirect\n\tgithub.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb // indirect\n\tgithub.com/blang/semver v3.5.1+incompatible // indirect\n\tgithub.com/bluesky-social/indigo v0.0.0-20240813042137-4006c0eca043 // indirect\n\tgithub.com/buger/jsonparser v1.2.0 // indirect\n\tgithub.com/caarlos0/env/v11 v11.4.1 // indirect\n\tgithub.com/caarlos0/go-reddit/v3 v3.0.1 // indirect\n\tgithub.com/caarlos0/go-version v0.2.2 // indirect\n\tgithub.com/caarlos0/log v0.6.0 // indirect\n\tgithub.com/carlmjohnson/versioninfo v0.22.5 // indirect\n\tgithub.com/cavaliergopher/cpio v1.0.1 // indirect\n\tgithub.com/cenkalti/backoff/v4 v4.3.0 // indirect\n\tgithub.com/cenkalti/backoff/v5 v5.0.3 // indirect\n\tgithub.com/cespare/xxhash/v2 v2.3.0 // indirect\n\tgithub.com/charmbracelet/colorprofile v0.4.3 // indirect\n\tgithub.com/charmbracelet/fang v1.0.0 // indirect\n\tgithub.com/charmbracelet/ultraviolet v0.0.0-20251205161215-1948445e3318 // indirect\n\tgithub.com/charmbracelet/x/ansi v0.11.7 // indirect\n\tgithub.com/charmbracelet/x/exp/charmtone v0.0.0-20250603201427-c31516f43444 // indirect\n\tgithub.com/charmbracelet/x/term v0.2.2 // indirect\n\tgithub.com/charmbracelet/x/termios v0.1.1 // indirect\n\tgithub.com/charmbracelet/x/windows v0.2.2 // indirect\n\tgithub.com/chrismellard/docker-credential-acr-env v0.0.0-20230304212654-82a0ddb27589 // indirect\n\tgithub.com/clipperhouse/displaywidth v0.11.0 // indirect\n\tgithub.com/clipperhouse/uax29/v2 v2.7.0 // indirect\n\tgithub.com/cloudflare/circl v1.6.3 // indirect\n\tgithub.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2 // indirect\n\tgithub.com/containerd/errdefs v1.0.0 // indirect\n\tgithub.com/containerd/errdefs/pkg v0.3.0 // indirect\n\tgithub.com/containerd/stargz-snapshotter/estargz v0.18.2 // indirect\n\tgithub.com/coreos/go-oidc/v3 v3.18.0 // indirect\n\tgithub.com/cyberphone/json-canonicalization v0.0.0-20241213102144-19d51d7fe467 // indirect\n\tgithub.com/cyphar/filepath-securejoin v0.6.1 // indirect\n\tgithub.com/davidmz/go-pageant v1.0.2 // indirect\n\tgithub.com/dghubble/go-twitter v0.0.0-20211115160449-93a8679adecb // indirect\n\tgithub.com/dghubble/oauth1 v0.7.3 // indirect\n\tgithub.com/dghubble/sling v1.4.2 // indirect\n\tgithub.com/digitorus/pkcs7 v0.0.0-20250730155240-ffadbf3f398c // indirect\n\tgithub.com/digitorus/timestamp v0.0.0-20250524132541-c45532741eea // indirect\n\tgithub.com/dimchansky/utfbom v1.1.1 // indirect\n\tgithub.com/disintegration/imaging v1.6.2 // indirect\n\tgithub.com/distribution/reference v0.6.0 // indirect\n\tgithub.com/docker/cli v29.4.3+incompatible // indirect\n\tgithub.com/docker/docker-credential-helpers v0.9.7 // indirect\n\tgithub.com/docker/go-connections v0.7.0 // indirect\n\tgithub.com/docker/go-units v0.5.0 // indirect\n\tgithub.com/dustin/go-humanize v1.0.1 // indirect\n\tgithub.com/emirpasic/gods v1.18.1 // indirect\n\tgithub.com/envoyproxy/go-control-plane/envoy v1.37.0 // indirect\n\tgithub.com/envoyproxy/protoc-gen-validate v1.3.3 // indirect\n\tgithub.com/evanphx/json-patch/v5 v5.9.11 // indirect\n\tgithub.com/felixge/httpsnoop v1.0.4 // indirect\n\tgithub.com/flopp/go-findfont v0.1.0 // indirect\n\tgithub.com/fogleman/gg v1.3.0 // indirect\n\tgithub.com/fsnotify/fsnotify v1.10.1 // indirect\n\tgithub.com/gabriel-vasile/mimetype v1.4.13 // indirect\n\tgithub.com/github/smimesign v0.2.0 // indirect\n\tgithub.com/go-fed/httpsig v1.1.0 // indirect\n\tgithub.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect\n\tgithub.com/go-git/go-billy/v5 v5.9.0 // indirect\n\tgithub.com/go-git/go-git/v5 v5.19.0 // indirect\n\tgithub.com/go-jose/go-jose/v4 v4.1.4 // indirect\n\tgithub.com/go-logr/logr v1.4.3 // indirect\n\tgithub.com/go-logr/stdr v1.2.2 // indirect\n\tgithub.com/go-openapi/analysis v0.25.0 // indirect\n\tgithub.com/go-openapi/errors v0.22.7 // indirect\n\tgithub.com/go-openapi/jsonpointer v0.23.1 // indirect\n\tgithub.com/go-openapi/jsonreference v0.21.5 // indirect\n\tgithub.com/go-openapi/loads v0.23.3 // indirect\n\tgithub.com/go-openapi/runtime v0.29.5 // indirect\n\tgithub.com/go-openapi/spec v0.22.4 // indirect\n\tgithub.com/go-openapi/strfmt v0.26.2 // indirect\n\tgithub.com/go-openapi/swag v0.26.0 // indirect\n\tgithub.com/go-openapi/swag/cmdutils v0.26.0 // indirect\n\tgithub.com/go-openapi/swag/conv v0.26.0 // indirect\n\tgithub.com/go-openapi/swag/fileutils v0.26.0 // indirect\n\tgithub.com/go-openapi/swag/jsonname v0.26.0 // indirect\n\tgithub.com/go-openapi/swag/jsonutils v0.26.0 // indirect\n\tgithub.com/go-openapi/swag/loading v0.26.0 // indirect\n\tgithub.com/go-openapi/swag/mangling v0.26.0 // indirect\n\tgithub.com/go-openapi/swag/netutils v0.26.0 // indirect\n\tgithub.com/go-openapi/swag/stringutils v0.26.0 // indirect\n\tgithub.com/go-openapi/swag/typeutils v0.26.0 // indirect\n\tgithub.com/go-openapi/swag/yamlutils v0.26.0 // indirect\n\tgithub.com/go-openapi/validate v0.25.2 // indirect\n\tgithub.com/go-restruct/restruct v1.2.0-alpha // indirect\n\tgithub.com/go-viper/mapstructure/v2 v2.5.0 // indirect\n\tgithub.com/gobwas/glob v0.2.3 // indirect\n\tgithub.com/goccy/go-graphviz v0.2.10 // indirect\n\tgithub.com/gogo/protobuf v1.3.2 // indirect\n\tgithub.com/golang-jwt/jwt/v4 v4.5.2 // indirect\n\tgithub.com/golang-jwt/jwt/v5 v5.3.1 // indirect\n\tgithub.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect\n\tgithub.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect\n\tgithub.com/google/certificate-transparency-go v1.3.3 // indirect\n\tgithub.com/google/go-containerregistry v0.21.5 // indirect\n\tgithub.com/google/go-github/v84 v84.0.0 // indirect\n\tgithub.com/google/go-querystring v1.2.0 // indirect\n\tgithub.com/google/ko v0.18.2-0.20260407063826-ae9c7272d7de // indirect\n\tgithub.com/google/rpmpack v0.7.1 // indirect\n\tgithub.com/google/s2a-go v0.1.9 // indirect\n\tgithub.com/google/uuid v1.6.0 // indirect\n\tgithub.com/google/wire v0.7.0 // indirect\n\tgithub.com/googleapis/enterprise-certificate-proxy v0.3.15 // indirect\n\tgithub.com/googleapis/gax-go/v2 v2.22.0 // indirect\n\tgithub.com/goreleaser/chglog v0.7.4 // indirect\n\tgithub.com/goreleaser/fileglob v1.4.0 // indirect\n\tgithub.com/goreleaser/go-shellwords v1.0.13 // indirect\n\tgithub.com/goreleaser/goreleaser/v2 v2.15.4 // indirect\n\tgithub.com/goreleaser/nfpm/v2 v2.46.3 // indirect\n\tgithub.com/goreleaser/quill v0.0.0-20260418030907-a259ef5caf05 // indirect\n\tgithub.com/gorilla/websocket v1.5.3 // indirect\n\tgithub.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0 // indirect\n\tgithub.com/hashicorp/errwrap v1.1.0 // indirect\n\tgithub.com/hashicorp/go-cleanhttp v0.5.2 // indirect\n\tgithub.com/hashicorp/go-multierror v1.1.1 // indirect\n\tgithub.com/hashicorp/go-retryablehttp v0.7.8 // indirect\n\tgithub.com/hashicorp/go-version v1.9.0 // indirect\n\tgithub.com/hashicorp/golang-lru v1.0.2 // indirect\n\tgithub.com/huandu/xstrings v1.5.0 // indirect\n\tgithub.com/in-toto/attestation v1.2.0 // indirect\n\tgithub.com/in-toto/in-toto-golang v0.11.0 // indirect\n\tgithub.com/inconshreveable/mousetrap v1.1.0 // indirect\n\tgithub.com/invopop/jsonschema v0.14.0 // indirect\n\tgithub.com/ipfs/bbloom v0.1.0 // indirect\n\tgithub.com/ipfs/boxo v0.39.0 // indirect\n\tgithub.com/ipfs/go-block-format v0.2.3 // indirect\n\tgithub.com/ipfs/go-cid v0.6.1 // indirect\n\tgithub.com/ipfs/go-datastore v0.9.1 // indirect\n\tgithub.com/ipfs/go-ipfs-blockstore v1.3.1 // indirect\n\tgithub.com/ipfs/go-ipfs-ds-help v1.1.1 // indirect\n\tgithub.com/ipfs/go-ipld-cbor v0.2.1 // indirect\n\tgithub.com/ipfs/go-ipld-format v0.6.3 // indirect\n\tgithub.com/ipfs/go-log v1.0.5 // indirect\n\tgithub.com/ipfs/go-log/v2 v2.9.2 // indirect\n\tgithub.com/ipfs/go-metrics-interface v0.3.0 // indirect\n\tgithub.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect\n\tgithub.com/jedisct1/go-minisign v0.0.0-20241212093149-d2f9f49435c7 // indirect\n\tgithub.com/kevinburke/ssh_config v1.6.0 // indirect\n\tgithub.com/klauspost/compress v1.18.6 // indirect\n\tgithub.com/klauspost/cpuid/v2 v2.3.0 // indirect\n\tgithub.com/klauspost/pgzip v1.2.6 // indirect\n\tgithub.com/kylelemons/godebug v1.1.0 // indirect\n\tgithub.com/lucasb-eyer/go-colorful v1.4.0 // indirect\n\tgithub.com/mattn/go-isatty v0.0.22 // indirect\n\tgithub.com/mattn/go-mastodon v0.0.11 // indirect\n\tgithub.com/mattn/go-runewidth v0.0.23 // indirect\n\tgithub.com/minio/sha256-simd v1.0.1 // indirect\n\tgithub.com/mitchellh/copystructure v1.2.0 // indirect\n\tgithub.com/mitchellh/go-homedir v1.1.0 // indirect\n\tgithub.com/mitchellh/reflectwalk v1.0.2 // indirect\n\tgithub.com/moby/docker-image-spec v1.3.1 // indirect\n\tgithub.com/moby/moby/api v1.54.2 // indirect\n\tgithub.com/moby/moby/client v0.4.1 // indirect\n\tgithub.com/moby/term v0.5.2 // indirect\n\tgithub.com/modelcontextprotocol/registry v1.7.9 // indirect\n\tgithub.com/mr-tron/base58 v1.3.0 // indirect\n\tgithub.com/muesli/cancelreader v0.2.2 // indirect\n\tgithub.com/muesli/mango v0.2.0 // indirect\n\tgithub.com/muesli/mango-cobra v1.3.0 // indirect\n\tgithub.com/muesli/mango-pflag v0.2.0 // indirect\n\tgithub.com/muesli/roff v0.1.0 // indirect\n\tgithub.com/multiformats/go-base32 v0.1.0 // indirect\n\tgithub.com/multiformats/go-base36 v0.2.0 // indirect\n\tgithub.com/multiformats/go-multibase v0.3.0 // indirect\n\tgithub.com/multiformats/go-multihash v0.2.3 // indirect\n\tgithub.com/multiformats/go-varint v0.1.0 // indirect\n\tgithub.com/ofabry/go-callvis v0.7.1 // indirect\n\tgithub.com/oklog/ulid/v2 v2.1.1 // indirect\n\tgithub.com/opencontainers/go-digest v1.0.0 // indirect\n\tgithub.com/opencontainers/image-spec v1.1.1 // indirect\n\tgithub.com/opentracing/opentracing-go v1.2.0 // indirect\n\tgithub.com/pb33f/ordered-map/v2 v2.3.1 // indirect\n\tgithub.com/pelletier/go-toml v1.9.5 // indirect\n\tgithub.com/pelletier/go-toml/v2 v2.3.1 // indirect\n\tgithub.com/pjbgf/sha1cd v0.6.0 // indirect\n\tgithub.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect\n\tgithub.com/pkg/errors v0.9.1 // indirect\n\tgithub.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect\n\tgithub.com/polydawn/refmt v0.89.1-0.20231129105047-37766d95467a // indirect\n\tgithub.com/rivo/uniseg v0.4.7 // indirect\n\tgithub.com/sagikazarmark/locafero v0.12.0 // indirect\n\tgithub.com/santhosh-tekuri/jsonschema/v5 v5.3.1 // indirect\n\tgithub.com/sassoftware/relic v7.2.1+incompatible // indirect\n\tgithub.com/scylladb/go-set v1.0.3-0.20200225121959-cc7b2070d91e // indirect\n\tgithub.com/secure-systems-lab/go-securesystemslib v0.11.0 // indirect\n\tgithub.com/sergi/go-diff v1.4.0 // indirect\n\tgithub.com/shibumi/go-pathspec v1.3.0 // indirect\n\tgithub.com/shopspring/decimal v1.4.0 // indirect\n\tgithub.com/sigstore/cosign/v3 v3.0.6 // indirect\n\tgithub.com/sigstore/protobuf-specs v0.5.1 // indirect\n\tgithub.com/sigstore/rekor v1.5.1 // indirect\n\tgithub.com/sigstore/rekor-tiles/v2 v2.2.1 // indirect\n\tgithub.com/sigstore/sigstore v1.10.5 // indirect\n\tgithub.com/sigstore/sigstore-go v1.1.4 // indirect\n\tgithub.com/sigstore/timestamp-authority/v2 v2.0.6 // indirect\n\tgithub.com/sirupsen/logrus v1.9.4 // indirect\n\tgithub.com/skeema/knownhosts v1.3.2 // indirect\n\tgithub.com/slack-go/slack v0.23.1 // indirect\n\tgithub.com/spaolacci/murmur3 v1.1.0 // indirect\n\tgithub.com/spf13/afero v1.15.0 // indirect\n\tgithub.com/spf13/cast v1.10.0 // indirect\n\tgithub.com/spf13/cobra v1.10.2 // indirect\n\tgithub.com/spf13/pflag v1.0.10 // indirect\n\tgithub.com/spf13/viper v1.21.0 // indirect\n\tgithub.com/spiffe/go-spiffe/v2 v2.6.0 // indirect\n\tgithub.com/subosito/gotenv v1.6.0 // indirect\n\tgithub.com/tetratelabs/wazero v1.11.0 // indirect\n\tgithub.com/theupdateframework/go-tuf v0.7.0 // indirect\n\tgithub.com/theupdateframework/go-tuf/v2 v2.4.1 // indirect\n\tgithub.com/tomnomnom/linkheader v0.0.0-20250811210735-e5fe3b51442e // indirect\n\tgithub.com/transparency-dev/formats v0.1.0 // indirect\n\tgithub.com/transparency-dev/merkle v0.0.2 // indirect\n\tgithub.com/ulikunitz/xz v0.5.15 // indirect\n\tgithub.com/vbatts/tar-split v0.12.3 // indirect\n\tgithub.com/wagoodman/go-progress v0.0.0-20230925121702-07e42b3cdba0 // indirect\n\tgithub.com/whyrusleeping/cbor-gen v0.3.1 // indirect\n\tgithub.com/xanzy/ssh-agent v0.3.3 // indirect\n\tgithub.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect\n\tgitlab.com/digitalxero/go-conventional-commit v1.0.7 // indirect\n\tgitlab.com/gitlab-org/api/client-go v1.46.0 // indirect\n\tgo.opentelemetry.io/auto/sdk v1.2.1 // indirect\n\tgo.opentelemetry.io/contrib/detectors/gcp v1.43.0 // indirect\n\tgo.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.68.0 // indirect\n\tgo.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 // indirect\n\tgo.opentelemetry.io/otel v1.43.0 // indirect\n\tgo.opentelemetry.io/otel/metric v1.43.0 // indirect\n\tgo.opentelemetry.io/otel/sdk v1.43.0 // indirect\n\tgo.opentelemetry.io/otel/sdk/metric v1.43.0 // indirect\n\tgo.opentelemetry.io/otel/trace v1.43.0 // indirect\n\tgo.uber.org/atomic v1.11.0 // indirect\n\tgo.uber.org/multierr v1.11.0 // indirect\n\tgo.uber.org/zap v1.28.0 // indirect\n\tgo.yaml.in/yaml/v2 v2.4.4 // indirect\n\tgo.yaml.in/yaml/v3 v3.0.4 // indirect\n\tgo.yaml.in/yaml/v4 v4.0.0-rc.4 // indirect\n\tgocloud.dev v0.45.0 // indirect\n\tgolang.org/x/crypto v0.51.0 // indirect\n\tgolang.org/x/image v0.40.0 // indirect\n\tgolang.org/x/mod v0.36.0 // indirect\n\tgolang.org/x/net v0.54.0 // indirect\n\tgolang.org/x/oauth2 v0.36.0 // indirect\n\tgolang.org/x/sync v0.20.0 // indirect\n\tgolang.org/x/sys v0.44.0 // indirect\n\tgolang.org/x/term v0.43.0 // indirect\n\tgolang.org/x/text v0.37.0 // indirect\n\tgolang.org/x/time v0.15.0 // indirect\n\tgolang.org/x/tools v0.45.0 // indirect\n\tgolang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect\n\tgoogle.golang.org/api v0.279.0 // indirect\n\tgoogle.golang.org/genproto v0.0.0-20260319201613-d00831a3d3e7 // indirect\n\tgoogle.golang.org/genproto/googleapis/api v0.0.0-20260414002931-afd174a4e478 // indirect\n\tgoogle.golang.org/genproto/googleapis/rpc v0.0.0-20260427160629-7cedc36a6bc4 // indirect\n\tgoogle.golang.org/grpc v1.81.0 // indirect\n\tgoogle.golang.org/protobuf v1.36.11 // indirect\n\tgopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect\n\tgopkg.in/mail.v2 v2.3.1 // indirect\n\tgopkg.in/warnings.v0 v0.1.2 // indirect\n\tk8s.io/klog/v2 v2.140.0 // indirect\n\tlukechampine.com/blake3 v1.4.1 // indirect\n\tsigs.k8s.io/kind v0.31.0 // indirect\n\tsigs.k8s.io/yaml v1.6.0 // indirect\n\tsoftware.sslmate.com/src/go-pkcs12 v0.7.1 // indirect\n)\n"
  },
  {
    "path": "tools/go.sum",
    "content": "al.essio.dev/pkg/shellescape v1.6.0 h1:NxFcEqzFSEVCGN2yq7Huv/9hyCEGVa/TncnOOBBeXHA=\nal.essio.dev/pkg/shellescape v1.6.0/go.mod h1:6sIqp7X2P6mThCQ7twERpZTuigpr6KbZWtls1U8I890=\ncel.dev/expr v0.25.2 h1:K6j46C81hXtZQfuX60cVWQFBJahKSE2gfRbNuvr5bFs=\ncel.dev/expr v0.25.2/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4=\ncharm.land/lipgloss/v2 v2.0.3 h1:yM2zJ4Cf5Y51b7RHIwioil4ApI/aypFXXVHSwlM6RzU=\ncharm.land/lipgloss/v2 v2.0.3/go.mod h1:7myLU9iG/3xluAWzpY/fSxYYHCgoKTie7laxk6ATwXA=\ncloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ncloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ncloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=\ncloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=\ncloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=\ncloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=\ncloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=\ncloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=\ncloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=\ncloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=\ncloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=\ncloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=\ncloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=\ncloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=\ncloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=\ncloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=\ncloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=\ncloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg=\ncloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8=\ncloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0=\ncloud.google.com/go v0.123.0 h1:2NAUJwPR47q+E35uaJeYoNhuNEM9kM8SjgRgdeOJUSE=\ncloud.google.com/go v0.123.0/go.mod h1:xBoMV08QcqUGuPW65Qfm1o9Y4zKZBpGS+7bImXLTAZU=\ncloud.google.com/go/auth v0.20.0 h1:kXTssoVb4azsVDoUiF8KvxAqrsQcQtB53DcSgta74CA=\ncloud.google.com/go/auth v0.20.0/go.mod h1:942/yi/itH1SsmpyrbnTMDgGfdy2BUqIKyd0cyYLc5Q=\ncloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=\ncloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=\ncloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=\ncloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=\ncloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=\ncloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=\ncloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=\ncloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=\ncloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs=\ncloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10=\ncloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=\ncloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=\ncloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=\ncloud.google.com/go/iam v1.11.0 h1:KieQ9Pb+LLPak1O3Rv3GgCxhnmkYf7Xyh0P5HfF1jFM=\ncloud.google.com/go/iam v1.11.0/go.mod h1:KP+nKGugNJW4LcLx1uEZcq1ok5sQHFaQehQNl4QDgV4=\ncloud.google.com/go/kms v1.31.0 h1:LS8N92OxFDgOLg5NCo3OmbvjtQAIVT5gUHVLKIDHaFE=\ncloud.google.com/go/kms v1.31.0/go.mod h1:YIyXZym11R5uovJJt4oN5eUL3oPmirF3yKeIh6QAf4U=\ncloud.google.com/go/logging v1.13.2 h1:qqlHCBvieJT9Cdq4QqYx1KPadCQ2noD4FK02eNqHAjA=\ncloud.google.com/go/logging v1.13.2/go.mod h1:zaybliM3yun1J8mU2dVQ1/qDzjbOqEijZCn6hSBtKak=\ncloud.google.com/go/longrunning v1.0.0 h1:lwzWEYD8+NkYV7dhexOz6kmlvajZA70+bW/xMhRVVdY=\ncloud.google.com/go/longrunning v1.0.0/go.mod h1:8nqFBPOO1U/XkhWl0I19AMZEphrHi73VNABIpKYaTwM=\ncloud.google.com/go/monitoring v1.29.0 h1:AHhDsFaSax1/4k+qlIDX/SDGe6hggnfXJ9dkgD9qBPY=\ncloud.google.com/go/monitoring v1.29.0/go.mod h1:72NOVjJXHY/HBfoLT0+qlCZBT059+9VXLeAnL2PeeVM=\ncloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=\ncloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=\ncloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=\ncloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=\ncloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=\ncloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=\ncloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=\ncloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=\ncloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=\ncloud.google.com/go/storage v1.62.1 h1:Os0G3XbUbjZumkpDUf2Y0rLoXJTCF1kU2kWUujKYXD8=\ncloud.google.com/go/storage v1.62.1/go.mod h1:cpYz/kRVZ+UQAF1uHeea10/9ewcRbxGoGNKsS9daSXA=\ncloud.google.com/go/trace v1.11.7 h1:kDNDX8JkaAG3R2nq1lIdkb7FCSi1rCmsEtKVsty7p+U=\ncloud.google.com/go/trace v1.11.7/go.mod h1:TNn9d5V3fQVf6s4SCveVMIBS2LJUqo73GACmq/Tky0s=\ncode.gitea.io/sdk/gitea v0.25.1 h1:yywxWwoV+SdjHtbC6unBiXojWdZOtoHuGhEazEXeWuE=\ncode.gitea.io/sdk/gitea v0.25.1/go.mod h1:uDFWYBU8dgZsgOHwe6C/6olxvf8FHguNB3wW1i83fgg=\ndario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=\ndario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=\ndmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=\nfilippo.io/edwards25519 v1.2.0 h1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo=\nfilippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc=\ngithub.com/42wim/httpsig v1.2.4 h1:mI5bH0nm4xn7K18fo1K3okNDRq8CCJ0KbBYWyA6r8lU=\ngithub.com/42wim/httpsig v1.2.4/go.mod h1:yKsYfSyTBEohkPik224QPFylmzEBtda/kjyIAJjh3ps=\ngithub.com/AdamKorcz/go-fuzz-headers-1 v0.0.0-20230919221257-8b5d3ce2d11d h1:zjqpY4C7H15HjRPEenkS4SAn3Jy2eRRjkjZbGR30TOg=\ngithub.com/AdamKorcz/go-fuzz-headers-1 v0.0.0-20230919221257-8b5d3ce2d11d/go.mod h1:XNqJ7hv2kY++g8XEHREpi+JqZo3+0l+CH2egBVN4yqM=\ngithub.com/AlekSi/pointer v1.2.0 h1:glcy/gc4h8HnG2Z3ZECSzZ1IX1x2JxRVuDzaJwQE0+w=\ngithub.com/AlekSi/pointer v1.2.0/go.mod h1:gZGfd3dpW4vEc/UlyfKKi1roIqcCgwOIvb0tSNSBle0=\ngithub.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU=\ngithub.com/Azure/azure-sdk-for-go v68.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=\ngithub.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.1 h1:jHb/wfvRikGdxMXYV3QG/SzUOPYN9KEUUuC0Yd0/vC0=\ngithub.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.1/go.mod h1:pzBXCYn05zvYIrwLgtK8Ap8QcjRg+0i76tMQdWN6wOk=\ngithub.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1 h1:Hk5QBxZQC1jb2Fwj6mpzme37xbCDdNTxU7O9eb5+LB4=\ngithub.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1/go.mod h1:IYus9qsFobWIc2YVwe/WPjcnyCkPKtnHAqUYeebc8z0=\ngithub.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2 h1:yz1bePFlP5Vws5+8ez6T3HWXPmwOK7Yvq8QxDBD3SKY=\ngithub.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2/go.mod h1:Pa9ZNPuoNu/GztvBSKk9J1cDJW6vk/n0zLtV4mgd8N8=\ngithub.com/Azure/azure-sdk-for-go/sdk/internal v1.12.0 h1:fhqpLE3UEXi9lPaBRpQ6XuRW0nU7hgg4zlmZZa+a9q4=\ngithub.com/Azure/azure-sdk-for-go/sdk/internal v1.12.0/go.mod h1:7dCRMLwisfRH3dBupKeNCioWYUZ4SS09Z14H+7i8ZoY=\ngithub.com/Azure/azure-sdk-for-go/sdk/keyvault/azkeys v0.10.0 h1:m/sWOGCREuSBqg2htVQTBY8nOZpyajYztF0vUvSZTuM=\ngithub.com/Azure/azure-sdk-for-go/sdk/keyvault/azkeys v0.10.0/go.mod h1:Pu5Zksi2KrU7LPbZbNINx6fuVrUp/ffvpxdDj+i8LeE=\ngithub.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.7.1 h1:FbH3BbSb4bvGluTesZZ+ttN/MDsnMmQP36OSnDuSXqw=\ngithub.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.7.1/go.mod h1:9V2j0jn9jDEkCkv8w/bKTNppX/d0FVA1ud77xCIP4KA=\ngithub.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.8.1 h1:/Zt+cDPnpC3OVDm/JKLOs7M2DKmLRIIp3XIx9pHHiig=\ngithub.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.8.1/go.mod h1:Ng3urmn6dYe8gnbCMoHHVl5APYz2txho3koEkV2o2HA=\ngithub.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.4.0 h1:E4MgwLBGeVB5f2MdcIVD3ELVAWpr+WD6MUe1i+tM/PA=\ngithub.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.4.0/go.mod h1:Y2b/1clN4zsAoUd/pgNAQHjLDnTis/6ROkUfyob6psM=\ngithub.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.2.0 h1:nCYfgcSyHZXJI8J0IWE5MsCGlb2xp9fJiXyxWgmOFg4=\ngithub.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.2.0/go.mod h1:ucUjca2JtSZboY8IoUqyQyuuXvwbMBVwFOm0vdQPNhA=\ngithub.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.4 h1:jWQK1GI+LeGGUKBADtcH2rRqPxYB1Ljwms5gFA2LqrM=\ngithub.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.4/go.mod h1:8mwH4klAm9DUgR2EEHyEEAQlRDvLPyg5fQry3y+cDew=\ngithub.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg=\ngithub.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=\ngithub.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs=\ngithub.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=\ngithub.com/Azure/go-autorest/autorest v0.11.28/go.mod h1:MrkzG3Y3AH668QyF9KRk5neJnGgmhQ6krbhR8Q5eMvA=\ngithub.com/Azure/go-autorest/autorest v0.11.30 h1:iaZ1RGz/ALZtN5eq4Nr1SOFSlf2E4pDI3Tcsl+dZPVE=\ngithub.com/Azure/go-autorest/autorest v0.11.30/go.mod h1:t1kpPIOpIVX7annvothKvb0stsrXa37i7b+xpmBW8Fs=\ngithub.com/Azure/go-autorest/autorest/adal v0.9.18/go.mod h1:XVVeme+LZwABT8K5Lc3hA4nAe8LDBVle26gTrguhhPQ=\ngithub.com/Azure/go-autorest/autorest/adal v0.9.22/go.mod h1:XuAbAEUv2Tta//+voMI038TrJBqjKam0me7qR+L8Cmk=\ngithub.com/Azure/go-autorest/autorest/adal v0.9.24 h1:BHZfgGsGwdkHDyZdtQRQk1WeUdW0m2WPAwuHZwUi5i4=\ngithub.com/Azure/go-autorest/autorest/adal v0.9.24/go.mod h1:7T1+g0PYFmACYW5LlG2fcoPiPlFHjClyRGL7dRlP5c8=\ngithub.com/Azure/go-autorest/autorest/azure/auth v0.5.13 h1:Ov8avRZi2vmrE2JcXw+tu5K/yB41r7xK9GZDiBF7NdM=\ngithub.com/Azure/go-autorest/autorest/azure/auth v0.5.13/go.mod h1:5BAVfWLWXihP47vYrPuBKKf4cS0bXI+KM9Qx6ETDJYo=\ngithub.com/Azure/go-autorest/autorest/azure/cli v0.4.6/go.mod h1:piCfgPho7BiIDdEQ1+g4VmKyD5y+p/XtSNqE6Hc4QD0=\ngithub.com/Azure/go-autorest/autorest/azure/cli v0.4.7 h1:Q9R3utmFg9K1B4OYtAZ7ZUUvIUdzQt7G2MN5Hi/d670=\ngithub.com/Azure/go-autorest/autorest/azure/cli v0.4.7/go.mod h1:bVrAueELJ0CKLBpUHDIvD516TwmHmzqwCpvONWRsw3s=\ngithub.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74=\ngithub.com/Azure/go-autorest/autorest/date v0.3.1 h1:o9Z8Jyt+VJJTCZ/UORishuHOusBwolhjokt9s5k8I4w=\ngithub.com/Azure/go-autorest/autorest/date v0.3.1/go.mod h1:Dz/RDmXlfiFFS/eW+b/xMUSFs1tboPVy6UjgADToWDM=\ngithub.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k=\ngithub.com/Azure/go-autorest/autorest/mocks v0.4.2 h1:PGN4EDXnuQbojHbU0UWoNvmu9AGVwYHG9/fkDYhtAfw=\ngithub.com/Azure/go-autorest/autorest/mocks v0.4.2/go.mod h1:Vy7OitM9Kei0i1Oj+LvyAWMXJHeKH1MVlzFugfVrmyU=\ngithub.com/Azure/go-autorest/autorest/to v0.4.1 h1:CxNHBqdzTr7rLtdrtb5CMjJcDut+WNGCVv7OmS5+lTc=\ngithub.com/Azure/go-autorest/autorest/to v0.4.1/go.mod h1:EtaofgU4zmtvn1zT2ARsjRFdq9vXx0YWtmElwL+GZ9M=\ngithub.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=\ngithub.com/Azure/go-autorest/logger v0.2.2 h1:hYqBsEBywrrOSW24kkOCXRcKfKhK76OzLTfF+MYDE2o=\ngithub.com/Azure/go-autorest/logger v0.2.2/go.mod h1:I5fg9K52o+iuydlWfa9T5K6WFos9XYr9dYTFzpqgibw=\ngithub.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=\ngithub.com/Azure/go-autorest/tracing v0.6.1 h1:YUMSrC/CeD1ZnnXcNYU4a/fzsO35u2Fsful9L/2nyR0=\ngithub.com/Azure/go-autorest/tracing v0.6.1/go.mod h1:/3EgjbsjraOqiicERAeu3m7/z0x1TzjQGAwDrJrXGkc=\ngithub.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM=\ngithub.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE=\ngithub.com/AzureAD/microsoft-authentication-library-for-go v1.7.2 h1:RHK7bS+HQMslb1sZpAokUt+zTVmue0hKSs2C791hhzU=\ngithub.com/AzureAD/microsoft-authentication-library-for-go v1.7.2/go.mod h1:HKpQxkWaGLJ+D/5H8QRpyQXA1eKjxkFlOMwck5+33Jk=\ngithub.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=\ngithub.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk=\ngithub.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=\ngithub.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=\ngithub.com/DataDog/zstd v1.5.5 h1:oWf5W7GtOLgp6bciQYDmhHHjdhYkALu6S/5Ni9ZgSvQ=\ngithub.com/DataDog/zstd v1.5.5/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw=\ngithub.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.32.0 h1:rIkQfkCOVKc1OiRCNcSDD8ml5RJlZbH/Xsq7lbpynwc=\ngithub.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.32.0/go.mod h1:RD2SsorTmYhF6HkTmDw7KmPYQk8OBYwTkuasChwv7R4=\ngithub.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.56.0 h1:O2sXMyJh8b7devAGdE+163xtRurt0RVpB6DIzX5vGfg=\ngithub.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.56.0/go.mod h1:hEpiGU18xf70qb3jbTcIggWAiEfX/cOIVc2OTe4OegA=\ngithub.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.56.0 h1:ZIT85vKP7LBS84XJ0WdJ3dPOX3iz4j3c0+lpajGQMyo=\ngithub.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.56.0/go.mod h1:rqP9UEhOXv9WhQ7Gjz+G5y/pf8+BJZW5/Ts0AhE0PwE=\ngithub.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.56.0 h1:0YP0+/ixwu+Uqeu/FGiBZNQ19huiUxxiPXIc9WsLKuQ=\ngithub.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.56.0/go.mod h1:6ZZMQhZKDvUvkJw2rc+oDP90tMMzuU/J+5HG1ZmPOmE=\ngithub.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=\ngithub.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=\ngithub.com/Masterminds/semver/v3 v3.5.0 h1:kQceYJfbupGfZOKZQg0kou0DgAKhzDg2NZPAwZ/2OOE=\ngithub.com/Masterminds/semver/v3 v3.5.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=\ngithub.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs=\ngithub.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0=\ngithub.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=\ngithub.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=\ngithub.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=\ngithub.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw=\ngithub.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk=\ngithub.com/ProtonMail/go-crypto v1.4.1 h1:9RfcZHqEQUvP8RzecWEUafnZVtEvrBVL9BiF67IQOfM=\ngithub.com/ProtonMail/go-crypto v1.4.1/go.mod h1:e1OaTyu5SYVrO9gKOEhTc+5UcXtTUa+P3uLudwcgPqo=\ngithub.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f h1:tCbYj7/299ekTTXpdwKYF8eBlsYsDVoggDAuAjoK66k=\ngithub.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f/go.mod h1:gcr0kNtGBqin9zDW9GOHcVntrwnjrK+qdJ06mWYBybw=\ngithub.com/ProtonMail/gopenpgp/v2 v2.7.1 h1:Awsg7MPc2gD3I7IFac2qE3Gdls0lZW8SzrFZ3k1oz0s=\ngithub.com/ProtonMail/gopenpgp/v2 v2.7.1/go.mod h1:/BU5gfAVwqyd8EfC3Eu7zmuhwYQpKs+cGD8M//iiaxs=\ngithub.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8=\ngithub.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo=\ngithub.com/agnivade/levenshtein v1.2.1 h1:EHBY3UOn1gwdy/VbFwgo4cxecRznFk7fKWN1KOX7eoM=\ngithub.com/agnivade/levenshtein v1.2.1/go.mod h1:QVVI16kDrtSuwcpd0p1+xMC6Z/VfhtCyDIjcwga4/DU=\ngithub.com/anchore/go-macholibre v0.1.0 h1:qHbdusBZNcZM/uuKf1Psa9xxAFSoyRTps8GW9gpJgsg=\ngithub.com/anchore/go-macholibre v0.1.0/go.mod h1:eu0gbwaZ+ocVFJLePdmPPDKU8MboV1MKsUCr36Ckd5s=\ngithub.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=\ngithub.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=\ngithub.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=\ngithub.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=\ngithub.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q=\ngithub.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE=\ngithub.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=\ngithub.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=\ngithub.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=\ngithub.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=\ngithub.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=\ngithub.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=\ngithub.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=\ngithub.com/atc0005/go-teams-notify/v2 v2.14.0 h1:7N+xw+COnYANLREaAveQ65rsNQ12nIZJED9nMLyscCo=\ngithub.com/atc0005/go-teams-notify/v2 v2.14.0/go.mod h1:EECsWM2b0Hvoz7O+QdlsvyN2KCUOFQCGj8bUBXv3A3Q=\ngithub.com/avast/retry-go/v4 v4.7.0 h1:yjDs35SlGvKwRNSykujfjdMxMhMQQM0TnIjJaHB+Zio=\ngithub.com/avast/retry-go/v4 v4.7.0/go.mod h1:ZMPDa3sY2bKgpLtap9JRUgk2yTAba7cgiFhqxY2Sg6Q=\ngithub.com/aws/aws-sdk-go v1.55.8 h1:JRmEUbU52aJQZ2AjX4q4Wu7t4uZjOu71uyNmaWlUkJQ=\ngithub.com/aws/aws-sdk-go v1.55.8/go.mod h1:ZkViS9AqA6otK+JBBNH2++sx1sgxrPKcSzPPvQkUtXk=\ngithub.com/aws/aws-sdk-go-v2 v1.41.7 h1:DWpAJt66FmnnaRIOT/8ASTucrvuDPZASqhhLey6tLY8=\ngithub.com/aws/aws-sdk-go-v2 v1.41.7/go.mod h1:4LAfZOPHNVNQEckOACQx60Y8pSRjIkNZQz1w92xpMJc=\ngithub.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.10 h1:gx1AwW1Iyk9Z9dD9F4akX5gnN3QZwUB20GGKH/I+Rho=\ngithub.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.10/go.mod h1:qqY157uZoqm5OXq/amuaBJyC9hgBCBQnsaWnPe905GY=\ngithub.com/aws/aws-sdk-go-v2/config v1.32.17 h1:FpL4/758/diKwqbytU0prpuiu60fgXKUWCpDJtApclU=\ngithub.com/aws/aws-sdk-go-v2/config v1.32.17/go.mod h1:OXqUMzgXytfoF9JaKkhrOYsyh72t9G+MJH8mMRaexOE=\ngithub.com/aws/aws-sdk-go-v2/credentials v1.19.16 h1:r3RJBuU7X9ibt8RHbMjWE6y60QbKBiII6wSrXnapxSU=\ngithub.com/aws/aws-sdk-go-v2/credentials v1.19.16/go.mod h1:6cx7zqDENJDbBIIWX6P8s0h6hqHC8Avbjh9Dseo27ug=\ngithub.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.23 h1:UuSfcORqNSz/ey3VPRS8TcVH2Ikf0/sC+Hdj400QI6U=\ngithub.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.23/go.mod h1:+G/OSGiOFnSOkYloKj/9M35s74LgVAdJBSD5lsFfqKg=\ngithub.com/aws/aws-sdk-go-v2/feature/s3/manager v1.22.18 h1:9XFUd2lkr7VrbE4Qtrhm7AtNhGgZeGFI5QLZtQIflj8=\ngithub.com/aws/aws-sdk-go-v2/feature/s3/manager v1.22.18/go.mod h1:trImuKdWelQIJALvyGj6sKolJ1W8t628JOoTdDGVL9Q=\ngithub.com/aws/aws-sdk-go-v2/internal/configsources v1.4.23 h1:GpT/TrnBYuE5gan2cZbTtvP+JlHsutdmlV2YfEyNde0=\ngithub.com/aws/aws-sdk-go-v2/internal/configsources v1.4.23/go.mod h1:xYWD6BS9ywC5bS3sz9Xh04whO/hzK2plt2Zkyrp4JuA=\ngithub.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.23 h1:bpd8vxhlQi2r1hiueOw02f/duEPTMK59Q4QMAoTTtTo=\ngithub.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.23/go.mod h1:15DfR2nw+CRHIk0tqNyifu3G1YdAOy68RftkhMDDwYk=\ngithub.com/aws/aws-sdk-go-v2/internal/v4a v1.4.24 h1:OQqn11BtaYv1WLUowvcA30MpzIu8Ti4pcLPIIyoKZrA=\ngithub.com/aws/aws-sdk-go-v2/internal/v4a v1.4.24/go.mod h1:X5ZJyfwVrWA96GzPmUCWFQaEARPR7gCrpq2E92PJwAE=\ngithub.com/aws/aws-sdk-go-v2/service/ecr v1.57.2 h1:rHEW02JFJUV2/ttjzyPIvbD0YraqpyU2w6m6DfQUmdg=\ngithub.com/aws/aws-sdk-go-v2/service/ecr v1.57.2/go.mod h1:gNS8pNht4VMzPd4UtQUL3NTUQbjEPLLmb9MqmqrqsCM=\ngithub.com/aws/aws-sdk-go-v2/service/ecrpublic v1.38.15 h1:nW/zPIjkAgHV1xv8NHdLQtGMoHVj2toMj8/H6SMqjVw=\ngithub.com/aws/aws-sdk-go-v2/service/ecrpublic v1.38.15/go.mod h1:FXDXpYy2PKdkQQr4ERMoRzVKcga0O/hmtRbMaQSpe8U=\ngithub.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.9 h1:FLudkZLt5ci0ozzgkVo8BJGwvqNaZbTWb3UcucAateA=\ngithub.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.9/go.mod h1:w7wZ/s9qK7c8g4al+UyoF1Sp/Z45UwMGcqIzLWVQHWk=\ngithub.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.15 h1:ieLCO1JxUWuxTZ1cRd0GAaeX7O6cIxnwk7tc1LsQhC4=\ngithub.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.15/go.mod h1:e3IzZvQ3kAWNykvE0Tr0RDZCMFInMvhku3qNpcIQXhM=\ngithub.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.23 h1:pbrxO/kuIwgEsOPLkaHu0O+m4fNgLU8B3vxQ+72jTPw=\ngithub.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.23/go.mod h1:/CMNUqoj46HpS3MNRDEDIwcgEnrtZlKRaHNaHxIFpNA=\ngithub.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.23 h1:03xatSQO4+AM1lTAbnRg5OK528EUg744nW7F73U8DKw=\ngithub.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.23/go.mod h1:M8l3mwgx5ToK7wot2sBBce/ojzgnPzZXUV445gTSyE8=\ngithub.com/aws/aws-sdk-go-v2/service/kms v1.51.1 h1:zuSf4olLKZW8cF/W9Y5wvGT+/0raY/3kVp49KsGs0QY=\ngithub.com/aws/aws-sdk-go-v2/service/kms v1.51.1/go.mod h1:Y0+uxvxz6ib4KktRdK0V4X45Vcs/JyYoz8H71pO8xeI=\ngithub.com/aws/aws-sdk-go-v2/service/s3 v1.101.0 h1:etqBTKY581iwLL/H/S2sVgk3C9lAsTJFeXWFDsDcWOU=\ngithub.com/aws/aws-sdk-go-v2/service/s3 v1.101.0/go.mod h1:L2dcoOgS2VSgbPLvpak2NyUPsO1TBN7M45Z4H7DlRc4=\ngithub.com/aws/aws-sdk-go-v2/service/signin v1.0.11 h1:TdJ+HdzOBhU8+iVAOGUTU63VXopcumCOF1paFulHWZc=\ngithub.com/aws/aws-sdk-go-v2/service/signin v1.0.11/go.mod h1:R82ZRExE/nheo0N+T8zHPcLRTcH8MGsnR3BiVGX0TwI=\ngithub.com/aws/aws-sdk-go-v2/service/sso v1.30.17 h1:7byT8HUWrgoRp6sXjxtZwgOKfhss5fW6SkLBtqzgRoE=\ngithub.com/aws/aws-sdk-go-v2/service/sso v1.30.17/go.mod h1:xNWknVi4Ezm1vg1QsB/5EWpAJURq22uqd38U8qKvOJc=\ngithub.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.21 h1:+1Kl1zx6bWi4X7cKi3VYh29h8BvsCoHQEQ6ST9X8w7w=\ngithub.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.21/go.mod h1:4vIRDq+CJB2xFAXZ+YgGUTiEft7oAQlhIs71xcSeuVg=\ngithub.com/aws/aws-sdk-go-v2/service/sts v1.42.1 h1:F/M5Y9I3nwr2IEpshZgh1GeHpOItExNM9L1euNuh/fk=\ngithub.com/aws/aws-sdk-go-v2/service/sts v1.42.1/go.mod h1:mTNxImtovCOEEuD65mKW7DCsL+2gjEH+RPEAexAzAio=\ngithub.com/aws/smithy-go v1.25.1 h1:J8ERsGSU7d+aCmdQur5Txg6bVoYelvQJgtZehD12GkI=\ngithub.com/aws/smithy-go v1.25.1/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc=\ngithub.com/awslabs/amazon-ecr-credential-helper/ecr-login v0.12.0 h1:JFWXO6QPihCknDdnL6VaQE57km4ZKheHIGd9YiOGcTo=\ngithub.com/awslabs/amazon-ecr-credential-helper/ecr-login v0.12.0/go.mod h1:046/oLyFlYdAghYQE2yHXi/E//VM5Cf3/dFmA+3CZ0c=\ngithub.com/aymanbagabas/go-udiff v0.4.1 h1:OEIrQ8maEeDBXQDoGCbbTTXYJMYRCRO1fnodZ12Gv5o=\ngithub.com/aymanbagabas/go-udiff v0.4.1/go.mod h1:0L9PGwj20lrtmEMeyw4WKJ/TMyDtvAoK9bf2u/mNo3w=\ngithub.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=\ngithub.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=\ngithub.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=\ngithub.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=\ngithub.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=\ngithub.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM=\ngithub.com/blacktop/go-dwarf v1.0.14 h1:OjmzfSgg/qAKckn2tWFebcgKgJ7HOqCj7bS+CiE1lrY=\ngithub.com/blacktop/go-dwarf v1.0.14/go.mod h1:4W2FKgSFYcZLDwnR7k+apv5i3nrau4NGl9N6VQ9DSTo=\ngithub.com/blacktop/go-macho v1.1.272 h1:5FDmY3bDkGj7yCdNWke0zKQC1vXmvwV41gGQqG0wCeM=\ngithub.com/blacktop/go-macho v1.1.272/go.mod h1:Hc5E2Lvt/U1VT+jOxr1O5l/LNFJeMYK4eAmDfazTiGc=\ngithub.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb h1:m935MPodAbYS46DG4pJSv7WO+VECIWUQ7OJYSoTrMh4=\ngithub.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI=\ngithub.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ=\ngithub.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=\ngithub.com/bluesky-social/indigo v0.0.0-20240813042137-4006c0eca043 h1:927VIkxPFKpfJKVDtCNgSQtlhksARaLvsLxppR2FukM=\ngithub.com/bluesky-social/indigo v0.0.0-20240813042137-4006c0eca043/go.mod h1:dXjdzg6bhg1JKnKuf6EBJTtcxtfHYBFEe9btxX5YeAE=\ngithub.com/buger/jsonparser v1.2.0 h1:4EFcvK1kD4jyj6YqNK6skK6w+y7FHHBR+XBCtxwu/6g=\ngithub.com/buger/jsonparser v1.2.0/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=\ngithub.com/caarlos0/env/v11 v11.4.1 h1:fYwH0sWEsBSMPG7t4e/PEfTFzrWrpjyygXyUnWiSwEw=\ngithub.com/caarlos0/env/v11 v11.4.1/go.mod h1:qupehSf/Y0TUTsxKywqRt/vJjN5nz6vauiYEUUr8P4U=\ngithub.com/caarlos0/go-reddit/v3 v3.0.1 h1:w8ugvsrHhaE/m4ez0BO/sTBOBWI9WZTjG7VTecHnql4=\ngithub.com/caarlos0/go-reddit/v3 v3.0.1/go.mod h1:QlwgmG5SAqxMeQvg/A2dD1x9cIZCO56BMnMdjXLoisI=\ngithub.com/caarlos0/go-version v0.2.2 h1:5r+nlrg4H2wOVwWjqRqRRIRbZ7ytRmjC9xoMIP0a5kQ=\ngithub.com/caarlos0/go-version v0.2.2/go.mod h1:X+rI5VAtJDpcjCjeEIXpxGa5+rTcgur1FK66wS0/944=\ngithub.com/caarlos0/log v0.6.0 h1:iS+oZ7DB8wpJxOjA4+BHkZQtrx8sMu8AYNZVQukmEtw=\ngithub.com/caarlos0/log v0.6.0/go.mod h1:iAv3N3ZkiEQUmZ8fGdD8bMA4zq6jMSlnz9D87333Gi0=\ngithub.com/caarlos0/testfs v0.4.4 h1:3PHvzHi5Lt+g332CiShwS8ogTgS3HjrmzZxCm6JCDr8=\ngithub.com/caarlos0/testfs v0.4.4/go.mod h1:bRN55zgG4XCUVVHZCeU+/Tz1Q6AxEJOEJTliBy+1DMk=\ngithub.com/carlmjohnson/versioninfo v0.22.5 h1:O00sjOLUAFxYQjlN/bzYTuZiS0y6fWDQjMRvwtKgwwc=\ngithub.com/carlmjohnson/versioninfo v0.22.5/go.mod h1:QT9mph3wcVfISUKd0i9sZfVrPviHuSF+cUtLjm2WSf8=\ngithub.com/cavaliergopher/cpio v1.0.1 h1:KQFSeKmZhv0cr+kawA3a0xTQCU4QxXF1vhU7P7av2KM=\ngithub.com/cavaliergopher/cpio v1.0.1/go.mod h1:pBdaqQjnvXxdS/6CvNDwIANIFSP0xRKI16PX4xejRQc=\ngithub.com/cenkalti/backoff/v4 v4.1.2/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=\ngithub.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=\ngithub.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=\ngithub.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=\ngithub.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=\ngithub.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=\ngithub.com/certifi/gocertifi v0.0.0-20180118203423-deb3ae2ef261/go.mod h1:GJKEexRPVJrBSOjoqN5VNOIKJ5Q3RViH6eu3puDRwx4=\ngithub.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=\ngithub.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=\ngithub.com/charmbracelet/colorprofile v0.4.3 h1:QPa1IWkYI+AOB+fE+mg/5/4HRMZcaXex9t5KX76i20Q=\ngithub.com/charmbracelet/colorprofile v0.4.3/go.mod h1:/zT4BhpD5aGFpqQQqw7a+VtHCzu+zrQtt1zhMt9mR4Q=\ngithub.com/charmbracelet/fang v1.0.0 h1:jESBY40agJOlLYnnv9jE0mLqDGTxEk0hkOnx7YGyRlQ=\ngithub.com/charmbracelet/fang v1.0.0/go.mod h1:P5/DNb9DddQ0Z0dbc0P3ol4/ix5Po7Ofr2KMBfAqoCo=\ngithub.com/charmbracelet/keygen v0.5.4 h1:XQYgf6UEaTGgQSSmiPpIQ78WfseNQp4Pz8N/c1OsrdA=\ngithub.com/charmbracelet/keygen v0.5.4/go.mod h1:t4oBRr41bvK7FaJsAaAQhhkUuHslzFXVjOBwA55CZNM=\ngithub.com/charmbracelet/ultraviolet v0.0.0-20251205161215-1948445e3318 h1:OqDqxQZliC7C8adA7KjelW3OjtAxREfeHkNcd66wpeI=\ngithub.com/charmbracelet/ultraviolet v0.0.0-20251205161215-1948445e3318/go.mod h1:Y6kE2GzHfkyQQVCSL9r2hwokSrIlHGzZG+71+wDYSZI=\ngithub.com/charmbracelet/x/ansi v0.11.7 h1:kzv1kJvjg2S3r9KHo8hDdHFQLEqn4RBCb39dAYC84jI=\ngithub.com/charmbracelet/x/ansi v0.11.7/go.mod h1:9qGpnAVYz+8ACONkZBUWPtL7lulP9No6p1epAihUZwQ=\ngithub.com/charmbracelet/x/exp/charmtone v0.0.0-20250603201427-c31516f43444 h1:IJDiTgVE56gkAGfq0lBEloWgkXMk4hl/bmuPoicI4R0=\ngithub.com/charmbracelet/x/exp/charmtone v0.0.0-20250603201427-c31516f43444/go.mod h1:T9jr8CzFpjhFVHjNjKwbAD7KwBNyFnj2pntAO7F2zw0=\ngithub.com/charmbracelet/x/exp/golden v0.0.0-20250806222409-83e3a29d542f h1:pk6gmGpCE7F3FcjaOEKYriCvpmIN4+6OS/RD0vm4uIA=\ngithub.com/charmbracelet/x/exp/golden v0.0.0-20250806222409-83e3a29d542f/go.mod h1:IfZAMTHB6XkZSeXUqriemErjAWCCzT0LwjKFYCZyw0I=\ngithub.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk=\ngithub.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI=\ngithub.com/charmbracelet/x/termios v0.1.1 h1:o3Q2bT8eqzGnGPOYheoYS8eEleT5ZVNYNy8JawjaNZY=\ngithub.com/charmbracelet/x/termios v0.1.1/go.mod h1:rB7fnv1TgOPOyyKRJ9o+AsTU/vK5WHJ2ivHeut/Pcwo=\ngithub.com/charmbracelet/x/windows v0.2.2 h1:IofanmuvaxnKHuV04sC0eBy/smG6kIKrWG2/jYn2GuM=\ngithub.com/charmbracelet/x/windows v0.2.2/go.mod h1:/8XtdKZzedat74NQFn0NGlGL4soHB0YQZrETF96h75k=\ngithub.com/chrismellard/docker-credential-acr-env v0.0.0-20230304212654-82a0ddb27589 h1:krfRl01rzPzxSxyLyrChD+U+MzsBXbm0OwYYB67uF+4=\ngithub.com/chrismellard/docker-credential-acr-env v0.0.0-20230304212654-82a0ddb27589/go.mod h1:OuDyvmLnMCwa2ep4Jkm6nyA0ocJuZlGyk2gGseVzERM=\ngithub.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=\ngithub.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=\ngithub.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=\ngithub.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=\ngithub.com/clipperhouse/displaywidth v0.11.0 h1:lBc6kY44VFw+TDx4I8opi/EtL9m20WSEFgwIwO+UVM8=\ngithub.com/clipperhouse/displaywidth v0.11.0/go.mod h1:bkrFNkf81G8HyVqmKGxsPufD3JhNl3dSqnGhOoSD/o0=\ngithub.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk=\ngithub.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM=\ngithub.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8=\ngithub.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4=\ngithub.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=\ngithub.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=\ngithub.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=\ngithub.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2 h1:aBangftG7EVZoUb69Os8IaYg++6uMOdKK83QtkkvJik=\ngithub.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2/go.mod h1:qwXFYgsP6T7XnJtbKlf1HP8AjxZZyzxMmc+Lq5GjlU4=\ngithub.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb h1:EDmT6Q9Zs+SbUoc7Ik9EfrFqcylYqgPZ9ANSbTAntnE=\ngithub.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb/go.mod h1:ZjrT6AXHbDs86ZSdt/osfBi5qfexBrKUdONk989Wnk4=\ngithub.com/containerd/continuity v0.4.5 h1:ZRoN1sXq9u7V6QoHMcVWGhOwDFqZ4B9i5H6un1Wh0x4=\ngithub.com/containerd/continuity v0.4.5/go.mod h1:/lNJvtJKUQStBzpVQ1+rasXO1LAWtUQssk28EZvJ3nE=\ngithub.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI=\ngithub.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=\ngithub.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE=\ngithub.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk=\ngithub.com/containerd/stargz-snapshotter/estargz v0.18.2 h1:yXkZFYIzz3eoLwlTUZKz2iQ4MrckBxJjkmD16ynUTrw=\ngithub.com/containerd/stargz-snapshotter/estargz v0.18.2/go.mod h1:XyVU5tcJ3PRpkA9XS2T5us6Eg35yM0214Y+wvrZTBrY=\ngithub.com/coreos/go-oidc/v3 v3.18.0 h1:V9orjXynvu5wiC9SemFTWnG4F45v403aIcjWo0d41+A=\ngithub.com/coreos/go-oidc/v3 v3.18.0/go.mod h1:DYCf24+ncYi+XkIH97GY1+dqoRlbaSI26KVTCI9SrY4=\ngithub.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=\ngithub.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=\ngithub.com/corona10/goimagehash v1.1.0 h1:teNMX/1e+Wn/AYSbLHX8mj+mF9r60R1kBeqE9MkoYwI=\ngithub.com/corona10/goimagehash v1.1.0/go.mod h1:VkvE0mLn84L4aF8vCb6mafVajEb6QYMHl2ZJLn0mOGI=\ngithub.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=\ngithub.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=\ngithub.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=\ngithub.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s=\ngithub.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=\ngithub.com/cyberphone/json-canonicalization v0.0.0-20241213102144-19d51d7fe467 h1:uX1JmpONuD549D73r6cgnxyUu18Zb7yHAy5AYU0Pm4Q=\ngithub.com/cyberphone/json-canonicalization v0.0.0-20241213102144-19d51d7fe467/go.mod h1:uzvlm1mxhHkdfqitSA92i7Se+S9ksOn3a3qmv/kyOCw=\ngithub.com/cyphar/filepath-securejoin v0.6.1 h1:5CeZ1jPXEiYt3+Z6zqprSAgSWiggmpVyciv8syjIpVE=\ngithub.com/cyphar/filepath-securejoin v0.6.1/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc=\ngithub.com/danieljoos/wincred v1.2.3 h1:v7dZC2x32Ut3nEfRH+vhoZGvN72+dQ/snVXo/vMFLdQ=\ngithub.com/danieljoos/wincred v1.2.3/go.mod h1:6qqX0WNrS4RzPZ1tnroDzq9kY3fu1KwE7MRLQK4X0bs=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=\ngithub.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davidmz/go-pageant v1.0.2 h1:bPblRCh5jGU+Uptpz6LgMZGD5hJoOt7otgT454WvHn0=\ngithub.com/davidmz/go-pageant v1.0.2/go.mod h1:P2EDDnMqIwG5Rrp05dTRITj9z2zpGcD9efWSkTNKLIE=\ngithub.com/dghubble/go-twitter v0.0.0-20211115160449-93a8679adecb h1:7ENzkH+O3juL+yj2undESLTaAeRllHwCs/b8z6aWSfc=\ngithub.com/dghubble/go-twitter v0.0.0-20211115160449-93a8679adecb/go.mod h1:qhZBgV9e4WyB1JNjHpcXVkUe3knWUwYuAPB1hITdm50=\ngithub.com/dghubble/oauth1 v0.7.3 h1:EkEM/zMDMp3zOsX2DC/ZQ2vnEX3ELK0/l9kb+vs4ptE=\ngithub.com/dghubble/oauth1 v0.7.3/go.mod h1:oxTe+az9NSMIucDPDCCtzJGsPhciJV33xocHfcR2sVY=\ngithub.com/dghubble/sling v1.4.0/go.mod h1:0r40aNsU9EdDUVBNhfCstAtFgutjgJGYbO1oNzkMoM8=\ngithub.com/dghubble/sling v1.4.2 h1:vs1HIGBbSl2SEALyU+irpYFLZMfc49Fp+jYryFebQjM=\ngithub.com/dghubble/sling v1.4.2/go.mod h1:o0arCOz0HwfqYQJLrRtqunaWOn4X6jxE/6ORKRpVTD4=\ngithub.com/dgryski/trifles v0.0.0-20230903005119-f50d829f2e54 h1:SG7nF6SRlWhcT7cNTs5R6Hk4V2lcmLz2NsG2VnInyNo=\ngithub.com/dgryski/trifles v0.0.0-20230903005119-f50d829f2e54/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA=\ngithub.com/digitorus/pkcs7 v0.0.0-20230713084857-e76b763bdc49/go.mod h1:SKVExuS+vpu2l9IoOc0RwqE7NYnb0JlcFHFnEJkVDzc=\ngithub.com/digitorus/pkcs7 v0.0.0-20250730155240-ffadbf3f398c h1:g349iS+CtAvba7i0Ee9EP1TlTZ9w+UncBY6HSmsFZa0=\ngithub.com/digitorus/pkcs7 v0.0.0-20250730155240-ffadbf3f398c/go.mod h1:mCGGmWkOQvEuLdIRfPIpXViBfpWto4AhwtJlAvo62SQ=\ngithub.com/digitorus/timestamp v0.0.0-20250524132541-c45532741eea h1:ALRwvjsSP53QmnN3Bcj0NpR8SsFLnskny/EIMebAk1c=\ngithub.com/digitorus/timestamp v0.0.0-20250524132541-c45532741eea/go.mod h1:GvWntX9qiTlOud0WkQ6ewFm0LPy5JUR1Xo0Ngbd1w6Y=\ngithub.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi/U=\ngithub.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE=\ngithub.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=\ngithub.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=\ngithub.com/distribution/distribution/v3 v3.1.0 h1:u1v788HreKTLGdNY6s7px8Exgrs9mZ9UrCDjSrpCM8g=\ngithub.com/distribution/distribution/v3 v3.1.0/go.mod h1:73BuF5/ziMHNVt7nnL1roYpH4Eg/FgUlKZm3WryIx/o=\ngithub.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=\ngithub.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=\ngithub.com/docker/cli v29.4.3+incompatible h1:u+UliYm2J/rYrIh2FqHQg32neRG8GjbvNuwQRTzGspU=\ngithub.com/docker/cli v29.4.3+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=\ngithub.com/docker/docker v28.5.2+incompatible h1:DBX0Y0zAjZbSrm1uzOkdr1onVghKaftjlSWt4AFexzM=\ngithub.com/docker/docker v28.5.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=\ngithub.com/docker/docker-credential-helpers v0.9.7 h1:jaPIxEIDz5bQeghNAdzz0ETwMMnM4vzjZlxz3pWP4JA=\ngithub.com/docker/docker-credential-helpers v0.9.7/go.mod h1:v1S+hepowrQXITkEfw6o4+BMbGot02wiKpzWhGUZK6c=\ngithub.com/docker/go-connections v0.7.0 h1:6SsRfJddP22WMrCkj19x9WKjEDTB+ahsdiGYf0mN39c=\ngithub.com/docker/go-connections v0.7.0/go.mod h1:no1qkHdjq7kLMGUXYAduOhYPSJxxvgWBh7ogVvptn3Q=\ngithub.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQV8=\ngithub.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw=\ngithub.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=\ngithub.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=\ngithub.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=\ngithub.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=\ngithub.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o=\ngithub.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE=\ngithub.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=\ngithub.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=\ngithub.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=\ngithub.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=\ngithub.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=\ngithub.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=\ngithub.com/envoyproxy/go-control-plane v0.14.0 h1:hbG2kr4RuFj222B6+7T83thSPqLjwBIfQawTkC++2HA=\ngithub.com/envoyproxy/go-control-plane v0.14.0/go.mod h1:NcS5X47pLl/hfqxU70yPwL9ZMkUlwlKxtAohpi2wBEU=\ngithub.com/envoyproxy/go-control-plane/envoy v1.37.0 h1:u3riX6BoYRfF4Dr7dwSOroNfdSbEPe9Yyl09/B6wBrQ=\ngithub.com/envoyproxy/go-control-plane/envoy v1.37.0/go.mod h1:DReE9MMrmecPy+YvQOAOHNYMALuowAnbjjEMkkWOi6A=\ngithub.com/envoyproxy/go-control-plane/ratelimit v0.1.0 h1:/G9QYbddjL25KvtKTv3an9lx6VBE2cnb8wp1vEGNYGI=\ngithub.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4=\ngithub.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=\ngithub.com/envoyproxy/protoc-gen-validate v1.3.3 h1:MVQghNeW+LZcmXe7SY1V36Z+WFMDjpqGAGacLe2T0ds=\ngithub.com/envoyproxy/protoc-gen-validate v1.3.3/go.mod h1:TsndJ/ngyIdQRhMcVVGDDHINPLWB7C82oDArY51KfB0=\ngithub.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjTM0wiaDU=\ngithub.com/evanphx/json-patch/v5 v5.9.11/go.mod h1:3j+LviiESTElxA4p3EMKAB9HXj3/XEtnUf6OZxqIQTM=\ngithub.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=\ngithub.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=\ngithub.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=\ngithub.com/fatih/set v0.2.1 h1:nn2CaJyknWE/6txyUDGwysr3G5QC6xWB/PtVjPBbeaA=\ngithub.com/fatih/set v0.2.1/go.mod h1:+RKtMCH+favT2+3YecHGxcc0b4KyVWA1QWWJUs4E0CI=\ngithub.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=\ngithub.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=\ngithub.com/flopp/go-findfont v0.1.0 h1:lPn0BymDUtJo+ZkV01VS3661HL6F4qFlkhcJN55u6mU=\ngithub.com/flopp/go-findfont v0.1.0/go.mod h1:wKKxRDjD024Rh7VMwoU90i6ikQRCr+JTHB5n4Ejkqvw=\ngithub.com/fogleman/gg v1.3.0 h1:/7zJX8F6AaYQc57WQCyN9cAIz+4bCJGO9B+dyW29am8=\ngithub.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=\ngithub.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=\ngithub.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=\ngithub.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=\ngithub.com/fsnotify/fsnotify v1.10.1 h1:b0/UzAf9yR5rhf3RPm9gf3ehBPpf0oZKIjtpKrx59Ho=\ngithub.com/fsnotify/fsnotify v1.10.1/go.mod h1:TLheqan6HD6GBK6PrDWyDPBaEV8LspOxvPSjC+bVfgo=\ngithub.com/gabriel-vasile/mimetype v1.4.13 h1:46nXokslUBsAJE/wMsp5gtO500a4F3Nkz9Ufpk2AcUM=\ngithub.com/gabriel-vasile/mimetype v1.4.13/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=\ngithub.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=\ngithub.com/github/smimesign v0.2.0 h1:Hho4YcX5N1I9XNqhq0fNx0Sts8MhLonHd+HRXVGNjvk=\ngithub.com/github/smimesign v0.2.0/go.mod h1:iZiiwNT4HbtGRVqCQu7uJPEZCuEE5sfSSttcnePkDl4=\ngithub.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=\ngithub.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=\ngithub.com/go-chi/chi/v5 v5.2.5 h1:Eg4myHZBjyvJmAFjFvWgrqDTXFyOzjj7YIm3L3mu6Ug=\ngithub.com/go-chi/chi/v5 v5.2.5/go.mod h1:X7Gx4mteadT3eDOMTsXzmI4/rwUpOwBHLpAfupzFJP0=\ngithub.com/go-fed/httpsig v1.1.0 h1:9M+hb0jkEICD8/cAiNqEB66R87tTINszBRTjwjQzWcI=\ngithub.com/go-fed/httpsig v1.1.0/go.mod h1:RCMrTZvN1bJYtofsG4rd5NaO5obxQ5xBkdiS7xsT7bM=\ngithub.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=\ngithub.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=\ngithub.com/go-git/go-billy/v5 v5.9.0 h1:jItGXszUDRtR/AlferWPTMN4j38BQ88XnXKbilmmBPA=\ngithub.com/go-git/go-billy/v5 v5.9.0/go.mod h1:jCnQMLj9eUgGU7+ludSTYoZL/GGmii14RxKFj7ROgHw=\ngithub.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=\ngithub.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=\ngithub.com/go-git/go-git/v5 v5.19.0 h1:+WkVUQZSy/F1Gb13udrMKjIM2PrzsNfDKFSfo5tkMtc=\ngithub.com/go-git/go-git/v5 v5.19.0/go.mod h1:Pb1v0c7/g8aGQJwx9Us09W85yGoyvSwuhEGMH7zjDKQ=\ngithub.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=\ngithub.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=\ngithub.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=\ngithub.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA=\ngithub.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=\ngithub.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=\ngithub.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=\ngithub.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=\ngithub.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=\ngithub.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=\ngithub.com/go-openapi/analysis v0.25.0 h1:EnjAq1yO8wEO9HbPmY8vLPEIkdZuuFhCAKBPvCB7bCs=\ngithub.com/go-openapi/analysis v0.25.0/go.mod h1:5WFTRE43WLkPG9r9OtlMfqkkvUTYLVVCIxLlEpyF8kE=\ngithub.com/go-openapi/errors v0.22.7 h1:JLFBGC0Apwdzw3484MmBqspjPbwa2SHvpDm0u5aGhUA=\ngithub.com/go-openapi/errors v0.22.7/go.mod h1://QW6SD9OsWtH6gHllUCddOXDL0tk0ZGNYHwsw4sW3w=\ngithub.com/go-openapi/jsonpointer v0.23.1 h1:1HBACs7XIwR2RcmItfdSFlALhGbe6S92p0ry4d1GWg4=\ngithub.com/go-openapi/jsonpointer v0.23.1/go.mod h1:iWRmZTrGn7XwYhtPt/fvdSFj1OfNBngqRT2UG3BxSqY=\ngithub.com/go-openapi/jsonreference v0.21.5 h1:6uCGVXU/aNF13AQNggxfysJ+5ZcU4nEAe+pJyVWRdiE=\ngithub.com/go-openapi/jsonreference v0.21.5/go.mod h1:u25Bw85sX4E2jzFodh1FOKMTZLcfifd1Q+iKKOUxExw=\ngithub.com/go-openapi/loads v0.23.3 h1:g5Xap1JfwKkUnZdn+S0L3SzBDpcTIYzZ5Qaag0YDkKQ=\ngithub.com/go-openapi/loads v0.23.3/go.mod h1:NOH07zLajXo8y55hom0omlHWDVVvCwBM/S+csCK8LqA=\ngithub.com/go-openapi/runtime v0.29.5 h1:uc5+/TtqLIfDBTUxnF3uppoGMt+9DzonwUWsviINlrY=\ngithub.com/go-openapi/runtime v0.29.5/go.mod h1:D9IUbWccdYv+km8QwmAm90FZvDcQk47vP2Y7y5as/D8=\ngithub.com/go-openapi/spec v0.22.4 h1:4pxGjipMKu0FzFiu/DPwN3CTBRlVM2yLf/YTWorYfDQ=\ngithub.com/go-openapi/spec v0.22.4/go.mod h1:WQ6Ai0VPWMZgMT4XySjlRIE6GP1bGQOtEThn3gcWLtQ=\ngithub.com/go-openapi/strfmt v0.26.2 h1:ysjheCh4i1rmFEo2LanhELDNucNzfWTZhUDKgWWPaFM=\ngithub.com/go-openapi/strfmt v0.26.2/go.mod h1:fXh1e449cyUn2NYuz+wb3wARBUdMl7qPEZwX00nqivY=\ngithub.com/go-openapi/swag v0.26.0 h1:GVDXCmfvhfu1BxiHo8/FA+BbKmhecHnG3varjON5/RI=\ngithub.com/go-openapi/swag v0.26.0/go.mod h1:82g3193sZJRbocs7bNCqGfIgq8pkuwVwCfhKIRlEQF0=\ngithub.com/go-openapi/swag/cmdutils v0.26.0 h1:iowihOcvq7y4egO8cOq0dmfohz6wfeQ63U1EnuhO2TU=\ngithub.com/go-openapi/swag/cmdutils v0.26.0/go.mod h1:Sm1MVFMkF6guJJ+pQqHnQA3N0j9qALV3NxzDSv6bETM=\ngithub.com/go-openapi/swag/conv v0.26.0 h1:5yGGsPYI1ZCva93U0AoKi/iZrNhaJEjr324YVsiD89I=\ngithub.com/go-openapi/swag/conv v0.26.0/go.mod h1:tpAmIL7X58VPnHHiSO4uE3jBeRamGsFsfdDeDtb5ECE=\ngithub.com/go-openapi/swag/fileutils v0.26.0 h1:WJoPRvsA7QRiiWluowkLJa9jaYR7FCuxmDvnCgaRRxU=\ngithub.com/go-openapi/swag/fileutils v0.26.0/go.mod h1:0WDJ7lp67eNjPMO50wAWYlKvhOb6CQ37rzR7wrgI8Tc=\ngithub.com/go-openapi/swag/jsonname v0.26.0 h1:gV1NFX9M8avo0YSpmWogqfQISigCmpaiNci8cGECU5w=\ngithub.com/go-openapi/swag/jsonname v0.26.0/go.mod h1:urBBR8bZNoDYGr653ynhIx+gTeIz0ARZxHkAPktJK2M=\ngithub.com/go-openapi/swag/jsonutils v0.26.0 h1:FawFML2iAXsPqmERscuMPIHmFsoP1tOqWkxBaKNMsnA=\ngithub.com/go-openapi/swag/jsonutils v0.26.0/go.mod h1:2VmA0CJlyFqgawOaPI9psnjFDqzyivIqLYN34t9p91E=\ngithub.com/go-openapi/swag/jsonutils/fixtures_test v0.26.0 h1:apqeINu/ICHouqiRZbyFvuDge5jCmmLTqGQ9V95EaOM=\ngithub.com/go-openapi/swag/jsonutils/fixtures_test v0.26.0/go.mod h1:AyM6QT8uz5IdKxk5akv0y6u4QvcL9GWERt0Jx/F/R8Y=\ngithub.com/go-openapi/swag/loading v0.26.0 h1:Apg6zaKhCJurpJer0DCxq99qwmhFddBhaMX7kilDcko=\ngithub.com/go-openapi/swag/loading v0.26.0/go.mod h1:dBxQ/6V2uBaAQdevN18VELE6xSpJWZxLX4txe12JwDg=\ngithub.com/go-openapi/swag/mangling v0.26.0 h1:Du2YC4YLA/Y5m/YKQd7AnY5qq0wRKSFZTTt8ktFaXcQ=\ngithub.com/go-openapi/swag/mangling v0.26.0/go.mod h1:jifS7W9vbg+pw63bT+GI53otluMQL3CeemuyCHKwVx0=\ngithub.com/go-openapi/swag/netutils v0.26.0 h1:CmZp+ZT7HrmFwrC3GdGsXBq2+42T1bjKBapcqVpIs3c=\ngithub.com/go-openapi/swag/netutils v0.26.0/go.mod h1:5iK+Ok3ZohWWex1C50BFTPexi03UaPwjW4Oj8kgrpwo=\ngithub.com/go-openapi/swag/stringutils v0.26.0 h1:qZQngLxs5s7SLijc3N2ZO+fUq2o8LjuWAASSrJuh+xg=\ngithub.com/go-openapi/swag/stringutils v0.26.0/go.mod h1:sWn5uY+QIIspwPhvgnqJsH8xqFT2ZbYcvbcFanRyhFE=\ngithub.com/go-openapi/swag/typeutils v0.26.0 h1:2kdEwdiNWy+JJdOvu5MA2IIg2SylWAFuuyQIKYybfq4=\ngithub.com/go-openapi/swag/typeutils v0.26.0/go.mod h1:oovDuIUvTrEHVMqWilQzKzV4YlSKgyZmFh7AlfABNVE=\ngithub.com/go-openapi/swag/yamlutils v0.26.0 h1:H7O8l/8NJJQ/oiReEN+oMpnGMyt8G0hl460nRZxhLMQ=\ngithub.com/go-openapi/swag/yamlutils v0.26.0/go.mod h1:1evKEGAtP37Pkwcc7EWMF0hedX0/x3Rkvei2wtG/TbU=\ngithub.com/go-openapi/testify/enable/yaml/v2 v2.5.0 h1:3hZD1fwydvCx/cc1R2uYNQirHqf2s6lqpKV3FcNTURA=\ngithub.com/go-openapi/testify/enable/yaml/v2 v2.5.0/go.mod h1:TvDZKBH7ZbMaF3EqH2AwTvNQCmzyZq8K1agRjf1B+Nk=\ngithub.com/go-openapi/testify/v2 v2.5.0 h1:UOCr63aAsMIDydZbZGqo5Ev01D4eydItRbekDuZMJLw=\ngithub.com/go-openapi/testify/v2 v2.5.0/go.mod h1:SgsVHtfooshd0tublTtJ50FPKhujf47YRqauXXOUxfw=\ngithub.com/go-openapi/validate v0.25.2 h1:12NsfLAwGegqbGWr2CnvT65X/Q2USJipmJ9b7xDJZz0=\ngithub.com/go-openapi/validate v0.25.2/go.mod h1:Pgl1LpPPGFnZ+ys4/hTlDiRYQdI1ocKypgE+8Q8BLfY=\ngithub.com/go-restruct/restruct v1.2.0-alpha h1:2Lp474S/9660+SJjpVxoKuWX09JsXHSrdV7Nv3/gkvc=\ngithub.com/go-restruct/restruct v1.2.0-alpha/go.mod h1:KqrpKpn4M8OLznErihXTGLlsXFGeLxHUrLRRI/1YjGk=\ngithub.com/go-rod/rod v0.116.2 h1:A5t2Ky2A+5eD/ZJQr1EfsQSe5rms5Xof/qj296e+ZqA=\ngithub.com/go-rod/rod v0.116.2/go.mod h1:H+CMO9SCNc2TJ2WfrG+pKhITz57uGNYU43qYHh438Mg=\ngithub.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo=\ngithub.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=\ngithub.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U=\ngithub.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=\ngithub.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=\ngithub.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=\ngithub.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0=\ngithub.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=\ngithub.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=\ngithub.com/goccy/go-graphviz v0.2.10 h1:jHu/1I0Iw0xIzzYk96Ous/ZeuD11Rt2oW8juHdIE30g=\ngithub.com/goccy/go-graphviz v0.2.10/go.mod h1:LRlMnNmY17QbN6fLnvOzY7g0rXQjLKAhzxeTHbEUM6w=\ngithub.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=\ngithub.com/godbus/dbus/v5 v5.2.2 h1:TUR3TgtSVDmjiXOgAAyaZbYmIeP3DPkld3jgKGV8mXQ=\ngithub.com/godbus/dbus/v5 v5.2.2/go.mod h1:3AAv2+hPq5rdnr5txxxRwiGjPXamgoIHgz9FPBfOp3c=\ngithub.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=\ngithub.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=\ngithub.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=\ngithub.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=\ngithub.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=\ngithub.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI=\ngithub.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=\ngithub.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY=\ngithub.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=\ngithub.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=\ngithub.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=\ngithub.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=\ngithub.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=\ngithub.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=\ngithub.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=\ngithub.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=\ngithub.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=\ngithub.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=\ngithub.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=\ngithub.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=\ngithub.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=\ngithub.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=\ngithub.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=\ngithub.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=\ngithub.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=\ngithub.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=\ngithub.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=\ngithub.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=\ngithub.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=\ngithub.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=\ngithub.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=\ngithub.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=\ngithub.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=\ngithub.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=\ngithub.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM=\ngithub.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=\ngithub.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=\ngithub.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=\ngithub.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=\ngithub.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=\ngithub.com/google/certificate-transparency-go v1.3.3 h1:hq/rSxztSkXN2tx/3jQqF6Xc0O565UQPdHrOWvZwybo=\ngithub.com/google/certificate-transparency-go v1.3.3/go.mod h1:iR17ZgSaXRzSa5qvjFl8TnVD5h8ky2JMVio+dzoKMgA=\ngithub.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=\ngithub.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=\ngithub.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=\ngithub.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=\ngithub.com/google/go-containerregistry v0.21.5 h1:KTJG9Pn/jC0VdZR6ctV3/jcN+q6/Iqlx0sTVz3ywZlM=\ngithub.com/google/go-containerregistry v0.21.5/go.mod h1:ySvMuiWg+dOsRW0Hw8GYwfMwBlNRTmpYBFJPlkco5zU=\ngithub.com/google/go-github/v84 v84.0.0 h1:I/0Xn5IuChMe8TdmI2bbim5nyhaRFJ7DEdzmD2w+yVA=\ngithub.com/google/go-github/v84 v84.0.0/go.mod h1:WwYL1z1ajRdlaPszjVu/47x1L0PXukJBn73xsiYrRRQ=\ngithub.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=\ngithub.com/google/go-querystring v1.2.0 h1:yhqkPbu2/OH+V9BfpCVPZkNmUXhb2gBxJArfhIxNtP0=\ngithub.com/google/go-querystring v1.2.0/go.mod h1:8IFJqpSRITyJ8QhQ13bmbeMBDfmeEJZD5A0egEOmkqU=\ngithub.com/google/go-replayers/grpcreplay v1.3.0 h1:1Keyy0m1sIpqstQmgz307zhiJ1pV4uIlFds5weTmxbo=\ngithub.com/google/go-replayers/grpcreplay v1.3.0/go.mod h1:v6NgKtkijC0d3e3RW8il6Sy5sqRVUwoQa4mHOGEy8DI=\ngithub.com/google/go-replayers/httpreplay v1.2.0 h1:VM1wEyyjaoU53BwrOnaf9VhAyQQEEioJvFYxYcLRKzk=\ngithub.com/google/go-replayers/httpreplay v1.2.0/go.mod h1:WahEFFZZ7a1P4VM1qEeHy+tME4bwyqPcwWbNlUI1Mcg=\ngithub.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=\ngithub.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=\ngithub.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=\ngithub.com/google/ko v0.18.2-0.20260407063826-ae9c7272d7de h1:vl2sUQed6LbK69Cws/LwoeSw7NYotn089jA08Li9wgQ=\ngithub.com/google/ko v0.18.2-0.20260407063826-ae9c7272d7de/go.mod h1:HjQh5NsrhK9hhGfcZiRV0Lr97Oo7s4RZnboONUapRUw=\ngithub.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no=\ngithub.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=\ngithub.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=\ngithub.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=\ngithub.com/google/martian/v3 v3.3.3 h1:DIhPTQrbPkgs2yJYdXU/eNACCG5DVQjySNRNlflZ9Fc=\ngithub.com/google/martian/v3 v3.3.3/go.mod h1:iEPrYcgCF7jA9OtScMFQyAlZZ4YXTKEtJ1E6RWzmBA0=\ngithub.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=\ngithub.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=\ngithub.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=\ngithub.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=\ngithub.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=\ngithub.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=\ngithub.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=\ngithub.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=\ngithub.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=\ngithub.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=\ngithub.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=\ngithub.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=\ngithub.com/google/rpmpack v0.7.1 h1:YdWh1IpzOjBz60Wvdw0TU0A5NWP+JTVHA5poDqwMO2o=\ngithub.com/google/rpmpack v0.7.1/go.mod h1:h1JL16sUTWCLI/c39ox1rDaTBo3BXUQGjczVJyK4toU=\ngithub.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=\ngithub.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=\ngithub.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=\ngithub.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=\ngithub.com/google/trillian v1.7.2 h1:EPBxc4YWY4Ak8tcuhyFleY+zYlbCDCa4Sn24e1Ka8Js=\ngithub.com/google/trillian v1.7.2/go.mod h1:mfQJW4qRH6/ilABtPYNBerVJAJ/upxHLX81zxNQw05s=\ngithub.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=\ngithub.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/google/wire v0.7.0 h1:JxUKI6+CVBgCO2WToKy/nQk0sS+amI9z9EjVmdaocj4=\ngithub.com/google/wire v0.7.0/go.mod h1:n6YbUQD9cPKTnHXEBN2DXlOp/mVADhVErcMFb0v3J18=\ngithub.com/googleapis/enterprise-certificate-proxy v0.3.15 h1:xolVQTEXusUcAA5UgtyRLjelpFFHWlPQ4XfWGc7MBas=\ngithub.com/googleapis/enterprise-certificate-proxy v0.3.15/go.mod h1:vqVt9yG9480NtzREnTlmGSBmFrA+bzb0yl0TxoBQXOg=\ngithub.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=\ngithub.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=\ngithub.com/googleapis/gax-go/v2 v2.22.0 h1:PjIWBpgGIVKGoCXuiCoP64altEJCj3/Ei+kSU5vlZD4=\ngithub.com/googleapis/gax-go/v2 v2.22.0/go.mod h1:irWBbALSr0Sk3qlqb9SyJ1h68WjgeFuiOzI4Rqw5+aY=\ngithub.com/gookit/color v1.2.5/go.mod h1:AhIE+pS6D4Ql0SQWbBeXPHw7gY0/sjHoA4s/n1KB7xg=\ngithub.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=\ngithub.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g=\ngithub.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k=\ngithub.com/goreleaser/chglog v0.7.4 h1:3pnNt/XCrUcAOq+KC91Azlgp5CRv4GHo1nl8Aws7OzI=\ngithub.com/goreleaser/chglog v0.7.4/go.mod h1:dTVoZZagTz7hHdWaZ9OshHntKiF44HbWIHWxYJQ/h0Y=\ngithub.com/goreleaser/fileglob v1.4.0 h1:Y7zcUnzQjT1gbntacGAkIIfLv+OwojxTXBFxjSFoBBs=\ngithub.com/goreleaser/fileglob v1.4.0/go.mod h1:1pbHx7hhmJIxNZvm6fi6WVrnP0tndq6p3ayWdLn1Yf8=\ngithub.com/goreleaser/go-shellwords v1.0.13 h1:ivvhC/RvUyud74c0urb1ZGYWPYGibY5QiFzcwHCege4=\ngithub.com/goreleaser/go-shellwords v1.0.13/go.mod h1:UtDFSSvW7wQL/4jmyzZbuP6HfI6R+oSm0v63cs61oDw=\ngithub.com/goreleaser/goreleaser/v2 v2.15.4 h1:nQljy2KLHhzm3aGuL7IAQ2sqIcF0t1hcYiNy1EzUiqE=\ngithub.com/goreleaser/goreleaser/v2 v2.15.4/go.mod h1:1N0vuARdSLTJX03iWLvTM8avidylXBDXIWs+hPHSdVw=\ngithub.com/goreleaser/nfpm/v2 v2.46.3 h1:sMNcGjbVnABe33avsFo++WwKubY8msP5j9ieulX09VI=\ngithub.com/goreleaser/nfpm/v2 v2.46.3/go.mod h1:wYKICOnvMALZcoGM20TcP9QuIV+Fb9fmLH364HyTvDQ=\ngithub.com/goreleaser/quill v0.0.0-20260418030907-a259ef5caf05 h1:iillcQW89rysvFhPQKQEKfQNcQi+Yu+f2DH1ZF7i6kQ=\ngithub.com/goreleaser/quill v0.0.0-20260418030907-a259ef5caf05/go.mod h1:gDef5vWJO4JWWFeWCnJ+hQnJsAEQWazvzmh39qd5Ods=\ngithub.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=\ngithub.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=\ngithub.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=\ngithub.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=\ngithub.com/graph-gophers/graphql-go v1.9.0 h1:yu0ucKHLc5qGpRwLYKIWtr9bOoxovkWasuBrPQwlHls=\ngithub.com/graph-gophers/graphql-go v1.9.0/go.mod h1:23olKZ7duEvHlF/2ELEoSZaY1aNPfShjP782SOoNTyM=\ngithub.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI=\ngithub.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8=\ngithub.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=\ngithub.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0 h1:5VipnvEpbqr2gA2VbM+nYVbkIF28c5ZQfqCBQ5g2xfk=\ngithub.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0/go.mod h1:Hyl3n6Twe1hvtd9XUXDec4pTvgMSEixRuQKPTMH2bNs=\ngithub.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=\ngithub.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=\ngithub.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=\ngithub.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=\ngithub.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=\ngithub.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=\ngithub.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=\ngithub.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=\ngithub.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k=\ngithub.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=\ngithub.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=\ngithub.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=\ngithub.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=\ngithub.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=\ngithub.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=\ngithub.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=\ngithub.com/hashicorp/go-retryablehttp v0.7.8 h1:ylXZWnqa7Lhqpk0L1P1LzDtGcCR0rPVUrx/c8Unxc48=\ngithub.com/hashicorp/go-retryablehttp v0.7.8/go.mod h1:rjiScheydd+CxvumBsIrFKlx3iS0jrZ7LvzFGFmuKbw=\ngithub.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=\ngithub.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc=\ngithub.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=\ngithub.com/hashicorp/go-secure-stdlib/parseutil v0.2.0 h1:U+kC2dOhMFQctRfhK0gRctKAPTloZdMU5ZJxaesJ/VM=\ngithub.com/hashicorp/go-secure-stdlib/parseutil v0.2.0/go.mod h1:Ll013mhdmsVDuoIXVfBtvgGJsXDYkTw1kooNcoCXuE0=\ngithub.com/hashicorp/go-secure-stdlib/strutil v0.1.2 h1:kes8mmyCpxJsI7FTwtzRqEy9CdjCtrXrXGuOpxEA7Ts=\ngithub.com/hashicorp/go-secure-stdlib/strutil v0.1.2/go.mod h1:Gou2R9+il93BqX25LAKCLuM+y9U2T4hlwvT1yprcna4=\ngithub.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=\ngithub.com/hashicorp/go-sockaddr v1.0.7 h1:G+pTkSO01HpR5qCxg7lxfsFEZaG+C0VssTy/9dbT+Fw=\ngithub.com/hashicorp/go-sockaddr v1.0.7/go.mod h1:FZQbEYa1pxkQ7WLpyXJ6cbjpT8q0YgQaK/JakXqGyWw=\ngithub.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=\ngithub.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=\ngithub.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=\ngithub.com/hashicorp/go-version v1.9.0 h1:CeOIz6k+LoN3qX9Z0tyQrPtiB1DFYRPfCIBtaXPSCnA=\ngithub.com/hashicorp/go-version v1.9.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=\ngithub.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=\ngithub.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=\ngithub.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=\ngithub.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c=\ngithub.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=\ngithub.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=\ngithub.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=\ngithub.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=\ngithub.com/hashicorp/hcl v1.0.1-vault-7 h1:ag5OxFVy3QYTFTJODRzTKVZ6xvdfLLCA1cy/Y6xGI0I=\ngithub.com/hashicorp/hcl v1.0.1-vault-7/go.mod h1:XYhtn6ijBSAj6n4YqAaf7RBPS4I06AItNorpy+MoQNM=\ngithub.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=\ngithub.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=\ngithub.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=\ngithub.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=\ngithub.com/hashicorp/vault/api v1.22.0 h1:+HYFquE35/B74fHoIeXlZIP2YADVboaPjaSicHEZiH0=\ngithub.com/hashicorp/vault/api v1.22.0/go.mod h1:IUZA2cDvr4Ok3+NtK2Oq/r+lJeXkeCrHRmqdyWfpmGM=\ngithub.com/howeyc/gopass v0.0.0-20210920133722-c8aef6fb66ef h1:A9HsByNhogrvm9cWb28sjiS3i7tcKCkflWFEkHfuAgM=\ngithub.com/howeyc/gopass v0.0.0-20210920133722-c8aef6fb66ef/go.mod h1:lADxMC39cJJqL93Duh1xhAs4I2Zs8mKS89XWXFGp9cs=\ngithub.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI=\ngithub.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=\ngithub.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=\ngithub.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=\ngithub.com/in-toto/attestation v1.2.0 h1:aPRUZ3azbqD7yEBD5fP3TD8Dszf+YHo284SOcpahjQk=\ngithub.com/in-toto/attestation v1.2.0/go.mod h1:r79G45gOmzPismgObLSL+rZTFxUgZLOQJI6LofTZgXk=\ngithub.com/in-toto/in-toto-golang v0.11.0 h1:nfidMYBFx+E0lnmX5KUnN2Pdm8zdNKal1ayjJuzzRoA=\ngithub.com/in-toto/in-toto-golang v0.11.0/go.mod h1:u3PjTnwFKjp5a1YCcw8SJg0G+tMeKfVoWsWeFMDCMtw=\ngithub.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=\ngithub.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=\ngithub.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=\ngithub.com/invopop/jsonschema v0.14.0 h1:MHQqLhvpNUZfw+hM3AZDYK7jxO8FZoQeQM77g8iyZjg=\ngithub.com/invopop/jsonschema v0.14.0/go.mod h1:ygm6C2EaVNMBDPpaPlnOA2pFAxBnxGjFlMZABxm9n2I=\ngithub.com/ipfs/bbloom v0.1.0 h1:nIWwfIE3AaG7RCDQIsrUonGCOTp7qSXzxH7ab/ss964=\ngithub.com/ipfs/bbloom v0.1.0/go.mod h1:lDy3A3i6ndgEW2z1CaRFvDi5/ZTzgM1IxA/pkL7Wgts=\ngithub.com/ipfs/boxo v0.39.0 h1:u9jLf5pLx5SWROXjHtj8VMvv+iDlMbiTyZ/vVTQ4VhI=\ngithub.com/ipfs/boxo v0.39.0/go.mod h1:k9YCvMjytFguMHndEiGdCGMMj4b7CkdOT44vtgAxOdk=\ngithub.com/ipfs/go-block-format v0.2.3 h1:mpCuDaNXJ4wrBJLrtEaGFGXkferrw5eqVvzaHhtFKQk=\ngithub.com/ipfs/go-block-format v0.2.3/go.mod h1:WJaQmPAKhD3LspLixqlqNFxiZ3BZ3xgqxxoSR/76pnA=\ngithub.com/ipfs/go-cid v0.6.1 h1:T5TnNb08+ueovG76Z5gx1L4Y7QOaGTXHg1F6raWFxIc=\ngithub.com/ipfs/go-cid v0.6.1/go.mod h1:zrY0SwOhjrrIdfPQ/kf+k1sXyJ0QE7cMxfCployLBs0=\ngithub.com/ipfs/go-datastore v0.9.1 h1:67Po2epre/o0UxrmkzdS9ZTe2GFGODgTd2odx8Wh6Yo=\ngithub.com/ipfs/go-datastore v0.9.1/go.mod h1:zi07Nvrpq1bQwSkEnx3bfjz+SQZbdbWyCNvyxMh9pN0=\ngithub.com/ipfs/go-detect-race v0.0.1 h1:qX/xay2W3E4Q1U7d9lNs1sU9nvguX0a7319XbyQ6cOk=\ngithub.com/ipfs/go-detect-race v0.0.1/go.mod h1:8BNT7shDZPo99Q74BpGMK+4D8Mn4j46UU0LZ723meps=\ngithub.com/ipfs/go-ipfs-blockstore v1.3.1 h1:cEI9ci7V0sRNivqaOr0elDsamxXFxJMMMy7PTTDQNsQ=\ngithub.com/ipfs/go-ipfs-blockstore v1.3.1/go.mod h1:KgtZyc9fq+P2xJUiCAzbRdhhqJHvsw8u2Dlqy2MyRTE=\ngithub.com/ipfs/go-ipfs-ds-help v1.1.1 h1:B5UJOH52IbcfS56+Ul+sv8jnIV10lbjLF5eOO0C66Nw=\ngithub.com/ipfs/go-ipfs-ds-help v1.1.1/go.mod h1:75vrVCkSdSFidJscs8n4W+77AtTpCIAdDGAwjitJMIo=\ngithub.com/ipfs/go-ipfs-util v0.0.3 h1:2RFdGez6bu2ZlZdI+rWfIdbQb1KudQp3VGwPtdNCmE0=\ngithub.com/ipfs/go-ipfs-util v0.0.3/go.mod h1:LHzG1a0Ig4G+iZ26UUOMjHd+lfM84LZCrn17xAKWBvs=\ngithub.com/ipfs/go-ipld-cbor v0.2.1 h1:H05yEJbK/hxg0uf2AJhyerBDbjOuHX4yi+1U/ogRa7E=\ngithub.com/ipfs/go-ipld-cbor v0.2.1/go.mod h1:x9Zbeq8CoE5R2WicYgBMcr/9mnkQ0lHddYWJP2sMV3A=\ngithub.com/ipfs/go-ipld-format v0.6.3 h1:9/lurLDTotJpZSuL++gh3sTdmcFhVkCwsgx2+rAh4j8=\ngithub.com/ipfs/go-ipld-format v0.6.3/go.mod h1:74ilVN12NXVMIV+SrBAyC05UJRk0jVvGqdmrcYZvCBk=\ngithub.com/ipfs/go-log v1.0.5 h1:2dOuUCB1Z7uoczMWgAyDck5JLb72zHzrMnGnCNNbvY8=\ngithub.com/ipfs/go-log v1.0.5/go.mod h1:j0b8ZoR+7+R99LD9jZ6+AJsrzkPbSXbZfGakb5JPtIo=\ngithub.com/ipfs/go-log/v2 v2.1.3/go.mod h1:/8d0SH3Su5Ooc31QlL1WysJhvyOTDCjcCZ9Axpmri6g=\ngithub.com/ipfs/go-log/v2 v2.9.2 h1:O/5BB0elpkRILvT24rCJ5976wWd7u0nJ436T3rdYdc4=\ngithub.com/ipfs/go-log/v2 v2.9.2/go.mod h1:RziRwwXWhndlk8L75RnEe0zeAYaq2heKtEMc3jqUov0=\ngithub.com/ipfs/go-metrics-interface v0.3.0 h1:YwG7/Cy4R94mYDUuwsBfeziJCVm9pBMJ6q/JR9V40TU=\ngithub.com/ipfs/go-metrics-interface v0.3.0/go.mod h1:OxxQjZDGocXVdyTPocns6cOLwHieqej/jos7H4POwoY=\ngithub.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=\ngithub.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=\ngithub.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=\ngithub.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=\ngithub.com/jackc/pgx/v5 v5.9.2 h1:3ZhOzMWnR4yJ+RW1XImIPsD1aNSz4T4fyP7zlQb56hw=\ngithub.com/jackc/pgx/v5 v5.9.2/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4=\ngithub.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=\ngithub.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=\ngithub.com/jarcoal/httpmock v1.4.1 h1:0Ju+VCFuARfFlhVXFc2HxlcQkfB+Xq12/EotHko+x2A=\ngithub.com/jarcoal/httpmock v1.4.1/go.mod h1:ftW1xULwo+j0R0JJkJIIi7UKigZUXCLLanykgjwBXL0=\ngithub.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=\ngithub.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=\ngithub.com/jedisct1/go-minisign v0.0.0-20241212093149-d2f9f49435c7 h1:FWpSWRD8FbVkKQu8M1DM9jF5oXFLyE+XpisIYfdzbic=\ngithub.com/jedisct1/go-minisign v0.0.0-20241212093149-d2f9f49435c7/go.mod h1:BMxO138bOokdgt4UaxZiEfypcSHX0t6SIFimVP1oRfk=\ngithub.com/jellydator/ttlcache/v3 v3.4.0 h1:YS4P125qQS0tNhtL6aeYkheEaB/m8HCqdMMP4mnWdTY=\ngithub.com/jellydator/ttlcache/v3 v3.4.0/go.mod h1:Hw9EgjymziQD3yGsQdf1FqFdpp7YjFMd4Srg5EJlgD4=\ngithub.com/jmespath/go-jmespath v0.4.1-0.20220621161143-b0104c826a24 h1:liMMTbpW34dhU4az1GN0pTPADwNmvoRSeoZ6PItiqnY=\ngithub.com/jmespath/go-jmespath v0.4.1-0.20220621161143-b0104c826a24/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=\ngithub.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=\ngithub.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=\ngithub.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=\ngithub.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=\ngithub.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=\ngithub.com/kevinburke/ssh_config v1.6.0 h1:J1FBfmuVosPHf5GRdltRLhPJtJpTlMdKTBjRgTaQBFY=\ngithub.com/kevinburke/ssh_config v1.6.0/go.mod h1:q2RIzfka+BXARoNexmF9gkxEX7DmvbW9P4hIVx2Kg4M=\ngithub.com/keybase/go-keychain v0.0.1 h1:way+bWYa6lDppZoZcgMbYsvC7GxljxrskdNInRtuthU=\ngithub.com/keybase/go-keychain v0.0.1/go.mod h1:PdEILRW3i9D8JcdM+FmY6RwkHGnhHxXwkPPMeUgOK1k=\ngithub.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=\ngithub.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=\ngithub.com/klauspost/compress v1.18.6 h1:2jupLlAwFm95+YDR+NwD2MEfFO9d4z4Prjl1XXDjuao=\ngithub.com/klauspost/compress v1.18.6/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=\ngithub.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=\ngithub.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=\ngithub.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=\ngithub.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=\ngithub.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=\ngithub.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=\ngithub.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=\ngithub.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=\ngithub.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=\ngithub.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=\ngithub.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=\ngithub.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=\ngithub.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=\ngithub.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=\ngithub.com/letsencrypt/boulder v0.20260223.0 h1:xdS2OnJNUasR6TgVIOpqqcvdkOu47+PQQMBk9ThuWBw=\ngithub.com/letsencrypt/boulder v0.20260223.0/go.mod h1:r3aTSA7UZ7dbDfiGK+HLHJz0bWNbHk6YSPiXgzl23sA=\ngithub.com/lucasb-eyer/go-colorful v1.4.0 h1:UtrWVfLdarDgc44HcS7pYloGHJUjHV/4FwW4TvVgFr4=\ngithub.com/lucasb-eyer/go-colorful v1.4.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=\ngithub.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=\ngithub.com/matryer/is v1.4.1 h1:55ehd8zaGABKLXQUe2awZ99BD/PTc2ls+KV/dXphgEQ=\ngithub.com/matryer/is v1.4.1/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=\ngithub.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=\ngithub.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=\ngithub.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=\ngithub.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=\ngithub.com/mattn/go-isatty v0.0.22 h1:j8l17JJ9i6VGPUFUYoTUKPSgKe/83EYU2zBC7YNKMw4=\ngithub.com/mattn/go-isatty v0.0.22/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4=\ngithub.com/mattn/go-mastodon v0.0.11 h1:Zcvc/8EHpf3os1mwAuUUB5es5VnfVdAeb4ed6ByJnCY=\ngithub.com/mattn/go-mastodon v0.0.11/go.mod h1:0DcwYEkqigrvknMvjmfKXLP0vYyeYm+vBdUOvoHcczg=\ngithub.com/mattn/go-runewidth v0.0.23 h1:7ykA0T0jkPpzSvMS5i9uoNn2Xy3R383f9HDx3RybWcw=\ngithub.com/mattn/go-runewidth v0.0.23/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=\ngithub.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=\ngithub.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM=\ngithub.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8=\ngithub.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=\ngithub.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=\ngithub.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=\ngithub.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=\ngithub.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=\ngithub.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=\ngithub.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=\ngithub.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=\ngithub.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=\ngithub.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=\ngithub.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=\ngithub.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=\ngithub.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c h1:cqn374mizHuIWj+OSJCajGr/phAmuMug9qIX3l9CflE=\ngithub.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=\ngithub.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=\ngithub.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=\ngithub.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=\ngithub.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=\ngithub.com/moby/moby/api v1.54.2 h1:wiat9QAhnDQjA7wk1kh/TqHz2I1uUA7M7t9SAl/JNXg=\ngithub.com/moby/moby/api v1.54.2/go.mod h1:+RQ6wluLwtYaTd1WnPLykIDPekkuyD/ROWQClE83pzs=\ngithub.com/moby/moby/client v0.4.1 h1:DMQgisVoMkmMs7fp3ROSdiBnoAu8+vo3GggFl06M/wY=\ngithub.com/moby/moby/client v0.4.1/go.mod h1:z52C9O2POPOsnxZAy//WtKcQ32P+jT/NGeXu/7nfjGQ=\ngithub.com/moby/sys/user v0.3.0 h1:9ni5DlcW5an3SvRSx4MouotOygvzaXbaSrc/wGDFWPo=\ngithub.com/moby/sys/user v0.3.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs=\ngithub.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ=\ngithub.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc=\ngithub.com/modelcontextprotocol/registry v1.7.9 h1:vpPfx2A2egjhm6YlbwfkX8NkR2N0S2eYmvYXI8bXaBs=\ngithub.com/modelcontextprotocol/registry v1.7.9/go.mod h1:y03zY98e+REsiCaj1sUKzXbk3qEp++Y3gzAnV83wrNs=\ngithub.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=\ngithub.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=\ngithub.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=\ngithub.com/mr-tron/base58 v1.3.0 h1:K6Y13R2h+dku0wOqKtecgRnBUBPrZzLZy5aIj8lCcJI=\ngithub.com/mr-tron/base58 v1.3.0/go.mod h1:2BuubE67DCSWwVfx37JWNG8emOC0sHEU4/HpcYgCLX8=\ngithub.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=\ngithub.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=\ngithub.com/muesli/mango v0.2.0 h1:iNNc0c5VLQ6fsMgAqGQofByNUBH2Q2nEbD6TaI+5yyQ=\ngithub.com/muesli/mango v0.2.0/go.mod h1:5XFpbC8jY5UUv89YQciiXNlbi+iJgt29VDC5xbzrLL4=\ngithub.com/muesli/mango-cobra v1.3.0 h1:vQy5GvPg3ndOSpduxutqFoINhWk3vD5K2dXo5E8pqec=\ngithub.com/muesli/mango-cobra v1.3.0/go.mod h1:Cj1ZrBu3806Qw7UjxnAUgE+7tllUBj1NCLQDwwGx19E=\ngithub.com/muesli/mango-pflag v0.2.0 h1:QViokgKDZQCzKhYe1zH8D+UlPJzBSGoP9yx0hBG0t5k=\ngithub.com/muesli/mango-pflag v0.2.0/go.mod h1:X9LT1p/pbGA1wjvEbtwnixujKErkP0jVmrxwrw3fL0Y=\ngithub.com/muesli/roff v0.1.0 h1:YD0lalCotmYuF5HhZliKWlIx7IEhiXeSfq7hNjFqGF8=\ngithub.com/muesli/roff v0.1.0/go.mod h1:pjAHQM9hdUUwm/krAfrLGgJkXJ+YuhtsfZ42kieB2Ig=\ngithub.com/multiformats/go-base32 v0.1.0 h1:pVx9xoSPqEIQG8o+UbAe7DNi51oej1NtK+aGkbLYxPE=\ngithub.com/multiformats/go-base32 v0.1.0/go.mod h1:Kj3tFY6zNr+ABYMqeUNeGvkIC/UYgtWibDcT0rExnbI=\ngithub.com/multiformats/go-base36 v0.2.0 h1:lFsAbNOGeKtuKozrtBsAkSVhv1p9D0/qedU9rQyccr0=\ngithub.com/multiformats/go-base36 v0.2.0/go.mod h1:qvnKE++v+2MWCfePClUEjE78Z7P2a1UV0xHgWc0hkp4=\ngithub.com/multiformats/go-multibase v0.3.0 h1:8helZD2+4Db7NNWFiktk2NePbF0boolBe6bDQvM4r68=\ngithub.com/multiformats/go-multibase v0.3.0/go.mod h1:MoBLQPCkRTOL3eveIPO81860j2AQY8JwcnNlRkGRUfI=\ngithub.com/multiformats/go-multihash v0.2.3 h1:7Lyc8XfX/IY2jWb/gI7JP+o7JEq9hOa7BFvVU9RSh+U=\ngithub.com/multiformats/go-multihash v0.2.3/go.mod h1:dXgKXCXjBzdscBLk9JkjINiEsCKRVch90MdaGiKsvSM=\ngithub.com/multiformats/go-varint v0.1.0 h1:i2wqFp4sdl3IcIxfAonHQV9qU5OsZ4Ts9IOoETFs5dI=\ngithub.com/multiformats/go-varint v0.1.0/go.mod h1:5KVAVXegtfmNQQm/lCY+ATvDzvJJhSkUlGQV9wgObdI=\ngithub.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=\ngithub.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=\ngithub.com/natefinch/atomic v1.0.1 h1:ZPYKxkqQOx3KZ+RsbnP/YsgvxWQPGxjC0oBt2AhwV0A=\ngithub.com/natefinch/atomic v1.0.1/go.mod h1:N/D/ELrljoqDyT3rZrsUmtsuzvHkeB/wWjHV22AZRbM=\ngithub.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=\ngithub.com/neelance/sourcemap v0.0.0-20200213170602-2833bce08e4c/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=\ngithub.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=\ngithub.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=\ngithub.com/ofabry/go-callvis v0.7.1 h1:Lu5YwEUUr+CK98nFqA77/nib0p6NCRjkJ9es/770p1U=\ngithub.com/ofabry/go-callvis v0.7.1/go.mod h1:4O2V2f7pJTEp3n8DaHt6/P6N39D6uDJGdMInZZ/nI38=\ngithub.com/oklog/ulid/v2 v2.1.1 h1:suPZ4ARWLOJLegGFiZZ1dFAkqzhMjL3J1TzI+5wHz8s=\ngithub.com/oklog/ulid/v2 v2.1.1/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNsTT1QQ=\ngithub.com/onsi/gomega v1.39.1 h1:1IJLAad4zjPn2PsnhH70V4DKRFlrCzGBNrNaru+Vf28=\ngithub.com/onsi/gomega v1.39.1/go.mod h1:hL6yVALoTOxeWudERyfppUcZXjMwIMLnuSfruD2lcfg=\ngithub.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=\ngithub.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=\ngithub.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=\ngithub.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=\ngithub.com/opencontainers/runc v1.2.8 h1:RnEICeDReapbZ5lZEgHvj7E9Q3Eex9toYmaGBsbvU5Q=\ngithub.com/opencontainers/runc v1.2.8/go.mod h1:cC0YkmZcuvr+rtBZ6T7NBoVbMGNAdLa/21vIElJDOzI=\ngithub.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs=\ngithub.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=\ngithub.com/ory/dockertest/v3 v3.12.0 h1:3oV9d0sDzlSQfHtIaB5k6ghUCVMVLpAY8hwrqoCyRCw=\ngithub.com/ory/dockertest/v3 v3.12.0/go.mod h1:aKNDTva3cp8dwOWwb9cWuX84aH5akkxXRvO7KCwWVjE=\ngithub.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=\ngithub.com/pb33f/ordered-map/v2 v2.3.1 h1:5319HDO0aw4DA4gzi+zv4FXU9UlSs3xGZ40wcP1nBjY=\ngithub.com/pb33f/ordered-map/v2 v2.3.1/go.mod h1:qxFQgd0PkVUtOMCkTapqotNgzRhMPL7VvaHKbd1HnmQ=\ngithub.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o=\ngithub.com/pborman/getopt v0.0.0-20180811024354-2b5b3bfb099b/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o=\ngithub.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=\ngithub.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=\ngithub.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=\ngithub.com/pelletier/go-toml/v2 v2.3.1 h1:MYEvvGnQjeNkRF1qUuGolNtNExTDwct51yp7olPtrEc=\ngithub.com/pelletier/go-toml/v2 v2.3.1/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=\ngithub.com/pjbgf/sha1cd v0.6.0 h1:3WJ8Wz8gvDz29quX1OcEmkAlUg9diU4GxJHqs0/XiwU=\ngithub.com/pjbgf/sha1cd v0.6.0/go.mod h1:lhpGlyHLpQZoxMv8HcgXvZEhcGs0PG/vsZnEJ7H0iCM=\ngithub.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=\ngithub.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=\ngithub.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=\ngithub.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=\ngithub.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo=\ngithub.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=\ngithub.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/polydawn/refmt v0.89.1-0.20231129105047-37766d95467a h1:cgqrm0F3zwf9IPzca7xN4w+Zy6MC9ZkPvAC8QEWa/iQ=\ngithub.com/polydawn/refmt v0.89.1-0.20231129105047-37766d95467a/go.mod h1:ocZfO/tLSHqfScRDNTJbAJR1by4D1lewauX9OwTaPuY=\ngithub.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=\ngithub.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=\ngithub.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=\ngithub.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=\ngithub.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=\ngithub.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=\ngithub.com/prometheus/common v0.67.5 h1:pIgK94WWlQt1WLwAC5j2ynLaBRDiinoAb86HZHTUGI4=\ngithub.com/prometheus/common v0.67.5/go.mod h1:SjE/0MzDEEAyrdr5Gqc6G+sXI67maCxzaT3A2+HqjUw=\ngithub.com/prometheus/otlptranslator v1.0.0 h1:s0LJW/iN9dkIH+EnhiD3BlkkP5QVIUVEoIwkU+A6qos=\ngithub.com/prometheus/otlptranslator v1.0.0/go.mod h1:vRYWnXvI6aWGpsdY/mOT/cbeVRBlPWtBNDb7kGR3uKM=\ngithub.com/prometheus/procfs v0.20.1 h1:XwbrGOIplXW/AU3YhIhLODXMJYyC1isLFfYCsTEycfc=\ngithub.com/prometheus/procfs v0.20.1/go.mod h1:o9EMBZGRyvDrSPH1RqdxhojkuXstoe4UlK79eF5TGGo=\ngithub.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=\ngithub.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=\ngithub.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=\ngithub.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=\ngithub.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=\ngithub.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=\ngithub.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=\ngithub.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=\ngithub.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=\ngithub.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk=\ngithub.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=\ngithub.com/sagikazarmark/locafero v0.12.0 h1:/NQhBAkUb4+fH1jivKHWusDYFjMOOKU88eegjfxfHb4=\ngithub.com/sagikazarmark/locafero v0.12.0/go.mod h1:sZh36u/YSZ918v0Io+U9ogLYQJ9tLLBmM4eneO6WwsI=\ngithub.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6NgVqpn3+iol9aGu4=\ngithub.com/santhosh-tekuri/jsonschema/v5 v5.3.1/go.mod h1:uToXkOrWAZ6/Oc07xWQrPOhJotwFIyu2bBVN41fcDUY=\ngithub.com/sassoftware/go-rpmutils v0.4.0 h1:ojND82NYBxgwrV+mX1CWsd5QJvvEZTKddtCdFLPWhpg=\ngithub.com/sassoftware/go-rpmutils v0.4.0/go.mod h1:3goNWi7PGAT3/dlql2lv3+MSN5jNYPjT5mVcQcIsYzI=\ngithub.com/sassoftware/relic v7.2.1+incompatible h1:Pwyh1F3I0r4clFJXkSI8bOyJINGqpgjJU3DYAZeI05A=\ngithub.com/sassoftware/relic v7.2.1+incompatible/go.mod h1:CWfAxv73/iLZ17rbyhIEq3K9hs5w6FpNMdUT//qR+zk=\ngithub.com/sassoftware/relic/v7 v7.6.2 h1:rS44Lbv9G9eXsukknS4mSjIAuuX+lMq/FnStgmZlUv4=\ngithub.com/sassoftware/relic/v7 v7.6.2/go.mod h1:kjmP0IBVkJZ6gXeAu35/KCEfca//+PKM6vTAsyDPY+k=\ngithub.com/scylladb/go-set v1.0.3-0.20200225121959-cc7b2070d91e h1:7q6NSFZDeGfvvtIRwBrU/aegEYJYmvev0cHAwo17zZQ=\ngithub.com/scylladb/go-set v1.0.3-0.20200225121959-cc7b2070d91e/go.mod h1:DkpGd78rljTxKAnTDPFqXSGxvETQnJyuSOQwsHycqfs=\ngithub.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=\ngithub.com/secure-systems-lab/go-securesystemslib v0.11.0 h1:iuCR9kcMFD4QurdKrGvPLoKZLv9YvwPYVr0473BdtFs=\ngithub.com/secure-systems-lab/go-securesystemslib v0.11.0/go.mod h1:+PMOTjUGwHj2vcZ+TFKlb1tXRbrdWE1LYDT5i9JC80Q=\ngithub.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=\ngithub.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw=\ngithub.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=\ngithub.com/shibumi/go-pathspec v1.3.0 h1:QUyMZhFo0Md5B8zV8x2tesohbb5kfbpTi9rBnKh5dkI=\ngithub.com/shibumi/go-pathspec v1.3.0/go.mod h1:Xutfslp817l2I1cZvgcfeMQJG5QnU2lh5tVaaMCl3jE=\ngithub.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=\ngithub.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=\ngithub.com/shurcooL/go v0.0.0-20200502201357-93f07166e636/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=\ngithub.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=\ngithub.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=\ngithub.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw=\ngithub.com/sigstore/cosign/v3 v3.0.6 h1:k8XaUd9pmLknHBst/v0rUGHVdB4D9cfaBmWUaMAOocE=\ngithub.com/sigstore/cosign/v3 v3.0.6/go.mod h1:ckLRkVecfUCYxL8isHODY9lwyKmDaRCPn00p6yFxHg0=\ngithub.com/sigstore/protobuf-specs v0.5.1 h1:/5OPaNuolRJmQfeZLayJGFXMpsRJEdgC6ah1/+7Px7U=\ngithub.com/sigstore/protobuf-specs v0.5.1/go.mod h1:DRBzpFuE+LnvQMN10/dU6nBeKwVLGEQ6o2FovN2Rats=\ngithub.com/sigstore/rekor v1.5.1 h1:Ca1egHRWRuDvXV4tZu9aXEXc3Gej9FG+HKeapV9OAMQ=\ngithub.com/sigstore/rekor v1.5.1/go.mod h1:gTLDuZuo3SyQCuZvKqwRPA79Qo/2rw39/WtLP/rZjUQ=\ngithub.com/sigstore/rekor-tiles/v2 v2.2.1 h1:UmV1CBQ3SjxxPGpFmwDoOhoIwiKpM2Qm1pU5tPGmvNk=\ngithub.com/sigstore/rekor-tiles/v2 v2.2.1/go.mod h1:z8n6l6oidpaLjjE6rJERuQqY9X38ulnHZCXyL+DEL7U=\ngithub.com/sigstore/sigstore v1.10.5 h1:KqrOjDhNOVY+uOzQFat2FrGLClPPCb3uz8pK3wuI+ow=\ngithub.com/sigstore/sigstore v1.10.5/go.mod h1:k/mcVVXw3I87dYG/iCVTSW2xTrW7vPzxxGic4KqsqXs=\ngithub.com/sigstore/sigstore-go v1.1.4 h1:wTTsgCHOfqiEzVyBYA6mDczGtBkN7cM8mPpjJj5QvMg=\ngithub.com/sigstore/sigstore-go v1.1.4/go.mod h1:2U/mQOT9cjjxrtIUeKDVhL+sHBKsnWddn8URlswdBsg=\ngithub.com/sigstore/sigstore/pkg/signature/kms/aws v1.10.5 h1:aqHRubTITULckG9JAcq2FEhtKkT/RRE8oErfuV3smSI=\ngithub.com/sigstore/sigstore/pkg/signature/kms/aws v1.10.5/go.mod h1:h9eK9QyPqpFskF/ewFkRLtwh4/Q3FLc2/DXbym4IHN8=\ngithub.com/sigstore/sigstore/pkg/signature/kms/azure v1.10.5 h1:+9C6CUkv+J4iT67Lx+H1EGBfAdoAHqXumHadeIj9jA4=\ngithub.com/sigstore/sigstore/pkg/signature/kms/azure v1.10.5/go.mod h1:myZsg7wRiy/vf102g5uUAitYhtXCwepmAGxgHG1VHuE=\ngithub.com/sigstore/sigstore/pkg/signature/kms/gcp v1.10.5 h1:BpQx6AhjwIN9LmlO4ypkcMcHiWiepgZQGSw5U69frHU=\ngithub.com/sigstore/sigstore/pkg/signature/kms/gcp v1.10.5/go.mod h1:ejMD/17lMJ4HykQRPdj5NNr+OQYIEZto8HjDKghVMOA=\ngithub.com/sigstore/sigstore/pkg/signature/kms/hashivault v1.10.5 h1:OFwQZgWkB/6J6W5sy3SkXE4pJnhNRnE2cJd8ySXmHpo=\ngithub.com/sigstore/sigstore/pkg/signature/kms/hashivault v1.10.5/go.mod h1:Ee/enmyxi/RFLVlajbnjgH2wOWQwlJ0wY8qZrk43hEw=\ngithub.com/sigstore/timestamp-authority/v2 v2.0.6 h1:1Vh7/SdmLsVLG6Br6/bisd1SnlicfDm0MJYiA+D7Ppw=\ngithub.com/sigstore/timestamp-authority/v2 v2.0.6/go.mod h1:Nk5ucGBDyH0tXAIMZ0prf6xn8qfTnbJhSq+CDabYcfc=\ngithub.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=\ngithub.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=\ngithub.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w=\ngithub.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g=\ngithub.com/skeema/knownhosts v1.3.2 h1:EDL9mgf4NzwMXCTfaxSD/o/a5fxDw/xL9nkU28JjdBg=\ngithub.com/skeema/knownhosts v1.3.2/go.mod h1:bEg3iQAuw+jyiw+484wwFJoKSLwcfd7fqRy+N0QTiow=\ngithub.com/slack-go/slack v0.23.1 h1:ZS5B96wxxYQRwvJ3/vJFtqtUZi3tXhsZCyT44Nv7M80=\ngithub.com/slack-go/slack v0.23.1/go.mod h1:H0yR/YBuRJ39RkE+JpV/d/oEsbanzTRowR82bCN0cEs=\ngithub.com/smarty/assertions v1.15.0 h1:cR//PqUBUiQRakZWqBiFFQ9wb8emQGDb0HeGdqGByCY=\ngithub.com/smarty/assertions v1.15.0/go.mod h1:yABtdzeQs6l1brC900WlRNwj6ZR55d7B+E8C6HtKdec=\ngithub.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=\ngithub.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=\ngithub.com/smartystreets/goconvey v1.8.1 h1:qGjIddxOk4grTu9JPOU31tVfq3cNdBlNa5sSznIX1xY=\ngithub.com/smartystreets/goconvey v1.8.1/go.mod h1:+/u4qLyY6x1jReYOp7GOM2FSt8aP9CzCZL03bI28W60=\ngithub.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=\ngithub.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=\ngithub.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=\ngithub.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=\ngithub.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=\ngithub.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=\ngithub.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=\ngithub.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=\ngithub.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk=\ngithub.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=\ngithub.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=\ngithub.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=\ngithub.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=\ngithub.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=\ngithub.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=\ngithub.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=\ngithub.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns=\ngithub.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU=\ngithub.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY=\ngithub.com/spiffe/go-spiffe/v2 v2.6.0 h1:l+DolpxNWYgruGQVV0xsfeya3CsC7m8iBzDnMpsbLuo=\ngithub.com/spiffe/go-spiffe/v2 v2.6.0/go.mod h1:gm2SeUoMZEtpnzPNs2Csc0D/gX33k1xIx7lEzqblHEs=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=\ngithub.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=\ngithub.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=\ngithub.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=\ngithub.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=\ngithub.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=\ngithub.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=\ngithub.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=\ngithub.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=\ngithub.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=\ngithub.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=\ngithub.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=\ngithub.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=\ngithub.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=\ngithub.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=\ngithub.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=\ngithub.com/tetratelabs/wazero v1.11.0 h1:+gKemEuKCTevU4d7ZTzlsvgd1uaToIDtlQlmNbwqYhA=\ngithub.com/tetratelabs/wazero v1.11.0/go.mod h1:eV28rsN8Q+xwjogd7f4/Pp4xFxO7uOGbLcD/LzB1wiU=\ngithub.com/theupdateframework/go-tuf v0.7.0 h1:CqbQFrWo1ae3/I0UCblSbczevCCbS31Qvs5LdxRWqRI=\ngithub.com/theupdateframework/go-tuf v0.7.0/go.mod h1:uEB7WSY+7ZIugK6R1hiBMBjQftaFzn7ZCDJcp1tCUug=\ngithub.com/theupdateframework/go-tuf/v2 v2.4.1 h1:K6ewW064rKZCPkRo1W/CTbTtm/+IB4+coG1iNURAGCw=\ngithub.com/theupdateframework/go-tuf/v2 v2.4.1/go.mod h1:Nex2enPVYDFCklrnbTzl3OVwD7fgIAj0J5++z/rvCj8=\ngithub.com/tink-crypto/tink-go-awskms/v2 v2.1.0 h1:N9UxlsOzu5mttdjhxkDLbzwtEecuXmlxZVo/ds7JKJI=\ngithub.com/tink-crypto/tink-go-awskms/v2 v2.1.0/go.mod h1:PxSp9GlOkKL9rlybW804uspnHuO9nbD98V/fDX4uSis=\ngithub.com/tink-crypto/tink-go-gcpkms/v2 v2.2.0 h1:3B9i6XBXNTRspfkTC0asN5W0K6GhOSgcujNiECNRNb0=\ngithub.com/tink-crypto/tink-go-gcpkms/v2 v2.2.0/go.mod h1:jY5YN2BqD/KSCHM9SqZPIpJNG/u3zwfLXHgws4x2IRw=\ngithub.com/tink-crypto/tink-go-hcvault/v2 v2.4.0 h1:j+S+WKBQ5ya26A5EM/uXoVe+a2IaPQN8KgBJZ22cJ+4=\ngithub.com/tink-crypto/tink-go-hcvault/v2 v2.4.0/go.mod h1:OCKJIujnTzDq7f+73NhVs99oA2c1TR6nsOpuasYM6Yo=\ngithub.com/tink-crypto/tink-go/v2 v2.6.0 h1:+KHNBHhWH33Vn+igZWcsgdEPUxKwBMEe0QC60t388v4=\ngithub.com/tink-crypto/tink-go/v2 v2.6.0/go.mod h1:2WbBA6pfNsAfBwDCggboaHeB2X29wkU8XHtGwh2YIk8=\ngithub.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 h1:e/5i7d4oYZ+C1wj2THlRK+oAhjeS/TRQwMfkIuet3w0=\ngithub.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399/go.mod h1:LdwHTNJT99C5fTAzDz0ud328OgXz+gierycbcIx2fRs=\ngithub.com/tomnomnom/linkheader v0.0.0-20250811210735-e5fe3b51442e h1:tD38/4xg4nuQCASJ/JxcvCHNb46w0cdAaJfkzQOO1bA=\ngithub.com/tomnomnom/linkheader v0.0.0-20250811210735-e5fe3b51442e/go.mod h1:krvJ5AY/MjdPkTeRgMYbIDhbbbVvnPQPzsIsDJO8xrY=\ngithub.com/transparency-dev/formats v0.1.0 h1:oL0zUFuYUjg8AbtjPMnIRDmjbaHo5jCjEWU5yaNuz0g=\ngithub.com/transparency-dev/formats v0.1.0/go.mod h1:d2FibUOHfCMdCe/+/rbKt1IPLBbPTDfwj46kt541/mU=\ngithub.com/transparency-dev/merkle v0.0.2 h1:Q9nBoQcZcgPamMkGn7ghV8XiTZ/kRxn1yCG81+twTK4=\ngithub.com/transparency-dev/merkle v0.0.2/go.mod h1:pqSy+OXefQ1EDUVmAJ8MUhHB9TXGuzVAT58PqBoHz1A=\ngithub.com/ulikunitz/xz v0.5.15 h1:9DNdB5s+SgV3bQ2ApL10xRc35ck0DuIX/isZvIk+ubY=\ngithub.com/ulikunitz/xz v0.5.15/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=\ngithub.com/urfave/cli v1.22.10/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=\ngithub.com/vbatts/tar-split v0.12.3 h1:Cd46rkGXI3Td4yrVNwU8ripbxFaQbmesqhjBUUYAJSw=\ngithub.com/vbatts/tar-split v0.12.3/go.mod h1:sQOc6OlqGCr7HkGx/IDBeKiTIvqhmj8KffNhEXG4Nq0=\ngithub.com/wagoodman/go-progress v0.0.0-20230925121702-07e42b3cdba0 h1:0KGbf+0SMg+UFy4e1A/CPVvXn21f1qtWdeJwxZFoQG8=\ngithub.com/wagoodman/go-progress v0.0.0-20230925121702-07e42b3cdba0/go.mod h1:jLXFoL31zFaHKAAyZUh+sxiTDFe1L1ZHrcK2T1itVKA=\ngithub.com/warpfork/go-wish v0.0.0-20220906213052-39a1cc7a02d0 h1:GDDkbFiaK8jsSDJfjId/PEGEShv6ugrt4kYsC5UIDaQ=\ngithub.com/warpfork/go-wish v0.0.0-20220906213052-39a1cc7a02d0/go.mod h1:x6AKhvSSexNrVSrViXSHUEbICjmGXhtgABaHIySUSGw=\ngithub.com/whyrusleeping/cbor-gen v0.3.1 h1:82ioxmhEYut7LBVGhGq8xoRkXPLElVuh5mV67AFfdv0=\ngithub.com/whyrusleeping/cbor-gen v0.3.1/go.mod h1:pM99HXyEbSQHcosHc0iW7YFmwnscr+t9Te4ibko05so=\ngithub.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=\ngithub.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=\ngithub.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=\ngithub.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=\ngithub.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=\ngithub.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=\ngithub.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=\ngithub.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=\ngithub.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo=\ngithub.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos=\ngithub.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=\ngithub.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=\ngithub.com/ysmood/fetchup v0.2.3 h1:ulX+SonA0Vma5zUFXtv52Kzip/xe7aj4vqT5AJwQ+ZQ=\ngithub.com/ysmood/fetchup v0.2.3/go.mod h1:xhibcRKziSvol0H1/pj33dnKrYyI2ebIvz5cOOkYGns=\ngithub.com/ysmood/goob v0.4.0 h1:HsxXhyLBeGzWXnqVKtmT9qM7EuVs/XOgkX7T6r1o1AQ=\ngithub.com/ysmood/goob v0.4.0/go.mod h1:u6yx7ZhS4Exf2MwciFr6nIM8knHQIE22lFpWHnfql18=\ngithub.com/ysmood/got v0.40.0 h1:ZQk1B55zIvS7zflRrkGfPDrPG3d7+JOza1ZkNxcc74Q=\ngithub.com/ysmood/got v0.40.0/go.mod h1:W7DdpuX6skL3NszLmAsC5hT7JAhuLZhByVzHTq874Qg=\ngithub.com/ysmood/gson v0.7.3 h1:QFkWbTH8MxyUTKPkVWAENJhxqdBa4lYTQWqZCiLG6kE=\ngithub.com/ysmood/gson v0.7.3/go.mod h1:3Kzs5zDl21g5F/BlLTNcuAGAYLKt2lV5G8D1zF3RNmg=\ngithub.com/ysmood/leakless v0.9.0 h1:qxCG5VirSBvmi3uynXFkcnLMzkphdh3xx5FtrORwDCU=\ngithub.com/ysmood/leakless v0.9.0/go.mod h1:R8iAXPRaG97QJwqxs74RdwzcRHT1SWCGTNqY8q0JvMQ=\ngithub.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngithub.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngithub.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngithub.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngithub.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=\ngithub.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=\ngithub.com/zalando/go-keyring v0.2.6 h1:r7Yc3+H+Ux0+M72zacZoItR3UDxeWfKTcabvkI8ua9s=\ngithub.com/zalando/go-keyring v0.2.6/go.mod h1:2TCrxYrbUNYfNS/Kgy/LSrkSQzZ5UPVH85RwfczwvcI=\ngitlab.com/digitalxero/go-conventional-commit v1.0.7 h1:8/dO6WWG+98PMhlZowt/YjuiKhqhGlOCwlIV8SqqGh8=\ngitlab.com/digitalxero/go-conventional-commit v1.0.7/go.mod h1:05Xc2BFsSyC5tKhK0y+P3bs0AwUtNuTp+mTpbCU/DZ0=\ngitlab.com/gitlab-org/api/client-go v1.46.0 h1:YxBWFZIFYKcGESCb9fpkwzouo+apyB9pr/XTWzNoL24=\ngitlab.com/gitlab-org/api/client-go v1.46.0/go.mod h1:FtgyU6g2HS5+fMhw6nLK96GBEEBx5MzntOiJWfIaiN8=\ngo.digitalxero.dev/go-msix v0.3.1 h1:V5E8PuFkA3Fr3VFYX6pTUutriogYC9sgxIWhzf9sSKw=\ngo.digitalxero.dev/go-msix v0.3.1/go.mod h1:QbUpFs0AUd1zk7e9fy17suiqEAF90TR3jZY+LCI2K+c=\ngo.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=\ngo.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=\ngo.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ=\ngo.mozilla.org/pkcs7 v0.9.0 h1:yM4/HS9dYv7ri2biPtxt8ikvB37a980dg69/pKmS+eI=\ngo.mozilla.org/pkcs7 v0.9.0/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk=\ngo.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=\ngo.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=\ngo.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=\ngo.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=\ngo.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=\ngo.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=\ngo.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=\ngo.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=\ngo.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=\ngo.opentelemetry.io/contrib/bridges/prometheus v0.67.0 h1:dkBzNEAIKADEaFnuESzcXvpd09vxvDZsOjx11gjUqLk=\ngo.opentelemetry.io/contrib/bridges/prometheus v0.67.0/go.mod h1:Z5RIwRkZgauOIfnG5IpidvLpERjhTninpP1dTG2jTl4=\ngo.opentelemetry.io/contrib/detectors/gcp v1.43.0 h1:62yY3dT7/ShwOxzA0RsKRgshBmfElKI4d/Myu2OxDFU=\ngo.opentelemetry.io/contrib/detectors/gcp v1.43.0/go.mod h1:RyaZMFY7yi1kAs45S6mbFGz8O8rqB0dTY14uzvG4LCs=\ngo.opentelemetry.io/contrib/exporters/autoexport v0.67.0 h1:4fnRcNpc6YFtG3zsFw9achKn3XgmxPxuMuqIL5rE8e8=\ngo.opentelemetry.io/contrib/exporters/autoexport v0.67.0/go.mod h1:qTvIHMFKoxW7HXg02gm6/Wofhq5p3Ib/A/NNt1EoBSQ=\ngo.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.68.0 h1:0Qx7VGBacMm9ZENQ7TnNObTYI4ShC+lHI16seduaxZo=\ngo.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.68.0/go.mod h1:Sje3i3MjSPKTSPvVWCaL8ugBzJwik3u4smCjUeuupqg=\ngo.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 h1:CqXxU8VOmDefoh0+ztfGaymYbhdB/tT3zs79QaZTNGY=\ngo.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0/go.mod h1:BuhAPThV8PBHBvg8ZzZ/Ok3idOdhWIodywz2xEcRbJo=\ngo.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I=\ngo.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0=\ngo.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.18.0 h1:deI9UQMoGFgrg5iLPgzueqFPHevDl+28YKfSpPTI6rY=\ngo.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.18.0/go.mod h1:PFx9NgpNUKXdf7J4Q3agRxMs3Y07QhTCVipKmLsMKnU=\ngo.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.19.0 h1:HIBTQ3VO5aupLKjC90JgMqpezVXwFuq6Ryjn0/izoag=\ngo.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.19.0/go.mod h1:ji9vId85hMxqfvICA0Jt8JqEdrXaAkcpkI9HPXya0ro=\ngo.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.42.0 h1:MdKucPl/HbzckWWEisiNqMPhRrAOQX8r4jTuGr636gk=\ngo.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.42.0/go.mod h1:RolT8tWtfHcjajEH5wFIZ4Dgh5jpPdFXYV9pTAk/qjc=\ngo.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.43.0 h1:w1K+pCJoPpQifuVpsKamUdn9U0zM3xUziVOqsGksUrY=\ngo.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.43.0/go.mod h1:HBy4BjzgVE8139ieRI75oXm3EcDN+6GhD88JT1Kjvxg=\ngo.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 h1:88Y4s2C8oTui1LGM6bTWkw0ICGcOLCAI5l6zsD1j20k=\ngo.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0/go.mod h1:Vl1/iaggsuRlrHf/hfPJPvVag77kKyvrLeD10kpMl+A=\ngo.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0 h1:zWWrB1U6nqhS/k6zYB74CjRpuiitRtLLi68VcgmOEto=\ngo.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0/go.mod h1:2qXPNBX1OVRC0IwOnfo1ljoid+RD0QK3443EaqVlsOU=\ngo.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0 h1:3iZJKlCZufyRzPzlQhUIWVmfltrXuGyfjREgGP3UUjc=\ngo.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0/go.mod h1:/G+nUPfhq2e+qiXMGxMwumDrP5jtzU+mWN7/sjT2rak=\ngo.opentelemetry.io/otel/exporters/prometheus v0.65.0 h1:jOveH/b4lU9HT7y+Gfamf18BqlOuz2PWEvs8yM7Q6XE=\ngo.opentelemetry.io/otel/exporters/prometheus v0.65.0/go.mod h1:i1P8pcumauPtUI4YNopea1dhzEMuEqWP1xoUZDylLHo=\ngo.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.18.0 h1:KJVjPD3rcPb98rIs3HznyJlrfx9ge5oJvxxlGR+P/7s=\ngo.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.18.0/go.mod h1:K3kRa2ckmHWQaTWQdPRHc7qGXASuVuoEQXzrvlA98Ws=\ngo.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.43.0 h1:TC+BewnDpeiAmcscXbGMfxkO+mwYUwE/VySwvw88PfA=\ngo.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.43.0/go.mod h1:J/ZyF4vfPwsSr9xJSPyQ4LqtcTPULFR64KwTikGLe+A=\ngo.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.42.0 h1:s/1iRkCKDfhlh1JF26knRneorus8aOwVIDhvYx9WoDw=\ngo.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.42.0/go.mod h1:UI3wi0FXg1Pofb8ZBiBLhtMzgoTm1TYkMvn71fAqDzs=\ngo.opentelemetry.io/otel/log v0.19.0 h1:KUZs/GOsw79TBBMfDWsXS+KZ4g2Ckzksd1ymzsIEbo4=\ngo.opentelemetry.io/otel/log v0.19.0/go.mod h1:5DQYeGmxVIr4n0/BcJvF4upsraHjg6vudJJpnkL6Ipk=\ngo.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM=\ngo.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY=\ngo.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg=\ngo.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg=\ngo.opentelemetry.io/otel/sdk/log v0.19.0 h1:scYVLqT22D2gqXItnWiocLUKGH9yvkkeql5dBDiXyko=\ngo.opentelemetry.io/otel/sdk/log v0.19.0/go.mod h1:vFBowwXGLlW9AvpuF7bMgnNI95LiW10szrOdvzBHlAg=\ngo.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw=\ngo.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A=\ngo.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A=\ngo.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0=\ngo.opentelemetry.io/proto/otlp v1.10.0 h1:IQRWgT5srOCYfiWnpqUYz9CVmbO8bFmKcwYxpuCSL2g=\ngo.opentelemetry.io/proto/otlp v1.10.0/go.mod h1:/CV4QoCR/S9yaPj8utp3lvQPoqMtxXdzn7ozvvozVqk=\ngo.step.sm/crypto v0.77.2 h1:qFjjei+RHc5kP5R7NW9OUWT7SqWIuAOvOkXqg4fNWj8=\ngo.step.sm/crypto v0.77.2/go.mod h1:W0YJb9onM5l78qgkXIJ2Up6grnwW8EtpCKIza/NCg0o=\ngo.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=\ngo.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=\ngo.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=\ngo.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=\ngo.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=\ngo.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=\ngo.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=\ngo.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=\ngo.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=\ngo.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=\ngo.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=\ngo.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ=\ngo.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=\ngo.uber.org/zap v1.28.0 h1:IZzaP1Fv73/T/pBMLk4VutPl36uNC+OSUh3JLG3FIjo=\ngo.uber.org/zap v1.28.0/go.mod h1:rDLpOi171uODNm/mxFcuYWxDsqWSAVkFdX4XojSKg/Q=\ngo.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ=\ngo.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ=\ngo.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=\ngo.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=\ngo.yaml.in/yaml/v4 v4.0.0-rc.4 h1:UP4+v6fFrBIb1l934bDl//mmnoIZEDK0idg1+AIvX5U=\ngo.yaml.in/yaml/v4 v4.0.0-rc.4/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0=\ngocloud.dev v0.45.0 h1:WknIK8IbRdmynDvara3Q7G6wQhmEiOGwpgJufbM39sY=\ngocloud.dev v0.45.0/go.mod h1:0kXKmkCLG6d31N7NyLZWzt7jDSQura9zD/mWgiB6THI=\ngolang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=\ngolang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=\ngolang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=\ngolang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=\ngolang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=\ngolang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=\ngolang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=\ngolang.org/x/crypto v0.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI=\ngolang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8=\ngolang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=\ngolang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=\ngolang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=\ngolang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=\ngolang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=\ngolang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=\ngolang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=\ngolang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=\ngolang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=\ngolang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=\ngolang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f h1:W3F4c+6OLc6H2lb//N1q4WpJkhzJCK5J6kUi1NTVXfM=\ngolang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f/go.mod h1:J1xhfL/vlindoeF/aINzNzt2Bket5bjo9sdOYzOsU80=\ngolang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=\ngolang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=\ngolang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=\ngolang.org/x/image v0.40.0 h1:Tw4GyDXMo+daZN1znreBRC3VayR1aLFUyUEOLUdW1a8=\ngolang.org/x/image v0.40.0/go.mod h1:uIc348UZMSvS5Z65CVZ7iDPaNobNFEPeJ4kbqTOszmA=\ngolang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=\ngolang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=\ngolang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=\ngolang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=\ngolang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=\ngolang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=\ngolang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=\ngolang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=\ngolang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=\ngolang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=\ngolang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=\ngolang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=\ngolang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=\ngolang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=\ngolang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=\ngolang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=\ngolang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=\ngolang.org/x/mod v0.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4=\ngolang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ=\ngolang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=\ngolang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=\ngolang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=\ngolang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=\ngolang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=\ngolang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=\ngolang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=\ngolang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=\ngolang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=\ngolang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=\ngolang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=\ngolang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=\ngolang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=\ngolang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=\ngolang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=\ngolang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=\ngolang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=\ngolang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=\ngolang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=\ngolang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=\ngolang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=\ngolang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=\ngolang.org/x/net v0.54.0 h1:2zJIZAxAHV/OHCDTCOHAYehQzLfSXuf/5SoL/Dv6w/w=\ngolang.org/x/net v0.54.0/go.mod h1:Sj4oj8jK6XmHpBZU/zWHw3BV3abl4Kvi+Ut7cQcY+cQ=\ngolang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=\ngolang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=\ngolang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=\ngolang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=\ngolang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=\ngolang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=\ngolang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=\ngolang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=\ngolang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=\ngolang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=\ngolang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=\ngolang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=\ngolang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=\ngolang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=\ngolang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=\ngolang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=\ngolang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=\ngolang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ=\ngolang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=\ngolang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=\ngolang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=\ngolang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=\ngolang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=\ngolang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=\ngolang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=\ngolang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4=\ngolang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk=\ngolang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=\ngolang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=\ngolang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=\ngolang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=\ngolang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=\ngolang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=\ngolang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=\ngolang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=\ngolang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\ngolang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\ngolang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\ngolang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=\ngolang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=\ngolang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=\ngolang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=\ngolang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=\ngolang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=\ngolang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=\ngolang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=\ngolang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=\ngolang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=\ngolang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=\ngolang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=\ngolang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=\ngolang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=\ngolang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=\ngolang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=\ngolang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=\ngolang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=\ngolang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=\ngolang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=\ngolang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=\ngolang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=\ngolang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=\ngolang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=\ngolang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=\ngolang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=\ngolang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=\ngolang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=\ngolang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s=\ngolang.org/x/tools v0.45.0 h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8=\ngolang.org/x/tools v0.45.0/go.mod h1:LuUGqqaXcXMEFEruIVJVm5mgDD8vww/z/SR1gQ4uE/0=\ngolang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da h1:noIWHXmPHxILtqtCOPIhSt0ABwskkZKjD3bXGnZGpNY=\ngolang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90=\ngonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=\ngonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=\ngoogle.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=\ngoogle.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=\ngoogle.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=\ngoogle.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=\ngoogle.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=\ngoogle.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=\ngoogle.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=\ngoogle.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=\ngoogle.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=\ngoogle.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=\ngoogle.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=\ngoogle.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=\ngoogle.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=\ngoogle.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=\ngoogle.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=\ngoogle.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=\ngoogle.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=\ngoogle.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=\ngoogle.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=\ngoogle.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU=\ngoogle.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94=\ngoogle.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8=\ngoogle.golang.org/api v0.279.0 h1:hsx2M2OaRcaKtVYK6vXEUnQvdjnend7ZYES+lYaot74=\ngoogle.golang.org/api v0.279.0/go.mod h1:B9TqLBwJqVjp1mtt7WeoQwWRwvu/400y5lETOql+giQ=\ngoogle.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=\ngoogle.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=\ngoogle.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=\ngoogle.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=\ngoogle.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=\ngoogle.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=\ngoogle.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=\ngoogle.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=\ngoogle.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=\ngoogle.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=\ngoogle.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=\ngoogle.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=\ngoogle.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=\ngoogle.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=\ngoogle.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=\ngoogle.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=\ngoogle.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=\ngoogle.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=\ngoogle.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=\ngoogle.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=\ngoogle.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=\ngoogle.golang.org/genproto v0.0.0-20260319201613-d00831a3d3e7 h1:XzmzkmB14QhVhgnawEVsOn6OFsnpyxNPRY9QV01dNB0=\ngoogle.golang.org/genproto v0.0.0-20260319201613-d00831a3d3e7/go.mod h1:L43LFes82YgSonw6iTXTxXUX1OlULt4AQtkik4ULL/I=\ngoogle.golang.org/genproto/googleapis/api v0.0.0-20260414002931-afd174a4e478 h1:yQugLulqltosq0B/f8l4w9VryjV+N/5gcW0jQ3N8Qec=\ngoogle.golang.org/genproto/googleapis/api v0.0.0-20260414002931-afd174a4e478/go.mod h1:C6ADNqOxbgdUUeRTU+LCHDPB9ttAMCTff6auwCVa4uc=\ngoogle.golang.org/genproto/googleapis/rpc v0.0.0-20260427160629-7cedc36a6bc4 h1:tEkOQcXgF6dH1G+MVKZrfpYvozGrzb91k6ha7jireSM=\ngoogle.golang.org/genproto/googleapis/rpc v0.0.0-20260427160629-7cedc36a6bc4/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=\ngoogle.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=\ngoogle.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=\ngoogle.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=\ngoogle.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=\ngoogle.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=\ngoogle.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=\ngoogle.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=\ngoogle.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=\ngoogle.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=\ngoogle.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=\ngoogle.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=\ngoogle.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=\ngoogle.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=\ngoogle.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=\ngoogle.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=\ngoogle.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=\ngoogle.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=\ngoogle.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=\ngoogle.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=\ngoogle.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=\ngoogle.golang.org/grpc v1.81.0 h1:W3G9N3KQf3BU+YuCtGKJk0CmxQNbAISICD/9AORxLIw=\ngoogle.golang.org/grpc v1.81.0/go.mod h1:xGH9GfzOyMTGIOXBJmXt+BX/V0kcdQbdcuwQ/zNw42I=\ngoogle.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=\ngoogle.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=\ngoogle.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=\ngoogle.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=\ngoogle.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=\ngoogle.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=\ngoogle.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=\ngoogle.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=\ngoogle.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=\ngoogle.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=\ngoogle.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=\ngopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=\ngopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=\ngopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=\ngopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=\ngopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=\ngopkg.in/mail.v2 v2.3.1 h1:WYFn/oANrAGP2C0dcV6/pbkPzv8yGzqTjPmTeO7qoXk=\ngopkg.in/mail.v2 v2.3.1/go.mod h1:htwXN1Qh09vZJ1NVKxQqHPBaCBbzKhp5GzuJEA4VJWw=\ngopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=\ngopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=\ngopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=\ngopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=\ngopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=\ngotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA=\nhonnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=\nhonnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=\nhonnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=\nk8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc=\nk8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0=\nlukechampine.com/blake3 v1.4.1 h1:I3Smz7gso8w4/TunLKec6K2fn+kyKtDxr/xcQEN84Wg=\nlukechampine.com/blake3 v1.4.1/go.mod h1:QFosUxmjB8mnrWFSNwKmvxHpfY72bmD2tQ0kBMM3kwo=\npgregory.net/rapid v1.2.0 h1:keKAYRcjm+e1F0oAuU5F5+YPAWcyxNNRK2wud503Gnk=\npgregory.net/rapid v1.2.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04=\nrsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=\nrsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=\nrsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=\nsigs.k8s.io/kind v0.31.0 h1:UcT4nzm+YM7YEbqiAKECk+b6dsvc/HRZZu9U0FolL1g=\nsigs.k8s.io/kind v0.31.0/go.mod h1:FSqriGaoTPruiXWfRnUXNykF8r2t+fHtK0P0m1AbGF8=\nsigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=\nsigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=\nsoftware.sslmate.com/src/go-pkcs12 v0.7.1 h1:bxkUPRsvTPNRBZa4M/aSX4PyMOEbq3V8I6hbkG4F4Q8=\nsoftware.sslmate.com/src/go-pkcs12 v0.7.1/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI=\n"
  }
]